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 3266 2008/08/12 07:31:01 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 return '"' + self.__str__() + '"'
549
551 """
552 This node, which already existed, is being looked up as the
553 specified klass. Raise an exception if it isn't.
554 """
555 if self.__class__ is klass or klass is Entry:
556 return
557 raise TypeError, "Tried to lookup %s '%s' as a %s." %\
558 (self.__class__.__name__, self.path, klass.__name__)
559
562
565
568
576
577 memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
578
580 try:
581 return self._memo['_save_str']
582 except KeyError:
583 pass
584 result = self._get_str()
585 self._memo['_save_str'] = result
586 return result
587
612
613 rstr = __str__
614
615 memoizer_counters.append(SCons.Memoize.CountValue('stat'))
616
618 try: return self._memo['stat']
619 except KeyError: pass
620 try: result = self.fs.stat(self.abspath)
621 except os.error: result = None
622 self._memo['stat'] = result
623 return result
624
626 return not self.stat() is None
627
630
632 st = self.stat()
633 if st: return st[stat.ST_MTIME]
634 else: return None
635
637 st = self.stat()
638 if st: return st[stat.ST_SIZE]
639 else: return None
640
642 st = self.stat()
643 return not st is None and stat.S_ISDIR(st[stat.ST_MODE])
644
646 st = self.stat()
647 return not st is None and stat.S_ISREG(st[stat.ST_MODE])
648
649 if hasattr(os, 'symlink'):
651 try: st = self.fs.lstat(self.abspath)
652 except os.error: return 0
653 return stat.S_ISLNK(st[stat.ST_MODE])
654 else:
657
659 if self is dir:
660 return 1
661 else:
662 return self.dir.is_under(dir)
663
666
678
680 """Return path relative to the current working directory of the
681 Node.FS.Base object that owns us."""
682 if not dir:
683 dir = self.fs.getcwd()
684 if self == dir:
685 return '.'
686 path_elems = self.path_elements
687 try: i = path_elems.index(dir)
688 except ValueError: pass
689 else: path_elems = path_elems[i+1:]
690 path_elems = map(lambda n: n.name, path_elems)
691 return string.join(path_elems, os.sep)
692
694 """Set the source code builder for this node."""
695 self.sbuilder = builder
696 if not self.has_builder():
697 self.builder_set(builder)
698
700 """Fetch the source code builder for this node.
701
702 If there isn't one, we cache the source code builder specified
703 for the directory (which in turn will cache the value from its
704 parent directory, and so on up to the file system root).
705 """
706 try:
707 scb = self.sbuilder
708 except AttributeError:
709 scb = self.dir.src_builder()
710 self.sbuilder = scb
711 return scb
712
714 """Get the absolute path of the file."""
715 return self.abspath
716
718
719
720
721 return self.name
722
724 try:
725 return self._proxy
726 except AttributeError:
727 ret = EntryProxy(self)
728 self._proxy = ret
729 return ret
730
732 """
733
734 Generates a target entry that corresponds to this entry (usually
735 a source file) with the specified prefix and suffix.
736
737 Note that this method can be overridden dynamically for generated
738 files that need different behavior. See Tool/swig.py for
739 an example.
740 """
741 return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
742
745
746 memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
747
749 """
750 Return all of the directories for a given path list, including
751 corresponding "backing" directories in any repositories.
752
753 The Node lookups are relative to this Node (typically a
754 directory), so memoizing result saves cycles from looking
755 up the same path for each target in a given directory.
756 """
757 try:
758 memo_dict = self._memo['Rfindalldirs']
759 except KeyError:
760 memo_dict = {}
761 self._memo['Rfindalldirs'] = memo_dict
762 else:
763 try:
764 return memo_dict[pathlist]
765 except KeyError:
766 pass
767
768 create_dir_relative_to_self = self.Dir
769 result = []
770 for path in pathlist:
771 if isinstance(path, SCons.Node.Node):
772 result.append(path)
773 else:
774 dir = create_dir_relative_to_self(path)
775 result.extend(dir.get_all_rdirs())
776
777 memo_dict[pathlist] = result
778
779 return result
780
781 - def RDirs(self, pathlist):
782 """Search for a list of directories in the Repository list."""
783 cwd = self.cwd or self.fs._cwd
784 return cwd.Rfindalldirs(pathlist)
785
786 memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
787
789 try:
790 return self._memo['rentry']
791 except KeyError:
792 pass
793 result = self
794 if not self.exists():
795 norm_name = _my_normcase(self.name)
796 for dir in self.dir.get_all_rdirs():
797 try:
798 node = dir.entries[norm_name]
799 except KeyError:
800 if dir.entry_exists_on_disk(self.name):
801 result = dir.Entry(self.name)
802 break
803 self._memo['rentry'] = result
804 return result
805
808
810 """This is the class for generic Node.FS entries--that is, things
811 that could be a File or a Dir, but we're just not sure yet.
812 Consequently, the methods in this class really exist just to
813 transform their associated object into the right class when the
814 time comes, and then call the same-named method in the transformed
815 class."""
816
817 - def diskcheck_match(self):
819
820 - def disambiguate(self, must_exist=None):
821 """
822 """
823 if self.isdir():
824 self.__class__ = Dir
825 self._morph()
826 elif self.isfile():
827 self.__class__ = File
828 self._morph()
829 self.clear()
830 else:
831
832
833
834
835
836
837
838
839
840 srcdir = self.dir.srcnode()
841 if srcdir != self.dir and \
842 srcdir.entry_exists_on_disk(self.name) and \
843 self.srcnode().isdir():
844 self.__class__ = Dir
845 self._morph()
846 elif must_exist:
847 msg = "No such file or directory: '%s'" % self.abspath
848 raise SCons.Errors.UserError, msg
849 else:
850 self.__class__ = File
851 self._morph()
852 self.clear()
853 return self
854
856 """We're a generic Entry, but the caller is actually looking for
857 a File at this point, so morph into one."""
858 self.__class__ = File
859 self._morph()
860 self.clear()
861 return File.rfile(self)
862
863 - def scanner_key(self):
864 return self.get_suffix()
865
866 - def get_contents(self):
867 """Fetch the contents of the entry.
868
869 Since this should return the real contents from the file
870 system, we check to see into what sort of subclass we should
871 morph this Entry."""
872 try:
873 self = self.disambiguate(must_exist=1)
874 except SCons.Errors.UserError:
875
876
877
878
879
880 return ''
881 else:
882 return self.get_contents()
883
884 - def must_be_same(self, klass):
885 """Called to make sure a Node is a Dir. Since we're an
886 Entry, we can morph into one."""
887 if not self.__class__ is klass:
888 self.__class__ = klass
889 self._morph()
890 self.clear
891
892
893
894
895
896
897
898
899
900
901
902
904 """Return if the Entry exists. Check the file system to see
905 what we should turn into first. Assume a file if there's no
906 directory."""
907 return self.disambiguate().exists()
908
909 - def rel_path(self, other):
910 d = self.disambiguate()
911 if d.__class__ == Entry:
912 raise "rel_path() could not disambiguate File/Dir"
913 return d.rel_path(other)
914
915 - def new_ninfo(self):
916 return self.disambiguate().new_ninfo()
917
918 - def changed_since_last_build(self, target, prev_ni):
919 return self.disambiguate().changed_since_last_build(target, prev_ni)
920
921 - def _glob1(self, pattern, ondisk=True, source=False, strings=False):
922 return self.disambiguate()._glob1(pattern, ondisk, source, strings)
923
924
925
926 _classEntry = Entry
927
928
930
931 if SCons.Memoize.use_memoizer:
932 __metaclass__ = SCons.Memoize.Memoized_Metaclass
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950 - def chmod(self, path, mode):
952 - def copy(self, src, dst):
953 return shutil.copy(src, dst)
954 - def copy2(self, src, dst):
955 return shutil.copy2(src, dst)
966 - def link(self, src, dst):
978 - def stat(self, path):
982 - def open(self, path):
986
987 if hasattr(os, 'symlink'):
990 else:
993
994 if hasattr(os, 'readlink'):
997 else:
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1012
1013 memoizer_counters = []
1014
1016 """Initialize the Node.FS subsystem.
1017
1018 The supplied path is the top of the source tree, where we
1019 expect to find the top-level build file. If no path is
1020 supplied, the current directory is the default.
1021
1022 The path argument must be a valid absolute path.
1023 """
1024 if __debug__: logInstanceCreation(self, 'Node.FS')
1025
1026 self._memo = {}
1027
1028 self.Root = {}
1029 self.SConstruct_dir = None
1030 self.max_drift = default_max_drift
1031
1032 self.Top = None
1033 if path is None:
1034 self.pathTop = os.getcwd()
1035 else:
1036 self.pathTop = path
1037 self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0])
1038
1039 self.Top = self.Dir(self.pathTop)
1040 self.Top.path = '.'
1041 self.Top.tpath = '.'
1042 self._cwd = self.Top
1043
1044 DirNodeInfo.fs = self
1045 FileNodeInfo.fs = self
1046
1048 self.SConstruct_dir = dir
1049
1051 return self.max_drift
1052
1054 self.max_drift = max_drift
1055
1058
1059 - def chdir(self, dir, change_os_dir=0):
1060 """Change the current working directory for lookups.
1061 If change_os_dir is true, we will also change the "real" cwd
1062 to match.
1063 """
1064 curr=self._cwd
1065 try:
1066 if not dir is None:
1067 self._cwd = dir
1068 if change_os_dir:
1069 os.chdir(dir.abspath)
1070 except OSError:
1071 self._cwd = curr
1072 raise
1073
1075 """
1076 Returns the root directory for the specified drive, creating
1077 it if necessary.
1078 """
1079 drive = _my_normcase(drive)
1080 try:
1081 return self.Root[drive]
1082 except KeyError:
1083 root = RootDir(drive, self)
1084 self.Root[drive] = root
1085 if not drive:
1086 self.Root[self.defaultDrive] = root
1087 elif drive == self.defaultDrive:
1088 self.Root[''] = root
1089 return root
1090
1091 - def _lookup(self, p, directory, fsclass, create=1):
1092 """
1093 The generic entry point for Node lookup with user-supplied data.
1094
1095 This translates arbitrary input into a canonical Node.FS object
1096 of the specified fsclass. The general approach for strings is
1097 to turn it into a fully normalized absolute path and then call
1098 the root directory's lookup_abs() method for the heavy lifting.
1099
1100 If the path name begins with '#', it is unconditionally
1101 interpreted relative to the top-level directory of this FS. '#'
1102 is treated as a synonym for the top-level SConstruct directory,
1103 much like '~' is treated as a synonym for the user's home
1104 directory in a UNIX shell. So both '#foo' and '#/foo' refer
1105 to the 'foo' subdirectory underneath the top-level SConstruct
1106 directory.
1107
1108 If the path name is relative, then the path is looked up relative
1109 to the specified directory, or the current directory (self._cwd,
1110 typically the SConscript directory) if the specified directory
1111 is None.
1112 """
1113 if isinstance(p, Base):
1114
1115
1116 p.must_be_same(fsclass)
1117 return p
1118
1119 p = str(p)
1120
1121 initial_hash = (p[0:1] == '#')
1122 if initial_hash:
1123
1124
1125
1126 p = p[1:]
1127 directory = self.Top
1128
1129 if directory and not isinstance(directory, Dir):
1130 directory = self.Dir(directory)
1131
1132 if do_splitdrive:
1133 drive, p = os.path.splitdrive(p)
1134 else:
1135 drive = ''
1136 if drive and not p:
1137
1138
1139 p = os.sep
1140 absolute = os.path.isabs(p)
1141
1142 needs_normpath = needs_normpath_check.match(p)
1143
1144 if initial_hash or not absolute:
1145
1146
1147
1148
1149
1150 if not directory:
1151 directory = self._cwd
1152 if p:
1153 p = directory.labspath + '/' + p
1154 else:
1155 p = directory.labspath
1156
1157 if needs_normpath:
1158 p = os.path.normpath(p)
1159
1160 if drive or absolute:
1161 root = self.get_root(drive)
1162 else:
1163 if not directory:
1164 directory = self._cwd
1165 root = directory.root
1166
1167 if os.sep != '/':
1168 p = string.replace(p, os.sep, '/')
1169 return root._lookup_abs(p, fsclass, create)
1170
1171 - def Entry(self, name, directory = None, create = 1):
1172 """Lookup or create a generic Entry node with the specified name.
1173 If the name is a relative path (begins with ./, ../, or a file
1174 name), then it is looked up relative to the supplied directory
1175 node, or to the top level directory of the FS (supplied at
1176 construction time) if no directory is supplied.
1177 """
1178 return self._lookup(name, directory, Entry, create)
1179
1180 - def File(self, name, directory = None, create = 1):
1181 """Lookup or create a File node with the specified name. If
1182 the name is a relative path (begins with ./, ../, or a file name),
1183 then it is looked up relative to the supplied directory node,
1184 or to the top level directory of the FS (supplied at construction
1185 time) if no directory is supplied.
1186
1187 This method will raise TypeError if a directory is found at the
1188 specified path.
1189 """
1190 return self._lookup(name, directory, File, create)
1191
1192 - def Dir(self, name, directory = None, create = True):
1193 """Lookup or create a Dir node with the specified name. If
1194 the name is a relative path (begins with ./, ../, or a file name),
1195 then it is looked up relative to the supplied directory node,
1196 or to the top level directory of the FS (supplied at construction
1197 time) if no directory is supplied.
1198
1199 This method will raise TypeError if a normal file is found at the
1200 specified path.
1201 """
1202 return self._lookup(name, directory, Dir, create)
1203
1204 - def VariantDir(self, variant_dir, src_dir, duplicate=1):
1205 """Link the supplied variant directory to the source directory
1206 for purposes of building files."""
1207
1208 if not isinstance(src_dir, SCons.Node.Node):
1209 src_dir = self.Dir(src_dir)
1210 if not isinstance(variant_dir, SCons.Node.Node):
1211 variant_dir = self.Dir(variant_dir)
1212 if src_dir.is_under(variant_dir):
1213 raise SCons.Errors.UserError, "Source directory cannot be under variant directory."
1214 if variant_dir.srcdir:
1215 if variant_dir.srcdir == src_dir:
1216 return
1217 raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir)
1218 variant_dir.link(src_dir, duplicate)
1219
1226
1228 """Create targets in corresponding variant directories
1229
1230 Climb the directory tree, and look up path names
1231 relative to any linked variant directories we find.
1232
1233 Even though this loops and walks up the tree, we don't memoize
1234 the return value because this is really only used to process
1235 the command-line targets.
1236 """
1237 targets = []
1238 message = None
1239 fmt = "building associated VariantDir targets: %s"
1240 start_dir = dir
1241 while dir:
1242 for bd in dir.variant_dirs:
1243 if start_dir.is_under(bd):
1244
1245 return [orig], fmt % str(orig)
1246 p = apply(os.path.join, [bd.path] + tail)
1247 targets.append(self.Entry(p))
1248 tail = [dir.name] + tail
1249 dir = dir.up()
1250 if targets:
1251 message = fmt % string.join(map(str, targets))
1252 return targets, message
1253
1255 """
1256 Globs
1257
1258 This is mainly a shim layer
1259 """
1260 if cwd is None:
1261 cwd = self.getcwd()
1262 return cwd.glob(pathname, ondisk, source, strings)
1263
1280
1283
1284 glob_magic_check = re.compile('[*?[]')
1285
1288
1290 """A class for directories in a file system.
1291 """
1292
1293 memoizer_counters = []
1294
1295 NodeInfo = DirNodeInfo
1296 BuildInfo = DirBuildInfo
1297
1298 - def __init__(self, name, directory, fs):
1302
1304 """Turn a file system Node (either a freshly initialized directory
1305 object or a separate Entry object) into a proper directory object.
1306
1307 Set up this directory's entries and hook it into the file
1308 system tree. Specify that directories (this Node) don't use
1309 signatures for calculating whether they're current.
1310 """
1311
1312 self.repositories = []
1313 self.srcdir = None
1314
1315 self.entries = {}
1316 self.entries['.'] = self
1317 self.entries['..'] = self.dir
1318 self.cwd = self
1319 self.searched = 0
1320 self._sconsign = None
1321 self.variant_dirs = []
1322 self.root = self.dir.root
1323
1324
1325
1326
1327 self.builder = get_MkdirBuilder()
1328 self.get_executor().set_action_list(self.builder.action)
1329
1333
1335 """Called when we change the repository(ies) for a directory.
1336 This clears any cached information that is invalidated by changing
1337 the repository."""
1338
1339 for node in self.entries.values():
1340 if node != self.dir:
1341 if node != self and isinstance(node, Dir):
1342 node.__clearRepositoryCache(duplicate)
1343 else:
1344 node.clear()
1345 try:
1346 del node._srcreps
1347 except AttributeError:
1348 pass
1349 if duplicate != None:
1350 node.duplicate=duplicate
1351
1353 if node != self:
1354 node.duplicate = node.get_dir().duplicate
1355
1356 - def Entry(self, name):
1357 """
1358 Looks up or creates an entry node named 'name' relative to
1359 this directory.
1360 """
1361 return self.fs.Entry(name, self)
1362
1363 - def Dir(self, name, create=True):
1364 """
1365 Looks up or creates a directory node named 'name' relative to
1366 this directory.
1367 """
1368 dir = self.fs.Dir(name, self, create)
1369 return dir
1370
1371 - def File(self, name):
1372 """
1373 Looks up or creates a file node named 'name' relative to
1374 this directory.
1375 """
1376 return self.fs.File(name, self)
1377
1379 """
1380 Looks up a *normalized* relative path name, relative to this
1381 directory.
1382
1383 This method is intended for use by internal lookups with
1384 already-normalized path data. For general-purpose lookups,
1385 use the Entry(), Dir() and File() methods above.
1386
1387 This method does *no* input checking and will die or give
1388 incorrect results if it's passed a non-normalized path name (e.g.,
1389 a path containing '..'), an absolute path name, a top-relative
1390 ('#foo') path name, or any kind of object.
1391 """
1392 name = self.entry_labspath(name)
1393 return self.root._lookup_abs(name, klass, create)
1394
1395 - def link(self, srcdir, duplicate):
1396 """Set this directory as the variant directory for the
1397 supplied source directory."""
1398 self.srcdir = srcdir
1399 self.duplicate = duplicate
1400 self.__clearRepositoryCache(duplicate)
1401 srcdir.variant_dirs.append(self)
1402
1404 """Returns a list of repositories for this directory.
1405 """
1406 if self.srcdir and not self.duplicate:
1407 return self.srcdir.get_all_rdirs() + self.repositories
1408 return self.repositories
1409
1410 memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
1411
1413 try:
1414 return self._memo['get_all_rdirs']
1415 except KeyError:
1416 pass
1417
1418 result = [self]
1419 fname = '.'
1420 dir = self
1421 while dir:
1422 for rep in dir.getRepositories():
1423 result.append(rep.Dir(fname))
1424 if fname == '.':
1425 fname = dir.name
1426 else:
1427 fname = dir.name + os.sep + fname
1428 dir = dir.up()
1429
1430 self._memo['get_all_rdirs'] = result
1431
1432 return result
1433
1435 if dir != self and not dir in self.repositories:
1436 self.repositories.append(dir)
1437 dir.tpath = '.'
1438 self.__clearRepositoryCache()
1439
1441 return self.entries['..']
1442
1445
1446 memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
1447
1449 """Return a path to "other" relative to this directory.
1450 """
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462 try:
1463 memo_dict = self._memo['rel_path']
1464 except KeyError:
1465 memo_dict = {}
1466 self._memo['rel_path'] = memo_dict
1467 else:
1468 try:
1469 return memo_dict[other]
1470 except KeyError:
1471 pass
1472
1473 if self is other:
1474
1475 result = '.'
1476
1477 elif not other in self.path_elements:
1478
1479 try:
1480 other_dir = other.get_dir()
1481 except AttributeError:
1482 result = str(other)
1483 else:
1484 if other_dir is None:
1485 result = other.name
1486 else:
1487 dir_rel_path = self.rel_path(other_dir)
1488 if dir_rel_path == '.':
1489 result = other.name
1490 else:
1491 result = dir_rel_path + os.sep + other.name
1492
1493 else:
1494
1495 i = self.path_elements.index(other) + 1
1496
1497 path_elems = ['..'] * (len(self.path_elements) - i) \
1498 + map(lambda n: n.name, other.path_elements[i:])
1499
1500 result = string.join(path_elems, os.sep)
1501
1502 memo_dict[other] = result
1503
1504 return result
1505
1509
1513
1515 """Return this directory's implicit dependencies.
1516
1517 We don't bother caching the results because the scan typically
1518 shouldn't be requested more than once (as opposed to scanning
1519 .h file contents, which can be requested as many times as the
1520 files is #included by other files).
1521 """
1522 if not scanner:
1523 return []
1524
1525
1526
1527
1528
1529
1530
1531
1532 self.clear()
1533 return scanner(self, env, path)
1534
1535
1536
1537
1538
1541
1547
1548
1549
1550
1551
1553 """Create this directory, silently and without worrying about
1554 whether the builder is the default or not."""
1555 listDirs = []
1556 parent = self
1557 while parent:
1558 if parent.exists():
1559 break
1560 listDirs.append(parent)
1561 p = parent.up()
1562 if p is None:
1563 raise SCons.Errors.StopError, parent.path
1564 parent = p
1565 listDirs.reverse()
1566 for dirnode in listDirs:
1567 try:
1568
1569
1570
1571
1572 SCons.Node.Node.build(dirnode)
1573 dirnode.get_executor().nullify()
1574
1575
1576
1577
1578 dirnode.clear()
1579 except OSError:
1580 pass
1581
1585
1587 """Return any corresponding targets in a variant directory.
1588 """
1589 return self.fs.variant_dir_target_climb(self, self, [])
1590
1592 """A directory does not get scanned."""
1593 return None
1594
1595 - def get_contents(self):
1596 """Return aggregate contents of all our children."""
1597 contents = map(lambda n: n.get_contents(), self.children())
1598 return string.join(contents, '')
1599
1602
1603 changed_since_last_build = SCons.Node.Node.state_has_changed
1604
1615
1617 if not self.exists():
1618 norm_name = _my_normcase(self.name)
1619 for dir in self.dir.get_all_rdirs():
1620 try: node = dir.entries[norm_name]
1621 except KeyError: node = dir.dir_on_disk(self.name)
1622 if node and node.exists() and \
1623 (isinstance(dir, Dir) or isinstance(dir, Entry)):
1624 return node
1625 return self
1626
1628 """Return the .sconsign file info for this directory,
1629 creating it first if necessary."""
1630 if not self._sconsign:
1631 import SCons.SConsign
1632 self._sconsign = SCons.SConsign.ForDirectory(self)
1633 return self._sconsign
1634
1636 """Dir has a special need for srcnode()...if we
1637 have a srcdir attribute set, then that *is* our srcnode."""
1638 if self.srcdir:
1639 return self.srcdir
1640 return Base.srcnode(self)
1641
1643 """Return the latest timestamp from among our children"""
1644 stamp = 0
1645 for kid in self.children():
1646 if kid.get_timestamp() > stamp:
1647 stamp = kid.get_timestamp()
1648 return stamp
1649
1650 - def entry_abspath(self, name):
1651 return self.abspath + os.sep + name
1652
1653 - def entry_labspath(self, name):
1654 return self.labspath + '/' + name
1655
1656 - def entry_path(self, name):
1657 return self.path + os.sep + name
1658
1659 - def entry_tpath(self, name):
1660 return self.tpath + os.sep + name
1661
1662 - def entry_exists_on_disk(self, name):
1663 try:
1664 d = self.on_disk_entries
1665 except AttributeError:
1666 d = {}
1667 try:
1668 entries = os.listdir(self.abspath)
1669 except OSError:
1670 pass
1671 else:
1672 for entry in map(_my_normcase, entries):
1673 d[entry] = 1
1674 self.on_disk_entries = d
1675 return d.has_key(_my_normcase(name))
1676
1677 memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
1678
1680 try:
1681 return self._memo['srcdir_list']
1682 except KeyError:
1683 pass
1684
1685 result = []
1686
1687 dirname = '.'
1688 dir = self
1689 while dir:
1690 if dir.srcdir:
1691 result.append(dir.srcdir.Dir(dirname))
1692 dirname = dir.name + os.sep + dirname
1693 dir = dir.up()
1694
1695 self._memo['srcdir_list'] = result
1696
1697 return result
1698
1715
1718
1719 memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
1720
1722 try:
1723 memo_dict = self._memo['srcdir_find_file']
1724 except KeyError:
1725 memo_dict = {}
1726 self._memo['srcdir_find_file'] = memo_dict
1727 else:
1728 try:
1729 return memo_dict[filename]
1730 except KeyError:
1731 pass
1732
1733 def func(node):
1734 if (isinstance(node, File) or isinstance(node, Entry)) and \
1735 (node.is_derived() or node.exists()):
1736 return node
1737 return None
1738
1739 norm_name = _my_normcase(filename)
1740
1741 for rdir in self.get_all_rdirs():
1742 try: node = rdir.entries[norm_name]
1743 except KeyError: node = rdir.file_on_disk(filename)
1744 else: node = func(node)
1745 if node:
1746 result = (node, self)
1747 memo_dict[filename] = result
1748 return result
1749
1750 for srcdir in self.srcdir_list():
1751 for rdir in srcdir.get_all_rdirs():
1752 try: node = rdir.entries[norm_name]
1753 except KeyError: node = rdir.file_on_disk(filename)
1754 else: node = func(node)
1755 if node:
1756 result = (File(filename, self, self.fs), srcdir)
1757 memo_dict[filename] = result
1758 return result
1759
1760 result = (None, None)
1761 memo_dict[filename] = result
1762 return result
1763
1769
1780
1781 - def walk(self, func, arg):
1782 """
1783 Walk this directory tree by calling the specified function
1784 for each directory in the tree.
1785
1786 This behaves like the os.path.walk() function, but for in-memory
1787 Node.FS.Dir objects. The function takes the same arguments as
1788 the functions passed to os.path.walk():
1789
1790 func(arg, dirname, fnames)
1791
1792 Except that "dirname" will actually be the directory *Node*,
1793 not the string. The '.' and '..' entries are excluded from
1794 fnames. The fnames list may be modified in-place to filter the
1795 subdirectories visited or otherwise impose a specific order.
1796 The "arg" argument is always passed to func() and may be used
1797 in any way (or ignored, passing None is common).
1798 """
1799 entries = self.entries
1800 names = entries.keys()
1801 names.remove('.')
1802 names.remove('..')
1803 func(arg, self, names)
1804 select_dirs = lambda n, e=entries: isinstance(e[n], Dir)
1805 for dirname in filter(select_dirs, names):
1806 entries[dirname].walk(func, arg)
1807
1809 """
1810 Returns a list of Nodes (or strings) matching a specified
1811 pathname pattern.
1812
1813 Pathname patterns follow UNIX shell semantics: * matches
1814 any-length strings of any characters, ? matches any character,
1815 and [] can enclose lists or ranges of characters. Matches do
1816 not span directory separators.
1817
1818 The matches take into account Repositories, returning local
1819 Nodes if a corresponding entry exists in a Repository (either
1820 an in-memory Node or something on disk).
1821
1822 By defafult, the glob() function matches entries that exist
1823 on-disk, in addition to in-memory Nodes. Setting the "ondisk"
1824 argument to False (or some other non-true value) causes the glob()
1825 function to only match in-memory Nodes. The default behavior is
1826 to return both the on-disk and in-memory Nodes.
1827
1828 The "source" argument, when true, specifies that corresponding
1829 source Nodes must be returned if you're globbing in a build
1830 directory (initialized with VariantDir()). The default behavior
1831 is to return Nodes local to the VariantDir().
1832
1833 The "strings" argument, when true, returns the matches as strings,
1834 not Nodes. The strings are path names relative to this directory.
1835
1836 The underlying algorithm is adapted from the glob.glob() function
1837 in the Python library (but heavily modified), and uses fnmatch()
1838 under the covers.
1839 """
1840 dirname, basename = os.path.split(pathname)
1841 if not dirname:
1842 return self._glob1(basename, ondisk, source, strings)
1843 if has_glob_magic(dirname):
1844 list = self.glob(dirname, ondisk, source, strings=False)
1845 else:
1846 list = [self.Dir(dirname, create=True)]
1847 result = []
1848 for dir in list:
1849 r = dir._glob1(basename, ondisk, source, strings)
1850 if strings:
1851 r = map(lambda x, d=str(dir): os.path.join(d, x), r)
1852 result.extend(r)
1853 return result
1854
1856 """
1857 Globs for and returns a list of entry names matching a single
1858 pattern in this directory.
1859
1860 This searches any repositories and source directories for
1861 corresponding entries and returns a Node (or string) relative
1862 to the current directory if an entry is found anywhere.
1863
1864 TODO: handle pattern with no wildcard
1865 """
1866 search_dir_list = self.get_all_rdirs()
1867 for srcdir in self.srcdir_list():
1868 search_dir_list.extend(srcdir.get_all_rdirs())
1869
1870 names = []
1871 for dir in search_dir_list:
1872
1873
1874
1875
1876 entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys())
1877 node_names = map(lambda n, e=dir.entries: e[n].name, entry_names)
1878 names.extend(node_names)
1879 if ondisk:
1880 try:
1881 disk_names = os.listdir(dir.abspath)
1882 except os.error:
1883 pass
1884 else:
1885 names.extend(disk_names)
1886 if not strings:
1887
1888
1889
1890
1891
1892
1893
1894
1895 if pattern[0] != '.':
1896
1897 disk_names = filter(lambda x: x[0] != '.', disk_names)
1898 disk_names = fnmatch.filter(disk_names, pattern)
1899 rep_nodes = map(dir.Entry, disk_names)
1900
1901 rep_nodes = map(lambda n: n.disambiguate(), rep_nodes)
1902 for node, name in izip(rep_nodes, disk_names):
1903 n = self.Entry(name)
1904 if n.__class__ != node.__class__:
1905 n.__class__ = node.__class__
1906 n._morph()
1907
1908 names = set(names)
1909 if pattern[0] != '.':
1910
1911 names = filter(lambda x: x[0] != '.', names)
1912 names = fnmatch.filter(names, pattern)
1913
1914 if strings:
1915 return names
1916
1917
1918 return map(lambda n, e=self.entries: e[_my_normcase(n)], names)
1919
1921 """A class for the root directory of a file system.
1922
1923 This is the same as a Dir class, except that the path separator
1924 ('/' or '\\') is actually part of the name, so we don't need to
1925 add a separator when creating the path names of entries within
1926 this directory.
1927 """
1929 if __debug__: logInstanceCreation(self, 'Node.FS.RootDir')
1930
1931
1932
1933 self.abspath = ''
1934 self.labspath = ''
1935 self.path = ''
1936 self.tpath = ''
1937 self.path_elements = []
1938 self.duplicate = 0
1939 self.root = self
1940 Base.__init__(self, name, self, fs)
1941
1942
1943
1944
1945
1946 self.abspath = name + os.sep
1947 self.labspath = ''
1948 self.path = name + os.sep
1949 self.tpath = name + os.sep
1950 self._morph()
1951
1952 self._lookupDict = {}
1953
1954
1955
1956
1957
1958 self._lookupDict[''] = self
1959 self._lookupDict['/'] = self
1960 self._lookupDict['//'] = self
1961 self._lookupDict[os.sep] = self
1962 self._lookupDict[os.sep + os.sep] = self
1963
1968
1970 """
1971 Fast (?) lookup of a *normalized* absolute path.
1972
1973 This method is intended for use by internal lookups with
1974 already-normalized path data. For general-purpose lookups,
1975 use the FS.Entry(), FS.Dir() or FS.File() methods.
1976
1977 The caller is responsible for making sure we're passed a
1978 normalized absolute path; we merely let Python's dictionary look
1979 up and return the One True Node.FS object for the path.
1980
1981 If no Node for the specified "p" doesn't already exist, and
1982 "create" is specified, the Node may be created after recursive
1983 invocation to find or create the parent directory or directories.
1984 """
1985 k = _my_normcase(p)
1986 try:
1987 result = self._lookupDict[k]
1988 except KeyError:
1989 if not create:
1990 raise SCons.Errors.UserError
1991
1992
1993 dir_name, file_name = os.path.split(p)
1994 dir_node = self._lookup_abs(dir_name, Dir)
1995 result = klass(file_name, dir_node, self.fs)
1996 self._lookupDict[k] = result
1997 dir_node.entries[_my_normcase(file_name)] = result
1998 dir_node.implicit = None
1999
2000
2001
2002 result.diskcheck_match()
2003 else:
2004
2005
2006 result.must_be_same(klass)
2007 return result
2008
2011
2012 - def entry_abspath(self, name):
2013 return self.abspath + name
2014
2015 - def entry_labspath(self, name):
2017
2018 - def entry_path(self, name):
2019 return self.path + name
2020
2021 - def entry_tpath(self, name):
2022 return self.tpath + name
2023
2025 if self is dir:
2026 return 1
2027 else:
2028 return 0
2029
2032
2035
2038
2057
2059 current_version_id = 1
2060
2062 """
2063 Converts this FileBuildInfo object for writing to a .sconsign file
2064
2065 This replaces each Node in our various dependency lists with its
2066 usual string representation: relative to the top-level SConstruct
2067 directory, or an absolute path if it's outside.
2068 """
2069 if os.sep == '/':
2070 node_to_str = str
2071 else:
2072 def node_to_str(n):
2073 try:
2074 s = n.path
2075 except AttributeError:
2076 s = str(n)
2077 else:
2078 s = string.replace(s, os.sep, '/')
2079 return s
2080 for attr in ['bsources', 'bdepends', 'bimplicit']:
2081 try:
2082 val = getattr(self, attr)
2083 except AttributeError:
2084 pass
2085 else:
2086 setattr(self, attr, map(node_to_str, val))
2088 """
2089 Converts a newly-read FileBuildInfo object for in-SCons use
2090
2091 For normal up-to-date checking, we don't have any conversion to
2092 perform--but we're leaving this method here to make that clear.
2093 """
2094 pass
2096 """
2097 Prepares a FileBuildInfo object for explaining what changed
2098
2099 The bsources, bdepends and bimplicit lists have all been
2100 stored on disk as paths relative to the top-level SConstruct
2101 directory. Convert the strings to actual Nodes (for use by the
2102 --debug=explain code and --implicit-cache).
2103 """
2104 attrs = [
2105 ('bsources', 'bsourcesigs'),
2106 ('bdepends', 'bdependsigs'),
2107 ('bimplicit', 'bimplicitsigs'),
2108 ]
2109 for (nattr, sattr) in attrs:
2110 try:
2111 strings = getattr(self, nattr)
2112 nodeinfos = getattr(self, sattr)
2113 except AttributeError:
2114 pass
2115 else:
2116 nodes = []
2117 for s, ni in izip(strings, nodeinfos):
2118 if not isinstance(s, SCons.Node.Node):
2119 s = ni.str_to_node(s)
2120 nodes.append(s)
2121 setattr(self, nattr, nodes)
2131
2133 """A class for files in a file system.
2134 """
2135
2136 memoizer_counters = []
2137
2138 NodeInfo = FileNodeInfo
2139 BuildInfo = FileBuildInfo
2140
2144
2145 - def __init__(self, name, directory, fs):
2149
2150 - def Entry(self, name):
2151 """Create an entry node named 'name' relative to
2152 the SConscript directory of this file."""
2153 cwd = self.cwd or self.fs._cwd
2154 return cwd.Entry(name)
2155
2156 - def Dir(self, name, create=True):
2157 """Create a directory node named 'name' relative to
2158 the SConscript directory of this file."""
2159 cwd = self.cwd or self.fs._cwd
2160 return cwd.Dir(name, create)
2161
2162 - def Dirs(self, pathlist):
2163 """Create a list of directories relative to the SConscript
2164 directory of this file."""
2165 return map(lambda p, s=self: s.Dir(p), pathlist)
2166
2167 - def File(self, name):
2168 """Create a file node named 'name' relative to
2169 the SConscript directory of this file."""
2170 cwd = self.cwd or self.fs._cwd
2171 return cwd.File(name)
2172
2173
2174
2175
2176
2177
2178
2179
2181 """Turn a file system node into a File object."""
2182 self.scanner_paths = {}
2183 if not hasattr(self, '_local'):
2184 self._local = 0
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196 if self.has_builder():
2197 self.changed_since_last_build = self.decide_target
2198
2201
2202 - def get_contents(self):
2203 if not self.rexists():
2204 return ''
2205 fname = self.rfile().abspath
2206 try:
2207 r = open(fname, "rb").read()
2208 except EnvironmentError, e:
2209 if not e.filename:
2210 e.filename = fname
2211 raise
2212 return r
2213
2214 memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
2215
2217 try:
2218 return self._memo['get_size']
2219 except KeyError:
2220 pass
2221
2222 if self.rexists():
2223 size = self.rfile().getsize()
2224 else:
2225 size = 0
2226
2227 self._memo['get_size'] = size
2228
2229 return size
2230
2231 memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
2232
2234 try:
2235 return self._memo['get_timestamp']
2236 except KeyError:
2237 pass
2238
2239 if self.rexists():
2240 timestamp = self.rfile().getmtime()
2241 else:
2242 timestamp = 0
2243
2244 self._memo['get_timestamp'] = timestamp
2245
2246 return timestamp
2247
2254
2255 convert_copy_attrs = [
2256 'bsources',
2257 'bimplicit',
2258 'bdepends',
2259 'bact',
2260 'bactsig',
2261 'ninfo',
2262 ]
2263
2264
2265 convert_sig_attrs = [
2266 'bsourcesigs',
2267 'bimplicitsigs',
2268 'bdependsigs',
2269 ]
2270
2271 - def convert_old_entry(self, old_entry):
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
2334
2335
2336
2337
2338
2339 import SCons.SConsign
2340 new_entry = SCons.SConsign.SConsignEntry()
2341 new_entry.binfo = self.new_binfo()
2342 binfo = new_entry.binfo
2343 for attr in self.convert_copy_attrs:
2344 try:
2345 value = getattr(old_entry, attr)
2346 except AttributeError:
2347 pass
2348 else:
2349 setattr(binfo, attr, value)
2350 delattr(old_entry, attr)
2351 for attr in self.convert_sig_attrs:
2352 try:
2353 sig_list = getattr(old_entry, attr)
2354 except AttributeError:
2355 pass
2356 else:
2357 value = []
2358 for sig in sig_list:
2359 ninfo = self.new_ninfo()
2360 if len(sig) == 32:
2361 ninfo.csig = sig
2362 else:
2363 ninfo.timestamp = sig
2364 value.append(ninfo)
2365 setattr(binfo, attr, value)
2366 delattr(old_entry, attr)
2367 return new_entry
2368
2369 memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
2370
2372 try:
2373 return self._memo['get_stored_info']
2374 except KeyError:
2375 pass
2376
2377 try:
2378 sconsign_entry = self.dir.sconsign().get_entry(self.name)
2379 except (KeyError, OSError):
2380 import SCons.SConsign
2381 sconsign_entry = SCons.SConsign.SConsignEntry()
2382 sconsign_entry.binfo = self.new_binfo()
2383 sconsign_entry.ninfo = self.new_ninfo()
2384 else:
2385 if isinstance(sconsign_entry, FileBuildInfo):
2386
2387
2388 sconsign_entry = self.convert_old_entry(sconsign_entry)
2389 try:
2390 delattr(sconsign_entry.ninfo, 'bsig')
2391 except AttributeError:
2392 pass
2393
2394 self._memo['get_stored_info'] = sconsign_entry
2395
2396 return sconsign_entry
2397
2403
2406
2408 return (id(env), id(scanner), path)
2409
2410 memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
2411
2413 """Return the included implicit dependencies in this file.
2414 Cache results so we only scan the file once per path
2415 regardless of how many times this information is requested.
2416 """
2417 memo_key = (id(env), id(scanner), path)
2418 try:
2419 memo_dict = self._memo['get_found_includes']
2420 except KeyError:
2421 memo_dict = {}
2422 self._memo['get_found_includes'] = memo_dict
2423 else:
2424 try:
2425 return memo_dict[memo_key]
2426 except KeyError:
2427 pass
2428
2429 if scanner:
2430 result = scanner(self, env, path)
2431 result = map(lambda N: N.disambiguate(), result)
2432 else:
2433 result = []
2434
2435 memo_dict[memo_key] = result
2436
2437 return result
2438
2443
2445 """Try to retrieve the node's content from a cache
2446
2447 This method is called from multiple threads in a parallel build,
2448 so only do thread safe stuff here. Do thread unsafe stuff in
2449 built().
2450
2451 Returns true iff the node was successfully retrieved.
2452 """
2453 if self.nocache:
2454 return None
2455 if not self.is_derived():
2456 return None
2457 return self.get_build_env().get_CacheDir().retrieve(self)
2458
2474
2497
2517
2519 """Return whether this Node has a source builder or not.
2520
2521 If this Node doesn't have an explicit source code builder, this
2522 is where we figure out, on the fly, if there's a transparent
2523 source code builder for it.
2524
2525 Note that if we found a source builder, we also set the
2526 self.builder attribute, so that all of the methods that actually
2527 *build* this file don't have to do anything different.
2528 """
2529 try:
2530 scb = self.sbuilder
2531 except AttributeError:
2532 scb = self.sbuilder = self.find_src_builder()
2533 return not scb is None
2534
2541
2547
2548
2549
2550
2551
2555
2570
2571
2572
2573
2574
2576 """Remove this file."""
2577 if self.exists() or self.islink():
2578 self.fs.unlink(self.path)
2579 return 1
2580 return None
2581
2595
2596 memoizer_counters.append(SCons.Memoize.CountValue('exists'))
2597
2599 try:
2600 return self._memo['exists']
2601 except KeyError:
2602 pass
2603
2604 if self.duplicate and not self.is_derived() and not self.linked:
2605 src = self.srcnode()
2606 if not src is self:
2607
2608 src = src.rfile()
2609 if src.abspath != self.abspath:
2610 if src.exists():
2611 self.do_duplicate(src)
2612
2613
2614 else:
2615
2616
2617 if Base.exists(self) or self.islink():
2618 self.fs.unlink(self.path)
2619
2620
2621 self._memo['exists'] = None
2622 return None
2623 result = Base.exists(self)
2624 self._memo['exists'] = result
2625 return result
2626
2627
2628
2629
2630
2632 """
2633 Returns the content signature currently stored for this node
2634 if it's been unmodified longer than the max_drift value, or the
2635 max_drift value is 0. Returns None otherwise.
2636 """
2637 old = self.get_stored_info()
2638 mtime = self.get_timestamp()
2639
2640 csig = None
2641 max_drift = self.fs.max_drift
2642 if max_drift > 0:
2643 if (time.time() - mtime) > max_drift:
2644 try:
2645 n = old.ninfo
2646 if n.timestamp and n.csig and n.timestamp == mtime:
2647 csig = n.csig
2648 except AttributeError:
2649 pass
2650 elif max_drift == 0:
2651 try:
2652 csig = old.ninfo.csig
2653 except AttributeError:
2654 pass
2655
2656 return csig
2657
2659 """
2660 Generate a node's content signature, the digested signature
2661 of its content.
2662
2663 node - the node
2664 cache - alternate node to use for the signature cache
2665 returns - the content signature
2666 """
2667 ninfo = self.get_ninfo()
2668 try:
2669 return ninfo.csig
2670 except AttributeError:
2671 pass
2672
2673 csig = self.get_max_drift_csig()
2674 if csig is None:
2675
2676 try:
2677 contents = self.get_contents()
2678 except IOError:
2679
2680
2681
2682
2683 csig = ''
2684 else:
2685 csig = SCons.Util.MD5signature(contents)
2686
2687 ninfo.csig = csig
2688
2689 return csig
2690
2691
2692
2693
2694
2698
2699 - def changed_content(self, target, prev_ni):
2700 cur_csig = self.get_csig()
2701 try:
2702 return cur_csig != prev_ni.csig
2703 except AttributeError:
2704 return 1
2705
2708
2709 - def changed_timestamp_then_content(self, target, prev_ni):
2710 if not self.changed_timestamp_match(target, prev_ni):
2711 try:
2712 self.get_ninfo().csig = prev_ni.csig
2713 except AttributeError:
2714 pass
2715 return False
2716 return self.changed_content(target, prev_ni)
2717
2723
2725 try:
2726 return self.get_timestamp() != prev_ni.timestamp
2727 except AttributeError:
2728 return 1
2729
2732
2735
2736
2737
2738 changed_since_last_build = decide_source
2739
2741 T = 0
2742 if T: Trace('is_up_to_date(%s):' % self)
2743 if not self.exists():
2744 if T: Trace(' not self.exists():')
2745
2746 r = self.rfile()
2747 if r != self:
2748
2749 if not self.changed(r):
2750 if T: Trace(' changed(%s):' % r)
2751
2752 if self._local:
2753
2754 e = LocalCopy(self, r, None)
2755 if isinstance(e, SCons.Errors.BuildError):
2756 raise
2757 self.store_info()
2758 if T: Trace(' 1\n')
2759 return 1
2760 self.changed()
2761 if T: Trace(' None\n')
2762 return None
2763 else:
2764 r = self.changed()
2765 if T: Trace(' self.exists(): %s\n' % r)
2766 return not r
2767
2768 memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
2769
2771 try:
2772 return self._memo['rfile']
2773 except KeyError:
2774 pass
2775 result = self
2776 if not self.exists():
2777 norm_name = _my_normcase(self.name)
2778 for dir in self.dir.get_all_rdirs():
2779 try: node = dir.entries[norm_name]
2780 except KeyError: node = dir.file_on_disk(self.name)
2781 if node and node.exists() and \
2782 (isinstance(node, File) or isinstance(node, Entry) \
2783 or not node.is_derived()):
2784 result = node
2785 break
2786 self._memo['rfile'] = result
2787 return result
2788
2790 return str(self.rfile())
2791
2793 """
2794 Fetch a Node's content signature for purposes of computing
2795 another Node's cachesig.
2796
2797 This is a wrapper around the normal get_csig() method that handles
2798 the somewhat obscure case of using CacheDir with the -n option.
2799 Any files that don't exist would normally be "built" by fetching
2800 them from the cache, but the normal get_csig() method will try
2801 to open up the local file, which doesn't exist because the -n
2802 option meant we didn't actually pull the file from cachedir.
2803 But since the file *does* actually exist in the cachedir, we
2804 can use its contents for the csig.
2805 """
2806 try:
2807 return self.cachedir_csig
2808 except AttributeError:
2809 pass
2810
2811 cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
2812 if not self.exists() and cachefile and os.path.exists(cachefile):
2813 contents = open(cachefile, 'rb').read()
2814 self.cachedir_csig = SCons.Util.MD5signature(contents)
2815 else:
2816 self.cachedir_csig = self.get_csig()
2817 return self.cachedir_csig
2818
2835
2836 default_fs = None
2837
2843
2845 """
2846 """
2847 if SCons.Memoize.use_memoizer:
2848 __metaclass__ = SCons.Memoize.Memoized_Metaclass
2849
2850 memoizer_counters = []
2851
2854
2856 """
2857 A helper method for find_file() that looks up a directory for
2858 a file we're trying to find. This only creates the Dir Node if
2859 it exists on-disk, since if the directory doesn't exist we know
2860 we won't find any files in it... :-)
2861
2862 It would be more compact to just use this as a nested function
2863 with a default keyword argument (see the commented-out version
2864 below), but that doesn't work unless you have nested scopes,
2865 so we define it here just so this work under Python 1.5.2.
2866 """
2867 if fd is None:
2868 fd = self.default_filedir
2869 dir, name = os.path.split(fd)
2870 drive, d = os.path.splitdrive(dir)
2871 if d in ('/', os.sep):
2872 return p.fs.get_root(drive).dir_on_disk(name)
2873 if dir:
2874 p = self.filedir_lookup(p, dir)
2875 if not p:
2876 return None
2877 norm_name = _my_normcase(name)
2878 try:
2879 node = p.entries[norm_name]
2880 except KeyError:
2881 return p.dir_on_disk(name)
2882 if isinstance(node, Dir):
2883 return node
2884 if isinstance(node, Entry):
2885 node.must_be_same(Dir)
2886 return node
2887 return None
2888
2890 return (filename, paths)
2891
2892 memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
2893
2894 - def find_file(self, filename, paths, verbose=None):
2895 """
2896 find_file(str, [Dir()]) -> [nodes]
2897
2898 filename - a filename to find
2899 paths - a list of directory path *nodes* to search in. Can be
2900 represented as a list, a tuple, or a callable that is
2901 called with no arguments and returns the list or tuple.
2902
2903 returns - the node created from the found file.
2904
2905 Find a node corresponding to either a derived file or a file
2906 that exists already.
2907
2908 Only the first file found is returned, and none is returned
2909 if no file is found.
2910 """
2911 memo_key = self._find_file_key(filename, paths)
2912 try:
2913 memo_dict = self._memo['find_file']
2914 except KeyError:
2915 memo_dict = {}
2916 self._memo['find_file'] = memo_dict
2917 else:
2918 try:
2919 return memo_dict[memo_key]
2920 except KeyError:
2921 pass
2922
2923 if verbose:
2924 if not SCons.Util.is_String(verbose):
2925 verbose = "find_file"
2926 if not callable(verbose):
2927 verbose = ' %s: ' % verbose
2928 verbose = lambda s, v=verbose: sys.stdout.write(v + s)
2929 else:
2930 verbose = lambda x: x
2931
2932 filedir, filename = os.path.split(filename)
2933 if filedir:
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
2959
2960
2961
2962
2963
2964 self.default_filedir = filedir
2965 paths = filter(None, map(self.filedir_lookup, paths))
2966
2967 result = None
2968 for dir in paths:
2969 verbose("looking for '%s' in '%s' ...\n" % (filename, dir))
2970 node, d = dir.srcdir_find_file(filename)
2971 if node:
2972 verbose("... FOUND '%s' in '%s'\n" % (filename, d))
2973 result = node
2974 break
2975
2976 memo_dict[memo_key] = result
2977
2978 return result
2979
2980 find_file = FileFinder().find_file
2981