diff options
author | Mike Kelly <mkelly@arista.com> | 2024-03-14 19:36:15 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-14 22:36:15 -0400 |
commit | 2aa2b8e842894e0c383817cdf3b1f4a6e96fcc3e (patch) | |
tree | 2912a8b72d1b2652b576e8c3e34113c8b6326bda | |
parent | 61132feb47add139967476401baa49cf4b7c6b2a (diff) | |
download | bazelbuild-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/BUILD | 1 | ||||
-rw-r--r-- | examples/rpm/subrpm/BUILD | 79 | ||||
-rw-r--r-- | examples/rpm/subrpm/MODULE.bazel | 32 | ||||
-rw-r--r-- | examples/rpm/subrpm/README.md | 14 | ||||
-rw-r--r-- | pkg/make_rpm.py | 64 | ||||
-rw-r--r-- | pkg/rpm.bzl | 10 | ||||
-rw-r--r-- | pkg/rpm/template.spec.tpl | 2 | ||||
-rw-r--r-- | pkg/rpm_pfg.bzl | 536 | ||||
-rw-r--r-- | tests/rpm/BUILD | 79 | ||||
-rw-r--r-- | tests/rpm/make_rpm_test.py | 2 | ||||
-rwxr-xr-x | tests/rpm/test_sub_rpm_contents.txt.golden | 30 |
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 |