1#! /usr/bin/env python
2#
3# SCons - a Software Constructor
4#
5# Copyright (c) 2001 - 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/08/24 12:12:31 garyo"
27
28__version__ = "2.3.3"
29
30__build__ = ""
31
32__buildsys__ = "lubuntu"
33
34__date__ = "2014/08/24 12:12:31"
35
36__developer__ = "garyo"
37
38import os
39import 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
57script_dir = sys.path[0]
58
59if script_dir in sys.path:
60    sys.path.remove(script_dir)
61
62libs = []
63
64if "SCONS_LIB_DIR" in os.environ:
65    libs.append(os.environ["SCONS_LIB_DIR"])
66
67local_version = 'scons-local-' + __version__
68local = 'scons-local'
69if script_dir:
70    local_version = os.path.join(script_dir, local_version)
71    local = os.path.join(script_dir, local)
72libs.append(os.path.abspath(local_version))
73libs.append(os.path.abspath(local))
74
75scons_version = 'scons-%s' % __version__
76
77# preferred order of scons lookup paths
78prefs = []
79
80try:
81    import pkg_resources
82except ImportError:
83    pass
84else:
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
93if 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'))
98else:
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'.
163libs.extend([os.path.join(x, scons_version) for x in prefs])
164libs.extend([os.path.join(x, 'scons') for x in prefs])
165
166sys.path = libs + sys.path
167
168##############################################################################
169# END STANDARD SCons SCRIPT HEADER
170##############################################################################
171
172import SCons.compat   # so pickle will import cPickle instead
173
174import whichdb
175import time
176import pickle
177import imp
178
179import SCons.SConsign
180
181def 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
193whichdb.whichdb = my_whichdb
194
195def 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
205class 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
213Do_Call = None
214Print_Directories = []
215Print_Entries = []
216Print_Flags = Flagger()
217Verbose = 0
218Readable = 0
219
220def default_mapper(entry, name):
221    try:
222        val = eval("entry."+name)
223    except:
224        val = None
225    return str(val)
226
227def 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
235def 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
245def 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
258map_field = {
259    'action'    : map_action,
260    'timestamp' : map_timestamp,
261    'bkids'     : map_bkids,
262}
263
264map_name = {
265    'implicit'  : 'bkids',
266}
267
268def 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
278def 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
293def 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
307nodeinfo_string = nodeinfo_cooked
308
309def 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
322def 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
348class 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
409def 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
429import getopt
430
431helpstr = """\
432Usage: sconsign [OPTIONS] FILE [...]
433Options:
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
448opts, 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
456for 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
495if Do_Call:
496    for a in args:
497        Do_Call(a)
498else:
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
508sys.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:
515