1 """SCons.Script.SConscript
2
3 This module defines the Python API provided to SConscript and SConstruct
4 files.
5
6 """
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 __revision__ = "src/engine/SCons/Script/SConscript.py 2928 2008/04/29 22:44:09 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
58
59
60
61
62
63
64
65
66
69
70 launch_dir = os.path.abspath(os.curdir)
71
72 GlobalDict = None
73
74
75 global_exports = {}
76
77
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
87
88
89
90
91
92
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"""
122 - def __init__(self, fs, exports, sconscript):
123 self.globals = BuildDefaultGlobals()
124 self.retval = None
125 self.prev_dir = fs.getcwd()
126 self.exports = compute_exports(exports)
127
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
136 call_stack = []
137
138
139
159
160
161 stack_bottom = '% Stack boTTom %'
162
164 top = fs.Top
165 sd = fs.SConstruct_dir.rdir()
166 exports = kw.get('exports', [])
167
168
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
185
186
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
192
193
194
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
202
203
204
205
206
207
208
209
210
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
219
220
221
222 ldir = fs.Dir(f.dir.get_path(sd))
223 try:
224 fs.chdir(ldir, change_os_dir=sconscript_chdir)
225 except OSError:
226
227
228
229
230
231
232 fs.chdir(ldir, change_os_dir=0)
233
234 os.chdir(f.rfile().dir.get_abspath())
235
236
237
238
239 sys.path = [ f.dir.get_abspath() ] + sys.path
240
241
242
243
244
245
246
247
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
274
275
276 fs.chdir(frame.prev_dir, change_os_dir=0)
277 rdir = frame.prev_dir.rdir()
278 rdir._create()
279 os.chdir(rdir.get_abspath())
280
281 results.append(frame.retval)
282
283
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
300
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
322 raise SCons.Errors.InternalError, "could not find SConscript stack frame"
323 node.creator = traceback.extract_stack(tb)[0]
324
325
326
327
328
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
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
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
435
436
437
438
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
472 - def Exit(self, value=0):
474
478
482
486
487 - def Help(self, text):
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
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
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569 _DefaultEnvironmentProxy = None
570
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)."""
587 - def __init__(self, method_name, subst=0):
594 env = self.factory()
595 method = getattr(env, self.method_name)
596 return apply(method, args, kw)
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