Created
September 24, 2019 21:44
-
-
Save elliottwilliams/fdf7730ef06809abeb88299a97d57ffa to your computer and use it in GitHub Desktop.
Hashing a Cartfile dependency's version with the versions of its subdependencies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from hashlib import sha1 | |
from collections import namedtuple | |
def version_hash_for_dependency(dependency, dependency_graph): | |
""" | |
Returns a hex string created by hashing together the versions of | |
`dependency` with the versions of its dependencies and subdependencies as | |
specified in `dependency_graph`. For example, given the dependency graph: | |
A -> B D | |
B -> C | |
C -> | |
D -> | |
E -> A | |
hash(A) = hash(A B C D) | |
hash(B) = hash(B C) | |
hash(C) = hash(C) | |
hash(D) = hash(D) | |
hash(E) = hash(E A B C D) | |
""" | |
def update_hasher_for_dependency(dependency, dependency_graph, hasher): | |
hasher.update(dependency.version_requirement.encode('utf8')) | |
for subdependency in dependency_graph[dependency]: | |
update_hasher_for_dependency(subdependency, dependency_graph, hasher) | |
hasher = sha1() | |
update_hasher_for_dependency(dependency, dependency_graph, hasher) | |
return hasher.hexdigest() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
from collections import defaultdict | |
from collections import deque | |
from collections import namedtuple | |
class Dependency(namedtuple('Dependency', 'source origin version_requirement')): | |
""" | |
A data type that represents a line from a Cartfile. For example: | |
github "antitypical/Result" ~> 4.0.0 | |
is | |
Dependency( | |
source='github', | |
origin='"antitypical/Result"', | |
version_requirement='~> 4.0.0' | |
) | |
""" | |
@property | |
def name(self): | |
""" | |
Returns the checkout name of the dependency by taking the last | |
component of the origin and removing any path extension. | |
""" | |
basename = self.origin.strip('"').split('/')[-1] | |
name = basename.partition('.')[0] | |
return name | |
def pinned_version(self): | |
""" | |
If the version requirement is a pinned version, return the version | |
string. | |
""" | |
match = re.match(r'"(.+)"', self.version_requirement) | |
if match: | |
return match.group(1) | |
def __str__(self): | |
return self.name | |
def read_cartfile(path): | |
""" | |
Given a pathlib.Path to a Cartfile, yield its dependencies. | |
""" | |
lines = path.read_text('utf8').splitlines() | |
yield from ( | |
dependency for dependency in | |
(parse_dependency(line) for line in lines) | |
if dependency is not None | |
) | |
def parse_dependency(line): | |
""" | |
Sanitize a line of text from a Cartfile and return a Dependency | |
representing that line, if the line contains a dependency. | |
""" | |
comment_marker = line.find('#') | |
if comment_marker > -1: | |
line = line[:comment_marker] | |
line = line.strip() | |
if line: | |
source, origin, *version_components = line.split() | |
return Dependency(source, origin, ' '.join(version_components)) | |
def dependencies_for_dependency(dependency, checkouts_dir): | |
""" | |
Yields dependencies from the Cartfile of a given dependency. Assumes the | |
dependency has been checked out if it's not binary. | |
""" | |
checkout = checkouts_dir / dependency.name | |
if dependency.source == 'binary': | |
return # yield nothing | |
assert checkout.exists(), f'{dependency.name} not checked out in {checkouts_dir}' | |
dependency_cartfile = checkout / 'Cartfile' | |
if dependency_cartfile.exists(): | |
yield from read_cartfile(dependency_cartfile) | |
def _dependency_graph_edges(cartfile, checkouts_dir): | |
""" | |
Yields 2-tuples for all of the dependency relationships given by a | |
`cartfile` and any additional Cartfiles contained in `checkouts_dir`. The | |
tuple | |
(Node, Leaf) | |
means that Node dependes on Leaf. | |
""" | |
visited_names = set() | |
q = deque(read_cartfile(cartfile)) | |
while q: | |
dependency = q.popleft() | |
if dependency.name not in visited_names: | |
visited_names.add(dependency.name) | |
for subdependency in dependencies_for_dependency(dependency, checkouts_dir): | |
yield (dependency, subdependency) | |
q.append(subdependency) | |
def dependency_graph_for_cartfile(cartfile, resolved_cartfile, checkouts_dir): | |
""" | |
Returns a dictionary representing the relationships of a given `cartfile` | |
and the Cartfiles contained in `checkouts_dir`. The dictionary maps | |
Dependencies to lists of Dependencies at the pinned versions given in | |
`resolved_cartfile`. | |
""" | |
graph = defaultdict(list) | |
resolved_versions = { | |
dependency.name: dependency | |
for dependency in read_cartfile(resolved_cartfile) | |
} | |
for dependency, subdependency in _dependency_graph_edges(cartfile, checkouts_dir): | |
resolved_dependency = resolved_versions[dependency.name] | |
resolved_subdependency = resolved_versions[subdependency.name] | |
graph[resolved_dependency].append(resolved_subdependency) | |
return graph |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment