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 5110 2010/07/25 16:14:38 bdeegan"
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:
85
86
87 frame = sys.exc_info()[2].tb_frame.f_back
88
89
90
91
92
93
94
95
96 while frame.f_globals.get("__name__") == __name__:
97 frame = frame.f_back
98
99 return frame.f_locals, frame.f_globals
100
101
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
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)
130
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
139 call_stack = []
140
141
142
162
163
164 stack_bottom = '% Stack boTTom %'
165
167 top = fs.Top
168 sd = fs.SConstruct_dir.rdir()
169 exports = kw.get('exports', [])
170
171
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
188
189
190 fs.chdir(top, change_os_dir=1)
191 if f.rexists():
192 actual = f.rfile()
193 _file_ = open(actual.get_abspath(), "r")
194 elif f.srcnode().rexists():
195 actual = f.srcnode().rfile()
196 _file_ = open(actual.get_abspath(), "r")
197 elif f.has_src_builder():
198
199
200
201
202 f.build()
203 f.built()
204 f.builder_set(None)
205 if f.exists():
206 _file_ = open(f.get_abspath(), "r")
207 if _file_:
208
209
210
211
212
213
214
215
216
217
218 try:
219 src_dir = kw['src_dir']
220 except KeyError:
221 ldir = fs.Dir(f.dir.get_path(sd))
222 else:
223 ldir = fs.Dir(src_dir)
224 if not ldir.is_under(f.dir):
225
226
227
228
229 ldir = fs.Dir(f.dir.get_path(sd))
230 try:
231 fs.chdir(ldir, change_os_dir=sconscript_chdir)
232 except OSError:
233
234
235
236
237
238
239 fs.chdir(ldir, change_os_dir=0)
240 os.chdir(actual.dir.get_abspath())
241
242
243
244
245 sys.path = [ f.dir.get_abspath() ] + sys.path
246
247
248
249
250
251
252
253
254 call_stack[-1].globals.update({stack_bottom:1})
255 old_file = call_stack[-1].globals.get('__file__')
256 try:
257 del call_stack[-1].globals['__file__']
258 except KeyError:
259 pass
260 try:
261 try:
262 exec _file_ in call_stack[-1].globals
263 except SConscriptReturn:
264 pass
265 finally:
266 if old_file is not None:
267 call_stack[-1].globals.update({__file__:old_file})
268 else:
269 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
270 "Ignoring missing SConscript '%s'" % f.path)
271
272 finally:
273 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
274 sys.path = old_sys_path
275 frame = call_stack.pop()
276 try:
277 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
278 except OSError:
279
280
281
282 fs.chdir(frame.prev_dir, change_os_dir=0)
283 rdir = frame.prev_dir.rdir()
284 rdir._create()
285 try:
286 os.chdir(rdir.get_abspath())
287 except OSError, e:
288
289
290
291
292
293
294
295
296
297 if SCons.Action.execute_actions:
298 raise e
299
300 results.append(frame.retval)
301
302
303 if len(results) == 1:
304 return results[0]
305 else:
306 return tuple(results)
307
309 """Print an exception stack trace just for the SConscript file(s).
310 This will show users who have Python errors where the problem is,
311 without cluttering the output with all of the internal calls leading
312 up to where we exec the SConscript."""
313 exc_type, exc_value, exc_tb = sys.exc_info()
314 tb = exc_tb
315 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
316 tb = tb.tb_next
317 if not tb:
318
319
320 tb = exc_tb
321 stack = traceback.extract_tb(tb)
322 try:
323 type = exc_type.__name__
324 except AttributeError:
325 type = str(exc_type)
326 if type[:11] == "exceptions.":
327 type = type[11:]
328 file.write('%s: %s:\n' % (type, exc_value))
329 for fname, line, func, text in stack:
330 file.write(' File "%s", line %d:\n' % (fname, line))
331 file.write(' %s\n' % text)
332
334 """Annotate a node with the stack frame describing the
335 SConscript file and line number that created it."""
336 tb = sys.exc_info()[2]
337 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
338 tb = tb.tb_next
339 if not tb:
340
341 raise SCons.Errors.InternalError, "could not find SConscript stack frame"
342 node.creator = traceback.extract_stack(tb)[0]
343
344
345
346
347
348
350 """An Environment subclass that contains all of the methods that
351 are particular to the wrapper SCons interface and which aren't
352 (or shouldn't be) part of the build engine itself.
353
354 Note that not all of the methods of this class have corresponding
355 global functions, there are some private methods.
356 """
357
358
359
360
362 """Return 1 if 'major' and 'minor' are greater than the version
363 in 'v_major' and 'v_minor', and 0 otherwise."""
364 return (major > v_major or (major == v_major and minor > v_minor))
365
367 """Split a version string into major, minor and (optionally)
368 revision parts.
369
370 This is complicated by the fact that a version string can be
371 something like 3.2b1."""
372 version = string.split(string.split(version_string, ' ')[0], '.')
373 v_major = int(version[0])
374 v_minor = int(re.match('\d+', version[1]).group())
375 if len(version) >= 3:
376 v_revision = int(re.match('\d+', version[2]).group())
377 else:
378 v_revision = 0
379 return v_major, v_minor, v_revision
380
382 """
383 Convert the parameters passed to # SConscript() calls into a list
384 of files and export variables. If the parameters are invalid,
385 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
386 is a list of SConscript filenames and e is a list of exports.
387 """
388 exports = []
389
390 if len(ls) == 0:
391 try:
392 dirs = kw["dirs"]
393 except KeyError:
394 raise SCons.Errors.UserError, \
395 "Invalid SConscript usage - no parameters"
396
397 if not SCons.Util.is_List(dirs):
398 dirs = [ dirs ]
399 dirs = map(str, dirs)
400
401 name = kw.get('name', 'SConscript')
402
403 files = map(lambda n, name = name: os.path.join(n, name), dirs)
404
405 elif len(ls) == 1:
406
407 files = ls[0]
408
409 elif len(ls) == 2:
410
411 files = ls[0]
412 exports = self.Split(ls[1])
413
414 else:
415
416 raise SCons.Errors.UserError, \
417 "Invalid SConscript() usage - too many arguments"
418
419 if not SCons.Util.is_List(files):
420 files = [ files ]
421
422 if kw.get('exports'):
423 exports.extend(self.Split(kw['exports']))
424
425 variant_dir = kw.get('variant_dir') or kw.get('build_dir')
426 if variant_dir:
427 if len(files) != 1:
428 raise SCons.Errors.UserError, \
429 "Invalid SConscript() usage - can only specify one SConscript with a variant_dir"
430 duplicate = kw.get('duplicate', 1)
431 src_dir = kw.get('src_dir')
432 if not src_dir:
433 src_dir, fname = os.path.split(str(files[0]))
434 files = [os.path.join(str(variant_dir), fname)]
435 else:
436 if not isinstance(src_dir, SCons.Node.Node):
437 src_dir = self.fs.Dir(src_dir)
438 fn = files[0]
439 if not isinstance(fn, SCons.Node.Node):
440 fn = self.fs.File(fn)
441 if fn.is_under(src_dir):
442
443 fname = fn.get_path(src_dir)
444 files = [os.path.join(str(variant_dir), fname)]
445 else:
446 files = [fn.abspath]
447 kw['src_dir'] = variant_dir
448 self.fs.VariantDir(variant_dir, src_dir, duplicate)
449
450 return (files, exports)
451
452
453
454
455
456
457
463
466
468 """Exit abnormally if the SCons version is not late enough."""
469 scons_ver = self._get_major_minor_revision(SCons.__version__)
470 if scons_ver < (major, minor, revision):
471 if revision:
472 scons_ver_string = '%d.%d.%d' % (major, minor, revision)
473 else:
474 scons_ver_string = '%d.%d' % (major, minor)
475 print "SCons %s or greater required, but you have SCons %s" % \
476 (scons_ver_string, SCons.__version__)
477 sys.exit(2)
478
480 """Exit abnormally if the Python version is not late enough."""
481 try:
482 v_major, v_minor, v_micro, release, serial = sys.version_info
483 python_ver = (v_major, v_minor)
484 except AttributeError:
485 python_ver = self._get_major_minor_revision(sys.version)[:2]
486 if python_ver < (major, minor):
487 v = string.split(sys.version, " ", 1)[0]
488 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
489 sys.exit(2)
490
491 - def Exit(self, value=0):
493
494 - def Export(self, *vars, **kw):
498
502
506
507 - def Help(self, text):
510
512 try:
513 frame = call_stack[-1]
514 globals = frame.globals
515 exports = frame.exports
516 for var in vars:
517 var = self.Split(var)
518 for v in var:
519 if v == '*':
520 globals.update(global_exports)
521 globals.update(exports)
522 else:
523 if exports.has_key(v):
524 globals[v] = exports[v]
525 else:
526 globals[v] = global_exports[v]
527 except KeyError,x:
528 raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
529
537 ls = map(subst_element, ls)
538 subst_kw = {}
539 for key, val in kw.items():
540 if SCons.Util.is_String(val):
541 val = self.subst(val)
542 elif SCons.Util.is_List(val):
543 result = []
544 for v in val:
545 if SCons.Util.is_String(v):
546 v = self.subst(v)
547 result.append(v)
548 val = result
549 subst_kw[key] = val
550
551 files, exports = self._get_SConscript_filenames(ls, subst_kw)
552 subst_kw['exports'] = exports
553 return apply(_SConscript, [self.fs,] + files, subst_kw)
554
558
562
563
564
565
566 SCons.Environment.Environment = SConsEnvironment
567
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589 _DefaultEnvironmentProxy = None
590
597
599 """A class that implements "global function" calls of
600 Environment methods by fetching the specified method from the
601 DefaultEnvironment's class. Note that this uses an intermediate
602 proxy class instead of calling the DefaultEnvironment method
603 directly so that the proxy can override the subst() method and
604 thereby prevent expansion of construction variables (since from
605 the user's point of view this was called as a global function,
606 with no associated construction environment)."""
607 - def __init__(self, method_name, subst=0):
614 env = self.factory()
615 method = getattr(env, self.method_name)
616 return apply(method, args, kw)
617
618
620 """
621 Create a dictionary containing all the default globals for
622 SConstruct and SConscript files.
623 """
624
625 global GlobalDict
626 if GlobalDict is None:
627 GlobalDict = {}
628
629 import SCons.Script
630 d = SCons.Script.__dict__
631 def not_a_module(m, d=d, mtype=type(SCons.Script)):
632 return type(d[m]) != mtype
633 for m in filter(not_a_module, dir(SCons.Script)):
634 GlobalDict[m] = d[m]
635
636 return GlobalDict.copy()
637
638
639
640
641
642
643