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