a2c36b8b70af88c631a75e946356c0451157fca7
[scons.git] / src / engine / SCons / Sig / __init__.py
1 """SCons.Sig
2
3 The Signature package for the scons software construction utility.
4
5 """
6
7 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
8
9 import os.path
10 import string
11
12 class SConsignFile:
13     """
14     Encapsulates reading and writing a .sconsign file.
15     """
16
17     def __init__(self, dir, module):
18         """
19         dir - the directory for the file
20         module - the signature module being used
21         """
22         
23         self.path = os.path.join(dir, '.sconsign')
24         self.entries = {}
25                     
26         try:
27             file = open(self.path, 'rt')
28         except:
29             pass
30         else:
31             for line in file.readlines():
32                 filename, rest = map(string.strip, string.split(line, ":"))
33                 time, signature = map(string.strip, string.split(rest, " "))
34                 self.entries[filename] = (int(time), module.from_string(signature))
35
36     def get(self, filename):
37         """
38         Get the signature for a file
39
40         filename - the filename whose signature will be returned
41         returns - (timestamp, signature)
42         """
43         
44         try:
45             return self.entries[filename]
46         except KeyError:
47             return (0, None)
48
49     def set(self, filename, timestamp, signature, module):
50         """
51         Set the signature for a file
52
53         filename - the filename whose signature will be set
54         timestamp - the timestamp
55         signature - the signature
56         module - the signature module being used
57         """
58         self.entries[filename] = (timestamp, module.to_string(signature))
59
60     def write(self):
61         """
62         Write the .sconsign file to disk.
63         """
64         
65         file = open(self.path, 'wt')
66         for item in self.entries.items():
67             file.write("%s: %d %s\n" % (item[0], item[1][0], item[1][1]))
68
69
70 class Calculator:
71     """
72     Encapsulates signature calculations and .sconsign file generating
73     for the build engine.
74     """
75
76     def __init__(self, module):
77         """
78         Initialize the calculator.
79
80         module - the signature module to use for signature calculations
81         """
82         self.module = module
83         self.sig_files = {}
84
85     
86     def collect(self, node, signatures):
87         """
88         Collect the signatures of the node's sources.
89
90         node - the node whose sources will be collected
91         signatures - the dictionary that the signatures will be
92         gathered into.
93         """
94         for source_node in node.sources + node.depends:
95             if not signatures.has_key(source_node):
96                 signature = self.signature(source_node)
97                 signatures[source_node] = signature
98                 self.collect(source_node, signatures)
99
100     def get_sig_file(self, dir):
101         """
102         Get a sconsign file from the cache, or add it to the cache.
103
104         dir - the dir for the sconsign file
105         returns - the sconsign file
106         """
107         if self.sig_files.has_key(dir):
108             return self.sig_files[dir]
109         else:
110             self.sig_files[dir] = SConsignFile(dir, self.module)
111             return self.sig_files[dir]
112
113     def signature(self, node):
114         """
115         Get the signature for a node.
116
117         node - the node
118         returns - the signature or None if the signature could not
119         be computed.
120
121         This method also stores the signature in the node and
122         in the .sconsign file.
123         """
124
125         if node.has_signature():
126             sig = node.get_signature()
127         elif node.derived:
128             signatures = {}
129             self.collect(node, signatures)
130             signatures = filter(lambda x: not x is None, signatures.values())
131             sig = self.module.collect(signatures)
132         else:
133             if not node.exists():
134                 return None
135             
136             # XXX handle nodes that are not under the source root
137             sig = self.module.signature(node)
138
139         node.set_signature(sig)
140
141         dir, filename = os.path.split(node.path)
142         if node.exists():
143             timestamp = node.get_timestamp()
144         else:
145             timestamp = 0
146             
147         self.get_sig_file(dir).set(filename,
148                                    timestamp,
149                                    sig,
150                                    self.module)
151
152         return sig
153
154     def current(self, node):
155         """
156         Check if a node is up to date.
157
158         node - the node whose signature will be checked
159
160         returns - 0 if the signature has changed since the last invocation,
161         and 1 if it hasn't
162         """
163
164         if not node.exists():
165             return 0
166
167         dir, filename = os.path.split(node.path)
168         oldtime, oldsig = self.get_sig_file(dir).get(filename)
169
170         newtime = node.get_timestamp()
171
172         if not node.derived and newtime == oldtime:
173             newsig = oldsig
174         else:
175             newsig = self.signature(node)
176         
177         return newsig == oldsig
178
179     def write(self, nodes):
180         """
181         Write out all of the signature files.
182         
183         nodes - the nodes whose signatures may have changed durring
184         the build
185         """
186
187         # make sure all the signatures have been calculated:
188         for node in nodes:
189             self.signature(node)
190             
191         for sig_file in self.sig_files.values():
192             sig_file.write()
193