Skip to content

Commit

Permalink
[antlir2][toolchain] macro to discover rpm dependencies for a distro …
Browse files Browse the repository at this point in the history
…binary

Summary:
`rpm-build` comes with a helpful tool `/usr/lib/rpm/rpmdeps` that will
produce a list of all the rpm subjects that a binary requires.

This diff packages that up into a few little macros so that distro-targetted
binaries can be easily installed along with their dependencies, without having
to package it up into an rpm first.

Test Plan:
```
❯ buck2 test fbcode//antlir/distro/toolchain/cxx/tests:
Buck UI: https://www.internalfb.com/buck2/2b25ecba-b0ef-4413-bd84-4b3319f929f6
Test UI: https://www.internalfb.com/intern/testinfra/testrun/13510798946868876
Tests finished: Pass 56. Fail 0. Fatal 0. Skip 0. Build failure 0
```

Reviewed By: epilatow

Differential Revision: D68342695

fbshipit-source-id: f69166835d32d385a2bbfc6c42ab2c92d514b75b
  • Loading branch information
vmagro authored and facebook-github-bot committed Jan 28, 2025
1 parent 42f7032 commit d4ba038
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 18 deletions.
37 changes: 37 additions & 0 deletions antlir/distro/rpm/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load("//antlir/antlir2/bzl/feature:defs.bzl", "feature")
load("//antlir/antlir2/bzl/image:defs.bzl", "image")
load("//antlir/antlir2/bzl/package:defs.bzl", "package")
load("//antlir/antlir2/image_command_alias:image_command_alias.bzl", "image_command_alias")

oncall("antlir")

image.layer(
name = "layer",
features = [
feature.rpms_install(subjects = [
"/usr/lib/rpm/find-requires",
]),
feature.install(
src = "find-requires",
dst = "/usr/bin/find-requires",
mode = "a+rx",
),
],
rootless = True,
visibility = [],
)

package.unprivileged_dir(
name = "root",
layer = ":layer",
rootless = True,
visibility = [],
)

image_command_alias(
name = "find-requires",
exe = "/usr/bin/find-requires",
root = ":root",
rootless = True,
visibility = ["PUBLIC"],
)
7 changes: 7 additions & 0 deletions antlir/distro/rpm/find-requires
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -ex

out="$1"
shift

exec /usr/lib/rpm/rpmdeps --define="_use_internal_dependency_generator 1" --requires "$@" > "$out"
60 changes: 60 additions & 0 deletions antlir/distro/rpm/requires.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

load("//antlir/antlir2/bzl:platform.bzl", "rule_with_default_target_platform")
load("//antlir/antlir2/bzl/feature:defs.bzl", "feature")
load("//antlir/bzl:build_defs.bzl", "buck_genrule")
load("//antlir/bzl:target_helpers.bzl", "normalize_target")

def rpm_requires(binary: str):
name = "rpm-deps-for-" + binary.replace(":", "_")
if not native.rule_exists(name):
buck_genrule(
name = name,
out = "subjects.txt",
bash = """
$(exe antlir//antlir/distro/rpm:find-requires) \
$OUT \
$(location {binary}) \
""".format(binary = binary),
)
return normalize_target(":" + name)

def install_with_rpm_requires(*, src: str, **kwargs):
req = rpm_requires(src)
return [
feature.rpms_install(subjects_src = req),
feature.install(src = src, **kwargs),
]

def _query_all_binary_requirements_impl(ctx: AnalysisContext) -> list[Provider]:
requires = ctx.actions.declare_output("requires.txt")
ctx.actions.run(
cmd_args(
ctx.attrs._find_requires[RunInfo],
requires.as_output(),
[binary[DefaultInfo].default_outputs[0] for binary in ctx.attrs.q],
),
category = "find_requires",
)
return [DefaultInfo(requires)]

_query_all_binary_requirements_rule = rule(
impl = _query_all_binary_requirements_impl,
attrs = {
"q": attrs.query(),
"_find_requires": attrs.default_only(attrs.exec_dep(default = "antlir//antlir/distro/rpm:find-requires")),
},
)

_query_all_binary_requirements = rule_with_default_target_platform(_query_all_binary_requirements_rule)

def install_all_binary_rpm_requirements(layer: str):
name = "rpm-requires-{}".format(layer.replace(":", "_"))
_query_all_binary_requirements(
name = name,
q = "kind(cxx_binary, deps({}, 10000, target_deps()))".format(layer),
)
return feature.rpms_install(subjects_src = normalize_target(":" + name))
104 changes: 99 additions & 5 deletions antlir/distro/toolchain/cxx/tests/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ load("//antlir/antlir2/bzl/package:defs.bzl", "package")
load("//antlir/antlir2/testing:image_test.bzl", "image_python_test")
load("//antlir/bzl:build_defs.bzl", "cpp_binary", "cpp_library", "third_party")
load("//antlir/distro/platform:defs.bzl", "alias_for_current_image_platform", "default_image_platform")
load("//antlir/distro/rpm:requires.bzl", "install_all_binary_rpm_requirements", "install_with_rpm_requires")

