worried-painter-31382
09/06/2022, 10:00 PMworried-painter-31382
09/06/2022, 10:00 PMfrom enum import Enum
import hashlib
from pants.engine.addresses import Addresses
from pants.engine.addresses import BuildFileAddress
from pants.engine.console import Console
from pants.engine.fs import Digest
from pants.engine.fs import DigestContents
from pants.engine.fs import PathGlobs
from pants.build_graph.address import BuildFileAddressRequest
from pants.engine.goal import Goal
from pants.engine.goal import GoalSubsystem
from pants.engine.goal import LineOriented
from pants.engine.rules import Get
from pants.engine.rules import MultiGet
from pants.engine.rules import goal_rule
from pants.build_graph.address import BuildFileAddressRequest
from pants.engine.rules import collect_rules
from pants.engine.target import HydratedSources
from pants.engine.target import HydrateSourcesRequest
from pants.engine.target import SourcesField
from pants.engine.target import Target
from pants.engine.target import TransitiveTargets
from pants.engine.target import TransitiveTargetsRequest
from pants.option.option_types import EnumOption
from pants.util.strutil import softwrap
HashAlgorithms = Enum('HashAlgorithms', {alg.upper(): alg for alg in hashlib.algorithms_available})
class HashTargetsSubsystem(LineOriented, GoalSubsystem):
name = 'hash'
help = 'Computes hash from contents of all files a target depends on.'
algorithm = EnumOption(
flag_name='--algorithm',
default=HashAlgorithms.SHA256,
help=softwrap(
"""
Specifies which hashing algorithm to use.
"""
),
)
class HashTargets(Goal):
subsystem_cls = HashTargetsSubsystem
@goal_rule
async def hash_targets(
console: Console,
hash_targets_subsystem: HashTargetsSubsystem,
addresses: Addresses,
) -> HashTargets:
for address in addresses:
transitive_targets = await Get(
TransitiveTargets, TransitiveTargetsRequest([address], include_special_cased_deps=True)
)
targets = transitive_targets.closure
build_file_addresses = await MultiGet(
Get(
BuildFileAddress,
BuildFileAddressRequest(target.address, description_of_origin="CLI arguments"),
)
for target in targets
)
unique_rel_paths = {build_file_address.rel_path for build_file_address in build_file_addresses}
all_hydrated_sources = await MultiGet(
Get(HydratedSources, HydrateSourcesRequest(target.get(SourcesField))) for target in targets
)
unique_rel_paths.update(
file
for hydrated_sources in all_hydrated_sources
for file in hydrated_sources.snapshot.files
)
digest = await Get(Digest, PathGlobs(sorted(unique_rel_paths)))
digest_contents = await Get(DigestContents, Digest, digest)
hash_content = hashlib.new(hash_targets_subsystem.algorithm.value)
for file_content in digest_contents:
hash_content.update(file_content.path.encode())
hash_content.update(file_content.content)
with hash_targets_subsystem.line_oriented(console) as print_stdout:
print_stdout(hash_content.hexdigest())
return HashTargets(exit_code=0)
def rules():
return collect_rules()
hundreds-father-404
09/06/2022, 10:58 PMDigest
? Use something like Get(HydratedSources, HydrateSourcesRequest(tgt[SourcesField])
, then hydrated_sources.snapshot.digest.fingerprint
. It's a sha256hundreds-father-404
09/06/2022, 10:59 PMGet(DigestEntries, Digest, my_digest)
, and it will give back a bunch of FileEntry
objects, which you can then do my_file_entry.file_digest.fingerprint
worried-painter-31382
09/07/2022, 8:08 AMworried-painter-31382
09/07/2022, 12:02 PMdef hash_python_requirement_versions(targets: Iterable[Target]) -> set[str]:
return {
str(hash(target.get(PythonRequirementsField).value))
for target in targets
if isinstance(target, PythonRequirementTarget)
}
worried-painter-31382
09/07/2022, 12:03 PMworried-painter-31382
09/07/2022, 12:05 PMfrom __future__ import annotations
import hashlib
from collections.abc import Iterable
from enum import Enum
from pants.backend.python.target_types import PythonRequirementsField
from pants.backend.python.target_types import PythonRequirementTarget
from pants.engine.addresses import Addresses
from pants.engine.console import Console
from pants.engine.goal import Goal
from pants.engine.goal import GoalSubsystem
from pants.engine.goal import LineOriented
from pants.engine.rules import collect_rules
from pants.engine.rules import Get
from pants.engine.rules import goal_rule
from pants.engine.rules import MultiGet
from pants.engine.target import HydratedSources
from pants.engine.target import HydrateSourcesRequest
from pants.engine.target import SourcesField
from pants.engine.target import Target
from pants.engine.target import TransitiveTargets
from pants.engine.target import TransitiveTargetsRequest
from pants.option.option_types import EnumOption
from pants.util.strutil import softwrap
HashAlgorithms = Enum('HashAlgorithms', {alg.upper(): alg for alg in hashlib.algorithms_available})
class HashTargetsSubsystem(LineOriented, GoalSubsystem):
name = 'hash'
help = 'Computes hash from contents of all files a target depends on.'
algorithm = EnumOption(
flag_name='--algorithm',
default=HashAlgorithms.SHA256,
help=softwrap(
"""
Specifies which hashing algorithm to use.
"""
),
)
class HashTargets(Goal):
subsystem_cls = HashTargetsSubsystem
def hash_python_requirement_versions(targets: Iterable[Target]) -> set[str]:
return {
str(hash(target.get(PythonRequirementsField).value))
for target in targets
if isinstance(target, PythonRequirementTarget)
}
@goal_rule
async def hash_targets(
console: Console, hash_targets_subsystem: HashTargetsSubsystem, addresses: Addresses
) -> HashTargets:
for address in addresses:
transitive_targets = await Get(
TransitiveTargets, TransitiveTargetsRequest([address], include_special_cased_deps=True)
)
targets = transitive_targets.closure
all_hydrated_sources = await MultiGet(
Get(HydratedSources, HydrateSourcesRequest(target.get(SourcesField)))
for target in targets
)
fingerprints = {
file
for hydrated_sources in all_hydrated_sources
for file in hydrated_sources.snapshot.digest.fingerprint
}
fingerprints.update(hash_python_requirement_versions(targets))
hash_content = hashlib.new(hash_targets_subsystem.algorithm.value)
for fingerprint in sorted(fingerprints):
hash_content.update(fingerprint.encode())
with hash_targets_subsystem.line_oriented(console) as print_stdout:
print_stdout(hash_content.hexdigest())
return HashTargets(exit_code=0)
def rules():
return collect_rules()
worried-painter-31382
09/07/2022, 12:06 PMPythonRequirementsTarget
, including the version string)worried-painter-31382
09/07/2022, 12:18 PMhundreds-father-404
09/07/2022, 1:19 PMPythonRequirementsTarget
doesn't have a sources field, indeed, so you should analyze the RequirementsField
. But use shalib.sha256
rather than hash()
. In Python, hash()
is not stable across runs