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