-
-
Notifications
You must be signed in to change notification settings - Fork 686
nfpm: add get_package_field_sets_for_nfpm_content_file_deps rule #22863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Iterable | ||
| from dataclasses import dataclass | ||
|
|
||
| from pants.backend.nfpm.field_sets import ( | ||
|
|
@@ -14,6 +15,7 @@ | |
| ) | ||
| from pants.backend.nfpm.field_sets import rules as field_sets_rules | ||
| from pants.backend.nfpm.subsystem import NfpmSubsystem | ||
| from pants.backend.nfpm.util_rules.contents import rules as contents_rules | ||
| from pants.backend.nfpm.util_rules.generate_config import ( | ||
| NfpmPackageConfigRequest, | ||
| generate_nfpm_yaml, | ||
|
|
@@ -34,7 +36,8 @@ | |
| from pants.engine.intrinsics import create_digest, digest_to_snapshot, merge_digests, remove_prefix | ||
| from pants.engine.platform import Platform | ||
| from pants.engine.process import Process, execute_process_or_raise | ||
| from pants.engine.rules import collect_rules, implicitly, rule | ||
| from pants.engine.rules import Rule, collect_rules, implicitly, rule | ||
| from pants.engine.unions import UnionRule | ||
| from pants.util.logging import LogLevel | ||
|
|
||
|
|
||
|
|
@@ -157,12 +160,13 @@ async def package_nfpm_rpm_package(field_set: NfpmRpmPackageFieldSet) -> BuiltPa | |
| return built_package | ||
|
|
||
|
|
||
| def rules(): | ||
| def rules() -> Iterable[Rule | UnionRule]: | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This added typehint is not strictly required by this PR. It's a minor consistency improvement (+4 -2 lines), and I'm already editing this file (+2 lines), so I included it here. |
||
| return [ | ||
| *package.rules(), | ||
| *field_sets_rules(), | ||
| *inject_config_rules(), | ||
| *generate_config_rules(), | ||
| *sandbox_rules(), | ||
| *contents_rules(), | ||
| *collect_rules(), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| # Copyright 2025 Pants project contributors (see CONTRIBUTORS.md). | ||
| # Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Iterable | ||
| from dataclasses import dataclass | ||
|
|
||
| from pants.backend.nfpm.field_sets import NfpmContentFileFieldSet | ||
| from pants.core.goals.package import PackageFieldSet, TraverseIfNotPackageTarget | ||
| from pants.engine.addresses import Address, Addresses | ||
| from pants.engine.internals.graph import find_valid_field_sets | ||
| from pants.engine.internals.graph import transitive_targets as get_transitive_targets | ||
| from pants.engine.rules import Rule, collect_rules, implicitly, rule | ||
| from pants.engine.target import ( | ||
| FieldSetsPerTarget, | ||
| FieldSetsPerTargetRequest, | ||
| TransitiveTargets, | ||
| TransitiveTargetsRequest, | ||
| ) | ||
| from pants.engine.unions import UnionMembership, UnionRule | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class GetPackageFieldSetsForNfpmContentFileDepsRequest: | ||
| addresses: Addresses | ||
| field_set_types: tuple[type[PackageFieldSet], ...] | ||
|
|
||
| def __init__( | ||
| self, addresses: Iterable[Address], field_set_types: Iterable[type[PackageFieldSet]] | ||
| ): | ||
| object.__setattr__(self, "addresses", Addresses(addresses)) | ||
| object.__setattr__(self, "field_set_types", tuple(field_set_types)) | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class PackageFieldSetsForNfpmContentFileDeps: | ||
| nfpm_content_file_targets: TransitiveTargets | ||
| package_field_sets: FieldSetsPerTarget | ||
|
|
||
|
|
||
| @rule | ||
| async def get_package_field_sets_for_nfpm_content_file_deps( | ||
| request: GetPackageFieldSetsForNfpmContentFileDepsRequest, | ||
| union_membership: UnionMembership, | ||
| ) -> PackageFieldSetsForNfpmContentFileDeps: | ||
| def transitive_targets_request(roots: Iterable[Address]): | ||
| return TransitiveTargetsRequest( | ||
| tuple(roots), | ||
| should_traverse_deps_predicate=TraverseIfNotPackageTarget( | ||
| roots=tuple(roots), | ||
| union_membership=union_membership, | ||
| ), | ||
| ) | ||
|
|
||
| transitive_targets: TransitiveTargets = await get_transitive_targets( | ||
| transitive_targets_request(request.addresses), **implicitly() | ||
| ) | ||
| content_file_transitive_targets: TransitiveTargets = await get_transitive_targets( | ||
| transitive_targets_request( | ||
| [ | ||
| tgt.address | ||
| for tgt in transitive_targets.dependencies | ||
| if NfpmContentFileFieldSet.is_applicable(tgt) | ||
| ] | ||
| ), | ||
| **implicitly(), | ||
| ) | ||
| package_field_sets: FieldSetsPerTarget = await find_valid_field_sets( | ||
| FieldSetsPerTargetRequest( | ||
| PackageFieldSet, # has to be a union parent | ||
| [ | ||
| tgt | ||
| for tgt in content_file_transitive_targets.dependencies | ||
| if any( | ||
| field_set_type.is_applicable(tgt) for field_set_type in request.field_set_types | ||
| ) | ||
| ], | ||
| ), | ||
| **implicitly(), | ||
| ) | ||
| return PackageFieldSetsForNfpmContentFileDeps( | ||
| content_file_transitive_targets, package_field_sets | ||
| ) | ||
|
|
||
|
|
||
| def rules() -> Iterable[Rule | UnionRule]: | ||
| return collect_rules() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| # Copyright 2025 Pants project contributors (see CONTRIBUTORS.md). | ||
| # Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from textwrap import dedent | ||
|
|
||
| import pytest | ||
|
|
||
| from pants.backend.nfpm.dependency_inference import rules as nfpm_dependency_inference_rules | ||
| from pants.backend.nfpm.field_sets import NFPM_CONTENT_FIELD_SET_TYPES, NfpmContentFieldSet | ||
| from pants.backend.nfpm.target_types import target_types as nfpm_target_types | ||
| from pants.backend.nfpm.target_types_rules import rules as nfpm_target_types_rules | ||
| from pants.backend.nfpm.util_rules.contents import ( | ||
| GetPackageFieldSetsForNfpmContentFileDepsRequest, | ||
| PackageFieldSetsForNfpmContentFileDeps, | ||
| ) | ||
| from pants.backend.nfpm.util_rules.contents import rules as nfpm_contents_rules | ||
| from pants.backend.nfpm.util_rules.generate_config import rules as nfpm_generate_config_rules | ||
| from pants.backend.nfpm.util_rules.inject_config import rules as nfpm_inject_config_rules | ||
| from pants.core.target_types import ArchiveFieldSet, ArchiveTarget, FilesGeneratorTarget, FileTarget | ||
| from pants.core.target_types import rules as core_target_type_rules | ||
| from pants.engine.addresses import Address | ||
| from pants.engine.rules import QueryRule | ||
| from pants.engine.unions import UnionRule | ||
| from pants.testutil.rule_runner import RuleRunner | ||
|
|
||
| _PKG_NAME = "pkg" | ||
| _PKG_VERSION = "3.2.1" | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def rule_runner() -> RuleRunner: | ||
| rule_runner = RuleRunner( | ||
| target_types=[ | ||
| ArchiveTarget, | ||
| FileTarget, | ||
| FilesGeneratorTarget, | ||
| *nfpm_target_types(), | ||
| ], | ||
| rules=[ | ||
| *core_target_type_rules(), | ||
| *nfpm_target_types_rules(), | ||
| *nfpm_dependency_inference_rules(), | ||
| *nfpm_generate_config_rules(), | ||
| *nfpm_inject_config_rules(), | ||
| *nfpm_contents_rules(), | ||
| *( | ||
| UnionRule(NfpmContentFieldSet, field_set_type) | ||
| for field_set_type in NFPM_CONTENT_FIELD_SET_TYPES | ||
| ), | ||
| QueryRule( | ||
| PackageFieldSetsForNfpmContentFileDeps, | ||
| (GetPackageFieldSetsForNfpmContentFileDepsRequest,), | ||
| ), | ||
| ], | ||
| ) | ||
| rule_runner.set_options([], env_inherit={"PATH", "PYENV_ROOT", "HOME"}) | ||
| return rule_runner | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("packager",), | ||
| ( | ||
| ("apk",), | ||
| ("archlinux",), | ||
| ("deb",), | ||
| ("rpm",), | ||
| ), | ||
| ) | ||
| def test_get_package_field_sets_for_nfpm_content_file_deps(rule_runner: RuleRunner, packager: str): | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I copied a bunch of this from the other tests in this directory. The tests are not as DRY as I would like, but moving the |
||
| description = f"A {packager} package" | ||
| rule_runner.write_files( | ||
| { | ||
| "BUILD": dedent( | ||
| f""" | ||
| nfpm_{packager}_package( | ||
| name="{_PKG_NAME}", | ||
| description="{description}", | ||
| package_name="{_PKG_NAME}", | ||
| version="{_PKG_VERSION}", | ||
| {"" if packager != "deb" else 'maintainer="Foo Bar <[email protected]>",'} | ||
| dependencies=[ | ||
| "contents:files", | ||
| "contents:file", | ||
| "package:package", | ||
| "package:output_path_package", | ||
| ], | ||
| ) | ||
| """ | ||
| ), | ||
| "package/BUILD": dedent( | ||
| """ | ||
| file( | ||
| name="file", | ||
| source="archive-contents.txt", | ||
| ) | ||
| archive( | ||
| name="archive", | ||
| format="tar", | ||
| files=[":file"], | ||
| ) | ||
| nfpm_content_file( | ||
| name="package", | ||
| src="archive.tar", | ||
| dst="/opt/foo/archive.tar", | ||
| dependencies=[":archive"], | ||
| ) | ||
| archive( | ||
| name="output_path_archive", | ||
| format="tar", | ||
| output_path="relative_to_build_root.tar", | ||
| files=[":file"], | ||
| ) | ||
| nfpm_content_file( | ||
| name="output_path_package", | ||
| src="relative_to_build_root.tar", | ||
| dst="/opt/foo/relative_to_build_root.tar", | ||
| dependencies=[":output_path_archive"], | ||
| ) | ||
| """ | ||
| ), | ||
| "package/archive-contents.txt": "", | ||
| "contents/BUILD": dedent( | ||
| f""" | ||
| file( | ||
| name="unrelated_file", | ||
| source="should.not.be.in.digest.txt", | ||
| ) | ||
| file( | ||
| name="sandbox_file", | ||
| source="sandbox-file.txt", | ||
| ) | ||
| nfpm_content_files( | ||
| name="files", | ||
| files=[ | ||
| ("sandbox-file.txt", "/usr/share/{_PKG_NAME}/{_PKG_NAME}.{_PKG_VERSION}/installed-file.txt"), | ||
| ("sandbox-file.txt", "/etc/{_PKG_NAME}/installed-file.txt"), | ||
| ], | ||
| dependencies=[":sandbox_file"], | ||
| ) | ||
| nfpm_content_file( | ||
| name="file", | ||
| source="some-executable", | ||
| dst="/usr/bin/some-executable", | ||
| ) | ||
| nfpm_content_symlinks( | ||
| name="symlinks", | ||
| symlinks=( | ||
| ("some-executable", "/usr/bin/new-relative-symlinked-exe"), | ||
| ("/usr/bin/some-executable", "/usr/bin/new-absolute-symlinked-exe"), | ||
| ), | ||
| overrides={{ | ||
| "/usr/bin/new-relative-symlinked-exe": dict(file_group="special-group"), | ||
| }}, | ||
| ) | ||
| nfpm_content_symlink( | ||
| name="symlink", | ||
| src="/usr/bin/some-executable", | ||
| dst="/usr/sbin/sbin-executable", | ||
| ) | ||
| nfpm_content_dirs( | ||
| name="dirs", | ||
| dirs=["/usr/share/{_PKG_NAME}"], | ||
| overrides={{ | ||
| "/usr/share/{_PKG_NAME}": dict(file_group="special-group"), | ||
| }}, | ||
| ) | ||
| nfpm_content_dir( | ||
| name="dir", | ||
| dst="/etc/{_PKG_NAME}", | ||
| file_mode=0o700, | ||
| ) | ||
| """ | ||
| ), | ||
| "contents/sandbox-file.txt": "", | ||
| "contents/some-executable": "", | ||
| } | ||
| ) | ||
| address = Address("", target_name=_PKG_NAME) | ||
|
|
||
| result = rule_runner.request( | ||
| PackageFieldSetsForNfpmContentFileDeps, | ||
| [ | ||
| GetPackageFieldSetsForNfpmContentFileDepsRequest([address], [ArchiveFieldSet]), | ||
| ], | ||
| ) | ||
|
|
||
| content_file_tgts = result.nfpm_content_file_targets.roots | ||
| assert len(content_file_tgts) == 5 | ||
| assert {tgt.address for tgt in content_file_tgts} == { | ||
| Address("package", target_name="package"), | ||
| Address("package", target_name="output_path_package"), | ||
| Address("contents", target_name="file"), | ||
| Address("contents", target_name="files", generated_name="/etc/pkg/installed-file.txt"), | ||
| Address( | ||
| "contents", | ||
| target_name="files", | ||
| generated_name="/usr/share/pkg/pkg.3.2.1/installed-file.txt", | ||
| ), | ||
| } | ||
|
|
||
| content_file_deps = result.nfpm_content_file_targets.dependencies | ||
| assert len(content_file_deps) == 3 | ||
| assert {tgt.address for tgt in content_file_deps} == { | ||
| Address("package", target_name="archive"), | ||
| Address("package", target_name="output_path_archive"), | ||
| Address("contents", target_name="sandbox_file"), | ||
| } | ||
|
|
||
| pkg_field_sets = result.package_field_sets | ||
| assert len(pkg_field_sets.collection) == 2 | ||
| assert [len(collection) for collection in pkg_field_sets.collection] == [1, 1] | ||
|
|
||
| assert len(pkg_field_sets.field_sets) == 2 | ||
| assert {tgt.address for tgt in pkg_field_sets.field_sets} == { | ||
| Address("package", target_name="archive"), | ||
| Address("package", target_name="output_path_archive"), | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I debated labeling this PR with release-notes:not-required[CI] PR doesn't require mention in release notes
I decided to add this release notes entry because the plugin API was originally included in the release notes and it is a separate rule meant for use in in-repo/external plugins.
It will also be used in pants in a rule that should be added in #22861 (or a PR that gets split off of that one), but it is separate so that I can reuse this tested rule in in-repo/external plugins as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think when in doubt, put it in the notes, so this is great.