| # Copyright 2019 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. |
| |
| """Provides rules for creating RPM packages via pkg_filegroup and friends. |
| |
| pkg_rpm() depends on the existence of an rpmbuild toolchain. Many users will |
| find to convenient to use the one provided with their system. To enable that |
| toolchain add the following stanza to WORKSPACE: |
| |
| ``` |
| # Find rpmbuild if it exists. |
| load("@rules_pkg//toolchains/rpm:rpmbuild_configure.bzl", "find_system_rpmbuild") |
| find_system_rpmbuild(name="rules_pkg_rpmbuild") |
| ``` |
| """ |
| |
| load( |
| "//pkg:providers.bzl", |
| "PackageDirsInfo", |
| "PackageFilegroupInfo", |
| "PackageFilesInfo", |
| "PackageSymlinkInfo", |
| "PackageVariablesInfo", |
| ) |
| load("//pkg/private:util.bzl", "setup_output_files", "substitute_package_variables") |
| load( |
| "//toolchains/rpm:rpmbuild_configure.bzl", |
| "DEBUGINFO_TYPE_FEDORA", |
| "DEBUGINFO_TYPE_NONE", |
| ) |
| |
| 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", |
| "postun_scriptlet": "RPM `$postun` scriplet for this subpackage", |
| "architecture": "Subpackage architecture", |
| "epoch": "RPM `Epoch` tag for this subpackage", |
| "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", |
| "conflicts": "List of RPM capability expressions that conflict with this package", |
| "obsoletes": "List of RPM capability expressions that this package obsoletes", |
| "srcs": "Mapping groups to include in this RPM", |
| }, |
| ) |
| |
| # default mode for %files |
| DEFAULT_FILE_MODE = "%defattr(-,root,root)" |
| |
| # TODO(nacl): __install, __cp |
| # {0} is the source, {1} is the dest |
| # |
| # TODO(nacl, #292): cp -r does not do the right thing with TreeArtifacts |
| _INSTALL_FILE_STANZA_FMT = """ |
| install -d "%{{buildroot}}/$(dirname '{1}')" |
| cp '{0}' '%{{buildroot}}/{1}' |
| chmod +w '%{{buildroot}}/{1}' |
| """.strip() |
| |
| _INSTALL_FILE_STANZA_FMT_FEDORA40_DEBUGINFO = """ |
| install -d "%{{buildroot}}/$(dirname '{1}')" |
| cp '../{0}' '%{{buildroot}}/{1}' |
| chmod +w '%{{buildroot}}/{1}' |
| """.strip() |
| |
| # TODO(nacl): __install |
| # {0} is the directory name |
| # |
| # This may not be strictly necessary, given that they'll be created in the |
| # CPIO when rpmbuild processes the `%files` list. |
| _INSTALL_DIR_STANZA_FMT = """ |
| install -d '%{{buildroot}}/{0}' |
| """.strip() |
| |
| # {0} is the name of the link, {1} is the target, {2} is the desired symlink "mode". |
| # |
| # In particular, {2} exists because umasks on symlinks apply on macOS, unlike |
| # Linux. You can't even change symlink permissions in Linux; all permissions |
| # apply to the target instead. |
| # |
| # This is not the case in BSDs and macOS. This comes up because rpmbuild(8) |
| # does not know about the BSD "lchmod" call, which would otherwise be used to |
| # set permissions. |
| # |
| # This is primarily to ensure that tests pass. Actually attempting to build |
| # functional RPMs on macOS in rules_pkg has not yet been attempted at any scale. |
| # |
| # XXX: This may not apply all that well to users of cygwin and mingw. We'll |
| # deal with that when the time comes. |
| _INSTALL_SYMLINK_STANZA_FMT = """ |
| %{{__install}} -d "%{{buildroot}}/$(dirname '{0}')" |
| %{{__ln_s}} '{1}' '%{{buildroot}}/{0}' |
| %if "%_host_os" != "linux" |
| %{{__chmod}} -h {2} '%{{buildroot}}/{0}' |
| %endif |
| """.strip() |
| |
| # {0} is the file tag, {1} is the the path to file |
| _FILE_MODE_STANZA_FMT = """ |
| {0} "{1}" |
| """.strip() |
| |
| def _package_contents_metadata(origin_label, grouping_label): |
| """Named construct for helping to identify conflicting packaged contents""" |
| return struct( |
| origin = origin_label if origin_label else "<UNKNOWN>", |
| group = grouping_label, |
| ) |
| |
| def _conflicting_contents_error(destination, from1, from2, attr_name = "srcs"): |
| real_from1_origin = "<UNKNOWN>" if not from1.origin else from1.origin |
| real_from1_group = "directly" if not from1.group else "from group {}".format(from1.group) |
| real_from2_origin = "<UNKNOWN>" if not from2.origin else from2.origin |
| real_from2_group = "directly" if not from2.group else "from group {}".format(from2.group) |
| |
| message = """Destination {destination} is provided by both (1) {from1_origin} and (2) {from2_origin}; please ensure that each destination is provided by exactly one input. |
| |
| (1) {from1_origin} is provided {from1_group} |
| (2) {from2_origin} is provided {from2_group} |
| """.format( |
| destination = destination, |
| from1_origin = real_from1_origin, |
| from1_group = real_from1_group, |
| from2_origin = real_from2_origin, |
| from2_group = real_from2_group, |
| ) |
| |
| fail(message, attr_name) |
| |
| def _make_filetags(attributes, default_filetag = None): |
| """Helper function for rendering RPM spec file tags, like |
| |
| ``` |
| %attr(0755, root, root) %dir |
| ``` |
| """ |
| template = "%attr({mode}, {user}, {group}) {supplied_filetag}" |
| |
| mode = attributes.get("mode", "-") |
| user = attributes.get("user", "-") |
| group = attributes.get("group", "-") |
| |
| supplied_filetag = attributes.get("rpm_filetag", default_filetag) |
| |
| return template.format( |
| mode = mode, |
| user = user, |
| group = group, |
| supplied_filetag = supplied_filetag or "", |
| ) |
| |
| def _make_absolute_if_not_already_or_is_macro(path): |
| # Make a destination path absolute if it isn't already or if it starts with |
| # a macro (assumed to be a value that starts with "%"). |
| # |
| # If the user has provided a macro as the installation destination, assume |
| # they know what they're doing. Specifically, the macro needs to resolve to |
| # an absolute path. |
| |
| # This may not be the fastest way to do this, but if it becomes a problem |
| # this can be inlined easily. |
| return path if path.startswith(("/", "%")) else "/" + path |
| |
| def _make_rpm_filename(rpm_name, version, architecture, package_name = None, release = None): |
| prefix = "%s-%s" |
| items = [rpm_name, version] |
| |
| if package_name: |
| prefix += "-%s" |
| items = [rpm_name, package_name, version] |
| |
| if release: |
| prefix += "-%s" |
| items.append(release) |
| |
| fmt = prefix + ".%s.rpm" |
| |
| return fmt % tuple(items + [architecture]) |
| |
| #### 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 |
| # there, but as of writing, it's not quite ready. |
| def _process_files(pfi, origin_label, grouping_label, file_base, rpm_ctx, debuginfo_type): |
| for dest, src in pfi.dest_src_map.items(): |
| metadata = _package_contents_metadata(origin_label, grouping_label) |
| if dest in rpm_ctx.dest_check_map: |
| _conflicting_contents_error(dest, metadata, rpm_ctx.dest_check_map[dest]) |
| else: |
| 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 |
| rpm_ctx.packaged_directories.append({ |
| "src": src, |
| "dest": abs_dest, |
| # This doesn't exactly make it extensible, but it saves |
| # us from having to having to maintain tag processing |
| # code in multiple places. |
| "tags": file_base, |
| }) |
| else: |
| # Files are well-known. Take care of them right here. |
| rpm_ctx.rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dest)) |
| |
| install_stanza_fmt = _INSTALL_FILE_STANZA_FMT |
| if debuginfo_type == DEBUGINFO_TYPE_FEDORA: |
| install_stanza_fmt = _INSTALL_FILE_STANZA_FMT_FEDORA40_DEBUGINFO |
| |
| rpm_ctx.install_script_pieces.append(install_stanza_fmt.format( |
| src.path, |
| abs_dest, |
| )) |
| |
| 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 rpm_ctx.dest_check_map: |
| _conflicting_contents_error(dest, metadata, rpm_ctx.dest_check_map[dest]) |
| else: |
| rpm_ctx.dest_check_map[dest] = metadata |
| |
| abs_dirname = _make_absolute_if_not_already_or_is_macro(dest) |
| rpm_ctx.rpm_files_list.append(_FILE_MODE_STANZA_FMT.format(file_base, abs_dirname)) |
| |
| rpm_ctx.install_script_pieces.append(_INSTALL_DIR_STANZA_FMT.format( |
| abs_dirname, |
| )) |
| |
| def _process_symlink(psi, origin_label, grouping_label, file_base, rpm_ctx): |
| metadata = _package_contents_metadata(origin_label, grouping_label) |
| if psi.destination in rpm_ctx.dest_check_map: |
| _conflicting_contents_error(psi.destination, metadata, rpm_ctx.dest_check_map[psi.destination]) |
| else: |
| rpm_ctx.dest_check_map[psi.destination] = metadata |
| |
| abs_dest = _make_absolute_if_not_already_or_is_macro(psi.destination) |
| 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, debuginfo_type): |
| # 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, |
| debuginfo_type, |
| ) |
| |
| 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, |
| debuginfo_type, |
| ) |
| 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, debuginfo_type): |
| 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.group: |
| rpm_lines.append("Group: %s" % rpm_info.group) |
| |
| if rpm_info.architecture: |
| rpm_lines.append("BuildArch: %s" % rpm_info.architecture) |
| |
| if rpm_info.epoch: |
| rpm_lines.append("Epoch: %s" % rpm_info.epoch) |
| |
| if rpm_info.version: |
| rpm_lines.append("Version: %s" % rpm_info.version) |
| |
| for r in rpm_info.requires: |
| rpm_lines.append("Requires: %s" % r) |
| |
| for p in rpm_info.provides: |
| rpm_lines.append("Provides: %s" % p) |
| |
| for c in rpm_info.conflicts: |
| rpm_lines.append("Conflicts: %s" % c) |
| |
| for o in rpm_info.obsoletes: |
| rpm_lines.append("Obsoletes: %s" % o) |
| |
| rpm_lines += [ |
| "", |
| "%%description %s" % rpm_info.package_name, |
| rpm_info.description, |
| ] |
| |
| if rpm_info.post_scriptlet: |
| rpm_lines += [ |
| "", |
| "%%post %s" % rpm_info.package_name, |
| rpm_info.post_scriptlet, |
| ] |
| |
| if rpm_info.postun_scriptlet: |
| rpm_lines += [ |
| "", |
| "%%postun %s" % rpm_info.package_name, |
| rpm_info.postun_scriptlet, |
| ] |
| |
| if rpm_info.srcs: |
| rpm_lines += [ |
| "", |
| "%%files %s" % rpm_info.package_name, |
| ] |
| |
| for dep in rpm_info.srcs: |
| _process_dep(dep, sub_rpm_ctx, debuginfo_type) |
| |
| # rpmbuild will be unhappy if we have no files so we stick |
| # default file mode in for that scenario |
| rpm_lines.append(DEFAULT_FILE_MODE) |
| rpm_lines += sub_rpm_ctx.rpm_files_list |
| |
| rpm_lines.append("") |
| |
| 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 = _make_rpm_filename( |
| rpm_name = rpm_name, |
| version = rpm_info.version or ctx.attr.version, |
| architecture = rpm_info.architecture or ctx.attr.architecture, |
| package_name = rpm_info.package_name, |
| release = ctx.attr.release, |
| ) |
| |
| 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 = [] |
| debuginfo_type = DEBUGINFO_TYPE_NONE |
| 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: |
| rpm_ctx.make_rpm_args.append("--debug") |
| |
| if 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" + |
| " to migrate to toolchains") |
| else: |
| toolchain = ctx.toolchains["@rules_pkg//toolchains/rpm:rpmbuild_toolchain_type"].rpmbuild |
| if not toolchain.valid: |
| fail("The rpmbuild_toolchain is not properly configured: " + |
| toolchain.name) |
| if toolchain.path: |
| rpm_ctx.make_rpm_args.append("--rpmbuild=" + toolchain.path) |
| else: |
| executable_files = toolchain.label[DefaultInfo].files_to_run |
| tools.append(executable_files) |
| rpm_ctx.make_rpm_args.append("--rpmbuild=%s" % executable_files.executable.path) |
| |
| if ctx.attr.debuginfo: |
| debuginfo_type = toolchain.debuginfo_type |
| rpm_ctx.make_rpm_args.append("--debuginfo_type=%s" % debuginfo_type) |
| |
| #### Calculate output file name |
| # rpm_name takes precedence over name if provided |
| if ctx.attr.package_name: |
| rpm_name = ctx.attr.package_name |
| else: |
| rpm_name = ctx.attr.name |
| |
| default_file = ctx.actions.declare_file("{}.rpm".format(rpm_name)) |
| |
| package_file_name = ctx.attr.package_file_name |
| if not package_file_name: |
| package_file_name = _make_rpm_filename( |
| rpm_name, |
| ctx.attr.version, |
| ctx.attr.architecture, |
| release = ctx.attr.release, |
| ) |
| |
| #### rpm spec "preamble" |
| preamble_pieces = [] |
| |
| preamble_pieces.append("Name: " + rpm_name) |
| |
| # Version can be specified by a file or inlined. |
| if ctx.attr.version_file: |
| if ctx.attr.version: |
| fail("Both version and version_file attributes were specified") |
| |
| preamble_pieces.append("Version: ${{VERSION_FROM_FILE}}") |
| 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) |
| else: |
| fail("None of the version or version_file attributes were specified") |
| |
| # Release can be specified by a file or inlined. |
| if ctx.attr.release_file: |
| if ctx.attr.release: |
| fail("Both release and release_file attributes were specified") |
| |
| preamble_pieces.append("Release: ${{RELEASE_FROM_FILE}}") |
| 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) |
| else: |
| fail("None of the release or release_file attributes were specified") |
| |
| # source_date_epoch is an integer, and Bazel (as of 4.2.2) does not allow |
| # you to put "None" as the default for an "int" attribute. See also |
| # https://github.com/bazelbuild/bazel/issues/14434. |
| # |
| # Since source_date_epoch cannot reasonably be negative, being zero or |
| # positive treated the same as existing below. |
| 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") |
| 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: |
| rpm_ctx.make_rpm_args.append("--source_date_epoch=" + str(ctx.attr.source_date_epoch)) |
| |
| if ctx.attr.epoch: |
| preamble_pieces.append("Epoch: " + ctx.attr.epoch) |
| if ctx.attr.summary: |
| preamble_pieces.append("Summary: " + ctx.attr.summary) |
| if ctx.attr.url: |
| preamble_pieces.append("URL: " + ctx.attr.url) |
| if ctx.attr.license: |
| preamble_pieces.append("License: " + ctx.attr.license) |
| if ctx.attr.group: |
| preamble_pieces.append("Group: " + ctx.attr.group) |
| if ctx.attr.provides: |
| preamble_pieces.extend(["Provides: " + p for p in ctx.attr.provides]) |
| if ctx.attr.conflicts: |
| preamble_pieces.extend(["Conflicts: " + c for c in ctx.attr.conflicts]) |
| if ctx.attr.obsoletes: |
| preamble_pieces.extend(["Obsoletes: " + o for o in ctx.attr.obsoletes]) |
| if ctx.attr.requires: |
| preamble_pieces.extend(["Requires: " + r for r in ctx.attr.requires]) |
| if ctx.attr.requires_contextual: |
| preamble_pieces.extend( |
| [ |
| "Requires({}): {}".format(scriptlet, capability) |
| for scriptlet in ctx.attr.requires_contextual.keys() |
| for capability in ctx.attr.requires_contextual[scriptlet] |
| ], |
| ) |
| |
| # TODO: BuildArch is usually not hardcoded in spec files, unless the package |
| # is indeed restricted to a particular CPU architecture, or is actually |
| # "noarch". This will become more of a concern when we start providing |
| # source RPMs. |
| # |
| # In the meantime, this will allow the "architecture" attribute to take |
| # effect. |
| if ctx.attr.architecture: |
| preamble_pieces.append("BuildArch: " + ctx.attr.architecture) |
| |
| if ctx.attr.debuginfo: |
| # RedHat distros have redhat-rpm-config with %_enable_debug_packages macro; others need explicit declaration |
| preamble_pieces.append("%{{!?_enable_debug_packages:%debug_package}}") # set %debug_package unless macro exists |
| |
| # https://rpm.org/wiki/Releases/4.14.0: "Add support for unique debug file names" |
| preamble_pieces.append("%undefine _unique_debug_names") # no-op if not defined |
| |
| preamble_file = ctx.actions.declare_file( |
| "{}.spec.preamble".format(rpm_name), |
| ) |
| ctx.actions.write( |
| output = preamble_file, |
| content = substitute_package_variables(ctx, "\n".join(preamble_pieces)), |
| ) |
| files.append(preamble_file) |
| rpm_ctx.make_rpm_args.append("--preamble=" + preamble_file.path) |
| |
| #### %description |
| |
| if ctx.attr.description_file: |
| if ctx.attr.description: |
| fail("Both description and description_file attributes were specified") |
| description_file = ctx.file.description_file |
| elif ctx.attr.description: |
| description_file = ctx.actions.declare_file( |
| "{}.spec.description".format(rpm_name), |
| ) |
| ctx.actions.write( |
| output = description_file, |
| content = ctx.attr.description, |
| ) |
| else: |
| fail("None of the description or description_file attributes were specified") |
| |
| files.append(description_file) |
| rpm_ctx.make_rpm_args.append("--description=" + description_file.path) |
| |
| if ctx.attr.changelog: |
| files.append(ctx.file.changelog) |
| rpm_ctx.make_rpm_args.append("--changelog=" + ctx.file.changelog.path) |
| |
| #### Non-procedurally-generated scriptlets |
| |
| substitutions = {} |
| if ctx.attr.pre_scriptlet_file: |
| if ctx.attr.pre_scriptlet: |
| fail("Both pre_scriptlet and pre_scriptlet_file attributes were specified") |
| pre_scriptlet_file = ctx.file.pre_scriptlet_file |
| files.append(pre_scriptlet_file) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| rpm_ctx.make_rpm_args.append("--posttrans_scriptlet=" + scriptlet_file.path) |
| |
| #### Expand the spec file template; prepare data files |
| |
| spec_file = ctx.actions.declare_file("%s.spec" % rpm_name) |
| ctx.actions.expand_template( |
| template = ctx.file.spec_template, |
| output = spec_file, |
| substitutions = substitutions, |
| ) |
| rpm_ctx.make_rpm_args.append("--spec_file=" + spec_file.path) |
| files.append(spec_file) |
| |
| # Add data files |
| files += ctx.files.srcs + ctx.files.subrpms |
| |
| _, output_file, _ = setup_output_files( |
| ctx, |
| package_file_name = package_file_name, |
| default_output_file = default_file, |
| ) |
| |
| rpm_ctx.make_rpm_args.append("--out_file=" + output_file.path) |
| rpm_ctx.output_rpm_files.append(output_file) |
| |
| if ctx.attr.debug: |
| 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. |
| # |
| # This is a naive approach to script creation is almost guaranteed to |
| # 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: |
| _process_dep(dep, rpm_ctx, debuginfo_type) |
| |
| #### subrpms |
| if ctx.attr.subrpms: |
| subrpm_lines = [] |
| for s in ctx.attr.subrpms: |
| subrpm_lines.extend(_process_subrpm( |
| ctx, |
| rpm_name, |
| s[PackageSubRPMInfo], |
| rpm_ctx, |
| debuginfo_type, |
| )) |
| |
| subrpm_file = ctx.actions.declare_file( |
| "{}.spec.subrpms".format(rpm_name), |
| ) |
| ctx.actions.write( |
| output = subrpm_file, |
| content = "\n".join(subrpm_lines), |
| ) |
| files.append(subrpm_file) |
| rpm_ctx.make_rpm_args.append("--subrpms=" + subrpm_file.path) |
| |
| if debuginfo_type != DEBUGINFO_TYPE_NONE: |
| debuginfo_default_file = ctx.actions.declare_file( |
| "{}-debuginfo.rpm".format(rpm_name), |
| ) |
| debuginfo_package_file_name = _make_rpm_filename( |
| rpm_name, |
| ctx.attr.version, |
| ctx.attr.architecture, |
| package_name = "debuginfo", |
| release = ctx.attr.release, |
| ) |
| |
| _, debuginfo_output_file, _ = setup_output_files( |
| ctx, |
| debuginfo_package_file_name, |
| default_output_file = debuginfo_default_file, |
| ) |
| |
| rpm_ctx.output_rpm_files.append(debuginfo_output_file) |
| rpm_ctx.make_rpm_args.append( |
| "--subrpm_out_file=debuginfo:%s" % debuginfo_output_file.path, |
| ) |
| |
| #### Procedurally-generated scripts/lists (%install, %files) |
| |
| # We need to write these out regardless of whether we are using |
| # TreeArtifacts. That stage will use these files as inputs. |
| install_script = ctx.actions.declare_file("{}.spec.install".format(rpm_name)) |
| ctx.actions.write( |
| install_script, |
| "\n".join(rpm_ctx.install_script_pieces), |
| ) |
| |
| rpm_files_file = ctx.actions.declare_file( |
| "{}.spec.files".format(rpm_name), |
| ) |
| |
| # rpmbuild will be unhappy if we have no files so we stick |
| # default file mode in for that scenario |
| rpm_files_contents = [DEFAULT_FILE_MODE] + rpm_ctx.rpm_files_list |
| ctx.actions.write(rpm_files_file, "\n".join(rpm_files_contents)) |
| |
| # TreeArtifact processing work |
| 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 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 |
| # JSONified. |
| # |
| # 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 rpm_ctx.packaged_directories: |
| d["src"] = d["src"].path |
| |
| 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. |
| install_script_old = install_script |
| install_script = ctx.actions.declare_file("{}.spec.install.with_dirs".format(rpm_name)) |
| rpm_files_file_old = rpm_files_file |
| rpm_files_file = ctx.actions.declare_file("{}.spec.files.with_dirs".format(rpm_name)) |
| |
| input_files = [packaged_directories_file, install_script_old, rpm_files_file_old] |
| output_files = [install_script, rpm_files_file] |
| |
| helper_args = ctx.actions.args() |
| helper_args.add_all(input_files) |
| helper_args.add_all(output_files) |
| |
| ctx.actions.run( |
| executable = ctx.executable._treeartifact_helper, |
| use_default_shell_env = True, |
| arguments = [helper_args], |
| inputs = input_files + packaged_directories_inputs, |
| outputs = output_files, |
| progress_message = "Generating RPM TreeArtifact Data " + str(ctx.label), |
| ) |
| |
| # And then we're done. Yay! |
| |
| files.append(install_script) |
| rpm_ctx.make_rpm_args.append("--install_script=" + install_script.path) |
| |
| files.append(rpm_files_file) |
| rpm_ctx.make_rpm_args.append("--file_list=" + rpm_files_file.path) |
| |
| #### Remaining setup |
| |
| additional_rpmbuild_args = [] |
| if ctx.attr.binary_payload_compression: |
| additional_rpmbuild_args.extend([ |
| "--define", |
| "_binary_payload {}".format(ctx.attr.binary_payload_compression), |
| ]) |
| |
| for key, value in ctx.attr.defines.items(): |
| additional_rpmbuild_args.extend([ |
| "--define", |
| "{} {}".format(key, value), |
| ]) |
| |
| rpm_ctx.make_rpm_args.extend(["--rpmbuild_arg=" + a for a in additional_rpmbuild_args]) |
| |
| for f in ctx.files.srcs + ctx.files.subrpms: |
| rpm_ctx.make_rpm_args.append(f.path) |
| |
| #### Call the generator script. |
| |
| ctx.actions.run( |
| mnemonic = "MakeRpm", |
| executable = ctx.executable._make_rpm, |
| use_default_shell_env = True, |
| arguments = rpm_ctx.make_rpm_args, |
| inputs = files + (ctx.files.data or []), |
| outputs = rpm_ctx.output_rpm_files, |
| env = { |
| "LANG": "en_US.UTF-8", |
| "LC_CTYPE": "UTF-8", |
| "PYTHONIOENCODING": "UTF-8", |
| "PYTHONUTF8": "1", |
| }, |
| tools = tools, |
| ) |
| |
| changes = [] |
| if ctx.file.changelog: |
| changes = [ctx.file.changelog] |
| |
| output_groups = { |
| "out": [default_file], |
| "rpm": rpm_ctx.output_rpm_files, |
| "changes": changes, |
| } |
| return [ |
| OutputGroupInfo(**output_groups), |
| DefaultInfo( |
| files = depset(rpm_ctx.output_rpm_files), |
| ), |
| ] |
| |
| # Define the rule. |
| pkg_rpm = rule( |
| doc = """Creates an RPM format package via `pkg_filegroup` and friends. |
| |
| The uses the outputs of the rules in `mappings.bzl` to construct arbitrary |
| RPM packages. Attributes of this rule provide preamble information and |
| scriptlets, which are then used to compose a valid RPM spec file. |
| |
| This rule will fail at analysis time if: |
| |
| - Any `srcs` input creates the same destination, regardless of other |
| attributes. |
| |
| This rule only functions on UNIXy platforms. The following tools must be |
| available on your system for this to function properly: |
| |
| - `rpmbuild` (as specified in `rpmbuild_path`, or available in `$PATH`) |
| |
| - GNU coreutils. BSD coreutils may work, but are not tested. |
| |
| To set RPM file attributes (like `%config` and friends), set the |
| `rpm_filetag` in corresponding packaging rule (`pkg_files`, etc). The value |
| is prepended with "%" and added to the `%files` list, for example: |
| |
| ``` |
| attrs = {"rpm_filetag": ("config(missingok, noreplace)",)}, |
| ``` |
| |
| Is the equivalent to `%config(missingok, noreplace)` in the `%files` list. |
| |
| This rule produces 2 artifacts: an .rpm and a .changes file. The DefaultInfo will |
| include both. If you need downstream rule to specifically depend on only the .rpm or |
| .changes file then you can use `filegroup` to select distinct output groups. |
| |
| **OutputGroupInfo** |
| - `out` the RPM or a symlink to the actual package. |
| - `rpm` the package with any precise file name created with `package_file_name`. |
| - `changes` the .changes file. |
| """, |
| # @unsorted-dict-items |
| attrs = { |
| "package_name": attr.string( |
| doc = """Optional; RPM name override. |
| |
| If not provided, the `name` attribute of this rule will be used |
| instead. |
| |
| This influences values like the spec file name. |
| """, |
| ), |
| "package_file_name": attr.string( |
| doc = """See 'Common Attributes' in the rules_pkg reference. |
| |
| If this is not provided, the package file given a NVRA-style |
| (name-version-release.arch) output, which is preferred by most RPM |
| repositories. |
| """, |
| ), |
| "package_variables": attr.label( |
| doc = "See 'Common Attributes' in the rules_pkg reference", |
| providers = [PackageVariablesInfo], |
| ), |
| "epoch": attr.string( |
| doc = """Optional; RPM "Epoch" tag.""", |
| ), |
| "version": attr.string( |
| doc = """RPM "Version" tag. |
| |
| Exactly one of `version` or `version_file` must be provided. |
| """, |
| ), |
| "version_file": attr.label( |
| doc = """File containing RPM "Version" tag.""", |
| allow_single_file = True, |
| ), |
| "release": attr.string( |
| doc = """RPM "Release" tag |
| |
| Exactly one of `release` or `release_file` must be provided. |
| """, |
| ), |
| "release_file": attr.label( |
| doc = """File containing RPM "Release" tag.""", |
| allow_single_file = True, |
| ), |
| "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. |
| |
| """, |
| ), |
| "source_date_epoch": attr.int( |
| doc = """Value to export as SOURCE_DATE_EPOCH to facilitate reproducible builds |
| |
| Implicitly sets the `%clamp_mtime_to_source_date_epoch` in the |
| subordinate call to `rpmbuild` to facilitate more consistent in-RPM |
| file timestamps. |
| |
| Negative values (like the default) disable this feature. |
| """, |
| default = -1, |
| ), |
| "source_date_epoch_file": attr.label( |
| doc = """File containing the SOURCE_DATE_EPOCH value. |
| |
| Implicitly sets the `%clamp_mtime_to_source_date_epoch` in the |
| subordinate call to `rpmbuild` to facilitate more consistent in-RPM |
| file timestamps. |
| """, |
| allow_single_file = True, |
| ), |
| # TODO(nacl): this should be augmented to use bazel platforms, and |
| # should not really set BuildArch. |
| # |
| # TODO(nacl): This, uh, is more required than it looks. It influences |
| # the "A" part of the "NVRA" RPM file name, and RPMs file names look |
| # 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. |
| |
| 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. |
| |
| When no attribute is provided, this will default to your host's |
| architecture. This is usually what you want. |
| |
| """, |
| ), |
| "license": attr.string( |
| doc = """RPM "License" tag. |
| |
| The software license for the code distributed in this package. |
| |
| The underlying RPM builder requires you to put something here; if |
| your package is not going to be distributed, feel free to set this |
| to something like "Internal". |
| |
| """, |
| mandatory = True, |
| ), |
| "summary": attr.string( |
| doc = """RPM "Summary" tag. |
| |
| One-line summary of this package. Must not contain newlines. |
| |
| """, |
| mandatory = True, |
| ), |
| "url": attr.string( |
| doc = """RPM "URL" tag; this project/vendor's home on the Internet.""", |
| ), |
| "description": attr.string( |
| doc = """Multi-line description of this package, corresponds to RPM %description. |
| |
| Exactly one of `description` or `description_file` must be provided. |
| """, |
| ), |
| "description_file": attr.label( |
| doc = """File containing a multi-line description of this package, corresponds to RPM |
| %description.""", |
| allow_single_file = True, |
| ), |
| # TODO: this isn't consumed yet |
| "changelog": attr.label( |
| allow_single_file = True, |
| ), |
| "srcs": attr.label_list( |
| doc = """Mapping groups to include in this RPM. |
| |
| These are typically brought into life as `pkg_filegroup`s. |
| """, |
| mandatory = True, |
| providers = [ |
| [PackageDirsInfo], |
| [PackageFilesInfo], |
| [PackageFilegroupInfo], |
| [PackageSymlinkInfo], |
| ], |
| ), |
| "debug": attr.bool( |
| doc = """Debug the RPM helper script and RPM generation""", |
| default = False, |
| ), |
| "pre_scriptlet": attr.string( |
| doc = """RPM `%pre` scriptlet. Currently only allowed to be a shell script. |
| |
| `pre_scriptlet` and `pre_scriptlet_file` are mutually exclusive. |
| """, |
| ), |
| "pre_scriptlet_file": attr.label( |
| doc = """File containing the RPM `%pre` scriptlet""", |
| allow_single_file = True, |
| ), |
| "post_scriptlet": attr.string( |
| doc = """RPM `%post` scriptlet. Currently only allowed to be a shell script. |
| |
| `post_scriptlet` and `post_scriptlet_file` are mutually exclusive. |
| """, |
| ), |
| "post_scriptlet_file": attr.label( |
| doc = """File containing the RPM `%post` scriptlet""", |
| allow_single_file = True, |
| ), |
| "preun_scriptlet": attr.string( |
| doc = """RPM `%preun` scriptlet. Currently only allowed to be a shell script. |
| |
| `preun_scriptlet` and `preun_scriptlet_file` are mutually exclusive. |
| """, |
| ), |
| "preun_scriptlet_file": attr.label( |
| doc = """File containing the RPM `%preun` scriptlet""", |
| allow_single_file = True, |
| ), |
| "postun_scriptlet": attr.string( |
| doc = """RPM `%postun` scriptlet. Currently only allowed to be a shell script. |
| |
| `postun_scriptlet` and `postun_scriptlet_file` are mutually exclusive. |
| """, |
| ), |
| "postun_scriptlet_file": attr.label( |
| doc = """File containing the RPM `%postun` scriptlet""", |
| allow_single_file = True, |
| ), |
| "posttrans_scriptlet": attr.string( |
| doc = """RPM `%posttrans` scriptlet. Currently only allowed to be a shell script. |
| |
| `posttrans_scriptlet` and `posttrans_scriptlet_file` are mutually exclusive. |
| """, |
| ), |
| "posttrans_scriptlet_file": attr.label( |
| doc = """File containing the RPM `%posttrans` scriptlet""", |
| allow_single_file = True, |
| ), |
| "conflicts": attr.string_list( |
| doc = """List of capabilities that conflict with this package when it is installed. |
| |
| Corresponds to the "Conflicts" preamble tag. |
| |
| See also: https://rpm-software-management.github.io/rpm/manual/dependencies.html |
| """, |
| ), |
| "provides": attr.string_list( |
| doc = """List of rpm capabilities that this package provides. |
| |
| Corresponds to the "Provides" preamble tag. |
| |
| See also: https://rpm-software-management.github.io/rpm/manual/dependencies.html |
| """, |
| ), |
| "obsoletes": attr.string_list( |
| doc = """List of rpm capability expressions that this package obsoletes. |
| |
| Corresponds to the "Obsoletes" preamble tag. |
| |
| 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. |
| |
| Corresponds to the "Requires" preamble tag. |
| |
| See also: https://rpm-software-management.github.io/rpm/manual/dependencies.html |
| """, |
| ), |
| "requires_contextual": attr.string_list_dict( |
| doc = """Contextualized requirement specifications |
| |
| This is a map of various properties (often scriptlet types) to |
| capability name specifications, e.g.: |
| |
| ```python |
| {"pre": ["GConf2"],"post": ["GConf2"], "postun": ["GConf2"]} |
| ``` |
| |
| Which causes the below to be added to the spec file's preamble: |
| |
| ``` |
| Requires(pre): GConf2 |
| Requires(post): GConf2 |
| Requires(postun): GConf2 |
| ``` |
| |
| This is most useful for ensuring that required tools exist when |
| scriptlets are run, although there may be other valid use cases. |
| Valid keys for this attribute may include, but are not limited to: |
| |
| - `pre` |
| - `post` |
| - `preun` |
| - `postun` |
| - `pretrans` |
| - `posttrans` |
| |
| For capabilities that are always required by packages at runtime, |
| use the `requires` attribute instead. |
| |
| See also: https://rpm-software-management.github.io/rpm/manual/more_dependencies.html |
| |
| NOTE: `pkg_rpm` does not check if the keys of this dictionary are |
| acceptable to `rpm(8)`. |
| """, |
| ), |
| "spec_template": attr.label( |
| doc = """Spec file template. |
| |
| Use this if you need to add additional logic to your spec files that |
| is not available by default. |
| |
| In most cases, you should not need to override this attribute. |
| """, |
| allow_single_file = spec_filetype, |
| default = "//pkg/rpm:template.spec.tpl", |
| ), |
| "binary_payload_compression": attr.string( |
| doc = """Compression mode used for this RPM |
| |
| Must be a form that `rpmbuild(8)` knows how to process, which will |
| depend on the version of `rpmbuild` in use. The value corresponds |
| to the `%_binary_payload` macro and is set on the `rpmbuild(8)` |
| command line if provided. |
| |
| Some examples of valid values (which may not be supported on your |
| system) can be found [here](https://git.io/JU9Wg). On CentOS |
| systems (also likely Red Hat and Fedora), you can find some |
| supported values by looking for `%_binary_payload` in |
| `/usr/lib/rpm/macros`. Other systems have similar files and |
| configurations. |
| |
| If not provided, the compression mode will be computed by `rpmbuild` |
| itself. Defaults may vary per distribution or build of `rpm`; |
| consult the relevant documentation for more details. |
| |
| WARNING: Bazel is currently not aware of action threading requirements |
| for non-test actions. Using threaded compression may result in |
| overcommitting your system. |
| """, |
| ), |
| "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], |
| ], |
| ), |
| "debuginfo": attr.bool( |
| doc = """Enable generation of debuginfo RPMs |
| |
| For supported platforms this will enable the generation of debuginfo RPMs adjacent |
| to the regular RPMs. Currently this is supported by Fedora 40, CentOS7 and |
| CentOS Stream 9. |
| """, |
| default = False, |
| ), |
| "rpmbuild_path": attr.string( |
| doc = """Path to a `rpmbuild` binary. Deprecated in favor of the rpmbuild toolchain""", |
| ), |
| "data": attr.label_list( |
| doc = """Extra files that are needed by rpmbuild or find-debuginfo""", |
| allow_files = True, |
| ), |
| # Implicit dependencies. |
| "_make_rpm": attr.label( |
| default = Label("//pkg:make_rpm"), |
| cfg = "exec", |
| executable = True, |
| allow_files = True, |
| ), |
| "_treeartifact_helper": attr.label( |
| default = Label("//pkg/rpm:augment_rpm_files_install"), |
| cfg = "exec", |
| executable = True, |
| allow_files = True, |
| ), |
| }, |
| executable = False, |
| 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, |
| postun_scriptlet = ctx.attr.postun_scriptlet, |
| architecture = ctx.attr.architecture, |
| epoch = ctx.attr.epoch, |
| version = ctx.attr.version, |
| requires = ctx.attr.requires, |
| provides = ctx.attr.provides, |
| conflicts = ctx.attr.conflicts, |
| obsoletes = ctx.attr.obsoletes, |
| 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"), |
| "postun_scriptlet": attr.string(doc = "RPM `%postun` scriplet for this subrpm"), |
| "architecture": attr.string(doc = "Sub RPM architecture"), |
| "epoch": attr.string(doc = "RPM `Epoch` tag for this subrpm"), |
| "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"), |
| "conflicts": attr.string_list(doc = "List of RPM capability expressions that conflict with this package"), |
| "obsoletes": attr.string_list(doc = "List of RPM capability expressions that this package obsoletes"), |
| "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], |
| ) |