| Index: mojo/tools/pylib/transitive_hash.py
|
| diff --git a/mojo/tools/pylib/transitive_hash.py b/mojo/tools/pylib/transitive_hash.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..93e8dc4e75ed3f327163afa5b05e9e7a34f7ffa6
|
| --- /dev/null
|
| +++ b/mojo/tools/pylib/transitive_hash.py
|
| @@ -0,0 +1,89 @@
|
| +# Copyright 2014 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +import logging
|
| +import subprocess
|
| +import sys
|
| +
|
| +from hashlib import sha256
|
| +from os.path import basename, realpath
|
| +
|
| +_logging = logging.getLogger()
|
| +
|
| +# Based on/taken from
|
| +# http://code.activestate.com/recipes/578231-probably-the-fastest-memoization-decorator-in-the-/
|
| +# (with cosmetic changes).
|
| +def _memoize(f):
|
| + """Memoization decorator for a function taking a single argument."""
|
| + class Memoize(dict):
|
| + def __missing__(self, key):
|
| + rv = self[key] = f(key)
|
| + return rv
|
| + return Memoize().__getitem__
|
| +
|
| +@_memoize
|
| +def _file_hash(filename):
|
| + """Returns a string representing the hash of the given file."""
|
| + _logging.debug("Hashing %s ...", filename)
|
| + rv = subprocess.check_output(['sha256sum', '-b', filename]).split(None, 1)[0]
|
| + _logging.debug(" => %s", rv)
|
| + return rv
|
| +
|
| +@_memoize
|
| +def _get_dependencies(filename):
|
| + """Returns a list of filenames for files that the given file depends on."""
|
| + _logging.debug("Getting dependencies for %s ...", filename)
|
| + lines = subprocess.check_output(['ldd', filename]).splitlines()
|
| + rv = []
|
| + for line in lines:
|
| + i = line.find('/')
|
| + if i < 0:
|
| + _logging.debug(" => no file found in line: %s", line)
|
| + continue
|
| + rv.append(line[i:].split(None, 1)[0])
|
| + _logging.debug(" => %s", rv)
|
| + return rv
|
| +
|
| +def transitive_hash(filename):
|
| + """Returns a string that represents the "transitive" hash of the given
|
| + file. The transitive hash is a hash of the file and all the shared libraries
|
| + on which it depends (done in an order-independent way)."""
|
| + hashes = set()
|
| + to_hash = [filename]
|
| + while to_hash:
|
| + current_filename = realpath(to_hash.pop())
|
| + current_hash = _file_hash(current_filename)
|
| + if current_hash in hashes:
|
| + _logging.debug("Already seen %s (%s) ...", current_filename, current_hash)
|
| + continue
|
| + _logging.debug("Haven't seen %s (%s) ...", current_filename, current_hash)
|
| + hashes.add(current_hash)
|
| + to_hash.extend(_get_dependencies(current_filename))
|
| + return sha256('|'.join(sorted(hashes))).hexdigest()
|
| +
|
| +def main(argv):
|
| + logging.basicConfig()
|
| + # Uncomment to debug:
|
| + # _logging.setLevel(logging.DEBUG)
|
| +
|
| + if len(argv) < 2:
|
| + print """\
|
| +Usage: %s [file] ...
|
| +
|
| +Prints the \"transitive\" hash of each (executable) file. The transitive
|
| +hash is a hash of the file and all the shared libraries on which it
|
| +depends (done in an order-independent way).""" % basename(argv[0])
|
| + return 0
|
| +
|
| + rv = 0
|
| + for filename in argv[1:]:
|
| + try:
|
| + print transitive_hash(filename), filename
|
| + except:
|
| + print "ERROR", filename
|
| + rv = 1
|
| + return rv
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main(sys.argv))
|
|
|