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 __revision__ = "src/engine/SCons/Script/SConscript.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
31
32 import SCons
33 import SCons.Action
34 import SCons.Builder
35 import SCons.Defaults
36 import SCons.Environment
37 import SCons.Errors
38 import SCons.Node
39 import SCons.Node.Alias
40 import SCons.Node.FS
41 import SCons.Platform
42 import SCons.SConf
43 import SCons.Script.Main
44 import SCons.Tool
45 import SCons.Util
46
47 from . import Main
48
49 import collections
50 import os
51 import os.path
52 import re
53 import sys
54 import traceback
55 import time
56
59
60 launch_dir = os.path.abspath(os.curdir)
61
62 GlobalDict = None
63
64
65 global_exports = {}
66
67
68 sconscript_chdir = 1
69
71 """Return the locals and globals for the function that called
72 into this module in the current call stack."""
73 try: 1//0
74 except ZeroDivisionError:
75
76
77 frame = sys.exc_info()[2].tb_frame.f_back
78
79
80
81
82
83
84
85
86 while frame.f_globals.get("__name__") == __name__:
87 frame = frame.f_back
88
89 return frame.f_locals, frame.f_globals
90
91
93 """Compute a dictionary of exports given one of the parameters
94 to the Export() function or the exports argument to SConscript()."""
95
96 loc, glob = get_calling_namespaces()
97
98 retval = {}
99 try:
100 for export in exports:
101 if SCons.Util.is_Dict(export):
102 retval.update(export)
103 else:
104 try:
105 retval[export] = loc[export]
106 except KeyError:
107 retval[export] = glob[export]
108 except KeyError as x:
109 raise SCons.Errors.UserError("Export of non-existent variable '%s'"%x)
110
111 return retval
112
114 """A frame on the SConstruct/SConscript call stack"""
115 - def __init__(self, fs, exports, sconscript):
116 self.globals = BuildDefaultGlobals()
117 self.retval = None
118 self.prev_dir = fs.getcwd()
119 self.exports = compute_exports(exports)
120
121 if isinstance(sconscript, SCons.Node.Node):
122 self.sconscript = sconscript
123 elif sconscript == '-':
124 self.sconscript = None
125 else:
126 self.sconscript = fs.File(str(sconscript))
127
128
129 call_stack = []
130
131
132
152
153
154 stack_bottom = '% Stack boTTom %'
155
157 top = fs.Top
158 sd = fs.SConstruct_dir.rdir()
159 exports = kw.get('exports', [])
160
161
162 results = []
163 for fn in files:
164 call_stack.append(Frame(fs, exports, fn))
165 old_sys_path = sys.path
166 try:
167 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
168 if fn == "-":
169 exec(sys.stdin.read(), call_stack[-1].globals)
170 else:
171 if isinstance(fn, SCons.Node.Node):
172 f = fn
173 else:
174 f = fs.File(str(fn))
175 _file_ = None
176
177
178
179
180 fs.chdir(top, change_os_dir=1)
181 if f.rexists():
182 actual = f.rfile()
183 _file_ = open(actual.get_abspath(), "rb")
184 elif f.srcnode().rexists():
185 actual = f.srcnode().rfile()
186 _file_ = open(actual.get_abspath(), "rb")
187 elif f.has_src_builder():
188
189
190
191
192 f.build()
193 f.built()
194 f.builder_set(None)
195 if f.exists():
196 _file_ = open(f.get_abspath(), "rb")
197 if _file_:
198
199
200
201
202
203
204
205
206
207
208 try:
209 src_dir = kw['src_dir']
210 except KeyError:
211 ldir = fs.Dir(f.dir.get_path(sd))
212 else:
213 ldir = fs.Dir(src_dir)
214 if not ldir.is_under(f.dir):
215
216
217
218
219 ldir = fs.Dir(f.dir.get_path(sd))
220 try:
221 fs.chdir(ldir, change_os_dir=sconscript_chdir)
222 except OSError:
223
224
225
226
227
228
229 fs.chdir(ldir, change_os_dir=0)
230 os.chdir(actual.dir.get_abspath())
231
232
233
234
235 sys.path = [ f.dir.get_abspath() ] + sys.path
236
237
238
239
240
241
242
243
244 call_stack[-1].globals.update({stack_bottom:1})
245 old_file = call_stack[-1].globals.get('__file__')
246 try:
247 del call_stack[-1].globals['__file__']
248 except KeyError:
249 pass
250 try:
251 try:
252
253 if Main.print_time:
254 time1 = time.time()
255 exec(compile(_file_.read(), _file_.name, 'exec'),
256 call_stack[-1].globals)
257 except SConscriptReturn:
258 pass
259 finally:
260 if Main.print_time:
261 time2 = time.time()
262 print('SConscript:%s took %0.3f ms' % (f.get_abspath(), (time2 - time1) * 1000.0))
263
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.get_internal_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 as 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.get_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
465 if SCons.__version__ == '__' + 'VERSION__':
466 SCons.Warnings.warn(SCons.Warnings.DevelopmentVersionWarning,
467 "EnsureSConsVersion is ignored for development version")
468 return
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 if sys.version_info < (major, minor):
482 v = sys.version.split()[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, append=False):
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 as 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