http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Platform / __init__.py
1 """SCons.Platform
2
3 SCons platform selection.
4
5 This looks for modules that define a callable object that can modify a
6 construction environment as appropriate for a given platform.
7
8 Note that we take a more simplistic view of "platform" than Python does.
9 We're looking for a single string that determines a set of
10 tool-independent variables with which to initialize a construction
11 environment.  Consequently, we'll examine both sys.platform and os.name
12 (and anything else that might come in to play) in order to return some
13 specification which is unique enough for our purposes.
14
15 Note that because this subsysem just *selects* a callable that can
16 modify a construction environment, it's possible for people to define
17 their own "platform specification" in an arbitrary callable function.
18 No one needs to use or tie in to this subsystem in order to roll
19 their own platform definition.
20 """
21
22 #
23 # __COPYRIGHT__
24
25 # Permission is hereby granted, free of charge, to any person obtaining
26 # a copy of this software and associated documentation files (the
27 # "Software"), to deal in the Software without restriction, including
28 # without limitation the rights to use, copy, modify, merge, publish,
29 # distribute, sublicense, and/or sell copies of the Software, and to
30 # permit persons to whom the Software is furnished to do so, subject to
31 # the following conditions:
32 #
33 # The above copyright notice and this permission notice shall be included
34 # in all copies or substantial portions of the Software.
35 #
36 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
37 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
38 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
39 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
40 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
41 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
42 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 #
44
45 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
46
47 import SCons.compat
48
49 import imp
50 import os
51 import sys
52 import tempfile
53
54 import SCons.Errors
55 import SCons.Subst
56 import SCons.Tool
57
58 def platform_default():
59     """Return the platform string for our execution environment.
60
61     The returned value should map to one of the SCons/Platform/*.py
62     files.  Since we're architecture independent, though, we don't
63     care about the machine architecture.
64     """
65     osname = os.name
66     if osname == 'java':
67         osname = os._osType
68     if osname == 'posix':
69         if sys.platform == 'cygwin':
70             return 'cygwin'
71         elif sys.platform.find('irix') != -1:
72             return 'irix'
73         elif sys.platform.find('sunos') != -1:
74             return 'sunos'
75         elif sys.platform.find('hp-ux') != -1:
76             return 'hpux'
77         elif sys.platform.find('aix') != -1:
78             return 'aix'
79         elif sys.platform.find('darwin') != -1:
80             return 'darwin'
81         else:
82             return 'posix'
83     elif os.name == 'os2':
84         return 'os2'
85     else:
86         return sys.platform
87
88 def platform_module(name = platform_default()):
89     """Return the imported module for the platform.
90
91     This looks for a module name that matches the specified argument.
92     If the name is unspecified, we fetch the appropriate default for
93     our execution environment.
94     """
95     full_name = 'SCons.Platform.' + name
96     if full_name not in sys.modules:
97         if os.name == 'java':
98             eval(full_name)
99         else:
100             try:
101                 file, path, desc = imp.find_module(name,
102                                         sys.modules['SCons.Platform'].__path__)
103                 try:
104                     mod = imp.load_module(full_name, file, path, desc)
105                 finally:
106                     if file:
107                         file.close()
108             except ImportError:
109                 try:
110                     import zipimport
111                     importer = zipimport.zipimporter( sys.modules['SCons.Platform'].__path__[0] )
112                     mod = importer.load_module(full_name)
113                 except ImportError:
114                     raise SCons.Errors.UserError("No platform named '%s'" % name)
115             setattr(SCons.Platform, name, mod)
116     return sys.modules[full_name]
117
118 def DefaultToolList(platform, env):
119     """Select a default tool list for the specified platform.
120     """
121     return SCons.Tool.tool_list(platform, env)
122
123 class PlatformSpec:
124     def __init__(self, name):
125         self.name = name
126
127     def __str__(self):
128         return self.name
129         
130 class TempFileMunge:
131     """A callable class.  You can set an Environment variable to this,
132     then call it with a string argument, then it will perform temporary
133     file substitution on it.  This is used to circumvent the long command
134     line limitation.
135
136     Example usage:
137     env["TEMPFILE"] = TempFileMunge
138     env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES')}"
139
140     By default, the name of the temporary file used begins with a
141     prefix of '@'.  This may be configred for other tool chains by
142     setting '$TEMPFILEPREFIX'.
143
144     env["TEMPFILEPREFIX"] = '-@'        # diab compiler
145     env["TEMPFILEPREFIX"] = '-via'      # arm tool chain
146     """
147     def __init__(self, cmd):
148         self.cmd = cmd
149
150     def __call__(self, target, source, env, for_signature):
151         if for_signature:
152             # If we're being called for signature calculation, it's
153             # because we're being called by the string expansion in
154             # Subst.py, which has the logic to strip any $( $) that
155             # may be in the command line we squirreled away.  So we
156             # just return the raw command line and let the upper
157             # string substitution layers do their thing.
158             return self.cmd
159
160         # Now we're actually being called because someone is actually
161         # going to try to execute the command, so we have to do our
162         # own expansion.
163         cmd = env.subst_list(self.cmd, SCons.Subst.SUBST_CMD, target, source)[0]
164         try:
165             maxline = int(env.subst('$MAXLINELENGTH'))
166         except ValueError:
167             maxline = 2048
168
169         if (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= maxline:
170             return self.cmd
171
172         # We do a normpath because mktemp() has what appears to be
173         # a bug in Windows that will use a forward slash as a path
174         # delimiter.  Windows's link mistakes that for a command line
175         # switch and barfs.
176         #
177         # We use the .lnk suffix for the benefit of the Phar Lap
178         # linkloc linker, which likes to append an .lnk suffix if
179         # none is given.
180         (fd, tmp) = tempfile.mkstemp('.lnk', text=True)
181         native_tmp = SCons.Util.get_native_path(os.path.normpath(tmp))
182
183         if env['SHELL'] and env['SHELL'] == 'sh':
184             # The sh shell will try to escape the backslashes in the
185             # path, so unescape them.
186             native_tmp = native_tmp.replace('\\', r'\\\\')
187             # In Cygwin, we want to use rm to delete the temporary
188             # file, because del does not exist in the sh shell.
189             rm = env.Detect('rm') or 'del'
190         else:
191             # Don't use 'rm' if the shell is not sh, because rm won't
192             # work with the Windows shells (cmd.exe or command.com) or
193             # Windows path names.
194             rm = 'del'
195
196         prefix = env.subst('$TEMPFILEPREFIX')
197         if not prefix:
198             prefix = '@'
199
200         args = list(map(SCons.Subst.quote_spaces, cmd[1:]))
201         os.write(fd, " ".join(args) + "\n")
202         os.close(fd)
203         # XXX Using the SCons.Action.print_actions value directly
204         # like this is bogus, but expedient.  This class should
205         # really be rewritten as an Action that defines the
206         # __call__() and strfunction() methods and lets the
207         # normal action-execution logic handle whether or not to
208         # print/execute the action.  The problem, though, is all
209         # of that is decided before we execute this method as
210         # part of expanding the $TEMPFILE construction variable.
211         # Consequently, refactoring this will have to wait until
212         # we get more flexible with allowing Actions to exist
213         # independently and get strung together arbitrarily like
214         # Ant tasks.  In the meantime, it's going to be more
215         # user-friendly to not let obsession with architectural
216         # purity get in the way of just being helpful, so we'll
217         # reach into SCons.Action directly.
218         if SCons.Action.print_actions:
219             print("Using tempfile "+native_tmp+" for command line:\n"+
220                   str(cmd[0]) + " " + " ".join(args))
221         return [ cmd[0], prefix + native_tmp + '\n' + rm, native_tmp ]
222     
223 def Platform(name = platform_default()):
224     """Select a canned Platform specification.
225     """
226     module = platform_module(name)
227     spec = PlatformSpec(name)
228     spec.__call__ = module.generate
229     return spec
230
231 # Local Variables:
232 # tab-width:4
233 # indent-tabs-mode:nil
234 # End:
235 # vim: set expandtab tabstop=4 shiftwidth=4: