Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc_build/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ ORDER = [
("pkg_deb_impl", "//pkg/private/deb:deb.bzl"),
("pkg_sub_rpm", "//pkg:rpm_pfg.bzl"),
("pkg_rpm", "//pkg:rpm_pfg.bzl"),
("pkg_rpm_impl", "//pkg:rpm_pfg.bzl"),
("pkg_tar", "//pkg/private/tar:tar.bzl"),
("pkg_tar_impl", "//pkg/private/tar:tar.bzl"),
("pkg_zip", "//pkg/private/zip:zip.bzl"),
Expand Down
42 changes: 42 additions & 0 deletions examples/rpm/system_rpmbuild_stamp_bzlmod/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2026 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.

# Example: use stamp = 1 to substitute workspace status variables in the
# release tag. Build with `bazel build --stamp :test-rpm-stamped` to embed
# the build timestamp.

load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
load("@rules_pkg//pkg:rpm.bzl", "pkg_rpm")

pkg_files(
name = "rpm_content",
srcs = [
"BUILD",
"MODULE.bazel",
"README.md",
],
prefix = "/usr/share",
)

pkg_rpm(
name = "test-rpm-stamped",
srcs = ["rpm_content"],
architecture = "noarch",
description = "placeholder 2",
license = "Apache License, v2.0",
release = "0.{BUILD_TIMESTAMP}",
stamp = 1,
summary = "rules_pkg example RPM with stamped release",
version = "1",
)
33 changes: 33 additions & 0 deletions examples/rpm/system_rpmbuild_stamp_bzlmod/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2026 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_pfg")

bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "rules_cc", version = "0.1.2")
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",
)
19 changes: 19 additions & 0 deletions examples/rpm/system_rpmbuild_stamp_bzlmod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Using system rpmbuild with bzlmod and stamp = 1

## Summary

Target declarations may use the `stamp` attribute to control the time
stamping of files in an archive. The behavior follows the pattern of
the cc_binary rule:

https://docs.bazel.build/versions/main/be/c-cpp.html#cc_binary

Read the BUILD file for more details.

## To Demonstrate

```
bazel build test-rpm-stamped
# look for the build timestamp in the Release field:
rpm -qip bazel-bin/test-rpm-stamped-1.noarch.rpm
```
6 changes: 2 additions & 4 deletions pkg/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,7 @@ py_binary(
"//conditions:default": [":not_compatible"],
}),
visibility = ["//visibility:public"],
deps = [
"//pkg:make_rpm_lib",
"//pkg/private:archive",
],
deps = ["//pkg:make_rpm_lib"],
)

py_library(
Expand All @@ -96,6 +93,7 @@ py_library(
],
deps = [
"//pkg/private:archive",
"//pkg/private:build_info",
"//pkg/private:helpers",
],
)
Expand Down
16 changes: 15 additions & 1 deletion pkg/make_rpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import tempfile
from string import Template

from pkg.private import build_info
from pkg.private import helpers


Expand Down Expand Up @@ -188,11 +189,14 @@ class RpmBuilder(object):
}

def __init__(self, name, version, release, arch, rpmbuild_path,
source_date_epoch=None,
source_date_epoch=None, stamp_vars=None,
debug=False):
self.name = name
self.version = helpers.GetFlagValue(version)
self.release = helpers.GetFlagValue(release)
if stamp_vars and self.release:
for key, val in stamp_vars.items():
self.release = self.release.replace('{' + key + '}', val)
self.arch = arch
self.files = []
self.rpmbuild_path = FindRpmbuild(rpmbuild_path)
Expand Down Expand Up @@ -560,6 +564,10 @@ def main(argv):
'environment variable')
parser.add_argument('--debug', action='store_true', default=False,
help='Print debug messages.')
parser.add_argument('--volatile_status_file', default='',
help='Path to volatile-status.txt for stamp variable substitution.')
parser.add_argument('--stable_status_file', default='',
help='Path to stable-status.txt for stamp variable substitution. ')

