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