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.

3545 lines
122 KiB

6 years ago
  1. """scons.Node.FS
  2. File system nodes.
  3. These Nodes represent the canonical external objects that people think
  4. of when they think of building software: files and directories.
  5. This holds a "default_fs" variable that should be initialized with an FS
  6. that can be used by scripts or modules looking for the canonical default.
  7. """
  8. #
  9. # Copyright (c) 2001 - 2017 The SCons Foundation
  10. #
  11. # Permission is hereby granted, free of charge, to any person obtaining
  12. # a copy of this software and associated documentation files (the
  13. # "Software"), to deal in the Software without restriction, including
  14. # without limitation the rights to use, copy, modify, merge, publish,
  15. # distribute, sublicense, and/or sell copies of the Software, and to
  16. # permit persons to whom the Software is furnished to do so, subject to
  17. # the following conditions:
  18. #
  19. # The above copyright notice and this permission notice shall be included
  20. # in all copies or substantial portions of the Software.
  21. #
  22. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  23. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  24. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. from __future__ import print_function
  30. __revision__ = "src/engine/SCons/Node/FS.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
  31. import fnmatch
  32. import os
  33. import re
  34. import shutil
  35. import stat
  36. import sys
  37. import time
  38. import codecs
  39. import SCons.Action
  40. import SCons.Debug
  41. from SCons.Debug import logInstanceCreation
  42. import SCons.Errors
  43. import SCons.Memoize
  44. import SCons.Node
  45. import SCons.Node.Alias
  46. import SCons.Subst
  47. import SCons.Util
  48. import SCons.Warnings
  49. from SCons.Debug import Trace
  50. print_duplicate = 0
  51. def sconsign_none(node):
  52. raise NotImplementedError
  53. def sconsign_dir(node):
  54. """Return the .sconsign file info for this directory,
  55. creating it first if necessary."""
  56. if not node._sconsign:
  57. import SCons.SConsign
  58. node._sconsign = SCons.SConsign.ForDirectory(node)
  59. return node._sconsign
  60. _sconsign_map = {0 : sconsign_none,
  61. 1 : sconsign_dir}
  62. class EntryProxyAttributeError(AttributeError):
  63. """
  64. An AttributeError subclass for recording and displaying the name
  65. of the underlying Entry involved in an AttributeError exception.
  66. """
  67. def __init__(self, entry_proxy, attribute):
  68. AttributeError.__init__(self)
  69. self.entry_proxy = entry_proxy
  70. self.attribute = attribute
  71. def __str__(self):
  72. entry = self.entry_proxy.get()
  73. fmt = "%s instance %s has no attribute %s"
  74. return fmt % (entry.__class__.__name__,
  75. repr(entry.name),
  76. repr(self.attribute))
  77. # The max_drift value: by default, use a cached signature value for
  78. # any file that's been untouched for more than two days.
  79. default_max_drift = 2*24*60*60
  80. #
  81. # We stringify these file system Nodes a lot. Turning a file system Node
  82. # into a string is non-trivial, because the final string representation
  83. # can depend on a lot of factors: whether it's a derived target or not,
  84. # whether it's linked to a repository or source directory, and whether
  85. # there's duplication going on. The normal technique for optimizing
  86. # calculations like this is to memoize (cache) the string value, so you
  87. # only have to do the calculation once.
  88. #
  89. # A number of the above factors, however, can be set after we've already
  90. # been asked to return a string for a Node, because a Repository() or
  91. # VariantDir() call or the like may not occur until later in SConscript
  92. # files. So this variable controls whether we bother trying to save
  93. # string values for Nodes. The wrapper interface can set this whenever
  94. # they're done mucking with Repository and VariantDir and the other stuff,
  95. # to let this module know it can start returning saved string values
  96. # for Nodes.
  97. #
  98. Save_Strings = None
  99. def save_strings(val):
  100. global Save_Strings
  101. Save_Strings = val
  102. #
  103. # Avoid unnecessary function calls by recording a Boolean value that
  104. # tells us whether or not os.path.splitdrive() actually does anything
  105. # on this system, and therefore whether we need to bother calling it
  106. # when looking up path names in various methods below.
  107. #
  108. do_splitdrive = None
  109. _my_splitdrive =None
  110. def initialize_do_splitdrive():
  111. global do_splitdrive
  112. global has_unc
  113. drive, path = os.path.splitdrive('X:/foo')
  114. has_unc = hasattr(os.path, 'splitunc')
  115. do_splitdrive = not not drive or has_unc
  116. global _my_splitdrive
  117. if has_unc:
  118. def splitdrive(p):
  119. if p[1:2] == ':':
  120. return p[:2], p[2:]
  121. if p[0:2] == '//':
  122. # Note that we leave a leading slash in the path
  123. # because UNC paths are always absolute.
  124. return '//', p[1:]
  125. return '', p
  126. else:
  127. def splitdrive(p):
  128. if p[1:2] == ':':
  129. return p[:2], p[2:]
  130. return '', p
  131. _my_splitdrive = splitdrive
  132. # Keep some commonly used values in global variables to skip to
  133. # module look-up costs.
  134. global OS_SEP
  135. global UNC_PREFIX
  136. global os_sep_is_slash
  137. OS_SEP = os.sep
  138. UNC_PREFIX = OS_SEP + OS_SEP
  139. os_sep_is_slash = OS_SEP == '/'
  140. initialize_do_splitdrive()
  141. # Used to avoid invoking os.path.normpath if not necessary.
  142. needs_normpath_check = re.compile(
  143. r'''
  144. # We need to renormalize the path if it contains any consecutive
  145. # '/' characters.
  146. .*// |
  147. # We need to renormalize the path if it contains a '..' directory.
  148. # Note that we check for all the following cases:
  149. #
  150. # a) The path is a single '..'
  151. # b) The path starts with '..'. E.g. '../' or '../moredirs'
  152. # but we not match '..abc/'.
  153. # c) The path ends with '..'. E.g. '/..' or 'dirs/..'
  154. # d) The path contains a '..' in the middle.
  155. # E.g. dirs/../moredirs
  156. (.*/)?\.\.(?:/|$) |
  157. # We need to renormalize the path if it contains a '.'
  158. # directory, but NOT if it is a single '.' '/' characters. We
  159. # do not want to match a single '.' because this case is checked
  160. # for explicitly since this is common enough case.
  161. #
  162. # Note that we check for all the following cases:
  163. #
  164. # a) We don't match a single '.'
  165. # b) We match if the path starts with '.'. E.g. './' or
  166. # './moredirs' but we not match '.abc/'.
  167. # c) We match if the path ends with '.'. E.g. '/.' or
  168. # 'dirs/.'
  169. # d) We match if the path contains a '.' in the middle.
  170. # E.g. dirs/./moredirs
  171. \./|.*/\.(?:/|$)
  172. ''',
  173. re.VERBOSE
  174. )
  175. needs_normpath_match = needs_normpath_check.match
  176. #
  177. # SCons.Action objects for interacting with the outside world.
  178. #
  179. # The Node.FS methods in this module should use these actions to
  180. # create and/or remove files and directories; they should *not* use
  181. # os.{link,symlink,unlink,mkdir}(), etc., directly.
  182. #
  183. # Using these SCons.Action objects ensures that descriptions of these
  184. # external activities are properly displayed, that the displays are
  185. # suppressed when the -s (silent) option is used, and (most importantly)
  186. # the actions are disabled when the the -n option is used, in which case
  187. # there should be *no* changes to the external file system(s)...
  188. #
  189. # For Now disable hard & softlinks for win32
  190. # PY3 supports them, but the rest of SCons is not ready for this
  191. # in some cases user permissions may be required.
  192. # TODO: See if theres a reasonable way to enable using links on win32/64
  193. if hasattr(os, 'link') and sys.platform != 'win32':
  194. def _hardlink_func(fs, src, dst):
  195. # If the source is a symlink, we can't just hard-link to it
  196. # because a relative symlink may point somewhere completely
  197. # different. We must disambiguate the symlink and then
  198. # hard-link the final destination file.
  199. while fs.islink(src):
  200. link = fs.readlink(src)
  201. if not os.path.isabs(link):
  202. src = link
  203. else:
  204. src = os.path.join(os.path.dirname(src), link)
  205. fs.link(src, dst)
  206. else:
  207. _hardlink_func = None
  208. if hasattr(os, 'symlink') and sys.platform != 'win32':
  209. def _softlink_func(fs, src, dst):
  210. fs.symlink(src, dst)
  211. else:
  212. _softlink_func = None
  213. def _copy_func(fs, src, dest):
  214. shutil.copy2(src, dest)
  215. st = fs.stat(src)
  216. fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
  217. Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
  218. 'hard-copy', 'soft-copy', 'copy']
  219. Link_Funcs = [] # contains the callables of the specified duplication style
  220. def set_duplicate(duplicate):
  221. # Fill in the Link_Funcs list according to the argument
  222. # (discarding those not available on the platform).
  223. # Set up the dictionary that maps the argument names to the
  224. # underlying implementations. We do this inside this function,
  225. # not in the top-level module code, so that we can remap os.link
  226. # and os.symlink for testing purposes.
  227. link_dict = {
  228. 'hard' : _hardlink_func,
  229. 'soft' : _softlink_func,
  230. 'copy' : _copy_func
  231. }
  232. if not duplicate in Valid_Duplicates:
  233. raise SCons.Errors.InternalError("The argument of set_duplicate "
  234. "should be in Valid_Duplicates")
  235. global Link_Funcs
  236. Link_Funcs = []
  237. for func in duplicate.split('-'):
  238. if link_dict[func]:
  239. Link_Funcs.append(link_dict[func])
  240. def LinkFunc(target, source, env):
  241. # Relative paths cause problems with symbolic links, so
  242. # we use absolute paths, which may be a problem for people
  243. # who want to move their soft-linked src-trees around. Those
  244. # people should use the 'hard-copy' mode, softlinks cannot be
  245. # used for that; at least I have no idea how ...
  246. src = source[0].get_abspath()
  247. dest = target[0].get_abspath()
  248. dir, file = os.path.split(dest)
  249. if dir and not target[0].fs.isdir(dir):
  250. os.makedirs(dir)
  251. if not Link_Funcs:
  252. # Set a default order of link functions.
  253. set_duplicate('hard-soft-copy')
  254. fs = source[0].fs
  255. # Now link the files with the previously specified order.
  256. for func in Link_Funcs:
  257. try:
  258. func(fs, src, dest)
  259. break
  260. except (IOError, OSError):
  261. # An OSError indicates something happened like a permissions
  262. # problem or an attempt to symlink across file-system
  263. # boundaries. An IOError indicates something like the file
  264. # not existing. In either case, keeping trying additional
  265. # functions in the list and only raise an error if the last
  266. # one failed.
  267. if func == Link_Funcs[-1]:
  268. # exception of the last link method (copy) are fatal
  269. raise
  270. return 0
  271. Link = SCons.Action.Action(LinkFunc, None)
  272. def LocalString(target, source, env):
  273. return 'Local copy of %s from %s' % (target[0], source[0])
  274. LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
  275. def UnlinkFunc(target, source, env):
  276. t = target[0]
  277. t.fs.unlink(t.get_abspath())
  278. return 0
  279. Unlink = SCons.Action.Action(UnlinkFunc, None)
  280. def MkdirFunc(target, source, env):
  281. t = target[0]
  282. if not t.exists():
  283. t.fs.mkdir(t.get_abspath())
  284. return 0
  285. Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
  286. MkdirBuilder = None
  287. def get_MkdirBuilder():
  288. global MkdirBuilder
  289. if MkdirBuilder is None:
  290. import SCons.Builder
  291. import SCons.Defaults
  292. # "env" will get filled in by Executor.get_build_env()
  293. # calling SCons.Defaults.DefaultEnvironment() when necessary.
  294. MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
  295. env = None,
  296. explain = None,
  297. is_explicit = None,
  298. target_scanner = SCons.Defaults.DirEntryScanner,
  299. name = "MkdirBuilder")
  300. return MkdirBuilder
  301. class _Null(object):
  302. pass
  303. _null = _Null()
  304. # Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
  305. _is_cygwin = sys.platform == "cygwin"
  306. if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
  307. def _my_normcase(x):
  308. return x
  309. else:
  310. def _my_normcase(x):
  311. return x.upper()
  312. class DiskChecker(object):
  313. def __init__(self, type, do, ignore):
  314. self.type = type
  315. self.do = do
  316. self.ignore = ignore
  317. self.func = do
  318. def __call__(self, *args, **kw):
  319. return self.func(*args, **kw)
  320. def set(self, list):
  321. if self.type in list:
  322. self.func = self.do
  323. else:
  324. self.func = self.ignore
  325. def do_diskcheck_match(node, predicate, errorfmt):
  326. result = predicate()
  327. try:
  328. # If calling the predicate() cached a None value from stat(),
  329. # remove it so it doesn't interfere with later attempts to
  330. # build this Node as we walk the DAG. (This isn't a great way
  331. # to do this, we're reaching into an interface that doesn't
  332. # really belong to us, but it's all about performance, so
  333. # for now we'll just document the dependency...)
  334. if node._memo['stat'] is None:
  335. del node._memo['stat']
  336. except (AttributeError, KeyError):
  337. pass
  338. if result:
  339. raise TypeError(errorfmt % node.get_abspath())
  340. def ignore_diskcheck_match(node, predicate, errorfmt):
  341. pass
  342. diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
  343. diskcheckers = [
  344. diskcheck_match,
  345. ]
  346. def set_diskcheck(list):
  347. for dc in diskcheckers:
  348. dc.set(list)
  349. def diskcheck_types():
  350. return [dc.type for dc in diskcheckers]
  351. class EntryProxy(SCons.Util.Proxy):
  352. __str__ = SCons.Util.Delegate('__str__')
  353. # In PY3 if a class defines __eq__, then it must explicitly provide
  354. # __hash__. Since SCons.Util.Proxy provides __eq__ we need the following
  355. # see: https://docs.python.org/3.1/reference/datamodel.html#object.__hash__
  356. __hash__ = SCons.Util.Delegate('__hash__')
  357. def __get_abspath(self):
  358. entry = self.get()
  359. return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
  360. entry.name + "_abspath")
  361. def __get_filebase(self):
  362. name = self.get().name
  363. return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
  364. name + "_filebase")
  365. def __get_suffix(self):
  366. name = self.get().name
  367. return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
  368. name + "_suffix")
  369. def __get_file(self):
  370. name = self.get().name
  371. return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
  372. def __get_base_path(self):
  373. """Return the file's directory and file name, with the
  374. suffix stripped."""
  375. entry = self.get()
  376. return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
  377. entry.name + "_base")
  378. def __get_posix_path(self):
  379. """Return the path with / as the path separator,
  380. regardless of platform."""
  381. if os_sep_is_slash:
  382. return self
  383. else:
  384. entry = self.get()
  385. r = entry.get_path().replace(OS_SEP, '/')
  386. return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
  387. def __get_windows_path(self):
  388. """Return the path with \ as the path separator,
  389. regardless of platform."""
  390. if OS_SEP == '\\':
  391. return self
  392. else:
  393. entry = self.get()
  394. r = entry.get_path().replace(OS_SEP, '\\')
  395. return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
  396. def __get_srcnode(self):
  397. return EntryProxy(self.get().srcnode())
  398. def __get_srcdir(self):
  399. """Returns the directory containing the source node linked to this
  400. node via VariantDir(), or the directory of this node if not linked."""
  401. return EntryProxy(self.get().srcnode().dir)
  402. def __get_rsrcnode(self):
  403. return EntryProxy(self.get().srcnode().rfile())
  404. def __get_rsrcdir(self):
  405. """Returns the directory containing the source node linked to this
  406. node via VariantDir(), or the directory of this node if not linked."""
  407. return EntryProxy(self.get().srcnode().rfile().dir)
  408. def __get_dir(self):
  409. return EntryProxy(self.get().dir)
  410. dictSpecialAttrs = { "base" : __get_base_path,
  411. "posix" : __get_posix_path,
  412. "windows" : __get_windows_path,
  413. "win32" : __get_windows_path,
  414. "srcpath" : __get_srcnode,
  415. "srcdir" : __get_srcdir,
  416. "dir" : __get_dir,
  417. "abspath" : __get_abspath,
  418. "filebase" : __get_filebase,
  419. "suffix" : __get_suffix,
  420. "file" : __get_file,
  421. "rsrcpath" : __get_rsrcnode,
  422. "rsrcdir" : __get_rsrcdir,
  423. }
  424. def __getattr__(self, name):
  425. # This is how we implement the "special" attributes
  426. # such as base, posix, srcdir, etc.
  427. try:
  428. attr_function = self.dictSpecialAttrs[name]
  429. except KeyError:
  430. try:
  431. attr = SCons.Util.Proxy.__getattr__(self, name)
  432. except AttributeError as e:
  433. # Raise our own AttributeError subclass with an
  434. # overridden __str__() method that identifies the
  435. # name of the entry that caused the exception.
  436. raise EntryProxyAttributeError(self, name)
  437. return attr
  438. else:
  439. return attr_function(self)
  440. class Base(SCons.Node.Node):
  441. """A generic class for file system entries. This class is for
  442. when we don't know yet whether the entry being looked up is a file
  443. or a directory. Instances of this class can morph into either
  444. Dir or File objects by a later, more precise lookup.
  445. Note: this class does not define __cmp__ and __hash__ for
  446. efficiency reasons. SCons does a lot of comparing of
  447. Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
  448. as fast as possible, which means we want to use Python's built-in
  449. object identity comparisons.
  450. """
  451. __slots__ = ['name',
  452. 'fs',
  453. '_abspath',
  454. '_labspath',
  455. '_path',
  456. '_tpath',
  457. '_path_elements',
  458. 'dir',
  459. 'cwd',
  460. 'duplicate',
  461. '_local',
  462. 'sbuilder',
  463. '_proxy',
  464. '_func_sconsign']
  465. def __init__(self, name, directory, fs):
  466. """Initialize a generic Node.FS.Base object.
  467. Call the superclass initialization, take care of setting up
  468. our relative and absolute paths, identify our parent
  469. directory, and indicate that this node should use
  470. signatures."""
  471. if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Base')
  472. SCons.Node.Node.__init__(self)
  473. # Filenames and paths are probably reused and are intern'ed to save some memory.
  474. # Filename with extension as it was specified when the object was
  475. # created; to obtain filesystem path, use Python str() function
  476. self.name = SCons.Util.silent_intern(name)
  477. self.fs = fs #: Reference to parent Node.FS object
  478. assert directory, "A directory must be provided"
  479. self._abspath = None
  480. self._labspath = None
  481. self._path = None
  482. self._tpath = None
  483. self._path_elements = None
  484. self.dir = directory
  485. self.cwd = None # will hold the SConscript directory for target nodes
  486. self.duplicate = directory.duplicate
  487. self.changed_since_last_build = 2
  488. self._func_sconsign = 0
  489. self._func_exists = 2
  490. self._func_rexists = 2
  491. self._func_get_contents = 0
  492. self._func_target_from_source = 1
  493. self.store_info = 1
  494. def str_for_display(self):
  495. return '"' + self.__str__() + '"'
  496. def must_be_same(self, klass):
  497. """
  498. This node, which already existed, is being looked up as the
  499. specified klass. Raise an exception if it isn't.
  500. """
  501. if isinstance(self, klass) or klass is Entry:
  502. return
  503. raise TypeError("Tried to lookup %s '%s' as a %s." %\
  504. (self.__class__.__name__, self.get_internal_path(), klass.__name__))
  505. def get_dir(self):
  506. return self.dir
  507. def get_suffix(self):
  508. return SCons.Util.splitext(self.name)[1]
  509. def rfile(self):
  510. return self
  511. def __getattr__(self, attr):
  512. """ Together with the node_bwcomp dict defined below,
  513. this method provides a simple backward compatibility
  514. layer for the Node attributes 'abspath', 'labspath',
  515. 'path', 'tpath', 'suffix' and 'path_elements'. These Node
  516. attributes used to be directly available in v2.3 and earlier, but
  517. have been replaced by getter methods that initialize the
  518. single variables lazily when required, in order to save memory.
  519. The redirection to the getters lets older Tools and
  520. SConstruct continue to work without any additional changes,
  521. fully transparent to the user.
  522. Note, that __getattr__ is only called as fallback when the
  523. requested attribute can't be found, so there should be no
  524. speed performance penalty involved for standard builds.
  525. """
  526. if attr in node_bwcomp:
  527. return node_bwcomp[attr](self)
  528. raise AttributeError("%r object has no attribute %r" %
  529. (self.__class__, attr))
  530. def __str__(self):
  531. """A Node.FS.Base object's string representation is its path
  532. name."""
  533. global Save_Strings
  534. if Save_Strings:
  535. return self._save_str()
  536. return self._get_str()
  537. def __lt__(self, other):
  538. """ less than operator used by sorting on py3"""
  539. return str(self) < str(other)
  540. @SCons.Memoize.CountMethodCall
  541. def _save_str(self):
  542. try:
  543. return self._memo['_save_str']
  544. except KeyError:
  545. pass
  546. result = SCons.Util.silent_intern(self._get_str())
  547. self._memo['_save_str'] = result
  548. return result
  549. def _get_str(self):
  550. global Save_Strings
  551. if self.duplicate or self.is_derived():
  552. return self.get_path()
  553. srcnode = self.srcnode()
  554. if srcnode.stat() is None and self.stat() is not None:
  555. result = self.get_path()
  556. else:
  557. result = srcnode.get_path()
  558. if not Save_Strings:
  559. # We're not at the point where we're saving the string
  560. # representations of FS Nodes (because we haven't finished
  561. # reading the SConscript files and need to have str() return
  562. # things relative to them). That also means we can't yet
  563. # cache values returned (or not returned) by stat(), since
  564. # Python code in the SConscript files might still create
  565. # or otherwise affect the on-disk file. So get rid of the
  566. # values that the underlying stat() method saved.
  567. try: del self._memo['stat']
  568. except KeyError: pass
  569. if self is not srcnode:
  570. try: del srcnode._memo['stat']
  571. except KeyError: pass
  572. return result
  573. rstr = __str__
  574. @SCons.Memoize.CountMethodCall
  575. def stat(self):
  576. try: return self._memo['stat']
  577. except KeyError: pass
  578. try: result = self.fs.stat(self.get_abspath())
  579. except os.error: result = None
  580. self._memo['stat'] = result
  581. return result
  582. def exists(self):
  583. return SCons.Node._exists_map[self._func_exists](self)
  584. def rexists(self):
  585. return SCons.Node._rexists_map[self._func_rexists](self)
  586. def getmtime(self):
  587. st = self.stat()
  588. if st: return st[stat.ST_MTIME]
  589. else: return None
  590. def getsize(self):
  591. st = self.stat()
  592. if st: return st[stat.ST_SIZE]
  593. else: return None
  594. def isdir(self):
  595. st = self.stat()
  596. return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
  597. def isfile(self):
  598. st = self.stat()
  599. return st is not None and stat.S_ISREG(st[stat.ST_MODE])
  600. if hasattr(os, 'symlink'):
  601. def islink(self):
  602. try: st = self.fs.lstat(self.get_abspath())
  603. except os.error: return 0
  604. return stat.S_ISLNK(st[stat.ST_MODE])
  605. else:
  606. def islink(self):
  607. return 0 # no symlinks
  608. def is_under(self, dir):
  609. if self is dir:
  610. return 1
  611. else:
  612. return self.dir.is_under(dir)
  613. def set_local(self):
  614. self._local = 1
  615. def srcnode(self):
  616. """If this node is in a build path, return the node
  617. corresponding to its source file. Otherwise, return
  618. ourself.
  619. """
  620. srcdir_list = self.dir.srcdir_list()
  621. if srcdir_list:
  622. srcnode = srcdir_list[0].Entry(self.name)
  623. srcnode.must_be_same(self.__class__)
  624. return srcnode
  625. return self
  626. def get_path(self, dir=None):
  627. """Return path relative to the current working directory of the
  628. Node.FS.Base object that owns us."""
  629. if not dir:
  630. dir = self.fs.getcwd()
  631. if self == dir:
  632. return '.'
  633. path_elems = self.get_path_elements()
  634. pathname = ''
  635. try: i = path_elems.index(dir)
  636. except ValueError:
  637. for p in path_elems[:-1]:
  638. pathname += p.dirname
  639. else:
  640. for p in path_elems[i+1:-1]:
  641. pathname += p.dirname
  642. return pathname + path_elems[-1].name
  643. def set_src_builder(self, builder):
  644. """Set the source code builder for this node."""
  645. self.sbuilder = builder
  646. if not self.has_builder():
  647. self.builder_set(builder)
  648. def src_builder(self):
  649. """Fetch the source code builder for this node.
  650. If there isn't one, we cache the source code builder specified
  651. for the directory (which in turn will cache the value from its
  652. parent directory, and so on up to the file system root).
  653. """
  654. try:
  655. scb = self.sbuilder
  656. except AttributeError:
  657. scb = self.dir.src_builder()
  658. self.sbuilder = scb
  659. return scb
  660. def get_abspath(self):
  661. """Get the absolute path of the file."""
  662. return self.dir.entry_abspath(self.name)
  663. def get_labspath(self):
  664. """Get the absolute path of the file."""
  665. return self.dir.entry_labspath(self.name)
  666. def get_internal_path(self):
  667. if self.dir._path == '.':
  668. return self.name
  669. else:
  670. return self.dir.entry_path(self.name)
  671. def get_tpath(self):
  672. if self.dir._tpath == '.':
  673. return self.name
  674. else:
  675. return self.dir.entry_tpath(self.name)
  676. def get_path_elements(self):
  677. return self.dir._path_elements + [self]
  678. def for_signature(self):
  679. # Return just our name. Even an absolute path would not work,
  680. # because that can change thanks to symlinks or remapped network
  681. # paths.
  682. return self.name
  683. def get_subst_proxy(self):
  684. try:
  685. return self._proxy
  686. except AttributeError:
  687. ret = EntryProxy(self)
  688. self._proxy = ret
  689. return ret
  690. def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
  691. """
  692. Generates a target entry that corresponds to this entry (usually
  693. a source file) with the specified prefix and suffix.
  694. Note that this method can be overridden dynamically for generated
  695. files that need different behavior. See Tool/swig.py for
  696. an example.
  697. """
  698. return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
  699. def _Rfindalldirs_key(self, pathlist):
  700. return pathlist
  701. @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
  702. def Rfindalldirs(self, pathlist):
  703. """
  704. Return all of the directories for a given path list, including
  705. corresponding "backing" directories in any repositories.
  706. The Node lookups are relative to this Node (typically a
  707. directory), so memoizing result saves cycles from looking
  708. up the same path for each target in a given directory.
  709. """
  710. try:
  711. memo_dict = self._memo['Rfindalldirs']
  712. except KeyError:
  713. memo_dict = {}
  714. self._memo['Rfindalldirs'] = memo_dict
  715. else:
  716. try:
  717. return memo_dict[pathlist]
  718. except KeyError:
  719. pass
  720. create_dir_relative_to_self = self.Dir
  721. result = []
  722. for path in pathlist:
  723. if isinstance(path, SCons.Node.Node):
  724. result.append(path)
  725. else:
  726. dir = create_dir_relative_to_self(path)
  727. result.extend(dir.get_all_rdirs())
  728. memo_dict[pathlist] = result
  729. return result
  730. def RDirs(self, pathlist):
  731. """Search for a list of directories in the Repository list."""
  732. cwd = self.cwd or self.fs._cwd
  733. return cwd.Rfindalldirs(pathlist)
  734. @SCons.Memoize.CountMethodCall
  735. def rentry(self):
  736. try:
  737. return self._memo['rentry']
  738. except KeyError:
  739. pass
  740. result = self
  741. if not self.exists():
  742. norm_name = _my_normcase(self.name)
  743. for dir in self.dir.get_all_rdirs():
  744. try:
  745. node = dir.entries[norm_name]
  746. except KeyError:
  747. if dir.entry_exists_on_disk(self.name):
  748. result = dir.Entry(self.name)
  749. break
  750. self._memo['rentry'] = result
  751. return result
  752. def _glob1(self, pattern, ondisk=True, source=False, strings=False):
  753. return []
  754. # Dict that provides a simple backward compatibility
  755. # layer for the Node attributes 'abspath', 'labspath',
  756. # 'path', 'tpath' and 'path_elements'.
  757. # @see Base.__getattr__ above
  758. node_bwcomp = {'abspath' : Base.get_abspath,
  759. 'labspath' : Base.get_labspath,
  760. 'path' : Base.get_internal_path,
  761. 'tpath' : Base.get_tpath,
  762. 'path_elements' : Base.get_path_elements,
  763. 'suffix' : Base.get_suffix}
  764. class Entry(Base):
  765. """This is the class for generic Node.FS entries--that is, things
  766. that could be a File or a Dir, but we're just not sure yet.
  767. Consequently, the methods in this class really exist just to
  768. transform their associated object into the right class when the
  769. time comes, and then call the same-named method in the transformed
  770. class."""
  771. __slots__ = ['scanner_paths',
  772. 'cachedir_csig',
  773. 'cachesig',
  774. 'repositories',
  775. 'srcdir',
  776. 'entries',
  777. 'searched',
  778. '_sconsign',
  779. 'variant_dirs',
  780. 'root',
  781. 'dirname',
  782. 'on_disk_entries',
  783. 'released_target_info',
  784. 'contentsig']
  785. def __init__(self, name, directory, fs):
  786. Base.__init__(self, name, directory, fs)
  787. self._func_exists = 3
  788. self._func_get_contents = 1
  789. def diskcheck_match(self):
  790. pass
  791. def disambiguate(self, must_exist=None):
  792. """
  793. """
  794. if self.isdir():
  795. self.__class__ = Dir
  796. self._morph()
  797. elif self.isfile():
  798. self.__class__ = File
  799. self._morph()
  800. self.clear()
  801. else:
  802. # There was nothing on-disk at this location, so look in
  803. # the src directory.
  804. #
  805. # We can't just use self.srcnode() straight away because
  806. # that would create an actual Node for this file in the src
  807. # directory, and there might not be one. Instead, use the
  808. # dir_on_disk() method to see if there's something on-disk
  809. # with that name, in which case we can go ahead and call
  810. # self.srcnode() to create the right type of entry.
  811. srcdir = self.dir.srcnode()
  812. if srcdir != self.dir and \
  813. srcdir.entry_exists_on_disk(self.name) and \
  814. self.srcnode().isdir():
  815. self.__class__ = Dir
  816. self._morph()
  817. elif must_exist:
  818. msg = "No such file or directory: '%s'" % self.get_abspath()
  819. raise SCons.Errors.UserError(msg)
  820. else:
  821. self.__class__ = File
  822. self._morph()
  823. self.clear()
  824. return self
  825. def rfile(self):
  826. """We're a generic Entry, but the caller is actually looking for
  827. a File at this point, so morph into one."""
  828. self.__class__ = File
  829. self._morph()
  830. self.clear()
  831. return File.rfile(self)
  832. def scanner_key(self):
  833. return self.get_suffix()
  834. def get_contents(self):
  835. """Fetch the contents of the entry. Returns the exact binary
  836. contents of the file."""
  837. return SCons.Node._get_contents_map[self._func_get_contents](self)
  838. def get_text_contents(self):
  839. """Fetch the decoded text contents of a Unicode encoded Entry.
  840. Since this should return the text contents from the file
  841. system, we check to see into what sort of subclass we should
  842. morph this Entry."""
  843. try:
  844. self = self.disambiguate(must_exist=1)
  845. except SCons.Errors.UserError:
  846. # There was nothing on disk with which to disambiguate
  847. # this entry. Leave it as an Entry, but return a null
  848. # string so calls to get_text_contents() in emitters and
  849. # the like (e.g. in qt.py) don't have to disambiguate by
  850. # hand or catch the exception.
  851. return ''
  852. else:
  853. return self.get_text_contents()
  854. def must_be_same(self, klass):
  855. """Called to make sure a Node is a Dir. Since we're an
  856. Entry, we can morph into one."""
  857. if self.__class__ is not klass:
  858. self.__class__ = klass
  859. self._morph()
  860. self.clear()
  861. # The following methods can get called before the Taskmaster has
  862. # had a chance to call disambiguate() directly to see if this Entry
  863. # should really be a Dir or a File. We therefore use these to call
  864. # disambiguate() transparently (from our caller's point of view).
  865. #
  866. # Right now, this minimal set of methods has been derived by just
  867. # looking at some of the methods that will obviously be called early
  868. # in any of the various Taskmasters' calling sequences, and then
  869. # empirically figuring out which additional methods are necessary
  870. # to make various tests pass.
  871. def exists(self):
  872. return SCons.Node._exists_map[self._func_exists](self)
  873. def rel_path(self, other):
  874. d = self.disambiguate()
  875. if d.__class__ is Entry:
  876. raise Exception("rel_path() could not disambiguate File/Dir")
  877. return d.rel_path(other)
  878. def new_ninfo(self):
  879. return self.disambiguate().new_ninfo()
  880. def _glob1(self, pattern, ondisk=True, source=False, strings=False):
  881. return self.disambiguate()._glob1(pattern, ondisk, source, strings)
  882. def get_subst_proxy(self):
  883. return self.disambiguate().get_subst_proxy()
  884. # This is for later so we can differentiate between Entry the class and Entry
  885. # the method of the FS class.
  886. _classEntry = Entry
  887. class LocalFS(object):
  888. # This class implements an abstraction layer for operations involving
  889. # a local file system. Essentially, this wraps any function in
  890. # the os, os.path or shutil modules that we use to actually go do
  891. # anything with or to the local file system.
  892. #
  893. # Note that there's a very good chance we'll refactor this part of
  894. # the architecture in some way as we really implement the interface(s)
  895. # for remote file system Nodes. For example, the right architecture
  896. # might be to have this be a subclass instead of a base class.
  897. # Nevertheless, we're using this as a first step in that direction.
  898. #
  899. # We're not using chdir() yet because the calling subclass method
  900. # needs to use os.chdir() directly to avoid recursion. Will we
  901. # really need this one?
  902. #def chdir(self, path):
  903. # return os.chdir(path)
  904. def chmod(self, path, mode):
  905. return os.chmod(path, mode)
  906. def copy(self, src, dst):
  907. return shutil.copy(src, dst)
  908. def copy2(self, src, dst):
  909. return shutil.copy2(src, dst)
  910. def exists(self, path):
  911. return os.path.exists(path)
  912. def getmtime(self, path):
  913. return os.path.getmtime(path)
  914. def getsize(self, path):
  915. return os.path.getsize(path)
  916. def isdir(self, path):
  917. return os.path.isdir(path)
  918. def isfile(self, path):
  919. return os.path.isfile(path)
  920. def link(self, src, dst):
  921. return os.link(src, dst)
  922. def lstat(self, path):
  923. return os.lstat(path)
  924. def listdir(self, path):
  925. return os.listdir(path)
  926. def makedirs(self, path):
  927. return os.makedirs(path)
  928. def mkdir(self, path):
  929. return os.mkdir(path)
  930. def rename(self, old, new):
  931. return os.rename(old, new)
  932. def stat(self, path):
  933. return os.stat(path)
  934. def symlink(self, src, dst):
  935. return os.symlink(src, dst)
  936. def open(self, path):
  937. return open(path)
  938. def unlink(self, path):
  939. return os.unlink(path)
  940. if hasattr(os, 'symlink'):
  941. def islink(self, path):
  942. return os.path.islink(path)
  943. else:
  944. def islink(self, path):
  945. return 0 # no symlinks
  946. if hasattr(os, 'readlink'):
  947. def readlink(self, file):
  948. return os.readlink(file)
  949. else:
  950. def readlink(self, file):
  951. return ''
  952. class FS(LocalFS):
  953. def __init__(self, path = None):
  954. """Initialize the Node.FS subsystem.
  955. The supplied path is the top of the source tree, where we
  956. expect to find the top-level build file. If no path is
  957. supplied, the current directory is the default.
  958. The path argument must be a valid absolute path.
  959. """
  960. if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS')
  961. self._memo = {}
  962. self.Root = {}
  963. self.SConstruct_dir = None
  964. self.max_drift = default_max_drift
  965. self.Top = None
  966. if path is None:
  967. self.pathTop = os.getcwd()
  968. else:
  969. self.pathTop = path
  970. self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
  971. self.Top = self.Dir(self.pathTop)
  972. self.Top._path = '.'
  973. self.Top._tpath = '.'
  974. self._cwd = self.Top
  975. DirNodeInfo.fs = self
  976. FileNodeInfo.fs = self
  977. def set_SConstruct_dir(self, dir):
  978. self.SConstruct_dir = dir
  979. def get_max_drift(self):
  980. return self.max_drift
  981. def set_max_drift(self, max_drift):
  982. self.max_drift = max_drift
  983. def getcwd(self):
  984. if hasattr(self, "_cwd"):
  985. return self._cwd
  986. else:
  987. return "<no cwd>"
  988. def chdir(self, dir, change_os_dir=0):
  989. """Change the current working directory for lookups.
  990. If change_os_dir is true, we will also change the "real" cwd
  991. to match.
  992. """
  993. curr=self._cwd
  994. try:
  995. if dir is not None:
  996. self._cwd = dir
  997. if change_os_dir:
  998. os.chdir(dir.get_abspath())
  999. except OSError:
  1000. self._cwd = curr
  1001. raise
  1002. def get_root(self, drive):
  1003. """
  1004. Returns the root directory for the specified drive, creating
  1005. it if necessary.
  1006. """
  1007. drive = _my_normcase(drive)
  1008. try:
  1009. return self.Root[drive]
  1010. except KeyError:
  1011. root = RootDir(drive, self)
  1012. self.Root[drive] = root
  1013. if not drive:
  1014. self.Root[self.defaultDrive] = root
  1015. elif drive == self.defaultDrive:
  1016. self.Root[''] = root
  1017. return root
  1018. def _lookup(self, p, directory, fsclass, create=1):
  1019. """
  1020. The generic entry point for Node lookup with user-supplied data.
  1021. This translates arbitrary input into a canonical Node.FS object
  1022. of the specified fsclass. The general approach for strings is
  1023. to turn it into a fully normalized absolute path and then call
  1024. the root directory's lookup_abs() method for the heavy lifting.
  1025. If the path name begins with '#', it is unconditionally
  1026. interpreted relative to the top-level directory of this FS. '#'
  1027. is treated as a synonym for the top-level SConstruct directory,
  1028. much like '~' is treated as a synonym for the user's home
  1029. directory in a UNIX shell. So both '#foo' and '#/foo' refer
  1030. to the 'foo' subdirectory underneath the top-level SConstruct
  1031. directory.
  1032. If the path name is relative, then the path is looked up relative
  1033. to the specified directory, or the current directory (self._cwd,
  1034. typically the SConscript directory) if the specified directory
  1035. is None.
  1036. """
  1037. if isinstance(p, Base):
  1038. # It's already a Node.FS object. Make sure it's the right
  1039. # class and return.
  1040. p.must_be_same(fsclass)
  1041. return p
  1042. # str(p) in case it's something like a proxy object
  1043. p = str(p)
  1044. if not os_sep_is_slash:
  1045. p = p.replace(OS_SEP, '/')
  1046. if p[0:1] == '#':
  1047. # There was an initial '#', so we strip it and override
  1048. # whatever directory they may have specified with the
  1049. # top-level SConstruct directory.
  1050. p = p[1:]
  1051. directory = self.Top
  1052. # There might be a drive letter following the
  1053. # '#'. Although it is not described in the SCons man page,
  1054. # the regression test suite explicitly tests for that
  1055. # syntax. It seems to mean the following thing:
  1056. #
  1057. # Assuming the the SCons top dir is in C:/xxx/yyy,
  1058. # '#X:/toto' means X:/xxx/yyy/toto.
  1059. #
  1060. # i.e. it assumes that the X: drive has a directory
  1061. # structure similar to the one found on drive C:.
  1062. if do_splitdrive:
  1063. drive, p = _my_splitdrive(p)
  1064. if drive:
  1065. root = self.get_root(drive)
  1066. else:
  1067. root = directory.root
  1068. else:
  1069. root = directory.root
  1070. # We can only strip trailing after splitting the drive
  1071. # since the drive might the UNC '//' prefix.
  1072. p = p.strip('/')
  1073. needs_normpath = needs_normpath_match(p)
  1074. # The path is relative to the top-level SCons directory.
  1075. if p in ('', '.'):
  1076. p = directory.get_labspath()
  1077. else:
  1078. p = directory.get_labspath() + '/' + p
  1079. else:
  1080. if do_splitdrive:
  1081. drive, p = _my_splitdrive(p)
  1082. if drive and not p:
  1083. # This causes a naked drive letter to be treated
  1084. # as a synonym for the root directory on that
  1085. # drive.
  1086. p = '/'
  1087. else:
  1088. drive = ''
  1089. # We can only strip trailing '/' since the drive might the
  1090. # UNC '//' prefix.
  1091. if p != '/':
  1092. p = p.rstrip('/')
  1093. needs_normpath = needs_normpath_match(p)
  1094. if p[0:1] == '/':
  1095. # Absolute path
  1096. root = self.get_root(drive)
  1097. else:
  1098. # This is a relative lookup or to the current directory
  1099. # (the path name is not absolute). Add the string to the
  1100. # appropriate directory lookup path, after which the whole
  1101. # thing gets normalized.
  1102. if directory:
  1103. if not isinstance(directory, Dir):
  1104. directory = self.Dir(directory)
  1105. else:
  1106. directory = self._cwd
  1107. if p in ('', '.'):
  1108. p = directory.get_labspath()
  1109. else:
  1110. p = directory.get_labspath() + '/' + p
  1111. if drive:
  1112. root = self.get_root(drive)
  1113. else:
  1114. root = directory.root
  1115. if needs_normpath is not None:
  1116. # Normalize a pathname. Will return the same result for
  1117. # equivalent paths.
  1118. #
  1119. # We take advantage of the fact that we have an absolute
  1120. # path here for sure. In addition, we know that the
  1121. # components of lookup path are separated by slashes at
  1122. # this point. Because of this, this code is about 2X
  1123. # faster than calling os.path.normpath() followed by
  1124. # replacing os.sep with '/' again.
  1125. ins = p.split('/')[1:]
  1126. outs = []
  1127. for d in ins:
  1128. if d == '..':
  1129. try:
  1130. outs.pop()
  1131. except IndexError:
  1132. pass
  1133. elif d not in ('', '.'):
  1134. outs.append(d)
  1135. p = '/' + '/'.join(outs)
  1136. return root._lookup_abs(p, fsclass, create)
  1137. def Entry(self, name, directory = None, create = 1):
  1138. """Look up or create a generic Entry node with the specified name.
  1139. If the name is a relative path (begins with ./, ../, or a file
  1140. name), then it is looked up relative to the supplied directory
  1141. node, or to the top level directory of the FS (supplied at
  1142. construction time) if no directory is supplied.
  1143. """
  1144. return self._lookup(name, directory, Entry, create)
  1145. def File(self, name, directory = None, create = 1):
  1146. """Look up or create a File node with the specified name. If
  1147. the name is a relative path (begins with ./, ../, or a file name),
  1148. then it is looked up relative to the supplied directory node,
  1149. or to the top level directory of the FS (supplied at construction
  1150. time) if no directory is supplied.
  1151. This method will raise TypeError if a directory is found at the
  1152. specified path.
  1153. """
  1154. return self._lookup(name, directory, File, create)
  1155. def Dir(self, name, directory = None, create = True):
  1156. """Look up or create a Dir node with the specified name. If
  1157. the name is a relative path (begins with ./, ../, or a file name),
  1158. then it is looked up relative to the supplied directory node,
  1159. or to the top level directory of the FS (supplied at construction
  1160. time) if no directory is supplied.
  1161. This method will raise TypeError if a normal file is found at the
  1162. specified path.
  1163. """
  1164. return self._lookup(name, directory, Dir, create)
  1165. def VariantDir(self, variant_dir, src_dir, duplicate=1):
  1166. """Link the supplied variant directory to the source directory
  1167. for purposes of building files."""
  1168. if not isinstance(src_dir, SCons.Node.Node):
  1169. src_dir = self.Dir(src_dir)
  1170. if not isinstance(variant_dir, SCons.Node.Node):
  1171. variant_dir = self.Dir(variant_dir)
  1172. if src_dir.is_under(variant_dir):
  1173. raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
  1174. if variant_dir.srcdir:
  1175. if variant_dir.srcdir == src_dir:
  1176. return # We already did this.
  1177. raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
  1178. variant_dir.link(src_dir, duplicate)
  1179. def Repository(self, *dirs):
  1180. """Specify Repository directories to search."""
  1181. for d in dirs:
  1182. if not isinstance(d, SCons.Node.Node):
  1183. d = self.Dir(d)
  1184. self.Top.addRepository(d)
  1185. def PyPackageDir(self, modulename):
  1186. """Locate the directory of a given python module name
  1187. For example scons might resolve to
  1188. Windows: C:\Python27\Lib\site-packages\scons-2.5.1
  1189. Linux: /usr/lib/scons
  1190. This can be useful when we want to determine a toolpath based on a python module name"""
  1191. dirpath = ''
  1192. if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] in (0,1,2,3,4)):
  1193. # Python2 Code
  1194. import imp
  1195. splitname = modulename.split('.')
  1196. srchpths = sys.path
  1197. for item in splitname:
  1198. file, path, desc = imp.find_module(item, srchpths)
  1199. if file is not None:
  1200. path = os.path.dirname(path)
  1201. srchpths = [path]
  1202. dirpath = path
  1203. else:
  1204. # Python3 Code
  1205. import importlib.util
  1206. modspec = importlib.util.find_spec(modulename)
  1207. dirpath = os.path.dirname(modspec.origin)
  1208. return self._lookup(dirpath, None, Dir, True)
  1209. def variant_dir_target_climb(self, orig, dir, tail):
  1210. """Create targets in corresponding variant directories
  1211. Climb the directory tree, and look up path names
  1212. relative to any linked variant directories we find.
  1213. Even though this loops and walks up the tree, we don't memoize
  1214. the return value because this is really only used to process
  1215. the command-line targets.
  1216. """
  1217. targets = []
  1218. message = None
  1219. fmt = "building associated VariantDir targets: %s"
  1220. start_dir = dir
  1221. while dir:
  1222. for bd in dir.variant_dirs:
  1223. if start_dir.is_under(bd):
  1224. # If already in the build-dir location, don't reflect
  1225. return [orig], fmt % str(orig)
  1226. p = os.path.join(bd._path, *tail)
  1227. targets.append(self.Entry(p))
  1228. tail = [dir.name] + tail
  1229. dir = dir.up()
  1230. if targets:
  1231. message = fmt % ' '.join(map(str, targets))
  1232. return targets, message
  1233. def Glob(self, pathname, ondisk=True, source=True, strings=False, exclude=None, cwd=None):
  1234. """
  1235. Globs
  1236. This is mainly a shim layer
  1237. """
  1238. if cwd is None:
  1239. cwd = self.getcwd()
  1240. return cwd.glob(pathname, ondisk, source, strings, exclude)
  1241. class DirNodeInfo(SCons.Node.NodeInfoBase):
  1242. __slots__ = ()
  1243. # This should get reset by the FS initialization.
  1244. current_version_id = 2
  1245. fs = None
  1246. def str_to_node(self, s):
  1247. top = self.fs.Top
  1248. root = top.root
  1249. if do_splitdrive:
  1250. drive, s = _my_splitdrive(s)
  1251. if drive:
  1252. root = self.fs.get_root(drive)
  1253. if not os.path.isabs(s):
  1254. s = top.get_labspath() + '/' + s
  1255. return root._lookup_abs(s, Entry)
  1256. class DirBuildInfo(SCons.Node.BuildInfoBase):
  1257. __slots__ = ()
  1258. current_version_id = 2
  1259. glob_magic_check = re.compile('[*?[]')
  1260. def has_glob_magic(s):
  1261. return glob_magic_check.search(s) is not None
  1262. class Dir(Base):
  1263. """A class for directories in a file system.
  1264. """
  1265. __slots__ = ['scanner_paths',
  1266. 'cachedir_csig',
  1267. 'cachesig',
  1268. 'repositories',
  1269. 'srcdir',
  1270. 'entries',
  1271. 'searched',
  1272. '_sconsign',
  1273. 'variant_dirs',
  1274. 'root',
  1275. 'dirname',
  1276. 'on_disk_entries',
  1277. 'released_target_info',
  1278. 'contentsig']
  1279. NodeInfo = DirNodeInfo
  1280. BuildInfo = DirBuildInfo
  1281. def __init__(self, name, directory, fs):
  1282. if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.Dir')
  1283. Base.__init__(self, name, directory, fs)
  1284. self._morph()
  1285. def _morph(self):
  1286. """Turn a file system Node (either a freshly initialized directory
  1287. object or a separate Entry object) into a proper directory object.
  1288. Set up this directory's entries and hook it into the file
  1289. system tree. Specify that directories (this Node) don't use
  1290. signatures for calculating whether they're current.
  1291. """
  1292. self.repositories = []
  1293. self.srcdir = None
  1294. self.entries = {}
  1295. self.entries['.'] = self
  1296. self.entries['..'] = self.dir
  1297. self.cwd = self
  1298. self.searched = 0
  1299. self._sconsign = None
  1300. self.variant_dirs = []
  1301. self.root = self.dir.root
  1302. self.changed_since_last_build = 3
  1303. self._func_sconsign = 1
  1304. self._func_exists = 2
  1305. self._func_get_contents = 2
  1306. self._abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name))
  1307. self._labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name))
  1308. if self.dir._path == '.':
  1309. self._path = SCons.Util.silent_intern(self.name)
  1310. else:
  1311. self._path = SCons.Util.silent_intern(self.dir.entry_path(self.name))
  1312. if self.dir._tpath == '.':
  1313. self._tpath = SCons.Util.silent_intern(self.name)
  1314. else:
  1315. self._tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name))
  1316. self._path_elements = self.dir._path_elements + [self]
  1317. # For directories, we make a difference between the directory
  1318. # 'name' and the directory 'dirname'. The 'name' attribute is
  1319. # used when we need to print the 'name' of the directory or
  1320. # when we it is used as the last part of a path. The 'dirname'
  1321. # is used when the directory is not the last element of the
  1322. # path. The main reason for making that distinction is that
  1323. # for RoorDir's the dirname can not be easily inferred from
  1324. # the name. For example, we have to add a '/' after a drive
  1325. # letter but not after a UNC path prefix ('//').
  1326. self.dirname = self.name + OS_SEP
  1327. # Don't just reset the executor, replace its action list,
  1328. # because it might have some pre-or post-actions that need to
  1329. # be preserved.
  1330. #
  1331. # But don't reset the executor if there is a non-null executor
  1332. # attached already. The existing executor might have other
  1333. # targets, in which case replacing the action list with a
  1334. # Mkdir action is a big mistake.
  1335. if not hasattr(self, 'executor'):
  1336. self.builder = get_MkdirBuilder()
  1337. self.get_executor().set_action_list(self.builder.action)
  1338. else:
  1339. # Prepend MkdirBuilder action to existing action list
  1340. l = self.get_executor().action_list
  1341. a = get_MkdirBuilder().action
  1342. l.insert(0, a)
  1343. self.get_executor().set_action_list(l)
  1344. def diskcheck_match(self):
  1345. diskcheck_match(self, self.isfile,
  1346. "File %s found where directory expected.")
  1347. def __clearRepositoryCache(self, duplicate=None):
  1348. """Called when we change the repository(ies) for a directory.
  1349. This clears any cached information that is invalidated by changing
  1350. the repository."""
  1351. for node in list(self.entries.values()):
  1352. if node != self.dir:
  1353. if node != self and isinstance(node, Dir):
  1354. node.__clearRepositoryCache(duplicate)
  1355. else:
  1356. node.clear()
  1357. try:
  1358. del node._srcreps
  1359. except AttributeError:
  1360. pass
  1361. if duplicate is not None:
  1362. node.duplicate=duplicate
  1363. def __resetDuplicate(self, node):
  1364. if node != self:
  1365. node.duplicate = node.get_dir().duplicate
  1366. def Entry(self, name):
  1367. """
  1368. Looks up or creates an entry node named 'name' relative to
  1369. this directory.
  1370. """
  1371. return self.fs.Entry(name, self)
  1372. def Dir(self, name, create=True):
  1373. """
  1374. Looks up or creates a directory node named 'name' relative to
  1375. this directory.
  1376. """
  1377. return self.fs.Dir(name, self, create)
  1378. def File(self, name):
  1379. """
  1380. Looks up or creates a file node named 'name' relative to
  1381. this directory.
  1382. """
  1383. return self.fs.File(name, self)
  1384. def link(self, srcdir, duplicate):
  1385. """Set this directory as the variant directory for the
  1386. supplied source directory."""
  1387. self.srcdir = srcdir
  1388. self.duplicate = duplicate
  1389. self.__clearRepositoryCache(duplicate)
  1390. srcdir.variant_dirs.append(self)
  1391. def getRepositories(self):
  1392. """Returns a list of repositories for this directory.
  1393. """
  1394. if self.srcdir and not self.duplicate:
  1395. return self.srcdir.get_all_rdirs() + self.repositories
  1396. return self.repositories
  1397. @SCons.Memoize.CountMethodCall
  1398. def get_all_rdirs(self):
  1399. try:
  1400. return list(self._memo['get_all_rdirs'])
  1401. except KeyError:
  1402. pass
  1403. result = [self]
  1404. fname = '.'
  1405. dir = self
  1406. while dir:
  1407. for rep in dir.getRepositories():
  1408. result.append(rep.Dir(fname))
  1409. if fname == '.':
  1410. fname = dir.name
  1411. else:
  1412. fname = dir.name + OS_SEP + fname
  1413. dir = dir.up()
  1414. self._memo['get_all_rdirs'] = list(result)
  1415. return result
  1416. def addRepository(self, dir):
  1417. if dir != self and not dir in self.repositories:
  1418. self.repositories.append(dir)
  1419. dir._tpath = '.'
  1420. self.__clearRepositoryCache()
  1421. def up(self):
  1422. return self.dir
  1423. def _rel_path_key(self, other):
  1424. return str(other)
  1425. @SCons.Memoize.CountDictCall(_rel_path_key)
  1426. def rel_path(self, other):
  1427. """Return a path to "other" relative to this directory.
  1428. """
  1429. # This complicated and expensive method, which constructs relative
  1430. # paths between arbitrary Node.FS objects, is no longer used
  1431. # by SCons itself. It was introduced to store dependency paths
  1432. # in .sconsign files relative to the target, but that ended up
  1433. # being significantly inefficient.
  1434. #
  1435. # We're continuing to support the method because some SConstruct
  1436. # files out there started using it when it was available, and
  1437. # we're all about backwards compatibility..
  1438. try:
  1439. memo_dict = self._memo['rel_path']
  1440. except KeyError:
  1441. memo_dict = {}
  1442. self._memo['rel_path'] = memo_dict
  1443. else:
  1444. try:
  1445. return memo_dict[other]
  1446. except KeyError:
  1447. pass
  1448. if self is other:
  1449. result = '.'
  1450. elif not other in self._path_elements:
  1451. try:
  1452. other_dir = other.get_dir()
  1453. except AttributeError:
  1454. result = str(other)
  1455. else:
  1456. if other_dir is None:
  1457. result = other.name
  1458. else:
  1459. dir_rel_path = self.rel_path(other_dir)
  1460. if dir_rel_path == '.':
  1461. result = other.name
  1462. else:
  1463. result = dir_rel_path + OS_SEP + other.name
  1464. else:
  1465. i = self._path_elements.index(other) + 1
  1466. path_elems = ['..'] * (len(self._path_elements) - i) \
  1467. + [n.name for n in other._path_elements[i:]]
  1468. result = OS_SEP.join(path_elems)
  1469. memo_dict[other] = result
  1470. return result
  1471. def get_env_scanner(self, env, kw={}):
  1472. import SCons.Defaults
  1473. return SCons.Defaults.DirEntryScanner
  1474. def get_target_scanner(self):
  1475. import SCons.Defaults
  1476. return SCons.Defaults.DirEntryScanner
  1477. def get_found_includes(self, env, scanner, path):
  1478. """Return this directory's implicit dependencies.
  1479. We don't bother caching the results because the scan typically
  1480. shouldn't be requested more than once (as opposed to scanning
  1481. .h file contents, which can be requested as many times as the
  1482. files is #included by other files).
  1483. """
  1484. if not scanner:
  1485. return []
  1486. # Clear cached info for this Dir. If we already visited this
  1487. # directory on our walk down the tree (because we didn't know at
  1488. # that point it was being used as the source for another Node)
  1489. # then we may have calculated build signature before realizing
  1490. # we had to scan the disk. Now that we have to, though, we need
  1491. # to invalidate the old calculated signature so that any node
  1492. # dependent on our directory structure gets one that includes
  1493. # info about everything on disk.
  1494. self.clear()
  1495. return scanner(self, env, path)
  1496. #
  1497. # Taskmaster interface subsystem
  1498. #
  1499. def prepare(self):
  1500. pass
  1501. def build(self, **kw):
  1502. """A null "builder" for directories."""
  1503. global MkdirBuilder
  1504. if self.builder is not MkdirBuilder:
  1505. SCons.Node.Node.build(self, **kw)
  1506. #
  1507. #
  1508. #
  1509. def _create(self):
  1510. """Create this directory, silently and without worrying about
  1511. whether the builder is the default or not."""
  1512. listDirs = []
  1513. parent = self
  1514. while parent:
  1515. if parent.exists():
  1516. break
  1517. listDirs.append(parent)
  1518. p = parent.up()
  1519. if p is None:
  1520. # Don't use while: - else: for this condition because
  1521. # if so, then parent is None and has no .path attribute.
  1522. raise SCons.Errors.StopError(parent._path)
  1523. parent = p
  1524. listDirs.reverse()
  1525. for dirnode in listDirs:
  1526. try:
  1527. # Don't call dirnode.build(), call the base Node method
  1528. # directly because we definitely *must* create this
  1529. # directory. The dirnode.build() method will suppress
  1530. # the build if it's the default builder.
  1531. SCons.Node.Node.build(dirnode)
  1532. dirnode.get_executor().nullify()
  1533. # The build() action may or may not have actually
  1534. # created the directory, depending on whether the -n
  1535. # option was used or not. Delete the _exists and
  1536. # _rexists attributes so they can be reevaluated.
  1537. dirnode.clear()
  1538. except OSError:
  1539. pass
  1540. def multiple_side_effect_has_builder(self):
  1541. global MkdirBuilder
  1542. return self.builder is not MkdirBuilder and self.has_builder()
  1543. def alter_targets(self):
  1544. """Return any corresponding targets in a variant directory.
  1545. """
  1546. return self.fs.variant_dir_target_climb(self, self, [])
  1547. def scanner_key(self):
  1548. """A directory does not get scanned."""
  1549. return None
  1550. def get_text_contents(self):
  1551. """We already emit things in text, so just return the binary
  1552. version."""
  1553. return self.get_contents()
  1554. def get_contents(self):
  1555. """Return content signatures and names of all our children
  1556. separated by new-lines. Ensure that the nodes are sorted."""
  1557. return SCons.Node._get_contents_map[self._func_get_contents](self)
  1558. def get_csig(self):
  1559. """Compute the content signature for Directory nodes. In
  1560. general, this is not needed and the content signature is not
  1561. stored in the DirNodeInfo. However, if get_contents on a Dir
  1562. node is called which has a child directory, the child
  1563. directory should return the hash of its contents."""
  1564. contents = self.get_contents()
  1565. return SCons.Util.MD5signature(contents)
  1566. def do_duplicate(self, src):
  1567. pass
  1568. def is_up_to_date(self):
  1569. """If any child is not up-to-date, then this directory isn't,
  1570. either."""
  1571. if self.builder is not MkdirBuilder and not self.exists():
  1572. return 0
  1573. up_to_date = SCons.Node.up_to_date
  1574. for kid in self.children():
  1575. if kid.get_state() > up_to_date:
  1576. return 0
  1577. return 1
  1578. def rdir(self):
  1579. if not self.exists():
  1580. norm_name = _my_normcase(self.name)
  1581. for dir in self.dir.get_all_rdirs():
  1582. try: node = dir.entries[norm_name]
  1583. except KeyError: node = dir.dir_on_disk(self.name)
  1584. if node and node.exists() and \
  1585. (isinstance(dir, Dir) or isinstance(dir, Entry)):
  1586. return node
  1587. return self
  1588. def sconsign(self):
  1589. """Return the .sconsign file info for this directory. """
  1590. return _sconsign_map[self._func_sconsign](self)
  1591. def srcnode(self):
  1592. """Dir has a special need for srcnode()...if we
  1593. have a srcdir attribute set, then that *is* our srcnode."""
  1594. if self.srcdir:
  1595. return self.srcdir
  1596. return Base.srcnode(self)
  1597. def get_timestamp(self):
  1598. """Return the latest timestamp from among our children"""
  1599. stamp = 0
  1600. for kid in self.children():
  1601. if kid.get_timestamp() > stamp:
  1602. stamp = kid.get_timestamp()
  1603. return stamp
  1604. def get_abspath(self):
  1605. """Get the absolute path of the file."""
  1606. return self._abspath
  1607. def get_labspath(self):
  1608. """Get the absolute path of the file."""
  1609. return self._labspath
  1610. def get_internal_path(self):
  1611. return self._path
  1612. def get_tpath(self):
  1613. return self._tpath
  1614. def get_path_elements(self):
  1615. return self._path_elements
  1616. def entry_abspath(self, name):
  1617. return self._abspath + OS_SEP + name
  1618. def entry_labspath(self, name):
  1619. return self._labspath + '/' + name
  1620. def entry_path(self, name):
  1621. return self._path + OS_SEP + name
  1622. def entry_tpath(self, name):
  1623. return self._tpath + OS_SEP + name
  1624. def entry_exists_on_disk(self, name):
  1625. """ Searches through the file/dir entries of the current
  1626. directory, and returns True if a physical entry with the given
  1627. name could be found.
  1628. @see rentry_exists_on_disk
  1629. """
  1630. try:
  1631. d = self.on_disk_entries
  1632. except AttributeError:
  1633. d = {}
  1634. try:
  1635. entries = os.listdir(self._abspath)
  1636. except OSError:
  1637. pass
  1638. else:
  1639. for entry in map(_my_normcase, entries):
  1640. d[entry] = True
  1641. self.on_disk_entries = d
  1642. if sys.platform == 'win32' or sys.platform == 'cygwin':
  1643. name = _my_normcase(name)
  1644. result = d.get(name)
  1645. if result is None:
  1646. # Belt-and-suspenders for Windows: check directly for
  1647. # 8.3 file names that don't show up in os.listdir().
  1648. result = os.path.exists(self._abspath + OS_SEP + name)
  1649. d[name] = result
  1650. return result
  1651. else:
  1652. return name in d
  1653. def rentry_exists_on_disk(self, name):
  1654. """ Searches through the file/dir entries of the current
  1655. *and* all its remote directories (repos), and returns
  1656. True if a physical entry with the given name could be found.
  1657. The local directory (self) gets searched first, so
  1658. repositories take a lower precedence regarding the
  1659. searching order.
  1660. @see entry_exists_on_disk
  1661. """
  1662. rentry_exists = self.entry_exists_on_disk(name)
  1663. if not rentry_exists:
  1664. # Search through the repository folders
  1665. norm_name = _my_normcase(name)
  1666. for rdir in self.get_all_rdirs():
  1667. try:
  1668. node = rdir.entries[norm_name]
  1669. if node:
  1670. rentry_exists = True
  1671. break
  1672. except KeyError:
  1673. if rdir.entry_exists_on_disk(name):
  1674. rentry_exists = True
  1675. break
  1676. return rentry_exists
  1677. @SCons.Memoize.CountMethodCall
  1678. def srcdir_list(self):
  1679. try:
  1680. return self._memo['srcdir_list']
  1681. except KeyError:
  1682. pass
  1683. result = []
  1684. dirname = '.'
  1685. dir = self
  1686. while dir:
  1687. if dir.srcdir:
  1688. result.append(dir.srcdir.Dir(dirname))
  1689. dirname = dir.name + OS_SEP + dirname
  1690. dir = dir.up()
  1691. self._memo['srcdir_list'] = result
  1692. return result
  1693. def srcdir_duplicate(self, name):
  1694. for dir in self.srcdir_list():
  1695. if self.is_under(dir):
  1696. # We shouldn't source from something in the build path;
  1697. # variant_dir is probably under src_dir, in which case
  1698. # we are reflecting.
  1699. break
  1700. if dir.entry_exists_on_disk(name):
  1701. srcnode = dir.Entry(name).disambiguate()
  1702. if self.duplicate:
  1703. node = self.Entry(name).disambiguate()
  1704. node.do_duplicate(srcnode)
  1705. return node
  1706. else:
  1707. return srcnode
  1708. return None
  1709. def _srcdir_find_file_key(self, filename):
  1710. return filename
  1711. @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
  1712. def srcdir_find_file(self, filename):
  1713. try:
  1714. memo_dict = self._memo['srcdir_find_file']
  1715. except KeyError:
  1716. memo_dict = {}
  1717. self._memo['srcdir_find_file'] = memo_dict
  1718. else:
  1719. try:
  1720. return memo_dict[filename]
  1721. except KeyError:
  1722. pass
  1723. def func(node):
  1724. if (isinstance(node, File) or isinstance(node, Entry)) and \
  1725. (node.is_derived() or node.exists()):
  1726. return node
  1727. return None
  1728. norm_name = _my_normcase(filename)
  1729. for rdir in self.get_all_rdirs():
  1730. try: node = rdir.entries[norm_name]
  1731. except KeyError: node = rdir.file_on_disk(filename)
  1732. else: node = func(node)
  1733. if node:
  1734. result = (node, self)
  1735. memo_dict[filename] = result
  1736. return result
  1737. for srcdir in self.srcdir_list():
  1738. for rdir in srcdir.get_all_rdirs():
  1739. try: node = rdir.entries[norm_name]
  1740. except KeyError: node = rdir.file_on_disk(filename)
  1741. else: node = func(node)
  1742. if node:
  1743. result = (File(filename, self, self.fs), srcdir)
  1744. memo_dict[filename] = result
  1745. return result
  1746. result = (None, None)
  1747. memo_dict[filename] = result
  1748. return result
  1749. def dir_on_disk(self, name):
  1750. if self.entry_exists_on_disk(name):
  1751. try: return self.Dir(name)
  1752. except TypeError: pass
  1753. node = self.srcdir_duplicate(name)
  1754. if isinstance(node, File):
  1755. return None
  1756. return node
  1757. def file_on_disk(self, name):
  1758. if self.entry_exists_on_disk(name):
  1759. try: return self.File(name)
  1760. except TypeError: pass
  1761. node = self.srcdir_duplicate(name)
  1762. if isinstance(node, Dir):
  1763. return None
  1764. return node
  1765. def walk(self, func, arg):
  1766. """
  1767. Walk this directory tree by calling the specified function
  1768. for each directory in the tree.
  1769. This behaves like the os.path.walk() function, but for in-memory
  1770. Node.FS.Dir objects. The function takes the same arguments as
  1771. the functions passed to os.path.walk():
  1772. func(arg, dirname, fnames)
  1773. Except that "dirname" will actually be the directory *Node*,
  1774. not the string. The '.' and '..' entries are excluded from
  1775. fnames. The fnames list may be modified in-place to filter the
  1776. subdirectories visited or otherwise impose a specific order.
  1777. The "arg" argument is always passed to func() and may be used
  1778. in any way (or ignored, passing None is common).
  1779. """
  1780. entries = self.entries
  1781. names = list(entries.keys())
  1782. names.remove('.')
  1783. names.remove('..')
  1784. func(arg, self, names)
  1785. for dirname in [n for n in names if isinstance(entries[n], Dir)]:
  1786. entries[dirname].walk(func, arg)
  1787. def glob(self, pathname, ondisk=True, source=False, strings=False, exclude=None):
  1788. """
  1789. Returns a list of Nodes (or strings) matching a specified
  1790. pathname pattern.
  1791. Pathname patterns follow UNIX shell semantics: * matches
  1792. any-length strings of any characters, ? matches any character,
  1793. and [] can enclose lists or ranges of characters. Matches do
  1794. not span directory separators.
  1795. The matches take into account Repositories, returning local
  1796. Nodes if a corresponding entry exists in a Repository (either
  1797. an in-memory Node or something on disk).
  1798. By defafult, the glob() function matches entries that exist
  1799. on-disk, in addition to in-memory Nodes. Setting the "ondisk"
  1800. argument to False (or some other non-true value) causes the glob()
  1801. function to only match in-memory Nodes. The default behavior is
  1802. to return both the on-disk and in-memory Nodes.
  1803. The "source" argument, when true, specifies that corresponding
  1804. source Nodes must be returned if you're globbing in a build
  1805. directory (initialized with VariantDir()). The default behavior
  1806. is to return Nodes local to the VariantDir().
  1807. The "strings" argument, when true, returns the matches as strings,
  1808. not Nodes. The strings are path names relative to this directory.
  1809. The "exclude" argument, if not None, must be a pattern or a list
  1810. of patterns following the same UNIX shell semantics.
  1811. Elements matching a least one pattern of this list will be excluded
  1812. from the result.
  1813. The underlying algorithm is adapted from the glob.glob() function
  1814. in the Python library (but heavily modified), and uses fnmatch()
  1815. under the covers.
  1816. """
  1817. dirname, basename = os.path.split(pathname)
  1818. if not dirname:
  1819. result = self._glob1(basename, ondisk, source, strings)
  1820. else:
  1821. if has_glob_magic(dirname):
  1822. list = self.glob(dirname, ondisk, source, False, exclude)
  1823. else:
  1824. list = [self.Dir(dirname, create=True)]
  1825. result = []
  1826. for dir in list:
  1827. r = dir._glob1(basename, ondisk, source, strings)
  1828. if strings:
  1829. r = [os.path.join(str(dir), x) for x in r]
  1830. result.extend(r)
  1831. if exclude:
  1832. excludes = []
  1833. excludeList = SCons.Util.flatten(exclude)
  1834. for x in excludeList:
  1835. r = self.glob(x, ondisk, source, strings)
  1836. excludes.extend(r)
  1837. result = [x for x in result if not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes))]
  1838. return sorted(result, key=lambda a: str(a))
  1839. def _glob1(self, pattern, ondisk=True, source=False, strings=False):
  1840. """
  1841. Globs for and returns a list of entry names matching a single
  1842. pattern in this directory.
  1843. This searches any repositories and source directories for
  1844. corresponding entries and returns a Node (or string) relative
  1845. to the current directory if an entry is found anywhere.
  1846. TODO: handle pattern with no wildcard
  1847. """
  1848. search_dir_list = self.get_all_rdirs()
  1849. for srcdir in self.srcdir_list():
  1850. search_dir_list.extend(srcdir.get_all_rdirs())
  1851. selfEntry = self.Entry
  1852. names = []
  1853. for dir in search_dir_list:
  1854. # We use the .name attribute from the Node because the keys of
  1855. # the dir.entries dictionary are normalized (that is, all upper
  1856. # case) on case-insensitive systems like Windows.
  1857. node_names = [ v.name for k, v in dir.entries.items()
  1858. if k not in ('.', '..') ]
  1859. names.extend(node_names)
  1860. if not strings:
  1861. # Make sure the working directory (self) actually has
  1862. # entries for all Nodes in repositories or variant dirs.
  1863. for name in node_names: selfEntry(name)
  1864. if ondisk:
  1865. try:
  1866. disk_names = os.listdir(dir._abspath)
  1867. except os.error:
  1868. continue
  1869. names.extend(disk_names)
  1870. if not strings:
  1871. # We're going to return corresponding Nodes in
  1872. # the local directory, so we need to make sure
  1873. # those Nodes exist. We only want to create
  1874. # Nodes for the entries that will match the
  1875. # specified pattern, though, which means we
  1876. # need to filter the list here, even though
  1877. # the overall list will also be filtered later,
  1878. # after we exit this loop.
  1879. if pattern[0] != '.':
  1880. disk_names = [x for x in disk_names if x[0] != '.']
  1881. disk_names = fnmatch.filter(disk_names, pattern)
  1882. dirEntry = dir.Entry
  1883. for name in disk_names:
  1884. # Add './' before disk filename so that '#' at
  1885. # beginning of filename isn't interpreted.
  1886. name = './' + name
  1887. node = dirEntry(name).disambiguate()
  1888. n = selfEntry(name)
  1889. if n.__class__ != node.__class__:
  1890. n.__class__ = node.__class__
  1891. n._morph()
  1892. names = set(names)
  1893. if pattern[0] != '.':
  1894. names = [x for x in names if x[0] != '.']
  1895. names = fnmatch.filter(names, pattern)
  1896. if strings:
  1897. return names
  1898. return [self.entries[_my_normcase(n)] for n in names]
  1899. class RootDir(Dir):
  1900. """A class for the root directory of a file system.
  1901. This is the same as a Dir class, except that the path separator
  1902. ('/' or '\\') is actually part of the name, so we don't need to
  1903. add a separator when creating the path names of entries within
  1904. this directory.
  1905. """
  1906. __slots__ = ['_lookupDict']
  1907. def __init__(self, drive, fs):
  1908. if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
  1909. SCons.Node.Node.__init__(self)
  1910. # Handle all the types of drives:
  1911. if drive == '':
  1912. # No drive, regular UNIX root or Windows default drive.
  1913. name = OS_SEP
  1914. dirname = OS_SEP
  1915. elif drive == '//':
  1916. # UNC path
  1917. name = UNC_PREFIX
  1918. dirname = UNC_PREFIX
  1919. else:
  1920. # Windows drive letter
  1921. name = drive
  1922. dirname = drive + OS_SEP
  1923. # Filename with extension as it was specified when the object was
  1924. # created; to obtain filesystem path, use Python str() function
  1925. self.name = SCons.Util.silent_intern(name)
  1926. self.fs = fs #: Reference to parent Node.FS object
  1927. self._path_elements = [self]
  1928. self.dir = self
  1929. self._func_rexists = 2
  1930. self._func_target_from_source = 1
  1931. self.store_info = 1
  1932. # Now set our paths to what we really want them to be. The
  1933. # name should already contain any necessary separators, such
  1934. # as the initial drive letter (the name) plus the directory
  1935. # separator, except for the "lookup abspath," which does not
  1936. # have the drive letter.
  1937. self._abspath = dirname
  1938. self._labspath = ''
  1939. self._path = dirname
  1940. self._tpath = dirname
  1941. self.dirname = dirname
  1942. self._morph()
  1943. self.duplicate = 0
  1944. self._lookupDict = {}
  1945. self._lookupDict[''] = self
  1946. self._lookupDict['/'] = self
  1947. self.root = self
  1948. # The // entry is necessary because os.path.normpath()
  1949. # preserves double slashes at the beginning of a path on Posix
  1950. # platforms.
  1951. if not has_unc:
  1952. self._lookupDict['//'] = self
  1953. def _morph(self):
  1954. """Turn a file system Node (either a freshly initialized directory
  1955. object or a separate Entry object) into a proper directory object.
  1956. Set up this directory's entries and hook it into the file
  1957. system tree. Specify that directories (this Node) don't use
  1958. signatures for calculating whether they're current.
  1959. """
  1960. self.repositories = []
  1961. self.srcdir = None
  1962. self.entries = {}
  1963. self.entries['.'] = self
  1964. self.entries['..'] = self.dir
  1965. self.cwd = self
  1966. self.searched = 0
  1967. self._sconsign = None
  1968. self.variant_dirs = []
  1969. self.changed_since_last_build = 3
  1970. self._func_sconsign = 1
  1971. self._func_exists = 2
  1972. self._func_get_contents = 2
  1973. # Don't just reset the executor, replace its action list,
  1974. # because it might have some pre-or post-actions that need to
  1975. # be preserved.
  1976. #
  1977. # But don't reset the executor if there is a non-null executor
  1978. # attached already. The existing executor might have other
  1979. # targets, in which case replacing the action list with a
  1980. # Mkdir action is a big mistake.
  1981. if not hasattr(self, 'executor'):
  1982. self.builder = get_MkdirBuilder()
  1983. self.get_executor().set_action_list(self.builder.action)
  1984. else:
  1985. # Prepend MkdirBuilder action to existing action list
  1986. l = self.get_executor().action_list
  1987. a = get_MkdirBuilder().action
  1988. l.insert(0, a)
  1989. self.get_executor().set_action_list(l)
  1990. def must_be_same(self, klass):
  1991. if klass is Dir:
  1992. return
  1993. Base.must_be_same(self, klass)
  1994. def _lookup_abs(self, p, klass, create=1):
  1995. """
  1996. Fast (?) lookup of a *normalized* absolute path.
  1997. This method is intended for use by internal lookups with
  1998. already-normalized path data. For general-purpose lookups,
  1999. use the FS.Entry(), FS.Dir() or FS.File() methods.
  2000. The caller is responsible for making sure we're passed a
  2001. normalized absolute path; we merely let Python's dictionary look
  2002. up and return the One True Node.FS object for the path.
  2003. If a Node for the specified "p" doesn't already exist, and
  2004. "create" is specified, the Node may be created after recursive
  2005. invocation to find or create the parent directory or directories.
  2006. """
  2007. k = _my_normcase(p)
  2008. try:
  2009. result = self._lookupDict[k]
  2010. except KeyError:
  2011. if not create:
  2012. msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
  2013. raise SCons.Errors.UserError(msg)
  2014. # There is no Node for this path name, and we're allowed
  2015. # to create it.
  2016. dir_name, file_name = p.rsplit('/',1)
  2017. dir_node = self._lookup_abs(dir_name, Dir)
  2018. result = klass(file_name, dir_node, self.fs)
  2019. # Double-check on disk (as configured) that the Node we
  2020. # created matches whatever is out there in the real world.
  2021. result.diskcheck_match()
  2022. self._lookupDict[k] = result
  2023. dir_node.entries[_my_normcase(file_name)] = result
  2024. dir_node.implicit = None
  2025. else:
  2026. # There is already a Node for this path name. Allow it to
  2027. # complain if we were looking for an inappropriate type.
  2028. result.must_be_same(klass)
  2029. return result
  2030. def __str__(self):
  2031. return self._abspath
  2032. def entry_abspath(self, name):
  2033. return self._abspath + name
  2034. def entry_labspath(self, name):
  2035. return '/' + name
  2036. def entry_path(self, name):
  2037. return self._path + name
  2038. def entry_tpath(self, name):
  2039. return self._tpath + name
  2040. def is_under(self, dir):
  2041. if self is dir:
  2042. return 1
  2043. else:
  2044. return 0
  2045. def up(self):
  2046. return None
  2047. def get_dir(self):
  2048. return None
  2049. def src_builder(self):
  2050. return _null
  2051. class FileNodeInfo(SCons.Node.NodeInfoBase):
  2052. __slots__ = ('csig', 'timestamp', 'size')
  2053. current_version_id = 2
  2054. field_list = ['csig', 'timestamp', 'size']
  2055. # This should get reset by the FS initialization.
  2056. fs = None
  2057. def str_to_node(self, s):
  2058. top = self.fs.Top
  2059. root = top.root
  2060. if do_splitdrive:
  2061. drive, s = _my_splitdrive(s)
  2062. if drive:
  2063. root = self.fs.get_root(drive)
  2064. if not os.path.isabs(s):
  2065. s = top.get_labspath() + '/' + s
  2066. return root._lookup_abs(s, Entry)
  2067. def __getstate__(self):
  2068. """
  2069. Return all fields that shall be pickled. Walk the slots in the class
  2070. hierarchy and add those to the state dictionary. If a '__dict__' slot is
  2071. available, copy all entries to the dictionary. Also include the version
  2072. id, which is fixed for all instances of a class.
  2073. """
  2074. state = getattr(self, '__dict__', {}).copy()
  2075. for obj in type(self).mro():
  2076. for name in getattr(obj,'__slots__',()):
  2077. if hasattr(self, name):
  2078. state[name] = getattr(self, name)
  2079. state['_version_id'] = self.current_version_id
  2080. try:
  2081. del state['__weakref__']
  2082. except KeyError:
  2083. pass
  2084. return state
  2085. def __setstate__(self, state):
  2086. """
  2087. Restore the attributes from a pickled state.
  2088. """
  2089. # TODO check or discard version
  2090. del state['_version_id']
  2091. for key, value in state.items():
  2092. if key not in ('__weakref__',):
  2093. setattr(self, key, value)
  2094. class FileBuildInfo(SCons.Node.BuildInfoBase):
  2095. __slots__ = ()
  2096. current_version_id = 2
  2097. def convert_to_sconsign(self):
  2098. """
  2099. Converts this FileBuildInfo object for writing to a .sconsign file
  2100. This replaces each Node in our various dependency lists with its
  2101. usual string representation: relative to the top-level SConstruct
  2102. directory, or an absolute path if it's outside.
  2103. """
  2104. if os_sep_is_slash:
  2105. node_to_str = str
  2106. else:
  2107. def node_to_str(n):
  2108. try:
  2109. s = n.get_internal_path()
  2110. except AttributeError:
  2111. s = str(n)
  2112. else:
  2113. s = s.replace(OS_SEP, '/')
  2114. return s
  2115. for attr in ['bsources', 'bdepends', 'bimplicit']:
  2116. try:
  2117. val = getattr(self, attr)
  2118. except AttributeError:
  2119. pass
  2120. else:
  2121. setattr(self, attr, list(map(node_to_str, val)))
  2122. def convert_from_sconsign(self, dir, name):
  2123. """
  2124. Converts a newly-read FileBuildInfo object for in-SCons use
  2125. For normal up-to-date checking, we don't have any conversion to
  2126. perform--but we're leaving this method here to make that clear.
  2127. """
  2128. pass
  2129. def prepare_dependencies(self):
  2130. """
  2131. Prepares a FileBuildInfo object for explaining what changed
  2132. The bsources, bdepends and bimplicit lists have all been
  2133. stored on disk as paths relative to the top-level SConstruct
  2134. directory. Convert the strings to actual Nodes (for use by the
  2135. --debug=explain code and --implicit-cache).
  2136. """
  2137. attrs = [
  2138. ('bsources', 'bsourcesigs'),
  2139. ('bdepends', 'bdependsigs'),
  2140. ('bimplicit', 'bimplicitsigs'),
  2141. ]
  2142. for (nattr, sattr) in attrs:
  2143. try:
  2144. strings = getattr(self, nattr)
  2145. nodeinfos = getattr(self, sattr)
  2146. except AttributeError:
  2147. continue
  2148. if strings is None or nodeinfos is None:
  2149. continue
  2150. nodes = []
  2151. for s, ni in zip(strings, nodeinfos):
  2152. if not isinstance(s, SCons.Node.Node):
  2153. s = ni.str_to_node(s)
  2154. nodes.append(s)
  2155. setattr(self, nattr, nodes)
  2156. def format(self, names=0):
  2157. result = []
  2158. bkids = self.bsources + self.bdepends + self.bimplicit
  2159. bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
  2160. for bkid, bkidsig in zip(bkids, bkidsigs):
  2161. result.append(str(bkid) + ': ' +
  2162. ' '.join(bkidsig.format(names=names)))
  2163. if not hasattr(self,'bact'):
  2164. self.bact = "none"
  2165. result.append('%s [%s]' % (self.bactsig, self.bact))
  2166. return '\n'.join(result)
  2167. class File(Base):
  2168. """A class for files in a file system.
  2169. """
  2170. __slots__ = ['scanner_paths',
  2171. 'cachedir_csig',
  2172. 'cachesig',
  2173. 'repositories',
  2174. 'srcdir',
  2175. 'entries',
  2176. 'searched',
  2177. '_sconsign',
  2178. 'variant_dirs',
  2179. 'root',
  2180. 'dirname',
  2181. 'on_disk_entries',
  2182. 'released_target_info',
  2183. 'contentsig']
  2184. NodeInfo = FileNodeInfo
  2185. BuildInfo = FileBuildInfo
  2186. md5_chunksize = 64
  2187. def diskcheck_match(self):
  2188. diskcheck_match(self, self.isdir,
  2189. "Directory %s found where file expected.")
  2190. def __init__(self, name, directory, fs):
  2191. if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.File')
  2192. Base.__init__(self, name, directory, fs)
  2193. self._morph()
  2194. def Entry(self, name):
  2195. """Create an entry node named 'name' relative to
  2196. the directory of this file."""
  2197. return self.dir.Entry(name)
  2198. def Dir(self, name, create=True):
  2199. """Create a directory node named 'name' relative to
  2200. the directory of this file."""
  2201. return self.dir.Dir(name, create=create)
  2202. def Dirs(self, pathlist):
  2203. """Create a list of directories relative to the SConscript
  2204. directory of this file."""
  2205. return [self.Dir(p) for p in pathlist]
  2206. def File(self, name):
  2207. """Create a file node named 'name' relative to
  2208. the directory of this file."""
  2209. return self.dir.File(name)
  2210. def _morph(self):
  2211. """Turn a file system node into a File object."""
  2212. self.scanner_paths = {}
  2213. if not hasattr(self, '_local'):
  2214. self._local = 0
  2215. if not hasattr(self, 'released_target_info'):
  2216. self.released_target_info = False
  2217. self.store_info = 1
  2218. self._func_exists = 4
  2219. self._func_get_contents = 3
  2220. # Initialize this Node's decider function to decide_source() because
  2221. # every file is a source file until it has a Builder attached...
  2222. self.changed_since_last_build = 4
  2223. # If there was already a Builder set on this entry, then
  2224. # we need to make sure we call the target-decider function,
  2225. # not the source-decider. Reaching in and doing this by hand
  2226. # is a little bogus. We'd prefer to handle this by adding
  2227. # an Entry.builder_set() method that disambiguates like the
  2228. # other methods, but that starts running into problems with the
  2229. # fragile way we initialize Dir Nodes with their Mkdir builders,
  2230. # yet still allow them to be overridden by the user. Since it's
  2231. # not clear right now how to fix that, stick with what works
  2232. # until it becomes clear...
  2233. if self.has_builder():
  2234. self.changed_since_last_build = 5
  2235. def scanner_key(self):
  2236. return self.get_suffix()
  2237. def get_contents(self):
  2238. return SCons.Node._get_contents_map[self._func_get_contents](self)
  2239. def get_text_contents(self):
  2240. """
  2241. This attempts to figure out what the encoding of the text is
  2242. based upon the BOM bytes, and then decodes the contents so that
  2243. it's a valid python string.
  2244. """
  2245. contents = self.get_contents()
  2246. # The behavior of various decode() methods and functions
  2247. # w.r.t. the initial BOM bytes is different for different
  2248. # encodings and/or Python versions. ('utf-8' does not strip
  2249. # them, but has a 'utf-8-sig' which does; 'utf-16' seems to
  2250. # strip them; etc.) Just sidestep all the complication by
  2251. # explicitly stripping the BOM before we decode().
  2252. if contents[:len(codecs.BOM_UTF8)] == codecs.BOM_UTF8:
  2253. return contents[len(codecs.BOM_UTF8):].decode('utf-8')
  2254. if contents[:len(codecs.BOM_UTF16_LE)] == codecs.BOM_UTF16_LE:
  2255. return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
  2256. if contents[:len(codecs.BOM_UTF16_BE)] == codecs.BOM_UTF16_BE:
  2257. return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
  2258. try:
  2259. return contents.decode('utf-8')
  2260. except UnicodeDecodeError as e:
  2261. try:
  2262. return contents.decode('latin-1')
  2263. except UnicodeDecodeError as e:
  2264. return contents.decode('utf-8', error='backslashreplace')
  2265. def get_content_hash(self):
  2266. """
  2267. Compute and return the MD5 hash for this file.
  2268. """
  2269. if not self.rexists():
  2270. return SCons.Util.MD5signature('')
  2271. fname = self.rfile().get_abspath()
  2272. try:
  2273. cs = SCons.Util.MD5filesignature(fname,
  2274. chunksize=SCons.Node.FS.File.md5_chunksize*1024)
  2275. except EnvironmentError as e:
  2276. if not e.filename:
  2277. e.filename = fname
  2278. raise
  2279. return cs
  2280. @SCons.Memoize.CountMethodCall
  2281. def get_size(self):
  2282. try:
  2283. return self._memo['get_size']
  2284. except KeyError:
  2285. pass
  2286. if self.rexists():
  2287. size = self.rfile().getsize()
  2288. else:
  2289. size = 0
  2290. self._memo['get_size'] = size
  2291. return size
  2292. @SCons.Memoize.CountMethodCall
  2293. def get_timestamp(self):
  2294. try:
  2295. return self._memo['get_timestamp']
  2296. except KeyError:
  2297. pass
  2298. if self.rexists():
  2299. timestamp = self.rfile().getmtime()
  2300. else:
  2301. timestamp = 0
  2302. self._memo['get_timestamp'] = timestamp
  2303. return timestamp
  2304. convert_copy_attrs = [
  2305. 'bsources',
  2306. 'bimplicit',
  2307. 'bdepends',
  2308. 'bact',
  2309. 'bactsig',
  2310. 'ninfo',
  2311. ]
  2312. convert_sig_attrs = [
  2313. 'bsourcesigs',
  2314. 'bimplicitsigs',
  2315. 'bdependsigs',
  2316. ]
  2317. def convert_old_entry(self, old_entry):
  2318. # Convert a .sconsign entry from before the Big Signature
  2319. # Refactoring, doing what we can to convert its information
  2320. # to the new .sconsign entry format.
  2321. #
  2322. # The old format looked essentially like this:
  2323. #
  2324. # BuildInfo
  2325. # .ninfo (NodeInfo)
  2326. # .bsig
  2327. # .csig
  2328. # .timestamp
  2329. # .size
  2330. # .bsources
  2331. # .bsourcesigs ("signature" list)
  2332. # .bdepends
  2333. # .bdependsigs ("signature" list)
  2334. # .bimplicit
  2335. # .bimplicitsigs ("signature" list)
  2336. # .bact
  2337. # .bactsig
  2338. #
  2339. # The new format looks like this:
  2340. #
  2341. # .ninfo (NodeInfo)
  2342. # .bsig
  2343. # .csig
  2344. # .timestamp
  2345. # .size
  2346. # .binfo (BuildInfo)
  2347. # .bsources
  2348. # .bsourcesigs (NodeInfo list)
  2349. # .bsig
  2350. # .csig
  2351. # .timestamp
  2352. # .size
  2353. # .bdepends
  2354. # .bdependsigs (NodeInfo list)
  2355. # .bsig
  2356. # .csig
  2357. # .timestamp
  2358. # .size
  2359. # .bimplicit
  2360. # .bimplicitsigs (NodeInfo list)
  2361. # .bsig
  2362. # .csig
  2363. # .timestamp
  2364. # .size
  2365. # .bact
  2366. # .bactsig
  2367. #
  2368. # The basic idea of the new structure is that a NodeInfo always
  2369. # holds all available information about the state of a given Node
  2370. # at a certain point in time. The various .b*sigs lists can just
  2371. # be a list of pointers to the .ninfo attributes of the different
  2372. # dependent nodes, without any copying of information until it's
  2373. # time to pickle it for writing out to a .sconsign file.
  2374. #
  2375. # The complicating issue is that the *old* format only stored one
  2376. # "signature" per dependency, based on however the *last* build
  2377. # was configured. We don't know from just looking at it whether
  2378. # it was a build signature, a content signature, or a timestamp
  2379. # "signature". Since we no longer use build signatures, the
  2380. # best we can do is look at the length and if it's thirty two,
  2381. # assume that it was (or might have been) a content signature.
  2382. # If it was actually a build signature, then it will cause a
  2383. # rebuild anyway when it doesn't match the new content signature,
  2384. # but that's probably the best we can do.
  2385. import SCons.SConsign
  2386. new_entry = SCons.SConsign.SConsignEntry()
  2387. new_entry.binfo = self.new_binfo()
  2388. binfo = new_entry.binfo
  2389. for attr in self.convert_copy_attrs:
  2390. try:
  2391. value = getattr(old_entry, attr)
  2392. except AttributeError:
  2393. continue
  2394. setattr(binfo, attr, value)
  2395. delattr(old_entry, attr)
  2396. for attr in self.convert_sig_attrs:
  2397. try:
  2398. sig_list = getattr(old_entry, attr)
  2399. except AttributeError:
  2400. continue
  2401. value = []
  2402. for sig in sig_list:
  2403. ninfo = self.new_ninfo()
  2404. if len(sig) == 32:
  2405. ninfo.csig = sig
  2406. else:
  2407. ninfo.timestamp = sig
  2408. value.append(ninfo)
  2409. setattr(binfo, attr, value)
  2410. delattr(old_entry, attr)
  2411. return new_entry
  2412. @SCons.Memoize.CountMethodCall
  2413. def get_stored_info(self):
  2414. try:
  2415. return self._memo['get_stored_info']
  2416. except KeyError:
  2417. pass
  2418. try:
  2419. sconsign_entry = self.dir.sconsign().get_entry(self.name)
  2420. except (KeyError, EnvironmentError):
  2421. import SCons.SConsign
  2422. sconsign_entry = SCons.SConsign.SConsignEntry()
  2423. sconsign_entry.binfo = self.new_binfo()
  2424. sconsign_entry.ninfo = self.new_ninfo()
  2425. else:
  2426. if isinstance(sconsign_entry, FileBuildInfo):
  2427. # This is a .sconsign file from before the Big Signature
  2428. # Refactoring; convert it as best we can.
  2429. sconsign_entry = self.convert_old_entry(sconsign_entry)
  2430. try:
  2431. delattr(sconsign_entry.ninfo, 'bsig')
  2432. except AttributeError:
  2433. pass
  2434. self._memo['get_stored_info'] = sconsign_entry
  2435. return sconsign_entry
  2436. def get_stored_implicit(self):
  2437. binfo = self.get_stored_info().binfo
  2438. binfo.prepare_dependencies()
  2439. try: return binfo.bimplicit
  2440. except AttributeError: return None
  2441. def rel_path(self, other):
  2442. return self.dir.rel_path(other)
  2443. def _get_found_includes_key(self, env, scanner, path):
  2444. return (id(env), id(scanner), path)
  2445. @SCons.Memoize.CountDictCall(_get_found_includes_key)
  2446. def get_found_includes(self, env, scanner, path):
  2447. """Return the included implicit dependencies in this file.
  2448. Cache results so we only scan the file once per path
  2449. regardless of how many times this information is requested.
  2450. """
  2451. memo_key = (id(env), id(scanner), path)
  2452. try:
  2453. memo_dict = self._memo['get_found_includes']
  2454. except KeyError:
  2455. memo_dict = {}
  2456. self._memo['get_found_includes'] = memo_dict
  2457. else:
  2458. try:
  2459. return memo_dict[memo_key]
  2460. except KeyError:
  2461. pass
  2462. if scanner:
  2463. result = [n.disambiguate() for n in scanner(self, env, path)]
  2464. else:
  2465. result = []
  2466. memo_dict[memo_key] = result
  2467. return result
  2468. def _createDir(self):
  2469. # ensure that the directories for this node are
  2470. # created.
  2471. self.dir._create()
  2472. def push_to_cache(self):
  2473. """Try to push the node into a cache
  2474. """
  2475. # This should get called before the Nodes' .built() method is
  2476. # called, which would clear the build signature if the file has
  2477. # a source scanner.
  2478. #
  2479. # We have to clear the local memoized values *before* we push
  2480. # the node to cache so that the memoization of the self.exists()
  2481. # return value doesn't interfere.
  2482. if self.nocache:
  2483. return
  2484. self.clear_memoized_values()
  2485. if self.exists():
  2486. self.get_build_env().get_CacheDir().push(self)
  2487. def retrieve_from_cache(self):
  2488. """Try to retrieve the node's content from a cache
  2489. This method is called from multiple threads in a parallel build,
  2490. so only do thread safe stuff here. Do thread unsafe stuff in
  2491. built().
  2492. Returns true if the node was successfully retrieved.
  2493. """
  2494. if self.nocache:
  2495. return None
  2496. if not self.is_derived():
  2497. return None
  2498. return self.get_build_env().get_CacheDir().retrieve(self)
  2499. def visited(self):
  2500. if self.exists() and self.executor is not None:
  2501. self.get_build_env().get_CacheDir().push_if_forced(self)
  2502. ninfo = self.get_ninfo()
  2503. csig = self.get_max_drift_csig()
  2504. if csig:
  2505. ninfo.csig = csig
  2506. ninfo.timestamp = self.get_timestamp()
  2507. ninfo.size = self.get_size()
  2508. if not self.has_builder():
  2509. # This is a source file, but it might have been a target file
  2510. # in another build that included more of the DAG. Copy
  2511. # any build information that's stored in the .sconsign file
  2512. # into our binfo object so it doesn't get lost.
  2513. old = self.get_stored_info()
  2514. self.get_binfo().merge(old.binfo)
  2515. SCons.Node.store_info_map[self.store_info](self)
  2516. def release_target_info(self):
  2517. """Called just after this node has been marked
  2518. up-to-date or was built completely.
  2519. This is where we try to release as many target node infos
  2520. as possible for clean builds and update runs, in order
  2521. to minimize the overall memory consumption.
  2522. We'd like to remove a lot more attributes like self.sources
  2523. and self.sources_set, but they might get used
  2524. in a next build step. For example, during configuration
  2525. the source files for a built E{*}.o file are used to figure out
  2526. which linker to use for the resulting Program (gcc vs. g++)!
  2527. That's why we check for the 'keep_targetinfo' attribute,
  2528. config Nodes and the Interactive mode just don't allow
  2529. an early release of most variables.
  2530. In the same manner, we can't simply remove the self.attributes
  2531. here. The smart linking relies on the shared flag, and some
  2532. parts of the java Tool use it to transport information
  2533. about nodes...
  2534. @see: built() and Node.release_target_info()
  2535. """
  2536. if (self.released_target_info or SCons.Node.interactive):
  2537. return
  2538. if not hasattr(self.attributes, 'keep_targetinfo'):
  2539. # Cache some required values, before releasing
  2540. # stuff like env, executor and builder...
  2541. self.changed(allowcache=True)
  2542. self.get_contents_sig()
  2543. self.get_build_env()
  2544. # Now purge unneeded stuff to free memory...
  2545. self.executor = None
  2546. self._memo.pop('rfile', None)
  2547. self.prerequisites = None
  2548. # Cleanup lists, but only if they're empty
  2549. if not len(self.ignore_set):
  2550. self.ignore_set = None
  2551. if not len(self.implicit_set):
  2552. self.implicit_set = None
  2553. if not len(self.depends_set):
  2554. self.depends_set = None
  2555. if not len(self.ignore):
  2556. self.ignore = None
  2557. if not len(self.depends):
  2558. self.depends = None
  2559. # Mark this node as done, we only have to release
  2560. # the memory once...
  2561. self.released_target_info = True
  2562. def find_src_builder(self):
  2563. if self.rexists():
  2564. return None
  2565. scb = self.dir.src_builder()
  2566. if scb is _null:
  2567. scb = None
  2568. if scb is not None:
  2569. try:
  2570. b = self.builder
  2571. except AttributeError:
  2572. b = None
  2573. if b is None:
  2574. self.builder_set(scb)
  2575. return scb
  2576. def has_src_builder(self):
  2577. """Return whether this Node has a source builder or not.
  2578. If this Node doesn't have an explicit source code builder, this
  2579. is where we figure out, on the fly, if there's a transparent
  2580. source code builder for it.
  2581. Note that if we found a source builder, we also set the
  2582. self.builder attribute, so that all of the methods that actually
  2583. *build* this file don't have to do anything different.
  2584. """
  2585. try:
  2586. scb = self.sbuilder
  2587. except AttributeError:
  2588. scb = self.sbuilder = self.find_src_builder()
  2589. return scb is not None
  2590. def alter_targets(self):
  2591. """Return any corresponding targets in a variant directory.
  2592. """
  2593. if self.is_derived():
  2594. return [], None
  2595. return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
  2596. def _rmv_existing(self):
  2597. self.clear_memoized_values()
  2598. if SCons.Node.print_duplicate:
  2599. print("dup: removing existing target {}".format(self))
  2600. e = Unlink(self, [], None)
  2601. if isinstance(e, SCons.Errors.BuildError):
  2602. raise e
  2603. #
  2604. # Taskmaster interface subsystem
  2605. #
  2606. def make_ready(self):
  2607. self.has_src_builder()
  2608. self.get_binfo()
  2609. def prepare(self):
  2610. """Prepare for this file to be created."""
  2611. SCons.Node.Node.prepare(self)
  2612. if self.get_state() != SCons.Node.up_to_date:
  2613. if self.exists():
  2614. if self.is_derived() and not self.precious:
  2615. self._rmv_existing()
  2616. else:
  2617. try:
  2618. self._createDir()
  2619. except SCons.Errors.StopError as drive:
  2620. raise SCons.Errors.StopError("No drive `{}' for target `{}'.".format(drive, self))
  2621. #
  2622. #
  2623. #
  2624. def remove(self):
  2625. """Remove this file."""
  2626. if self.exists() or self.islink():
  2627. self.fs.unlink(self.get_internal_path())
  2628. return 1
  2629. return None
  2630. def do_duplicate(self, src):
  2631. self._createDir()
  2632. if SCons.Node.print_duplicate:
  2633. print("dup: relinking variant '{}' from '{}'".format(self, src))
  2634. Unlink(self, None, None)
  2635. e = Link(self, src, None)
  2636. if isinstance(e, SCons.Errors.BuildError):
  2637. raise SCons.Errors.StopError("Cannot duplicate `{}' in `{}': {}.".format(src.get_internal_path(), self.dir._path, e.errstr))
  2638. self.linked = 1
  2639. # The Link() action may or may not have actually
  2640. # created the file, depending on whether the -n
  2641. # option was used or not. Delete the _exists and
  2642. # _rexists attributes so they can be reevaluated.
  2643. self.clear()
  2644. @SCons.Memoize.CountMethodCall
  2645. def exists(self):
  2646. try:
  2647. return self._memo['exists']
  2648. except KeyError:
  2649. pass
  2650. result = SCons.Node._exists_map[self._func_exists](self)
  2651. self._memo['exists'] = result
  2652. return result
  2653. #
  2654. # SIGNATURE SUBSYSTEM
  2655. #
  2656. def get_max_drift_csig(self):
  2657. """
  2658. Returns the content signature currently stored for this node
  2659. if it's been unmodified longer than the max_drift value, or the
  2660. max_drift value is 0. Returns None otherwise.
  2661. """
  2662. old = self.get_stored_info()
  2663. mtime = self.get_timestamp()
  2664. max_drift = self.fs.max_drift
  2665. if max_drift > 0:
  2666. if (time.time() - mtime) > max_drift:
  2667. try:
  2668. n = old.ninfo
  2669. if n.timestamp and n.csig and n.timestamp == mtime:
  2670. return n.csig
  2671. except AttributeError:
  2672. pass
  2673. elif max_drift == 0:
  2674. try:
  2675. return old.ninfo.csig
  2676. except AttributeError:
  2677. pass
  2678. return None
  2679. def get_csig(self):
  2680. """
  2681. Generate a node's content signature, the digested signature
  2682. of its content.
  2683. node - the node
  2684. cache - alternate node to use for the signature cache
  2685. returns - the content signature
  2686. """
  2687. ninfo = self.get_ninfo()
  2688. try:
  2689. return ninfo.csig
  2690. except AttributeError:
  2691. pass
  2692. csig = self.get_max_drift_csig()
  2693. if csig is None:
  2694. try:
  2695. if self.get_size() < SCons.Node.FS.File.md5_chunksize:
  2696. contents = self.get_contents()
  2697. else:
  2698. csig = self.get_content_hash()
  2699. except IOError:
  2700. # This can happen if there's actually a directory on-disk,
  2701. # which can be the case if they've disabled disk checks,
  2702. # or if an action with a File target actually happens to
  2703. # create a same-named directory by mistake.
  2704. csig = ''
  2705. else:
  2706. if not csig:
  2707. csig = SCons.Util.MD5signature(contents)
  2708. ninfo.csig = csig
  2709. return csig
  2710. #
  2711. # DECISION SUBSYSTEM
  2712. #
  2713. def builder_set(self, builder):
  2714. SCons.Node.Node.builder_set(self, builder)
  2715. self.changed_since_last_build = 5
  2716. def built(self):
  2717. """Called just after this File node is successfully built.
  2718. Just like for 'release_target_info' we try to release
  2719. some more target node attributes in order to minimize the
  2720. overall memory consumption.
  2721. @see: release_target_info
  2722. """
  2723. SCons.Node.Node.built(self)
  2724. if (not SCons.Node.interactive and
  2725. not hasattr(self.attributes, 'keep_targetinfo')):
  2726. # Ensure that the build infos get computed and cached...
  2727. SCons.Node.store_info_map[self.store_info](self)
  2728. # ... then release some more variables.
  2729. self._specific_sources = False
  2730. self._labspath = None
  2731. self._save_str()
  2732. self.cwd = None
  2733. self.scanner_paths = None
  2734. def changed(self, node=None, allowcache=False):
  2735. """
  2736. Returns if the node is up-to-date with respect to the BuildInfo
  2737. stored last time it was built.
  2738. For File nodes this is basically a wrapper around Node.changed(),
  2739. but we allow the return value to get cached after the reference
  2740. to the Executor got released in release_target_info().
  2741. @see: Node.changed()
  2742. """
  2743. if node is None:
  2744. try:
  2745. return self._memo['changed']
  2746. except KeyError:
  2747. pass
  2748. has_changed = SCons.Node.Node.changed(self, node)
  2749. if allowcache:
  2750. self._memo['changed'] = has_changed
  2751. return has_changed
  2752. def changed_content(self, target, prev_ni):
  2753. cur_csig = self.get_csig()
  2754. try:
  2755. return cur_csig != prev_ni.csig
  2756. except AttributeError:
  2757. return 1
  2758. def changed_state(self, target, prev_ni):
  2759. return self.state != SCons.Node.up_to_date
  2760. def changed_timestamp_then_content(self, target, prev_ni):
  2761. if not self.changed_timestamp_match(target, prev_ni):
  2762. try:
  2763. self.get_ninfo().csig = prev_ni.csig
  2764. except AttributeError:
  2765. pass
  2766. return False
  2767. return self.changed_content(target, prev_ni)
  2768. def changed_timestamp_newer(self, target, prev_ni):
  2769. try:
  2770. return self.get_timestamp() > target.get_timestamp()
  2771. except AttributeError:
  2772. return 1
  2773. def changed_timestamp_match(self, target, prev_ni):
  2774. try:
  2775. return self.get_timestamp() != prev_ni.timestamp
  2776. except AttributeError:
  2777. return 1
  2778. def is_up_to_date(self):
  2779. T = 0
  2780. if T: Trace('is_up_to_date(%s):' % self)
  2781. if not self.exists():
  2782. if T: Trace(' not self.exists():')
  2783. # The file doesn't exist locally...
  2784. r = self.rfile()
  2785. if r != self:
  2786. # ...but there is one in a Repository...
  2787. if not self.changed(r):
  2788. if T: Trace(' changed(%s):' % r)
  2789. # ...and it's even up-to-date...
  2790. if self._local:
  2791. # ...and they'd like a local copy.
  2792. e = LocalCopy(self, r, None)
  2793. if isinstance(e, SCons.Errors.BuildError):
  2794. raise
  2795. SCons.Node.store_info_map[self.store_info](self)
  2796. if T: Trace(' 1\n')
  2797. return 1
  2798. self.changed()
  2799. if T: Trace(' None\n')
  2800. return None
  2801. else:
  2802. r = self.changed()
  2803. if T: Trace(' self.exists(): %s\n' % r)
  2804. return not r
  2805. @SCons.Memoize.CountMethodCall
  2806. def rfile(self):
  2807. try:
  2808. return self._memo['rfile']
  2809. except KeyError:
  2810. pass
  2811. result = self
  2812. if not self.exists():
  2813. norm_name = _my_normcase(self.name)
  2814. for dir in self.dir.get_all_rdirs():
  2815. try: node = dir.entries[norm_name]
  2816. except KeyError: node = dir.file_on_disk(self.name)
  2817. if node and node.exists() and \
  2818. (isinstance(node, File) or isinstance(node, Entry) \
  2819. or not node.is_derived()):
  2820. result = node
  2821. # Copy over our local attributes to the repository
  2822. # Node so we identify shared object files in the
  2823. # repository and don't assume they're static.
  2824. #
  2825. # This isn't perfect; the attribute would ideally
  2826. # be attached to the object in the repository in
  2827. # case it was built statically in the repository
  2828. # and we changed it to shared locally, but that's
  2829. # rarely the case and would only occur if you
  2830. # intentionally used the same suffix for both
  2831. # shared and static objects anyway. So this
  2832. # should work well in practice.
  2833. result.attributes = self.attributes
  2834. break
  2835. self._memo['rfile'] = result
  2836. return result
  2837. def rstr(self):
  2838. return str(self.rfile())
  2839. def get_cachedir_csig(self):
  2840. """
  2841. Fetch a Node's content signature for purposes of computing
  2842. another Node's cachesig.
  2843. This is a wrapper around the normal get_csig() method that handles
  2844. the somewhat obscure case of using CacheDir with the -n option.
  2845. Any files that don't exist would normally be "built" by fetching
  2846. them from the cache, but the normal get_csig() method will try
  2847. to open up the local file, which doesn't exist because the -n
  2848. option meant we didn't actually pull the file from cachedir.
  2849. But since the file *does* actually exist in the cachedir, we
  2850. can use its contents for the csig.
  2851. """
  2852. try:
  2853. return self.cachedir_csig
  2854. except AttributeError:
  2855. pass
  2856. cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
  2857. if not self.exists() and cachefile and os.path.exists(cachefile):
  2858. self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
  2859. SCons.Node.FS.File.md5_chunksize * 1024)
  2860. else:
  2861. self.cachedir_csig = self.get_csig()
  2862. return self.cachedir_csig
  2863. def get_contents_sig(self):
  2864. """
  2865. A helper method for get_cachedir_bsig.
  2866. It computes and returns the signature for this
  2867. node's contents.
  2868. """
  2869. try:
  2870. return self.contentsig
  2871. except AttributeError:
  2872. pass
  2873. executor = self.get_executor()
  2874. result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
  2875. return result
  2876. def get_cachedir_bsig(self):
  2877. """
  2878. Return the signature for a cached file, including
  2879. its children.
  2880. It adds the path of the cached file to the cache signature,
  2881. because multiple targets built by the same action will all
  2882. have the same build signature, and we have to differentiate
  2883. them somehow.
  2884. """
  2885. try:
  2886. return self.cachesig
  2887. except AttributeError:
  2888. pass
  2889. # Collect signatures for all children
  2890. children = self.children()
  2891. sigs = [n.get_cachedir_csig() for n in children]
  2892. # Append this node's signature...
  2893. sigs.append(self.get_contents_sig())
  2894. # ...and it's path
  2895. sigs.append(self.get_internal_path())
  2896. # Merge this all into a single signature
  2897. result = self.cachesig = SCons.Util.MD5collect(sigs)
  2898. return result
  2899. default_fs = None
  2900. def get_default_fs():
  2901. global default_fs
  2902. if not default_fs:
  2903. default_fs = FS()
  2904. return default_fs
  2905. class FileFinder(object):
  2906. """
  2907. """
  2908. def __init__(self):
  2909. self._memo = {}
  2910. def filedir_lookup(self, p, fd=None):
  2911. """
  2912. A helper method for find_file() that looks up a directory for
  2913. a file we're trying to find. This only creates the Dir Node if
  2914. it exists on-disk, since if the directory doesn't exist we know
  2915. we won't find any files in it... :-)
  2916. It would be more compact to just use this as a nested function
  2917. with a default keyword argument (see the commented-out version
  2918. below), but that doesn't work unless you have nested scopes,
  2919. so we define it here just so this work under Python 1.5.2.
  2920. """
  2921. if fd is None:
  2922. fd = self.default_filedir
  2923. dir, name = os.path.split(fd)
  2924. drive, d = _my_splitdrive(dir)
  2925. if not name and d[:1] in ('/', OS_SEP):
  2926. #return p.fs.get_root(drive).dir_on_disk(name)
  2927. return p.fs.get_root(drive)
  2928. if dir:
  2929. p = self.filedir_lookup(p, dir)
  2930. if not p:
  2931. return None
  2932. norm_name = _my_normcase(name)
  2933. try:
  2934. node = p.entries[norm_name]
  2935. except KeyError:
  2936. return p.dir_on_disk(name)
  2937. if isinstance(node, Dir):
  2938. return node
  2939. if isinstance(node, Entry):
  2940. node.must_be_same(Dir)
  2941. return node
  2942. return None
  2943. def _find_file_key(self, filename, paths, verbose=None):
  2944. return (filename, paths)
  2945. @SCons.Memoize.CountDictCall(_find_file_key)
  2946. def find_file(self, filename, paths, verbose=None):
  2947. """
  2948. Find a node corresponding to either a derived file or a file that exists already.
  2949. Only the first file found is returned, and none is returned if no file is found.
  2950. filename: A filename to find
  2951. paths: A list of directory path *nodes* to search in. Can be represented as a list, a tuple, or a callable that is called with no arguments and returns the list or tuple.
  2952. returns The node created from the found file.
  2953. """
  2954. memo_key = self._find_file_key(filename, paths)
  2955. try:
  2956. memo_dict = self._memo['find_file']
  2957. except KeyError:
  2958. memo_dict = {}
  2959. self._memo['find_file'] = memo_dict
  2960. else:
  2961. try:
  2962. return memo_dict[memo_key]
  2963. except KeyError:
  2964. pass
  2965. if verbose and not callable(verbose):
  2966. if not SCons.Util.is_String(verbose):
  2967. verbose = "find_file"
  2968. _verbose = u' %s: ' % verbose
  2969. verbose = lambda s: sys.stdout.write(_verbose + s)
  2970. filedir, filename = os.path.split(filename)
  2971. if filedir:
  2972. self.default_filedir = filedir
  2973. paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
  2974. result = None
  2975. for dir in paths:
  2976. if verbose:
  2977. verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
  2978. node, d = dir.srcdir_find_file(filename)
  2979. if node:
  2980. if verbose:
  2981. verbose("... FOUND '%s' in '%s'\n" % (filename, d))
  2982. result = node
  2983. break
  2984. memo_dict[memo_key] = result
  2985. return result
  2986. find_file = FileFinder().find_file
  2987. def invalidate_node_memos(targets):
  2988. """
  2989. Invalidate the memoized values of all Nodes (files or directories)
  2990. that are associated with the given entries. Has been added to
  2991. clear the cache of nodes affected by a direct execution of an
  2992. action (e.g. Delete/Copy/Chmod). Existing Node caches become
  2993. inconsistent if the action is run through Execute(). The argument
  2994. `targets` can be a single Node object or filename, or a sequence
  2995. of Nodes/filenames.
  2996. """
  2997. from traceback import extract_stack
  2998. # First check if the cache really needs to be flushed. Only
  2999. # actions run in the SConscript with Execute() seem to be
  3000. # affected. XXX The way to check if Execute() is in the stacktrace
  3001. # is a very dirty hack and should be replaced by a more sensible
  3002. # solution.
  3003. for f in extract_stack():
  3004. if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
  3005. break
  3006. else:
  3007. # Dont have to invalidate, so return
  3008. return
  3009. if not SCons.Util.is_List(targets):
  3010. targets = [targets]
  3011. for entry in targets:
  3012. # If the target is a Node object, clear the cache. If it is a
  3013. # filename, look up potentially existing Node object first.
  3014. try:
  3015. entry.clear_memoized_values()
  3016. except AttributeError:
  3017. # Not a Node object, try to look up Node by filename. XXX
  3018. # This creates Node objects even for those filenames which
  3019. # do not correspond to an existing Node object.
  3020. node = get_default_fs().Entry(entry)
  3021. if node:
  3022. node.clear_memoized_values()
  3023. # Local Variables:
  3024. # tab-width:4
  3025. # indent-tabs-mode:nil
  3026. # End:
  3027. # vim: set expandtab tabstop=4 shiftwidth=4: