From 6c27a34cfe5a37901803ad8478f3b9ec668a3b69 Mon Sep 17 00:00:00 2001 From: Alex Blago Date: Sun, 13 Aug 2023 00:33:00 -0700 Subject: [PATCH] Support for cross-platform RPM package generation This change introduces proper support for generating RPM packages for the designated target platform. The current version of the "pkg_rpm"-rule exposes an "architecture"-option, which is meant to indicate the package's intended platform. Unfortunately, this option is being incorrectly utilized. It is being used to define the "BuildArch"-option in the RPM spec file. However, as per RPM packaging guide (https://rpm-packaging-guide.github.io/): "'BuildArch' should be used if the package is not architecture dependent. For example, if written entirely in an interpreted programming language, set this to BuildArch: noarch. If not set, the package automatically inherits the Architecture of the machine on which it is built". So, it sounds like it actually has nothing to do with target architecture. It is supposed to represent either architecture of host machine or "noarch". The purpose of this patch is to maintain backward compatibility while provide the solution for the described issue. It preserves the existing "architecture"-attribute of the "pkg_rpm" rule, as it might already be in use by someone. It introduces the "target_architecture"-attribute to the "pkg_rpm" rule, aiming to utilize it as a "arch"-suffix for the package name and to provide it to the "rpmbuild"-utility as the value for the "--target" option. --- pkg/make_rpm.py | 16 ++++++++++++---- pkg/rpm_pfg.bzl | 26 +++++++++++++++++++------- tests/rpm/BUILD | 14 ++++++++++++++ tests/rpm/make_rpm_test.py | 4 ++-- tests/rpm/pkg_rpm_basic_test.py | 21 +++++++++++++++++++++ 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/pkg/make_rpm.py b/pkg/make_rpm.py index e2ffca0a..76a2e51d 100644 --- a/pkg/make_rpm.py +++ b/pkg/make_rpm.py @@ -178,13 +178,14 @@ class RpmBuilder(object): RPMS_DIR = 'RPMS' DIRS = [SOURCE_DIR, BUILD_DIR, RPMS_DIR, TEMP_DIR] - def __init__(self, name, version, release, arch, rpmbuild_path, - source_date_epoch=None, + def __init__(self, name, version, release, arch, target_arch, + rpmbuild_path, source_date_epoch=None, debug=False): self.name = name self.version = helpers.GetFlagValue(version) self.release = helpers.GetFlagValue(release) self.arch = arch + self.target_arch = target_arch self.files = [] self.rpmbuild_path = FindRpmbuild(rpmbuild_path) self.rpm_path = None @@ -354,6 +355,10 @@ def CallRpmBuild(self, dirname, rpmbuild_args): '--buildroot=%s' % buildroot, ] # yapf: disable + # Target platform + if self.target_arch: + args += ['--target=%s' % self.target_arch] + # Macro-based RPM parameter substitution, if necessary inputs provided. if self.preamble_file: args += ['--define', 'build_rpm_options %s' % self.preamble_file] @@ -462,7 +467,10 @@ def main(argv): help='The release of the software being packaged.') parser.add_argument( '--arch', - help='The CPU architecture of the software being packaged.') + help='The CPU architecture of the machine on which it is built. ' + 'If the package is not architecture dependent, set this to "noarch".') + parser.add_argument('--target_arch', + help='The CPU architecture of the target platform the software being packaged for.') parser.add_argument('--spec_file', required=True, help='The file containing the RPM specification.') parser.add_argument('--out_file', required=True, @@ -501,7 +509,7 @@ def main(argv): try: builder = RpmBuilder(options.name, options.version, options.release, - options.arch, options.rpmbuild, + options.arch, options.target_arch, options.rpmbuild, source_date_epoch=options.source_date_epoch, debug=options.debug) builder.AddFiles(options.files) diff --git a/pkg/rpm_pfg.bzl b/pkg/rpm_pfg.bzl index 1e3450c1..596dc26d 100644 --- a/pkg/rpm_pfg.bzl +++ b/pkg/rpm_pfg.bzl @@ -142,7 +142,7 @@ def _make_absolute_if_not_already_or_is_macro(path): # this can be inlined easily. return path if path.startswith(("/", "%")) else "/" + path -#### Input processing helper functons. +#### Input processing helper functions. # TODO(nacl, #459): These are redundant with functions and structures in # pkg/private/pkg_files.bzl. We should really use the infrastructure provided @@ -251,7 +251,7 @@ def _pkg_rpm_impl(ctx): rpm_name, ctx.attr.version, ctx.attr.release, - ctx.attr.architecture, + ctx.attr.architecture if ctx.attr.architecture else ctx.attr.target_architecture, ) outputs, output_file, output_name = setup_output_files( @@ -432,6 +432,9 @@ def _pkg_rpm_impl(ctx): args.append("--out_file=" + output_file.path) + if ctx.attr.target_architecture: + args.append("--target_arch=" + ctx.attr.target_architecture) + # Add data files. if ctx.file.changelog: files.append(ctx.file.changelog) @@ -666,7 +669,7 @@ def _pkg_rpm_impl(ctx): output_groups = { "out": [default_file], "rpm": [output_file], - "changes": changes + "changes": changes, } return [ OutputGroupInfo(**output_groups), @@ -791,21 +794,30 @@ pkg_rpm = rule( # funny if it's not provided. The contents of the RPM are believed to # be set as expected, though. "architecture": attr.string( - doc = """Package architecture. + doc = """Host architecture. This currently sets the `BuildArch` tag, which influences the output architecture of the package. Typically, `BuildArch` only needs to be set when the package is - known to be cross-platform (e.g. written in an interpreted - language), or, less common, when it is known that the application is - only valid for specific architectures. + not architecture dependent (e.g. written in an interpreted + language). When no attribute is provided, this will default to your host's architecture. This is usually what you want. """, ), + "target_architecture": attr.string( + doc = """Package architecture. + + This currently sets the value for the "--target" argument to "rpmbuild" + to specify platform package is built for. + + When no attribute is provided, this will default to your host's + architecture. + """, + ), "license": attr.string( doc = """RPM "License" tag. diff --git a/tests/rpm/BUILD b/tests/rpm/BUILD index 5f91da8d..37f3f6ad 100644 --- a/tests/rpm/BUILD +++ b/tests/rpm/BUILD @@ -149,6 +149,19 @@ pkg_rpm( version = "1.1.1", ) +pkg_rpm( + name = "test_rpm_alien", + srcs = [ + ":test_pfg", + ], + target_architecture = "alien", + description = """pkg_rpm test rpm for alien target platform description""", + license = "Apache 2.0", + release = "2222", + summary = "pkg_rpm test rpm for alien target platform summary", + version = "1.1.1", +) + # Just like the above one, except the compression is changed. pkg_rpm( name = "test_rpm-bzip2", @@ -305,6 +318,7 @@ sh_library( testonly = True, srcs = [ ":test_rpm", + ":test_rpm_alien", ":test_rpm-bzip2", ":test_rpm_direct", ":test_rpm_manifest", diff --git a/tests/rpm/make_rpm_test.py b/tests/rpm/make_rpm_test.py index cb8608be..2a964a15 100644 --- a/tests/rpm/make_rpm_test.py +++ b/tests/rpm/make_rpm_test.py @@ -122,7 +122,7 @@ def testSetupWorkdir(self): with PrependPath([outer]): # Create the builder and exercise it. - builder = make_rpm.RpmBuilder('test', '1.0', '0', 'x86', None) + builder = make_rpm.RpmBuilder('test', '1.0', '0', 'x86', 'x86', None) # Create spec_file, test files. WriteFile('test.spec', 'Name: test', 'Version: 0.1', @@ -162,7 +162,7 @@ def testBuild(self): with PrependPath([outer]): # Create the builder and exercise it. - builder = make_rpm.RpmBuilder('test', '1.0', '0', 'x86', None) + builder = make_rpm.RpmBuilder('test', '1.0', '0', 'x86', 'x86', None) # Create spec_file, test files. WriteFile('test.spec', 'Name: test', 'Version: 0.1', diff --git a/tests/rpm/pkg_rpm_basic_test.py b/tests/rpm/pkg_rpm_basic_test.py index a152f064..21f2b6db 100644 --- a/tests/rpm/pkg_rpm_basic_test.py +++ b/tests/rpm/pkg_rpm_basic_test.py @@ -43,6 +43,8 @@ def setUp(self): self.runfiles = runfiles.Create() self.test_rpm_path = self.runfiles.Rlocation( "rules_pkg/tests/rpm/test_rpm.rpm") + self.test_rpm_alien_path = self.runfiles.Rlocation( + "rules_pkg/tests/rpm/test_rpm_alien.rpm") self.test_rpm_direct_path = self.runfiles.Rlocation( "rules_pkg/tests/rpm/test_rpm_direct.rpm") self.test_rpm_bzip2_path = self.runfiles.Rlocation( @@ -85,6 +87,25 @@ def test_basic_headers(self): output, expected, "RPM Tag {} does not match expected value".format(fieldname)) + def test_basic_headers_alien_target(self): + fields = { + "NAME": b"test_rpm_alien", + "VERSION": b"1.1.1", + "RELEASE": b"2222", + "ARCH": b"alien", + "GROUP": b"Unspecified", + "SUMMARY": b"pkg_rpm test rpm for alien target platform summary", + } + for fieldname, expected in fields.items(): + output = subprocess.check_output([ + "rpm", "-qp", "--queryformat", "%{" + fieldname + "}", + self.test_rpm_alien_path + ]) + + self.assertEqual( + output, expected, + "RPM Tag {} does not match expected value".format(fieldname)) + def test_contents(self): manifest_file = self.runfiles.Rlocation( "rules_pkg/tests/rpm/manifest.csv")