summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Kelly <mkelly@arista.com>2024-03-14 19:36:15 -0700
committerGitHub <noreply@github.com>2024-03-14 22:36:15 -0400
commit2aa2b8e842894e0c383817cdf3b1f4a6e96fcc3e (patch)
tree2912a8b72d1b2652b576e8c3e34113c8b6326bda
parent61132feb47add139967476401baa49cf4b7c6b2a (diff)
downloadbazelbuild-rules_pkg-2aa2b8e842894e0c383817cdf3b1f4a6e96fcc3e.tar.gz
Add pkg_sub_rpm rule for RPM subpackages (#824)
* rpm: Add support for sub packages to make_rpm.py script Before we can enable support for sub RPM building as part of a single `pkg_rpm()` rule we must add the underlying support to make_pkg.py which is the underlying driver for `pkg_rpm()`. This covers three pieces: * specifying `buildsubdir` rpm variable * capturing multiple RPM files as outputs * injecting sub RPM definitions into specfile * rpm: Factor out rpm_ctx helper The various processing functions pass around a bunch of collections everywhere which is a bit fragile. This collects them together into a struct to make it a bit less messy. * rpm: Factor out _process_dep() helper function _process_dep() handles processing an individual dep and is currently called from the processing loop. We'll need to re-use this logic for processing individual sub RPMs as well so we want it in a helper. * rpm: Capture generated output RPM files in rpm_ctx Currently we only generate one RPM file, but once we generate sub RPM files we'll need to be able to capture those outputs as well. This prepares us for that step. * rpm: Add args for make_rpm to rpm_ctx We'll need to add additional arguments to make_rpm for sub RPM building. It's easier to capture this in our context object than to try to shuttle these bits around. * rpm: Pass correct `--name` argument to make_rpm If we don't pass the correct RPM name to `make_rpm.py` than we won't be able to correctly determine the subrpm names. Currently, the name is only used by `make_rpm` to generate some progress output, so this shouldn't break anything internally. * rpm: Implementation of `pkg_sub_rpm` rule This introduces a `pkg_sub_rpm` rule that allows us to generate and capture RPM subpackages and generate them as part of a single RPM invocation in lieu of cobbling this together from multiple RPM rules. This has a few benefits: - faster execution due to single rpmbuild invocation - sharing configuration between RPMs in the same fashion as vanilla RPM building from a specfile - will enable the proper construction of debuginfo RPMs in a later PR The current implementation *only* works with non-specfile based rules and currently allows for a subset of the general RPM configuration. Internally, the process is for `pkg_sub_rpm` to generate a PackageSubRPMInfo provider that will subsequently get consumed by the `pkg_rpm` rule that generates the actual RPMs. We re-use the internal dependency processing logic that's used by the top-level RPM to generate the content related to the sub-RPM. * rpm: Update entry points for RPM rules to include pkg_sub_rpm This change updates `rpm.bzl` in two ways to account for sub RPMs: - it adds an entrypoint for `pkg_sub_rpm` - it adds a check in `pkg_rpm` to assert incompatibility with `spec_file` * examples: Add an example of how to use the subrpm rule This provides a basic example using the `pkg_sub_rpm` rule to generate multiple RPMs. * Fix buildifier noise * Fix make_rpm failures * doc: Clean up sub-RPM docstring and add to doc_build The initial docstring for pkg_sub_rpm is not great, so this change fixes that while adding this to the list of rules to have documentation generated for them. * doc: Additional documentation for subrpms in pkg_rpm This clarifies the `subrpms` attribute usage as well as indicating the incompatibility with `spec_file` mode. * Fix issue in subrpm passthrough When adding the check to verify if we can use subrpms, this pass through wasn't added. * Add a basic test for pkg_sub_rpm This introduces a basic test with a single sub RPM and main RPM each containing a single source file. * Tweaks to test * Test fixes for CentOS 7 The `rpm` version on CentOS 7 doesn't work exactly the same way as newer versions of rpm and requires a `-p` parameter to inspect non-installed RPMs. CentOS 7 also inserts a `Relocations` field that we didn't see on other platforms so we'll filter that out to make our test a bit more portable. * Move PackageSubRPMInfoProvider into rpm_pfg.bzl This is private to the RPM rules so should probably live there.
-rw-r--r--doc_build/BUILD1
-rw-r--r--examples/rpm/subrpm/BUILD79
-rw-r--r--examples/rpm/subrpm/MODULE.bazel32
-rw-r--r--examples/rpm/subrpm/README.md14
-rw-r--r--pkg/make_rpm.py64
-rw-r--r--pkg/rpm.bzl10
-rw-r--r--pkg/rpm/template.spec.tpl2
-rw-r--r--pkg/rpm_pfg.bzl536
-rw-r--r--tests/rpm/BUILD79
-rw-r--r--tests/rpm/make_rpm_test.py2
-rwxr-xr-xtests/rpm/test_sub_rpm_contents.txt.golden30
11 files changed, 659 insertions, 190 deletions
diff --git a/doc_build/BUILD b/doc_build/BUILD
index 725da8e..1292aa5 100644
--- a/doc_build/BUILD
+++ b/doc_build/BUILD
@@ -53,6 +53,7 @@ ORDER = [
("common", None),
("pkg_deb", "//pkg/private/deb:deb.bzl"),
("pkg_deb_impl", "//pkg/private/deb:deb.bzl"),
+ ("pkg_sub_rpm", "//pkg:rpm_pfg.bzl"),
("pkg_rpm", "//pkg:rpm_pfg.bzl"),
("pkg_tar", "//pkg/private/tar:tar.bzl"),
("pkg_tar_impl", "//pkg/private/tar:tar.bzl"),
diff --git a/examples/rpm/subrpm/BUILD b/examples/rpm/subrpm/BUILD
new file mode 100644
index 0000000..0da73ca
--- /dev/null
+++ b/examples/rpm/subrpm/BUILD
@@ -0,0 +1,79 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# -*- coding: utf-8 -*-
+
+load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
+load("@rules_pkg//pkg:rpm.bzl", "pkg_sub_rpm", "pkg_rpm")
+
+pkg_files(
+ name = "subrpm_files",
+ srcs = [
+ "BUILD",
+ ],
+)
+
+pkg_sub_rpm(
+ name = "subrpm",
+ package_name = "subrpm",
+ summary = "Test subrpm",
+ description = "Test subrpm description",
+ requires = [
+ "somerpm",
+ ],
+ provides = [
+ "someprovision",
+ ],
+ srcs = [
+ ":subrpm_files",
+ ],
+)
+
+pkg_files(
+ name = "rpm_files",
+ srcs = [
+ "MODULE.bazel",
+ "README.md",
+ ],
+)
+
+pkg_rpm(
+ name = "test-rpm",
+ srcs = [
+ ":rpm_files",
+ ],
+ release = "0",
+ version = "1",
+ summary = "rules_pkg example RPM",
+ description = "This is a package description.",
+ license = "Apache License, v2.0",
+ architecture = "x86_64",
+ requires = [
+ "somerpm",
+ ],
+ provides = [
+ "somefile",
+ ],
+ subrpms = [
+ ":subrpm",
+ ],
+)
+
+# If you have rpmbuild, you probably have rpm2cpio too.
+# Feature idea: Add rpm2cpio and cpio to the rpmbuild toolchain
+genrule(
+ name = "inspect_content",
+ srcs = [":test-rpm"],
+ outs = ["content.txt"],
+ cmd = "rpm2cpio $(locations :test-rpm) | cpio -ivt >$@",
+)
diff --git a/examples/rpm/subrpm/MODULE.bazel b/examples/rpm/subrpm/MODULE.bazel
new file mode 100644
index 0000000..b2d8a8f
--- /dev/null
+++ b/examples/rpm/subrpm/MODULE.bazel
@@ -0,0 +1,32 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module(name = "rules_pkg_example_rpm_system_rpmbuild_bzlmod")
+
+bazel_dep(name = "rules_pkg")
+
+local_path_override(
+ module_name = "rules_pkg",
+ path = "../../..",
+)
+
+find_rpmbuild = use_extension(
+ "@rules_pkg//toolchains/rpm:rpmbuild_configure.bzl",
+ "find_system_rpmbuild_bzlmod",
+)
+use_repo(find_rpmbuild, "rules_pkg_rpmbuild")
+
+register_toolchains(
+ "@rules_pkg_rpmbuild//:all",
+)
diff --git a/examples/rpm/subrpm/README.md b/examples/rpm/subrpm/README.md
new file mode 100644
index 0000000..1229e8a
--- /dev/null
+++ b/examples/rpm/subrpm/README.md
@@ -0,0 +1,14 @@
+# Using system rpmbuild with bzlmod
+
+## Summary
+
+This example builds an RPM using the system `rpmbuild` from a pure bazel
+`pkg_rpm()` definition instead of using a separate specfile.
+
+## To use
+
+```
+bazel build :*
+rpm2cpio bazel-bin/test-rpm.rpm | cpio -ivt
+cat bazel-bin/content.txt
+```
diff --git a/pkg/make_rpm.py b/pkg/make_rpm.py
index dd30a49..28c8e89 100644
--- a/pkg/make_rpm.py
+++ b/pkg/make_rpm.py
@@ -81,10 +81,9 @@ WROTE_FILE_RE = re.compile(r'Wrote: (?P<rpm_path>.+)', re.MULTILINE)
def FindOutputFile(log):
"""Find the written file from the log information."""
-
- m = WROTE_FILE_RE.search(log)
+ m = WROTE_FILE_RE.findall(log)
if m:
- return m.group('rpm_path')
+ return m
return None
def SlurpFile(input_path):
@@ -187,7 +186,7 @@ class RpmBuilder(object):
self.arch = arch
self.files = []
self.rpmbuild_path = FindRpmbuild(rpmbuild_path)
- self.rpm_path = None
+ self.rpm_paths = None
self.source_date_epoch = helpers.GetFlagValue(source_date_epoch)
self.debug = debug
@@ -204,6 +203,7 @@ class RpmBuilder(object):
self.post_scriptlet = None
self.preun_scriptlet = None
self.postun_scriptlet = None
+ self.subrpms = None
def AddFiles(self, paths, root=''):
"""Add a set of files to the current RPM.
@@ -227,6 +227,7 @@ class RpmBuilder(object):
preamble_file=None,
description_file=None,
install_script_file=None,
+ subrpms_file=None,
pre_scriptlet_path=None,
post_scriptlet_path=None,
preun_scriptlet_path=None,
@@ -267,6 +268,8 @@ class RpmBuilder(object):
SlurpFile(os.path.join(original_dir, postun_scriptlet_path)) if postun_scriptlet_path is not None else ''
self.posttrans_scriptlet = \
SlurpFile(os.path.join(original_dir, posttrans_scriptlet_path)) if posttrans_scriptlet_path is not None else ''
+ self.subrpms = \
+ SlurpFile(os.path.join(original_dir, subrpms_file)) if subrpms_file is not None else ''
# Then prepare for textual substitution. This is typically only the case for the
# experimental `pkg_rpm`.
@@ -276,6 +279,7 @@ class RpmBuilder(object):
'PREUN_SCRIPTLET': ("%preun\n" + self.preun_scriptlet) if self.preun_scriptlet else "",
'POSTUN_SCRIPTLET': ("%postun\n" + self.postun_scriptlet) if self.postun_scriptlet else "",
'POSTTRANS_SCRIPTLET': ("%posttrans\n" + self.posttrans_scriptlet) if self.posttrans_scriptlet else "",
+ 'SUBRPMS' : (self.subrpms if self.subrpms else ""),
'CHANGELOG': ""
}
@@ -362,6 +366,7 @@ class RpmBuilder(object):
'--define', '_topdir %s' % dirname,
'--define', '_tmppath %s/TMP' % dirname,
'--define', '_builddir %s/BUILD' % dirname,
+ '--define', 'buildsubdir .',
'--bb',
'--buildroot=%s' % buildroot,
] # yapf: disable
@@ -405,9 +410,9 @@ class RpmBuilder(object):
if p.returncode == 0:
# Find the created file.
- self.rpm_path = FindOutputFile(output)
+ self.rpm_paths = FindOutputFile(output)
- if p.returncode != 0 or not self.rpm_path:
+ if p.returncode != 0 or not self.rpm_paths:
print('Error calling rpmbuild:')
print(output)
elif self.debug:
@@ -416,20 +421,35 @@ class RpmBuilder(object):
# Return the status.
return p.returncode
- def SaveResult(self, out_file):
+ def SaveResult(self, out_file, subrpm_out_files):
"""Save the result RPM out of the temporary working directory."""
-
- if self.rpm_path:
- shutil.copy(self.rpm_path, out_file)
- if self.debug:
- print('Saved RPM file to %s' % out_file)
+ if self.rpm_paths:
+ for p in self.rpm_paths:
+ is_subrpm = False
+
+ for subrpm_name, subrpm_out_file in subrpm_out_files:
+ subrpm_prefix = self.name + '-' + subrpm_name
+
+ if os.path.basename(p).startswith(subrpm_prefix):
+ shutil.copy(p, subrpm_out_file)
+ is_subrpm = True
+ if self.debug or True:
+ print('Saved %s sub RPM file to %s' % (
+ subrpm_name, subrpm_out_file))
+ break
+
+ if not is_subrpm:
+ shutil.copy(p, out_file)
+ if self.debug or True:
+ print('Saved RPM file to %s' % out_file)
else:
print('No RPM file created.')
- def Build(self, spec_file, out_file,
+ def Build(self, spec_file, out_file, subrpm_out_files=None,
preamble_file=None,
description_file=None,
install_script_file=None,
+ subrpms_file=None,
pre_scriptlet_path=None,
post_scriptlet_path=None,
preun_scriptlet_path=None,
@@ -446,12 +466,21 @@ class RpmBuilder(object):
original_dir = os.getcwd()
spec_file = os.path.join(original_dir, spec_file)
out_file = os.path.join(original_dir, out_file)
+
+ if subrpm_out_files is not None:
+ subrpm_out_files = (s.split(':') for s in subrpm_out_files)
+ subrpm_out_files = [
+ (s[0], os.path.join(original_dir, s[1])) for s in subrpm_out_files]
+ else:
+ subrpm_out_files = []
+
with Tempdir() as dirname:
self.SetupWorkdir(spec_file,
original_dir,
preamble_file=preamble_file,
description_file=description_file,
install_script_file=install_script_file,
+ subrpms_file=subrpms_file,
file_list_path=file_list_path,
pre_scriptlet_path=pre_scriptlet_path,
post_scriptlet_path=post_scriptlet_path,
@@ -460,7 +489,7 @@ class RpmBuilder(object):
posttrans_scriptlet_path=posttrans_scriptlet_path,
changelog_file=changelog_file)
status = self.CallRpmBuild(dirname, rpmbuild_args or [])
- self.SaveResult(out_file)
+ self.SaveResult(out_file, subrpm_out_files)
return status
@@ -483,6 +512,9 @@ def main(argv):
help='The file containing the RPM specification.')
parser.add_argument('--out_file', required=True,
help='The destination to save the resulting RPM file to.')
+ parser.add_argument('--subrpm_out_file', action='append',
+ help='List of destinations to save resulting ' +
+ 'Sub RPMs to in the form of name:destination')
parser.add_argument('--rpmbuild', help='Path to rpmbuild executable.')
parser.add_argument('--source_date_epoch',
help='Value for the SOURCE_DATE_EPOCH rpmbuild '
@@ -499,6 +531,8 @@ def main(argv):
help='File containing the RPM Preamble')
parser.add_argument('--description',
help='File containing the RPM %description text')
+ parser.add_argument('--subrpms',
+ help='File containing the RPM subrpm details')
parser.add_argument('--pre_scriptlet',
help='File containing the RPM %pre scriptlet, if to be substituted')
parser.add_argument('--post_scriptlet',
@@ -526,9 +560,11 @@ def main(argv):
debug=options.debug)
builder.AddFiles(options.files)
return builder.Build(options.spec_file, options.out_file,
+ options.subrpm_out_file,
preamble_file=options.preamble,
description_file=options.description,
install_script_file=options.install_script,
+ subrpms_file=options.subrpms,
file_list_path=options.file_list,
pre_scriptlet_path=options.pre_scriptlet,
post_scriptlet_path=options.post_scriptlet,
diff --git a/pkg/rpm.bzl b/pkg/rpm.bzl
index 2fc5804..87cef3c 100644
--- a/pkg/rpm.bzl
+++ b/pkg/rpm.bzl
@@ -29,10 +29,12 @@ The mechanism for choosing between the two is documented in the function itself.
"""
-load("//pkg:rpm_pfg.bzl", pkg_rpm_pfg = "pkg_rpm")
+load("//pkg:rpm_pfg.bzl", _pkg_sub_rpm = "pkg_sub_rpm", pkg_rpm_pfg = "pkg_rpm")
load("//pkg/legacy:rpm.bzl", pkg_rpm_legacy = "pkg_rpm")
-def pkg_rpm(name, srcs = None, spec_file = None, **kwargs):
+pkg_sub_rpm = _pkg_sub_rpm
+
+def pkg_rpm(name, srcs = None, spec_file = None, subrpms = None, **kwargs):
"""pkg_rpm wrapper
This rule selects between the two implementations of pkg_rpm as described in
@@ -54,6 +56,9 @@ def pkg_rpm(name, srcs = None, spec_file = None, **kwargs):
if srcs and spec_file:
fail("Cannot determine which pkg_rpm rule to use. `srcs` and `spec_file` are mutually exclusive")
+ if subrpms and spec_file:
+ fail("Cannot build sub RPMs with a specfile. `subrpms` and `spec_file` are mutually exclusive")
+
if not srcs and not spec_file:
fail("Either `srcs` or `spec_file` must be provided.")
@@ -61,6 +66,7 @@ def pkg_rpm(name, srcs = None, spec_file = None, **kwargs):
pkg_rpm_pfg(
name = name,
srcs = srcs,
+ subrpms = subrpms,
**kwargs
)
elif spec_file:
diff --git a/pkg/rpm/template.spec.tpl b/pkg/rpm/template.spec.tpl
index ba25db8..839a48d 100644
--- a/pkg/rpm/template.spec.tpl
+++ b/pkg/rpm/template.spec.tpl
@@ -21,4 +21,6 @@ ${POSTUN_SCRIPTLET}
${POSTTRANS_SCRIPTLET}
+${SUBRPMS}
+
${CHANGELOG}
diff --git a/pkg/rpm_pfg.bzl b/pkg/rpm_pfg.bzl
index 0ccdbaf..b0a8695 100644
--- a/pkg/rpm_pfg.bzl
+++ b/pkg/rpm_pfg.bzl
@@ -39,6 +39,22 @@ rpm_filetype = [".rpm"]
spec_filetype = [".spec", ".spec.in", ".spec.tpl"]
+PackageSubRPMInfo = provider(
+ doc = """Provider representing a sub-RPM that can be built as part of a larger RPM""",
+ fields = {
+ "package_name": "name of the subpackage",
+ "summary": "RPM subpackage `Summary` tag",
+ "group": "RPM subpackage `Group` tag",
+ "description": "Multi-line description of this subpackage",
+ "post_scriptlet": "RPM `$post` scriplet for this subpackage",
+ "architecture": "Subpackage architecture",
+ "version": "RPM `Version` tag for this subpackage",
+ "requires": "List of RPM capability expressions that this package requires",
+ "provides": "List of RPM capability expressions that this package provides",
+ "srcs": "Mapping groups to include in this RPM",
+ },
+)
+
# TODO(nacl): __install, __cp
# {0} is the source, {1} is the dest
#
@@ -151,20 +167,20 @@ def _make_absolute_if_not_already_or_is_macro(path):
# TODO(nacl, #459): These are redundant with functions and structures in
# pkg/private/pkg_files.bzl. We should really use the infrastructure provided
# there, but as of writing, it's not quite ready.
-def _process_files(pfi, origin_label, grouping_label, file_base, dest_check_map, packaged_directories, rpm_files_list, install_script_pieces):
+def _process_files(pfi, origin_label, grouping_label, file_base, rpm_ctx):
for dest, src in pfi.dest_src_map.items():
metadata = _package_contents_metadata(origin_label, grouping_label)
- if dest in dest_check_map:
- _conflicting_contents_error(dest, metadata, dest_check_map[dest])
+ if dest in rpm_ctx.dest_check_map:
+ _conflicting_contents_error(dest, metadata, rpm_ctx.dest_check_map[dest])
else:
- dest_check_map[dest] = metadata
+ rpm_ctx.dest_check_map[dest] = metadata
abs_dest = _make_absolute_if_not_already_or_is_macro(dest)
if src.is_directory:
# Set aside TreeArtifact information for external processing
#
# @unsorted-dict-items
- packaged_directories.append({
+ rpm_ctx.packaged_directories.append({
"src": src,
"dest": abs_dest,
# This doesn't exactly make it extensible, but it saves
@@ -174,56 +190,233 @@ def _process_files(pfi, origin_label, grouping_label, file_base, dest_check_map,
})
else:
# Files are well-known. Take care of them right here.
- rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dest))
- install_script_pieces.append(_INSTALL_FILE_STANZA_FMT.format(
+ rpm_ctx.rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dest))
+ rpm_ctx.install_script_pieces.append(_INSTALL_FILE_STANZA_FMT.format(
src.path,
abs_dest,
))
-def _process_dirs(pdi, origin_label, grouping_label, file_base, dest_check_map, _, rpm_files_list, install_script_pieces):
+def _process_dirs(pdi, origin_label, grouping_label, file_base, rpm_ctx):
for dest in pdi.dirs:
metadata = _package_contents_metadata(origin_label, grouping_label)
- if dest in dest_check_map:
- _conflicting_contents_error(dest, metadata, dest_check_map[dest])
+ if dest in rpm_ctx.dest_check_map:
+ _conflicting_contents_error(dest, metadata, rpm_ctx.dest_check_map[dest])
else:
- dest_check_map[dest] = metadata
+ rpm_ctx.dest_check_map[dest] = metadata
abs_dirname = _make_absolute_if_not_already_or_is_macro(dest)
- rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dirname))
+ rpm_ctx.rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dirname))
- install_script_pieces.append(_INSTALL_DIR_STANZA_FMT.format(
+ rpm_ctx.install_script_pieces.append(_INSTALL_DIR_STANZA_FMT.format(
abs_dirname,
))
-def _process_symlink(psi, origin_label, grouping_label, file_base, dest_check_map, _, rpm_files_list, install_script_pieces):
+def _process_symlink(psi, origin_label, grouping_label, file_base, rpm_ctx):
metadata = _package_contents_metadata(origin_label, grouping_label)
- if psi.destination in dest_check_map:
- _conflicting_contents_error(psi.destination, metadata, dest_check_map[psi.destination])
+ if psi.destination in rpm_ctx.dest_check_map:
+ _conflicting_contents_error(psi.destination, metadata, rpm_ctx.dest_check_map[psi.destination])
else:
- dest_check_map[psi.destination] = metadata
+ rpm_ctx.dest_check_map[psi.destination] = metadata
abs_dest = _make_absolute_if_not_already_or_is_macro(psi.destination)
- rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dest))
- install_script_pieces.append(_INSTALL_SYMLINK_STANZA_FMT.format(
+ rpm_ctx.rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dest))
+ rpm_ctx.install_script_pieces.append(_INSTALL_SYMLINK_STANZA_FMT.format(
abs_dest,
psi.target,
psi.attributes["mode"],
))
+def _process_dep(dep, rpm_ctx):
+ # NOTE: This does not detect cases where directories are not named
+ # consistently. For example, all of these may collide in reality, but
+ # won't be detected by the below:
+ #
+ # 1) usr/lib/libfoo.a
+ # 2) /usr/lib/libfoo.a
+ # 3) %{_libdir}/libfoo.a
+ #
+ # The most important thing, regardless of how these checks below are
+ # done, is to be consistent with path naming conventions.
+ #
+ # There is also an unsolved question of determining how to handle
+ # subdirectories of "PackageFilesInfo" targets that are actually
+ # directories.
+
+ # dep is a Target
+ if PackageFilesInfo in dep:
+ _process_files(
+ dep[PackageFilesInfo],
+ dep.label, # origin label
+ None, # group label
+ _make_filetags(dep[PackageFilesInfo].attributes), # file_base
+ rpm_ctx,
+ )
+
+ if PackageDirsInfo in dep:
+ _process_dirs(
+ dep[PackageDirsInfo],
+ dep.label, # origin label
+ None, # group label
+ _make_filetags(dep[PackageDirsInfo].attributes, "%dir"), # file_base
+ rpm_ctx,
+ )
+
+ if PackageSymlinkInfo in dep:
+ _process_symlink(
+ dep[PackageSymlinkInfo],
+ dep.label, # origin label
+ None, # group label
+ _make_filetags(dep[PackageSymlinkInfo].attributes), # file_base
+ rpm_ctx,
+ )
+
+ if PackageFilegroupInfo in dep:
+ pfg_info = dep[PackageFilegroupInfo]
+ for entry, origin in pfg_info.pkg_files:
+ file_base = _make_filetags(entry.attributes)
+ _process_files(
+ entry,
+ origin,
+ dep.label,
+ file_base,
+ rpm_ctx,
+ )
+ for entry, origin in pfg_info.pkg_dirs:
+ file_base = _make_filetags(entry.attributes, "%dir")
+ _process_dirs(
+ entry,
+ origin,
+ dep.label,
+ file_base,
+ rpm_ctx,
+ )
+
+ for entry, origin in pfg_info.pkg_symlinks:
+ file_base = _make_filetags(entry.attributes)
+ _process_symlink(
+ entry,
+ origin,
+ dep.label,
+ file_base,
+ rpm_ctx,
+ )
+
+def _process_subrpm(ctx, rpm_name, rpm_info, rpm_ctx):
+ sub_rpm_ctx = struct(
+ dest_check_map = {},
+ install_script_pieces = [],
+ packaged_directories = [],
+ rpm_files_list = [],
+ )
+
+ rpm_lines = [
+ "%%package %s" % rpm_info.package_name,
+ "Summary: %s" % rpm_info.summary,
+ ]
+
+ if rpm_info.architecture:
+ rpm_lines += ["BuildArch: %s" % rpm_info.architecture]
+
+ if rpm_info.version:
+ rpm_lines += ["Version: %s" % rpm_info.version]
+
+ for r in rpm_info.requires:
+ rpm_lines += ["Requires: %s" % r]
+
+ for p in rpm_info.provides:
+ rpm_lines += ["Provides: %s" % p]
+
+ rpm_lines += [
+ "",
+ "%%description %s" % rpm_info.package_name,
+ rpm_info.description,
+ ]
+
+ if rpm_info.post_scriptlet:
+ rpm_lines += [
+ "",
+ "%%post %s" % rpm_info.package_name,
+ ]
+
+ if rpm_info.srcs:
+ rpm_lines += [
+ "",
+ "%%files %s" % rpm_info.package_name,
+ ]
+
+ for dep in rpm_info.srcs:
+ _process_dep(dep, sub_rpm_ctx)
+
+ rpm_lines += sub_rpm_ctx.rpm_files_list
+ rpm_lines += [""]
+
+ rpm_ctx.install_script_pieces.extend(sub_rpm_ctx.install_script_pieces)
+ rpm_ctx.packaged_directories.extend(sub_rpm_ctx.packaged_directories)
+
+ package_file_name = "%s-%s-%s-%s.%s.rpm" % (
+ rpm_name,
+ rpm_info.package_name,
+ rpm_info.version or ctx.attr.version,
+ ctx.attr.release,
+ rpm_info.architecture or ctx.attr.architecture,
+ )
+
+ default_file = ctx.actions.declare_file("{}-{}.rpm".format(rpm_name, rpm_info.package_name))
+
+ _, output_file, _ = setup_output_files(
+ ctx,
+ package_file_name = package_file_name,
+ default_output_file = default_file,
+ )
+
+ rpm_ctx.output_rpm_files.append(output_file)
+ rpm_ctx.make_rpm_args.append("--subrpm_out_file=%s:%s" % (
+ rpm_info.package_name,
+ output_file.path,
+ ))
+
+ return rpm_lines
+
#### Rule implementation
def _pkg_rpm_impl(ctx):
"""Implements the pkg_rpm rule."""
+ rpm_ctx = struct(
+ # Ensure that no destinations collide. RPMs that fail this check may be
+ # correct, but the output may also create hard-to-debug issues. Better
+ # to err on the side of correctness here.
+ dest_check_map = {},
+
+ # The contents of the "%install" scriptlet
+ install_script_pieces = [],
+
+ # The list of entries in the "%files" list
+ rpm_files_list = [],
+
+ # Directories (TreeArtifacts) are to be treated differently.
+ # Specifically, since Bazel does not know their contents at analysis
+ # time, processing them needs to be delegated to a helper script. This
+ # is done via the _treeartifact_helper script used later on.
+ packaged_directories = [],
+
+ # RPM files we expect to generate
+ output_rpm_files = [],
+
+ # Arguments that we pass to make_rpm.py
+ make_rpm_args = [],
+ )
+
files = []
tools = []
- args = ["--name=" + ctx.label.name]
+ name = ctx.attr.package_name if ctx.attr.package_name else ctx.label.name
+ rpm_ctx.make_rpm_args.append("--name=" + name)
if ctx.attr.debug:
- args.append("--debug")
+ rpm_ctx.make_rpm_args.append("--debug")
if ctx.attr.rpmbuild_path:
- args.append("--rpmbuild=" + ctx.attr.rpmbuild_path)
+ rpm_ctx.make_rpm_args.append("--rpmbuild=" + ctx.attr.rpmbuild_path)
# buildifier: disable=print
print("rpmbuild_path is deprecated. See the README for instructions on how" +
@@ -234,11 +427,11 @@ def _pkg_rpm_impl(ctx):
fail("The rpmbuild_toolchain is not properly configured: " +
toolchain.name)
if toolchain.path:
- args.append("--rpmbuild=" + toolchain.path)
+ rpm_ctx.make_rpm_args.append("--rpmbuild=" + toolchain.path)
else:
executable_files = toolchain.label[DefaultInfo].files_to_run
tools.append(executable_files)
- args.append("--rpmbuild=%s" % executable_files.executable.path)
+ rpm_ctx.make_rpm_args.append("--rpmbuild=%s" % executable_files.executable.path)
#### Calculate output file name
# rpm_name takes precedence over name if provided
@@ -258,12 +451,6 @@ def _pkg_rpm_impl(ctx):
ctx.attr.architecture,
)
- _, output_file, _ = setup_output_files(
- ctx,
- package_file_name = package_file_name,
- default_output_file = default_file,
- )
-
#### rpm spec "preamble"
preamble_pieces = []
@@ -275,7 +462,7 @@ def _pkg_rpm_impl(ctx):
fail("Both version and version_file attributes were specified")
preamble_pieces.append("Version: ${{VERSION_FROM_FILE}}")
- args.append("--version=@" + ctx.file.version_file.path)
+ rpm_ctx.make_rpm_args.append("--version=@" + ctx.file.version_file.path)
files.append(ctx.file.version_file)
elif ctx.attr.version:
preamble_pieces.append("Version: " + ctx.attr.version)
@@ -288,7 +475,7 @@ def _pkg_rpm_impl(ctx):
fail("Both release and release_file attributes were specified")
preamble_pieces.append("Release: ${{RELEASE_FROM_FILE}}")
- args.append("--release=@" + ctx.file.release_file.path)
+ rpm_ctx.make_rpm_args.append("--release=@" + ctx.file.release_file.path)
files.append(ctx.file.release_file)
elif ctx.attr.release:
preamble_pieces.append("Release: " + ctx.attr.release)
@@ -304,10 +491,10 @@ def _pkg_rpm_impl(ctx):
if ctx.attr.source_date_epoch_file:
if ctx.attr.source_date_epoch >= 0:
fail("Both source_date_epoch and source_date_epoch_file attributes were specified")
- args.append("--source_date_epoch=@" + ctx.file.source_date_epoch_file.path)
+ rpm_ctx.make_rpm_args.append("--source_date_epoch=@" + ctx.file.source_date_epoch_file.path)
files.append(ctx.file.source_date_epoch_file)
elif ctx.attr.source_date_epoch >= 0:
- args.append("--source_date_epoch=" + str(ctx.attr.source_date_epoch))
+ rpm_ctx.make_rpm_args.append("--source_date_epoch=" + str(ctx.attr.source_date_epoch))
if ctx.attr.summary:
preamble_pieces.append("Summary: " + ctx.attr.summary)
@@ -352,7 +539,7 @@ def _pkg_rpm_impl(ctx):
content = substitute_package_variables(ctx, "\n".join(preamble_pieces)),
)
files.append(preamble_file)
- args.append("--preamble=" + preamble_file.path)
+ rpm_ctx.make_rpm_args.append("--preamble=" + preamble_file.path)
#### %description
@@ -372,11 +559,11 @@ def _pkg_rpm_impl(ctx):
fail("None of the description or description_file attributes were specified")
files.append(description_file)
- args.append("--description=" + description_file.path)
+ rpm_ctx.make_rpm_args.append("--description=" + description_file.path)
if ctx.attr.changelog:
files.append(ctx.file.changelog)
- args.append("--changelog=" + ctx.file.changelog.path)
+ rpm_ctx.make_rpm_args.append("--changelog=" + ctx.file.changelog.path)
#### Non-procedurally-generated scriptlets
@@ -386,60 +573,60 @@ def _pkg_rpm_impl(ctx):
fail("Both pre_scriptlet and pre_scriptlet_file attributes were specified")
pre_scriptlet_file = ctx.file.pre_scriptlet_file
files.append(pre_scriptlet_file)
- args.append("--pre_scriptlet=" + pre_scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--pre_scriptlet=" + pre_scriptlet_file.path)
elif ctx.attr.pre_scriptlet:
scriptlet_file = ctx.actions.declare_file(ctx.label.name + ".pre_scriptlet")
files.append(scriptlet_file)
ctx.actions.write(scriptlet_file, ctx.attr.pre_scriptlet)
- args.append("--pre_scriptlet=" + scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--pre_scriptlet=" + scriptlet_file.path)
if ctx.attr.post_scriptlet_file:
if ctx.attr.post_scriptlet:
fail("Both post_scriptlet and post_scriptlet_file attributes were specified")
post_scriptlet_file = ctx.file.post_scriptlet_file
files.append(post_scriptlet_file)
- args.append("--post_scriptlet=" + post_scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--post_scriptlet=" + post_scriptlet_file.path)
elif ctx.attr.post_scriptlet:
scriptlet_file = ctx.actions.declare_file(ctx.label.name + ".post_scriptlet")
files.append(scriptlet_file)
ctx.actions.write(scriptlet_file, ctx.attr.post_scriptlet)
- args.append("--post_scriptlet=" + scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--post_scriptlet=" + scriptlet_file.path)
if ctx.attr.preun_scriptlet_file:
if ctx.attr.preun_scriptlet:
fail("Both preun_scriptlet and preun_scriptlet_file attributes were specified")
preun_scriptlet_file = ctx.file.preun_scriptlet_file
files.append(preun_scriptlet_file)
- args.append("--preun_scriptlet=" + preun_scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--preun_scriptlet=" + preun_scriptlet_file.path)
elif ctx.attr.preun_scriptlet:
scriptlet_file = ctx.actions.declare_file(ctx.label.name + ".preun_scriptlet")
files.append(scriptlet_file)
ctx.actions.write(scriptlet_file, ctx.attr.preun_scriptlet)
- args.append("--preun_scriptlet=" + scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--preun_scriptlet=" + scriptlet_file.path)
if ctx.attr.postun_scriptlet_file:
if ctx.attr.postun_scriptlet:
fail("Both postun_scriptlet and postun_scriptlet_file attributes were specified")
postun_scriptlet_file = ctx.file.postun_scriptlet_file
files.append(postun_scriptlet_file)
- args.append("--postun_scriptlet=" + postun_scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--postun_scriptlet=" + postun_scriptlet_file.path)
elif ctx.attr.postun_scriptlet:
scriptlet_file = ctx.actions.declare_file(ctx.label.name + ".postun_scriptlet")
files.append(scriptlet_file)
ctx.actions.write(scriptlet_file, ctx.attr.postun_scriptlet)
- args.append("--postun_scriptlet=" + scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--postun_scriptlet=" + scriptlet_file.path)
if ctx.attr.posttrans_scriptlet_file:
if ctx.attr.posttrans_scriptlet:
fail("Both posttrans_scriptlet and posttrans_scriptlet_file attributes were specified")
posttrans_scriptlet_file = ctx.file.posttrans_scriptlet_file
files.append(posttrans_scriptlet_file)
- args.append("--posttrans_scriptlet=" + posttrans_scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--posttrans_scriptlet=" + posttrans_scriptlet_file.path)
elif ctx.attr.posttrans_scriptlet:
scriptlet_file = ctx.actions.declare_file(ctx.label.name + ".posttrans_scriptlet")
files.append(scriptlet_file)
ctx.actions.write(scriptlet_file, ctx.attr.posttrans_scriptlet)
- args.append("--posttrans_scriptlet=" + scriptlet_file.path)
+ rpm_ctx.make_rpm_args.append("--posttrans_scriptlet=" + scriptlet_file.path)
#### Expand the spec file template; prepare data files
@@ -449,34 +636,23 @@ def _pkg_rpm_impl(ctx):
output = spec_file,
substitutions = substitutions,
)
- args.append("--spec_file=" + spec_file.path)
+ rpm_ctx.make_rpm_args.append("--spec_file=" + spec_file.path)
files.append(spec_file)
- args.append("--out_file=" + output_file.path)
-
# Add data files
- files += ctx.files.srcs
+ files += ctx.files.srcs + ctx.files.subrpms
- #### Consistency checking; input processing
+ _, output_file, _ = setup_output_files(
+ ctx,
+ package_file_name = package_file_name,
+ default_output_file = default_file,
+ )
- # Ensure that no destinations collide. RPMs that fail this check may be
- # correct, but the output may also create hard-to-debug issues. Better to
- # err on the side of correctness here.
- dest_check_map = {}
+ rpm_ctx.make_rpm_args.append("--out_file=" + output_file.path)
+ rpm_ctx.output_rpm_files.append(output_file)
- # The contents of the "%install" scriptlet
- install_script_pieces = []
if ctx.attr.debug:
- install_script_pieces.append("set -x")
-
- # The list of entries in the "%files" list
- rpm_files_list = []
-
- # Directories (TreeArtifacts) are to be treated differently. Specifically,
- # since Bazel does not know their contents at analysis time, processing them
- # needs to be delegated to a helper script. This is done via the
- # _treeartifact_helper script used later on.
- packaged_directories = []
+ rpm_ctx.install_script_pieces.append("set -x")
# Iterate over all incoming data, checking for conflicts and creating
# datasets as we go from the actual contents of the RPM.
@@ -485,99 +661,32 @@ def _pkg_rpm_impl(ctx):
# produce an installation script that is longer than necessary. A better
# implementation would track directories that are created and ensure that
# they aren't unnecessarily recreated.
- for dep in ctx.attr.srcs:
- # NOTE: This does not detect cases where directories are not named
- # consistently. For example, all of these may collide in reality, but
- # won't be detected by the below:
- #
- # 1) usr/lib/libfoo.a
- # 2) /usr/lib/libfoo.a
- # 3) %{_libdir}/libfoo.a
- #
- # The most important thing, regardless of how these checks below are
- # done, is to be consistent with path naming conventions.
- #
- # There is also an unsolved question of determining how to handle
- # subdirectories of "PackageFilesInfo" targets that are actually
- # directories.
- # dep is a Target
- if PackageFilesInfo in dep:
- _process_files(
- dep[PackageFilesInfo],
- dep.label, # origin label
- None, # group label
- _make_filetags(dep[PackageFilesInfo].attributes), # file_base
- dest_check_map,
- packaged_directories,
- rpm_files_list,
- install_script_pieces,
- )
+ for dep in ctx.attr.srcs:
+ _process_dep(dep, rpm_ctx)
- if PackageDirsInfo in dep:
- _process_dirs(
- dep[PackageDirsInfo],
- dep.label, # origin label
- None, # group label
- _make_filetags(dep[PackageDirsInfo].attributes, "%dir"), # file_base
- dest_check_map,
- packaged_directories,
- rpm_files_list,
- install_script_pieces,
- )
+ #### subrpms
+ subrpm_file = ctx.actions.declare_file(
+ "{}.spec.subrpms".format(rpm_name),
+ )
+ if ctx.attr.subrpms:
+ subrpm_lines = []
- if PackageSymlinkInfo in dep:
- _process_symlink(
- dep[PackageSymlinkInfo],
- dep.label, # origin label
- None, # group label
- _make_filetags(dep[PackageSymlinkInfo].attributes), # file_base
- dest_check_map,
- packaged_directories,
- rpm_files_list,
- install_script_pieces,
- )
+ for s in ctx.attr.subrpms:
+ subrpm_lines += _process_subrpm(ctx, rpm_name, s[PackageSubRPMInfo], rpm_ctx)
- if PackageFilegroupInfo in dep:
- pfg_info = dep[PackageFilegroupInfo]
- for entry, origin in pfg_info.pkg_files:
- file_base = _make_filetags(entry.attributes)
- _process_files(
- entry,
- origin,
- dep.label,
- file_base,
- dest_check_map,
- packaged_directories,
- rpm_files_list,
- install_script_pieces,
- )
- for entry, origin in pfg_info.pkg_dirs:
- file_base = _make_filetags(entry.attributes, "%dir")
- _process_dirs(
- entry,
- origin,
- dep.label,
- file_base,
- dest_check_map,
- packaged_directories,
- rpm_files_list,
- install_script_pieces,
- )
-
- for entry, origin in pfg_info.pkg_symlinks:
- file_base = _make_filetags(entry.attributes)
- _process_symlink(
- entry,
- origin,
- dep.label,
- file_base,
- dest_check_map,
- packaged_directories,
- rpm_files_list,
- install_script_pieces,
- )
+ ctx.actions.write(
+ output = subrpm_file,
+ content = "\n".join(subrpm_lines),
+ )
+ else:
+ ctx.actions.write(
+ output = subrpm_file,
+ content = "# no subrpms",
+ )
+ files.append(subrpm_file)
+ rpm_ctx.make_rpm_args.append("--subrpms=" + subrpm_file.path)
#### Procedurally-generated scripts/lists (%install, %files)
# We need to write these out regardless of whether we are using
@@ -585,7 +694,7 @@ def _pkg_rpm_impl(ctx):
install_script = ctx.actions.declare_file("{}.spec.install".format(rpm_name))
ctx.actions.write(
install_script,
- "\n".join(install_script_pieces),
+ "\n".join(rpm_ctx.install_script_pieces),
)
rpm_files_file = ctx.actions.declare_file(
@@ -593,14 +702,14 @@ def _pkg_rpm_impl(ctx):
)
ctx.actions.write(
rpm_files_file,
- "\n".join(rpm_files_list),
+ "\n".join(rpm_ctx.rpm_files_list),
)
# TreeArtifact processing work
- if packaged_directories:
+ if rpm_ctx.packaged_directories:
packaged_directories_file = ctx.actions.declare_file("{}.spec.packaged_directories.json".format(rpm_name))
- packaged_directories_inputs = [d["src"] for d in packaged_directories]
+ packaged_directories_inputs = [d["src"] for d in rpm_ctx.packaged_directories]
# This isn't the prettiest thing in the world, but it works. Bazel
# needs the "File" data to pass to the command, but "File"s cannot be
@@ -609,10 +718,10 @@ def _pkg_rpm_impl(ctx):
# This data isn't used outside of this block, so it's probably fine.
# Cleaner code would separate the JSONable values from the File type (in
# a struct, probably).
- for d in packaged_directories:
+ for d in rpm_ctx.packaged_directories:
d["src"] = d["src"].path
- ctx.actions.write(packaged_directories_file, json.encode(packaged_directories))
+ ctx.actions.write(packaged_directories_file, json.encode(rpm_ctx.packaged_directories))
# Overwrite all following uses of the install script and files lists to
# use the ones generated below.
@@ -640,10 +749,10 @@ def _pkg_rpm_impl(ctx):
# And then we're done. Yay!
files.append(install_script)
- args.append("--install_script=" + install_script.path)
+ rpm_ctx.make_rpm_args.append("--install_script=" + install_script.path)
files.append(rpm_files_file)
- args.append("--file_list=" + rpm_files_file.path)
+ rpm_ctx.make_rpm_args.append("--file_list=" + rpm_files_file.path)
#### Remaining setup
@@ -660,10 +769,10 @@ def _pkg_rpm_impl(ctx):
"{} {}".format(key, value),
])
- args.extend(["--rpmbuild_arg=" + a for a in additional_rpmbuild_args])
+ rpm_ctx.make_rpm_args.extend(["--rpmbuild_arg=" + a for a in additional_rpmbuild_args])
- for f in ctx.files.srcs:
- args.append(f.path)
+ for f in ctx.files.srcs + ctx.files.subrpms:
+ rpm_ctx.make_rpm_args.append(f.path)
#### Call the generator script.
@@ -671,9 +780,9 @@ def _pkg_rpm_impl(ctx):
mnemonic = "MakeRpm",
executable = ctx.executable._make_rpm,
use_default_shell_env = True,
- arguments = args,
+ arguments = rpm_ctx.make_rpm_args,
inputs = files,
- outputs = [output_file],
+ outputs = rpm_ctx.output_rpm_files,
env = {
"LANG": "en_US.UTF-8",
"LC_CTYPE": "UTF-8",
@@ -689,13 +798,13 @@ def _pkg_rpm_impl(ctx):
output_groups = {
"out": [default_file],
- "rpm": [output_file],
+ "rpm": rpm_ctx.output_rpm_files,
"changes": changes,
}
return [
OutputGroupInfo(**output_groups),
DefaultInfo(
- files = depset([output_file]),
+ files = depset(rpm_ctx.output_rpm_files),
),
]
@@ -961,7 +1070,7 @@ pkg_rpm = rule(
See also: https://rpm-software-management.github.io/rpm/manual/dependencies.html
""",
- ),
+ ),
"requires": attr.string_list(
doc = """List of rpm capability expressions that this package requires.
@@ -1046,6 +1155,18 @@ pkg_rpm = rule(
"defines": attr.string_dict(
doc = """Additional definitions to pass to rpmbuild""",
),
+ "subrpms": attr.label_list(
+ doc = """Sub RPMs to build with this RPM
+
+ A list of `pkg_sub_rpm` instances that can be used to create sub RPMs as part of the
+ overall package build.
+
+ NOTE: use of `subrpms` is incompatible with the legacy `spec_file` mode
+ """,
+ providers = [
+ [PackageSubRPMInfo],
+ ],
+ ),
"rpmbuild_path": attr.string(
doc = """Path to a `rpmbuild` binary. Deprecated in favor of the rpmbuild toolchain""",
),
@@ -1067,3 +1188,74 @@ pkg_rpm = rule(
implementation = _pkg_rpm_impl,
toolchains = ["@rules_pkg//toolchains/rpm:rpmbuild_toolchain_type"],
)
+
+def _pkg_sub_rpm_impl(ctx):
+ mapped_files_depsets = []
+
+ for s in ctx.attr.srcs:
+ if PackageFilegroupInfo in s:
+ mapped_files_depsets.append(s[DefaultInfo].files)
+
+ if PackageFilesInfo in s:
+ # dict.values() returns a list, not an iterator like in python3
+ mapped_files_depsets.append(s[DefaultInfo].files)
+
+ return [
+ PackageSubRPMInfo(
+ package_name = ctx.attr.package_name,
+ summary = ctx.attr.summary,
+ group = ctx.attr.group,
+ description = ctx.attr.description,
+ post_scriptlet = ctx.attr.post_scriptlet,
+ architecture = ctx.attr.architecture,
+ version = ctx.attr.version,
+ requires = ctx.attr.requires,
+ provides = ctx.attr.provides,
+ srcs = ctx.attr.srcs,
+ ),
+ DefaultInfo(
+ files = depset(transitive = mapped_files_depsets),
+ ),
+ ]
+
+pkg_sub_rpm = rule(
+ doc = """Define a sub RPM to be built as part of a parent RPM
+
+ This rule uses the outputs of the rules in `mappings.bzl` to define an sub
+ RPM that will be built as part of a larger RPM defined by a `pkg_rpm` instance.
+
+ """,
+ implementation = _pkg_sub_rpm_impl,
+ # @unsorted-dict-items
+ attrs = {
+ "package_name": attr.string(doc = "name of the subrpm"),
+ "summary": attr.string(doc = "Sub RPM `Summary` tag"),
+ "group": attr.string(
+ doc = """Optional; RPM "Group" tag.
+
+ NOTE: some distributions (as of writing, Fedora > 17 and CentOS/RHEL
+ > 5) have deprecated this tag. Other distributions may require it,
+ but it is harmless in any case.
+
+ """,
+ ),
+ "description": attr.string(doc = "Multi-line description of this subrpm"),
+ "post_scriptlet": attr.string(doc = "RPM `%post` scriplet for this subrpm"),
+ "architecture": attr.string(doc = "Sub RPM architecture"),
+ "version": attr.string(doc = "RPM `Version` tag for this subrpm"),
+ "requires": attr.string_list(doc = "List of RPM capability expressions that this package requires"),
+ "provides": attr.string_list(doc = "List of RPM capability expressions that this package provides"),
+ "srcs": attr.label_list(
+ doc = "Mapping groups to include in this RPM",
+ mandatory = True,
+ providers = [
+ [PackageSubRPMInfo, DefaultInfo],
+ [PackageFilegroupInfo, DefaultInfo],
+ [PackageFilesInfo, DefaultInfo],
+ [PackageDirsInfo],
+ [PackageSymlinkInfo],
+ ],
+ ),
+ },
+ provides = [PackageSubRPMInfo],
+)
diff --git a/tests/rpm/BUILD b/tests/rpm/BUILD
index f51d75b..89b1e61 100644
--- a/tests/rpm/BUILD
+++ b/tests/rpm/BUILD
@@ -24,7 +24,7 @@ load(
"pkg_mkdirs",
"pkg_mklink",
)
-load("//pkg:rpm.bzl", "pkg_rpm")
+load("//pkg:rpm.bzl", "pkg_rpm", "pkg_sub_rpm")
load("analysis_tests.bzl", "analysis_tests")
load("toolchain_tests.bzl", "create_toolchain_analysis_tests")
@@ -465,6 +465,83 @@ diff_test(
)
############################################################################
+# pkg_sub_rpm tests
+############################################################################
+genrule(
+ name = "test_sub_rpm_file_input",
+ outs = ["test_sub_rpm_file_input.txt"],
+ cmd = """
+ echo "test subrpm data" > $@
+ """,
+)
+
+pkg_files(
+ name = "test_sub_rpm_files",
+ srcs = [":test_sub_rpm_file_input"],
+)
+
+pkg_sub_rpm(
+ name = "sub_rpm",
+ package_name = "test_sub_rpm",
+ description = "Test subrpm description",
+ summary = "Test subrpm",
+ srcs = [
+ ":test_sub_rpm_files",
+ ],
+)
+
+genrule(
+ name = "test_sub_rpm_main_file_input",
+ outs = ["test_sub_rpm_main_file_input.txt"],
+ cmd = """
+ echo "test main rpm data" > $@
+ """,
+)
+
+pkg_files(
+ name = "test_sub_rpm_main_files",
+ srcs = [":test_sub_rpm_main_file_input"],
+)
+
+pkg_rpm(
+ name = "test_sub_rpm_main",
+ description = "This is a package description.",
+ summary = "rules_pkg example RPM",
+ architecture = "noarch",
+ version = "1",
+ license = "Apache License, v2.0",
+ release = "0",
+ srcs = [
+ ":test_sub_rpm_main_files",
+ ],
+ subrpms = [
+ ":sub_rpm",
+ ],
+)
+
+genrule(
+ name = "test_sub_rpm_contents",
+ srcs = [":test_sub_rpm_main"],
+ outs = [":test_sub_rpm_contents.txt"],
+ cmd = """
+ # pkg_rpm emits two outputs
+ RPMS=($(SRCS))
+ echo "===== main RPM =====" > $@
+ rpm -qpi --list $${RPMS[0]} | \
+ grep -v 'Build Date' | grep -v 'Build Host' | grep -v 'Relocations' >> $@
+ echo "===== sub RPM ======" >> $@
+ rpm -qpi --list $${RPMS[1]} | \
+ grep -v 'Build Date' | grep -v 'Build Host' | grep -v 'Relocations'>> $@
+ """,
+)
+
+diff_test(
+ name = "test_golden_sub_rpm_contents",
+ file1 = ":test_sub_rpm_contents",
+ file2 = "test_sub_rpm_contents.txt.golden",
+)
+
+############################################################################
# Common tests
############################################################################
diff --git a/tests/rpm/make_rpm_test.py b/tests/rpm/make_rpm_test.py
index cb8608b..1399f8f 100644
--- a/tests/rpm/make_rpm_test.py
+++ b/tests/rpm/make_rpm_test.py
@@ -75,7 +75,7 @@ class MakeRpmTest(unittest.TestCase):
"""
result = make_rpm.FindOutputFile(log)
- self.assertEqual('/path/to/file/here.rpm', result)
+ self.assertEqual(['/path/to/file/here.rpm'], result)
def testFindOutputFile_missing(self):
log = """
diff --git a/tests/rpm/test_sub_rpm_contents.txt.golden b/tests/rpm/test_sub_rpm_contents.txt.golden
new file mode 100755
index 0000000..9760069
--- /dev/null
+++ b/tests/rpm/test_sub_rpm_contents.txt.golden
@@ -0,0 +1,30 @@
+===== main RPM =====
+Name : test_sub_rpm_main
+Version : 1
+Release : 0
+Architecture: noarch
+Install Date: (not installed)
+Group : Unspecified
+Size : 19
+License : Apache License, v2.0
+Signature : (none)
+Source RPM : test_sub_rpm_main-1-0.src.rpm
+Summary : rules_pkg example RPM
+Description :
+This is a package description.
+/test_sub_rpm_main_file_input.txt
+===== sub RPM ======
+Name : test_sub_rpm_main-test_sub_rpm
+Version : 1
+Release : 0
+Architecture: noarch
+Install Date: (not installed)
+Group : Unspecified
+Size : 17
+License : Apache License, v2.0
+Signature : (none)
+Source RPM : test_sub_rpm_main-1-0.src.rpm
+Summary : Test subrpm
+Description :
+Test subrpm description
+/test_sub_rpm_file_input.txt