Fix various SConf bugs. (Christoph Wiedemann)
[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
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
57 def do_nothing(text): pass
58 HelpFunction = do_nothing
59
60 arguments = {}
61 GlobalDict = {}
62 launch_dir = os.path.abspath(os.curdir)
63
64 # global exports set by Export():
65 global_exports = {}
66
67 # chdir flag
68 sconscript_chdir = 1
69
70 # will be set to 1, if we are reading a SConscript
71 sconscript_reading = 0
72
73 def _scons_add_args(alist):
74     global arguments
75     for arg in alist:
76         a, b = string.split(arg, '=', 2)
77         arguments[a] = b
78
79 def get_calling_namespaces():
80     """Return the locals and globals for the function that called
81     into this module in the current callstack."""
82     try: 1/0
83     except: frame = sys.exc_info()[2].tb_frame
84     
85     while frame.f_globals.get("__name__") == __name__: frame = frame.f_back
86
87     return frame.f_locals, frame.f_globals
88
89     
90 def compute_exports(exports):
91     """Compute a dictionary of exports given one of the parameters
92     to the Export() function or the exports argument to SConscript()."""
93
94     loc, glob = get_calling_namespaces()
95
96     retval = {}
97     try:
98         for export in exports:
99             if SCons.Util.is_Dict(export):
100                 retval.update(export)
101             else:
102                 try:
103                     retval[export] = loc[export]
104                 except KeyError:
105                     retval[export] = glob[export]
106     except KeyError, x:
107         raise SCons.Errors.UserError, "Export of non-existant variable '%s'"%x
108
109     return retval
110     
111
112 class Frame:
113     """A frame on the SConstruct/SConscript call stack"""
114     def __init__(self, exports, sconscript):
115         self.globals = BuildDefaultGlobals()
116         self.retval = None 
117         self.prev_dir = SCons.Node.FS.default_fs.getcwd()
118         self.exports = compute_exports(exports)  # exports from the calling SConscript
119         # make sure the sconscript attr is a Node.
120         if isinstance(sconscript, SCons.Node.Node):
121             self.sconscript = sconscript
122         else:
123             self.sconscript = SCons.Node.FS.default_fs.File(str(sconscript))
124         
125 # the SConstruct/SConscript call stack:
126 stack = []
127
128 # For documentation on the methods in this file, see the scons man-page
129
130 def Return(*vars):
131     retval = []
132     try:
133         for var in vars:
134             for v in string.split(var):
135                 retval.append(stack[-1].globals[v])
136     except KeyError, x:
137         raise SCons.Errors.UserError, "Return of non-existant variable '%s'"%x
138         
139     if len(retval) == 1:
140         stack[-1].retval = retval[0]
141     else:
142         stack[-1].retval = tuple(retval)
143
144 def _SConscript(fs, *files, **kw):
145     global sconscript_reading
146     sconscript_reading = 1
147     top = fs.Top
148     sd = fs.SConstruct_dir.rdir()
149     exports = kw.get('exports', [])
150
151     # evaluate each SConscript file
152     results = []
153     for fn in files:
154         stack.append(Frame(exports,fn))
155         old_sys_path = sys.path
156         try:
157             if fn == "-":
158                 exec sys.stdin in stack[-1].globals
159             else:
160                 if isinstance(fn, SCons.Node.Node):
161                     f = fn
162                 else:
163                     f = fs.File(str(fn))
164                 _file_ = None
165
166                 # Change directory to the top of the source
167                 # tree to make sure the os's cwd and the cwd of
168                 # fs match so we can open the SConscript.
169                 fs.chdir(top, change_os_dir=1)
170                 if f.rexists():
171                     _file_ = open(f.rstr(), "r")
172                 elif f.has_src_builder():
173                     # The SConscript file apparently exists in a source
174                     # code management system.  Build it, but then clear
175                     # the builder so that it doesn't get built *again*
176                     # during the actual build phase.
177                     f.build()
178                     f.builder_set(None)
179                     s = str(f)
180                     if os.path.exists(s):
181                         _file_ = open(s, "r")
182                 if _file_:
183                     # Chdir to the SConscript directory.  Use a path
184                     # name relative to the SConstruct file so that if
185                     # we're using the -f option, we're essentially
186                     # creating a parallel SConscript directory structure
187                     # in our local directory tree.
188                     #
189                     # XXX This is broken for multiple-repository cases
190                     # where the SConstruct and SConscript files might be
191                     # in different Repositories.  For now, cross that
192                     # bridge when someone comes to it.
193                     ldir = fs.Dir(f.dir.get_path(sd))
194                     try:
195                         fs.chdir(ldir, change_os_dir=sconscript_chdir)
196                     except OSError:
197                         # There was no local directory, so we should be
198                         # able to chdir to the Repository directory.
199                         # Note that we do this directly, not through
200                         # fs.chdir(), because we still need to
201                         # interpret the stuff within the SConscript file
202                         # relative to where we are logically.
203                         fs.chdir(ldir, change_os_dir=0)
204                         os.chdir(f.rfile().dir.get_abspath())
205
206                     # Append the SConscript directory to the beginning
207                     # of sys.path so Python modules in the SConscript
208                     # directory can be easily imported.
209                     sys.path = [ f.dir.get_abspath() ] + sys.path
210
211                     # This is the magic line that actually reads up and
212                     # executes the stuff in the SConscript file.  We
213                     # look for the "exec _file_ " from the beginning
214                     # of this line to find the right stack frame (the
215                     # next one) describing the SConscript file and line
216                     # number that creates a node.
217                     exec _file_ in stack[-1].globals
218                 else:
219                     sys.stderr.write("Ignoring missing SConscript '%s'\n" %
220                                      f.path)
221                 
222         finally:
223             sconscript_reading = 0
224             sys.path = old_sys_path
225             frame = stack.pop()
226             try:
227                 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
228             except OSError:
229                 # There was no local directory, so chdir to the
230                 # Repository directory.  Like above, we do this
231                 # directly.
232                 fs.chdir(frame.prev_dir, change_os_dir=0)
233                 os.chdir(frame.prev_dir.rdir().get_abspath())
234
235             results.append(frame.retval)
236
237     # if we only have one script, don't return a tuple
238     if len(results) == 1:
239         return results[0]
240     else:
241         return tuple(results)
242
243 def is_our_exec_statement(line):
244     return not line is None and line[:12] == "exec _file_ "
245
246 def SConscript_exception(file=sys.stderr):
247     """Print an exception stack trace just for the SConscript file(s).
248     This will show users who have Python errors where the problem is,
249     without cluttering the output with all of the internal calls leading
250     up to where we exec the SConscript."""
251     stack = traceback.extract_tb(sys.exc_traceback)
252     last_text = ""
253     i = 0
254     for frame in stack:
255         if is_our_exec_statement(last_text):
256             break
257         i = i + 1
258         last_text = frame[3]
259     type = str(sys.exc_type)
260     if type[:11] == "exceptions.":
261         type = type[11:]
262     file.write('%s: %s:\n' % (type, sys.exc_value))
263     for fname, line, func, text in stack[i:]:
264         file.write('  File "%s", line %d:\n' % (fname, line))
265         file.write('    %s\n' % text)
266
267 def annotate(node):
268     """Annotate a node with the stack frame describing the
269     SConscript file and line number that created it."""
270     stack = traceback.extract_stack()
271     last_text = ""
272     for frame in stack:
273         # If the script text of the previous frame begins with the
274         # magic "exec _file_ " string, then this frame describes the
275         # SConscript file and line number that caused this node to be
276         # created.  Record the tuple and carry on.
277         if is_our_exec_statement(last_text):
278             node.creator = frame
279             return
280         last_text = frame[3]
281
282 # The following line would cause each Node to be annotated using the
283 # above function.  Unfortunately, this is a *huge* performance hit, so
284 # leave this disabled until we find a more efficient mechanism.
285 #SCons.Node.Annotate = annotate
286
287 class SConsEnvironment(SCons.Environment.Base):
288     """An Environment subclass that contains all of the methods that
289     are particular to the wrapper SCons interface and which aren't
290     (or shouldn't be) part of the build engine itself.
291
292     Note that not all of the methods of this class have corresponding
293     global functions, there are some private methods.
294     """
295
296     #
297     # Private methods of an SConsEnvironment.
298     #
299
300     def _check_version(self, major, minor, version_string):
301         """Return 0 if 'major' and 'minor' are greater than the version
302         in 'version_string', and 1 otherwise."""
303         try:
304             v_major, v_minor, v_micro, release, serial = sys.version_info
305         except AttributeError:
306             version = string.split(string.split(version_string, ' ')[0], '.')
307             v_major = int(version[0])
308             v_minor = int(re.match('\d+', version[1]).group())
309         if major > v_major or (major == v_major and minor > v_minor):
310             return 0
311         else:
312             return 1
313
314     def _get_SConscript_filenames(self, ls, kw):
315         """
316         Convert the parameters passed to # SConscript() calls into a list
317         of files and export variables.  If the parameters are invalid,
318         throws SCons.Errors.UserError. Returns a tuple (l, e) where l
319         is a list of SConscript filenames and e is a list of exports.
320         """
321         exports = []
322
323         if len(ls) == 0:
324             try:
325                 dirs = kw["dirs"]
326             except KeyError:
327                 raise SCons.Errors.UserError, \
328                       "Invalid SConscript usage - no parameters"
329
330             if not SCons.Util.is_List(dirs):
331                 dirs = [ dirs ]
332             dirs = map(str, dirs)
333
334             name = kw.get('name', 'SConscript')
335
336             files = map(lambda n, name = name: os.path.join(n, name), dirs)
337
338         elif len(ls) == 1:
339
340             files = ls[0]
341
342         elif len(ls) == 2:
343
344             files   = ls[0]
345             exports = self.Split(ls[1])
346
347         else:
348
349             raise SCons.Errors.UserError, \
350                   "Invalid SConscript() usage - too many arguments"
351
352         if not SCons.Util.is_List(files):
353             files = [ files ]
354
355         if kw.get('exports'):
356             exports.extend(self.Split(kw['exports']))
357
358         build_dir = kw.get('build_dir')
359         if build_dir:
360             if len(files) != 1:
361                 raise SCons.Errors.UserError, \
362                     "Invalid SConscript() usage - can only specify one SConscript with a build_dir"
363             duplicate = kw.get('duplicate', 1)
364             src_dir = kw.get('src_dir')
365             if not src_dir:
366                 src_dir, fname = os.path.split(str(files[0]))
367             else:
368                 if not isinstance(src_dir, SCons.Node.Node):
369                     src_dir = self.fs.Dir(src_dir)
370                 fn = files[0]
371                 if not isinstance(fn, SCons.Node.Node):
372                     fn = self.fs.File(fn)
373                 if fn.is_under(src_dir):
374                     # Get path relative to the source directory.
375                     fname = fn.get_path(src_dir)
376                 else:
377                     # Fast way to only get the terminal path component of a Node.
378                     fname = fn.get_path(fn.dir)
379             self.fs.BuildDir(build_dir, src_dir, duplicate)
380             files = [os.path.join(str(build_dir), fname)]
381
382         return (files, exports)
383
384     #
385     # Public methods of an SConsEnvironment.  These get
386     # entry points in the global name space so they can be called
387     # as global functions.
388     #
389
390     def EnsureSConsVersion(self, major, minor):
391         """Exit abnormally if the SCons version is not late enough."""
392         if not self._check_version(major,minor,SCons.__version__):
393             print "SCons %d.%d or greater required, but you have SCons %s" %(major,minor,SCons.__version__)
394             sys.exit(2)
395
396     def EnsurePythonVersion(self, major, minor):
397         """Exit abnormally if the Python version is not late enough."""
398         if not self._check_version(major,minor,sys.version):
399             v = string.split(sys.version, " ", 1)[0]
400             print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
401             sys.exit(2)
402
403     def Exit(self, value=0):
404         sys.exit(value)
405
406     def Export(self, *vars):
407         for var in vars:
408             global_exports.update(compute_exports(self.Split(var)))
409
410     def GetLaunchDir(self):
411         global launch_dir
412         return launch_dir
413
414     def GetOption(self, name):
415         name = self.subst(name)
416         return SCons.Script.ssoptions.get(name)
417
418     def Help(self, text):
419         text = self.subst(text, raw=1)
420         HelpFunction(text)
421
422     def Import(self, *vars):
423         try:
424             for var in vars:
425                 var = self.Split(var)
426                 for v in var:
427                     if v == '*':
428                         stack[-1].globals.update(global_exports)
429                         stack[-1].globals.update(stack[-1].exports)
430                     else:
431                         if stack[-1].exports.has_key(v):
432                             stack[-1].globals[v] = stack[-1].exports[v]
433                         else:
434                             stack[-1].globals[v] = global_exports[v]
435         except KeyError,x:
436             raise SCons.Errors.UserError, "Import of non-existant variable '%s'"%x
437
438     def SConscript(self, *ls, **kw):
439         ls = map(lambda l, self=self: self.subst(l), ls)
440         subst_kw = {}
441         for key, val in kw.items():
442             if SCons.Util.is_String(val):
443                 val = self.subst(val)
444             subst_kw[key] = val
445
446         files, exports = self._get_SConscript_filenames(ls, subst_kw)
447
448         return apply(_SConscript, [self.fs,] + files, {'exports' : exports})
449
450     def SConscriptChdir(self, flag):
451         global sconscript_chdir
452         sconscript_chdir = flag
453
454     def SetOption(self, name, value):
455         name = self.subst(name)
456         SCons.Script.ssoptions.set(name, value)
457
458 #
459 #
460 #
461 SCons.Environment.Environment = SConsEnvironment
462
463 def Options(files=None, args=arguments):
464     return SCons.Options.Options(files, args)
465
466 def SetBuildSignatureType(type):
467     SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
468                         "The SetBuildSignatureType() function has been deprecated;\n" +\
469                         "\tuse the TargetSignatures() function instead.")
470     SCons.Defaults.DefaultEnvironment().TargetSignatures(type)
471
472 def SetContentSignatureType(type):
473     SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
474                         "The SetContentSignatureType() function has been deprecated;\n" +\
475                         "\tuse the SourceSignatures() function instead.")
476     SCons.Defaults.DefaultEnvironment().SourceSignatures(type)
477
478 def GetJobs():
479     SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
480                         "The GetJobs() function has been deprecated;\n" +\
481                         "\tuse GetOption('num_jobs') instead.")
482
483     return GetOption('num_jobs')
484  
485 def SetJobs(num):
486     SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
487                         "The SetJobs() function has been deprecated;\n" +\
488                         "\tuse SetOption('num_jobs', num) instead.")
489     SetOption('num_jobs', num)
490  
491 def ParseConfig(env, command, function=None):
492     SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
493                         "The ParseConfig() function has been deprecated;\n" +\
494                         "\tuse the env.ParseConfig() method instead.")
495     return env.ParseConfig(command, function)
496
497 #
498 _DefaultEnvironmentProxy = None
499
500 def get_DefaultEnvironmentProxy():
501     global _DefaultEnvironmentProxy
502     if not _DefaultEnvironmentProxy:
503         class EnvironmentProxy(SCons.Environment.Environment):
504             """A proxy subclass for an environment instance that overrides
505             the subst() and subst_list() methods so they don't actually
506             actually perform construction variable substitution.  This is
507             specifically intended to be the shim layer in between global
508             function calls (which don't want want construction variable
509             substitution) and the DefaultEnvironment() (which would
510             substitute variables if left to its own devices)."""
511             def __init__(self, subject):
512                 self.__dict__['__subject'] = subject
513             def __getattr__(self, name):
514                 return getattr(self.__dict__['__subject'], name)
515             def __setattr__(self, name, value):
516                 return setattr(self.__dict__['__subject'], name, value)
517             def subst(self, string, raw=0, target=None, source=None):
518                 return string
519             def subst_kw(self, kw, raw=0, target=None, source=None):
520                 return kw
521             def subst_list(self, string, raw=0, target=None, source=None):
522                 return string
523         default_env = SCons.Defaults.DefaultEnvironment()
524         _DefaultEnvironmentProxy = EnvironmentProxy(default_env)
525     return _DefaultEnvironmentProxy
526
527 class DefaultEnvironmentCall:
528     """A class that implements "global function" calls of
529     Environment methods by fetching the specified method from the
530     DefaultEnvironment's class.  Note that this uses an intermediate
531     proxy class instead of calling the DefaultEnvironment method
532     directly so that the proxy can override the subst() method and
533     thereby prevent expansion of construction variables (since from
534     the user's point of view this was called as a global function,
535     with no associated construction environment)."""
536     def __init__(self, method_name):
537         self.method_name = method_name
538     def __call__(self, *args, **kw):
539         proxy = get_DefaultEnvironmentProxy()
540         method = getattr(proxy, self.method_name)
541         return apply(method, args, kw)
542
543 # The list of global functions to add to the SConscript name space
544 # that end up calling corresponding methods or Builders in the
545 # DefaultEnvironment().
546 GlobalDefaultEnvironmentFunctions = [
547     # Methods from the SConsEnvironment class, above.
548     'EnsurePythonVersion',
549     'EnsureSConsVersion',
550     'Exit',
551     'Export',
552     'GetLaunchDir',
553     'GetOption',
554     'Help',
555     'Import',
556     'SConscript',
557     'SConscriptChdir',
558     'SetOption',
559
560     # Methods from the Environment.Base class.
561     'Action',
562     'AddPostAction',
563     'AddPreAction',
564     'Alias',
565     'AlwaysBuild',
566     'BuildDir',
567     'Builder',
568     'CacheDir',
569     'Clean',
570     'Command',
571     'Default',
572     'Depends',
573     'Dir',
574     'Environment',
575     'File',
576     'FindFile',
577     'GetBuildPath',
578     'Ignore',
579     'Install',
580     'InstallAs',
581     'Literal',
582     'Local',
583     'Precious',
584     'Repository',
585     'Scanner',
586     'SConsignFile',
587     'SideEffect',
588     'SourceCode',
589     'SourceSignatures',
590     'Split',
591     'TargetSignatures',
592     'Value',
593 ]
594
595 GlobalDefaultBuilders = [
596     # Supported builders.
597     'CFile',
598     'CXXFile',
599     'DVI',
600     'Jar',
601     'Java',
602     'JavaH',
603     'Library',
604     'M4',
605     'MSVSProject',
606     'Object',
607     'PCH',
608     'PDF',
609     'PostScript',
610     'Program',
611     'RES',
612     'RMIC',
613     'SharedLibrary',
614     'SharedObject',
615     'StaticLibrary',
616     'StaticObject',
617     'Tar',
618     'TypeLibrary',
619     'Zip',
620 ]
621
622 for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders:
623     GlobalDict[name] = DefaultEnvironmentCall(name)
624
625 def BuildDefaultGlobals():
626     """
627     Create a dictionary containing all the default globals for 
628     SConstruct and SConscript files.
629     """
630
631     globals = {}
632     globals['ARGUMENTS']         = arguments
633     globals['Configure']         = SCons.SConf.SConf
634     globals['Options']           = Options
635     globals['Platform']          = SCons.Platform.Platform
636     globals['Return']            = Return
637     globals['Tool']              = SCons.Tool.Tool
638     globals['WhereIs']           = SCons.Util.WhereIs
639
640     # Functions we're in the process of converting to Environment methods.
641     globals['CScan']             = SCons.Defaults.CScan
642     globals['DefaultEnvironment'] = SCons.Defaults.DefaultEnvironment
643
644     # Deprecated functions, leave these here for now.
645     globals['GetJobs']           = GetJobs
646     globals['ParseConfig']       = ParseConfig
647     globals['SetBuildSignatureType'] = SetBuildSignatureType
648     globals['SetContentSignatureType'] = SetContentSignatureType
649     globals['SetJobs']           = SetJobs
650
651     globals.update(GlobalDict)
652
653     return globals