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 3603 2008/10/10 05:46:45 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 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 _file_ = open(f.rfile().get_abspath(), "r")
193 elif f.has_src_builder():
194
195
196
197
198 f.build()
199 f.built()
200 f.builder_set(None)
201 if f.exists():
202 _file_ = open(f.get_abspath(), "r")
203 if _file_:
204
205
206
207
208
209
210
211
212
213
214 try:
215 src_dir = kw['src_dir']
216 except KeyError:
217 ldir = fs.Dir(f.dir.get_path(sd))
218 else:
219 ldir = fs.Dir(src_dir)
220 if not ldir.is_under(f.dir):
221
222
223
224
225 ldir = fs.Dir(f.dir.get_path(sd))
226 try:
227 fs.chdir(ldir, change_os_dir=sconscript_chdir)
228 except OSError:
229
230
231
232
233
234
235 fs.chdir(ldir, change_os_dir=0)
236
237 os.chdir(f.rfile().dir.get_abspath())
238
239
240
241
242 sys.path = [ f.dir.get_abspath() ] + sys.path
243
244
245
246
247
248
249
250
251 call_stack[-1].globals.update({stack_bottom:1})
252 old_file = call_stack[-1].globals.get('__file__')
253 try:
254 del call_stack[-1].globals['__file__']
255 except KeyError:
256 pass
257 try:
258 try:
259 exec _file_ in call_stack[-1].globals
260 except SConscriptReturn:
261 pass
262 finally:
263 if old_file is not None:
264 call_stack[-1].globals.update({__file__:old_file})
265 else:
266 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
267 "Ignoring missing SConscript '%s'" % f.path)
268
269 finally:
270 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
271 sys.path = old_sys_path
272 frame = call_stack.pop()
273 try:
274 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
275 except OSError:
276
277
278
279 fs.chdir(frame.prev_dir, change_os_dir=0)
280 rdir = frame.prev_dir.rdir()
281 rdir._create()
282 os.chdir(rdir.get_abspath())
283
284 results.append(frame.retval)
285
286
287 if len(results) == 1:
288 return results[0]
289 else:
290 return tuple(results)
291
293 """Print an exception stack trace just for the SConscript file(s).
294 This will show users who have Python errors where the problem is,
295 without cluttering the output with all of the internal calls leading
296 up to where we exec the SConscript."""
297 exc_type, exc_value, exc_tb = sys.exc_info()
298 tb = exc_tb
299 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
300 tb = tb.tb_next
301 if not tb:
302
303
304 tb = exc_tb
305 stack = traceback.extract_tb(tb)
306 try:
307 type = exc_type.__name__
308 except AttributeError:
309 type = str(exc_type)
310 if type[:11] == "exceptions.":
311 type = type[11:]
312 file.write('%s: %s:\n' % (type, exc_value))
313 for fname, line, func, text in stack:
314 file.write(' File "%s", line %d:\n' % (fname, line))
315 file.write(' %s\n' % text)
316
318 """Annotate a node with the stack frame describing the
319 SConscript file and line number that created it."""
320 tb = sys.exc_info()[2]
321 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
322 tb = tb.tb_next
323 if not tb:
324
325 raise SCons.Errors.InternalError, "could not find SConscript stack frame"
326 node.creator = traceback.extract_stack(tb)[0]
327
328
329
330
331
332
334 """An Environment subclass that contains all of the methods that
335 are particular to the wrapper SCons interface and which aren't
336 (or shouldn't be) part of the build engine itself.
337
338 Note that not all of the methods of this class have corresponding
339 global functions, there are some private methods.
340 """
341
342
343
344
346 """Return 1 if 'major' and 'minor' are greater than the version
347 in 'v_major' and 'v_minor', and 0 otherwise."""
348 return (major > v_major or (major == v_major and minor > v_minor))
349
351 """Split a version string into major, minor and (optionally)
352 revision parts.
353
354 This is complicated by the fact that a version string can be
355 something like 3.2b1."""
356 version = string.split(string.split(version_string, ' ')[0], '.')
357 v_major = int(version[0])
358 v_minor = int(re.match('\d+', version[1]).group())
359 if len(version) >= 3:
360 v_revision = int(re.match('\d+', version[2]).group())
361 else:
362 v_revision = 0
363 return v_major, v_minor, v_revision
364
366 """
367 Convert the parameters passed to # SConscript() calls into a list
368 of files and export variables. If the parameters are invalid,
369 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
370 is a list of SConscript filenames and e is a list of exports.
371 """
372 exports = []
373
374 if len(ls) == 0:
375 try:
376 dirs = kw["dirs"]
377 except KeyError:
378 raise SCons.Errors.UserError, \
379 "Invalid SConscript usage - no parameters"
380
381 if not SCons.Util.is_List(dirs):
382 dirs = [ dirs ]
383 dirs = map(str, dirs)
384
385 name = kw.get('name', 'SConscript')
386
387 files = map(lambda n, name = name: os.path.join(n, name), dirs)
388
389 elif len(ls) == 1:
390
391 files = ls[0]
392
393 elif len(ls) == 2:
394
395 files = ls[0]
396 exports = self.Split(ls[1])
397
398 else:
399
400 raise SCons.Errors.UserError, \
401 "Invalid SConscript() usage - too many arguments"
402
403 if not SCons.Util.is_List(files):
404 files = [ files ]
405
406 if kw.get('exports'):
407 exports.extend(self.Split(kw['exports']))
408
409 variant_dir = kw.get('variant_dir') or kw.get('build_dir')
410 if variant_dir:
411 if len(files) != 1:
412 raise SCons.Errors.UserError, \
413 "Invalid SConscript() usage - can only specify one SConscript with a variant_dir"
414 duplicate = kw.get('duplicate', 1)
415 src_dir = kw.get('src_dir')
416 if not src_dir:
417 src_dir, fname = os.path.split(str(files[0]))
418 files = [os.path.join(str(variant_dir), fname)]
419 else:
420 if not isinstance(src_dir, SCons.Node.Node):
421 src_dir = self.fs.Dir(src_dir)
422 fn = files[0]
423 if not isinstance(fn, SCons.Node.Node):
424 fn = self.fs.File(fn)
425 if fn.is_under(src_dir):
426
427 fname = fn.get_path(src_dir)
428 files = [os.path.join(str(variant_dir), fname)]
429 else:
430 files = [fn.abspath]
431 kw['src_dir'] = variant_dir
432 self.fs.VariantDir(variant_dir, src_dir, duplicate)
433
434 return (files, exports)
435
436
437
438
439
440
441
447
450
452 """Exit abnormally if the SCons version is not late enough."""
453 scons_ver = self._get_major_minor_revision(SCons.__version__)
454 if scons_ver < (major, minor, revision):
455 if revision:
456 scons_ver_string = '%d.%d.%d' % (major, minor, revision)
457 else:
458 scons_ver_string = '%d.%d' % (major, minor)
459 print "SCons %s or greater required, but you have SCons %s" % \
460 (scons_ver_string, SCons.__version__)
461 sys.exit(2)
462
464 """Exit abnormally if the Python version is not late enough."""
465 try:
466 v_major, v_minor, v_micro, release, serial = sys.version_info
467 python_ver = (v_major, v_minor)
468 except AttributeError:
469 python_ver = self._get_major_minor_revision(sys.version)[:2]
470 if python_ver < (major, minor):
471 v = string.split(sys.version, " ", 1)[0]
472 print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v)
473 sys.exit(2)
474
475 - def Exit(self, value=0):
477
481
485
489
490 - def Help(self, text):
493
495 try:
496 frame = call_stack[-1]
497 globals = frame.globals
498 exports = frame.exports
499 for var in vars:
500 var = self.Split(var)
501 for v in var:
502 if v == '*':
503 globals.update(global_exports)
504 globals.update(exports)
505 else:
506 if exports.has_key(v):
507 globals[v] = exports[v]
508 else:
509 globals[v] = global_exports[v]
510 except KeyError,x:
511 raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x
512
520 ls = map(subst_element, ls)
521 subst_kw = {}
522 for key, val in kw.items():
523 if SCons.Util.is_String(val):
524 val = self.subst(val)
525 elif SCons.Util.is_List(val):
526 result = []
527 for v in val:
528 if SCons.Util.is_String(v):
529 v = self.subst(v)
530 result.append(v)
531 val = result
532 subst_kw[key] = val
533
534 files, exports = self._get_SConscript_filenames(ls, subst_kw)
535 subst_kw['exports'] = exports
536 return apply(_SConscript, [self.fs,] + files, subst_kw)
537
541
545
546
547
548
549 SCons.Environment.Environment = SConsEnvironment
550
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572 _DefaultEnvironmentProxy = None
573
580
582 """A class that implements "global function" calls of
583 Environment methods by fetching the specified method from the
584 DefaultEnvironment's class. Note that this uses an intermediate
585 proxy class instead of calling the DefaultEnvironment method
586 directly so that the proxy can override the subst() method and
587 thereby prevent expansion of construction variables (since from
588 the user's point of view this was called as a global function,
589 with no associated construction environment)."""
590 - def __init__(self, method_name, subst=0):
597 env = self.factory()
598 method = getattr(env, self.method_name)
599 return apply(method, args, kw)
600
601
603 """
604 Create a dictionary containing all the default globals for
605 SConstruct and SConscript files.
606 """
607
608 global GlobalDict
609 if GlobalDict is None:
610 GlobalDict = {}
611
612 import SCons.Script
613 d = SCons.Script.__dict__
614 def not_a_module(m, d=d, mtype=type(SCons.Script)):
615 return type(d[m]) != mtype
616 for m in filter(not_a_module, dir(SCons.Script)):
617 GlobalDict[m] = d[m]
618
619 return GlobalDict.copy()
620