1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
9 from hashlib
import sha256
10 from os
.path
import basename
, realpath
12 _logging
= logging
.getLogger()
15 # http://code.activestate.com/recipes/578231-probably-the-fastest-memoization-decorator-in-the-/
16 # (with cosmetic changes).
18 """Memoization decorator for a function taking a single argument."""
20 def __missing__(self
, key
):
21 rv
= self
[key
] = f(key
)
23 return Memoize().__getitem
__
26 def _file_hash(filename
):
27 """Returns a string representing the hash of the given file."""
28 _logging
.debug("Hashing %s ...", filename
)
29 rv
= subprocess
.check_output(['sha256sum', '-b', filename
]).split(None, 1)[0]
30 _logging
.debug(" => %s", rv
)
34 def _get_dependencies(filename
):
35 """Returns a list of filenames for files that the given file depends on."""
36 _logging
.debug("Getting dependencies for %s ...", filename
)
37 lines
= subprocess
.check_output(['ldd', filename
]).splitlines()
42 _logging
.debug(" => no file found in line: %s", line
)
44 rv
.append(line
[i
:].split(None, 1)[0])
45 _logging
.debug(" => %s", rv
)
48 def transitive_hash(filename
):
49 """Returns a string that represents the "transitive" hash of the given
50 file. The transitive hash is a hash of the file and all the shared libraries
51 on which it depends (done in an order-independent way)."""
55 current_filename
= realpath(to_hash
.pop())
56 current_hash
= _file_hash(current_filename
)
57 if current_hash
in hashes
:
58 _logging
.debug("Already seen %s (%s) ...", current_filename
, current_hash
)
60 _logging
.debug("Haven't seen %s (%s) ...", current_filename
, current_hash
)
61 hashes
.add(current_hash
)
62 to_hash
.extend(_get_dependencies(current_filename
))
63 return sha256('|'.join(sorted(hashes
))).hexdigest()
68 # _logging.setLevel(logging.DEBUG)
74 Prints the \"transitive\" hash of each (executable) file. The transitive
75 hash is a hash of the file and all the shared libraries on which it
76 depends (done in an order-independent way).""" % basename(argv
[0])
80 for filename
in argv
[1:]:
82 print transitive_hash(filename
), filename
84 print "ERROR", filename
88 if __name__
== '__main__':
89 sys
.exit(main(sys
.argv
))