You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

633 lines
24 KiB

6 years ago
  1. """SCons.Script.SConscript
  2. This module defines the Python API provided to SConscript and SConstruct
  3. files.
  4. """
  5. from __future__ import print_function
  6. #
  7. # Copyright (c) 2001 - 2017 The SCons Foundation
  8. #
  9. # Permission is hereby granted, free of charge, to any person obtaining
  10. # a copy of this software and associated documentation files (the
  11. # "Software"), to deal in the Software without restriction, including
  12. # without limitation the rights to use, copy, modify, merge, publish,
  13. # distribute, sublicense, and/or sell copies of the Software, and to
  14. # permit persons to whom the Software is furnished to do so, subject to
  15. # the following conditions:
  16. #
  17. # The above copyright notice and this permission notice shall be included
  18. # in all copies or substantial portions of the Software.
  19. #
  20. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  21. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  22. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. __revision__ = "src/engine/SCons/Script/SConscript.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
  28. import SCons
  29. import SCons.Action
  30. import SCons.Builder
  31. import SCons.Defaults
  32. import SCons.Environment
  33. import SCons.Errors
  34. import SCons.Node
  35. import SCons.Node.Alias
  36. import SCons.Node.FS
  37. import SCons.Platform
  38. import SCons.SConf
  39. import SCons.Script.Main
  40. import SCons.Tool
  41. import SCons.Util
  42. import collections
  43. import os
  44. import os.path
  45. import re
  46. import sys
  47. import traceback
  48. class SConscriptReturn(Exception):
  49. pass
  50. launch_dir = os.path.abspath(os.curdir)
  51. GlobalDict = None
  52. # global exports set by Export():
  53. global_exports = {}
  54. # chdir flag
  55. sconscript_chdir = 1
  56. def get_calling_namespaces():
  57. """Return the locals and globals for the function that called
  58. into this module in the current call stack."""
  59. try: 1//0
  60. except ZeroDivisionError:
  61. # Don't start iterating with the current stack-frame to
  62. # prevent creating reference cycles (f_back is safe).
  63. frame = sys.exc_info()[2].tb_frame.f_back
  64. # Find the first frame that *isn't* from this file. This means
  65. # that we expect all of the SCons frames that implement an Export()
  66. # or SConscript() call to be in this file, so that we can identify
  67. # the first non-Script.SConscript frame as the user's local calling
  68. # environment, and the locals and globals dictionaries from that
  69. # frame as the calling namespaces. See the comment below preceding
  70. # the DefaultEnvironmentCall block for even more explanation.
  71. while frame.f_globals.get("__name__") == __name__:
  72. frame = frame.f_back
  73. return frame.f_locals, frame.f_globals
  74. def compute_exports(exports):
  75. """Compute a dictionary of exports given one of the parameters
  76. to the Export() function or the exports argument to SConscript()."""
  77. loc, glob = get_calling_namespaces()
  78. retval = {}
  79. try:
  80. for export in exports:
  81. if SCons.Util.is_Dict(export):
  82. retval.update(export)
  83. else:
  84. try:
  85. retval[export] = loc[export]
  86. except KeyError:
  87. retval[export] = glob[export]
  88. except KeyError as x:
  89. raise SCons.Errors.UserError("Export of non-existent variable '%s'"%x)
  90. return retval
  91. class Frame(object):
  92. """A frame on the SConstruct/SConscript call stack"""
  93. def __init__(self, fs, exports, sconscript):
  94. self.globals = BuildDefaultGlobals()
  95. self.retval = None
  96. self.prev_dir = fs.getcwd()
  97. self.exports = compute_exports(exports) # exports from the calling SConscript
  98. # make sure the sconscript attr is a Node.
  99. if isinstance(sconscript, SCons.Node.Node):
  100. self.sconscript = sconscript
  101. elif sconscript == '-':
  102. self.sconscript = None
  103. else:
  104. self.sconscript = fs.File(str(sconscript))
  105. # the SConstruct/SConscript call stack:
  106. call_stack = []
  107. # For documentation on the methods in this file, see the scons man-page
  108. def Return(*vars, **kw):
  109. retval = []
  110. try:
  111. fvars = SCons.Util.flatten(vars)
  112. for var in fvars:
  113. for v in var.split():
  114. retval.append(call_stack[-1].globals[v])
  115. except KeyError as x:
  116. raise SCons.Errors.UserError("Return of non-existent variable '%s'"%x)
  117. if len(retval) == 1:
  118. call_stack[-1].retval = retval[0]
  119. else:
  120. call_stack[-1].retval = tuple(retval)
  121. stop = kw.get('stop', True)
  122. if stop:
  123. raise SConscriptReturn
  124. stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
  125. def _SConscript(fs, *files, **kw):
  126. top = fs.Top
  127. sd = fs.SConstruct_dir.rdir()
  128. exports = kw.get('exports', [])
  129. # evaluate each SConscript file
  130. results = []
  131. for fn in files:
  132. call_stack.append(Frame(fs, exports, fn))
  133. old_sys_path = sys.path
  134. try:
  135. SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
  136. if fn == "-":
  137. exec(sys.stdin.read(), call_stack[-1].globals)
  138. else:
  139. if isinstance(fn, SCons.Node.Node):
  140. f = fn
  141. else:
  142. f = fs.File(str(fn))
  143. _file_ = None
  144. # Change directory to the top of the source
  145. # tree to make sure the os's cwd and the cwd of
  146. # fs match so we can open the SConscript.
  147. fs.chdir(top, change_os_dir=1)
  148. if f.rexists():
  149. actual = f.rfile()
  150. _file_ = open(actual.get_abspath(), "rb")
  151. elif f.srcnode().rexists():
  152. actual = f.srcnode().rfile()
  153. _file_ = open(actual.get_abspath(), "rb")
  154. elif f.has_src_builder():
  155. # The SConscript file apparently exists in a source
  156. # code management system. Build it, but then clear
  157. # the builder so that it doesn't get built *again*
  158. # during the actual build phase.
  159. f.build()
  160. f.built()
  161. f.builder_set(None)
  162. if f.exists():
  163. _file_ = open(f.get_abspath(), "rb")
  164. if _file_:
  165. # Chdir to the SConscript directory. Use a path
  166. # name relative to the SConstruct file so that if
  167. # we're using the -f option, we're essentially
  168. # creating a parallel SConscript directory structure
  169. # in our local directory tree.
  170. #
  171. # XXX This is broken for multiple-repository cases
  172. # where the SConstruct and SConscript files might be
  173. # in different Repositories. For now, cross that
  174. # bridge when someone comes to it.
  175. try:
  176. src_dir = kw['src_dir']
  177. except KeyError:
  178. ldir = fs.Dir(f.dir.get_path(sd))
  179. else:
  180. ldir = fs.Dir(src_dir)
  181. if not ldir.is_under(f.dir):
  182. # They specified a source directory, but
  183. # it's above the SConscript directory.
  184. # Do the sensible thing and just use the
  185. # SConcript directory.
  186. ldir = fs.Dir(f.dir.get_path(sd))
  187. try:
  188. fs.chdir(ldir, change_os_dir=sconscript_chdir)
  189. except OSError:
  190. # There was no local directory, so we should be
  191. # able to chdir to the Repository directory.
  192. # Note that we do this directly, not through
  193. # fs.chdir(), because we still need to
  194. # interpret the stuff within the SConscript file
  195. # relative to where we are logically.
  196. fs.chdir(ldir, change_os_dir=0)
  197. os.chdir(actual.dir.get_abspath())
  198. # Append the SConscript directory to the beginning
  199. # of sys.path so Python modules in the SConscript
  200. # directory can be easily imported.
  201. sys.path = [ f.dir.get_abspath() ] + sys.path
  202. # This is the magic line that actually reads up
  203. # and executes the stuff in the SConscript file.
  204. # The locals for this frame contain the special
  205. # bottom-of-the-stack marker so that any
  206. # exceptions that occur when processing this
  207. # SConscript can base the printed frames at this
  208. # level and not show SCons internals as well.
  209. call_stack[-1].globals.update({stack_bottom:1})
  210. old_file = call_stack[-1].globals.get('__file__')
  211. try:
  212. del call_stack[-1].globals['__file__']
  213. except KeyError:
  214. pass
  215. try:
  216. try:
  217. # _file_ = SCons.Util.to_str(_file_)
  218. exec(compile(_file_.read(), _file_.name, 'exec'),
  219. call_stack[-1].globals)
  220. except SConscriptReturn:
  221. pass
  222. finally:
  223. if old_file is not None:
  224. call_stack[-1].globals.update({__file__:old_file})
  225. else:
  226. SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
  227. "Ignoring missing SConscript '%s'" % f.get_internal_path())
  228. finally:
  229. SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
  230. sys.path = old_sys_path
  231. frame = call_stack.pop()
  232. try:
  233. fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
  234. except OSError:
  235. # There was no local directory, so chdir to the
  236. # Repository directory. Like above, we do this
  237. # directly.
  238. fs.chdir(frame.prev_dir, change_os_dir=0)
  239. rdir = frame.prev_dir.rdir()
  240. rdir._create() # Make sure there's a directory there.
  241. try:
  242. os.chdir(rdir.get_abspath())
  243. except OSError as e:
  244. # We still couldn't chdir there, so raise the error,
  245. # but only if actions are being executed.
  246. #
  247. # If the -n option was used, the directory would *not*
  248. # have been created and we should just carry on and
  249. # let things muddle through. This isn't guaranteed
  250. # to work if the SConscript files are reading things
  251. # from disk (for example), but it should work well
  252. # enough for most configurations.
  253. if SCons.Action.execute_actions:
  254. raise e
  255. results.append(frame.retval)
  256. # if we only have one script, don't return a tuple
  257. if len(results) == 1:
  258. return results[0]
  259. else:
  260. return tuple(results)
  261. def SConscript_exception(file=sys.stderr):
  262. """Print an exception stack trace just for the SConscript file(s).
  263. This will show users who have Python errors where the problem is,
  264. without cluttering the output with all of the internal calls leading
  265. up to where we exec the SConscript."""
  266. exc_type, exc_value, exc_tb = sys.exc_info()
  267. tb = exc_tb
  268. while tb and stack_bottom not in tb.tb_frame.f_locals:
  269. tb = tb.tb_next
  270. if not tb:
  271. # We did not find our exec statement, so this was actually a bug
  272. # in SCons itself. Show the whole stack.
  273. tb = exc_tb
  274. stack = traceback.extract_tb(tb)
  275. try:
  276. type = exc_type.__name__
  277. except AttributeError:
  278. type = str(exc_type)
  279. if type[:11] == "exceptions.":
  280. type = type[11:]
  281. file.write('%s: %s:\n' % (type, exc_value))
  282. for fname, line, func, text in stack:
  283. file.write(' File "%s", line %d:\n' % (fname, line))
  284. file.write(' %s\n' % text)
  285. def annotate(node):
  286. """Annotate a node with the stack frame describing the
  287. SConscript file and line number that created it."""
  288. tb = sys.exc_info()[2]
  289. while tb and stack_bottom not in tb.tb_frame.f_locals:
  290. tb = tb.tb_next
  291. if not tb:
  292. # We did not find any exec of an SConscript file: what?!
  293. raise SCons.Errors.InternalError("could not find SConscript stack frame")
  294. node.creator = traceback.extract_stack(tb)[0]
  295. # The following line would cause each Node to be annotated using the
  296. # above function. Unfortunately, this is a *huge* performance hit, so
  297. # leave this disabled until we find a more efficient mechanism.
  298. #SCons.Node.Annotate = annotate
  299. class SConsEnvironment(SCons.Environment.Base):
  300. """An Environment subclass that contains all of the methods that
  301. are particular to the wrapper SCons interface and which aren't
  302. (or shouldn't be) part of the build engine itself.
  303. Note that not all of the methods of this class have corresponding
  304. global functions, there are some private methods.
  305. """
  306. #
  307. # Private methods of an SConsEnvironment.
  308. #
  309. def _exceeds_version(self, major, minor, v_major, v_minor):
  310. """Return 1 if 'major' and 'minor' are greater than the version
  311. in 'v_major' and 'v_minor', and 0 otherwise."""
  312. return (major > v_major or (major == v_major and minor > v_minor))
  313. def _get_major_minor_revision(self, version_string):
  314. """Split a version string into major, minor and (optionally)
  315. revision parts.
  316. This is complicated by the fact that a version string can be
  317. something like 3.2b1."""
  318. version = version_string.split(' ')[0].split('.')
  319. v_major = int(version[0])
  320. v_minor = int(re.match('\d+', version[1]).group())
  321. if len(version) >= 3:
  322. v_revision = int(re.match('\d+', version[2]).group())
  323. else:
  324. v_revision = 0
  325. return v_major, v_minor, v_revision
  326. def _get_SConscript_filenames(self, ls, kw):
  327. """
  328. Convert the parameters passed to SConscript() calls into a list
  329. of files and export variables. If the parameters are invalid,
  330. throws SCons.Errors.UserError. Returns a tuple (l, e) where l
  331. is a list of SConscript filenames and e is a list of exports.
  332. """
  333. exports = []
  334. if len(ls) == 0:
  335. try:
  336. dirs = kw["dirs"]
  337. except KeyError:
  338. raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
  339. if not SCons.Util.is_List(dirs):
  340. dirs = [ dirs ]
  341. dirs = list(map(str, dirs))
  342. name = kw.get('name', 'SConscript')
  343. files = [os.path.join(n, name) for n in dirs]
  344. elif len(ls) == 1:
  345. files = ls[0]
  346. elif len(ls) == 2:
  347. files = ls[0]
  348. exports = self.Split(ls[1])
  349. else:
  350. raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
  351. if not SCons.Util.is_List(files):
  352. files = [ files ]
  353. if kw.get('exports'):
  354. exports.extend(self.Split(kw['exports']))
  355. variant_dir = kw.get('variant_dir') or kw.get('build_dir')
  356. if variant_dir:
  357. if len(files) != 1:
  358. raise SCons.Errors.UserError("Invalid SConscript() usage - can only specify one SConscript with a variant_dir")
  359. duplicate = kw.get('duplicate', 1)
  360. src_dir = kw.get('src_dir')
  361. if not src_dir:
  362. src_dir, fname = os.path.split(str(files[0]))
  363. files = [os.path.join(str(variant_dir), fname)]
  364. else:
  365. if not isinstance(src_dir, SCons.Node.Node):
  366. src_dir = self.fs.Dir(src_dir)
  367. fn = files[0]
  368. if not isinstance(fn, SCons.Node.Node):
  369. fn = self.fs.File(fn)
  370. if fn.is_under(src_dir):
  371. # Get path relative to the source directory.
  372. fname = fn.get_path(src_dir)
  373. files = [os.path.join(str(variant_dir), fname)]
  374. else:
  375. files = [fn.get_abspath()]
  376. kw['src_dir'] = variant_dir
  377. self.fs.VariantDir(variant_dir, src_dir, duplicate)
  378. return (files, exports)
  379. #
  380. # Public methods of an SConsEnvironment. These get
  381. # entry points in the global namespace so they can be called
  382. # as global functions.
  383. #
  384. def Configure(self, *args, **kw):
  385. if not SCons.Script.sconscript_reading:
  386. raise SCons.Errors.UserError("Calling Configure from Builders is not supported.")
  387. kw['_depth'] = kw.get('_depth', 0) + 1
  388. return SCons.Environment.Base.Configure(self, *args, **kw)
  389. def Default(self, *targets):
  390. SCons.Script._Set_Default_Targets(self, targets)
  391. def EnsureSConsVersion(self, major, minor, revision=0):
  392. """Exit abnormally if the SCons version is not late enough."""
  393. # split string to avoid replacement during build process
  394. if SCons.__version__ == '__' + 'VERSION__':
  395. SCons.Warnings.warn(SCons.Warnings.DevelopmentVersionWarning,
  396. "EnsureSConsVersion is ignored for development version")
  397. return
  398. scons_ver = self._get_major_minor_revision(SCons.__version__)
  399. if scons_ver < (major, minor, revision):
  400. if revision:
  401. scons_ver_string = '%d.%d.%d' % (major, minor, revision)
  402. else:
  403. scons_ver_string = '%d.%d' % (major, minor)
  404. print("SCons %s or greater required, but you have SCons %s" % \
  405. (scons_ver_string, SCons.__version__))
  406. sys.exit(2)
  407. def EnsurePythonVersion(self, major, minor):
  408. """Exit abnormally if the Python version is not late enough."""
  409. if sys.version_info < (major, minor):
  410. v = sys.version.split()[0]
  411. print("Python %d.%d or greater required, but you have Python %s" %(major,minor,v))
  412. sys.exit(2)
  413. def Exit(self, value=0):
  414. sys.exit(value)
  415. def Export(self, *vars, **kw):
  416. for var in vars:
  417. global_exports.update(compute_exports(self.Split(var)))
  418. global_exports.update(kw)
  419. def GetLaunchDir(self):
  420. global launch_dir
  421. return launch_dir
  422. def GetOption(self, name):
  423. name = self.subst(name)
  424. return SCons.Script.Main.GetOption(name)
  425. def Help(self, text, append=False):
  426. text = self.subst(text, raw=1)
  427. SCons.Script.HelpFunction(text, append=append)
  428. def Import(self, *vars):
  429. try:
  430. frame = call_stack[-1]
  431. globals = frame.globals
  432. exports = frame.exports
  433. for var in vars:
  434. var = self.Split(var)
  435. for v in var:
  436. if v == '*':
  437. globals.update(global_exports)
  438. globals.update(exports)
  439. else:
  440. if v in exports:
  441. globals[v] = exports[v]
  442. else:
  443. globals[v] = global_exports[v]
  444. except KeyError as x:
  445. raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
  446. def SConscript(self, *ls, **kw):
  447. if 'build_dir' in kw:
  448. msg = """The build_dir keyword has been deprecated; use the variant_dir keyword instead."""
  449. SCons.Warnings.warn(SCons.Warnings.DeprecatedBuildDirWarning, msg)
  450. def subst_element(x, subst=self.subst):
  451. if SCons.Util.is_List(x):
  452. x = list(map(subst, x))
  453. else:
  454. x = subst(x)
  455. return x
  456. ls = list(map(subst_element, ls))
  457. subst_kw = {}
  458. for key, val in kw.items():
  459. if SCons.Util.is_String(val):
  460. val = self.subst(val)
  461. elif SCons.Util.is_List(val):
  462. result = []
  463. for v in val:
  464. if SCons.Util.is_String(v):
  465. v = self.subst(v)
  466. result.append(v)
  467. val = result
  468. subst_kw[key] = val
  469. files, exports = self._get_SConscript_filenames(ls, subst_kw)
  470. subst_kw['exports'] = exports
  471. return _SConscript(self.fs, *files, **subst_kw)
  472. def SConscriptChdir(self, flag):
  473. global sconscript_chdir
  474. sconscript_chdir = flag
  475. def SetOption(self, name, value):
  476. name = self.subst(name)
  477. SCons.Script.Main.SetOption(name, value)
  478. #
  479. #
  480. #
  481. SCons.Environment.Environment = SConsEnvironment
  482. def Configure(*args, **kw):
  483. if not SCons.Script.sconscript_reading:
  484. raise SCons.Errors.UserError("Calling Configure from Builders is not supported.")
  485. kw['_depth'] = 1
  486. return SCons.SConf.SConf(*args, **kw)
  487. # It's very important that the DefaultEnvironmentCall() class stay in this
  488. # file, with the get_calling_namespaces() function, the compute_exports()
  489. # function, the Frame class and the SConsEnvironment.Export() method.
  490. # These things make up the calling stack leading up to the actual global
  491. # Export() or SConscript() call that the user issued. We want to allow
  492. # users to export local variables that they define, like so:
  493. #
  494. # def func():
  495. # x = 1
  496. # Export('x')
  497. #
  498. # To support this, the get_calling_namespaces() function assumes that
  499. # the *first* stack frame that's not from this file is the local frame
  500. # for the Export() or SConscript() call.
  501. _DefaultEnvironmentProxy = None
  502. def get_DefaultEnvironmentProxy():
  503. global _DefaultEnvironmentProxy
  504. if not _DefaultEnvironmentProxy:
  505. default_env = SCons.Defaults.DefaultEnvironment()
  506. _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
  507. return _DefaultEnvironmentProxy
  508. class DefaultEnvironmentCall(object):
  509. """A class that implements "global function" calls of
  510. Environment methods by fetching the specified method from the
  511. DefaultEnvironment's class. Note that this uses an intermediate
  512. proxy class instead of calling the DefaultEnvironment method
  513. directly so that the proxy can override the subst() method and
  514. thereby prevent expansion of construction variables (since from
  515. the user's point of view this was called as a global function,
  516. with no associated construction environment)."""
  517. def __init__(self, method_name, subst=0):
  518. self.method_name = method_name
  519. if subst:
  520. self.factory = SCons.Defaults.DefaultEnvironment
  521. else:
  522. self.factory = get_DefaultEnvironmentProxy
  523. def __call__(self, *args, **kw):
  524. env = self.factory()
  525. method = getattr(env, self.method_name)
  526. return method(*args, **kw)
  527. def BuildDefaultGlobals():
  528. """
  529. Create a dictionary containing all the default globals for
  530. SConstruct and SConscript files.
  531. """
  532. global GlobalDict
  533. if GlobalDict is None:
  534. GlobalDict = {}
  535. import SCons.Script
  536. d = SCons.Script.__dict__
  537. def not_a_module(m, d=d, mtype=type(SCons.Script)):
  538. return not isinstance(d[m], mtype)
  539. for m in filter(not_a_module, dir(SCons.Script)):
  540. GlobalDict[m] = d[m]
  541. return GlobalDict.copy()
  542. # Local Variables:
  543. # tab-width:4
  544. # indent-tabs-mode:nil
  545. # End:
  546. # vim: set expandtab tabstop=4 shiftwidth=4: