Provide rpmbuild as a toolchain. (#254)

* Find path to rpmbuild via a toolchain.
- repository rule to autoconfigure by finding the path
- use target_compatible_with so pkg_rpm rules can skip on platforms without rpmbuild.
This commit is contained in:
aiuto 2021-01-14 00:00:35 -05:00 committed by GitHub
parent 3b614da0c7
commit f8284d8ed4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 552 additions and 10 deletions

1
examples/BUILD Normal file
View File

@ -0,0 +1 @@
# placeholder

View File

@ -0,0 +1,38 @@
# Copyright 2020 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//:rpm.bzl", "pkg_rpm")
pkg_rpm(
name = "test-rpm",
data = [
"BUILD",
"WORKSPACE",
"readme.md",
"test_rpm.spec",
],
release = "0",
spec_file = "test_rpm.spec",
version = "1",
)
# 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 >$@",
)

View File

@ -0,0 +1,28 @@
# Copyright 2020 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.
workspace(name = "rules_pkg_example_prebuilt_rpmbuild")
local_repository(
name = "rules_pkg",
path = "../../pkg",
)
load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
rules_pkg_dependencies()
load("//local:rpmbuild.bzl", "register_my_rpmbuild_toolchain")
register_my_rpmbuild_toolchain()

View File

@ -0,0 +1,27 @@
# Copyright 2020 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.
load("@rules_pkg//toolchains:rpmbuild.bzl", "rpmbuild_toolchain")
rpmbuild_toolchain(
name = "prebuilt_rpmbuild",
# You could also point to a target that builds rpmbuild from source.
label = ":rpmbuild_binary",
)
toolchain(
name = "local_rpmbuild",
toolchain = ":prebuilt_rpmbuild",
toolchain_type = "@rules_pkg//toolchains:rpmbuild_toolchain_type",
)

View File

@ -0,0 +1,16 @@
# Copyright 2020 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.
def register_my_rpmbuild_toolchain():
native.register_toolchains("//local:local_rpmbuild")

Binary file not shown.

View File

@ -0,0 +1,10 @@
# Using a prebuilt rpmbuild instead of the system one.
## To use
```
cp /usr/bin/rpmbuild local/rpmbuild_binary
bazel build :*
rpm2cpio bazel-bin/test-rpm.rpm | cpio -ivt
cat bazel-bin/content.txt
```

View File

@ -0,0 +1,24 @@
Name: example
Version: 0
Release: 1
Summary: Example .spec file
License: Apache License, v2.0
# Do not try to use magic to determine file types
%define __spec_install_post %{nil}
# Do not die becuse we give it more input files than are in the files section
%define _unpackaged_files_terminate_build 0
%description
This is a package description.
%build
%install
cp WORKSPACE BUILD readme.md test_rpm.spec %{buildroot}/
%files
/WORKSPACE
/BUILD
/readme.md
/test_rpm.spec

View File

