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