Skip to content

Commit 1d69a9d

Browse files
aiutomwdd146980
authored andcommitted
Wrap otool and patchelf in toolchains. (#44023)
- Create toolchain types for otool and patchelf - otool - macos: find system tool - linux: not available - patchelf: - macos: use local build - linux: prefer use patchelf from system, fallback to local build. ### Motivation - Having explicit toolchains is an important part of building an SBOM covering *how* we built the Agent. Even if we don't think we need this today, we will in a quarter or two. - The toolchains come with config_setting targets so that we can skip tests using `target_compatible_with` when they are not available. That will make it easier to manage conditional tests on rules using these tools (such as the rewrite_paths). ### Testing - Query the toolchains and look for the providers. Note that linux has 2 marked `valid`, and /usr/bin/patchelf is first. Macos has one marked `valid`. - The model for the code is lifted from rules_pkg, it has been used for a long time there. linux: ``` $ bazel cquery @otool//:all --output=starlark --starlark:expr 'providers(target).get("ToolchainInfo", None)' | grep -v None struct(otool = struct(label = None, name = "@@+find_system_otool+otool//:otool_auto", path = "", valid = False, version = "unknown")) $ bazel cquery @patchelf_toolchains//:all --output=starlark --starlark:expr 'providers(target).get("ToolchainInfo", None)' | grep -v None struct(patchelf = struct(label = None, name = "@@+find_system_patchelf+patchelf_toolchains//:patchelf_auto", path = "/usr/bin/patchelf", valid = True, version = "0.18.0")) struct(patchelf = struct(label = <target @@+_repo_rules+patchelf//:patchelf>, name = "@@+find_system_patchelf+patchelf_toolchains//:patchelf_local_build", path = "", valid = True, version = "")) ``` macos: ``` $ bazel cquery @otool//:all --output=starlark --starlark:expr 'providers(target).get("ToolchainInfo", None)' struct(otool = struct(label = None, name = "@@+find_system_otool+otool//:otool_auto", path = "/usr/bin/otool", valid = True, version = "cctools-1030.6.3")) $ bazel cquery @patchelf_toolchains//:all --output=starlark --starlark:expr 'providers(target).get("ToolchainInfo", None)' struct(patchelf = struct(label = None, name = "@@+find_system_patchelf+patchelf_toolchains//:patchelf_auto", path = "", valid = False, version = "unknown")) struct(patchelf = struct(label = <target @@+_repo_rules+patchelf//:patchelf>, name = "@@+find_system_patchelf+patchelf_toolchains//:patchelf_local_build", path = "", valid = True, version = "")) ``` Co-authored-by: tony.aiuto <tony.aiuto@datadoghq.com>
1 parent c6a202b commit 1d69a9d

File tree

13 files changed

+433
-1
lines changed

13 files changed

+433
-1
lines changed

MODULE.bazel

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ multitool.hub(lockfile = "//bazel:prebuilt_buildtools.json")
6565
multitool.hub(lockfile = "//bazel:prebuilt_jq.json")
6666
use_repo(multitool, "multitool")
6767

68+
## Use local object file tools
69+
find_system_otool = use_extension("//bazel/toolchains/otool:otool_configure.bzl", "find_system_otool", dev_dependency = True)
70+
use_repo(find_system_otool, "otool")
71+
72+
register_toolchains(
73+
"@otool//:all",
74+
dev_dependency = True,
75+
)
76+
77+
find_system_patchelf = use_extension("//bazel/toolchains/patchelf:patchelf_configure.bzl", "find_system_patchelf", dev_dependency = True)
78+
use_repo(find_system_patchelf, "patchelf_toolchains")
79+
80+
register_toolchains(
81+
"@patchelf_toolchains//:all",
82+
dev_dependency = True,
83+
)
84+
6885
######################################
6986
## Compilers and toolchains ##
7087
####################################

MODULE.bazel.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bazel/toolchains/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# //bazel/toolchains.
2+
3+
# Placeholder package

bazel/toolchains/otool/BUILD.bazel

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""toolchain to wrap the otool binary.
2+
3+
Type: @@//bazel/toolchains/otool:otool_toolchain_type
4+
5+
Toolchains:
6+
- otool_missing_toolchain: provides a fallback toolchain for exec platforms
7+
where otool might not be available.
8+
9+
- otool_auto_toolchain: a toolchain that uses the installed otool. See
10+
otool_configure.bzl%find_system_otool for usage.
11+
"""
12+
13+
load("@@//bazel/toolchains/otool:otool.bzl", "is_otool_available", "otool_toolchain")
14+
15+
# Expose the availability of an actual otool as a config_setting, so we can
16+
# select() on it.
17+
config_setting(
18+
name = "have_otool",
19+
flag_values = {
20+
":is_otool_available": "1",
21+
},
22+
visibility = ["//visibility:public"],
23+
)
24+
25+
# Expose the availability of an actual otool as a feature flag, so we can
26+
# create a config_setting from it.
27+
is_otool_available(
28+
name = "is_otool_available",
29+
)
30+
31+
toolchain_type(
32+
name = "otool_toolchain_type",
33+
visibility = ["//visibility:public"],
34+
)
35+
36+
# otool_missing_toolchain provides a fallback toolchain so that toolchain
37+
# resolution can succeed even on platforms that do not have a working otool.
38+
# If this toolchain is selected, the constraint ":have_otool" will not be
39+
# satistifed.
40+
otool_toolchain(
41+
name = "no_otool",
42+
)
43+
44+
toolchain(
45+
name = "otool_missing_toolchain",
46+
toolchain = ":no_otool",
47+
toolchain_type = ":otool_toolchain_type",
48+
)

bazel/toolchains/otool/BUILD.tpl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This content is generated by {GENERATOR}
2+
load("@@//bazel/toolchains/otool:otool.bzl", "otool_toolchain")
3+
4+
otool_toolchain(
5+
name = "otool_auto",
6+
path = "{OTOOL_PATH}",
7+
version = "{OTOOL_VERSION}",
8+
)
9+
10+
toolchain(
11+
name = "otool_auto_toolchain",
12+
toolchain = ":otool_auto",
13+
toolchain_type = "@@//bazel/toolchains/otool:otool_toolchain_type",
14+
)
15+
16+
toolchain(
17+
name = "zzz_otool_missing_toolchain", # keep name lexicographically last
18+
toolchain = "@@//bazel/toolchains/otool:no_otool",
19+
toolchain_type = "@@//bazel/toolchains/otool:otool_toolchain_type",
20+
)

bazel/toolchains/otool/otool.bzl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""toolchain to provide an otool binary."""
2+
3+
load("@@//bazel/toolchains:toolchain_info.bzl", "ToolInfo")
4+
5+
def _otool_toolchain_impl(ctx):
6+
if ctx.attr.label and ctx.attr.path:
7+
fail("otool_toolchain must not specify both label and path.")
8+
valid = bool(ctx.attr.label) or bool(ctx.attr.path)
9+
toolchain_info = platform_common.ToolchainInfo(
10+
otool = ToolInfo(
11+
name = str(ctx.label),
12+
valid = valid,
13+
label = ctx.attr.label,
14+
path = ctx.attr.path,
15+
version = ctx.attr.version,
16+
),
17+
)
18+
return [toolchain_info]
19+
20+
otool_toolchain = rule(
21+
implementation = _otool_toolchain_impl,
22+
attrs = {
23+
"label": attr.label(
24+
doc = "A valid label of a target to build or a prebuilt binary. Mutually exclusive with path.",
25+
cfg = "exec",
26+
executable = True,
27+
allow_files = True,
28+
),
29+
"path": attr.string(
30+
doc = "The path to the executable. Mutually exclusive with label.",
31+
),
32+
"version": attr.string(
33+
doc = "The version string of the executable. This should be manually set.",
34+
),
35+
},
36+
)
37+
38+
# Expose the presence of otool in the resolved toolchain as a flag.
39+
def _is_otool_available_impl(ctx):
40+
toolchain = ctx.toolchains["@@//bazel/toolchains/otool:otool_toolchain_type"].otool
41+
return [config_common.FeatureFlagInfo(
42+
value = ("1" if toolchain.valid else "0"),
43+
)]
44+
45+
is_otool_available = rule(
46+
implementation = _is_otool_available_impl,
47+
attrs = {},
48+
toolchains = ["@@//bazel/toolchains/otool:otool_toolchain_type"],
49+
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Repository rule to autoconfigure a toolchain using the system otool."""
2+
3+
# NOTE: this must match the name used by register_toolchains in consuming
4+
# MODULE.bazel files. It seems like we should have a better interface that
5+
# allows for this module name to be specified from a single point.
6+
NAME = "otool"
7+
8+
def _write_build(rctx, path, version):
9+
if not path:
10+
path = ""
11+
rctx.template(
12+
"BUILD",
13+
Label("@@//bazel/toolchains/otool:BUILD.tpl"),
14+
substitutions = {
15+
"{GENERATOR}": "@@//bazel/toolchains/otool/otool_configure.bzl%find_system_otool",
16+
"{OTOOL_PATH}": str(path),
17+
"{OTOOL_VERSION}": version,
18+
},
19+
executable = False,
20+
)
21+
22+
def _build_repo_for_otool_toolchain_impl(rctx):
23+
otool_path = rctx.which("otool")
24+
if rctx.attr.verbose:
25+
if otool_path:
26+
print("Found otool at '%s'" % otool_path) # buildifier: disable=print
27+
else:
28+
print("No system otool found.") # buildifier: disable=print
29+
30+
version = "unknown"
31+
if otool_path:
32+
res = rctx.execute([otool_path, "--version"])
33+
if res.return_code == 0:
34+
# expect stderr like:
35+
# llvm-otool(1): Apple Inc. version cctools-1030.6.3
36+
# otool(1): Apple Inc. version cctools-1030.6.3
37+
# disassembler: LLVM version 17.0.0
38+
for word in res.stderr.strip().replace("\n", " ").split(" "):
39+
if word.startswith("cctools-"):
40+
version = word
41+
break
42+
43+
_write_build(
44+
rctx = rctx,
45+
path = otool_path,
46+
version = version,
47+
)
48+
49+
build_repo_for_otool_toolchain = repository_rule(
50+
implementation = _build_repo_for_otool_toolchain_impl,
51+
doc = """Create a repository that defines an otool toolchain based on the system otool.""",
52+
local = True,
53+
environ = ["PATH"],
54+
attrs = {
55+
"verbose": attr.bool(
56+
doc = "If true, print status messages.",
57+
),
58+
},
59+
)
60+
61+
find_system_otool = module_extension(
62+
implementation = lambda ctx: build_repo_for_otool_toolchain(name = NAME),
63+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""toolchain to wrap the patchelf binary.
2+
3+
Type: @@//bazel/toolchains/patchelf:patchelf_toolchain_type
4+
5+
Toolchains:
6+
- patchelf_missing_toolchain: provides a fallback toolchain for exec platforms
7+
where patchelf might not be available.
8+
9+
- patchelf_auto_toolchain: a toolchain that uses the installed patchelf. See
10+
patchelf_configure.bzl%find_system_patchelf for usage.
11+
"""
12+
13+
load("@@//bazel/toolchains/patchelf:patchelf.bzl", "is_patchelf_available", "patchelf_toolchain")
14+
15+
# Expose the availability of an actual patchelf as a config_setting, so we can
16+
# select() on it.
17+
config_setting(
18+
name = "have_patchelf",
19+
flag_values = {
20+
":is_patchelf_available": "1",
21+
},
22+
visibility = ["//visibility:public"],
23+
)
24+
25+
# Expose the availability of an actual patchelf as a feature flag, so we can
26+
# create a config_setting from it.
27+
is_patchelf_available(
28+
name = "is_patchelf_available",
29+
)
30+
31+
toolchain_type(
32+
name = "patchelf_toolchain_type",
33+
visibility = ["//visibility:public"],
34+
)
35+
36+
# patchelf_missing_toolchain provides a fallback toolchain so that toolchain
37+
# resolution can succeed even on platforms that do not have a working patchelf.
38+
# If this toolchain is selected, the constraint ":have_patchelf" will not be
39+
# satistifed.
40+
patchelf_toolchain(
41+
name = "no_patchelf",
42+
)
43+
44+
toolchain(
45+
name = "patchelf_missing_toolchain",
46+
toolchain = ":no_patchelf",
47+
toolchain_type = ":patchelf_toolchain_type",
48+
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This content is generated by {GENERATOR}
2+
load("@@//bazel/toolchains/patchelf:patchelf.bzl", "patchelf_toolchain")
3+
4+
patchelf_toolchain(
5+
name = "patchelf_local_build",
6+
label = "@patchelf//:patchelf",
7+
)
8+
9+
toolchain(
10+
name = "patchelf_built_toolchain",
11+
toolchain = ":patchelf_local_build",
12+
toolchain_type = "@@//bazel/toolchains/patchelf:patchelf_toolchain_type",
13+
)
14+
15+
patchelf_toolchain(
16+
name = "patchelf_auto",
17+
path = "{PATCHELF_PATH}",
18+
version = "{PATCHELF_VERSION}",
19+
)
20+
21+
toolchain(
22+
name = "patchelf_auto_toolchain",
23+
toolchain = ":patchelf_auto",
24+
toolchain_type = "@@//bazel/toolchains/patchelf:patchelf_toolchain_type",
25+
)
26+
27+
toolchain(
28+
name = "zzz_patchelf_missing_toolchain", # keep name lexicographically last
29+
toolchain = "@@//bazel/toolchains/patchelf:no_patchelf",
30+
toolchain_type = "@@//bazel/toolchains/patchelf:patchelf_toolchain_type",
31+
)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""toolchain to provide an patchelf binary."""
2+
3+
load("@@//bazel/toolchains:toolchain_info.bzl", "ToolInfo")
4+
5+
def _patchelf_toolchain_impl(ctx):
6+
if ctx.attr.label and ctx.attr.path:
7+
fail("patchelf_toolchain must not specify both label and path.")
8+
valid = bool(ctx.attr.label) or bool(ctx.attr.path)
9+
toolchain_info = platform_common.ToolchainInfo(
10+
patchelf = ToolInfo(
11+
name = str(ctx.label),
12+
valid = valid,
13+
label = ctx.attr.label,
14+
path = ctx.attr.path,
15+
version = ctx.attr.version,
16+
),
17+
)
18+
return [toolchain_info]
19+
20+
patchelf_toolchain = rule(
21+
implementation = _patchelf_toolchain_impl,
22+
attrs = {
23+
"label": attr.label(
24+
doc = "A valid label of a target to build or a prebuilt binary. Mutually exclusive with path.",
25+
cfg = "exec",
26+
executable = True,
27+
allow_files = True,
28+
),
29+
"path": attr.string(
30+
doc = "The path to the executable. Mutually exclusive with label.",
31+
),
32+
"version": attr.string(
33+
doc = "The version string of the executable. This should be manually set.",
34+
),
35+
},
36+
)
37+
38+
# Expose the presence of patchelf in the resolved toolchain as a flag.
39+
def _is_patchelf_available_impl(ctx):
40+
toolchain = ctx.toolchains["@@//bazel/toolchains/patchelf:patchelf_toolchain_type"].patchelf
41+
return [config_common.FeatureFlagInfo(
42+
value = ("1" if toolchain.valid else "0"),
43+
)]
44+
45+
is_patchelf_available = rule(
46+
implementation = _is_patchelf_available_impl,
47+
attrs = {},
48+
toolchains = ["@@//bazel/toolchains/patchelf:patchelf_toolchain_type"],
49+
)

0 commit comments

Comments
 (0)