@ -8,6 +8,13 @@ exports_files(
visibility = ["//visibility:public"],
)
constraint_setting(name = "not_compatible_setting")
constraint_value(
name = "not_compatible",
constraint_setting = ":not_compatible_setting",
)
filegroup(
name = "standard_package",
srcs = glob([
@ -93,6 +100,11 @@ py_binary(
srcs = ["make_rpm.py"],
python_version = "PY3",
srcs_version = "PY3",
# TODO(aiuto): Enable this for bazel 4.x
#target_compatible_with = select({
# "//toolchains:have_rpmbuild": [],
# "//conditions:default": [":not_compatible"],
#}),
visibility = ["//visibility:public"],
deps = [
":archive",

View File

@ -24,13 +24,22 @@ and debian package.
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_pkg",
url = "https://github.com/bazelbuild/rules_pkg/releases/download/0.2.5/rules_pkg-0.2.5.tar.gz",
sha256 = "352c090cc3d3f9a6b4e676cf42a6047c16824959b438895a76c2989c6d7c246a",
url = "https://github.com/bazelbuild/rules_pkg/releases/download/0.3.0/rules_pkg-0.3.0.tar.gz",
sha256 = "6b5969a7acd7b60c02f816773b06fcf32fbe8ba0c7919ccdc2df4f8fb923804a",
)
load("@rules_pkg//:deps.bzl", "rules_pkg_dependencies")
rules_pkg_dependencies()
```
If you want to use `pkg_rpm()` you must instantiate a toolchain to provide the
`rpmbuild` tool. Add this to WORKSPACE to use the installed version.
```
# Find rpmbuild provided on your system.
load("//toolchains:rpmbuild_configure.bzl", "find_system_rpmbuild")
find_system_rpmbuild(name="rules_pkg_rpmbuild")
```
<a name="basic-example"></a>
### Basic Example

View File

@ -27,11 +27,19 @@ bazel_skylib_workspace()
#
# Include dependencies which are only needed for development here.
# Find rpmbuild if it exists.
load("@rules_pkg//toolchains:rpmbuild_configure.bzl", "find_system_rpmbuild")
find_system_rpmbuild(
name = "rules_pkg_rpmbuild",
verbose = True,
)
http_archive(
name = "bazel_stardoc",
url = "https://github.com/bazelbuild/stardoc/archive/0.4.0.zip",
sha256 = "36b8d6c2260068b9ff82faea2f7add164bf3436eac9ba3ec14809f335346d66a",
strip_prefix = "stardoc-0.4.0",
url = "https://github.com/bazelbuild/stardoc/archive/0.4.0.zip",
)
load("@bazel_stardoc//:setup.bzl", "stardoc_repositories")

View File

@ -17,6 +17,7 @@ pkg_tar(
srcs = [
":small_workspace",
"//:standard_package",
"//toolchains:standard_package",
"//releasing:standard_package",
],
extension = "tar.gz",

View File

@ -151,9 +151,15 @@ class InvalidRpmbuildError(Exception):
def FindRpmbuild(rpmbuild_path):
"""Finds absolute path to rpmbuild.
Args:
rpmbuild_path: path to the rpmbuild_binary. If None, find 'rpmbuild' by
walking $PATH.
"""
if rpmbuild_path:
if not IsExe(rpmbuild_path):
raise InvalidRpmbuildError('{} is not executable'.format(rpmbuild_path))
if not rpmbuild_path.startswith(os.path.sep):
return os.path.join(os.getcwd(), rpmbuild_path)
return rpmbuild_path
path = Which('rpmbuild')
if path:

View File

@ -11,7 +11,16 @@
# 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.
"""Rules to create RPM archives."""
"""Rules to create RPM archives.
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:rpmbuild_configure.bzl", "find_system_rpmbuild")
find_system_rpmbuild(name="rules_pkg_rpmbuild")
"""
rpm_filetype = [".rpm"]
@ -21,12 +30,27 @@ def _pkg_rpm_impl(ctx):
"""Implements to pkg_rpm rule."""
files = []
tools = []
args = ["--name=" + ctx.label.name]
if ctx.attr.debug:
args += ["--debug"]
if ctx.attr.rpmbuild_path:
args += ["--rpmbuild=" + ctx.attr.rpmbuild_path]
print('rpmbuild_path is deprecated. See the README for instructions on how'
+ ' to migrate to toolchains')
else:
toolchain = ctx.toolchains["@rules_pkg//toolchains:rpmbuild_toolchain_type"].rpmbuild
if not toolchain.valid:
fail("The rpmbuild_toolchain is not properly configured: "
+ toolchain.name)
if toolchain.path:
args += ["--rpmbuild=" + toolchain.path]
else:
executable = toolchain.label.files_to_run.executable
tools += [executable]
tools += toolchain.label.default_runfiles.files.to_list()
args += ["--rpmbuild=%s" % executable.path]
# Version can be specified by a file or inlined.
if ctx.attr.version_file:
@ -89,7 +113,6 @@ def _pkg_rpm_impl(ctx):
args += [f.path]
# Call the generator script.
# TODO(katre): Generate a source RPM.
ctx.actions.run(
mnemonic = "MakeRpm",
executable = ctx.executable._make_rpm,
@ -103,19 +126,20 @@ def _pkg_rpm_impl(ctx):
"PYTHONIOENCODING": "UTF-8",
"PYTHONUTF8": "1",
},
tools = tools,
)
# Link the RPM to the expected output name.
ctx.actions.symlink(
output = ctx.outputs.out,
target_file = ctx.outputs.rpm
target_file = ctx.outputs.rpm,
)
# Link the RPM to the RPM-recommended output name if possible.
if "rpm_nvra" in dir(ctx.outputs):
ctx.actions.symlink(
output = ctx.outputs.rpm_nvra,
target_file = ctx.outputs.rpm
target_file = ctx.outputs.rpm,
)
def _pkg_rpm_outputs(version, release):
@ -158,7 +182,7 @@ pkg_rpm = rule(
"debug": attr.bool(default = False),
# Implicit dependencies.
"rpmbuild_path": attr.string(),
"rpmbuild_path": attr.string(), # deprecated
"_make_rpm": attr.label(
default = Label("//:make_rpm"),
cfg = "exec",
@ -169,6 +193,7 @@ pkg_rpm = rule(
executable = False,
outputs = _pkg_rpm_outputs,
implementation = _pkg_rpm_impl,
toolchains = ["@rules_pkg//toolchains:rpmbuild_toolchain_type"],
)
"""Creates an RPM format package from the data files.

View File

@ -15,6 +15,7 @@
licenses(["notice"])
load(":toolchain_tests.bzl", "create_toolchain_analysis_tests")
load("//:rpm.bzl", "pkg_rpm")
pkg_rpm(
@ -25,3 +26,5 @@ pkg_rpm(
spec_file = "test_rpm.spec",
version = "1",
)
create_toolchain_analysis_tests()

View File

@ -0,0 +1,123 @@
# Copyright 2020 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.
"""Tests for rpmbuild toolchain type."""
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("//toolchains:rpmbuild.bzl", "rpmbuild_toolchain")
# Generic negative test boilerplate
def _generic_neg_test_impl(ctx):
env = analysistest.begin(ctx)
asserts.expect_failure(env, ctx.attr.reason)
return analysistest.end(env)
generic_neg_test = analysistest.make(
_generic_neg_test_impl,
attrs = {
"reason": attr.string(
default = "",
),
},
expect_failure = True,
)
def _toolchain_contents_test_impl(ctx):
env = analysistest.begin(ctx)
target_under_test = analysistest.target_under_test(env)
info = target_under_test[platform_common.ToolchainInfo].rpmbuild
asserts.equals(
env,
ctx.attr.expect_valid,
info.valid,
)
asserts.equals(
env,
ctx.attr.expect_label,
info.label,
)
asserts.equals(
env,
ctx.attr.expect_path,
info.path,
)
return analysistest.end(env)
toolchain_contents_test = analysistest.make(
_toolchain_contents_test_impl,
attrs = {
"expect_valid": attr.bool(default = True),
"expect_label": attr.label(
cfg = "exec",
executable = True,
allow_files = True,
),
"expect_path": attr.string(),
},
)
def _create_toolchain_creation_tests():
rpmbuild_toolchain(
name = "tc_label_and_path",
label = "foo",
path = "bar",
tags = ["manual"],
)
generic_neg_test(
name = "tc_label_and_path_test",
target_under_test = ":tc_label_and_path",
reason = "rpmbuild_toolchain must not specify both label and path.",
)
rpmbuild_toolchain(
name = "tc_no_label_or_path",
tags = ["manual"],
)
toolchain_contents_test(
name = "tc_no_label_or_path_test",
target_under_test = ":tc_no_label_or_path",
expect_valid = False,
expect_label = None,
expect_path = "",
)
rpmbuild_toolchain(
name = "tc_just_label",
label = ":toolchain_test.bzl", # Using self so we have a real target.
tags = ["manual"],
)
toolchain_contents_test(
name = "tc_just_label_test",
target_under_test = ":tc_just_label",
expect_valid = True,
expect_label = Label("//tests/rpm:toolchain_test.bzl"),
expect_path = "",
)
rpmbuild_toolchain(
name = "tc_just_path",
path = "/usr/bin/foo",
tags = ["manual"],
)
toolchain_contents_test(
name = "tc_just_path_test",
target_under_test = ":tc_just_path",
expect_valid = True,
expect_label = None,
expect_path = "/usr/bin/foo",
)
def create_toolchain_analysis_tests():
_create_toolchain_creation_tests()

76
pkg/toolchains/BUILD Normal file
View File

@ -0,0 +1,76 @@
# Copyright 2020 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.
"""toolchain to wrap an rpmbuild binary.
Type: @rules_pkg//toolchains:rpmbuild_toolchain_type
Toolchains:
- rpmbuild_missing_toolchain: provides a fallback toolchain for exec plaforms
where rpmbuild might not be available.
- rpmbuild_auto_toolchain: a toolchain that uses the installed rpmbuild. See
rpmbuild_configure.bzl%find_system_rpmbuild for usage.
"""
load("//toolchains:rpmbuild.bzl", "is_rpmbuild_available", "rpmbuild_toolchain")
filegroup(
name = "standard_package",
srcs = glob([
"*",
]),
visibility = ["//distro:__pkg__"],
)
exports_files(
glob(["*"]),
visibility = ["//visibility:public"],
)
# Expose the availabilty of an actual rpmbuild as a config_setting, so we can
# select() on it.
config_setting(
name = "have_rpmbuild",
flag_values = {
":is_rpmbuild_available": "1",
},
visibility = ["//visibility:public"],
)
# Expose the availabilty of an actual rpmbuild as a feature flag, so we can
# create a config_setting from it.
is_rpmbuild_available(
name = "is_rpmbuild_available",
visibility = ["//:__subpackages__"],
)
toolchain_type(
name = "rpmbuild_toolchain_type",
visibility = ["//visibility:public"],
)
# rpmbuild_missing_toolchain provides a fallback toolchain so that toolchain
# resolution can succeed even on platforms that do not have a working rpmbuild.
# If this toolchain is selected, the constraint ":have_rpmbuild" will not be
# satistifed.
rpmbuild_toolchain(
name = "no_rpmbuild",
)
toolchain(
name = "rpmbuild_missing_toolchain",
toolchain = ":no_rpmbuild",
toolchain_type = ":rpmbuild_toolchain_type",
)

13
pkg/toolchains/BUILD.tmpl Normal file
View File

@ -0,0 +1,13 @@
# This content is generated by {GENERATOR}
load("@rules_pkg//toolchains:rpmbuild.bzl", "rpmbuild_toolchain")
rpmbuild_toolchain(
name = "rpmbuild_auto",
path = "{RPMBUILD_PATH}",
)
toolchain(
name = "rpmbuild_auto_toolchain",
toolchain = ":rpmbuild_auto",
toolchain_type = "@rules_pkg//toolchains:rpmbuild_toolchain_type",
)

View File

@ -0,0 +1,69 @@
# Copyright 2020 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.
"""toolchain to provide an rpmbuild binary."""
RpmbuildInfo = provider(
doc = """Platform inde artifact.""",
fields = {
"name": "The name of the toolchain",
"valid": "Is this toolchain valid and usable?",
"label": "The path to a target I will build",
"path": "The path to a pre-built rpmbuild",
},
)
def _rpmbuild_toolchain_impl(ctx):
if ctx.attr.label and ctx.attr.path:
fail("rpmbuild_toolchain must not specify both label and path.")
valid = bool(ctx.attr.label) or bool(ctx.attr.path)
toolchain_info = platform_common.ToolchainInfo(
rpmbuild = RpmbuildInfo(
name = str(ctx.label),
valid = valid,
label = ctx.attr.label,
path = ctx.attr.path,
),
)
return [toolchain_info]
rpmbuild_toolchain = rule(
implementation = _rpmbuild_toolchain_impl,
attrs = {
"label": attr.label(
doc = "A valid label of a target to build or a prebuilt binary. Mutually exclusive with path.",
cfg = "exec",
executable = True,
allow_files = True,
),
"path": attr.string(
doc = "The path to the rpmbuild executable. Mutually exclusive with label.",
),
},
)
# Expose the presence of an rpmbuild in the resolved toolchain as a flag.
def _is_rpmbuild_available_impl(ctx):
toolchain = ctx.toolchains["@rules_pkg//toolchains:rpmbuild_toolchain_type"].rpmbuild
return [config_common.FeatureFlagInfo(
value = ("1" if toolchain.valid else "0"),
)]
is_rpmbuild_available = rule(
implementation = _is_rpmbuild_available_impl,
attrs = {},
toolchains = ["@rules_pkg//toolchains:rpmbuild_toolchain_type"],
)
def rpmbuild_register_toolchains():
native.register_toolchains("@rules_pkg//toolchains:rpmbuild_missing_toolchain")

View File

@ -0,0 +1,53 @@
# Copyright 2020 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.
"""Repository rule to autoconfigure a toolchain using the system rpmbuild."""
def _write_build(rctx, path):
if not path:
path = ""
rctx.template(
"BUILD",
Label("//toolchains:BUILD.tmpl"),
substitutions = {
"{GENERATOR}": "@rules_pkg//toolchains/rpmbuild_configure.bzl%find_system_rpmbuild",
"{RPMBUILD_PATH}": str(path),
},
executable = False,
)
def _find_system_rpmbuild_impl(rctx):
rpmbuild_path = rctx.which("rpmbuild")
if rctx.attr.verbose:
if rpmbuild_path:
print("Found rpmbuild at '%s'" % rpmbuild_path)
else:
print("No system rpmbuild found.")
_write_build(rctx = rctx, path = rpmbuild_path)
_find_system_rpmbuild = repository_rule(
implementation = _find_system_rpmbuild_impl,
doc = """Create a repository that defines an rpmbuild toolchain based on the system rpmbuild.""",
local = True,
attrs = {
"verbose": attr.bool(
doc = "If true, print status messages.",
),
},
)
def find_system_rpmbuild(name, verbose=False):
_find_system_rpmbuild(name=name, verbose=verbose)
native.register_toolchains(
"@%s//:rpmbuild_auto_toolchain" % name,
"@rules_pkg//toolchains:rpmbuild_missing_toolchain")