Merged revisions 2302-2362,2364-2452 via svnmerge from
[scons.git] / src / script / sconsign.py
1 #! /usr/bin/env python
2 #
3 # SCons - a Software Constructor
4 #
5 # __COPYRIGHT__
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
19 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 #
26
27 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
28
29 __version__ = "__VERSION__"
30
31 __build__ = "__BUILD__"
32
33 __buildsys__ = "__BUILDSYS__"
34
35 __date__ = "__DATE__"
36
37 __developer__ = "__DEVELOPER__"
38
39 import os
40 import os.path
41 import sys
42 import time
43
44 ##############################################################################
45 # BEGIN STANDARD SCons SCRIPT HEADER
46 #
47 # This is the cut-and-paste logic so that a self-contained script can
48 # interoperate correctly with different SCons versions and installation
49 # locations for the engine.  If you modify anything in this section, you
50 # should also change other scripts that use this same header.
51 ##############################################################################
52
53 # Strip the script directory from sys.path() so on case-insensitive
54 # (WIN32) systems Python doesn't think that the "scons" script is the
55 # "SCons" package.  Replace it with our own library directories
56 # (version-specific first, in case they installed by hand there,
57 # followed by generic) so we pick up the right version of the build
58 # engine modules if they're in either directory.
59
60 script_dir = sys.path[0]
61
62 if script_dir in sys.path:
63     sys.path.remove(script_dir)
64
65 libs = []
66
67 if os.environ.has_key("SCONS_LIB_DIR"):
68     libs.append(os.environ["SCONS_LIB_DIR"])
69
70 local = 'scons-local-' + __version__
71 if script_dir:
72     local = os.path.join(script_dir, local)
73 libs.append(os.path.abspath(local))
74
75 scons_version = 'scons-%s' % __version__
76
77 prefs = []
78
79 if sys.platform == 'win32':
80     # sys.prefix is (likely) C:\Python*;
81     # check only C:\Python*.
82     prefs.append(sys.prefix)
83     prefs.append(os.path.join(sys.prefix, 'Lib', 'site-packages'))
84 else:
85     # On other (POSIX) platforms, things are more complicated due to
86     # the variety of path names and library locations.  Try to be smart
87     # about it.
88     if script_dir == 'bin':
89         # script_dir is `pwd`/bin;
90         # check `pwd`/lib/scons*.
91         prefs.append(os.getcwd())
92     else:
93         if script_dir == '.' or script_dir == '':
94             script_dir = os.getcwd()
95         head, tail = os.path.split(script_dir)
96         if tail == "bin":
97             # script_dir is /foo/bin;
98             # check /foo/lib/scons*.
99             prefs.append(head)
100
101     head, tail = os.path.split(sys.prefix)
102     if tail == "usr":
103         # sys.prefix is /foo/usr;
104         # check /foo/usr/lib/scons* first,
105         # then /foo/usr/local/lib/scons*.
106         prefs.append(sys.prefix)
107         prefs.append(os.path.join(sys.prefix, "local"))
108     elif tail == "local":
109         h, t = os.path.split(head)
110         if t == "usr":
111             # sys.prefix is /foo/usr/local;
112             # check /foo/usr/local/lib/scons* first,
113             # then /foo/usr/lib/scons*.
114             prefs.append(sys.prefix)
115             prefs.append(head)
116         else:
117             # sys.prefix is /foo/local;
118             # check only /foo/local/lib/scons*.
119             prefs.append(sys.prefix)
120     else:
121         # sys.prefix is /foo (ends in neither /usr or /local);
122         # check only /foo/lib/scons*.
123         prefs.append(sys.prefix)
124
125     temp = map(lambda x: os.path.join(x, 'lib'), prefs)
126     temp.extend(map(lambda x: os.path.join(x,
127                                            'lib',
128                                            'python' + sys.version[:3],
129                                            'site-packages'),
130                            prefs))
131     prefs = temp
132
133     # Add the parent directory of the current python's library to the
134     # preferences.  On SuSE-91/AMD64, for example, this is /usr/lib64,
135     # not /usr/lib.
136     try:
137         libpath = os.__file__
138     except AttributeError:
139         pass
140     else:
141         while libpath:
142             libpath, tail = os.path.split(libpath)
143             if tail[:6] == "python":
144                 break
145         if libpath:
146             # Python library is in /usr/libfoo/python*;
147             # check /usr/libfoo/scons*.
148             prefs.append(libpath)
149
150 # Look first for 'scons-__version__' in all of our preference libs,
151 # then for 'scons'.
152 libs.extend(map(lambda x: os.path.join(x, scons_version), prefs))
153 libs.extend(map(lambda x: os.path.join(x, 'scons'), prefs))
154
155 sys.path = libs + sys.path
156
157 ##############################################################################
158 # END STANDARD SCons SCRIPT HEADER
159 ##############################################################################
160
161 import cPickle
162 import imp
163 import string
164 import whichdb
165
166 import SCons.SConsign
167
168 def my_whichdb(filename):
169     if filename[-7:] == ".dblite":
170         return "SCons.dblite"
171     try:
172         f = open(filename + ".dblite", "rb")
173         f.close()
174         return "SCons.dblite"
175     except IOError:
176         pass
177     return _orig_whichdb(filename)
178
179 _orig_whichdb = whichdb.whichdb
180 whichdb.whichdb = my_whichdb
181
182 def my_import(mname):
183     if '.' in mname:
184         i = string.rfind(mname, '.')
185         parent = my_import(mname[:i])
186         fp, pathname, description = imp.find_module(mname[i+1:],
187                                                     parent.__path__)
188     else:
189         fp, pathname, description = imp.find_module(mname)
190     return imp.load_module(mname, fp, pathname, description)
191
192 class Flagger:
193     default_value = 1
194     def __setitem__(self, item, value):
195         self.__dict__[item] = value
196         self.default_value = 0
197     def __getitem__(self, item):
198         return self.__dict__.get(item, self.default_value)
199
200 Do_Call = None
201 Print_Directories = []
202 Print_Entries = []
203 Print_Flags = Flagger()
204 Verbose = 0
205 Readable = 0
206
207 def default_mapper(entry, name):
208     try:
209         val = eval("entry."+name)
210     except:
211         val = None
212     return str(val)
213
214 def map_action(entry, name):
215     try:
216         bact = entry.bact
217         bactsig = entry.bactsig
218     except AttributeError:
219         return None
220     return '%s [%s]' % (bactsig, bact)
221
222 def map_timestamp(entry, name):
223     try:
224         timestamp = entry.timestamp
225     except AttributeError:
226         timestamp = None
227     if Readable and timestamp:
228         return "'" + time.ctime(timestamp) + "'"
229     else:
230         return str(timestamp)
231
232 def map_bkids(entry, name):
233     try:
234         bkids = entry.bsources + entry.bdepends + entry.bimplicit
235         bkidsigs = entry.bsourcesigs + entry.bdependsigs + entry.bimplicitsigs
236     except AttributeError:
237         return None
238     result = []
239     for i in xrange(len(bkids)):
240         result.append(nodeinfo_string(bkids[i], bkidsigs[i], "        "))
241     if result == []:
242         return None
243     return string.join(result, "\n        ")
244
245 map_field = {
246     'action'    : map_action,
247     'timestamp' : map_timestamp,
248     'bkids'     : map_bkids,
249 }
250
251 map_name = {
252     'implicit'  : 'bkids',
253 }
254
255 def field(name, entry, verbose=Verbose):
256     if not Print_Flags[name]:
257         return None
258     fieldname = map_name.get(name, name)
259     mapper = map_field.get(fieldname, default_mapper)
260     val = mapper(entry, name)
261     if verbose:
262         val = name + ": " + val
263     return val
264
265 def nodeinfo_raw(name, ninfo, prefix=""):
266     # This just formats the dictionary, which we would normally use str()
267     # to do, except that we want the keys sorted for deterministic output.
268     d = ninfo.__dict__
269     try:
270         keys = ninfo.field_list + ['_version_id']
271     except AttributeError:
272         keys = d.keys()
273         keys.sort()
274     l = []
275     for k in keys:
276         l.append('%s: %s' % (repr(k), repr(d.get(k))))
277     return name + ': {' + string.join(l, ', ') + '}'
278
279 def nodeinfo_cooked(name, ninfo, prefix=""):
280     try:
281         field_list = ninfo.field_list
282     except AttributeError:
283         field_list = []
284     f = lambda x, ni=ninfo, v=Verbose: field(x, ni, v)
285     outlist = [name+':'] + filter(None, map(f, field_list))
286     if Verbose:
287         sep = '\n    ' + prefix
288     else:
289         sep = ' '
290     return string.join(outlist, sep)
291
292 nodeinfo_string = nodeinfo_cooked
293
294 def printfield(name, entry, prefix=""):
295     outlist = field("implicit", entry, 0)
296     if outlist:
297         if Verbose:
298             print "    implicit:"
299         print "        " + outlist
300     outact = field("action", entry, 0)
301     if outact:
302         if Verbose:
303             print "    action: " + outact
304         else:
305             print "        " + outact
306
307 def printentries(entries, location):
308     if Print_Entries:
309         for name in Print_Entries:
310             try:
311                 entry = entries[name]
312             except KeyError:
313                 sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, location))
314             else:
315                 try:
316                     ninfo = entry.ninfo
317                 except AttributeError:
318                     print name + ":"
319                 else:
320                     print nodeinfo_string(name, entry.ninfo)
321                 printfield(name, entry.binfo)
322     else:
323         names = entries.keys()
324         names.sort()
325         for name in names:
326             entry = entries[name]
327             try:
328                 ninfo = entry.ninfo
329             except AttributeError:
330                 print name + ":"
331             else:
332                 print nodeinfo_string(name, entry.ninfo)
333             printfield(name, entry.binfo)
334
335 class Do_SConsignDB:
336     def __init__(self, dbm_name, dbm):
337         self.dbm_name = dbm_name
338         self.dbm = dbm
339
340     def __call__(self, fname):
341         # The *dbm modules stick their own file suffixes on the names
342         # that are passed in.  This is causes us to jump through some
343         # hoops here to be able to allow the user
344         try:
345             # Try opening the specified file name.  Example:
346             #   SPECIFIED                  OPENED BY self.dbm.open()
347             #   ---------                  -------------------------
348             #   .sconsign               => .sconsign.dblite
349             #   .sconsign.dblite        => .sconsign.dblite.dblite
350             db = self.dbm.open(fname, "r")
351         except (IOError, OSError), e:
352             print_e = e
353             try:
354                 # That didn't work, so try opening the base name,
355                 # so that if the actually passed in 'sconsign.dblite'
356                 # (for example), the dbm module will put the suffix back
357                 # on for us and open it anyway.
358                 db = self.dbm.open(os.path.splitext(fname)[0], "r")
359             except (IOError, OSError):
360                 # That didn't work either.  See if the file name
361                 # they specified just exists (independent of the dbm
362                 # suffix-mangling).
363                 try:
364                     open(fname, "r")
365                 except (IOError, OSError), e:
366                     # Nope, that file doesn't even exist, so report that
367                     # fact back.
368                     print_e = e
369                 sys.stderr.write("sconsign: %s\n" % (print_e))
370                 return
371         except KeyboardInterrupt:
372             raise
373         except cPickle.UnpicklingError:
374             sys.stderr.write("sconsign: ignoring invalid `%s' file `%s'\n" % (self.dbm_name, fname))
375             return
376         except Exception, e:
377             sys.stderr.write("sconsign: ignoring invalid `%s' file `%s': %s\n" % (self.dbm_name, fname, e))
378             return
379
380         if Print_Directories:
381             for dir in Print_Directories:
382                 try:
383                     val = db[dir]
384                 except KeyError:
385                     sys.stderr.write("sconsign: no dir `%s' in `%s'\n" % (dir, args[0]))
386                 else:
387                     self.printentries(dir, val)
388         else:
389             keys = db.keys()
390             keys.sort()
391             for dir in keys:
392                 self.printentries(dir, db[dir])
393
394     def printentries(self, dir, val):
395         print '=== ' + dir + ':'
396         printentries(cPickle.loads(val), dir)
397
398 def Do_SConsignDir(name):
399     try:
400         fp = open(name, 'rb')
401     except (IOError, OSError), e:
402         sys.stderr.write("sconsign: %s\n" % (e))
403         return
404     try:
405         sconsign = SCons.SConsign.Dir(fp)
406     except KeyboardInterrupt:
407         raise
408     except cPickle.UnpicklingError:
409         sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % (name))
410         return
411     except Exception, e:
412         sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s': %s\n" % (name, e))
413         return
414     printentries(sconsign.entries, args[0])
415
416 ##############################################################################
417
418 import getopt
419
420 helpstr = """\
421 Usage: sconsign [OPTIONS] FILE [...]
422 Options:
423   -a, --act, --action         Print build action information.
424   -c, --csig                  Print content signature information.
425   -d DIR, --dir=DIR           Print only info about DIR.
426   -e ENTRY, --entry=ENTRY     Print only info about ENTRY.
427   -f FORMAT, --format=FORMAT  FILE is in the specified FORMAT.
428   -h, --help                  Print this message and exit.
429   -i, --implicit              Print implicit dependency information.
430   -r, --readable              Print timestamps in human-readable form.
431   --raw                       Print raw Python object representations.
432   -s, --size                  Print file sizes.
433   -t, --timestamp             Print timestamp information.
434   -v, --verbose               Verbose, describe each field.
435 """
436
437 opts, args = getopt.getopt(sys.argv[1:], "acd:e:f:hirstv",
438                             ['act', 'action',
439                              'csig', 'dir=', 'entry=',
440                              'format=', 'help', 'implicit',
441                              'raw', 'readable',
442                              'size', 'timestamp', 'verbose'])
443
444
445 for o, a in opts:
446     if o in ('-a', '--act', '--action'):
447         Print_Flags['action'] = 1
448     elif o in ('-c', '--csig'):
449         Print_Flags['csig'] = 1
450     elif o in ('-d', '--dir'):
451         Print_Directories.append(a)
452     elif o in ('-e', '--entry'):
453         Print_Entries.append(a)
454     elif o in ('-f', '--format'):
455         Module_Map = {'dblite'   : 'SCons.dblite',
456                       'sconsign' : None}
457         dbm_name = Module_Map.get(a, a)
458         if dbm_name:
459             try:
460                 dbm = my_import(dbm_name)
461             except:
462                 sys.stderr.write("sconsign: illegal file format `%s'\n" % a)
463                 print helpstr
464                 sys.exit(2)
465             Do_Call = Do_SConsignDB(a, dbm)
466         else:
467             Do_Call = Do_SConsignDir
468     elif o in ('-h', '--help'):
469         print helpstr
470         sys.exit(0)
471     elif o in ('-i', '--implicit'):
472         Print_Flags['implicit'] = 1
473     elif o in ('--raw',):
474         nodeinfo_string = nodeinfo_raw
475     elif o in ('-r', '--readable'):
476         Readable = 1
477     elif o in ('-s', '--size'):
478         Print_Flags['size'] = 1
479     elif o in ('-t', '--timestamp'):
480         Print_Flags['timestamp'] = 1
481     elif o in ('-v', '--verbose'):
482         Verbose = 1
483
484 if Do_Call:
485     for a in args:
486         Do_Call(a)
487 else:
488     for a in args:
489         dbm_name = whichdb.whichdb(a)
490         if dbm_name:
491             Map_Module = {'SCons.dblite' : 'dblite'}
492             dbm = my_import(dbm_name)
493             Do_SConsignDB(Map_Module.get(dbm_name, dbm_name), dbm)(a)
494         else:
495             Do_SConsignDir(a)
496
497 sys.exit(0)