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 from __future__ import division
30
31 __revision__ = "src/engine/SCons/Script/SConscript.py 5023 2010/06/14 22:05:46 scons"
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 collections
49 import os
50 import os.path
51 import re
52 import sys
53 import traceback
54
55
56
57
58
59
60
61
62
63
64
67
68 launch_dir = os.path.abspath(os.curdir)
69
70 GlobalDict = None
71
72
73 global_exports = {}
74
75
76 sconscript_chdir = 1
77
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:
83
84
85 frame = sys.exc_info()[2].tb_frame.f_back
86
87
88
89
90
91
92
93
94 while frame.f_globals.get("__name__") == __name__:
95 frame = frame.f_back
96
97 return frame.f_locals, frame.f_globals
98
99
101 """Compute a dictionary of exports given one of the parameters
102 to the Export() function or the exports argument to SConscript()."""
103
104 loc, glob = get_calling_namespaces()
105
106 retval = {}
107 try:
108 for export in exports:
109 if SCons.Util.is_Dict(export):
110 retval.update(export)
111 else:
112 try:
113 retval[export] = loc[export]
114 except KeyError:
115 retval[export] = glob[export]
116 except KeyError, x:
117 raise SCons.Errors.UserError("Export of non-existent variable '%s'"%x)
118
119 return retval
120
122 """A frame on the SConstruct/SConscript call stack"""
123 - def __init__(self, fs, exports, sconscript):
124 self.globals = BuildDefaultGlobals()
125 self.retval = None
126 self.prev_dir = fs.getcwd()
127 self.exports = compute_exports(exports)
128
129 if isinstance(sconscript, SCons.Node.Node):
130 self.sconscript = sconscript
131 elif sconscript == '-':
132 self.sconscript = None
133 else:
134 self.sconscript = fs.File(str(sconscript))
135
136
137 call_stack = []
138
139
140
160
161
162 stack_bottom = '% Stack boTTom %'
163
165 top = fs.Top
166 sd = fs.SConstruct_dir.rdir()
167 exports = kw.get('exports', [])
168
169
170 results = []
171 for fn in files:
172 call_stack.append(Frame(fs, exports, fn))
173 old_sys_path = sys.path
174 try:
175 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
176 if fn == "-":
177 exec sys.stdin in call_stack[-1].globals
178 else:
179 if isinstance(fn, SCons.Node.Node):
180 f = fn
181 else:
182 f = fs.File(str(fn))
183 _file_ = None
184
185
186
187
188 fs.chdir(top, change_os_dir=1)
189 if f.rexists():
190 actual = f.rfile()
191 _file_ = open(actual.get_abspath(), "r")
192 elif f.srcnode().rexists():
193 actual = f.srcnode().rfile()
194 _file_ = open(actual.get_abspath(), "r")
195 elif f.has_src_builder():
196
197
198
199
200 f.build()
201 f.built()
202 f.builder_set(None)
203 if f.exists():
204 _file_ = open(f.get_abspath(), "r")
205 if _file_:
206
207
208
209
210
211
212
213
214
215
216 try:
217 src_dir = kw['src_dir']
218 except KeyError:
219 ldir = fs.Dir(f.dir.get_path(sd))
220 else:
221 ldir = fs.Dir(src_dir)
222 if not ldir.is_under(f.dir):
223
224
225
226
227 ldir = fs.Dir(f.dir.get_path(sd))
228 try:
229 fs.chdir(ldir, change_os_dir=sconscript_chdir)
230 except OSError:
231
232
233
234
235
236
237 fs.chdir(ldir, change_os_dir=0)
238 os.chdir(actual.dir.get_abspath())
239
240
241
242
243 sys.path = [ f.dir.get_abspath() ] + sys.path
244
245
246
247
248
249
250
251
252 call_stack[-1].globals.update({stack_bottom:1})
253 old_file = call_stack[-1].globals.get('__file__')
254 try:
255 del call_stack[-1].globals['__file__']
256 except KeyError:
257 pass
258 try:
259 try:
260 exec _file_ in call_stack[-1].globals
261 except SConscriptReturn:
262 pass
263 finally:
264 if old_file is not None:
265 call_stack[-1].globals.update({__file__:old_file})
266 else:
267 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
268 "Ignoring missing SConscript '%s'" % f.path)
269
270 finally:
271 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
272 sys.path = old_sys_path
273 frame = call_stack.pop()
274 try:
275 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
276 except OSError:
277
278
279
280 fs.chdir(frame.prev_dir, change_os_dir=0)
281 rdir = frame.prev_dir.rdir()
282 rdir._create()
283 try:
284 os.chdir(rdir.get_abspath())
285 except OSError, e:
286
287
288
289
290
291
292
293
294
295 if SCons.Action.execute_actions:
296 raise e
297
298 results.append(frame.retval)
299
300
301 if len(results) == 1:
302 return results[0]
303 else:
304 return tuple(results)
305
307 """Print an exception stack trace just for the SConscript file(s).
308 This will show users who have Python errors where the problem is,
309 without cluttering the output with all of the internal calls leading
310 up to where we exec the SConscript."""
311 exc_type, exc_value, exc_tb = sys.exc_info()
312 tb = exc_tb
313 while tb and stack_bottom not in tb.tb_frame.f_locals:
314 tb = tb.tb_next
315 if not tb:
316
317
318 tb = exc_tb
319 stack = traceback.extract_tb(tb)
320 try:
321 type = exc_type.__name__
322 except AttributeError:
323 type = str(exc_type)
324 if type[:11] == "exceptions.":
325 type = type[11:]
326 file.write('%s: %s:\n' % (type, exc_value))
327 for fname, line, func, text in stack:
328 file.write(' File "%s", line %d:\n' % (fname, line))
329 file.write(' %s\n' % text)
330
332 """Annotate a node with the stack frame describing the
333 SConscript file and line number that created it."""
334 tb = sys.exc_info()[2]
335 while tb and stack_bottom not in tb.tb_frame.f_locals:
336 tb = tb.tb_next
337 if not tb:
338
339 raise SCons.Errors.InternalError("could not find SConscript stack frame")
340 node.creator = traceback.extract_stack(tb)[0]
341
342
343
344
345
346
348 """An Environment subclass that contains all of the methods that
349 are particular to the wrapper SCons interface and which aren't
350 (or shouldn't be) part of the build engine itself.
351
352 Note that not all of the methods of this class have corresponding
353 global functions, there are some private methods.
354 """
355
356
357
358
360 """Return 1 if 'major' and 'minor' are greater than the version
361 in 'v_major' and 'v_minor', and 0 otherwise."""
362 return (major > v_major or (major == v_major and minor > v_minor))
363
365 """Split a version string into major, minor and (optionally)
366 revision parts.
367
368 This is complicated by the fact that a version string can be
369 something like 3.2b1."""
370 version = version_string.split(' ')[0].split('.')
371 v_major = int(version[0])
372 v_minor = int(re.match('\d+', version[1]).group())
373 if len(version) >= 3:
374 v_revision = int(re.match('\d+', version[2]).group())
375 else:
376 v_revision = 0
377 return v_major, v_minor, v_revision
378
380 """
381 Convert the parameters passed to SConscript() calls into a list
382 of files and export variables. If the parameters are invalid,
383 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
384 is a list of SConscript filenames and e is a list of exports.
385 """
386 exports = []
387
388 if len(ls) == 0:
389 try:
390 dirs = kw["dirs"]
391 except KeyError:
392 raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
393
394 if not SCons.Util.is_List(dirs):
395 dirs = [ dirs ]
396 dirs = list(map(str, dirs))
397
398 name = kw.get('name', 'SConscript')
399
400 files = [os.path.join(n, name) for n in dirs]
401
402 elif len(ls) == 1:
403
404 files = ls[0]
405
406 elif len(ls) == 2:
407
408 files = ls[0]
409 exports = self.Split(ls[1])
410
411 else:
412
413 raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
414
415 if not SCons.Util.is_List(files):
416 files = [ files ]
417
418 if kw.get('exports'):
419 exports.extend(self.Split(kw['exports']))
420
421 variant_dir = kw.get('variant_dir') or kw.get('build_dir')
422 if variant_dir:
423 if len(files) != 1:
424 raise SCons.Errors.UserError("Invalid SConscript() usage - can only specify one SConscript with a variant_dir")
425 duplicate = kw.get('duplicate', 1)
426 src_dir = kw.get('src_dir')
427 if not src_dir:
428 src_dir, fname = os.path.split(str(files[0]))
429 files = [os.path.join(str(variant_dir), fname)]
430 else:
431 if not isinstance(src_dir, SCons.Node.Node):
432 src_dir = self.fs.Dir(src_dir)
433 fn = files[0]
434 if not isinstance(fn, SCons.Node.Node):
435 fn = self.fs.File(fn)
436 if fn.is_under(src_dir):
437
438 fname = fn.get_path(src_dir)
439 files = [os.path.join(str(variant_dir), fname)]
440 else:
441 files = [fn.abspath]
442 kw['src_dir'] = variant_dir
443 self.fs.VariantDir(variant_dir, src_dir, duplicate)
444
445 return (files, exports)
446
447
448
449
450
451
452
458
461
463 """Exit abnormally if the SCons version is not late enough."""
464 scons_ver = self._get_major_minor_revision(SCons.__version__)
465 if scons_ver < (major, minor, revision):
466 if revision:
467 scons_ver_string = '%d.%d.%d' % (major, minor, revision)
468 else:
469 scons_ver_string = '%d.%d' % (major, minor)
470 print "SCons %s or greater required, but you have SCons %s" % \
471 (scons_ver_string, SCons.__version__)
472 sys.exit(2)
473
475 """Exit abnormally if the Python version is not late enough."""
476 try:
477 v_major, v_minor, v_micro, release, serial = sys.version_info
478 python_ver = (v_major, v_minor)
479 except AttributeError:
480 python_ver = self._get_major_minor_revision(sys.version)[:2]
481 if python_ver < (major, minor):
482 v = sys.version.split(" ", 1)[0]
483 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
484 sys.exit(2)
485
486 - def Exit(self, value=0):
488
489 - def Export(self, *vars, **kw):
493
497
501
502 - def Help(self, text):
505
507 try:
508 frame = call_stack[-1]
509 globals = frame.globals
510 exports = frame.exports
511 for var in vars:
512 var = self.Split(var)
513 for v in var:
514 if v == '*':
515 globals.update(global_exports)
516 globals.update(exports)
517 else:
518 if v in exports:
519 globals[v] = exports[v]
520 else:
521 globals[v] = global_exports[v]
522 except KeyError,x:
523 raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
524
535 ls = list(map(subst_element, ls))
536 subst_kw = {}
537 for key, val in kw.items():
538 if SCons.Util.is_String(val):
539 val = self.subst(val)
540 elif SCons.Util.is_List(val):
541 result = []
542 for v in val:
543 if SCons.Util.is_String(v):
544 v = self.subst(v)
545 result.append(v)
546 val = result
547 subst_kw[key] = val
548
549 files, exports = self._get_SConscript_filenames(ls, subst_kw)
550 subst_kw['exports'] = exports
551 return _SConscript(self.fs, *files, **subst_kw)
552
556
560
561
562
563
564 SCons.Environment.Environment = SConsEnvironment
565
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587 _DefaultEnvironmentProxy = None
588
595
597 """A class that implements "global function" calls of
598 Environment methods by fetching the specified method from the
599 DefaultEnvironment's class. Note that this uses an intermediate
600 proxy class instead of calling the DefaultEnvironment method
601 directly so that the proxy can override the subst() method and
602 thereby prevent expansion of construction variables (since from
603 the user's point of view this was called as a global function,
604 with no associated construction environment)."""
605 - def __init__(self, method_name, subst=0):
612 env = self.factory()
613 method = getattr(env, self.method_name)
614 return method(*args, **kw)
615
616
618 """
619 Create a dictionary containing all the default globals for
620 SConstruct and SConscript files.
621 """
622
623 global GlobalDict
624 if GlobalDict is None:
625 GlobalDict = {}
626
627 import SCons.Script
628 d = SCons.Script.__dict__
629 def not_a_module(m, d=d, mtype=type(SCons.Script)):
630 return not isinstance(d[m], mtype)
631 for m in filter(not_a_module, dir(SCons.Script)):
632 GlobalDict[m] = d[m]
633
634 return GlobalDict.copy()
635
636
637
638
639
640
641