oncall("antlir")

Expand Down Expand Up @@ -111,8 +112,12 @@ package.rpm(
rpm_name = "main",
)

# We want to test a matrix of combinations:
# CentOS 9 x 10
# Binary installed via packaged RPM x Binary installed directly

image.layer(
name = "test-layer",
name = "test-layer-from-rpm",
features = [
feature.rpms_install(rpms = [
"binutils",
Expand All @@ -123,21 +128,110 @@ image.layer(
)

image_python_test(
name = "test",
name = "test-from-rpm",
srcs = ["test.py"],
default_os = "centos10",
env = {
"INSTALL_MODE": "rpm",
"OS": select({
"//antlir/antlir2/os:centos10": "centos10",
"//antlir/antlir2/os:centos9": "centos9",
"DEFAULT": "<NEVER USED>",
}),
},
layer = ":test-layer-from-rpm",
)

antlir2_configured_alias(
name = "test-c9-from-rpm",
actual = ":test-from-rpm",
default_os = "centos9",
)

image.layer(
name = "test-layer-from-install",
features = [
feature.rpms_install(rpms = [
"binutils",
"coreutils",
]),
feature.ensure_dirs_exist(dirs = "/test"),
install_with_rpm_requires(
src = ":main-for-os",
dst = select({
"//antlir/antlir2/os:centos10": "/test/main-for-centos10",
"//antlir/antlir2/os:centos9": "/test/main-for-centos9",
}),
never_use_dev_binary_symlink = True,
),
],
)

image_python_test(
name = "test-from-install",
srcs = ["test.py"],
default_os = "centos10",
env = {
"INSTALL_MODE": "install",
"OS": select({
"//antlir/antlir2/os:centos10": "centos10",
"//antlir/antlir2/os:centos9": "centos9",
"DEFAULT": "<NEVER USED>",
}),
},
layer = ":test-layer-from-install",
)

antlir2_configured_alias(
name = "test-c9-from-install",
actual = ":test-from-install",
default_os = "centos9",
)

image.layer(
name = "test-layer-from-global-query-pre",
features = [
feature.rpms_install(rpms = [
"binutils",
"coreutils",
]),
feature.ensure_dirs_exist(dirs = "/test"),
feature.install(
src = ":main-for-os",
dst = select({
"//antlir/antlir2/os:centos10": "/test/main-for-centos10",
"//antlir/antlir2/os:centos9": "/test/main-for-centos9",
}),
never_use_dev_binary_symlink = True,
),
],
)

image.layer(
name = "test-layer-from-global-query",
features = [
install_all_binary_rpm_requirements(layer = ":test-layer-from-global-query-pre"),
],
parent_layer = ":test-layer-from-global-query-pre",
)

image_python_test(
name = "test-from-global-query",
srcs = ["test.py"],
default_os = "centos10",
env = {
"INSTALL_MODE": "global-query",
"OS": select({
"//antlir/antlir2/os:centos10": "centos10",
"//antlir/antlir2/os:centos9": "centos9",
"DEFAULT": "<NEVER USED>",
}),
},
layer = ":test-layer",
layer = ":test-layer-from-install",
)

antlir2_configured_alias(
name = "test-c9",
actual = ":test",
name = "test-c9-from-global-query",
actual = ":test-from-global-query",
default_os = "centos9",
)
34 changes: 21 additions & 13 deletions antlir/distro/toolchain/cxx/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,28 @@ def test_rpm_dependencies(self) -> None:
binary and letting rpmbuild do it is great to avoid user mistakes
forgetting to define dependencies.
"""
requires = set(
subprocess.run(
["rpm", "-q", "--requires", "main"],
check=True,
capture_output=True,
text=True,
if os.environ["INSTALL_MODE"] == "rpm":
requires = set(
subprocess.run(
["rpm", "-q", "--requires", "main"],
check=True,
capture_output=True,
text=True,
)
.stdout.strip()
.splitlines()
)
.stdout.strip()
.splitlines()
)
self.assertTrue(
any(r.startswith("librpm.so") for r in requires),
"'main' did not require librpm.so",
)
self.assertTrue(
any(r.startswith("librpm.so") for r in requires),
"'main' did not require librpm.so",
)
elif os.environ["INSTALL_MODE"] in {"install", "global-query"}:
# Don't really need to do anything here, if the other tests pass
# then that means that the rpm dependencies were correctly
# determined
pass
else:
self.fail("unknown INSTALL_MODE=" + os.environ["INSTALL_MODE"])

def test_platform_preprocessor_flags(self) -> None:
"""
Expand Down

0 comments on commit d4ba038

Please sign in to comment.