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 e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
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
184
186 top = fs.Top
187 sd = fs.SConstruct_dir.rdir()
188 exports = kw.get('exports', [])
189
190
191 results = []
192 for fn in files:
193 call_stack.append(Frame(fs, exports, fn))
194 old_sys_path = sys.path
195 try:
196 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
197 if fn == "-":
198 exec(sys.stdin.read(), call_stack[-1].globals)
199 else:
200 if isinstance(fn, SCons.Node.Node):
201 f = fn
202 else:
203 f = fs.File(str(fn))
204 _file_ = None
205
206
207
208
209 fs.chdir(top, change_os_dir=1)
210 if f.rexists():
211 actual = f.rfile()
212 _file_ = open(actual.get_abspath(), "rb")
213 elif f.srcnode().rexists():
214 actual = f.srcnode().rfile()
215 _file_ = open(actual.get_abspath(), "rb")
216 elif f.has_src_builder():
217
218
219
220
221 f.build()
222 f.built()
223 f.builder_set(None)
224 if f.exists():
225 _file_ = open(f.get_abspath(), "rb")
226 if _file_:
227
228
229
230
231
232
233
234
235
236
237 try:
238 src_dir = kw['src_dir']
239 except KeyError:
240 ldir = fs.Dir(f.dir.get_path(sd))
241 else:
242 ldir = fs.Dir(src_dir)
243 if not ldir.is_under(f.dir):
244
245
246
247
248 ldir = fs.Dir(f.dir.get_path(sd))
249 try:
250 fs.chdir(ldir, change_os_dir=sconscript_chdir)
251 except OSError:
252
253
254
255
256
257
258 fs.chdir(ldir, change_os_dir=0)
259 os.chdir(actual.dir.get_abspath())
260
261
262
263
264 sys.path = [ f.dir.get_abspath() ] + sys.path
265
266
267
268
269
270
271
272
273 call_stack[-1].globals.update({stack_bottom:1})
274 old_file = call_stack[-1].globals.get('__file__')
275 try:
276 del call_stack[-1].globals['__file__']
277 except KeyError:
278 pass
279 try:
280 try:
281 if Main.print_time:
282 time1 = time.time()
283 scriptdata = _file_.read()
284 scriptname = _file_.name
285 _file_.close()
286 exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals)
287 except SConscriptReturn:
288 pass
289 finally:
290 if Main.print_time:
291 time2 = time.time()
292 print('SConscript:%s took %0.3f ms' % (f.get_abspath(), (time2 - time1) * 1000.0))
293
294 if old_file is not None:
295 call_stack[-1].globals.update({__file__:old_file})
296 else:
297 handle_missing_SConscript(f, kw.get('must_exist', None))
298
299 finally:
300 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
301 sys.path = old_sys_path
302 frame = call_stack.pop()
303 try:
304 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
305 except OSError:
306
307
308
309 fs.chdir(frame.prev_dir, change_os_dir=0)
310 rdir = frame.prev_dir.rdir()
311 rdir._create()
312 try:
313 os.chdir(rdir.get_abspath())
314 except OSError as e:
315
316
317
318
319
320
321
322
323
324 if SCons.Action.execute_actions:
325 raise e
326
327 results.append(frame.retval)
328
329
330 if len(results) == 1:
331 return results[0]
332 else:
333 return tuple(results)
334
336 """Print an exception stack trace just for the SConscript file(s).
337 This will show users who have Python errors where the problem is,
338 without cluttering the output with all of the internal calls leading
339 up to where we exec the SConscript."""
340 exc_type, exc_value, exc_tb = sys.exc_info()
341 tb = exc_tb
342 while tb and stack_bottom not in tb.tb_frame.f_locals:
343 tb = tb.tb_next
344 if not tb:
345
346
347 tb = exc_tb
348 stack = traceback.extract_tb(tb)
349 try:
350 type = exc_type.__name__
351 except AttributeError:
352 type = str(exc_type)
353 if type[:11] == "exceptions.":
354 type = type[11:]
355 file.write('%s: %s:\n' % (type, exc_value))
356 for fname, line, func, text in stack:
357 file.write(' File "%s", line %d:\n' % (fname, line))
358 file.write(' %s\n' % text)
359
361 """Annotate a node with the stack frame describing the
362 SConscript file and line number that created it."""
363 tb = sys.exc_info()[2]
364 while tb and stack_bottom not in tb.tb_frame.f_locals:
365 tb = tb.tb_next
366 if not tb:
367
368 raise SCons.Errors.InternalError("could not find SConscript stack frame")
369 node.creator = traceback.extract_stack(tb)[0]
370
371
372
373
374
375
377 """An Environment subclass that contains all of the methods that
378 are particular to the wrapper SCons interface and which aren't
379 (or shouldn't be) part of the build engine itself.
380
381 Note that not all of the methods of this class have corresponding
382 global functions, there are some private methods.
383 """
384
385
386
387
389 """Return 1 if 'major' and 'minor' are greater than the version
390 in 'v_major' and 'v_minor', and 0 otherwise."""
391 return (major > v_major or (major == v_major and minor > v_minor))
392
394 """Split a version string into major, minor and (optionally)
395 revision parts.
396
397 This is complicated by the fact that a version string can be
398 something like 3.2b1."""
399 version = version_string.split(' ')[0].split('.')
400 v_major = int(version[0])
401 v_minor = int(re.match(r'\d+', version[1]).group())
402 if len(version) >= 3:
403 v_revision = int(re.match(r'\d+', version[2]).group())
404 else:
405 v_revision = 0
406 return v_major, v_minor, v_revision
407
409 """
410 Convert the parameters passed to SConscript() calls into a list
411 of files and export variables. If the parameters are invalid,
412 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
413 is a list of SConscript filenames and e is a list of exports.
414 """
415 exports = []
416
417 if len(ls) == 0:
418 try:
419 dirs = kw["dirs"]
420 except KeyError:
421 raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
422
423 if not SCons.Util.is_List(dirs):
424 dirs = [ dirs ]
425 dirs = list(map(str, dirs))
426
427 name = kw.get('name', 'SConscript')
428
429 files = [os.path.join(n, name) for n in dirs]
430
431 elif len(ls) == 1:
432
433 files = ls[0]
434
435 elif len(ls) == 2:
436
437 files = ls[0]
438 exports = self.Split(ls[1])
439
440 else:
441
442 raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
443
444 if not SCons.Util.is_List(files):
445 files = [ files ]
446
447 if kw.get('exports'):
448 exports.extend(self.Split(kw['exports']))
449
450 variant_dir = kw.get('variant_dir') or kw.get('build_dir')
451 if variant_dir:
452 if len(files) != 1:
453 raise SCons.Errors.UserError("Invalid SConscript() usage - can only specify one SConscript with a variant_dir")
454 duplicate = kw.get('duplicate', 1)
455 src_dir = kw.get('src_dir')
456 if not src_dir:
457 src_dir, fname = os.path.split(str(files[0]))
458 files = [os.path.join(str(variant_dir), fname)]
459 else:
460 if not isinstance(src_dir, SCons.Node.Node):
461 src_dir = self.fs.Dir(src_dir)
462 fn = files[0]
463 if not isinstance(fn, SCons.Node.Node):
464 fn = self.fs.File(fn)
465 if fn.is_under(src_dir):
466
467 fname = fn.get_path(src_dir)
468 files = [os.path.join(str(variant_dir), fname)]
469 else:
470 files = [fn.get_abspath()]
471 kw['src_dir'] = variant_dir
472 self.fs.VariantDir(variant_dir, src_dir, duplicate)
473
474 return (files, exports)
475
476
477
478
479
480
481
487
490
492 """Exit abnormally if the SCons version is not late enough."""
493
494 if SCons.__version__ == '__' + 'VERSION__':
495 SCons.Warnings.warn(SCons.Warnings.DevelopmentVersionWarning,
496 "EnsureSConsVersion is ignored for development version")
497 return
498 scons_ver = self._get_major_minor_revision(SCons.__version__)
499 if scons_ver < (major, minor, revision):
500 if revision:
501 scons_ver_string = '%d.%d.%d' % (major, minor, revision)
502 else:
503 scons_ver_string = '%d.%d' % (major, minor)
504 print("SCons %s or greater required, but you have SCons %s" % \
505 (scons_ver_string, SCons.__version__))
506 sys.exit(2)
507
509 """Exit abnormally if the Python version is not late enough."""
510 if sys.version_info < (major, minor):
511 v = sys.version.split()[0]
512 print("Python %d.%d or greater required, but you have Python %s" %(major,minor,v))
513 sys.exit(2)
514
515 - def Exit(self, value=0):
517
518 - def Export(self, *vars, **kw):
522
526
530
531 - def Help(self, text, append=False):
534
536 try:
537 frame = call_stack[-1]
538 globals = frame.globals
539 exports = frame.exports
540 for var in vars:
541 var = self.Split(var)
542 for v in var:
543 if v == '*':
544 globals.update(global_exports)
545 globals.update(exports)
546 else:
547 if v in exports:
548 globals[v] = exports[v]
549 else:
550 globals[v] = global_exports[v]
551 except KeyError as x:
552 raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
553
555 """Execute SCons configuration files.
556
557 Parameters:
558 *ls (str or list): configuration file(s) to execute.
559
560 Keyword arguments:
561 dirs (list): execute SConscript in each listed directory.
562 name (str): execute script 'name' (used only with 'dirs').
563 exports (list or dict): locally export variables the
564 called script(s) can import.
565 variant_dir (str): mirror sources needed for the build in
566 a variant directory to allow building in it.
567 duplicate (bool): physically duplicate sources instead of just
568 adjusting paths of derived files (used only with 'variant_dir')
569 (default is True).
570 must_exist (bool): fail if a requested script is missing
571 (default is False, default is deprecated).
572
573 Returns:
574 list of variables returned by the called script
575
576 Raises:
577 UserError: a script is not found and such exceptions are enabled.
578 """
579
580 if 'build_dir' in kw:
581 msg = """The build_dir keyword has been deprecated; use the variant_dir keyword instead."""
582 SCons.Warnings.warn(SCons.Warnings.DeprecatedBuildDirWarning, msg)
583 def subst_element(x, subst=self.subst):
584 if SCons.Util.is_List(x):
585 x = list(map(subst, x))
586 else:
587 x = subst(x)
588 return x
589 ls = list(map(subst_element, ls))
590 subst_kw = {}
591 for key, val in kw.items():
592 if SCons.Util.is_String(val):
593 val = self.subst(val)
594 elif SCons.Util.is_List(val):
595 result = []
596 for v in val:
597 if SCons.Util.is_String(v):
598 v = self.subst(v)
599 result.append(v)
600 val = result
601 subst_kw[key] = val
602
603 files, exports = self._get_SConscript_filenames(ls, subst_kw)
604 subst_kw['exports'] = exports
605 return _SConscript(self.fs, *files, **subst_kw)
606
610
614
615
616
617
618 SCons.Environment.Environment = SConsEnvironment
619
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641 _DefaultEnvironmentProxy = None
642
649
651 """A class that implements "global function" calls of
652 Environment methods by fetching the specified method from the
653 DefaultEnvironment's class. Note that this uses an intermediate
654 proxy class instead of calling the DefaultEnvironment method
655 directly so that the proxy can override the subst() method and
656 thereby prevent expansion of construction variables (since from
657 the user's point of view this was called as a global function,
658 with no associated construction environment)."""
659 - def __init__(self, method_name, subst=0):
666 env = self.factory()
667 method = getattr(env, self.method_name)
668 return method(*args, **kw)
669
670
672 """
673 Create a dictionary containing all the default globals for
674 SConstruct and SConscript files.
675 """
676
677 global GlobalDict
678 if GlobalDict is None:
679 GlobalDict = {}
680
681 import SCons.Script
682 d = SCons.Script.__dict__
683 def not_a_module(m, d=d, mtype=type(SCons.Script)):
684 return not isinstance(d[m], mtype)
685 for m in filter(not_a_module, dir(SCons.Script)):
686 GlobalDict[m] = d[m]
687
688 return GlobalDict.copy()
689
690
691
692
693
694
695