5
These Nodes represent the canonical external objects that people think
6
of when they think of building software: files and directories.
8
This holds a "default_fs" variable that should be initialized with an FS
9
that can be used by scripts or modules looking for the canonical default.
14
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 The SCons Foundation
16
# Permission is hereby granted, free of charge, to any person obtaining
17
# a copy of this software and associated documentation files (the
18
# "Software"), to deal in the Software without restriction, including
19
# without limitation the rights to use, copy, modify, merge, publish,
20
# distribute, sublicense, and/or sell copies of the Software, and to
21
# permit persons to whom the Software is furnished to do so, subject to
22
# the following conditions:
24
# The above copyright notice and this permission notice shall be included
25
# in all copies or substantial portions of the Software.
27
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
28
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
29
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
__revision__ = "src/engine/SCons/Node/FS.py 2013/03/03 09:48:35 garyo"
47
from SCons.Debug import logInstanceCreation
51
import SCons.Node.Alias
56
from SCons.Debug import Trace
62
class EntryProxyAttributeError(AttributeError):
64
An AttributeError subclass for recording and displaying the name
65
of the underlying Entry involved in an AttributeError exception.
67
def __init__(self, entry_proxy, attribute):
68
AttributeError.__init__(self)
69
self.entry_proxy = entry_proxy
70
self.attribute = attribute
72
entry = self.entry_proxy.get()
73
fmt = "%s instance %s has no attribute %s"
74
return fmt % (entry.__class__.__name__,
78
# The max_drift value: by default, use a cached signature value for
79
# any file that's been untouched for more than two days.
80
default_max_drift = 2*24*60*60
83
# We stringify these file system Nodes a lot. Turning a file system Node
84
# into a string is non-trivial, because the final string representation
85
# can depend on a lot of factors: whether it's a derived target or not,
86
# whether it's linked to a repository or source directory, and whether
87
# there's duplication going on. The normal technique for optimizing
88
# calculations like this is to memoize (cache) the string value, so you
89
# only have to do the calculation once.
91
# A number of the above factors, however, can be set after we've already
92
# been asked to return a string for a Node, because a Repository() or
93
# VariantDir() call or the like may not occur until later in SConscript
94
# files. So this variable controls whether we bother trying to save
95
# string values for Nodes. The wrapper interface can set this whenever
96
# they're done mucking with Repository and VariantDir and the other stuff,
97
# to let this module know it can start returning saved string values
102
def save_strings(val):
107
# Avoid unnecessary function calls by recording a Boolean value that
108
# tells us whether or not os.path.splitdrive() actually does anything
109
# on this system, and therefore whether we need to bother calling it
110
# when looking up path names in various methods below.
116
def initialize_do_splitdrive():
119
drive, path = os.path.splitdrive('X:/foo')
120
has_unc = hasattr(os.path, 'splitunc')
122
do_splitdrive = not not drive or has_unc
124
global _my_splitdrive
130
# Note that we leave a leading slash in the path
131
# because UNC paths are always absolute.
139
_my_splitdrive = splitdrive
141
# Keep some commonly used values in global variables to skip to
142
# module look-up costs.
145
global os_sep_is_slash
148
UNC_PREFIX = OS_SEP + OS_SEP
149
os_sep_is_slash = OS_SEP == '/'
151
initialize_do_splitdrive()
153
# Used to avoid invoking os.path.normpath if not necessary.
154
needs_normpath_check = re.compile(
156
# We need to renormalize the path if it contains any consecutive
160
# We need to renormalize the path if it contains a '..' directory.
161
# Note that we check for all the following cases:
163
# a) The path is a single '..'
164
# b) The path starts with '..'. E.g. '../' or '../moredirs'
165
# but we not match '..abc/'.
166
# c) The path ends with '..'. E.g. '/..' or 'dirs/..'
167
# d) The path contains a '..' in the middle.
168
# E.g. dirs/../moredirs
172
# We need to renormalize the path if it contains a '.'
173
# directory, but NOT if it is a single '.' '/' characters. We
174
# do not want to match a single '.' because this case is checked
175
# for explicitely since this is common enough case.
177
# Note that we check for all the following cases:
179
# a) We don't match a single '.'
180
# b) We match if the path starts with '.'. E.g. './' or
181
# './moredirs' but we not match '.abc/'.
182
# c) We match if the path ends with '.'. E.g. '/.' or
184
# d) We match if the path contains a '.' in the middle.
185
# E.g. dirs/./moredirs
192
needs_normpath_match = needs_normpath_check.match
195
# SCons.Action objects for interacting with the outside world.
197
# The Node.FS methods in this module should use these actions to
198
# create and/or remove files and directories; they should *not* use
199
# os.{link,symlink,unlink,mkdir}(), etc., directly.
201
# Using these SCons.Action objects ensures that descriptions of these
202
# external activities are properly displayed, that the displays are
203
# suppressed when the -s (silent) option is used, and (most importantly)
204
# the actions are disabled when the the -n option is used, in which case
205
# there should be *no* changes to the external file system(s)...
208
if hasattr(os, 'link'):
209
def _hardlink_func(fs, src, dst):
210
# If the source is a symlink, we can't just hard-link to it
211
# because a relative symlink may point somewhere completely
212
# different. We must disambiguate the symlink and then
213
# hard-link the final destination file.
214
while fs.islink(src):
215
link = fs.readlink(src)
216
if not os.path.isabs(link):
219
src = os.path.join(os.path.dirname(src), link)
222
_hardlink_func = None
224
if hasattr(os, 'symlink'):
225
def _softlink_func(fs, src, dst):
228
_softlink_func = None
230
def _copy_func(fs, src, dest):
231
shutil.copy2(src, dest)
233
fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
236
Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
237
'hard-copy', 'soft-copy', 'copy']
239
Link_Funcs = [] # contains the callables of the specified duplication style
241
def set_duplicate(duplicate):
242
# Fill in the Link_Funcs list according to the argument
243
# (discarding those not available on the platform).
245
# Set up the dictionary that maps the argument names to the
246
# underlying implementations. We do this inside this function,
247
# not in the top-level module code, so that we can remap os.link
248
# and os.symlink for testing purposes.
250
'hard' : _hardlink_func,
251
'soft' : _softlink_func,
255
if not duplicate in Valid_Duplicates:
256
raise SCons.Errors.InternalError("The argument of set_duplicate "
257
"should be in Valid_Duplicates")
260
for func in duplicate.split('-'):
262
Link_Funcs.append(link_dict[func])
264
def LinkFunc(target, source, env):
265
# Relative paths cause problems with symbolic links, so
266
# we use absolute paths, which may be a problem for people
267
# who want to move their soft-linked src-trees around. Those
268
# people should use the 'hard-copy' mode, softlinks cannot be
269
# used for that; at least I have no idea how ...
270
src = source[0].abspath
271
dest = target[0].abspath
272
dir, file = os.path.split(dest)
273
if dir and not target[0].fs.isdir(dir):
276
# Set a default order of link functions.
277
set_duplicate('hard-soft-copy')
279
# Now link the files with the previously specified order.
280
for func in Link_Funcs:
284
except (IOError, OSError):
285
# An OSError indicates something happened like a permissions
286
# problem or an attempt to symlink across file-system
287
# boundaries. An IOError indicates something like the file
288
# not existing. In either case, keeping trying additional
289
# functions in the list and only raise an error if the last
291
if func == Link_Funcs[-1]:
292
# exception of the last link method (copy) are fatal
296
Link = SCons.Action.Action(LinkFunc, None)
297
def LocalString(target, source, env):
298
return 'Local copy of %s from %s' % (target[0], source[0])
300
LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
302
def UnlinkFunc(target, source, env):
304
t.fs.unlink(t.abspath)
307
Unlink = SCons.Action.Action(UnlinkFunc, None)
309
def MkdirFunc(target, source, env):
312
t.fs.mkdir(t.abspath)
315
Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
319
def get_MkdirBuilder():
321
if MkdirBuilder is None:
323
import SCons.Defaults
324
# "env" will get filled in by Executor.get_build_env()
325
# calling SCons.Defaults.DefaultEnvironment() when necessary.
326
MkdirBuilder = SCons.Builder.Builder(action = Mkdir,
330
target_scanner = SCons.Defaults.DirEntryScanner,
331
name = "MkdirBuilder")
339
DefaultSCCSBuilder = None
340
DefaultRCSBuilder = None
342
def get_DefaultSCCSBuilder():
343
global DefaultSCCSBuilder
344
if DefaultSCCSBuilder is None:
346
# "env" will get filled in by Executor.get_build_env()
347
# calling SCons.Defaults.DefaultEnvironment() when necessary.
348
act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR')
349
DefaultSCCSBuilder = SCons.Builder.Builder(action = act,
351
name = "DefaultSCCSBuilder")
352
return DefaultSCCSBuilder
354
def get_DefaultRCSBuilder():
355
global DefaultRCSBuilder
356
if DefaultRCSBuilder is None:
358
# "env" will get filled in by Executor.get_build_env()
359
# calling SCons.Defaults.DefaultEnvironment() when necessary.
360
act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR')
361
DefaultRCSBuilder = SCons.Builder.Builder(action = act,
363
name = "DefaultRCSBuilder")
364
return DefaultRCSBuilder
366
# Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
367
_is_cygwin = sys.platform == "cygwin"
368
if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
377
class DiskChecker(object):
378
def __init__(self, type, do, ignore):
383
def __call__(self, *args, **kw):
384
return self.func(*args, **kw)
386
if self.type in list:
389
self.func = self.ignore
391
def do_diskcheck_match(node, predicate, errorfmt):
394
# If calling the predicate() cached a None value from stat(),
395
# remove it so it doesn't interfere with later attempts to
396
# build this Node as we walk the DAG. (This isn't a great way
397
# to do this, we're reaching into an interface that doesn't
398
# really belong to us, but it's all about performance, so
399
# for now we'll just document the dependency...)
400
if node._memo['stat'] is None:
401
del node._memo['stat']
402
except (AttributeError, KeyError):
405
raise TypeError(errorfmt % node.abspath)
407
def ignore_diskcheck_match(node, predicate, errorfmt):
410
def do_diskcheck_rcs(node, name):
412
rcs_dir = node.rcs_dir
413
except AttributeError:
414
if node.entry_exists_on_disk('RCS'):
415
rcs_dir = node.Dir('RCS')
418
node.rcs_dir = rcs_dir
420
return rcs_dir.entry_exists_on_disk(name+',v')
423
def ignore_diskcheck_rcs(node, name):
426
def do_diskcheck_sccs(node, name):
428
sccs_dir = node.sccs_dir
429
except AttributeError:
430
if node.entry_exists_on_disk('SCCS'):
431
sccs_dir = node.Dir('SCCS')
434
node.sccs_dir = sccs_dir
436
return sccs_dir.entry_exists_on_disk('s.'+name)
439
def ignore_diskcheck_sccs(node, name):
442
diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
443
diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
444
diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
452
def set_diskcheck(list):
453
for dc in diskcheckers:
456
def diskcheck_types():
457
return [dc.type for dc in diskcheckers]
461
class EntryProxy(SCons.Util.Proxy):
463
__str__ = SCons.Util.Delegate('__str__')
465
def __get_abspath(self):
467
return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
468
entry.name + "_abspath")
470
def __get_filebase(self):
471
name = self.get().name
472
return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
475
def __get_suffix(self):
476
name = self.get().name
477
return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
480
def __get_file(self):
481
name = self.get().name
482
return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
484
def __get_base_path(self):
485
"""Return the file's directory and file name, with the
488
return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
489
entry.name + "_base")
491
def __get_posix_path(self):
492
"""Return the path with / as the path separator,
493
regardless of platform."""
498
r = entry.get_path().replace(OS_SEP, '/')
499
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
501
def __get_windows_path(self):
502
"""Return the path with \ as the path separator,
503
regardless of platform."""
508
r = entry.get_path().replace(OS_SEP, '\\')
509
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
511
def __get_srcnode(self):
512
return EntryProxy(self.get().srcnode())
514
def __get_srcdir(self):
515
"""Returns the directory containing the source node linked to this
516
node via VariantDir(), or the directory of this node if not linked."""
517
return EntryProxy(self.get().srcnode().dir)
519
def __get_rsrcnode(self):
520
return EntryProxy(self.get().srcnode().rfile())
522
def __get_rsrcdir(self):
523
"""Returns the directory containing the source node linked to this
524
node via VariantDir(), or the directory of this node if not linked."""
525
return EntryProxy(self.get().srcnode().rfile().dir)
528
return EntryProxy(self.get().dir)
530
dictSpecialAttrs = { "base" : __get_base_path,
531
"posix" : __get_posix_path,
532
"windows" : __get_windows_path,
533
"win32" : __get_windows_path,
534
"srcpath" : __get_srcnode,
535
"srcdir" : __get_srcdir,
537
"abspath" : __get_abspath,
538
"filebase" : __get_filebase,
539
"suffix" : __get_suffix,
541
"rsrcpath" : __get_rsrcnode,
542
"rsrcdir" : __get_rsrcdir,
545
def __getattr__(self, name):
546
# This is how we implement the "special" attributes
547
# such as base, posix, srcdir, etc.
549
attr_function = self.dictSpecialAttrs[name]
552
attr = SCons.Util.Proxy.__getattr__(self, name)
553
except AttributeError, e:
554
# Raise our own AttributeError subclass with an
555
# overridden __str__() method that identifies the
556
# name of the entry that caused the exception.
557
raise EntryProxyAttributeError(self, name)
560
return attr_function(self)
562
class Base(SCons.Node.Node):
563
"""A generic class for file system entries. This class is for
564
when we don't know yet whether the entry being looked up is a file
565
or a directory. Instances of this class can morph into either
566
Dir or File objects by a later, more precise lookup.
568
Note: this class does not define __cmp__ and __hash__ for
569
efficiency reasons. SCons does a lot of comparing of
570
Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
571
as fast as possible, which means we want to use Python's built-in
572
object identity comparisons.
575
memoizer_counters = []
577
def __init__(self, name, directory, fs):
578
"""Initialize a generic Node.FS.Base object.
580
Call the superclass initialization, take care of setting up
581
our relative and absolute paths, identify our parent
582
directory, and indicate that this node should use
584
if __debug__: logInstanceCreation(self, 'Node.FS.Base')
585
SCons.Node.Node.__init__(self)
587
# Filenames and paths are probably reused and are intern'ed to
590
#: Filename with extension as it was specified when the object was
591
#: created; to obtain filesystem path, use Python str() function
592
self.name = SCons.Util.silent_intern(name)
593
#: Cached filename extension
594
self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
595
self.fs = fs #: Reference to parent Node.FS object
597
assert directory, "A directory must be provided"
599
self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name))
600
self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name))
601
if directory.path == '.':
602
self.path = SCons.Util.silent_intern(name)
604
self.path = SCons.Util.silent_intern(directory.entry_path(name))
605
if directory.tpath == '.':
606
self.tpath = SCons.Util.silent_intern(name)
608
self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name))
609
self.path_elements = directory.path_elements + [self]
612
self.cwd = None # will hold the SConscript directory for target nodes
613
self.duplicate = directory.duplicate
615
def str_for_display(self):
616
return '"' + self.__str__() + '"'
618
def must_be_same(self, klass):
620
This node, which already existed, is being looked up as the
621
specified klass. Raise an exception if it isn't.
623
if isinstance(self, klass) or klass is Entry:
625
raise TypeError("Tried to lookup %s '%s' as a %s." %\
626
(self.__class__.__name__, self.path, klass.__name__))
631
def get_suffix(self):
638
"""A Node.FS.Base object's string representation is its path
642
return self._save_str()
643
return self._get_str()
645
memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
649
return self._memo['_save_str']
652
result = sys.intern(self._get_str())
653
self._memo['_save_str'] = result
658
if self.duplicate or self.is_derived():
659
return self.get_path()
660
srcnode = self.srcnode()
661
if srcnode.stat() is None and self.stat() is not None:
662
result = self.get_path()
664
result = srcnode.get_path()
666
# We're not at the point where we're saving the string
667
# representations of FS Nodes (because we haven't finished
668
# reading the SConscript files and need to have str() return
669
# things relative to them). That also means we can't yet
670
# cache values returned (or not returned) by stat(), since
671
# Python code in the SConscript files might still create
672
# or otherwise affect the on-disk file. So get rid of the
673
# values that the underlying stat() method saved.
674
try: del self._memo['stat']
675
except KeyError: pass
676
if self is not srcnode:
677
try: del srcnode._memo['stat']
678
except KeyError: pass
683
memoizer_counters.append(SCons.Memoize.CountValue('stat'))
686
try: return self._memo['stat']
687
except KeyError: pass
688
try: result = self.fs.stat(self.abspath)
689
except os.error: result = None
690
self._memo['stat'] = result
694
return self.stat() is not None
697
return self.rfile().exists()
701
if st: return st[stat.ST_MTIME]
706
if st: return st[stat.ST_SIZE]
711
return st is not None and stat.S_ISDIR(st[stat.ST_MODE])
715
return st is not None and stat.S_ISREG(st[stat.ST_MODE])
717
if hasattr(os, 'symlink'):
719
try: st = self.fs.lstat(self.abspath)
720
except os.error: return 0
721
return stat.S_ISLNK(st[stat.ST_MODE])
724
return 0 # no symlinks
726
def is_under(self, dir):
730
return self.dir.is_under(dir)
736
"""If this node is in a build path, return the node
737
corresponding to its source file. Otherwise, return
740
srcdir_list = self.dir.srcdir_list()
742
srcnode = srcdir_list[0].Entry(self.name)
743
srcnode.must_be_same(self.__class__)
747
def get_path(self, dir=None):
748
"""Return path relative to the current working directory of the
749
Node.FS.Base object that owns us."""
751
dir = self.fs.getcwd()
754
path_elems = self.path_elements
756
try: i = path_elems.index(dir)
758
for p in path_elems[:-1]:
759
pathname += p.dirname
761
for p in path_elems[i+1:-1]:
762
pathname += p.dirname
763
return pathname + path_elems[-1].name
765
def set_src_builder(self, builder):
766
"""Set the source code builder for this node."""
767
self.sbuilder = builder
768
if not self.has_builder():
769
self.builder_set(builder)
771
def src_builder(self):
772
"""Fetch the source code builder for this node.
774
If there isn't one, we cache the source code builder specified
775
for the directory (which in turn will cache the value from its
776
parent directory, and so on up to the file system root).
780
except AttributeError:
781
scb = self.dir.src_builder()
785
def get_abspath(self):
786
"""Get the absolute path of the file."""
789
def for_signature(self):
790
# Return just our name. Even an absolute path would not work,
791
# because that can change thanks to symlinks or remapped network
795
def get_subst_proxy(self):
798
except AttributeError:
799
ret = EntryProxy(self)
803
def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext):
806
Generates a target entry that corresponds to this entry (usually
807
a source file) with the specified prefix and suffix.
809
Note that this method can be overridden dynamically for generated
810
files that need different behavior. See Tool/swig.py for
813
return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
815
def _Rfindalldirs_key(self, pathlist):
818
memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
820
def Rfindalldirs(self, pathlist):
822
Return all of the directories for a given path list, including
823
corresponding "backing" directories in any repositories.
825
The Node lookups are relative to this Node (typically a
826
directory), so memoizing result saves cycles from looking
827
up the same path for each target in a given directory.
830
memo_dict = self._memo['Rfindalldirs']
833
self._memo['Rfindalldirs'] = memo_dict
836
return memo_dict[pathlist]
840
create_dir_relative_to_self = self.Dir
842
for path in pathlist:
843
if isinstance(path, SCons.Node.Node):
846
dir = create_dir_relative_to_self(path)
847
result.extend(dir.get_all_rdirs())
849
memo_dict[pathlist] = result
853
def RDirs(self, pathlist):
854
"""Search for a list of directories in the Repository list."""
855
cwd = self.cwd or self.fs._cwd
856
return cwd.Rfindalldirs(pathlist)
858
memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
862
return self._memo['rentry']
866
if not self.exists():
867
norm_name = _my_normcase(self.name)
868
for dir in self.dir.get_all_rdirs():
870
node = dir.entries[norm_name]
872
if dir.entry_exists_on_disk(self.name):
873
result = dir.Entry(self.name)
875
self._memo['rentry'] = result
878
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
882
"""This is the class for generic Node.FS entries--that is, things
883
that could be a File or a Dir, but we're just not sure yet.
884
Consequently, the methods in this class really exist just to
885
transform their associated object into the right class when the
886
time comes, and then call the same-named method in the transformed
889
def diskcheck_match(self):
892
def disambiguate(self, must_exist=None):
899
self.__class__ = File
903
# There was nothing on-disk at this location, so look in
906
# We can't just use self.srcnode() straight away because
907
# that would create an actual Node for this file in the src
908
# directory, and there might not be one. Instead, use the
909
# dir_on_disk() method to see if there's something on-disk
910
# with that name, in which case we can go ahead and call
911
# self.srcnode() to create the right type of entry.
912
srcdir = self.dir.srcnode()
913
if srcdir != self.dir and \
914
srcdir.entry_exists_on_disk(self.name) and \
915
self.srcnode().isdir():
919
msg = "No such file or directory: '%s'" % self.abspath
920
raise SCons.Errors.UserError(msg)
922
self.__class__ = File
928
"""We're a generic Entry, but the caller is actually looking for
929
a File at this point, so morph into one."""
930
self.__class__ = File
933
return File.rfile(self)
935
def scanner_key(self):
936
return self.get_suffix()
938
def get_contents(self):
939
"""Fetch the contents of the entry. Returns the exact binary
940
contents of the file."""
942
self = self.disambiguate(must_exist=1)
943
except SCons.Errors.UserError:
944
# There was nothing on disk with which to disambiguate
945
# this entry. Leave it as an Entry, but return a null
946
# string so calls to get_contents() in emitters and the
947
# like (e.g. in qt.py) don't have to disambiguate by hand
948
# or catch the exception.
951
return self.get_contents()
953
def get_text_contents(self):
954
"""Fetch the decoded text contents of a Unicode encoded Entry.
956
Since this should return the text contents from the file
957
system, we check to see into what sort of subclass we should
960
self = self.disambiguate(must_exist=1)
961
except SCons.Errors.UserError:
962
# There was nothing on disk with which to disambiguate
963
# this entry. Leave it as an Entry, but return a null
964
# string so calls to get_text_contents() in emitters and
965
# the like (e.g. in qt.py) don't have to disambiguate by
966
# hand or catch the exception.
969
return self.get_text_contents()
971
def must_be_same(self, klass):
972
"""Called to make sure a Node is a Dir. Since we're an
973
Entry, we can morph into one."""
974
if self.__class__ is not klass:
975
self.__class__ = klass
979
# The following methods can get called before the Taskmaster has
980
# had a chance to call disambiguate() directly to see if this Entry
981
# should really be a Dir or a File. We therefore use these to call
982
# disambiguate() transparently (from our caller's point of view).
984
# Right now, this minimal set of methods has been derived by just
985
# looking at some of the methods that will obviously be called early
986
# in any of the various Taskmasters' calling sequences, and then
987
# empirically figuring out which additional methods are necessary
988
# to make various tests pass.
991
"""Return if the Entry exists. Check the file system to see
992
what we should turn into first. Assume a file if there's no
994
return self.disambiguate().exists()
996
def rel_path(self, other):
997
d = self.disambiguate()
998
if d.__class__ is Entry:
999
raise Exception("rel_path() could not disambiguate File/Dir")
1000
return d.rel_path(other)
1002
def new_ninfo(self):
1003
return self.disambiguate().new_ninfo()
1005
def changed_since_last_build(self, target, prev_ni):
1006
return self.disambiguate().changed_since_last_build(target, prev_ni)
1008
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
1009
return self.disambiguate()._glob1(pattern, ondisk, source, strings)
1011
def get_subst_proxy(self):
1012
return self.disambiguate().get_subst_proxy()
1014
# This is for later so we can differentiate between Entry the class and Entry
1015
# the method of the FS class.
1019
class LocalFS(object):
1021
if SCons.Memoize.use_memoizer:
1022
__metaclass__ = SCons.Memoize.Memoized_Metaclass
1024
# This class implements an abstraction layer for operations involving
1025
# a local file system. Essentially, this wraps any function in
1026
# the os, os.path or shutil modules that we use to actually go do
1027
# anything with or to the local file system.
1029
# Note that there's a very good chance we'll refactor this part of
1030
# the architecture in some way as we really implement the interface(s)
1031
# for remote file system Nodes. For example, the right architecture
1032
# might be to have this be a subclass instead of a base class.
1033
# Nevertheless, we're using this as a first step in that direction.
1035
# We're not using chdir() yet because the calling subclass method
1036
# needs to use os.chdir() directly to avoid recursion. Will we
1037
# really need this one?
1038
#def chdir(self, path):
1039
# return os.chdir(path)
1040
def chmod(self, path, mode):
1041
return os.chmod(path, mode)
1042
def copy(self, src, dst):
1043
return shutil.copy(src, dst)
1044
def copy2(self, src, dst):
1045
return shutil.copy2(src, dst)
1046
def exists(self, path):
1047
return os.path.exists(path)
1048
def getmtime(self, path):
1049
return os.path.getmtime(path)
1050
def getsize(self, path):
1051
return os.path.getsize(path)
1052
def isdir(self, path):
1053
return os.path.isdir(path)
1054
def isfile(self, path):
1055
return os.path.isfile(path)
1056
def link(self, src, dst):
1057
return os.link(src, dst)
1058
def lstat(self, path):
1059
return os.lstat(path)
1060
def listdir(self, path):
1061
return os.listdir(path)
1062
def makedirs(self, path):
1063
return os.makedirs(path)
1064
def mkdir(self, path):
1065
return os.mkdir(path)
1066
def rename(self, old, new):
1067
return os.rename(old, new)
1068
def stat(self, path):
1069
return os.stat(path)
1070
def symlink(self, src, dst):
1071
return os.symlink(src, dst)
1072
def open(self, path):
1074
def unlink(self, path):
1075
return os.unlink(path)
1077
if hasattr(os, 'symlink'):
1078
def islink(self, path):
1079
return os.path.islink(path)
1081
def islink(self, path):
1082
return 0 # no symlinks
1084
if hasattr(os, 'readlink'):
1085
def readlink(self, file):
1086
return os.readlink(file)
1088
def readlink(self, file):
1093
# # Skeleton for the obvious methods we might need from the
1094
# # abstraction layer for a remote filesystem.
1095
# def upload(self, local_src, remote_dst):
1097
# def download(self, remote_src, local_dst):
1103
memoizer_counters = []
1105
def __init__(self, path = None):
1106
"""Initialize the Node.FS subsystem.
1108
The supplied path is the top of the source tree, where we
1109
expect to find the top-level build file. If no path is
1110
supplied, the current directory is the default.
1112
The path argument must be a valid absolute path.
1114
if __debug__: logInstanceCreation(self, 'Node.FS')
1119
self.SConstruct_dir = None
1120
self.max_drift = default_max_drift
1124
self.pathTop = os.getcwd()
1127
self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1129
self.Top = self.Dir(self.pathTop)
1131
self.Top.tpath = '.'
1132
self._cwd = self.Top
1134
DirNodeInfo.fs = self
1135
FileNodeInfo.fs = self
1137
def set_SConstruct_dir(self, dir):
1138
self.SConstruct_dir = dir
1140
def get_max_drift(self):
1141
return self.max_drift
1143
def set_max_drift(self, max_drift):
1144
self.max_drift = max_drift
1147
if hasattr(self, "_cwd"):
1152
def chdir(self, dir, change_os_dir=0):
1153
"""Change the current working directory for lookups.
1154
If change_os_dir is true, we will also change the "real" cwd
1162
os.chdir(dir.abspath)
1167
def get_root(self, drive):
1169
Returns the root directory for the specified drive, creating
1172
drive = _my_normcase(drive)
1174
return self.Root[drive]
1176
root = RootDir(drive, self)
1177
self.Root[drive] = root
1179
self.Root[self.defaultDrive] = root
1180
elif drive == self.defaultDrive:
1181
self.Root[''] = root
1184
def _lookup(self, p, directory, fsclass, create=1):
1186
The generic entry point for Node lookup with user-supplied data.
1188
This translates arbitrary input into a canonical Node.FS object
1189
of the specified fsclass. The general approach for strings is
1190
to turn it into a fully normalized absolute path and then call
1191
the root directory's lookup_abs() method for the heavy lifting.
1193
If the path name begins with '#', it is unconditionally
1194
interpreted relative to the top-level directory of this FS. '#'
1195
is treated as a synonym for the top-level SConstruct directory,
1196
much like '~' is treated as a synonym for the user's home
1197
directory in a UNIX shell. So both '#foo' and '#/foo' refer
1198
to the 'foo' subdirectory underneath the top-level SConstruct
1201
If the path name is relative, then the path is looked up relative
1202
to the specified directory, or the current directory (self._cwd,
1203
typically the SConscript directory) if the specified directory
1206
if isinstance(p, Base):
1207
# It's already a Node.FS object. Make sure it's the right
1209
p.must_be_same(fsclass)
1211
# str(p) in case it's something like a proxy object
1214
if not os_sep_is_slash:
1215
p = p.replace(OS_SEP, '/')
1218
# There was an initial '#', so we strip it and override
1219
# whatever directory they may have specified with the
1220
# top-level SConstruct directory.
1222
directory = self.Top
1224
# There might be a drive letter following the
1225
# '#'. Although it is not described in the SCons man page,
1226
# the regression test suite explicitly tests for that
1227
# syntax. It seems to mean the following thing:
1229
# Assuming the the SCons top dir is in C:/xxx/yyy,
1230
# '#X:/toto' means X:/xxx/yyy/toto.
1232
# i.e. it assumes that the X: drive has a directory
1233
# structure similar to the one found on drive C:.
1235
drive, p = _my_splitdrive(p)
1237
root = self.get_root(drive)
1239
root = directory.root
1241
root = directory.root
1243
# We can only strip trailing after splitting the drive
1244
# since the drive might the UNC '//' prefix.
1247
needs_normpath = needs_normpath_match(p)
1249
# The path is relative to the top-level SCons directory.
1251
p = directory.labspath
1253
p = directory.labspath + '/' + p
1256
drive, p = _my_splitdrive(p)
1258
# This causes a naked drive letter to be treated
1259
# as a synonym for the root directory on that
1265
# We can only strip trailing '/' since the drive might the
1270
needs_normpath = needs_normpath_match(p)
1274
root = self.get_root(drive)
1276
# This is a relative lookup or to the current directory
1277
# (the path name is not absolute). Add the string to the
1278
# appropriate directory lookup path, after which the whole
1279
# thing gets normalized.
1281
if not isinstance(directory, Dir):
1282
directory = self.Dir(directory)
1284
directory = self._cwd
1287
p = directory.labspath
1289
p = directory.labspath + '/' + p
1292
root = self.get_root(drive)
1294
root = directory.root
1296
if needs_normpath is not None:
1297
# Normalize a pathname. Will return the same result for
1300
# We take advantage of the fact that we have an absolute
1301
# path here for sure. In addition, we know that the
1302
# components of lookup path are separated by slashes at
1303
# this point. Because of this, this code is about 2X
1304
# faster than calling os.path.normpath() followed by
1305
# replacing os.sep with '/' again.
1306
ins = p.split('/')[1:]
1314
elif d not in ('', '.'):
1316
p = '/' + '/'.join(outs)
1318
return root._lookup_abs(p, fsclass, create)
1320
def Entry(self, name, directory = None, create = 1):
1321
"""Look up or create a generic Entry node with the specified name.
1322
If the name is a relative path (begins with ./, ../, or a file
1323
name), then it is looked up relative to the supplied directory
1324
node, or to the top level directory of the FS (supplied at
1325
construction time) if no directory is supplied.
1327
return self._lookup(name, directory, Entry, create)
1329
def File(self, name, directory = None, create = 1):
1330
"""Look up or create a File node with the specified name. If
1331
the name is a relative path (begins with ./, ../, or a file name),
1332
then it is looked up relative to the supplied directory node,
1333
or to the top level directory of the FS (supplied at construction
1334
time) if no directory is supplied.
1336
This method will raise TypeError if a directory is found at the
1339
return self._lookup(name, directory, File, create)
1341
def Dir(self, name, directory = None, create = True):
1342
"""Look up or create a Dir node with the specified name. If
1343
the name is a relative path (begins with ./, ../, or a file name),
1344
then it is looked up relative to the supplied directory node,
1345
or to the top level directory of the FS (supplied at construction
1346
time) if no directory is supplied.
1348
This method will raise TypeError if a normal file is found at the
1351
return self._lookup(name, directory, Dir, create)
1353
def VariantDir(self, variant_dir, src_dir, duplicate=1):
1354
"""Link the supplied variant directory to the source directory
1355
for purposes of building files."""
1357
if not isinstance(src_dir, SCons.Node.Node):
1358
src_dir = self.Dir(src_dir)
1359
if not isinstance(variant_dir, SCons.Node.Node):
1360
variant_dir = self.Dir(variant_dir)
1361
if src_dir.is_under(variant_dir):
1362
raise SCons.Errors.UserError("Source directory cannot be under variant directory.")
1363
if variant_dir.srcdir:
1364
if variant_dir.srcdir == src_dir:
1365
return # We already did this.
1366
raise SCons.Errors.UserError("'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir))
1367
variant_dir.link(src_dir, duplicate)
1369
def Repository(self, *dirs):
1370
"""Specify Repository directories to search."""
1372
if not isinstance(d, SCons.Node.Node):
1374
self.Top.addRepository(d)
1376
def variant_dir_target_climb(self, orig, dir, tail):
1377
"""Create targets in corresponding variant directories
1379
Climb the directory tree, and look up path names
1380
relative to any linked variant directories we find.
1382
Even though this loops and walks up the tree, we don't memoize
1383
the return value because this is really only used to process
1384
the command-line targets.
1388
fmt = "building associated VariantDir targets: %s"
1391
for bd in dir.variant_dirs:
1392
if start_dir.is_under(bd):
1393
# If already in the build-dir location, don't reflect
1394
return [orig], fmt % str(orig)
1395
p = os.path.join(bd.path, *tail)
1396
targets.append(self.Entry(p))
1397
tail = [dir.name] + tail
1400
message = fmt % ' '.join(map(str, targets))
1401
return targets, message
1403
def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None):
1407
This is mainly a shim layer
1411
return cwd.glob(pathname, ondisk, source, strings)
1413
class DirNodeInfo(SCons.Node.NodeInfoBase):
1414
# This should get reset by the FS initialization.
1415
current_version_id = 1
1419
def str_to_node(self, s):
1423
drive, s = _my_splitdrive(s)
1425
root = self.fs.get_root(drive)
1426
if not os.path.isabs(s):
1427
s = top.labspath + '/' + s
1428
return root._lookup_abs(s, Entry)
1430
class DirBuildInfo(SCons.Node.BuildInfoBase):
1431
current_version_id = 1
1433
glob_magic_check = re.compile('[*?[]')
1435
def has_glob_magic(s):
1436
return glob_magic_check.search(s) is not None
1439
"""A class for directories in a file system.
1442
memoizer_counters = []
1444
NodeInfo = DirNodeInfo
1445
BuildInfo = DirBuildInfo
1447
def __init__(self, name, directory, fs):
1448
if __debug__: logInstanceCreation(self, 'Node.FS.Dir')
1449
Base.__init__(self, name, directory, fs)
1453
"""Turn a file system Node (either a freshly initialized directory
1454
object or a separate Entry object) into a proper directory object.
1456
Set up this directory's entries and hook it into the file
1457
system tree. Specify that directories (this Node) don't use
1458
signatures for calculating whether they're current.
1461
self.repositories = []
1465
self.entries['.'] = self
1466
self.entries['..'] = self.dir
1469
self._sconsign = None
1470
self.variant_dirs = []
1471
self.root = self.dir.root
1473
# For directories, we make a difference between the directory
1474
# 'name' and the directory 'dirname'. The 'name' attribute is
1475
# used when we need to print the 'name' of the directory or
1476
# when we it is used as the last part of a path. The 'dirname'
1477
# is used when the directory is not the last element of the
1478
# path. The main reason for making that distinction is that
1479
# for RoorDir's the dirname can not be easily inferred from
1480
# the name. For example, we have to add a '/' after a drive
1481
# letter but not after a UNC path prefix ('//').
1482
self.dirname = self.name + OS_SEP
1484
# Don't just reset the executor, replace its action list,
1485
# because it might have some pre-or post-actions that need to
1488
# But don't reset the executor if there is a non-null executor
1489
# attached already. The existing executor might have other
1490
# targets, in which case replacing the action list with a
1491
# Mkdir action is a big mistake.
1492
if not hasattr(self, 'executor'):
1493
self.builder = get_MkdirBuilder()
1494
self.get_executor().set_action_list(self.builder.action)
1496
# Prepend MkdirBuilder action to existing action list
1497
l = self.get_executor().action_list
1498
a = get_MkdirBuilder().action
1500
self.get_executor().set_action_list(l)
1502
def diskcheck_match(self):
1503
diskcheck_match(self, self.isfile,
1504
"File %s found where directory expected.")
1506
def __clearRepositoryCache(self, duplicate=None):
1507
"""Called when we change the repository(ies) for a directory.
1508
This clears any cached information that is invalidated by changing
1511
for node in self.entries.values():
1512
if node != self.dir:
1513
if node != self and isinstance(node, Dir):
1514
node.__clearRepositoryCache(duplicate)
1519
except AttributeError:
1521
if duplicate is not None:
1522
node.duplicate=duplicate
1524
def __resetDuplicate(self, node):
1526
node.duplicate = node.get_dir().duplicate
1528
def Entry(self, name):
1530
Looks up or creates an entry node named 'name' relative to
1533
return self.fs.Entry(name, self)
1535
def Dir(self, name, create=True):
1537
Looks up or creates a directory node named 'name' relative to
1540
return self.fs.Dir(name, self, create)
1542
def File(self, name):
1544
Looks up or creates a file node named 'name' relative to
1547
return self.fs.File(name, self)
1549
def link(self, srcdir, duplicate):
1550
"""Set this directory as the variant directory for the
1551
supplied source directory."""
1552
self.srcdir = srcdir
1553
self.duplicate = duplicate
1554
self.__clearRepositoryCache(duplicate)
1555
srcdir.variant_dirs.append(self)
1557
def getRepositories(self):
1558
"""Returns a list of repositories for this directory.
1560
if self.srcdir and not self.duplicate:
1561
return self.srcdir.get_all_rdirs() + self.repositories
1562
return self.repositories
1564
memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1566
def get_all_rdirs(self):
1568
return list(self._memo['get_all_rdirs'])
1576
for rep in dir.getRepositories():
1577
result.append(rep.Dir(fname))
1581
fname = dir.name + OS_SEP + fname
1584
self._memo['get_all_rdirs'] = list(result)
1588
def addRepository(self, dir):
1589
if dir != self and not dir in self.repositories:
1590
self.repositories.append(dir)
1592
self.__clearRepositoryCache()
1597
def _rel_path_key(self, other):
1600
memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1602
def rel_path(self, other):
1603
"""Return a path to "other" relative to this directory.
1606
# This complicated and expensive method, which constructs relative
1607
# paths between arbitrary Node.FS objects, is no longer used
1608
# by SCons itself. It was introduced to store dependency paths
1609
# in .sconsign files relative to the target, but that ended up
1610
# being significantly inefficient.
1612
# We're continuing to support the method because some SConstruct
1613
# files out there started using it when it was available, and
1614
# we're all about backwards compatibility..
1617
memo_dict = self._memo['rel_path']
1620
self._memo['rel_path'] = memo_dict
1623
return memo_dict[other]
1630
elif not other in self.path_elements:
1632
other_dir = other.get_dir()
1633
except AttributeError:
1636
if other_dir is None:
1639
dir_rel_path = self.rel_path(other_dir)
1640
if dir_rel_path == '.':
1643
result = dir_rel_path + OS_SEP + other.name
1645
i = self.path_elements.index(other) + 1
1647
path_elems = ['..'] * (len(self.path_elements) - i) \
1648
+ [n.name for n in other.path_elements[i:]]
1650
result = OS_SEP.join(path_elems)
1652
memo_dict[other] = result
1656
def get_env_scanner(self, env, kw={}):
1657
import SCons.Defaults
1658
return SCons.Defaults.DirEntryScanner
1660
def get_target_scanner(self):
1661
import SCons.Defaults
1662
return SCons.Defaults.DirEntryScanner
1664
def get_found_includes(self, env, scanner, path):
1665
"""Return this directory's implicit dependencies.
1667
We don't bother caching the results because the scan typically
1668
shouldn't be requested more than once (as opposed to scanning
1669
.h file contents, which can be requested as many times as the
1670
files is #included by other files).
1674
# Clear cached info for this Dir. If we already visited this
1675
# directory on our walk down the tree (because we didn't know at
1676
# that point it was being used as the source for another Node)
1677
# then we may have calculated build signature before realizing
1678
# we had to scan the disk. Now that we have to, though, we need
1679
# to invalidate the old calculated signature so that any node
1680
# dependent on our directory structure gets one that includes
1681
# info about everything on disk.
1683
return scanner(self, env, path)
1686
# Taskmaster interface subsystem
1692
def build(self, **kw):
1693
"""A null "builder" for directories."""
1695
if self.builder is not MkdirBuilder:
1696
SCons.Node.Node.build(self, **kw)
1703
"""Create this directory, silently and without worrying about
1704
whether the builder is the default or not."""
1710
listDirs.append(parent)
1713
# Don't use while: - else: for this condition because
1714
# if so, then parent is None and has no .path attribute.
1715
raise SCons.Errors.StopError(parent.path)
1718
for dirnode in listDirs:
1720
# Don't call dirnode.build(), call the base Node method
1721
# directly because we definitely *must* create this
1722
# directory. The dirnode.build() method will suppress
1723
# the build if it's the default builder.
1724
SCons.Node.Node.build(dirnode)
1725
dirnode.get_executor().nullify()
1726
# The build() action may or may not have actually
1727
# created the directory, depending on whether the -n
1728
# option was used or not. Delete the _exists and
1729
# _rexists attributes so they can be reevaluated.
1734
def multiple_side_effect_has_builder(self):
1736
return self.builder is not MkdirBuilder and self.has_builder()
1738
def alter_targets(self):
1739
"""Return any corresponding targets in a variant directory.
1741
return self.fs.variant_dir_target_climb(self, self, [])
1743
def scanner_key(self):
1744
"""A directory does not get scanned."""
1747
def get_text_contents(self):
1748
"""We already emit things in text, so just return the binary
1750
return self.get_contents()
1752
def get_contents(self):
1753
"""Return content signatures and names of all our children
1754
separated by new-lines. Ensure that the nodes are sorted."""
1756
for node in sorted(self.children(), key=lambda t: t.name):
1757
contents.append('%s %s\n' % (node.get_csig(), node.name))
1758
return ''.join(contents)
1761
"""Compute the content signature for Directory nodes. In
1762
general, this is not needed and the content signature is not
1763
stored in the DirNodeInfo. However, if get_contents on a Dir
1764
node is called which has a child directory, the child
1765
directory should return the hash of its contents."""
1766
contents = self.get_contents()
1767
return SCons.Util.MD5signature(contents)
1769
def do_duplicate(self, src):
1772
changed_since_last_build = SCons.Node.Node.state_has_changed
1774
def is_up_to_date(self):
1775
"""If any child is not up-to-date, then this directory isn't,
1777
if self.builder is not MkdirBuilder and not self.exists():
1779
up_to_date = SCons.Node.up_to_date
1780
for kid in self.children():
1781
if kid.get_state() > up_to_date:
1786
if not self.exists():
1787
norm_name = _my_normcase(self.name)
1788
for dir in self.dir.get_all_rdirs():
1789
try: node = dir.entries[norm_name]
1790
except KeyError: node = dir.dir_on_disk(self.name)
1791
if node and node.exists() and \
1792
(isinstance(dir, Dir) or isinstance(dir, Entry)):
1797
"""Return the .sconsign file info for this directory,
1798
creating it first if necessary."""
1799
if not self._sconsign:
1800
import SCons.SConsign
1801
self._sconsign = SCons.SConsign.ForDirectory(self)
1802
return self._sconsign
1805
"""Dir has a special need for srcnode()...if we
1806
have a srcdir attribute set, then that *is* our srcnode."""
1809
return Base.srcnode(self)
1811
def get_timestamp(self):
1812
"""Return the latest timestamp from among our children"""
1814
for kid in self.children():
1815
if kid.get_timestamp() > stamp:
1816
stamp = kid.get_timestamp()
1819
def entry_abspath(self, name):
1820
return self.abspath + OS_SEP + name
1822
def entry_labspath(self, name):
1823
return self.labspath + '/' + name
1825
def entry_path(self, name):
1826
return self.path + OS_SEP + name
1828
def entry_tpath(self, name):
1829
return self.tpath + OS_SEP + name
1831
def entry_exists_on_disk(self, name):
1833
d = self.on_disk_entries
1834
except AttributeError:
1837
entries = os.listdir(self.abspath)
1841
for entry in map(_my_normcase, entries):
1843
self.on_disk_entries = d
1844
if sys.platform == 'win32':
1845
name = _my_normcase(name)
1846
result = d.get(name)
1848
# Belt-and-suspenders for Windows: check directly for
1849
# 8.3 file names that don't show up in os.listdir().
1850
result = os.path.exists(self.abspath + OS_SEP + name)
1856
memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1858
def srcdir_list(self):
1860
return self._memo['srcdir_list']
1870
result.append(dir.srcdir.Dir(dirname))
1871
dirname = dir.name + OS_SEP + dirname
1874
self._memo['srcdir_list'] = result
1878
def srcdir_duplicate(self, name):
1879
for dir in self.srcdir_list():
1880
if self.is_under(dir):
1881
# We shouldn't source from something in the build path;
1882
# variant_dir is probably under src_dir, in which case
1883
# we are reflecting.
1885
if dir.entry_exists_on_disk(name):
1886
srcnode = dir.Entry(name).disambiguate()
1888
node = self.Entry(name).disambiguate()
1889
node.do_duplicate(srcnode)
1895
def _srcdir_find_file_key(self, filename):
1898
memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1900
def srcdir_find_file(self, filename):
1902
memo_dict = self._memo['srcdir_find_file']
1905
self._memo['srcdir_find_file'] = memo_dict
1908
return memo_dict[filename]
1913
if (isinstance(node, File) or isinstance(node, Entry)) and \
1914
(node.is_derived() or node.exists()):
1918
norm_name = _my_normcase(filename)
1920
for rdir in self.get_all_rdirs():
1921
try: node = rdir.entries[norm_name]
1922
except KeyError: node = rdir.file_on_disk(filename)
1923
else: node = func(node)
1925
result = (node, self)
1926
memo_dict[filename] = result
1929
for srcdir in self.srcdir_list():
1930
for rdir in srcdir.get_all_rdirs():
1931
try: node = rdir.entries[norm_name]
1932
except KeyError: node = rdir.file_on_disk(filename)
1933
else: node = func(node)
1935
result = (File(filename, self, self.fs), srcdir)
1936
memo_dict[filename] = result
1939
result = (None, None)
1940
memo_dict[filename] = result
1943
def dir_on_disk(self, name):
1944
if self.entry_exists_on_disk(name):
1945
try: return self.Dir(name)
1946
except TypeError: pass
1947
node = self.srcdir_duplicate(name)
1948
if isinstance(node, File):
1952
def file_on_disk(self, name):
1953
if self.entry_exists_on_disk(name) or \
1954
diskcheck_rcs(self, name) or \
1955
diskcheck_sccs(self, name):
1956
try: return self.File(name)
1957
except TypeError: pass
1958
node = self.srcdir_duplicate(name)
1959
if isinstance(node, Dir):
1963
def walk(self, func, arg):
1965
Walk this directory tree by calling the specified function
1966
for each directory in the tree.
1968
This behaves like the os.path.walk() function, but for in-memory
1969
Node.FS.Dir objects. The function takes the same arguments as
1970
the functions passed to os.path.walk():
1972
func(arg, dirname, fnames)
1974
Except that "dirname" will actually be the directory *Node*,
1975
not the string. The '.' and '..' entries are excluded from
1976
fnames. The fnames list may be modified in-place to filter the
1977
subdirectories visited or otherwise impose a specific order.
1978
The "arg" argument is always passed to func() and may be used
1979
in any way (or ignored, passing None is common).
1981
entries = self.entries
1982
names = list(entries.keys())
1985
func(arg, self, names)
1986
for dirname in [n for n in names if isinstance(entries[n], Dir)]:
1987
entries[dirname].walk(func, arg)
1989
def glob(self, pathname, ondisk=True, source=False, strings=False):
1991
Returns a list of Nodes (or strings) matching a specified
1994
Pathname patterns follow UNIX shell semantics: * matches
1995
any-length strings of any characters, ? matches any character,
1996
and [] can enclose lists or ranges of characters. Matches do
1997
not span directory separators.
1999
The matches take into account Repositories, returning local
2000
Nodes if a corresponding entry exists in a Repository (either
2001
an in-memory Node or something on disk).
2003
By defafult, the glob() function matches entries that exist
2004
on-disk, in addition to in-memory Nodes. Setting the "ondisk"
2005
argument to False (or some other non-true value) causes the glob()
2006
function to only match in-memory Nodes. The default behavior is
2007
to return both the on-disk and in-memory Nodes.
2009
The "source" argument, when true, specifies that corresponding
2010
source Nodes must be returned if you're globbing in a build
2011
directory (initialized with VariantDir()). The default behavior
2012
is to return Nodes local to the VariantDir().
2014
The "strings" argument, when true, returns the matches as strings,
2015
not Nodes. The strings are path names relative to this directory.
2017
The underlying algorithm is adapted from the glob.glob() function
2018
in the Python library (but heavily modified), and uses fnmatch()
2021
dirname, basename = os.path.split(pathname)
2023
return sorted(self._glob1(basename, ondisk, source, strings),
2024
key=lambda t: str(t))
2025
if has_glob_magic(dirname):
2026
list = self.glob(dirname, ondisk, source, strings=False)
2028
list = [self.Dir(dirname, create=True)]
2031
r = dir._glob1(basename, ondisk, source, strings)
2033
r = [os.path.join(str(dir), x) for x in r]
2035
return sorted(result, key=lambda a: str(a))
2037
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
2039
Globs for and returns a list of entry names matching a single
2040
pattern in this directory.
2042
This searches any repositories and source directories for
2043
corresponding entries and returns a Node (or string) relative
2044
to the current directory if an entry is found anywhere.
2046
TODO: handle pattern with no wildcard
2048
search_dir_list = self.get_all_rdirs()
2049
for srcdir in self.srcdir_list():
2050
search_dir_list.extend(srcdir.get_all_rdirs())
2052
selfEntry = self.Entry
2054
for dir in search_dir_list:
2055
# We use the .name attribute from the Node because the keys of
2056
# the dir.entries dictionary are normalized (that is, all upper
2057
# case) on case-insensitive systems like Windows.
2058
node_names = [ v.name for k, v in dir.entries.items()
2059
if k not in ('.', '..') ]
2060
names.extend(node_names)
2062
# Make sure the working directory (self) actually has
2063
# entries for all Nodes in repositories or variant dirs.
2064
for name in node_names: selfEntry(name)
2067
disk_names = os.listdir(dir.abspath)
2070
names.extend(disk_names)
2072
# We're going to return corresponding Nodes in
2073
# the local directory, so we need to make sure
2074
# those Nodes exist. We only want to create
2075
# Nodes for the entries that will match the
2076
# specified pattern, though, which means we
2077
# need to filter the list here, even though
2078
# the overall list will also be filtered later,
2079
# after we exit this loop.
2080
if pattern[0] != '.':
2081
#disk_names = [ d for d in disk_names if d[0] != '.' ]
2082
disk_names = [x for x in disk_names if x[0] != '.']
2083
disk_names = fnmatch.filter(disk_names, pattern)
2084
dirEntry = dir.Entry
2085
for name in disk_names:
2086
# Add './' before disk filename so that '#' at
2087
# beginning of filename isn't interpreted.
2089
node = dirEntry(name).disambiguate()
2091
if n.__class__ != node.__class__:
2092
n.__class__ = node.__class__
2096
if pattern[0] != '.':
2097
#names = [ n for n in names if n[0] != '.' ]
2098
names = [x for x in names if x[0] != '.']
2099
names = fnmatch.filter(names, pattern)
2104
#return [ self.entries[_my_normcase(n)] for n in names ]
2105
return [self.entries[_my_normcase(n)] for n in names]
2108
"""A class for the root directory of a file system.
2110
This is the same as a Dir class, except that the path separator
2111
('/' or '\\') is actually part of the name, so we don't need to
2112
add a separator when creating the path names of entries within
2115
def __init__(self, drive, fs):
2116
if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
2117
# We're going to be our own parent directory (".." entry and .dir
2118
# attribute) so we have to set up some values so Base.__init__()
2119
# won't gag won't it calls some of our methods.
2124
self.path_elements = []
2128
# Handle all the types of drives:
2130
# No drive, regular UNIX root or Windows default drive.
2136
dirname = UNC_PREFIX
2138
# Windows drive letter
2140
dirname = drive + OS_SEP
2142
Base.__init__(self, name, self, fs)
2144
# Now set our paths to what we really want them to be. The
2145
# name should already contain any necessary separators, such
2146
# as the initial drive letter (the name) plus the directory
2147
# separator, except for the "lookup abspath," which does not
2148
# have the drive letter.
2149
self.abspath = dirname
2152
self.tpath = dirname
2155
# Must be reset after Dir._morph() is invoked...
2156
self.dirname = dirname
2158
self._lookupDict = {}
2160
self._lookupDict[''] = self
2161
self._lookupDict['/'] = self
2163
# The // entry is necessary because os.path.normpath()
2164
# preserves double slashes at the beginning of a path on Posix
2167
self._lookupDict['//'] = self
2169
def must_be_same(self, klass):
2172
Base.must_be_same(self, klass)
2174
def _lookup_abs(self, p, klass, create=1):
2176
Fast (?) lookup of a *normalized* absolute path.
2178
This method is intended for use by internal lookups with
2179
already-normalized path data. For general-purpose lookups,
2180
use the FS.Entry(), FS.Dir() or FS.File() methods.
2182
The caller is responsible for making sure we're passed a
2183
normalized absolute path; we merely let Python's dictionary look
2184
up and return the One True Node.FS object for the path.
2186
If a Node for the specified "p" doesn't already exist, and
2187
"create" is specified, the Node may be created after recursive
2188
invocation to find or create the parent directory or directories.
2192
result = self._lookupDict[k]
2195
msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self))
2196
raise SCons.Errors.UserError(msg)
2197
# There is no Node for this path name, and we're allowed
2199
# (note: would like to use p.rsplit('/',1) here but
2200
# that's not in python 2.3)
2201
# e.g.: dir_name, file_name = p.rsplit('/',1)
2202
last_slash = p.rindex('/')
2203
if (last_slash >= 0):
2204
dir_name = p[:last_slash]
2205
file_name = p[last_slash+1:]
2207
dir_name = p # shouldn't happen, just in case
2210
dir_node = self._lookup_abs(dir_name, Dir)
2211
result = klass(file_name, dir_node, self.fs)
2213
# Double-check on disk (as configured) that the Node we
2214
# created matches whatever is out there in the real world.
2215
result.diskcheck_match()
2217
self._lookupDict[k] = result
2218
dir_node.entries[_my_normcase(file_name)] = result
2219
dir_node.implicit = None
2221
# There is already a Node for this path name. Allow it to
2222
# complain if we were looking for an inappropriate type.
2223
result.must_be_same(klass)
2229
def entry_abspath(self, name):
2230
return self.abspath + name
2232
def entry_labspath(self, name):
2235
def entry_path(self, name):
2236
return self.path + name
2238
def entry_tpath(self, name):
2239
return self.tpath + name
2241
def is_under(self, dir):
2253
def src_builder(self):
2256
class FileNodeInfo(SCons.Node.NodeInfoBase):
2257
current_version_id = 1
2259
field_list = ['csig', 'timestamp', 'size']
2261
# This should get reset by the FS initialization.
2264
def str_to_node(self, s):
2268
drive, s = _my_splitdrive(s)
2270
root = self.fs.get_root(drive)
2271
if not os.path.isabs(s):
2272
s = top.labspath + '/' + s
2273
return root._lookup_abs(s, Entry)
2275
class FileBuildInfo(SCons.Node.BuildInfoBase):
2276
current_version_id = 1
2278
def convert_to_sconsign(self):
2280
Converts this FileBuildInfo object for writing to a .sconsign file
2282
This replaces each Node in our various dependency lists with its
2283
usual string representation: relative to the top-level SConstruct
2284
directory, or an absolute path if it's outside.
2292
except AttributeError:
2295
s = s.replace(OS_SEP, '/')
2297
for attr in ['bsources', 'bdepends', 'bimplicit']:
2299
val = getattr(self, attr)
2300
except AttributeError:
2303
setattr(self, attr, list(map(node_to_str, val)))
2304
def convert_from_sconsign(self, dir, name):
2306
Converts a newly-read FileBuildInfo object for in-SCons use
2308
For normal up-to-date checking, we don't have any conversion to
2309
perform--but we're leaving this method here to make that clear.
2312
def prepare_dependencies(self):
2314
Prepares a FileBuildInfo object for explaining what changed
2316
The bsources, bdepends and bimplicit lists have all been
2317
stored on disk as paths relative to the top-level SConstruct
2318
directory. Convert the strings to actual Nodes (for use by the
2319
--debug=explain code and --implicit-cache).
2322
('bsources', 'bsourcesigs'),
2323
('bdepends', 'bdependsigs'),
2324
('bimplicit', 'bimplicitsigs'),
2326
for (nattr, sattr) in attrs:
2328
strings = getattr(self, nattr)
2329
nodeinfos = getattr(self, sattr)
2330
except AttributeError:
2333
for s, ni in zip(strings, nodeinfos):
2334
if not isinstance(s, SCons.Node.Node):
2335
s = ni.str_to_node(s)
2337
setattr(self, nattr, nodes)
2338
def format(self, names=0):
2340
bkids = self.bsources + self.bdepends + self.bimplicit
2341
bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs
2342
for bkid, bkidsig in zip(bkids, bkidsigs):
2343
result.append(str(bkid) + ': ' +
2344
' '.join(bkidsig.format(names=names)))
2345
result.append('%s [%s]' % (self.bactsig, self.bact))
2346
return '\n'.join(result)
2349
"""A class for files in a file system.
2352
memoizer_counters = []
2354
NodeInfo = FileNodeInfo
2355
BuildInfo = FileBuildInfo
2359
def diskcheck_match(self):
2360
diskcheck_match(self, self.isdir,
2361
"Directory %s found where file expected.")
2363
def __init__(self, name, directory, fs):
2364
if __debug__: logInstanceCreation(self, 'Node.FS.File')
2365
Base.__init__(self, name, directory, fs)
2368
def Entry(self, name):
2369
"""Create an entry node named 'name' relative to
2370
the directory of this file."""
2371
return self.dir.Entry(name)
2373
def Dir(self, name, create=True):
2374
"""Create a directory node named 'name' relative to
2375
the directory of this file."""
2376
return self.dir.Dir(name, create=create)
2378
def Dirs(self, pathlist):
2379
"""Create a list of directories relative to the SConscript
2380
directory of this file."""
2381
return [self.Dir(p) for p in pathlist]
2383
def File(self, name):
2384
"""Create a file node named 'name' relative to
2385
the directory of this file."""
2386
return self.dir.File(name)
2388
#def generate_build_dict(self):
2389
# """Return an appropriate dictionary of values for building
2391
# return {'Dir' : self.Dir,
2392
# 'File' : self.File,
2393
# 'RDirs' : self.RDirs}
2396
"""Turn a file system node into a File object."""
2397
self.scanner_paths = {}
2398
if not hasattr(self, '_local'):
2401
# If there was already a Builder set on this entry, then
2402
# we need to make sure we call the target-decider function,
2403
# not the source-decider. Reaching in and doing this by hand
2404
# is a little bogus. We'd prefer to handle this by adding
2405
# an Entry.builder_set() method that disambiguates like the
2406
# other methods, but that starts running into problems with the
2407
# fragile way we initialize Dir Nodes with their Mkdir builders,
2408
# yet still allow them to be overridden by the user. Since it's
2409
# not clear right now how to fix that, stick with what works
2410
# until it becomes clear...
2411
if self.has_builder():
2412
self.changed_since_last_build = self.decide_target
2414
def scanner_key(self):
2415
return self.get_suffix()
2417
def get_contents(self):
2418
if not self.rexists():
2420
fname = self.rfile().abspath
2422
contents = open(fname, "rb").read()
2423
except EnvironmentError, e:
2429
# This attempts to figure out what the encoding of the text is
2430
# based upon the BOM bytes, and then decodes the contents so that
2431
# it's a valid python string.
2432
def get_text_contents(self):
2433
contents = self.get_contents()
2434
# The behavior of various decode() methods and functions
2435
# w.r.t. the initial BOM bytes is different for different
2436
# encodings and/or Python versions. ('utf-8' does not strip
2437
# them, but has a 'utf-8-sig' which does; 'utf-16' seems to
2438
# strip them; etc.) Just sidestep all the complication by
2439
# explicitly stripping the BOM before we decode().
2440
if contents.startswith(codecs.BOM_UTF8):
2441
return contents[len(codecs.BOM_UTF8):].decode('utf-8')
2442
if contents.startswith(codecs.BOM_UTF16_LE):
2443
return contents[len(codecs.BOM_UTF16_LE):].decode('utf-16-le')
2444
if contents.startswith(codecs.BOM_UTF16_BE):
2445
return contents[len(codecs.BOM_UTF16_BE):].decode('utf-16-be')
2448
def get_content_hash(self):
2450
Compute and return the MD5 hash for this file.
2452
if not self.rexists():
2453
return SCons.Util.MD5signature('')
2454
fname = self.rfile().abspath
2456
cs = SCons.Util.MD5filesignature(fname,
2457
chunksize=SCons.Node.FS.File.md5_chunksize*1024)
2458
except EnvironmentError, e:
2465
memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2469
return self._memo['get_size']
2474
size = self.rfile().getsize()
2478
self._memo['get_size'] = size
2482
memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2484
def get_timestamp(self):
2486
return self._memo['get_timestamp']
2491
timestamp = self.rfile().getmtime()
2495
self._memo['get_timestamp'] = timestamp
2499
def store_info(self):
2500
# Merge our build information into the already-stored entry.
2501
# This accomodates "chained builds" where a file that's a target
2502
# in one build (SConstruct file) is a source in a different build.
2503
# See test/chained-build.py for the use case.
2505
self.dir.sconsign().store_info(self.name, self)
2507
convert_copy_attrs = [
2517
convert_sig_attrs = [
2523
def convert_old_entry(self, old_entry):
2524
# Convert a .sconsign entry from before the Big Signature
2525
# Refactoring, doing what we can to convert its information
2526
# to the new .sconsign entry format.
2528
# The old format looked essentially like this:
2537
# .bsourcesigs ("signature" list)
2539
# .bdependsigs ("signature" list)
2541
# .bimplicitsigs ("signature" list)
2545
# The new format looks like this:
2552
# .binfo (BuildInfo)
2554
# .bsourcesigs (NodeInfo list)
2560
# .bdependsigs (NodeInfo list)
2566
# .bimplicitsigs (NodeInfo list)
2574
# The basic idea of the new structure is that a NodeInfo always
2575
# holds all available information about the state of a given Node
2576
# at a certain point in time. The various .b*sigs lists can just
2577
# be a list of pointers to the .ninfo attributes of the different
2578
# dependent nodes, without any copying of information until it's
2579
# time to pickle it for writing out to a .sconsign file.
2581
# The complicating issue is that the *old* format only stored one
2582
# "signature" per dependency, based on however the *last* build
2583
# was configured. We don't know from just looking at it whether
2584
# it was a build signature, a content signature, or a timestamp
2585
# "signature". Since we no longer use build signatures, the
2586
# best we can do is look at the length and if it's thirty two,
2587
# assume that it was (or might have been) a content signature.
2588
# If it was actually a build signature, then it will cause a
2589
# rebuild anyway when it doesn't match the new content signature,
2590
# but that's probably the best we can do.
2591
import SCons.SConsign
2592
new_entry = SCons.SConsign.SConsignEntry()
2593
new_entry.binfo = self.new_binfo()
2594
binfo = new_entry.binfo
2595
for attr in self.convert_copy_attrs:
2597
value = getattr(old_entry, attr)
2598
except AttributeError:
2600
setattr(binfo, attr, value)
2601
delattr(old_entry, attr)
2602
for attr in self.convert_sig_attrs:
2604
sig_list = getattr(old_entry, attr)
2605
except AttributeError:
2608
for sig in sig_list:
2609
ninfo = self.new_ninfo()
2613
ninfo.timestamp = sig
2615
setattr(binfo, attr, value)
2616
delattr(old_entry, attr)
2619
memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2621
def get_stored_info(self):
2623
return self._memo['get_stored_info']
2628
sconsign_entry = self.dir.sconsign().get_entry(self.name)
2629
except (KeyError, EnvironmentError):
2630
import SCons.SConsign
2631
sconsign_entry = SCons.SConsign.SConsignEntry()
2632
sconsign_entry.binfo = self.new_binfo()
2633
sconsign_entry.ninfo = self.new_ninfo()
2635
if isinstance(sconsign_entry, FileBuildInfo):
2636
# This is a .sconsign file from before the Big Signature
2637
# Refactoring; convert it as best we can.
2638
sconsign_entry = self.convert_old_entry(sconsign_entry)
2640
delattr(sconsign_entry.ninfo, 'bsig')
2641
except AttributeError:
2644
self._memo['get_stored_info'] = sconsign_entry
2646
return sconsign_entry
2648
def get_stored_implicit(self):
2649
binfo = self.get_stored_info().binfo
2650
binfo.prepare_dependencies()
2651
try: return binfo.bimplicit
2652
except AttributeError: return None
2654
def rel_path(self, other):
2655
return self.dir.rel_path(other)
2657
def _get_found_includes_key(self, env, scanner, path):
2658
return (id(env), id(scanner), path)
2660
memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2662
def get_found_includes(self, env, scanner, path):
2663
"""Return the included implicit dependencies in this file.
2664
Cache results so we only scan the file once per path
2665
regardless of how many times this information is requested.
2667
memo_key = (id(env), id(scanner), path)
2669
memo_dict = self._memo['get_found_includes']
2672
self._memo['get_found_includes'] = memo_dict
2675
return memo_dict[memo_key]
2680
# result = [n.disambiguate() for n in scanner(self, env, path)]
2681
result = scanner(self, env, path)
2682
result = [N.disambiguate() for N in result]
2686
memo_dict[memo_key] = result
2690
def _createDir(self):
2691
# ensure that the directories for this node are
2695
def push_to_cache(self):
2696
"""Try to push the node into a cache
2698
# This should get called before the Nodes' .built() method is
2699
# called, which would clear the build signature if the file has
2702
# We have to clear the local memoized values *before* we push
2703
# the node to cache so that the memoization of the self.exists()
2704
# return value doesn't interfere.
2707
self.clear_memoized_values()
2709
self.get_build_env().get_CacheDir().push(self)
2711
def retrieve_from_cache(self):
2712
"""Try to retrieve the node's content from a cache
2714
This method is called from multiple threads in a parallel build,
2715
so only do thread safe stuff here. Do thread unsafe stuff in
2718
Returns true if the node was successfully retrieved.
2722
if not self.is_derived():
2724
return self.get_build_env().get_CacheDir().retrieve(self)
2728
self.get_build_env().get_CacheDir().push_if_forced(self)
2730
ninfo = self.get_ninfo()
2732
csig = self.get_max_drift_csig()
2736
ninfo.timestamp = self.get_timestamp()
2737
ninfo.size = self.get_size()
2739
if not self.has_builder():
2740
# This is a source file, but it might have been a target file
2741
# in another build that included more of the DAG. Copy
2742
# any build information that's stored in the .sconsign file
2743
# into our binfo object so it doesn't get lost.
2744
old = self.get_stored_info()
2745
self.get_binfo().__dict__.update(old.binfo.__dict__)
2749
def find_src_builder(self):
2752
scb = self.dir.src_builder()
2754
if diskcheck_sccs(self.dir, self.name):
2755
scb = get_DefaultSCCSBuilder()
2756
elif diskcheck_rcs(self.dir, self.name):
2757
scb = get_DefaultRCSBuilder()
2763
except AttributeError:
2766
self.builder_set(scb)
2769
def has_src_builder(self):
2770
"""Return whether this Node has a source builder or not.
2772
If this Node doesn't have an explicit source code builder, this
2773
is where we figure out, on the fly, if there's a transparent
2774
source code builder for it.
2776
Note that if we found a source builder, we also set the
2777
self.builder attribute, so that all of the methods that actually
2778
*build* this file don't have to do anything different.
2782
except AttributeError:
2783
scb = self.sbuilder = self.find_src_builder()
2784
return scb is not None
2786
def alter_targets(self):
2787
"""Return any corresponding targets in a variant directory.
2789
if self.is_derived():
2791
return self.fs.variant_dir_target_climb(self, self.dir, [self.name])
2793
def _rmv_existing(self):
2794
self.clear_memoized_values()
2796
print "dup: removing existing target %s"%self
2797
e = Unlink(self, [], None)
2798
if isinstance(e, SCons.Errors.BuildError):
2802
# Taskmaster interface subsystem
2805
def make_ready(self):
2806
self.has_src_builder()
2810
"""Prepare for this file to be created."""
2811
SCons.Node.Node.prepare(self)
2813
if self.get_state() != SCons.Node.up_to_date:
2815
if self.is_derived() and not self.precious:
2816
self._rmv_existing()
2820
except SCons.Errors.StopError, drive:
2821
desc = "No drive `%s' for target `%s'." % (drive, self)
2822
raise SCons.Errors.StopError(desc)
2829
"""Remove this file."""
2830
if self.exists() or self.islink():
2831
self.fs.unlink(self.path)
2835
def do_duplicate(self, src):
2838
print "dup: relinking variant '%s' from '%s'"%(self, src)
2839
Unlink(self, None, None)
2840
e = Link(self, src, None)
2841
if isinstance(e, SCons.Errors.BuildError):
2842
desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
2843
raise SCons.Errors.StopError(desc)
2845
# The Link() action may or may not have actually
2846
# created the file, depending on whether the -n
2847
# option was used or not. Delete the _exists and
2848
# _rexists attributes so they can be reevaluated.
2851
memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2855
return self._memo['exists']
2858
# Duplicate from source path if we are set up to do this.
2859
if self.duplicate and not self.is_derived() and not self.linked:
2860
src = self.srcnode()
2862
# At this point, src is meant to be copied in a variant directory.
2864
if src.abspath != self.abspath:
2866
self.do_duplicate(src)
2867
# Can't return 1 here because the duplication might
2868
# not actually occur if the -n option is being used.
2870
# The source file does not exist. Make sure no old
2871
# copy remains in the variant directory.
2873
print "dup: no src for %s, unlinking old variant copy"%self
2874
if Base.exists(self) or self.islink():
2875
self.fs.unlink(self.path)
2876
# Return None explicitly because the Base.exists() call
2877
# above will have cached its value if the file existed.
2878
self._memo['exists'] = None
2880
result = Base.exists(self)
2881
self._memo['exists'] = result
2885
# SIGNATURE SUBSYSTEM
2888
def get_max_drift_csig(self):
2890
Returns the content signature currently stored for this node
2891
if it's been unmodified longer than the max_drift value, or the
2892
max_drift value is 0. Returns None otherwise.
2894
old = self.get_stored_info()
2895
mtime = self.get_timestamp()
2897
max_drift = self.fs.max_drift
2899
if (time.time() - mtime) > max_drift:
2902
if n.timestamp and n.csig and n.timestamp == mtime:
2904
except AttributeError:
2906
elif max_drift == 0:
2908
return old.ninfo.csig
2909
except AttributeError:
2916
Generate a node's content signature, the digested signature
2920
cache - alternate node to use for the signature cache
2921
returns - the content signature
2923
ninfo = self.get_ninfo()
2926
except AttributeError:
2929
csig = self.get_max_drift_csig()
2933
if self.get_size() < SCons.Node.FS.File.md5_chunksize:
2934
contents = self.get_contents()
2936
csig = self.get_content_hash()
2938
# This can happen if there's actually a directory on-disk,
2939
# which can be the case if they've disabled disk checks,
2940
# or if an action with a File target actually happens to
2941
# create a same-named directory by mistake.
2945
csig = SCons.Util.MD5signature(contents)
2952
# DECISION SUBSYSTEM
2955
def builder_set(self, builder):
2956
SCons.Node.Node.builder_set(self, builder)
2957
self.changed_since_last_build = self.decide_target
2959
def changed_content(self, target, prev_ni):
2960
cur_csig = self.get_csig()
2962
return cur_csig != prev_ni.csig
2963
except AttributeError:
2966
def changed_state(self, target, prev_ni):
2967
return self.state != SCons.Node.up_to_date
2969
def changed_timestamp_then_content(self, target, prev_ni):
2970
if not self.changed_timestamp_match(target, prev_ni):
2972
self.get_ninfo().csig = prev_ni.csig
2973
except AttributeError:
2976
return self.changed_content(target, prev_ni)
2978
def changed_timestamp_newer(self, target, prev_ni):
2980
return self.get_timestamp() > target.get_timestamp()
2981
except AttributeError:
2984
def changed_timestamp_match(self, target, prev_ni):
2986
return self.get_timestamp() != prev_ni.timestamp
2987
except AttributeError:
2990
def decide_source(self, target, prev_ni):
2991
return target.get_build_env().decide_source(self, target, prev_ni)
2993
def decide_target(self, target, prev_ni):
2994
return target.get_build_env().decide_target(self, target, prev_ni)
2996
# Initialize this Node's decider function to decide_source() because
2997
# every file is a source file until it has a Builder attached...
2998
changed_since_last_build = decide_source
3000
def is_up_to_date(self):
3002
if T: Trace('is_up_to_date(%s):' % self)
3003
if not self.exists():
3004
if T: Trace(' not self.exists():')
3005
# The file doesn't exist locally...
3008
# ...but there is one in a Repository...
3009
if not self.changed(r):
3010
if T: Trace(' changed(%s):' % r)
3011
# ...and it's even up-to-date...
3013
# ...and they'd like a local copy.
3014
e = LocalCopy(self, r, None)
3015
if isinstance(e, SCons.Errors.BuildError):
3021
if T: Trace(' None\n')
3025
if T: Trace(' self.exists(): %s\n' % r)
3028
memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
3032
return self._memo['rfile']
3036
if not self.exists():
3037
norm_name = _my_normcase(self.name)
3038
for dir in self.dir.get_all_rdirs():
3039
try: node = dir.entries[norm_name]
3040
except KeyError: node = dir.file_on_disk(self.name)
3041
if node and node.exists() and \
3042
(isinstance(node, File) or isinstance(node, Entry) \
3043
or not node.is_derived()):
3045
# Copy over our local attributes to the repository
3046
# Node so we identify shared object files in the
3047
# repository and don't assume they're static.
3049
# This isn't perfect; the attribute would ideally
3050
# be attached to the object in the repository in
3051
# case it was built statically in the repository
3052
# and we changed it to shared locally, but that's
3053
# rarely the case and would only occur if you
3054
# intentionally used the same suffix for both
3055
# shared and static objects anyway. So this
3056
# should work well in practice.
3057
result.attributes = self.attributes
3059
self._memo['rfile'] = result
3063
return str(self.rfile())
3065
def get_cachedir_csig(self):
3067
Fetch a Node's content signature for purposes of computing
3068
another Node's cachesig.
3070
This is a wrapper around the normal get_csig() method that handles
3071
the somewhat obscure case of using CacheDir with the -n option.
3072
Any files that don't exist would normally be "built" by fetching
3073
them from the cache, but the normal get_csig() method will try
3074
to open up the local file, which doesn't exist because the -n
3075
option meant we didn't actually pull the file from cachedir.
3076
But since the file *does* actually exist in the cachedir, we
3077
can use its contents for the csig.
3080
return self.cachedir_csig
3081
except AttributeError:
3084
cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
3085
if not self.exists() and cachefile and os.path.exists(cachefile):
3086
self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
3087
SCons.Node.FS.File.md5_chunksize * 1024)
3089
self.cachedir_csig = self.get_csig()
3090
return self.cachedir_csig
3092
def get_cachedir_bsig(self):
3094
return self.cachesig
3095
except AttributeError:
3098
# Add the path to the cache signature, because multiple
3099
# targets built by the same action will all have the same
3100
# build signature, and we have to differentiate them somehow.
3101
children = self.children()
3102
executor = self.get_executor()
3103
# sigs = [n.get_cachedir_csig() for n in children]
3104
sigs = [n.get_cachedir_csig() for n in children]
3105
sigs.append(SCons.Util.MD5signature(executor.get_contents()))
3106
sigs.append(self.path)
3107
result = self.cachesig = SCons.Util.MD5collect(sigs)
3113
def get_default_fs():
3119
class FileFinder(object):
3122
if SCons.Memoize.use_memoizer:
3123
__metaclass__ = SCons.Memoize.Memoized_Metaclass
3125
memoizer_counters = []
3130
def filedir_lookup(self, p, fd=None):
3132
A helper method for find_file() that looks up a directory for
3133
a file we're trying to find. This only creates the Dir Node if
3134
it exists on-disk, since if the directory doesn't exist we know
3135
we won't find any files in it... :-)
3137
It would be more compact to just use this as a nested function
3138
with a default keyword argument (see the commented-out version
3139
below), but that doesn't work unless you have nested scopes,
3140
so we define it here just so this work under Python 1.5.2.
3143
fd = self.default_filedir
3144
dir, name = os.path.split(fd)
3145
drive, d = _my_splitdrive(dir)
3146
if not name and d[:1] in ('/', OS_SEP):
3147
#return p.fs.get_root(drive).dir_on_disk(name)
3148
return p.fs.get_root(drive)
3150
p = self.filedir_lookup(p, dir)
3153
norm_name = _my_normcase(name)
3155
node = p.entries[norm_name]
3157
return p.dir_on_disk(name)
3158
if isinstance(node, Dir):
3160
if isinstance(node, Entry):
3161
node.must_be_same(Dir)
3165
def _find_file_key(self, filename, paths, verbose=None):
3166
return (filename, paths)
3168
memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
3170
def find_file(self, filename, paths, verbose=None):
3172
find_file(str, [Dir()]) -> [nodes]
3174
filename - a filename to find
3175
paths - a list of directory path *nodes* to search in. Can be
3176
represented as a list, a tuple, or a callable that is
3177
called with no arguments and returns the list or tuple.
3179
returns - the node created from the found file.
3181
Find a node corresponding to either a derived file or a file
3182
that exists already.
3184
Only the first file found is returned, and none is returned
3185
if no file is found.
3187
memo_key = self._find_file_key(filename, paths)
3189
memo_dict = self._memo['find_file']
3192
self._memo['find_file'] = memo_dict
3195
return memo_dict[memo_key]
3199
if verbose and not callable(verbose):
3200
if not SCons.Util.is_String(verbose):
3201
verbose = "find_file"
3202
_verbose = u' %s: ' % verbose
3203
verbose = lambda s: sys.stdout.write(_verbose + s)
3205
filedir, filename = os.path.split(filename)
3207
# More compact code that we can't use until we drop
3208
# support for Python 1.5.2:
3210
#def filedir_lookup(p, fd=filedir):
3212
# A helper function that looks up a directory for a file
3213
# we're trying to find. This only creates the Dir Node
3214
# if it exists on-disk, since if the directory doesn't
3215
# exist we know we won't find any files in it... :-)
3217
# dir, name = os.path.split(fd)
3219
# p = filedir_lookup(p, dir)
3222
# norm_name = _my_normcase(name)
3224
# node = p.entries[norm_name]
3226
# return p.dir_on_disk(name)
3227
# if isinstance(node, Dir):
3229
# if isinstance(node, Entry):
3230
# node.must_be_same(Dir)
3232
# if isinstance(node, Dir) or isinstance(node, Entry):
3235
#paths = [_f for _f in map(filedir_lookup, paths) if _f]
3237
self.default_filedir = filedir
3238
paths = [_f for _f in map(self.filedir_lookup, paths) if _f]
3243
verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
3244
node, d = dir.srcdir_find_file(filename)
3247
verbose("... FOUND '%s' in '%s'\n" % (filename, d))
3251
memo_dict[memo_key] = result
3255
find_file = FileFinder().find_file
3258
def invalidate_node_memos(targets):
3260
Invalidate the memoized values of all Nodes (files or directories)
3261
that are associated with the given entries. Has been added to
3262
clear the cache of nodes affected by a direct execution of an
3263
action (e.g. Delete/Copy/Chmod). Existing Node caches become
3264
inconsistent if the action is run through Execute(). The argument
3265
`targets` can be a single Node object or filename, or a sequence
3268
from traceback import extract_stack
3270
# First check if the cache really needs to be flushed. Only
3271
# actions run in the SConscript with Execute() seem to be
3272
# affected. XXX The way to check if Execute() is in the stacktrace
3273
# is a very dirty hack and should be replaced by a more sensible
3275
for f in extract_stack():
3276
if f[2] == 'Execute' and f[0][-14:] == 'Environment.py':
3279
# Dont have to invalidate, so return
3282
if not SCons.Util.is_List(targets):
3285
for entry in targets:
3286
# If the target is a Node object, clear the cache. If it is a
3287
# filename, look up potentially existing Node object first.
3289
entry.clear_memoized_values()
3290
except AttributeError:
3291
# Not a Node object, try to look up Node by filename. XXX
3292
# This creates Node objects even for those filenames which
3293
# do not correspond to an existing Node object.
3294
node = get_default_fs().Entry(entry)
3296
node.clear_memoized_values()
3300
# indent-tabs-mode:nil
3302
# vim: set expandtab tabstop=4 shiftwidth=4: