Add package_variables support to pkg_files and pkg_filegroup prefix (#1052)
Closes #781.
pkg_files and pkg_filegroup now accept a package_variables attribute, enabling
make-variable substitution in their prefix attribute. For example:
my_platform_vars(name = "platform_vars", os = "linux", arch = "x86_64")
pkg_filegroup(
name = "platform_libs",
srcs = [":my_libs"],
prefix = "usr/lib/$(os)_$(arch)",
package_variables = ":platform_vars",
)
Previously the only workaround was select() on prefix, while package_file_name already supported variable substitution via package_variables. This closes the inconsistency: the same mechanism now works for destination paths in the mapping rules.
The implementation follows the same pattern as pkg_deb and pkg_tar, reusing substitute_package_variables() from //pkg/private:util.bzl.
- Admittedly this was too small to use an agent, but it saved me time writing the tests.
- I created a hints file for the next time.diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..327b0dc
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,9 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(gh issue *)",
+ "Bash(buildifier)",
+ "Bash(echo \"exit: $?\")"
+ ]
+ }
+}
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..914d9c9
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,161 @@
+# rules_pkg — Claude Code guide
+
+rules_pkg is a set of Bazel rules for building distribution packages (tar, zip, deb, rpm, …).
+The core abstraction is a set of package-format-agnostic mapping rules (`pkg_files`,
+`pkg_filegroup`, `pkg_mkdirs`, `pkg_mklink`) that describe *what* goes where in a package;
+format-specific rules (`pkg_tar`, `pkg_zip`, `pkg_deb`, `pkg_rpm`) consume those descriptions.
+
+## Repository layout
+
+```
+pkg/ Runtime rules and providers (shipped in the distribution)
+ mappings.bzl pkg_files, pkg_filegroup, pkg_mkdirs, pkg_mklink
+ providers.bzl PackageVariablesInfo and other providers
+ private/ Internal implementation helpers (not public API)
+ util.bzl substitute_package_variables, setup_output_files, …
+ deb/ pkg_deb implementation
+ tar/ pkg_tar implementation
+ zip/ pkg_zip implementation
+tests/ All tests (not shipped)
+ mappings/ Analysis tests for pkg_files / pkg_filegroup
+ tar/ Tests for pkg_tar
+ deb/ Tests for pkg_deb
+ rpm/ Tests for pkg_rpm
+ zip/ Tests for pkg_zip
+examples/ Runnable examples (tested in CI)
+docs/ Generated reference docs (do not edit by hand)
+distro/ Rules to build the distribution tarball
+```
+
+Top-level `.bzl` shims (`mappings.bzl`, `pkg.bzl`, etc.) are backward-compatibility
+re-exports of the files inside `pkg/`.
+
+## Code style
+
+### Starlark / BUILD files — always run buildifier after editing
+
+After editing any `.bzl` or `BUILD` file, run:
+
+```
+buildifier --lint=fix <FILE>
+```
+
+buildifier enforces load ordering, argument sorting, and other canonical style.
+It will reorder loads alphabetically; let it.
+
+### Starlark conventions
+
+- All public rule attributes must have a `doc =` string.
+- Use `substitute_package_variables(ctx, value)` (from `//pkg/private:util.bzl`)
+ to expand `$(VAR)` make-variable syntax in string attributes.
+ The rule must also declare a `package_variables` attribute typed
+ `attr.label(providers = [PackageVariablesInfo])`.
+- Prefer solutions that work for all package formats (via `pkg_files`/`pkg_filegroup`)
+ over format-specific additions.
+- Actions must not write quoted strings directly to command lines — write paths to
+ an intermediate file instead.
+
+### Python
+
+- Python 3 only; no Python 2 support.
+- Always import with full paths from the workspace root.
+- No new third-party package dependencies — standard library only.
+
+### General
+
+- No files should have trailing whitespace.
+- Try to keep lines under 100 characters long.
+
+## Testing
+
+**All features and bug fixes must have tests.**
+
+### Mappings (pkg_files / pkg_filegroup)
+
+Tests live in `tests/mappings/mappings_test.bzl` and are registered via the
+`mappings_analysis_tests()` macro called from `tests/mappings/BUILD`.
+
+- Use `pkg_files_contents_test` (defined in `mappings_test.bzl`) to assert
+ expected destination paths from a `pkg_files` target.
+- Use `pkg_filegroup_contents_test` to compare a `pkg_filegroup` output against
+ reference `pkg_files` / `pkg_mkdirs` / `pkg_mklink` targets.
+- Use `generic_negative_test` for targets that are expected to fail analysis.
+- Add new test names to the `pkg_files_analysis_tests` test suite list at the
+ bottom of `mappings_analysis_tests()`.
+
+Run them with:
+
+```
+bazel test //tests/mappings/...
+```
+
+### package_variables / make-variable substitution
+
+The sample naming rule used across tests is `my_package_naming` in
+`tests/my_package_name.bzl`. Load it as:
+
+```python
+load("//tests:my_package_name.bzl", "my_package_naming")
+```
+
+Create an instance, then wire it to the `package_variables` attribute of
+the rule under test. Example:
+
+```python
+my_package_naming(name = "my_vars", label = "linux_x86_64", tags = ["manual"])
+
+pkg_files(
+ name = "my_files",
+ srcs = [...],
+ prefix = "usr/lib/$(label)",
+ package_variables = ":my_vars",
+ tags = ["manual"],
+)
+```
+
+### Format-specific tests
+
+```
+bazel test //tests/tar/...
+bazel test //tests/deb/...
+bazel test //tests/zip/...
+bazel test //tests/rpm/... # may require rpm toolchain
+```
+
+### Running everything
+
+```
+bazel test //tests/...
+```
+
+## Regenerating docs
+
+After any feature change, regenerate the reference docs before committing:
+
+```
+bazel build //doc_build:reference
+cp bazel-bin/doc_build/reference.md docs/latest.md
+```
+
+Do **not** `git commit` yet — that is a separate step the user will handle.
+
+## Common patterns
+
+### Adding package_variables support to an attribute
+
+1. Import `PackageVariablesInfo` from `//pkg:providers.bzl` and
+ `substitute_package_variables` from `//pkg/private:util.bzl`.
+2. Add to the rule attrs:
+ ```python
+ "package_variables": attr.label(
+ doc = """See [Common Attributes](#package_variables)""",
+ providers = [PackageVariablesInfo],
+ ),
+ ```
+3. In the implementation, call substitution at the top before using the value:
+ ```python
+ prefix = substitute_package_variables(ctx, ctx.attr.prefix)
+ ```
+4. Use the substituted local variable everywhere instead of `ctx.attr.prefix`.
+5. Run `buildifier --lint=fix` on the modified file.
+6. Add analysis tests in the appropriate test file.
diff --git a/docs/latest.md b/docs/latest.md
index 4f10cf4..ba68f1e 100755
--- a/docs/latest.md
+++ b/docs/latest.md
@@ -96,8 +96,8 @@
<a href="#pkg_deb-built_using_file">built_using_file</a>, <a href="#pkg_deb-changelog">changelog</a>, <a href="#pkg_deb-conffiles">conffiles</a>, <a href="#pkg_deb-conffiles_file">conffiles_file</a>, <a href="#pkg_deb-config">config</a>, <a href="#pkg_deb-conflicts">conflicts</a>, <a href="#pkg_deb-depends">depends</a>,
<a href="#pkg_deb-depends_file">depends_file</a>, <a href="#pkg_deb-description">description</a>, <a href="#pkg_deb-description_file">description_file</a>, <a href="#pkg_deb-distribution">distribution</a>, <a href="#pkg_deb-enhances">enhances</a>, <a href="#pkg_deb-homepage">homepage</a>, <a href="#pkg_deb-license">license</a>,
<a href="#pkg_deb-maintainer">maintainer</a>, <a href="#pkg_deb-md5sums">md5sums</a>, <a href="#pkg_deb-package">package</a>, <a href="#pkg_deb-package_file_name">package_file_name</a>, <a href="#pkg_deb-package_variables">package_variables</a>, <a href="#pkg_deb-postinst">postinst</a>, <a href="#pkg_deb-postrm">postrm</a>,
- <a href="#pkg_deb-predepends">predepends</a>, <a href="#pkg_deb-preinst">preinst</a>, <a href="#pkg_deb-prerm">prerm</a>, <a href="#pkg_deb-priority">priority</a>, <a href="#pkg_deb-provides">provides</a>, <a href="#pkg_deb-recommends">recommends</a>, <a href="#pkg_deb-replaces">replaces</a>, <a href="#pkg_deb-section">section</a>, <a href="#pkg_deb-suggests">suggests</a>,
- <a href="#pkg_deb-templates">templates</a>, <a href="#pkg_deb-triggers">triggers</a>, <a href="#pkg_deb-urgency">urgency</a>, <a href="#pkg_deb-version">version</a>, <a href="#pkg_deb-version_file">version_file</a>)
+ <a href="#pkg_deb-predepends">predepends</a>, <a href="#pkg_deb-preinst">preinst</a>, <a href="#pkg_deb-prerm">prerm</a>, <a href="#pkg_deb-priority">priority</a>, <a href="#pkg_deb-provides">provides</a>, <a href="#pkg_deb-provides_file">provides_file</a>, <a href="#pkg_deb-recommends">recommends</a>, <a href="#pkg_deb-replaces">replaces</a>,
+ <a href="#pkg_deb-replaces_file">replaces_file</a>, <a href="#pkg_deb-section">section</a>, <a href="#pkg_deb-suggests">suggests</a>, <a href="#pkg_deb-templates">templates</a>, <a href="#pkg_deb-triggers">triggers</a>, <a href="#pkg_deb-urgency">urgency</a>, <a href="#pkg_deb-version">version</a>, <a href="#pkg_deb-version_file">version_file</a>)
</pre>
Create a Debian package.
@@ -148,9 +148,11 @@
| <a id="pkg_deb-preinst"></a>preinst | "The pre-install script for the package. See http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_deb-prerm"></a>prerm | The pre-remove script for the package. See http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_deb-priority"></a>priority | The priority of the package. See http://www.debian.org/doc/debian-policy/ch-archive.html#s-priorities. | String | optional | `""` |
-| <a id="pkg_deb-provides"></a>provides | See http://www.debian.org/doc/debian-policy/ch-relationships.html#s-binarydeps. | List of strings | optional | `[]` |
+| <a id="pkg_deb-provides"></a>provides | See https://www.debian.org/doc/debian-policy/ch-relationships.html#virtual-packages-provides. | List of strings | optional | `[]` |
+| <a id="pkg_deb-provides_file"></a>provides_file | File that contains a list of provided packages. Must not be used with `provides`. See https://www.debian.org/doc/debian-policy/ch-relationships.html#virtual-packages-provides. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_deb-recommends"></a>recommends | See http://www.debian.org/doc/debian-policy/ch-relationships.html#s-binarydeps. | List of strings | optional | `[]` |
-| <a id="pkg_deb-replaces"></a>replaces | See http://www.debian.org/doc/debian-policy/ch-relationships.html#s-binarydeps. | List of strings | optional | `[]` |
+| <a id="pkg_deb-replaces"></a>replaces | See https://www.debian.org/doc/debian-policy/ch-relationships.html#overwriting-files-and-replacing-packages-replaces. | List of strings | optional | `[]` |
+| <a id="pkg_deb-replaces_file"></a>replaces_file | File that contains a list of replaced packages. Must not be used with `replaces`. See https://www.debian.org/doc/debian-policy/ch-relationships.html#overwriting-files-and-replacing-packages-replaces. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_deb-section"></a>section | The section of the package. See http://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections. | String | optional | `""` |
| <a id="pkg_deb-suggests"></a>suggests | See http://www.debian.org/doc/debian-policy/ch-relationships.html#s-binarydeps. | List of strings | optional | `[]` |
| <a id="pkg_deb-templates"></a>templates | templates file used for debconf integration. See https://www.debian.org/doc/debian-policy/ch-binary.html#prompting-in-maintainer-scripts. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
@@ -235,13 +237,14 @@
<pre>
load("@rules_pkg//pkg:rpm_pfg.bzl", "pkg_rpm")
-pkg_rpm(<a href="#pkg_rpm-name">name</a>, <a href="#pkg_rpm-srcs">srcs</a>, <a href="#pkg_rpm-data">data</a>, <a href="#pkg_rpm-architecture">architecture</a>, <a href="#pkg_rpm-binary_payload_compression">binary_payload_compression</a>, <a href="#pkg_rpm-changelog">changelog</a>, <a href="#pkg_rpm-conflicts">conflicts</a>, <a href="#pkg_rpm-debug">debug</a>,
- <a href="#pkg_rpm-debuginfo">debuginfo</a>, <a href="#pkg_rpm-defines">defines</a>, <a href="#pkg_rpm-description">description</a>, <a href="#pkg_rpm-description_file">description_file</a>, <a href="#pkg_rpm-epoch">epoch</a>, <a href="#pkg_rpm-group">group</a>, <a href="#pkg_rpm-license">license</a>, <a href="#pkg_rpm-obsoletes">obsoletes</a>,
- <a href="#pkg_rpm-package_file_name">package_file_name</a>, <a href="#pkg_rpm-package_name">package_name</a>, <a href="#pkg_rpm-package_variables">package_variables</a>, <a href="#pkg_rpm-post_scriptlet">post_scriptlet</a>, <a href="#pkg_rpm-post_scriptlet_file">post_scriptlet_file</a>,
- <a href="#pkg_rpm-posttrans_scriptlet">posttrans_scriptlet</a>, <a href="#pkg_rpm-posttrans_scriptlet_file">posttrans_scriptlet_file</a>, <a href="#pkg_rpm-postun_scriptlet">postun_scriptlet</a>, <a href="#pkg_rpm-postun_scriptlet_file">postun_scriptlet_file</a>,
- <a href="#pkg_rpm-pre_scriptlet">pre_scriptlet</a>, <a href="#pkg_rpm-pre_scriptlet_file">pre_scriptlet_file</a>, <a href="#pkg_rpm-preun_scriptlet">preun_scriptlet</a>, <a href="#pkg_rpm-preun_scriptlet_file">preun_scriptlet_file</a>, <a href="#pkg_rpm-provides">provides</a>, <a href="#pkg_rpm-release">release</a>,
- <a href="#pkg_rpm-release_file">release_file</a>, <a href="#pkg_rpm-requires">requires</a>, <a href="#pkg_rpm-requires_contextual">requires_contextual</a>, <a href="#pkg_rpm-rpmbuild_path">rpmbuild_path</a>, <a href="#pkg_rpm-source_date_epoch">source_date_epoch</a>,
- <a href="#pkg_rpm-source_date_epoch_file">source_date_epoch_file</a>, <a href="#pkg_rpm-spec_template">spec_template</a>, <a href="#pkg_rpm-subrpms">subrpms</a>, <a href="#pkg_rpm-summary">summary</a>, <a href="#pkg_rpm-url">url</a>, <a href="#pkg_rpm-version">version</a>, <a href="#pkg_rpm-version_file">version_file</a>)
+pkg_rpm(<a href="#pkg_rpm-name">name</a>, <a href="#pkg_rpm-srcs">srcs</a>, <a href="#pkg_rpm-data">data</a>, <a href="#pkg_rpm-architecture">architecture</a>, <a href="#pkg_rpm-binary_payload_compression">binary_payload_compression</a>, <a href="#pkg_rpm-changelog">changelog</a>, <a href="#pkg_rpm-conflicts">conflicts</a>,
+ <a href="#pkg_rpm-debug">debug</a>, <a href="#pkg_rpm-debuginfo">debuginfo</a>, <a href="#pkg_rpm-defines">defines</a>, <a href="#pkg_rpm-description">description</a>, <a href="#pkg_rpm-description_file">description_file</a>, <a href="#pkg_rpm-epoch">epoch</a>, <a href="#pkg_rpm-group">group</a>, <a href="#pkg_rpm-license">license</a>,
+ <a href="#pkg_rpm-obsoletes">obsoletes</a>, <a href="#pkg_rpm-package_file_name">package_file_name</a>, <a href="#pkg_rpm-package_name">package_name</a>, <a href="#pkg_rpm-package_variables">package_variables</a>, <a href="#pkg_rpm-post_scriptlet">post_scriptlet</a>,
+ <a href="#pkg_rpm-post_scriptlet_file">post_scriptlet_file</a>, <a href="#pkg_rpm-posttrans_scriptlet">posttrans_scriptlet</a>, <a href="#pkg_rpm-posttrans_scriptlet_file">posttrans_scriptlet_file</a>, <a href="#pkg_rpm-postun_scriptlet">postun_scriptlet</a>,
+ <a href="#pkg_rpm-postun_scriptlet_file">postun_scriptlet_file</a>, <a href="#pkg_rpm-pre_scriptlet">pre_scriptlet</a>, <a href="#pkg_rpm-pre_scriptlet_file">pre_scriptlet_file</a>, <a href="#pkg_rpm-preun_scriptlet">preun_scriptlet</a>,
+ <a href="#pkg_rpm-preun_scriptlet_file">preun_scriptlet_file</a>, <a href="#pkg_rpm-private_stamp_detect">private_stamp_detect</a>, <a href="#pkg_rpm-provides">provides</a>, <a href="#pkg_rpm-release">release</a>, <a href="#pkg_rpm-release_file">release_file</a>, <a href="#pkg_rpm-requires">requires</a>,
+ <a href="#pkg_rpm-requires_contextual">requires_contextual</a>, <a href="#pkg_rpm-rpmbuild_path">rpmbuild_path</a>, <a href="#pkg_rpm-source_date_epoch">source_date_epoch</a>, <a href="#pkg_rpm-source_date_epoch_file">source_date_epoch_file</a>,
+ <a href="#pkg_rpm-spec_template">spec_template</a>, <a href="#pkg_rpm-stamp">stamp</a>, <a href="#pkg_rpm-subrpms">subrpms</a>, <a href="#pkg_rpm-summary">summary</a>, <a href="#pkg_rpm-url">url</a>, <a href="#pkg_rpm-version">version</a>, <a href="#pkg_rpm-version_file">version_file</a>)
</pre>
Creates an RPM format package via `pkg_filegroup` and friends.
@@ -315,8 +318,9 @@
| <a id="pkg_rpm-pre_scriptlet_file"></a>pre_scriptlet_file | File containing the RPM `%pre` scriptlet | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_rpm-preun_scriptlet"></a>preun_scriptlet | RPM `%preun` scriptlet. Currently only allowed to be a shell script.<br><br>`preun_scriptlet` and `preun_scriptlet_file` are mutually exclusive. | String | optional | `""` |
| <a id="pkg_rpm-preun_scriptlet_file"></a>preun_scriptlet_file | File containing the RPM `%preun` scriptlet | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
+| <a id="pkg_rpm-private_stamp_detect"></a>private_stamp_detect | - | Boolean | optional | `False` |
| <a id="pkg_rpm-provides"></a>provides | List of rpm capabilities that this package provides.<br><br>Corresponds to the "Provides" preamble tag.<br><br>See also: https://rpm-software-management.github.io/rpm/manual/dependencies.html | List of strings | optional | `[]` |
-| <a id="pkg_rpm-release"></a>release | RPM "Release" tag<br><br>Exactly one of `release` or `release_file` must be provided. | String | optional | `""` |
+| <a id="pkg_rpm-release"></a>release | RPM "Release" tag<br><br>Exactly one of `release` or `release_file` must be provided.<br><br>When `stamp` is enabled, workspace status variable placeholders of the form `{VARIABLE_NAME}` will be substituted at build time using values from the stable and volatile status files. For example, setting `release = "0.{BUILD_TIMESTAMP}"` with `stamp = 1` will embed the build timestamp in the release tag. See https://bazel.build/docs/user-manual#workspace-status for details on workspace status variables. | String | optional | `""` |
| <a id="pkg_rpm-release_file"></a>release_file | File containing RPM "Release" tag. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_rpm-requires"></a>requires | List of rpm capability expressions that this package requires.<br><br>Corresponds to the "Requires" preamble tag.<br><br>See also: https://rpm-software-management.github.io/rpm/manual/dependencies.html | List of strings | optional | `[]` |
| <a id="pkg_rpm-requires_contextual"></a>requires_contextual | Contextualized requirement specifications<br><br>This is a map of various properties (often scriptlet types) to capability name specifications, e.g.:<br><br><pre><code class="language-python">{"pre": ["GConf2"],"post": ["GConf2"], "postun": ["GConf2"]}</code></pre><br><br>Which causes the below to be added to the spec file's preamble:<br><br><pre><code>Requires(pre): GConf2 Requires(post): GConf2 Requires(postun): GConf2</code></pre><br><br>This is most useful for ensuring that required tools exist when scriptlets are run, although there may be other valid use cases. Valid keys for this attribute may include, but are not limited to:<br><br>- `pre` - `post` - `preun` - `postun` - `pretrans` - `posttrans`<br><br>For capabilities that are always required by packages at runtime, use the `requires` attribute instead.<br><br>See also: https://rpm-software-management.github.io/rpm/manual/more_dependencies.html<br><br>NOTE: `pkg_rpm` does not check if the keys of this dictionary are acceptable to `rpm(8)`. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> List of strings</a> | optional | `{}` |
@@ -324,6 +328,7 @@
| <a id="pkg_rpm-source_date_epoch"></a>source_date_epoch | Value to export as SOURCE_DATE_EPOCH to facilitate reproducible builds<br><br>Implicitly sets the `%clamp_mtime_to_source_date_epoch` in the subordinate call to `rpmbuild` to facilitate more consistent in-RPM file timestamps.<br><br>Negative values (like the default) disable this feature. | Integer | optional | `-1` |
| <a id="pkg_rpm-source_date_epoch_file"></a>source_date_epoch_file | File containing the SOURCE_DATE_EPOCH value.<br><br>Implicitly sets the `%clamp_mtime_to_source_date_epoch` in the subordinate call to `rpmbuild` to facilitate more consistent in-RPM file timestamps. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_rpm-spec_template"></a>spec_template | Spec file template.<br><br>Use this if you need to add additional logic to your spec files that is not available by default.<br><br>In most cases, you should not need to override this attribute. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `"@rules_pkg//pkg/rpm:template.spec.tpl"` |
+| <a id="pkg_rpm-stamp"></a>stamp | Enable stamping for volatile release values. Possible values: <li>stamp = 1: Substitute workspace status variables in the release tag. <li>stamp = 0: No substitution; release tag used as-is. <li>stamp = -1: Controlled by the --[no]stamp flag. | Integer | optional | `0` |
| <a id="pkg_rpm-subrpms"></a>subrpms | Sub RPMs to build with this RPM<br><br>A list of `pkg_sub_rpm` instances that can be used to create sub RPMs as part of the overall package build.<br><br>NOTE: use of `subrpms` is incompatible with the legacy `spec_file` mode | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="pkg_rpm-summary"></a>summary | RPM "Summary" tag.<br><br>One-line summary of this package. Must not contain newlines. | String | required | |
| <a id="pkg_rpm-url"></a>url | RPM "URL" tag; this project/vendor's home on the Internet. | String | optional | `""` |
@@ -510,7 +515,7 @@
<pre>
load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup")
-pkg_filegroup(<a href="#pkg_filegroup-name">name</a>, <a href="#pkg_filegroup-srcs">srcs</a>, <a href="#pkg_filegroup-prefix">prefix</a>)
+pkg_filegroup(<a href="#pkg_filegroup-name">name</a>, <a href="#pkg_filegroup-srcs">srcs</a>, <a href="#pkg_filegroup-package_variables">package_variables</a>, <a href="#pkg_filegroup-prefix">prefix</a>)
</pre>
Package contents grouping rule.
@@ -526,6 +531,7 @@
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="pkg_filegroup-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
| <a id="pkg_filegroup-srcs"></a>srcs | A list of packaging specifications to be grouped together. | <a href="https://bazel.build/concepts/labels">List of labels</a> | required | |
+| <a id="pkg_filegroup-package_variables"></a>package_variables | See [Common Attributes](#package_variables) | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_filegroup-prefix"></a>prefix | A prefix to prepend to provided paths, applied like so:<br><br>- For files and directories, this is simply prepended to the destination - For symbolic links, this is prepended to the "destination" part. | String | optional | `""` |
@@ -536,7 +542,8 @@
<pre>
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
-pkg_files(<a href="#pkg_files-name">name</a>, <a href="#pkg_files-srcs">srcs</a>, <a href="#pkg_files-attributes">attributes</a>, <a href="#pkg_files-excludes">excludes</a>, <a href="#pkg_files-include_runfiles">include_runfiles</a>, <a href="#pkg_files-prefix">prefix</a>, <a href="#pkg_files-renames">renames</a>, <a href="#pkg_files-strip_prefix">strip_prefix</a>)
+pkg_files(<a href="#pkg_files-name">name</a>, <a href="#pkg_files-srcs">srcs</a>, <a href="#pkg_files-attributes">attributes</a>, <a href="#pkg_files-excludes">excludes</a>, <a href="#pkg_files-include_runfiles">include_runfiles</a>, <a href="#pkg_files-package_variables">package_variables</a>, <a href="#pkg_files-prefix">prefix</a>, <a href="#pkg_files-renames">renames</a>,
+ <a href="#pkg_files-strip_prefix">strip_prefix</a>)
</pre>
General-purpose package target-to-destination mapping rule.
@@ -562,6 +569,7 @@
| <a id="pkg_files-attributes"></a>attributes | Attributes to set on packaged files.<br><br>Always use `pkg_attributes()` to set this rule attribute.<br><br>If not otherwise overridden, the file's mode will be set to UNIX "0644", or the target platform's equivalent.<br><br>Consult the "Mapping Attributes" documentation in the rules_pkg reference for more details. | String | optional | `"{}"` |
| <a id="pkg_files-excludes"></a>excludes | List of files or labels to exclude from the inputs to this rule.<br><br>Mostly useful for removing files from generated outputs or preexisting `filegroup`s. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | `[]` |
| <a id="pkg_files-include_runfiles"></a>include_runfiles | Add runfiles for all srcs.<br><br>The runfiles are in the paths that Bazel uses. For example, for the target `//my_prog:foo`, we would see files under paths like `foo.runfiles/<repo name>/my_prog/<file>` | Boolean | optional | `False` |
+| <a id="pkg_files-package_variables"></a>package_variables | See [Common Attributes](#package_variables) | <a href="https://bazel.build/concepts/labels">Label</a> | optional | `None` |
| <a id="pkg_files-prefix"></a>prefix | Installation prefix.<br><br>This may be an arbitrary string, but it should be understandable by the packaging system you are using to have the desired outcome. For example, RPM macros like `%{_libdir}` may work correctly in paths for RPM packages, not, say, Debian packages.<br><br>If any part of the directory structure of the computed destination of a file provided to `pkg_filegroup` or any similar rule does not already exist within a package, the package builder will create it for you with a reasonable set of default permissions (typically `0755 root.root`).<br><br>It is possible to establish directory structures with arbitrary permissions using `pkg_mkdirs`. | String | optional | `""` |
| <a id="pkg_files-renames"></a>renames | Destination override map.<br><br>This attribute allows the user to override destinations of files in `pkg_file`s relative to the `prefix` attribute. Keys to the dict are source files/labels, values are destinations relative to the `prefix`, ignoring whatever value was provided for `strip_prefix`.<br><br>If the key refers to a TreeArtifact (directory output), you may specify the constant `REMOVE_BASE_DIRECTORY` as the value, which will result in all containing files and directories being installed relative to the otherwise specified install prefix (via the `prefix` and `strip_prefix` attributes), not the directory name.<br><br>The following keys are rejected:<br><br>- Any label that expands to more than one file (mappings must be one-to-one).<br><br>- Any label or file that was either not provided or explicitly `exclude`d.<br><br>The following values result in undefined behavior:<br><br>- "" (the empty string)<br><br>- "."<br><br>- Anything containing ".." | <a href="https://bazel.build/rules/lib/dict">Dictionary: Label -> String</a> | optional | `{}` |
| <a id="pkg_files-strip_prefix"></a>strip_prefix | What prefix of a file's path to discard prior to installation.<br><br>This specifies what prefix of an incoming file's path should not be included in the output package at after being appended to the install prefix (the `prefix` attribute). Note that this is only applied to full directory names, see `strip_prefix` for more details.<br><br>Use the `strip_prefix` struct to define this attribute. If this attribute is not specified, all directories will be stripped from all files prior to being included in packages (`strip_prefix.files_only()`).<br><br>If prefix stripping fails on any file provided in `srcs`, the build will fail.<br><br>Note that this only functions on paths that are known at analysis time. Specifically, this will not consider directories within TreeArtifacts (directory outputs), or the directories themselves. See also #269. | String | optional | `"."` |
diff --git a/pkg/mappings.bzl b/pkg/mappings.bzl
index 0e44158..beeb392 100644
--- a/pkg/mappings.bzl
+++ b/pkg/mappings.bzl
@@ -28,8 +28,15 @@
"""
load("@bazel_skylib//lib:paths.bzl", "paths")
-load("//pkg:providers.bzl", "PackageDirsInfo", "PackageFilegroupInfo", "PackageFilesInfo", "PackageSymlinkInfo")
-load("//pkg/private:util.bzl", "get_repo_mapping_manifest")
+load(
+ "//pkg:providers.bzl",
+ "PackageDirsInfo",
+ "PackageFilegroupInfo",
+ "PackageFilesInfo",
+ "PackageSymlinkInfo",
+ "PackageVariablesInfo",
+)
+load("//pkg/private:util.bzl", "get_repo_mapping_manifest", "substitute_package_variables")
# TODO(#333): strip_prefix module functions should produce unique outputs. In
# particular, this one and `_sp_from_pkg` can overlap.
@@ -216,6 +223,7 @@
def _pkg_files_impl(ctx):
# The input sources are already known. Let's calculate the destinations...
+ prefix = substitute_package_variables(ctx, ctx.attr.prefix)
# Exclude excludes
srcs = [] # srcs is source File objects, not Targets
@@ -227,11 +235,11 @@
file_to_target[f] = src
if ctx.attr.strip_prefix == _PKGFILEGROUP_STRIP_ALL:
- src_dest_paths_map = {src: paths.join(ctx.attr.prefix, src.basename) for src in srcs}
+ src_dest_paths_map = {src: paths.join(prefix, src.basename) for src in srcs}
elif ctx.attr.strip_prefix.startswith("/"):
# Relative to workspace/repository root
src_dest_paths_map = {src: paths.join(
- ctx.attr.prefix,
+ prefix,
_do_strip_prefix(
_path_relative_to_repo_root(src),
ctx.attr.strip_prefix[1:],
@@ -241,7 +249,7 @@
else:
# Relative to package
src_dest_paths_map = {src: paths.join(
- ctx.attr.prefix,
+ prefix,
_do_strip_prefix(
_path_relative_to_package(src),
ctx.attr.strip_prefix,
@@ -283,10 +291,10 @@
# REMOVE_BASE_DIRECTORY results in the contents being dropped into
# place directly in the prefix path.
- src_dest_paths_map[src_file] = ctx.attr.prefix
+ src_dest_paths_map[src_file] = prefix
else:
- src_dest_paths_map[src_file] = paths.join(ctx.attr.prefix, rename_dest)
+ src_dest_paths_map[src_file] = paths.join(prefix, rename_dest)
# At this point, we have a fully valid src -> dest mapping for all the
# explicitly named targets in srcs. Now we can fill in their runfiles.
@@ -466,6 +474,10 @@
`foo.runfiles/<repo name>/my_prog/<file>`
""",
),
+ "package_variables": attr.label(
+ doc = """See [Common Attributes](#package_variables)""",
+ providers = [PackageVariablesInfo],
+ ),
},
provides = [PackageFilesInfo],
)
@@ -632,8 +644,9 @@
dirs = []
links = []
mapped_files_depsets = []
+ prefix = substitute_package_variables(ctx, ctx.attr.prefix)
- if ctx.attr.prefix:
+ if prefix:
# If "prefix" is provided, we need to manipulate the incoming providers.
for s in ctx.attr.srcs:
if PackageFilegroupInfo in s:
@@ -643,7 +656,7 @@
(
PackageFilesInfo(
dest_src_map = {
- paths.join(ctx.attr.prefix, dest): src
+ paths.join(prefix, dest): src
for dest, src in pfi.dest_src_map.items()
},
attributes = pfi.attributes,
@@ -655,7 +668,7 @@
dirs += [
(
PackageDirsInfo(
- dirs = [paths.join(ctx.attr.prefix, d) for d in pdi.dirs],
+ dirs = [paths.join(prefix, d) for d in pdi.dirs],
attributes = pdi.attributes,
),
origin,
@@ -666,7 +679,7 @@
(
PackageSymlinkInfo(
target = psi.target,
- destination = paths.join(ctx.attr.prefix, psi.destination),
+ destination = paths.join(prefix, psi.destination),
attributes = psi.attributes,
),
origin,
@@ -679,7 +692,7 @@
if PackageFilesInfo in s:
new_pfi = PackageFilesInfo(
dest_src_map = {
- paths.join(ctx.attr.prefix, dest): src
+ paths.join(prefix, dest): src
for dest, src in s[PackageFilesInfo].dest_src_map.items()
},
attributes = s[PackageFilesInfo].attributes,
@@ -691,7 +704,7 @@
if PackageDirsInfo in s:
new_pdi = PackageDirsInfo(
- dirs = [paths.join(ctx.attr.prefix, d) for d in s[PackageDirsInfo].dirs],
+ dirs = [paths.join(prefix, d) for d in s[PackageDirsInfo].dirs],
attributes = s[PackageDirsInfo].attributes,
)
dirs.append((new_pdi, s.label))
@@ -699,7 +712,7 @@
if PackageSymlinkInfo in s:
new_psi = PackageSymlinkInfo(
target = s[PackageSymlinkInfo].target,
- destination = paths.join(ctx.attr.prefix, s[PackageSymlinkInfo].destination),
+ destination = paths.join(prefix, s[PackageSymlinkInfo].destination),
attributes = s[PackageSymlinkInfo].attributes,
)
links.append((new_psi, s.label))
@@ -763,6 +776,10 @@
""",
),
+ "package_variables": attr.label(
+ doc = """See [Common Attributes](#package_variables)""",
+ providers = [PackageVariablesInfo],
+ ),
},
provides = [PackageFilegroupInfo],
)
diff --git a/tests/mappings/mappings_test.bzl b/tests/mappings/mappings_test.bzl
index 27a61cb..f663c52 100644
--- a/tests/mappings/mappings_test.bzl
+++ b/tests/mappings/mappings_test.bzl
@@ -34,6 +34,7 @@
"PackageFilesInfo",
"PackageSymlinkInfo",
)
+load("//tests:my_package_name.bzl", "my_package_naming")
load(
"//tests/util:defs.bzl",
"directory",
@@ -904,6 +905,72 @@
)
##########
+# Test package_variables substitution in prefix
+##########
+
+def _test_pkg_files_package_variables():
+ my_package_naming(
+ name = "pf_pkg_vars_naming",
+ label = "amazing",
+ tags = ["manual"],
+ )
+
+ pkg_files(
+ name = "pf_with_package_variables_g",
+ srcs = ["testdata/hello.txt"],
+ prefix = "usr/$(label)/share",
+ package_variables = ":pf_pkg_vars_naming",
+ tags = ["manual"],
+ )
+
+ pkg_files_contents_test(
+ name = "pf_with_package_variables",
+ target_under_test = ":pf_with_package_variables_g",
+ expected_dests = ["usr/amazing/share/hello.txt"],
+ )
+
+def _test_pkg_filegroup_package_variables():
+ my_package_naming(
+ name = "pfg_pkg_vars_naming",
+ label = "amazing",
+ tags = ["manual"],
+ )
+
+ # Inner pkg_files with a literal prefix; variable substitution happens in
+ # the pkg_filegroup that wraps it.
+ pkg_files(
+ name = "pfg_pkg_vars_inner_files_g",
+ srcs = ["foo", "bar"],
+ prefix = "bin",
+ tags = ["manual"],
+ )
+
+ pkg_filegroup(
+ name = "pfg_with_package_variables_g",
+ srcs = [":pfg_pkg_vars_inner_files_g"],
+ prefix = "usr/$(label)",
+ package_variables = ":pfg_pkg_vars_naming",
+ tags = ["manual"],
+ )
+
+ # Reference target: the expected result after variable substitution.
+ pkg_files(
+ name = "pfg_pkg_vars_expected_g",
+ srcs = ["foo", "bar"],
+ prefix = "usr/amazing/bin",
+ tags = ["manual"],
+ )
+
+ pkg_filegroup_contents_test(
+ name = "pfg_with_package_variables",
+ target_under_test = ":pfg_with_package_variables_g",
+ expected_pkg_files = [":pfg_pkg_vars_expected_g"],
+ # Origins will differ (inner target vs. reference target); only check
+ # the resolved destinations.
+ verify_origins = False,
+ )
+
+##########
# Test strip_prefix pseudo-module
##########
@@ -930,6 +997,8 @@
# TODO(nacl) migrate the above to use a scheme the one used here. At the very
# least, the test suites should be easy to find/name.
_test_pkg_filegroup(name = "pfg_tests")
+ _test_pkg_files_package_variables()
+ _test_pkg_filegroup_package_variables()
native.test_suite(
name = "pkg_files_analysis_tests",
@@ -968,6 +1037,9 @@
":pkg_mklink_mode_overlay_if_not_provided",
# Tests involving pkg_filegroup
":pfg_tests",
+ # Tests for package_variables in prefix
+ ":pf_with_package_variables",
+ ":pfg_with_package_variables",
],
)