# Options currently used experimental/rpm.bzl:
parser.add_argument('--install_script',
Expand Down Expand Up @@ -595,10 +603,16 @@ def main(argv):
options = parser.parse_args(argv or ())

try:
stamp_vars = {}
if options.volatile_status_file:
stamp_vars = build_info.get_status_vars(options.volatile_status_file)
if options.stable_status_file:
stamp_vars.update(build_info.get_status_vars(options.stable_status_file))
builder = RpmBuilder(options.name,
options.version, options.release,
options.arch, options.rpmbuild,
source_date_epoch=options.source_date_epoch,
stamp_vars=stamp_vars,
debug=options.debug)
builder.AddFiles(options.files)
return builder.Build(options.spec_file, options.out_file,
Expand Down
47 changes: 37 additions & 10 deletions pkg/private/build_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,41 @@
# 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.
"""Get BUILD_TIMESTAMP."""
"""Workspace status file utilities."""

def get_status_vars(status_file, include_empty=True):
"""Read workspace status variables from a status file.

Reads a file of "name<space>value" pairs and returns a dict of all
variables found. The file should be in the workspace status format:
https://docs.bazel.build/versions/master/user-manual.html#workspace_status

Args:
status_file: path to a workspace status file. Typically
ctx.info_file.path (stable) or ctx.version_file.path (volatile).
include_empty: if True (default), keys with no value are included
in the result with an empty-string value.
Returns:
dict: mapping of variable name to value.
"""
result = {}
with open(status_file, 'r') as f:
for line in f:
stripped = line.strip()
if not stripped:
continue
parts = stripped.split(' ', 1)
if len(parts) == 2:
result[parts[0]] = parts[1]
elif include_empty:
result[parts[0]] = ""

return result

def get_timestamp(volatile_status_file):
"""Get BUILD_TIMESTAMP as an integer.
"""Get BUILD_TIMESTAMP as an integer from volatile-status.txt.

Reads a file of "name<space>value" pairs and returns the value
of the BUILD_TIMESTAMP. The file should be in the workspace status
The file should be in the workspace status
format: https://docs.bazel.build/versions/master/user-manual.html#workspace_status

Args:
Expand All @@ -28,10 +55,10 @@ def get_timestamp(volatile_status_file):
Exceptions:
Exception: Raised if there is no BUILD_TIMESTAMP or if it is not a number.
"""
with open(volatile_status_file, 'r') as status_f:
for line in status_f:
parts = line.strip().split(' ')
if len(parts) > 1 and parts[0] == 'BUILD_TIMESTAMP':
return int(parts[1])
status_vars = get_status_vars(volatile_status_file, include_empty=False)
key = 'BUILD_TIMESTAMP'
ts = status_vars.get(key)
if ts is not None:
return int(ts)
raise Exception(
"Invalid status file <%s>. Expected to find BUILD_TIMESTAMP" % volatile_status_file)
"Invalid status file <%s>. Expected to find %s" % (volatile_status_file, key))
60 changes: 53 additions & 7 deletions pkg/rpm_pfg.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ load(
"PackageSymlinkInfo",
"PackageVariablesInfo",
)
load("//pkg/private:util.bzl", "setup_output_files", "substitute_package_variables")
load("//pkg/private:util.bzl", "get_stamp_detect", "setup_output_files", "substitute_package_variables")
load(
"//toolchains/rpm:rpmbuild_configure.bzl",
"DEBUGINFO_TYPE_FEDORA",
Expand Down Expand Up @@ -343,7 +343,7 @@ def _process_dep(dep, rpm_ctx, debuginfo_type):
rpm_ctx,
)

def _process_subrpm(ctx, rpm_name, rpm_info, rpm_ctx, debuginfo_type):
def _process_subrpm(ctx, rpm_name, rpm_info, rpm_ctx, debuginfo_type, effective_release = None):
sub_rpm_ctx = struct(
dest_check_map = {},
install_script_pieces = [],
Expand Down Expand Up @@ -424,7 +424,7 @@ def _process_subrpm(ctx, rpm_name, rpm_info, rpm_ctx, debuginfo_type):
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,
release = effective_release,
)

default_file = ctx.actions.declare_file("{}-{}.rpm".format(rpm_name, rpm_info.package_name))
Expand Down Expand Up @@ -478,6 +478,7 @@ def _pkg_rpm_impl(ctx):
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)
_stamp_active = ctx.attr.stamp == 1 or (ctx.attr.stamp == -1 and ctx.attr.private_stamp_detect)

if ctx.attr.debug:
rpm_ctx.make_rpm_args.append("--debug")
Expand Down Expand Up @@ -513,13 +514,15 @@ def _pkg_rpm_impl(ctx):

default_file = ctx.actions.declare_file("{}.rpm".format(rpm_name))

# When stamp is active, don't embed template placeholders in the filename.
effective_release = ctx.attr.release if (ctx.attr.release and not _stamp_active) else None
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,
release = effective_release,
)

#### rpm spec "preamble"
Expand Down Expand Up @@ -549,10 +552,21 @@ def _pkg_rpm_impl(ctx):
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)
if _stamp_active:
# Route through make_rpm.py so stamp vars can be substituted
preamble_pieces.append("Release: ${{RELEASE_FROM_FILE}}")
rpm_ctx.make_rpm_args.append("--release=" + ctx.attr.release)
else:
preamble_pieces.append("Release: " + ctx.attr.release)
else:
fail("None of the release or release_file attributes were specified")

if _stamp_active:
rpm_ctx.make_rpm_args.append("--volatile_status_file=" + ctx.version_file.path)
files.append(ctx.version_file)
rpm_ctx.make_rpm_args.append("--stable_status_file=" + ctx.info_file.path)
files.append(ctx.info_file)

# 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.
Expand Down Expand Up @@ -755,6 +769,7 @@ def _pkg_rpm_impl(ctx):
s[PackageSubRPMInfo],
rpm_ctx,
debuginfo_type,
effective_release = effective_release,
))

subrpm_file = ctx.actions.declare_file(
Expand All @@ -776,7 +791,7 @@ def _pkg_rpm_impl(ctx):
ctx.attr.version,
ctx.attr.architecture,
package_name = "debuginfo",
release = ctx.attr.release,
release = effective_release,
)

_, debuginfo_output_file, _ = setup_output_files(
Expand Down Expand Up @@ -913,7 +928,7 @@ def _pkg_rpm_impl(ctx):
]

# Define the rule.
pkg_rpm = rule(
pkg_rpm_impl = 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
Expand Down Expand Up @@ -991,6 +1006,14 @@ pkg_rpm = rule(
doc = """RPM "Release" tag

Exactly one of `release` or `release_file` must be provided.

When `stamp` is enabled, workspace status variable placeholders
of the form `{VARIABLE_NAME}` will be substituted at build time
using values from the stable and volatile status files. For
example, setting `release = "0.{BUILD_TIMESTAMP}"` with
`stamp = 1` will embed the build timestamp in the release tag.
See https://bazel.build/docs/user-manual#workspace-status for
details on workspace status variables.
""",
),
"release_file": attr.label(
Expand Down Expand Up @@ -1290,6 +1313,18 @@ pkg_rpm = rule(
doc = """Extra files that are needed by rpmbuild or find-debuginfo""",
allow_files = True,
),
"stamp": attr.int(
doc = """Enable stamping for volatile release values. Possible values:
<li>stamp = 1: Substitute workspace status variables in the release tag.
<li>stamp = 0: No substitution; release tag used as-is.
<li>stamp = -1: Controlled by the --[no]stamp flag.
""",
values = [-1, 0, 1],
default = 0,
),
# Is --stamp set on the command line?
# TODO(https://github.com/bazelbuild/rules_pkg/issues/340): Remove this.
"private_stamp_detect": attr.bool(default = False),
# Implicit dependencies.
"_make_rpm": attr.label(
default = Label("//pkg:make_rpm"),
Expand All @@ -1309,6 +1344,17 @@ pkg_rpm = rule(
toolchains = ["@rules_pkg//toolchains/rpm:rpmbuild_toolchain_type"],
)

def pkg_rpm(name, **kwargs):
"""Creates an RPM format package via `pkg_filegroup` and friends.

@wraps(pkg_rpm_impl)
"""
pkg_rpm_impl(
name = name,
private_stamp_detect = get_stamp_detect(kwargs.get("stamp", 0)),
**kwargs
)

def _pkg_sub_rpm_impl(ctx):
mapped_files_depsets = []

Expand Down
Loading