1 """scons.Node.FS
2
3 File system nodes.
4
5 These Nodes represent the canonical external objects that people think
6 of when they think of building software: files and directories.
7
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.
10
11 """
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 __revision__ = "src/engine/SCons/Node/FS.py 2928 2008/04/29 22:44:09 knight"
37
38 import fnmatch
39 from itertools import izip
40 import os
41 import os.path
42 import re
43 import shutil
44 import stat
45 import string
46 import sys
47 import time
48 import cStringIO
49
50 import SCons.Action
51 from SCons.Debug import logInstanceCreation
52 import SCons.Errors
53 import SCons.Memoize
54 import SCons.Node
55 import SCons.Node.Alias
56 import SCons.Subst
57 import SCons.Util
58 import SCons.Warnings
59
60 from SCons.Debug import Trace
61
62
63
64 default_max_drift = 2*24*60*60
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 Save_Strings = None
85
89
90
91
92
93
94
95
96
97 do_splitdrive = None
98
103
104 initialize_do_splitdrive()
105
106
107
108 needs_normpath_check = None
109
111 """
112 Initialize the normpath_check regular expression.
113
114 This function is used by the unit tests to re-initialize the pattern
115 when testing for behavior with different values of os.sep.
116 """
117 global needs_normpath_check
118 if os.sep == '/':
119 pattern = r'.*/|\.$|\.\.$'
120 else:
121 pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep)
122 needs_normpath_check = re.compile(pattern)
123
124 initialize_normpath_check()
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 if hasattr(os, 'link'):
153 else:
154 _hardlink_func = None
155
156 if hasattr(os, 'symlink'):
159 else:
160 _softlink_func = None
161
166
167
168 Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy',
169 'hard-copy', 'soft-copy', 'copy']
170
171 Link_Funcs = []
172
195
197
198
199
200
201
202 src = source[0].abspath
203 dest = target[0].abspath
204 dir, file = os.path.split(dest)
205 if dir and not target[0].fs.isdir(dir):
206 os.makedirs(dir)
207 if not Link_Funcs:
208
209 set_duplicate('hard-soft-copy')
210 fs = source[0].fs
211
212 for func in Link_Funcs:
213 try:
214 func(fs, src, dest)
215 break
216 except (IOError, OSError):
217
218
219
220
221
222
223 if func == Link_Funcs[-1]:
224
225 raise
226 else:
227 pass
228 return 0
229
230 Link = SCons.Action.Action(LinkFunc, None)
232 return 'Local copy of %s from %s' % (target[0], source[0])
233
234 LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
235
237 t = target[0]
238 t.fs.unlink(t.abspath)
239 return 0
240
241 Unlink = SCons.Action.Action(UnlinkFunc, None)
242
244 t = target[0]
245 if not t.exists():
246 t.fs.mkdir(t.abspath)
247 return 0
248
249 Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
250
251 MkdirBuilder = None
252
267
270
271 _null = _Null()
272
273 DefaultSCCSBuilder = None
274 DefaultRCSBuilder = None
275
287
299
300
301 _is_cygwin = sys.platform == "cygwin"
302 if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin:
305 else:
308
309
310
313 self.type = type
314 self.do = do
315 self.ignore = ignore
316 self.set_do()
321 - def set(self, list):
326
328 result = predicate()
329 try:
330
331
332
333
334
335
336 if node._memo['stat'] is None:
337 del node._memo['stat']
338 except (AttributeError, KeyError):
339 pass
340 if result:
341 raise TypeError, errorfmt % node.abspath
342
345
347 try:
348 rcs_dir = node.rcs_dir
349 except AttributeError:
350 if node.entry_exists_on_disk('RCS'):
351 rcs_dir = node.Dir('RCS')
352 else:
353 rcs_dir = None
354 node.rcs_dir = rcs_dir
355 if rcs_dir:
356 return rcs_dir.entry_exists_on_disk(name+',v')
357 return None
358
361
363 try:
364 sccs_dir = node.sccs_dir
365 except AttributeError:
366 if node.entry_exists_on_disk('SCCS'):
367 sccs_dir = node.Dir('SCCS')
368 else:
369 sccs_dir = None
370 node.sccs_dir = sccs_dir
371 if sccs_dir:
372 return sccs_dir.entry_exists_on_disk('s.'+name)
373 return None
374
377
378 diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match)
379 diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs)
380 diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs)
381
382 diskcheckers = [
383 diskcheck_match,
384 diskcheck_rcs,
385 diskcheck_sccs,
386 ]
387
391
394
395
396
397 -class EntryProxy(SCons.Util.Proxy):
398 - def __get_abspath(self):
399 entry = self.get()
400 return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
401 entry.name + "_abspath")
402
403 - def __get_filebase(self):
407
408 - def __get_suffix(self):
412
413 - def __get_file(self):
416
417 - def __get_base_path(self):
418 """Return the file's directory and file name, with the
419 suffix stripped."""
420 entry = self.get()
421 return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
422 entry.name + "_base")
423
425 """Return the path with / as the path separator,
426 regardless of platform."""
427 if os.sep == '/':
428 return self
429 else:
430 entry = self.get()
431 r = string.replace(entry.get_path(), os.sep, '/')
432 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
433
435 """Return the path with \ as the path separator,
436 regardless of platform."""
437 if os.sep == '\\':
438 return self
439 else:
440 entry = self.get()
441 r = string.replace(entry.get_path(), os.sep, '\\')
442 return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows")
443
444 - def __get_srcnode(self):
445 return EntryProxy(self.get().srcnode())
446
447 - def __get_srcdir(self):
448 """Returns the directory containing the source node linked to this
449 node via VariantDir(), or the directory of this node if not linked."""
450 return EntryProxy(self.get().srcnode().dir)
451
452 - def __get_rsrcnode(self):
453 return EntryProxy(self.get().srcnode().rfile())
454
455 - def __get_rsrcdir(self):
456 """Returns the directory containing the source node linked to this
457 node via VariantDir(), or the directory of this node if not linked."""
458 return EntryProxy(self.get().srcnode().rfile().dir)
459
460 - def __get_dir(self):
461 return EntryProxy(self.get().dir)
462
463 dictSpecialAttrs = { "base" : __get_base_path,
464 "posix" : __get_posix_path,
465 "windows" : __get_windows_path,
466 "win32" : __get_windows_path,
467 "srcpath" : __get_srcnode,
468 "srcdir" : __get_srcdir,
469 "dir" : __get_dir,
470 "abspath" : __get_abspath,
471 "filebase" : __get_filebase,
472 "suffix" : __get_suffix,
473 "file" : __get_file,
474 "rsrcpath" : __get_rsrcnode,
475 "rsrcdir" : __get_rsrcdir,
476 }
477
478 - def __getattr__(self, name):
479
480
481 try:
482 attr_function = self.dictSpecialAttrs[name]
483 except KeyError:
484 try:
485 attr = SCons.Util.Proxy.__getattr__(self, name)
486 except AttributeError:
487 entry = self.get()
488 classname = string.split(str(entry.__class__), '.')[-1]
489 if classname[-2:] == "'>":
490
491
492
493
494 classname = classname[:-2]
495 raise AttributeError, "%s instance '%s' has no attribute '%s'" % (classname, entry.name, name)
496 return attr
497 else:
498 return attr_function(self)
499
500 -class Base(SCons.Node.Node):
501 """A generic class for file system entries. This class is for
502 when we don't know yet whether the entry being looked up is a file
503 or a directory. Instances of this class can morph into either
504 Dir or File objects by a later, more precise lookup.
505
506 Note: this class does not define __cmp__ and __hash__ for
507 efficiency reasons. SCons does a lot of comparing of
508 Node.FS.{Base,Entry,File,Dir} objects, so those operations must be
509 as fast as possible, which means we want to use Python's built-in
510 object identity comparisons.
511 """
512
513 memoizer_counters = []
514
515 - def __init__(self, name, directory, fs):
516 """Initialize a generic Node.FS.Base object.
517
518 Call the superclass initialization, take care of setting up
519 our relative and absolute paths, identify our parent
520 directory, and indicate that this node should use
521 signatures."""
522 if __debug__: logInstanceCreation(self, 'Node.FS.Base')
523 SCons.Node.Node.__init__(self)
524
525 self.name = name
526 self.suffix = SCons.Util.splitext(name)[1]
527 self.fs = fs
528
529 assert directory, "A directory must be provided"
530
531 self.abspath = directory.entry_abspath(name)
532 self.labspath = directory.entry_labspath(name)
533 if directory.path == '.':
534 self.path = name
535 else:
536 self.path = directory.entry_path(name)
537 if directory.tpath == '.':
538 self.tpath = name
539 else:
540 self.tpath = directory.entry_tpath(name)
541 self.path_elements = directory.path_elements + [self]
542
543 self.dir = directory
544 self.cwd = None
545 self.duplicate = directory.duplicate
546
548 """
549 This node, which already existed, is being looked up as the
550 specified klass. Raise an exception if it isn't.
551 """
552 if self.__class__ is klass or klass is Entry:
553 return
554 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
555 (self.__class__.__name__, self.path, klass.__name__)
556
559
562
565
573
574 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
575
577 try:
578 return self._memo['_save_str']
579 except KeyError:
580 pass
581 result = self._get_str()
582 self._memo['_save_str'] = result
583 return result
584
609
610 rstr = __str__
611
612 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
613
615 try: return self._memo['stat']
616 except KeyError: pass
617 try: result = self.fs.stat(self.abspath)
618 except os.error: result = None
619 self._memo['stat'] = result
620 return result
621
623 return not self.stat() is None
624
627
629 st = self.stat()
630 if st: return st[stat.ST_MTIME]
631 else: return None
632
634 st = self.stat()
635 if st: return st[stat.ST_SIZE]
636 else: return None
637
639 st = self.stat()
640 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
641
643 st = self.stat()
644 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
645
646 if hasattr(os, 'symlink'):
648 try: st = self.fs.lstat(self.abspath)
649 except os.error: return 0
650 return stat.S_ISLNK(st[stat.ST_MODE])
651 else:
654
656 if self is dir:
657 return 1
658 else:
659 return self.dir.is_under(dir)
660
663
675
677 """Return path relative to the current working directory of the
678 Node.FS.Base object that owns us."""
679 if not dir:
680 dir = self.fs.getcwd()
681 if self == dir:
682 return '.'
683 path_elems = self.path_elements
684 try: i = path_elems.index(dir)
685 except ValueError: pass
686 else: path_elems = path_elems[i+1:]
687 path_elems = map(lambda n: n.name, path_elems)
688 return string.join(path_elems, os.sep)
689
691 """Set the source code builder for this node."""
692 self.sbuilder = builder
693 if not self.has_builder():
694 self.builder_set(builder)
695
697 """Fetch the source code builder for this node.
698
699 If there isn't one, we cache the source code builder specified
700 for the directory (which in turn will cache the value from its
701 parent directory, and so on up to the file system root).
702 """
703 try:
704 scb = self.sbuilder
705 except AttributeError:
706 scb = self.dir.src_builder()
707 self.sbuilder = scb
708 return scb
709
711 """Get the absolute path of the file."""
712 return self.abspath
713
715
716
717
718 return self.name
719
721 try:
722 return self._proxy
723 except AttributeError:
724 ret = EntryProxy(self)
725 self._proxy = ret
726 return ret
727
729 """
730
731 Generates a target entry that corresponds to this entry (usually
732 a source file) with the specified prefix and suffix.
733
734 Note that this method can be overridden dynamically for generated
735 files that need different behavior. See Tool/swig.py for
736 an example.
737 """
738 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
739
742
743 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
744
746 """
747 Return all of the directories for a given path list, including
748 corresponding "backing" directories in any repositories.
749
750 The Node lookups are relative to this Node (typically a
751 directory), so memoizing result saves cycles from looking
752 up the same path for each target in a given directory.
753 """
754 try:
755 memo_dict = self._memo['Rfindalldirs']
756 except KeyError:
757 memo_dict = {}
758 self._memo['Rfindalldirs'] = memo_dict
759 else:
760 try:
761 return memo_dict[pathlist]
762 except KeyError:
763 pass
764
765 create_dir_relative_to_self = self.Dir
766 result = []
767 for path in pathlist:
768 if isinstance(path, SCons.Node.Node):
769 result.append(path)
770 else:
771 dir = create_dir_relative_to_self(path)
772 result.extend(dir.get_all_rdirs())
773
774 memo_dict[pathlist] = result
775
776 return result
777
778 - def RDirs(self, pathlist):
779 """Search for a list of directories in the Repository list."""
780 cwd = self.cwd or self.fs._cwd
781 return cwd.Rfindalldirs(pathlist)
782
783 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
784
786 try:
787 return self._memo['rentry']
788 except KeyError:
789 pass
790 result = self
791 if not self.exists():
792 norm_name = _my_normcase(self.name)
793 for dir in self.dir.get_all_rdirs():
794 try:
795 node = dir.entries[norm_name]
796 except KeyError:
797 if dir.entry_exists_on_disk(self.name):
798 result = dir.Entry(self.name)
799 break
800 self._memo['rentry'] = result
801 return result
802
805
807 """This is the class for generic Node.FS entries--that is, things
808 that could be a File or a Dir, but we're just not sure yet.
809 Consequently, the methods in this class really exist just to
810 transform their associated object into the right class when the
811 time comes, and then call the same-named method in the transformed
812 class."""
813
814 - def diskcheck_match(self):
816
817 - def disambiguate(self, must_exist=None):
818 """
819 """
820 if self.isdir():
821 self.__class__ = Dir
822 self._morph()
823 elif self.isfile():
824 self.__class__ = File
825 self._morph()
826 self.clear()
827 else:
828
829
830
831
832
833
834
835
836
837 srcdir = self.dir.srcnode()
838 if srcdir != self.dir and \
839 srcdir.entry_exists_on_disk(self.name) and \
840 self.srcnode().isdir():
841 self.__class__ = Dir
842 self._morph()
843 elif must_exist:
844 msg = "No such file or directory: '%s'" % self.abspath
845 raise SCons.Errors.UserError, msg
846 else:
847 self.__class__ = File
848 self._morph()
849 self.clear()
850 return self
851
853 """We're a generic Entry, but the caller is actually looking for
854 a File at this point, so morph into one."""
855 self.__class__ = File
856 self._morph()
857 self.clear()
858 return File.rfile(self)
859
860 - def scanner_key(self):
861 return self.get_suffix()
862
863 - def get_contents(self):
864 """Fetch the contents of the entry.
865
866 Since this should return the real contents from the file
867 system, we check to see into what sort of subclass we should
868 morph this Entry."""
869 try:
870 self = self.disambiguate(must_exist=1)
871 except SCons.Errors.UserError:
872
873
874
875
876
877 return ''
878 else:
879 return self.get_contents()
880
881 - def must_be_same(self, klass):
882 """Called to make sure a Node is a Dir. Since we're an
883 Entry, we can morph into one."""
884 if not self.__class__ is klass:
885 self.__class__ = klass
886 self._morph()
887 self.clear
888
889
890
891
892
893
894
895
896
897
898
899
901 """Return if the Entry exists. Check the file system to see
902 what we should turn into first. Assume a file if there's no
903 directory."""
904 return self.disambiguate().exists()
905
906 - def rel_path(self, other):
907 d = self.disambiguate()
908 if d.__class__ == Entry:
909 raise "rel_path() could not disambiguate File/Dir"
910 return d.rel_path(other)
911
912 - def new_ninfo(self):
913 return self.disambiguate().new_ninfo()
914
915 - def changed_since_last_build(self, target, prev_ni):
916 return self.disambiguate().changed_since_last_build(target, prev_ni)
917
918 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
919 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
920
921
922
923 _classEntry = Entry
924
925
927
928 if SCons.Memoize.use_memoizer:
929 __metaclass__ = SCons.Memoize.Memoized_Metaclass
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947 - def chmod(self, path, mode):
949 - def copy(self, src, dst):
950 return shutil.copy(src, dst)
951 - def copy2(self, src, dst):
952 return shutil.copy2(src, dst)
963 - def link(self, src, dst):
975 - def stat(self, path):
979 - def open(self, path):
983
984 if hasattr(os, 'symlink'):
987 else:
990
991 if hasattr(os, 'readlink'):
994 else:
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1009
1010 memoizer_counters = []
1011
1013 """Initialize the Node.FS subsystem.
1014
1015 The supplied path is the top of the source tree, where we
1016 expect to find the top-level build file. If no path is
1017 supplied, the current directory is the default.
1018
1019 The path argument must be a valid absolute path.
1020 """
1021 if __debug__: logInstanceCreation(self, 'Node.FS')
1022
1023 self._memo = {}
1024
1025 self.Root = {}
1026 self.SConstruct_dir = None
1027 self.max_drift = default_max_drift
1028
1029 self.Top = None
1030 if path is None:
1031 self.pathTop = os.getcwd()
1032 else:
1033 self.pathTop = path
1034 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1035
1036 self.Top = self.Dir(self.pathTop)
1037 self.Top.path = '.'
1038 self.Top.tpath = '.'
1039 self._cwd = self.Top
1040
1041 DirNodeInfo.fs = self
1042 FileNodeInfo.fs = self
1043
1045 self.SConstruct_dir = dir
1046
1048 return self.max_drift
1049
1051 self.max_drift = max_drift
1052
1055
1056 - def chdir(self, dir, change_os_dir=0):
1057 """Change the current working directory for lookups.
1058 If change_os_dir is true, we will also change the "real" cwd
1059 to match.
1060 """
1061 curr=self._cwd
1062 try:
1063 if not dir is None:
1064 self._cwd = dir
1065 if change_os_dir:
1066 os.chdir(dir.abspath)
1067 except OSError:
1068 self._cwd = curr
1069 raise
1070
1072 """
1073 Returns the root directory for the specified drive, creating
1074 it if necessary.
1075 """
1076 drive = _my_normcase(drive)
1077 try:
1078 return self.Root[drive]
1079 except KeyError:
1080 root = RootDir(drive, self)
1081 self.Root[drive] = root
1082 if not drive:
1083 self.Root[self.defaultDrive] = root
1084 elif drive == self.defaultDrive:
1085 self.Root[''] = root
1086 return root
1087
1088 - def _lookup(self, p, directory, fsclass, create=1):
1089 """
1090 The generic entry point for Node lookup with user-supplied data.
1091
1092 This translates arbitrary input into a canonical Node.FS object
1093 of the specified fsclass. The general approach for strings is
1094 to turn it into a fully normalized absolute path and then call
1095 the root directory's lookup_abs() method for the heavy lifting.
1096
1097 If the path name begins with '#', it is unconditionally
1098 interpreted relative to the top-level directory of this FS. '#'
1099 is treated as a synonym for the top-level SConstruct directory,
1100 much like '~' is treated as a synonym for the user's home
1101 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1102 to the 'foo' subdirectory underneath the top-level SConstruct
1103 directory.
1104
1105 If the path name is relative, then the path is looked up relative
1106 to the specified directory, or the current directory (self._cwd,
1107 typically the SConscript directory) if the specified directory
1108 is None.
1109 """
1110 if isinstance(p, Base):
1111
1112
1113 p.must_be_same(fsclass)
1114 return p
1115
1116 p = str(p)
1117
1118 initial_hash = (p[0:1] == '#')
1119 if initial_hash:
1120
1121
1122
1123 p = p[1:]
1124 directory = self.Top
1125
1126 if directory and not isinstance(directory, Dir):
1127 directory = self.Dir(directory)
1128
1129 if do_splitdrive:
1130 drive, p = os.path.splitdrive(p)
1131 else:
1132 drive = ''
1133 if drive and not p:
1134
1135
1136 p = os.sep
1137 absolute = os.path.isabs(p)
1138
1139 needs_normpath = needs_normpath_check.match(p)
1140
1141 if initial_hash or not absolute:
1142
1143
1144
1145
1146
1147 if not directory:
1148 directory = self._cwd
1149 if p:
1150 p = directory.labspath + '/' + p
1151 else:
1152 p = directory.labspath
1153
1154 if needs_normpath:
1155 p = os.path.normpath(p)
1156
1157 if drive or absolute:
1158 root = self.get_root(drive)
1159 else:
1160 if not directory:
1161 directory = self._cwd
1162 root = directory.root
1163
1164 if os.sep != '/':
1165 p = string.replace(p, os.sep, '/')
1166 return root._lookup_abs(p, fsclass, create)
1167
1168 - def Entry(self, name, directory = None, create = 1):
1169 """Lookup or create a generic Entry node with the specified name.
1170 If the name is a relative path (begins with ./, ../, or a file
1171 name), then it is looked up relative to the supplied directory
1172 node, or to the top level directory of the FS (supplied at
1173 construction time) if no directory is supplied.
1174 """
1175 return self._lookup(name, directory, Entry, create)
1176
1177 - def File(self, name, directory = None, create = 1):
1178 """Lookup or create a File node with the specified name. If
1179 the name is a relative path (begins with ./, ../, or a file name),
1180 then it is looked up relative to the supplied directory node,
1181 or to the top level directory of the FS (supplied at construction
1182 time) if no directory is supplied.
1183
1184 This method will raise TypeError if a directory is found at the
1185 specified path.
1186 """
1187 return self._lookup(name, directory, File, create)
1188
1189 - def Dir(self, name, directory = None, create = True):
1190 """Lookup or create a Dir node with the specified name. If
1191 the name is a relative path (begins with ./, ../, or a file name),
1192 then it is looked up relative to the supplied directory node,
1193 or to the top level directory of the FS (supplied at construction
1194 time) if no directory is supplied.
1195
1196 This method will raise TypeError if a normal file is found at the
1197 specified path.
1198 """
1199 return self._lookup(name, directory, Dir, create)
1200
1201 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1202 """Link the supplied variant directory to the source directory
1203 for purposes of building files."""
1204
1205 if not isinstance(src_dir, SCons.Node.Node):
1206 src_dir = self.Dir(src_dir)
1207 if not isinstance(variant_dir, SCons.Node.Node):
1208 variant_dir = self.Dir(variant_dir)
1209 if src_dir.is_under(variant_dir):
1210 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1211 if variant_dir.srcdir:
1212 if variant_dir.srcdir == src_dir:
1213 return
1214 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1215 variant_dir.link(src_dir, duplicate)
1216
1223
1225 """Create targets in corresponding variant directories
1226
1227 Climb the directory tree, and look up path names
1228 relative to any linked variant directories we find.
1229
1230 Even though this loops and walks up the tree, we don't memoize
1231 the return value because this is really only used to process
1232 the command-line targets.
1233 """
1234 targets = []
1235 message = None
1236 fmt = "building associated VariantDir targets: %s"
1237 start_dir = dir
1238 while dir:
1239 for bd in dir.variant_dirs:
1240 if start_dir.is_under(bd):
1241
1242 return [orig], fmt % str(orig)
1243 p = apply(os.path.join, [bd.path] + tail)
1244 targets.append(self.Entry(p))
1245 tail = [dir.name] + tail
1246 dir = dir.up()
1247 if targets:
1248 message = fmt % string.join(map(str, targets))
1249 return targets, message
1250
1252 """
1253 Globs
1254
1255 This is mainly a shim layer
1256 """
1257 if cwd is None:
1258 cwd = self.getcwd()
1259 return cwd.glob(pathname, ondisk, source, strings)
1260
1277
1280
1281 glob_magic_check = re.compile('[*?[]')
1282
1285
1287 """A class for directories in a file system.
1288 """
1289
1290 memoizer_counters = []
1291
1292 NodeInfo = DirNodeInfo
1293 BuildInfo = DirBuildInfo
1294
1295 - def __init__(self, name, directory, fs):
1299
1301 """Turn a file system Node (either a freshly initialized directory
1302 object or a separate Entry object) into a proper directory object.
1303
1304 Set up this directory's entries and hook it into the file
1305 system tree. Specify that directories (this Node) don't use
1306 signatures for calculating whether they're current.
1307 """
1308
1309 self.repositories = []
1310 self.srcdir = None
1311
1312 self.entries = {}
1313 self.entries['.'] = self
1314 self.entries['..'] = self.dir
1315 self.cwd = self
1316 self.searched = 0
1317 self._sconsign = None
1318 self.variant_dirs = []
1319 self.root = self.dir.root
1320
1321
1322
1323
1324 self.builder = get_MkdirBuilder()
1325 self.get_executor().set_action_list(self.builder.action)
1326
1330
1332 """Called when we change the repository(ies) for a directory.
1333 This clears any cached information that is invalidated by changing
1334 the repository."""
1335
1336 for node in self.entries.values():
1337 if node != self.dir:
1338 if node != self and isinstance(node, Dir):
1339 node.__clearRepositoryCache(duplicate)
1340 else:
1341 node.clear()
1342 try:
1343 del node._srcreps
1344 except AttributeError:
1345 pass
1346 if duplicate != None:
1347 node.duplicate=duplicate
1348
1350 if node != self:
1351 node.duplicate = node.get_dir().duplicate
1352
1353 - def Entry(self, name):
1354 """
1355 Looks up or creates an entry node named 'name' relative to
1356 this directory.
1357 """
1358 return self.fs.Entry(name, self)
1359
1360 - def Dir(self, name, create=True):
1361 """
1362 Looks up or creates a directory node named 'name' relative to
1363 this directory.
1364 """
1365 dir = self.fs.Dir(name, self, create)
1366 return dir
1367
1368 - def File(self, name):
1369 """
1370 Looks up or creates a file node named 'name' relative to
1371 this directory.
1372 """
1373 return self.fs.File(name, self)
1374
1376 """
1377 Looks up a *normalized* relative path name, relative to this
1378 directory.
1379
1380 This method is intended for use by internal lookups with
1381 already-normalized path data. For general-purpose lookups,
1382 use the Entry(), Dir() and File() methods above.
1383
1384 This method does *no* input checking and will die or give
1385 incorrect results if it's passed a non-normalized path name (e.g.,
1386 a path containing '..'), an absolute path name, a top-relative
1387 ('#foo') path name, or any kind of object.
1388 """
1389 name = self.entry_labspath(name)
1390 return self.root._lookup_abs(name, klass, create)
1391
1392 - def link(self, srcdir, duplicate):
1393 """Set this directory as the variant directory for the
1394 supplied source directory."""
1395 self.srcdir = srcdir
1396 self.duplicate = duplicate
1397 self.__clearRepositoryCache(duplicate)
1398 srcdir.variant_dirs.append(self)
1399
1401 """Returns a list of repositories for this directory.
1402 """
1403 if self.srcdir and not self.duplicate:
1404 return self.srcdir.get_all_rdirs() + self.repositories
1405 return self.repositories
1406
1407 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1408
1410 try:
1411 return self._memo['get_all_rdirs']
1412 except KeyError:
1413 pass
1414
1415 result = [self]
1416 fname = '.'
1417 dir = self
1418 while dir:
1419 for rep in dir.getRepositories():
1420 result.append(rep.Dir(fname))
1421 if fname == '.':
1422 fname = dir.name
1423 else:
1424 fname = dir.name + os.sep + fname
1425 dir = dir.up()
1426
1427 self._memo['get_all_rdirs'] = result
1428
1429 return result
1430
1432 if dir != self and not dir in self.repositories:
1433 self.repositories.append(dir)
1434 dir.tpath = '.'
1435 self.__clearRepositoryCache()
1436
1438 return self.entries['..']
1439
1442
1443 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1444
1446 """Return a path to "other" relative to this directory.
1447 """
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459 try:
1460 memo_dict = self._memo['rel_path']
1461 except KeyError:
1462 memo_dict = {}
1463 self._memo['rel_path'] = memo_dict
1464 else:
1465 try:
1466 return memo_dict[other]
1467 except KeyError:
1468 pass
1469
1470 if self is other:
1471
1472 result = '.'
1473
1474 elif not other in self.path_elements:
1475
1476 try:
1477 other_dir = other.get_dir()
1478 except AttributeError:
1479 result = str(other)
1480 else:
1481 if other_dir is None:
1482 result = other.name
1483 else:
1484 dir_rel_path = self.rel_path(other_dir)
1485 if dir_rel_path == '.':
1486 result = other.name
1487 else:
1488 result = dir_rel_path + os.sep + other.name
1489
1490 else:
1491
1492 i = self.path_elements.index(other) + 1
1493
1494 path_elems = ['..'] * (len(self.path_elements) - i) \
1495 + map(lambda n: n.name, other.path_elements[i:])
1496
1497 result = string.join(path_elems, os.sep)
1498
1499 memo_dict[other] = result
1500
1501 return result
1502
1506
1510
1512 """Return this directory's implicit dependencies.
1513
1514 We don't bother caching the results because the scan typically
1515 shouldn't be requested more than once (as opposed to scanning
1516 .h file contents, which can be requested as many times as the
1517 files is #included by other files).
1518 """
1519 if not scanner:
1520 return []
1521
1522
1523
1524
1525
1526
1527
1528
1529 self.clear()
1530 return scanner(self, env, path)
1531
1532
1533
1534
1535
1538
1544
1545
1546
1547
1548
1550 """Create this directory, silently and without worrying about
1551 whether the builder is the default or not."""
1552 listDirs = []
1553 parent = self
1554 while parent:
1555 if parent.exists():
1556 break
1557 listDirs.append(parent)
1558 p = parent.up()
1559 if p is None:
1560 raise SCons.Errors.StopError, parent.path
1561 parent = p
1562 listDirs.reverse()
1563 for dirnode in listDirs:
1564 try:
1565
1566
1567
1568
1569 SCons.Node.Node.build(dirnode)
1570 dirnode.get_executor().nullify()
1571
1572
1573
1574
1575 dirnode.clear()
1576 except OSError:
1577 pass
1578
1582
1584 """Return any corresponding targets in a variant directory.
1585 """
1586 return self.fs.variant_dir_target_climb(self, self, [])
1587
1589 """A directory does not get scanned."""
1590 return None
1591
1592 - def get_contents(self):
1593 """Return aggregate contents of all our children."""
1594 contents = map(lambda n: n.get_contents(), self.children())
1595 return string.join(contents, '')
1596
1599
1600 changed_since_last_build = SCons.Node.Node.state_has_changed
1601
1612
1614 if not self.exists():
1615 norm_name = _my_normcase(self.name)
1616 for dir in self.dir.get_all_rdirs():
1617 try: node = dir.entries[norm_name]
1618 except KeyError: node = dir.dir_on_disk(self.name)
1619 if node and node.exists() and \
1620 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1621 return node
1622 return self
1623
1625 """Return the .sconsign file info for this directory,
1626 creating it first if necessary."""
1627 if not self._sconsign:
1628 import SCons.SConsign
1629 self._sconsign = SCons.SConsign.ForDirectory(self)
1630 return self._sconsign
1631
1633 """Dir has a special need for srcnode()...if we
1634 have a srcdir attribute set, then that *is* our srcnode."""
1635 if self.srcdir:
1636 return self.srcdir
1637 return Base.srcnode(self)
1638
1640 """Return the latest timestamp from among our children"""
1641 stamp = 0
1642 for kid in self.children():
1643 if kid.get_timestamp() > stamp:
1644 stamp = kid.get_timestamp()
1645 return stamp
1646
1647 - def entry_abspath(self, name):
1648 return self.abspath + os.sep + name
1649
1650 - def entry_labspath(self, name):
1651 return self.labspath + '/' + name
1652
1653 - def entry_path(self, name):
1654 return self.path + os.sep + name
1655
1656 - def entry_tpath(self, name):
1657 return self.tpath + os.sep + name
1658
1659 - def entry_exists_on_disk(self, name):
1660 try:
1661 d = self.on_disk_entries
1662 except AttributeError:
1663 d = {}
1664 try:
1665 entries = os.listdir(self.abspath)
1666 except OSError:
1667 pass
1668 else:
1669 for entry in map(_my_normcase, entries):
1670 d[entry] = 1
1671 self.on_disk_entries = d
1672 return d.has_key(_my_normcase(name))
1673
1674 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1675
1677 try:
1678 return self._memo['srcdir_list']
1679 except KeyError:
1680 pass
1681
1682 result = []
1683
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
1692 self._memo['srcdir_list'] = result
1693
1694 return result
1695
1712
1715
1716 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1717
1719 try:
1720 memo_dict = self._memo['srcdir_find_file']
1721 except KeyError:
1722 memo_dict = {}
1723 self._memo['srcdir_find_file'] = memo_dict
1724 else:
1725 try:
1726 return memo_dict[filename]
1727 except KeyError:
1728 pass
1729
1730 def func(node):
1731 if (isinstance(node, File) or isinstance(node, Entry)) and \
1732 (node.is_derived() or node.exists()):
1733 return node
1734 return None
1735
1736 norm_name = _my_normcase(filename)
1737
1738 for rdir in self.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 = (node, self)
1744 memo_dict[filename] = result
1745 return result
1746
1747 for srcdir in self.srcdir_list():
1748 for rdir in srcdir.get_all_rdirs():
1749 try: node = rdir.entries[norm_name]
1750 except KeyError: node = rdir.file_on_disk(filename)
1751 else: node = func(node)
1752 if node:
1753 result = (File(filename, self, self.fs), srcdir)
1754 memo_dict[filename] = result
1755 return result
1756
1757 result = (None, None)
1758 memo_dict[filename] = result
1759 return result
1760
1766
1777
1778 - def walk(self, func, arg):
1779 """
1780 Walk this directory tree by calling the specified function
1781 for each directory in the tree.
1782
1783 This behaves like the os.path.walk() function, but for in-memory
1784 Node.FS.Dir objects. The function takes the same arguments as
1785 the functions passed to os.path.walk():
1786
1787 func(arg, dirname, fnames)
1788
1789 Except that "dirname" will actually be the directory *Node*,
1790 not the string. The '.' and '..' entries are excluded from
1791 fnames. The fnames list may be modified in-place to filter the
1792 subdirectories visited or otherwise impose a specific order.
1793 The "arg" argument is always passed to func() and may be used
1794 in any way (or ignored, passing None is common).
1795 """
1796 entries = self.entries
1797 names = entries.keys()
1798 names.remove('.')
1799 names.remove('..')
1800 func(arg, self, names)
1801 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1802 for dirname in filter(select_dirs, names):
1803 entries[dirname].walk(func, arg)
1804
1806 """
1807 Returns a list of Nodes (or strings) matching a specified
1808 pathname pattern.
1809
1810 Pathname patterns follow UNIX shell semantics: * matches
1811 any-length strings of any characters, ? matches any character,
1812 and [] can enclose lists or ranges of characters. Matches do
1813 not span directory separators.
1814
1815 The matches take into account Repositories, returning local
1816 Nodes if a corresponding entry exists in a Repository (either
1817 an in-memory Node or something on disk).
1818
1819 By defafult, the glob() function matches entries that exist
1820 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1821 argument to False (or some other non-true value) causes the glob()
1822 function to only match in-memory Nodes. The default behavior is
1823 to return both the on-disk and in-memory Nodes.
1824
1825 The "source" argument, when true, specifies that corresponding
1826 source Nodes must be returned if you're globbing in a build
1827 directory (initialized with VariantDir()). The default behavior
1828 is to return Nodes local to the VariantDir().
1829
1830 The "strings" argument, when true, returns the matches as strings,
1831 not Nodes. The strings are path names relative to this directory.
1832
1833 The underlying algorithm is adapted from the glob.glob() function
1834 in the Python library (but heavily modified), and uses fnmatch()
1835 under the covers.
1836 """
1837 dirname, basename = os.path.split(pathname)
1838 if not dirname:
1839 return self._glob1(basename, ondisk, source, strings)
1840 if has_glob_magic(dirname):
1841 list = self.glob(dirname, ondisk, source, strings=False)
1842 else:
1843 list = [self.Dir(dirname, create=True)]
1844 result = []
1845 for dir in list:
1846 r = dir._glob1(basename, ondisk, source, strings)
1847 if strings:
1848 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1849 result.extend(r)
1850 return result
1851
1853 """
1854 Globs for and returns a list of entry names matching a single
1855 pattern in this directory.
1856
1857 This searches any repositories and source directories for
1858 corresponding entries and returns a Node (or string) relative
1859 to the current directory if an entry is found anywhere.
1860
1861 TODO: handle pattern with no wildcard
1862 """
1863 search_dir_list = self.get_all_rdirs()
1864 for srcdir in self.srcdir_list():
1865 search_dir_list.extend(srcdir.get_all_rdirs())
1866
1867 names = []
1868 for dir in search_dir_list:
1869
1870
1871
1872
1873 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1874 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1875 names.extend(node_names)
1876 if ondisk:
1877 try:
1878 disk_names = os.listdir(dir.abspath)
1879 except os.error:
1880 pass
1881 else:
1882 names.extend(disk_names)
1883 if not strings:
1884
1885
1886
1887
1888
1889
1890
1891
1892 if pattern[0] != '.':
1893
1894 disk_names = filter(lambda x: x[0] != '.', disk_names)
1895 disk_names = fnmatch.filter(disk_names, pattern)
1896 rep_nodes = map(dir.Entry, disk_names)
1897
1898 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1899 for node, name in izip(rep_nodes, disk_names):
1900 n = self.Entry(name)
1901 if n.__class__ != node.__class__:
1902 n.__class__ = node.__class__
1903 n._morph()
1904
1905 names = set(names)
1906 if pattern[0] != '.':
1907
1908 names = filter(lambda x: x[0] != '.', names)
1909 names = fnmatch.filter(names, pattern)
1910
1911 if strings:
1912 return names
1913
1914
1915 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1916
1918 """A class for the root directory of a file system.
1919
1920 This is the same as a Dir class, except that the path separator
1921 ('/' or '\\') is actually part of the name, so we don't need to
1922 add a separator when creating the path names of entries within
1923 this directory.
1924 """
1926 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1927
1928
1929
1930 self.abspath = ''
1931 self.labspath = ''
1932 self.path = ''
1933 self.tpath = ''
1934 self.path_elements = []
1935 self.duplicate = 0
1936 self.root = self
1937 Base.__init__(self, name, self, fs)
1938
1939
1940
1941
1942
1943 self.abspath = name + os.sep
1944 self.labspath = ''
1945 self.path = name + os.sep
1946 self.tpath = name + os.sep
1947 self._morph()
1948
1949 self._lookupDict = {}
1950
1951
1952
1953
1954
1955 self._lookupDict[''] = self
1956 self._lookupDict['/'] = self
1957 self._lookupDict['//'] = self
1958 self._lookupDict[os.sep] = self
1959 self._lookupDict[os.sep + os.sep] = self
1960
1965
1967 """
1968 Fast (?) lookup of a *normalized* absolute path.
1969
1970 This method is intended for use by internal lookups with
1971 already-normalized path data. For general-purpose lookups,
1972 use the FS.Entry(), FS.Dir() or FS.File() methods.
1973
1974 The caller is responsible for making sure we're passed a
1975 normalized absolute path; we merely let Python's dictionary look
1976 up and return the One True Node.FS object for the path.
1977
1978 If no Node for the specified "p" doesn't already exist, and
1979 "create" is specified, the Node may be created after recursive
1980 invocation to find or create the parent directory or directories.
1981 """
1982 k = _my_normcase(p)
1983 try:
1984 result = self._lookupDict[k]
1985 except KeyError:
1986 if not create:
1987 raise SCons.Errors.UserError
1988
1989
1990 dir_name, file_name = os.path.split(p)
1991 dir_node = self._lookup_abs(dir_name, Dir)
1992 result = klass(file_name, dir_node, self.fs)
1993 self._lookupDict[k] = result
1994 dir_node.entries[_my_normcase(file_name)] = result
1995 dir_node.implicit = None
1996
1997
1998
1999 result.diskcheck_match()
2000 else:
2001
2002
2003 result.must_be_same(klass)
2004 return result
2005
2008
2009 - def entry_abspath(self, name):
2010 return self.abspath + name
2011
2012 - def entry_labspath(self, name):
2014
2015 - def entry_path(self, name):
2016 return self.path + name
2017
2018 - def entry_tpath(self, name):
2019 return self.tpath + name
2020
2022 if self is dir:
2023 return 1
2024 else:
2025 return 0
2026
2029
2032
2035
2054
2056 current_version_id = 1
2057
2059 """
2060 Converts this FileBuildInfo object for writing to a .sconsign file
2061
2062 This replaces each Node in our various dependency lists with its
2063 usual string representation: relative to the top-level SConstruct
2064 directory, or an absolute path if it's outside.
2065 """
2066 if os.sep == '/':
2067 node_to_str = str
2068 else:
2069 def node_to_str(n):
2070 try:
2071 s = n.path
2072 except AttributeError:
2073 s = str(n)
2074 else:
2075 s = string.replace(s, os.sep, '/')
2076 return s
2077 for attr in ['bsources', 'bdepends', 'bimplicit']:
2078 try:
2079 val = getattr(self, attr)
2080 except AttributeError:
2081 pass
2082 else:
2083 setattr(self, attr, map(node_to_str, val))
2085 """
2086 Converts a newly-read FileBuildInfo object for in-SCons use
2087
2088 For normal up-to-date checking, we don't have any conversion to
2089 perform--but we're leaving this method here to make that clear.
2090 """
2091 pass
2093 """
2094 Prepares a FileBuildInfo object for explaining what changed
2095
2096 The bsources, bdepends and bimplicit lists have all been
2097 stored on disk as paths relative to the top-level SConstruct
2098 directory. Convert the strings to actual Nodes (for use by the
2099 --debug=explain code and --implicit-cache).
2100 """
2101 attrs = [
2102 ('bsources', 'bsourcesigs'),
2103 ('bdepends', 'bdependsigs'),
2104 ('bimplicit', 'bimplicitsigs'),
2105 ]
2106 for (nattr, sattr) in attrs:
2107 try:
2108 strings = getattr(self, nattr)
2109 nodeinfos = getattr(self, sattr)
2110 except AttributeError:
2111 pass
2112 else:
2113 nodes = []
2114 for s, ni in izip(strings, nodeinfos):
2115 if not isinstance(s, SCons.Node.Node):
2116 s = ni.str_to_node(s)
2117 nodes.append(s)
2118 setattr(self, nattr, nodes)
2128
2130 """A class for files in a file system.
2131 """
2132
2133 memoizer_counters = []
2134
2135 NodeInfo = FileNodeInfo
2136 BuildInfo = FileBuildInfo
2137
2141
2142 - def __init__(self, name, directory, fs):
2146
2147 - def Entry(self, name):
2148 """Create an entry node named 'name' relative to
2149 the SConscript directory of this file."""
2150 return self.cwd.Entry(name)
2151
2152 - def Dir(self, name, create=True):
2153 """Create a directory node named 'name' relative to
2154 the SConscript directory of this file."""
2155 return self.cwd.Dir(name, create)
2156
2157 - def Dirs(self, pathlist):
2158 """Create a list of directories relative to the SConscript
2159 directory of this file."""
2160 return map(lambda p, s=self: s.Dir(p), pathlist)
2161
2162 - def File(self, name):
2163 """Create a file node named 'name' relative to
2164 the SConscript directory of this file."""
2165 return self.cwd.File(name)
2166
2167
2168
2169
2170
2171
2172
2173
2175 """Turn a file system node into a File object."""
2176 self.scanner_paths = {}
2177 if not hasattr(self, '_local'):
2178 self._local = 0
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190 if self.has_builder():
2191 self.changed_since_last_build = self.decide_target
2192
2195
2196 - def get_contents(self):
2197 if not self.rexists():
2198 return ''
2199 fname = self.rfile().abspath
2200 try:
2201 r = open(fname, "rb").read()
2202 except EnvironmentError, e:
2203 if not e.filename:
2204 e.filename = fname
2205 raise
2206 return r
2207
2208 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2209
2211 try:
2212 return self._memo['get_size']
2213 except KeyError:
2214 pass
2215
2216 if self.rexists():
2217 size = self.rfile().getsize()
2218 else:
2219 size = 0
2220
2221 self._memo['get_size'] = size
2222
2223 return size
2224
2225 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2226
2228 try:
2229 return self._memo['get_timestamp']
2230 except KeyError:
2231 pass
2232
2233 if self.rexists():
2234 timestamp = self.rfile().getmtime()
2235 else:
2236 timestamp = 0
2237
2238 self._memo['get_timestamp'] = timestamp
2239
2240 return timestamp
2241
2248
2249 convert_copy_attrs = [
2250 'bsources',
2251 'bimplicit',
2252 'bdepends',
2253 'bact',
2254 'bactsig',
2255 'ninfo',
2256 ]
2257
2258
2259 convert_sig_attrs = [
2260 'bsourcesigs',
2261 'bimplicitsigs',
2262 'bdependsigs',
2263 ]
2264
2265 - def convert_old_entry(self, old_entry):
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333 import SCons.SConsign
2334 new_entry = SCons.SConsign.SConsignEntry()
2335 new_entry.binfo = self.new_binfo()
2336 binfo = new_entry.binfo
2337 for attr in self.convert_copy_attrs:
2338 try:
2339 value = getattr(old_entry, attr)
2340 except AttributeError:
2341 pass
2342 else:
2343 setattr(binfo, attr, value)
2344 delattr(old_entry, attr)
2345 for attr in self.convert_sig_attrs:
2346 try:
2347 sig_list = getattr(old_entry, attr)
2348 except AttributeError:
2349 pass
2350 else:
2351 value = []
2352 for sig in sig_list:
2353 ninfo = self.new_ninfo()
2354 if len(sig) == 32:
2355 ninfo.csig = sig
2356 else:
2357 ninfo.timestamp = sig
2358 value.append(ninfo)
2359 setattr(binfo, attr, value)
2360 delattr(old_entry, attr)
2361 return new_entry
2362
2363 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2364
2366 try:
2367 return self._memo['get_stored_info']
2368 except KeyError:
2369 pass
2370
2371 try:
2372 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2373 except (KeyError, OSError):
2374 import SCons.SConsign
2375 sconsign_entry = SCons.SConsign.SConsignEntry()
2376 sconsign_entry.binfo = self.new_binfo()
2377 sconsign_entry.ninfo = self.new_ninfo()
2378 else:
2379 if isinstance(sconsign_entry, FileBuildInfo):
2380
2381
2382 sconsign_entry = self.convert_old_entry(sconsign_entry)
2383 try:
2384 delattr(sconsign_entry.ninfo, 'bsig')
2385 except AttributeError:
2386 pass
2387
2388 self._memo['get_stored_info'] = sconsign_entry
2389
2390 return sconsign_entry
2391
2397
2400
2402 return (id(env), id(scanner), path)
2403
2404 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2405
2407 """Return the included implicit dependencies in this file.
2408 Cache results so we only scan the file once per path
2409 regardless of how many times this information is requested.
2410 """
2411 memo_key = (id(env), id(scanner), path)
2412 try:
2413 memo_dict = self._memo['get_found_includes']
2414 except KeyError:
2415 memo_dict = {}
2416 self._memo['get_found_includes'] = memo_dict
2417 else:
2418 try:
2419 return memo_dict[memo_key]
2420 except KeyError:
2421 pass
2422
2423 if scanner:
2424 result = scanner(self, env, path)
2425 result = map(lambda N: N.disambiguate(), result)
2426 else:
2427 result = []
2428
2429 memo_dict[memo_key] = result
2430
2431 return result
2432
2437
2439 """Try to retrieve the node's content from a cache
2440
2441 This method is called from multiple threads in a parallel build,
2442 so only do thread safe stuff here. Do thread unsafe stuff in
2443 built().
2444
2445 Returns true iff the node was successfully retrieved.
2446 """
2447 if self.nocache:
2448 return None
2449 if not self.is_derived():
2450 return None
2451 return self.get_build_env().get_CacheDir().retrieve(self)
2452
2468
2491
2511
2513 """Return whether this Node has a source builder or not.
2514
2515 If this Node doesn't have an explicit source code builder, this
2516 is where we figure out, on the fly, if there's a transparent
2517 source code builder for it.
2518
2519 Note that if we found a source builder, we also set the
2520 self.builder attribute, so that all of the methods that actually
2521 *build* this file don't have to do anything different.
2522 """
2523 try:
2524 scb = self.sbuilder
2525 except AttributeError:
2526 scb = self.sbuilder = self.find_src_builder()
2527 return not scb is None
2528
2535
2541
2542
2543
2544
2545
2549
2564
2565
2566
2567
2568
2570 """Remove this file."""
2571 if self.exists() or self.islink():
2572 self.fs.unlink(self.path)
2573 return 1
2574 return None
2575
2589
2590 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2591
2593 try:
2594 return self._memo['exists']
2595 except KeyError:
2596 pass
2597
2598 if self.duplicate and not self.is_derived() and not self.linked:
2599 src = self.srcnode()
2600 if not src is self:
2601
2602 src = src.rfile()
2603 if src.abspath != self.abspath:
2604 if src.exists():
2605 self.do_duplicate(src)
2606
2607
2608 else:
2609
2610
2611 if Base.exists(self) or self.islink():
2612 self.fs.unlink(self.path)
2613
2614
2615 self._memo['exists'] = None
2616 return None
2617 result = Base.exists(self)
2618 self._memo['exists'] = result
2619 return result
2620
2621
2622
2623
2624
2626 """
2627 Returns the content signature currently stored for this node
2628 if it's been unmodified longer than the max_drift value, or the
2629 max_drift value is 0. Returns None otherwise.
2630 """
2631 old = self.get_stored_info()
2632 mtime = self.get_timestamp()
2633
2634 csig = None
2635 max_drift = self.fs.max_drift
2636 if max_drift > 0:
2637 if (time.time() - mtime) > max_drift:
2638 try:
2639 n = old.ninfo
2640 if n.timestamp and n.csig and n.timestamp == mtime:
2641 csig = n.csig
2642 except AttributeError:
2643 pass
2644 elif max_drift == 0:
2645 try:
2646 csig = old.ninfo.csig
2647 except AttributeError:
2648 pass
2649
2650 return csig
2651
2653 """
2654 Generate a node's content signature, the digested signature
2655 of its content.
2656
2657 node - the node
2658 cache - alternate node to use for the signature cache
2659 returns - the content signature
2660 """
2661 ninfo = self.get_ninfo()
2662 try:
2663 return ninfo.csig
2664 except AttributeError:
2665 pass
2666
2667 csig = self.get_max_drift_csig()
2668 if csig is None:
2669
2670 try:
2671 contents = self.get_contents()
2672 except IOError:
2673
2674
2675
2676
2677 csig = ''
2678 else:
2679 csig = SCons.Util.MD5signature(contents)
2680
2681 ninfo.csig = csig
2682
2683 return csig
2684
2685
2686
2687
2688
2692
2693 - def changed_content(self, target, prev_ni):
2694 cur_csig = self.get_csig()
2695 try:
2696 return cur_csig != prev_ni.csig
2697 except AttributeError:
2698 return 1
2699
2702
2703 - def changed_timestamp_then_content(self, target, prev_ni):
2704 if not self.changed_timestamp_match(target, prev_ni):
2705 try:
2706 self.get_ninfo().csig = prev_ni.csig
2707 except AttributeError:
2708 pass
2709 return False
2710 return self.changed_content(target, prev_ni)
2711
2717
2719 try:
2720 return self.get_timestamp() != prev_ni.timestamp
2721 except AttributeError:
2722 return 1
2723
2726
2729
2730
2731
2732 changed_since_last_build = decide_source
2733
2735 T = 0
2736 if T: Trace('is_up_to_date(%s):' % self)
2737 if not self.exists():
2738 if T: Trace(' not self.exists():')
2739
2740 r = self.rfile()
2741 if r != self:
2742
2743 if not self.changed(r):
2744 if T: Trace(' changed(%s):' % r)
2745
2746 if self._local:
2747
2748 e = LocalCopy(self, r, None)
2749 if isinstance(e, SCons.Errors.BuildError):
2750 raise
2751 self.store_info()
2752 if T: Trace(' 1\n')
2753 return 1
2754 self.changed()
2755 if T: Trace(' None\n')
2756 return None
2757 else:
2758 r = self.changed()
2759 if T: Trace(' self.exists(): %s\n' % r)
2760 return not r
2761
2762 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2763
2765 try:
2766 return self._memo['rfile']
2767 except KeyError:
2768 pass
2769 result = self
2770 if not self.exists():
2771 norm_name = _my_normcase(self.name)
2772 for dir in self.dir.get_all_rdirs():
2773 try: node = dir.entries[norm_name]
2774 except KeyError: node = dir.file_on_disk(self.name)
2775 if node and node.exists() and \
2776 (isinstance(node, File) or isinstance(node, Entry) \
2777 or not node.is_derived()):
2778 result = node
2779 break
2780 self._memo['rfile'] = result
2781 return result
2782
2784 return str(self.rfile())
2785
2787 """
2788 Fetch a Node's content signature for purposes of computing
2789 another Node's cachesig.
2790
2791 This is a wrapper around the normal get_csig() method that handles
2792 the somewhat obscure case of using CacheDir with the -n option.
2793 Any files that don't exist would normally be "built" by fetching
2794 them from the cache, but the normal get_csig() method will try
2795 to open up the local file, which doesn't exist because the -n
2796 option meant we didn't actually pull the file from cachedir.
2797 But since the file *does* actually exist in the cachedir, we
2798 can use its contents for the csig.
2799 """
2800 try:
2801 return self.cachedir_csig
2802 except AttributeError:
2803 pass
2804
2805 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2806 if not self.exists() and cachefile and os.path.exists(cachefile):
2807 contents = open(cachefile, 'rb').read()
2808 self.cachedir_csig = SCons.Util.MD5signature(contents)
2809 else:
2810 self.cachedir_csig = self.get_csig()
2811 return self.cachedir_csig
2812
2829
2830 default_fs = None
2831
2837
2839 """
2840 """
2841 if SCons.Memoize.use_memoizer:
2842 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2843
2844 memoizer_counters = []
2845
2848
2850 """
2851 A helper method for find_file() that looks up a directory for
2852 a file we're trying to find. This only creates the Dir Node if
2853 it exists on-disk, since if the directory doesn't exist we know
2854 we won't find any files in it... :-)
2855
2856 It would be more compact to just use this as a nested function
2857 with a default keyword argument (see the commented-out version
2858 below), but that doesn't work unless you have nested scopes,
2859 so we define it here just so this work under Python 1.5.2.
2860 """
2861 if fd is None:
2862 fd = self.default_filedir
2863 dir, name = os.path.split(fd)
2864 drive, d = os.path.splitdrive(dir)
2865 if d in ('/', os.sep):
2866 return p.fs.get_root(drive).dir_on_disk(name)
2867 if dir:
2868 p = self.filedir_lookup(p, dir)
2869 if not p:
2870 return None
2871 norm_name = _my_normcase(name)
2872 try:
2873 node = p.entries[norm_name]
2874 except KeyError:
2875 return p.dir_on_disk(name)
2876 if isinstance(node, Dir):
2877 return node
2878 if isinstance(node, Entry):
2879 node.must_be_same(Dir)
2880 return node
2881 return None
2882
2884 return (filename, paths)
2885
2886 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2887
2888 - def find_file(self, filename, paths, verbose=None):
2889 """
2890 find_file(str, [Dir()]) -> [nodes]
2891
2892 filename - a filename to find
2893 paths - a list of directory path *nodes* to search in. Can be
2894 represented as a list, a tuple, or a callable that is
2895 called with no arguments and returns the list or tuple.
2896
2897 returns - the node created from the found file.
2898
2899 Find a node corresponding to either a derived file or a file
2900 that exists already.
2901
2902 Only the first file found is returned, and none is returned
2903 if no file is found.
2904 """
2905 memo_key = self._find_file_key(filename, paths)
2906 try:
2907 memo_dict = self._memo['find_file']
2908 except KeyError:
2909 memo_dict = {}
2910 self._memo['find_file'] = memo_dict
2911 else:
2912 try:
2913 return memo_dict[memo_key]
2914 except KeyError:
2915 pass
2916
2917 if verbose:
2918 if not SCons.Util.is_String(verbose):
2919 verbose = "find_file"
2920 if not callable(verbose):
2921 verbose = ' %s: ' % verbose
2922 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2923 else:
2924 verbose = lambda x: x
2925
2926 filedir, filename = os.path.split(filename)
2927 if filedir:
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958 self.default_filedir = filedir
2959 paths = filter(None, map(self.filedir_lookup, paths))
2960
2961 result = None
2962 for dir in paths:
2963 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2964 node, d = dir.srcdir_find_file(filename)
2965 if node:
2966 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2967 result = node
2968 break
2969
2970 memo_dict[memo_key] = result
2971
2972 return result
2973
2974 find_file = FileFinder().find_file
2975