1 """SCons.Script.SConscript
2
3 This module defines the Python API provided to SConscript and SConstruct
4 files.
5
6 """
7
8 from __future__ import print_function
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 __revision__ = "src/engine/SCons/Script/SConscript.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
33
34 import SCons
35 import SCons.Action
36 import SCons.Builder
37 import SCons.Defaults
38 import SCons.Environment
39 import SCons.Errors
40 import SCons.Node
41 import SCons.Node.Alias
42 import SCons.Node.FS
43 import SCons.Platform
44 import SCons.SConf
45 import SCons.Script.Main
46 import SCons.Tool
47 import SCons.Util
48
49 import collections
50 import os
51 import os.path
52 import re
53 import sys
54 import traceback
55
58
59 launch_dir = os.path.abspath(os.curdir)
60
61 GlobalDict = None
62
63
64 global_exports = {}
65
66
67 sconscript_chdir = 1
68
70 """Return the locals and globals for the function that called
71 into this module in the current call stack."""
72 try: 1//0
73 except ZeroDivisionError:
74
75
76 frame = sys.exc_info()[2].tb_frame.f_back
77
78
79
80
81
82
83
84
85 while frame.f_globals.get("__name__") == __name__:
86 frame = frame.f_back
87
88 return frame.f_locals, frame.f_globals
89
90
92 """Compute a dictionary of exports given one of the parameters
93 to the Export() function or the exports argument to SConscript()."""
94
95 loc, glob = get_calling_namespaces()
96
97 retval = {}
98 try:
99 for export in exports:
100 if SCons.Util.is_Dict(export):
101 retval.update(export)
102 else:
103 try:
104 retval[export] = loc[export]
105 except KeyError:
106 retval[export] = glob[export]
107 except KeyError as x:
108 raise SCons.Errors.UserError("Export of non-existent variable '%s'"%x)
109
110 return retval
111
113 """A frame on the SConstruct/SConscript call stack"""
114 - def __init__(self, fs, exports, sconscript):
115 self.globals = BuildDefaultGlobals()
116 self.retval = None
117 self.prev_dir = fs.getcwd()
118 self.exports = compute_exports(exports)
119
120 if isinstance(sconscript, SCons.Node.Node):
121 self.sconscript = sconscript
122 elif sconscript == '-':
123 self.sconscript = None
124 else:
125 self.sconscript = fs.File(str(sconscript))
126
127
128 call_stack = []
129
130
131
151
152
153 stack_bottom = '% Stack boTTom %'
154
156 top = fs.Top
157 sd = fs.SConstruct_dir.rdir()
158 exports = kw.get('exports', [])
159
160
161 results = []
162 for fn in files:
163 call_stack.append(Frame(fs, exports, fn))
164 old_sys_path = sys.path
165 try:
166 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
167 if fn == "-":
168 exec(sys.stdin.read(), call_stack[-1].globals)
169 else:
170 if isinstance(fn, SCons.Node.Node):
171 f = fn
172 else:
173 f = fs.File(str(fn))
174 _file_ = None
175
176
177
178
179 fs.chdir(top, change_os_dir=1)
180 if f.rexists():
181 actual = f.rfile()
182 _file_ = open(actual.get_abspath(), "rb")
183 elif f.srcnode().rexists():
184 actual = f.srcnode().rfile()
185 _file_ = open(actual.get_abspath(), "rb")
186 elif f.has_src_builder():
187
188
189
190
191 f.build()
192 f.built()
193 f.builder_set(None)
194 if f.exists():
195 _file_ = open(f.get_abspath(), "rb")
196 if _file_:
197
198
199
200
201
202
203
204
205
206
207 try:
208 src_dir = kw['src_dir']
209 except KeyError:
210 ldir = fs.Dir(f.dir.get_path(sd))
211 else:
212 ldir = fs.Dir(src_dir)
213 if not ldir.is_under(f.dir):
214
215
216
217
218 ldir = fs.Dir(f.dir.get_path(sd))
219 try:
220 fs.chdir(ldir, change_os_dir=sconscript_chdir)
221 except OSError:
222
223
224
225
226
227
228 fs.chdir(ldir, change_os_dir=0)
229 os.chdir(actual.dir.get_abspath())
230
231
232
233
234 sys.path = [ f.dir.get_abspath() ] + sys.path
235
236
237
238
239
240
241
242
243 call_stack[-1].globals.update({stack_bottom:1})
244 old_file = call_stack[-1].globals.get('__file__')
245 try:
246 del call_stack[-1].globals['__file__']
247 except KeyError:
248 pass
249 try:
250 try:
251
252 exec(compile(_file_.read(), _file_.name, 'exec'),
253 call_stack[-1].globals)
254 except SConscriptReturn:
255 pass
256 finally:
257 if old_file is not None:
258 call_stack[-1].globals.update({__file__:old_file})
259 else:
260 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
261 "Ignoring missing SConscript '%s'" % f.get_internal_path())
262
263 finally:
264 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
265 sys.path = old_sys_path
266 frame = call_stack.pop()
267 try:
268 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
269 except OSError:
270
271
272
273 fs.chdir(frame.prev_dir, change_os_dir=0)
274 rdir = frame.prev_dir.rdir()
275 rdir._create()
276 try:
277 os.chdir(rdir.get_abspath())
278 except OSError as e:
279
280
281
282
283
284
285
286
287
288 if SCons.Action.execute_actions:
289 raise e
290
291 results.append(frame.retval)
292
293
294 if len(results) == 1:
295 return results[0]
296 else:
297 return tuple(results)
298
300 """Print an exception stack trace just for the SConscript file(s).
301 This will show users who have Python errors where the problem is,
302 without cluttering the output with all of the internal calls leading
303 up to where we exec the SConscript."""
304 exc_type, exc_value, exc_tb = sys.exc_info()
305 tb = exc_tb
306 while tb and stack_bottom not in tb.tb_frame.f_locals:
307 tb = tb.tb_next
308 if not tb:
309
310
311 tb = exc_tb
312 stack = traceback.extract_tb(tb)
313 try:
314 type = exc_type.__name__
315 except AttributeError:
316 type = str(exc_type)
317 if type[:11] == "exceptions.":
318 type = type[11:]
319 file.write('%s: %s:\n' % (type, exc_value))
320 for fname, line, func, text in stack:
321 file.write(' File "%s", line %d:\n' % (fname, line))
322 file.write(' %s\n' % text)
323
325 """Annotate a node with the stack frame describing the
326 SConscript file and line number that created it."""
327 tb = sys.exc_info()[2]
328 while tb and stack_bottom not in tb.tb_frame.f_locals:
329 tb = tb.tb_next
330 if not tb:
331
332 raise SCons.Errors.InternalError("could not find SConscript stack frame")
333 node.creator = traceback.extract_stack(tb)[0]
334
335
336
337
338
339
341 """An Environment subclass that contains all of the methods that
342 are particular to the wrapper SCons interface and which aren't
343 (or shouldn't be) part of the build engine itself.
344
345 Note that not all of the methods of this class have corresponding
346 global functions, there are some private methods.
347 """
348
349
350
351
353 """Return 1 if 'major' and 'minor' are greater than the version
354 in 'v_major' and 'v_minor', and 0 otherwise."""
355 return (major > v_major or (major == v_major and minor > v_minor))
356
358 """Split a version string into major, minor and (optionally)
359 revision parts.
360
361 This is complicated by the fact that a version string can be
362 something like 3.2b1."""
363 version = version_string.split(' ')[0].split('.')
364 v_major = int(version[0])
365 v_minor = int(re.match('\d+', version[1]).group())
366 if len(version) >= 3:
367 v_revision = int(re.match('\d+', version[2]).group())
368 else:
369 v_revision = 0
370 return v_major, v_minor, v_revision
371
373 """
374 Convert the parameters passed to SConscript() calls into a list
375 of files and export variables. If the parameters are invalid,
376 throws SCons.Errors.UserError. Returns a tuple (l, e) where l
377 is a list of SConscript filenames and e is a list of exports.
378 """
379 exports = []
380
381 if len(ls) == 0:
382 try:
383 dirs = kw["dirs"]
384 except KeyError:
385 raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
386
387 if not SCons.Util.is_List(dirs):
388 dirs = [ dirs ]
389 dirs = list(map(str, dirs))
390
391 name = kw.get('name', 'SConscript')
392
393 files = [os.path.join(n, name) for n in dirs]
394
395 elif len(ls) == 1:
396
397 files = ls[0]
398
399 elif len(ls) == 2:
400
401 files = ls[0]
402 exports = self.Split(ls[1])
403
404 else:
405
406 raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
407
408 if not SCons.Util.is_List(files):
409 files = [ files ]
410
411 if kw.get('exports'):
412 exports.extend(self.Split(kw['exports']))
413
414 variant_dir = kw.get('variant_dir') or kw.get('build_dir')
415 if variant_dir:
416 if len(files) != 1:
417 raise SCons.Errors.UserError("Invalid SConscript() usage - can only specify one SConscript with a variant_dir")
418 duplicate = kw.get('duplicate', 1)
419 src_dir = kw.get('src_dir')
420 if not src_dir:
421 src_dir, fname = os.path.split(str(files[0]))
422 files = [os.path.join(str(variant_dir), fname)]
423 else:
424 if not isinstance(src_dir, SCons.Node.Node):
425 src_dir = self.fs.Dir(src_dir)
426 fn = files[0]
427 if not isinstance(fn, SCons.Node.Node):
428 fn = self.fs.File(fn)
429 if fn.is_under(src_dir):
430
431 fname = fn.get_path(src_dir)
432 files = [os.path.join(str(variant_dir), fname)]
433 else:
434 files = [fn.get_abspath()]
435 kw['src_dir'] = variant_dir
436 self.fs.VariantDir(variant_dir, src_dir, duplicate)
437
438 return (files, exports)
439
440
441
442
443
444
445
451
454
456 """Exit abnormally if the SCons version is not late enough."""
457
458 if SCons.__version__ == '__' + 'VERSION__':
459 SCons.Warnings.warn(SCons.Warnings.DevelopmentVersionWarning,
460 "EnsureSConsVersion is ignored for development version")
461 return
462 scons_ver = self._get_major_minor_revision(SCons.__version__)
463 if scons_ver < (major, minor, revision):
464 if revision:
465 scons_ver_string = '%d.%d.%d' % (major, minor, revision)
466 else:
467 scons_ver_string = '%d.%d' % (major, minor)
468 print("SCons %s or greater required, but you have SCons %s" % \
469 (scons_ver_string, SCons.__version__))
470 sys.exit(2)
471
473 """Exit abnormally if the Python version is not late enough."""
474 if sys.version_info < (major, minor):
475 v = sys.version.split()[0]
476 print("Python %d.%d or greater required, but you have Python %s" %(major,minor,v))
477 sys.exit(2)
478
479 - def Exit(self, value=0):
481
482 - def Export(self, *vars, **kw):
486
490
494
495 - def Help(self, text, append=False):
498
500 try:
501 frame = call_stack[-1]
502 globals = frame.globals
503 exports = frame.exports
504 for var in vars:
505 var = self.Split(var)
506 for v in var:
507 if v == '*':
508 globals.update(global_exports)
509 globals.update(exports)
510 else:
511 if v in exports:
512 globals[v] = exports[v]
513 else:
514 globals[v] = global_exports[v]
515 except KeyError as x:
516 raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
517
528 ls = list(map(subst_element, ls))
529 subst_kw = {}
530 for key, val in kw.items():
531 if SCons.Util.is_String(val):
532 val = self.subst(val)
533 elif SCons.Util.is_List(val):
534 result = []
535 for v in val:
536 if SCons.Util.is_String(v):
537 v = self.subst(v)
538 result.append(v)
539 val = result
540 subst_kw[key] = val
541
542 files, exports = self._get_SConscript_filenames(ls, subst_kw)
543 subst_kw['exports'] = exports
544 return _SConscript(self.fs, *files, **subst_kw)
545
549
553
554
555
556
557 SCons.Environment.Environment = SConsEnvironment
558
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580 _DefaultEnvironmentProxy = None
581
588
590 """A class that implements "global function" calls of
591 Environment methods by fetching the specified method from the
592 DefaultEnvironment's class. Note that this uses an intermediate
593 proxy class instead of calling the DefaultEnvironment method
594 directly so that the proxy can override the subst() method and
595 thereby prevent expansion of construction variables (since from
596 the user's point of view this was called as a global function,
597 with no associated construction environment)."""
598 - def __init__(self, method_name, subst=0):
605 env = self.factory()
606 method = getattr(env, self.method_name)
607 return method(*args, **kw)
608
609
611 """
612 Create a dictionary containing all the default globals for
613 SConstruct and SConscript files.
614 """
615
616 global GlobalDict
617 if GlobalDict is None:
618 GlobalDict = {}
619
620 import SCons.Script
621 d = SCons.Script.__dict__
622 def not_a_module(m, d=d, mtype=type(SCons.Script)):
623 return not isinstance(d[m], mtype)
624 for m in filter(not_a_module, dir(SCons.Script)):
625 GlobalDict[m] = d[m]
626
627 return GlobalDict.copy()
628
629
630
631
632
633
634