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