Toplevel directory cleanup
[senf.git] / tools / scons-1.2.0 / engine / SCons / Script / SConscript.py
1 """SCons.Script.SConscript
2
3 This module defines the Python API provided to SConscript and SConstruct
4 files.
5
6 """
7
8 #
9 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
10 #
11 # Permission is hereby granted, free of charge, to any person obtaining
12 # a copy of this software and associated documentation files (the
13 # "Software"), to deal in the Software without restriction, including
14 # without limitation the rights to use, copy, modify, merge, publish,
15 # distribute, sublicense, and/or sell copies of the Software, and to
16 # permit persons to whom the Software is furnished to do so, subject to
17 # the following conditions:
18 #
19 # The above copyright notice and this permission notice shall be included
20 # in all copies or substantial portions of the Software.
21 #
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
23 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
24 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 #
30
31 __revision__ = "src/engine/SCons/Script/SConscript.py 3842 2008/12/20 22:59:52 scons"
32
33 import SCons
34 import SCons.Action
35 import SCons.Builder
36 import SCons.Defaults
37 import SCons.Environment
38 import SCons.Errors
39 import SCons.Node
40 import SCons.Node.Alias
41 import SCons.Node.FS
42 import SCons.Platform
43 import SCons.SConf
44 import SCons.Script.Main
45 import SCons.Tool
46 import SCons.Util
47
48 import os
49 import os.path
50 import re
51 import string
52 import sys
53 import traceback
54 import types
55 import UserList
56
57 # The following variables used to live in this module.  Some
58 # SConscript files out there may have referred to them directly as
59 # SCons.Script.SConscript.*.  This is now supported by some special
60 # handling towards the bottom of the SConscript.__init__.py module.
61 #Arguments = {}
62 #ArgList = []
63 #BuildTargets = TargetList()
64 #CommandLineTargets = []
65 #DefaultTargets = []
66
67 class SConscriptReturn(Exception):
68     pass
69
70 launch_dir = os.path.abspath(os.curdir)
71
72 GlobalDict = None
73
74 # global exports set by Export():
75 global_exports = {}
76
77 # chdir flag
78 sconscript_chdir = 1
79
80 def get_calling_namespaces():
81     """Return the locals and globals for the function that called
82     into this module in the current call stack."""
83     try: 1/0
84     except ZeroDivisionError: 
85         # Don't start iterating with the current stack-frame to
86         # prevent creating reference cycles (f_back is safe).
87         frame = sys.exc_info()[2].tb_frame.f_back
88
89     # Find the first frame that *isn't* from this file.  This means
90     # that we expect all of the SCons frames that implement an Export()
91     # or SConscript() call to be in this file, so that we can identify
92     # the first non-Script.SConscript frame as the user's local calling
93     # environment, and the locals and globals dictionaries from that
94     # frame as the calling namespaces.  See the comment below preceding
95     # the DefaultEnvironmentCall block for even more explanation.
96     while frame.f_globals.get("__name__") == __name__:
97         frame = frame.f_back
98
99     return frame.f_locals, frame.f_globals
100
101
102 def compute_exports(exports):
103     """Compute a dictionary of exports given one of the parameters
104     to the Export() function or the exports argument to SConscript()."""
105
106     loc, glob = get_calling_namespaces()
107
108     retval = {}
109     try:
110         for export in exports:
111             if SCons.Util.is_Dict(export):
112                 retval.update(export)
113             else:
114                 try:
115                     retval[export] = loc[export]
116                 except KeyError:
117                     retval[export] = glob[export]
118     except KeyError, x:
119         raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x
120
121     return retval
122
123 class Frame:
124     """A frame on the SConstruct/SConscript call stack"""
125     def __init__(self, fs, exports, sconscript):
126         self.globals = BuildDefaultGlobals()
127         self.retval = None
128         self.prev_dir = fs.getcwd()
129         self.exports = compute_exports(exports)  # exports from the calling SConscript
130         # make sure the sconscript attr is a Node.
131         if isinstance(sconscript, SCons.Node.Node):
132             self.sconscript = sconscript
133         elif sconscript == '-':
134             self.sconscript = None
135         else:
136             self.sconscript = fs.File(str(sconscript))
137
138 # the SConstruct/SConscript call stack:
139 call_stack = []
140
141 # For documentation on the methods in this file, see the scons man-page
142
143 def Return(*vars, **kw):
144     retval = []
145     try:
146         fvars = SCons.Util.flatten(vars)
147         for var in fvars:
148             for v in string.split(var):
149                 retval.append(call_stack[-1].globals[v])
150     except KeyError, x:
151         raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x
152
153     if len(retval) == 1:
154         call_stack[-1].retval = retval[0]
155     else:
156         call_stack[-1].retval = tuple(retval)
157
158     stop = kw.get('stop', True)
159
160     if stop:
161         raise SConscriptReturn
162
163
164 stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
165
166 def _SConscript(fs, *files, **kw):
167     top = fs.Top
168     sd = fs.SConstruct_dir.rdir()
169     exports = kw.get('exports', [])
170
171     # evaluate each SConscript file
172     results = []
173     for fn in files:
174         call_stack.append(Frame(fs, exports, fn))
175         old_sys_path = sys.path
176         try:
177             SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
178             if fn == "-":
179                 exec sys.stdin in call_stack[-1].globals
180             else:
181                 if isinstance(fn, SCons.Node.Node):
182                     f = fn
183                 else:
184                     f = fs.File(str(fn))
185                 _file_ = None
186
187                 # Change directory to the top of the source
188                 # tree to make sure the os's cwd and the cwd of
189                 # fs match so we can open the SConscript.
190                 fs.chdir(top, change_os_dir=1)
191                 if f.rexists():
192                     _file_ = open(f.rfile().get_abspath(), "r")
193                 elif f.has_src_builder():
194                     # The SConscript file apparently exists in a source
195                     # code management system.  Build it, but then clear
196                     # the builder so that it doesn't get built *again*
197                     # during the actual build phase.
198                     f.build()
199                     f.built()
200                     f.builder_set(None)
201                     if f.exists():
202                         _file_ = open(f.get_abspath(), "r")
203                 if _file_:
204                     # Chdir to the SConscript directory.  Use a path
205                     # name relative to the SConstruct file so that if
206                     # we're using the -f option, we're essentially
207                     # creating a parallel SConscript directory structure
208                     # in our local directory tree.
209                     #
210                     # XXX This is broken for multiple-repository cases
211                     # where the SConstruct and SConscript files might be
212                     # in different Repositories.  For now, cross that
213                     # bridge when someone comes to it.
214                     try:
215                         src_dir = kw['src_dir']
216                     except KeyError:
217                         ldir = fs.Dir(f.dir.get_path(sd))
218                     else:
219                         ldir = fs.Dir(src_dir)
220                         if not ldir.is_under(f.dir):
221                             # They specified a source directory, but
222                             # it's above the SConscript directory.
223                             # Do the sensible thing and just use the
224                             # SConcript directory.
225                             ldir = fs.Dir(f.dir.get_path(sd))
226                     try:
227                         fs.chdir(ldir, change_os_dir=sconscript_chdir)
228                     except OSError:
229                         # There was no local directory, so we should be
230                         # able to chdir to the Repository directory.
231                         # Note that we do this directly, not through
232                         # fs.chdir(), because we still need to
233                         # interpret the stuff within the SConscript file
234                         # relative to where we are logically.
235                         fs.chdir(ldir, change_os_dir=0)
236                         # TODO Not sure how to handle src_dir here
237                         os.chdir(f.rfile().dir.get_abspath())
238
239                     # Append the SConscript directory to the beginning
240                     # of sys.path so Python modules in the SConscript
241                     # directory can be easily imported.
242                     sys.path = [ f.dir.get_abspath() ] + sys.path
243
244                     # This is the magic line that actually reads up
245                     # and executes the stuff in the SConscript file.
246                     # The locals for this frame contain the special
247                     # bottom-of-the-stack marker so that any
248                     # exceptions that occur when processing this
249                     # SConscript can base the printed frames at this
250                     # level and not show SCons internals as well.
251                     call_stack[-1].globals.update({stack_bottom:1})
252                     old_file = call_stack[-1].globals.get('__file__')
253                     try:
254                         del call_stack[-1].globals['__file__']
255                     except KeyError:
256                         pass
257                     try:
258                         try:
259                             exec _file_ in call_stack[-1].globals
260                         except SConscriptReturn:
261                             pass
262                     finally:
263                         if old_file is not None:
264                             call_stack[-1].globals.update({__file__:old_file})
265                 else:
266                     SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
267                              "Ignoring missing SConscript '%s'" % f.path)
268
269         finally:
270             SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
271             sys.path = old_sys_path
272             frame = call_stack.pop()
273             try:
274                 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
275             except OSError:
276                 # There was no local directory, so chdir to the
277                 # Repository directory.  Like above, we do this
278                 # directly.
279                 fs.chdir(frame.prev_dir, change_os_dir=0)
280                 rdir = frame.prev_dir.rdir()
281                 rdir._create()  # Make sure there's a directory there.
282                 try:
283                     os.chdir(rdir.get_abspath())
284                 except OSError, e:
285                     # We still couldn't chdir there, so raise the error,
286                     # but only if actions are being executed.
287                     #
288                     # If the -n option was used, the directory would *not*
289                     # have been created and we should just carry on and
290                     # let things muddle through.  This isn't guaranteed
291                     # to work if the SConscript files are reading things
292                     # from disk (for example), but it should work well
293                     # enough for most configurations.
294                     if SCons.Action.execute_actions:
295                         raise e
296
297             results.append(frame.retval)
298
299     # if we only have one script, don't return a tuple
300     if len(results) == 1:
301         return results[0]
302     else:
303         return tuple(results)
304
305 def SConscript_exception(file=sys.stderr):
306     """Print an exception stack trace just for the SConscript file(s).
307     This will show users who have Python errors where the problem is,
308     without cluttering the output with all of the internal calls leading
309     up to where we exec the SConscript."""
310     exc_type, exc_value, exc_tb = sys.exc_info()
311     tb = exc_tb
312     while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
313         tb = tb.tb_next
314     if not tb:
315         # We did not find our exec statement, so this was actually a bug
316         # in SCons itself.  Show the whole stack.
317         tb = exc_tb
318     stack = traceback.extract_tb(tb)
319     try:
320         type = exc_type.__name__
321     except AttributeError:
322         type = str(exc_type)
323         if type[:11] == "exceptions.":
324             type = type[11:]
325     file.write('%s: %s:\n' % (type, exc_value))
326     for fname, line, func, text in stack:
327         file.write('  File "%s", line %d:\n' % (fname, line))
328         file.write('    %s\n' % text)
329
330 def annotate(node):
331     """Annotate a node with the stack frame describing the
332     SConscript file and line number that created it."""
333     tb = sys.exc_info()[2]
334     while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
335         tb = tb.tb_next
336     if not tb:
337         # We did not find any exec of an SConscript file: what?!
338         raise SCons.Errors.InternalError, "could not find SConscript stack frame"
339     node.creator = traceback.extract_stack(tb)[0]
340
341 # The following line would cause each Node to be annotated using the
342 # above function.  Unfortunately, this is a *huge* performance hit, so
343 # leave this disabled until we find a more efficient mechanism.
344 #SCons.Node.Annotate = annotate
345
346 class SConsEnvironment(SCons.Environment.Base):
347     """An Environment subclass that contains all of the methods that
348     are particular to the wrapper SCons interface and which aren't
349     (or shouldn't be) part of the build engine itself.
350
351     Note that not all of the methods of this class have corresponding
352     global functions, there are some private methods.
353     """
354
355     #
356     # Private methods of an SConsEnvironment.
357     #
358     def _exceeds_version(self, major, minor, v_major, v_minor):
359         """Return 1 if 'major' and 'minor' are greater than the version
360         in 'v_major' and 'v_minor', and 0 otherwise."""
361         return (major > v_major or (major == v_major and minor > v_minor))
362
363     def _get_major_minor_revision(self, version_string):
364         """Split a version string into major, minor and (optionally)
365         revision parts.
366
367         This is complicated by the fact that a version string can be
368         something like 3.2b1."""
369         version = string.split(string.split(version_string, ' ')[0], '.')
370         v_major = int(version[0])
371         v_minor = int(re.match('\d+', version[1]).group())
372         if len(version) >= 3:
373             v_revision = int(re.match('\d+', version[2]).group())
374         else:
375             v_revision = 0
376         return v_major, v_minor, v_revision
377
378     def _get_SConscript_filenames(self, ls, kw):
379         """
380         Convert the parameters passed to # SConscript() calls into a list
381         of files and export variables.  If the parameters are invalid,
382         throws SCons.Errors.UserError. Returns a tuple (l, e) where l
383         is a list of SConscript filenames and e is a list of exports.
384         """
385         exports = []
386
387         if len(ls) == 0:
388             try:
389                 dirs = kw["dirs"]
390             except KeyError:
391                 raise SCons.Errors.UserError, \
392                       "Invalid SConscript usage - no parameters"
393
394             if not SCons.Util.is_List(dirs):
395                 dirs = [ dirs ]
396             dirs = map(str, dirs)
397
398             name = kw.get('name', 'SConscript')
399
400             files = map(lambda n, name = name: os.path.join(n, name), dirs)
401
402         elif len(ls) == 1:
403
404             files = ls[0]
405
406         elif len(ls) == 2:
407
408             files   = ls[0]
409             exports = self.Split(ls[1])
410
411         else:
412
413             raise SCons.Errors.UserError, \
414                   "Invalid SConscript() usage - too many arguments"
415
416         if not SCons.Util.is_List(files):
417             files = [ files ]
418
419         if kw.get('exports'):
420             exports.extend(self.Split(kw['exports']))
421
422         variant_dir = kw.get('variant_dir') or kw.get('build_dir')
423         if variant_dir:
424             if len(files) != 1:
425                 raise SCons.Errors.UserError, \
426                     "Invalid SConscript() usage - can only specify one SConscript with a variant_dir"
427             duplicate = kw.get('duplicate', 1)
428             src_dir = kw.get('src_dir')
429             if not src_dir:
430                 src_dir, fname = os.path.split(str(files[0]))
431                 files = [os.path.join(str(variant_dir), fname)]
432             else:
433                 if not isinstance(src_dir, SCons.Node.Node):
434                     src_dir = self.fs.Dir(src_dir)
435                 fn = files[0]
436                 if not isinstance(fn, SCons.Node.Node):
437                     fn = self.fs.File(fn)
438                 if fn.is_under(src_dir):
439                     # Get path relative to the source directory.
440                     fname = fn.get_path(src_dir)
441                     files = [os.path.join(str(variant_dir), fname)]
442                 else:
443                     files = [fn.abspath]
444                 kw['src_dir'] = variant_dir
445             self.fs.VariantDir(variant_dir, src_dir, duplicate)
446
447         return (files, exports)
448
449     #
450     # Public methods of an SConsEnvironment.  These get
451     # entry points in the global name space so they can be called
452     # as global functions.
453     #
454
455     def Configure(self, *args, **kw):
456         if not SCons.Script.sconscript_reading:
457             raise SCons.Errors.UserError, "Calling Configure from Builders is not supported."
458         kw['_depth'] = kw.get('_depth', 0) + 1
459         return apply(SCons.Environment.Base.Configure, (self,)+args, kw)
460
461     def Default(self, *targets):
462         SCons.Script._Set_Default_Targets(self, targets)
463
464     def EnsureSConsVersion(self, major, minor, revision=0):
465         """Exit abnormally if the SCons version is not late enough."""
466         scons_ver = self._get_major_minor_revision(SCons.__version__)
467         if scons_ver < (major, minor, revision):
468             if revision:
469                 scons_ver_string = '%d.%d.%d' % (major, minor, revision)
470             else:
471                 scons_ver_string = '%d.%d' % (major, minor)
472             print "SCons %s or greater required, but you have SCons %s" % \
473                   (scons_ver_string, SCons.__version__)
474             sys.exit(2)
475
476     def EnsurePythonVersion(self, major, minor):
477         """Exit abnormally if the Python version is not late enough."""
478         try:
479             v_major, v_minor, v_micro, release, serial = sys.version_info
480             python_ver = (v_major, v_minor)
481         except AttributeError:
482             python_ver = self._get_major_minor_revision(sys.version)[:2]
483         if python_ver < (major, minor):
484             v = string.split(sys.version, " ", 1)[0]
485             print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
486             sys.exit(2)
487
488     def Exit(self, value=0):
489         sys.exit(value)
490
491     def Export(self, *vars):
492         for var in vars:
493             global_exports.update(compute_exports(self.Split(var)))
494
495     def GetLaunchDir(self):
496         global launch_dir
497         return launch_dir
498
499     def GetOption(self, name):
500         name = self.subst(name)
501         return SCons.Script.Main.GetOption(name)
502
503     def Help(self, text):
504         text = self.subst(text, raw=1)
505         SCons.Script.HelpFunction(text)
506
507     def Import(self, *vars):
508         try:
509             frame = call_stack[-1]
510             globals = frame.globals
511             exports = frame.exports
512             for var in vars:
513                 var = self.Split(var)
514                 for v in var:
515                     if v == '*':
516                         globals.update(global_exports)
517                         globals.update(exports)
518                     else:
519                         if exports.has_key(v):
520                             globals[v] = exports[v]
521                         else:
522                             globals[v] = global_exports[v]
523         except KeyError,x:
524             raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
525
526     def SConscript(self, *ls, **kw):
527         def subst_element(x, subst=self.subst):
528             if SCons.Util.is_List(x):
529                 x = map(subst, x)
530             else:
531                 x = subst(x)
532             return x
533         ls = map(subst_element, ls)
534         subst_kw = {}
535         for key, val in kw.items():
536             if SCons.Util.is_String(val):
537                 val = self.subst(val)
538             elif SCons.Util.is_List(val):
539                 result = []
540                 for v in val:
541                     if SCons.Util.is_String(v):
542                         v = self.subst(v)
543                     result.append(v)
544                 val = result
545             subst_kw[key] = val
546
547         files, exports = self._get_SConscript_filenames(ls, subst_kw)
548         subst_kw['exports'] = exports
549         return apply(_SConscript, [self.fs,] + files, subst_kw)
550
551     def SConscriptChdir(self, flag):
552         global sconscript_chdir
553         sconscript_chdir = flag
554
555     def SetOption(self, name, value):
556         name = self.subst(name)
557         SCons.Script.Main.SetOption(name, value)
558
559 #
560 #
561 #
562 SCons.Environment.Environment = SConsEnvironment
563
564 def Configure(*args, **kw):
565     if not SCons.Script.sconscript_reading:
566         raise SCons.Errors.UserError, "Calling Configure from Builders is not supported."
567     kw['_depth'] = 1
568     return apply(SCons.SConf.SConf, args, kw)
569
570 # It's very important that the DefaultEnvironmentCall() class stay in this
571 # file, with the get_calling_namespaces() function, the compute_exports()
572 # function, the Frame class and the SConsEnvironment.Export() method.
573 # These things make up the calling stack leading up to the actual global
574 # Export() or SConscript() call that the user issued.  We want to allow
575 # users to export local variables that they define, like so:
576 #
577 #       def func():
578 #           x = 1
579 #           Export('x')
580 #
581 # To support this, the get_calling_namespaces() function assumes that
582 # the *first* stack frame that's not from this file is the local frame
583 # for the Export() or SConscript() call.
584
585 _DefaultEnvironmentProxy = None
586
587 def get_DefaultEnvironmentProxy():
588     global _DefaultEnvironmentProxy
589     if not _DefaultEnvironmentProxy:
590         default_env = SCons.Defaults.DefaultEnvironment()
591         _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
592     return _DefaultEnvironmentProxy
593
594 class DefaultEnvironmentCall:
595     """A class that implements "global function" calls of
596     Environment methods by fetching the specified method from the
597     DefaultEnvironment's class.  Note that this uses an intermediate
598     proxy class instead of calling the DefaultEnvironment method
599     directly so that the proxy can override the subst() method and
600     thereby prevent expansion of construction variables (since from
601     the user's point of view this was called as a global function,
602     with no associated construction environment)."""
603     def __init__(self, method_name, subst=0):
604         self.method_name = method_name
605         if subst:
606             self.factory = SCons.Defaults.DefaultEnvironment
607         else:
608             self.factory = get_DefaultEnvironmentProxy
609     def __call__(self, *args, **kw):
610         env = self.factory()
611         method = getattr(env, self.method_name)
612         return apply(method, args, kw)
613
614
615 def BuildDefaultGlobals():
616     """
617     Create a dictionary containing all the default globals for
618     SConstruct and SConscript files.
619     """
620
621     global GlobalDict
622     if GlobalDict is None:
623         GlobalDict = {}
624
625         import SCons.Script
626         d = SCons.Script.__dict__
627         def not_a_module(m, d=d, mtype=type(SCons.Script)):
628              return type(d[m]) != mtype
629         for m in filter(not_a_module, dir(SCons.Script)):
630              GlobalDict[m] = d[m]
631
632     return GlobalDict.copy()