summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYifan Hong <elsk@google.com>2024-05-07 22:12:55 -0700
committerYifan Hong <elsk@google.com>2024-05-13 16:56:21 -0700
commitcb67cfc5870c2d0c2b5f13313c4717a7d6665148 (patch)
tree2b0e502f33a7cd4b69f1b228b4b96a458abf02fd
parent5dae65a58d7a523c89adb7a92caf4a109e2bfc55 (diff)
downloadbuild-cb67cfc5870c2d0c2b5f13313c4717a7d6665148.tar.gz
kleaf: Wrap hermetic tools with C++ binary.
Instead of symlinking to a script that uses ${0%/*}, use a native binary that executes $(readlink /proc/self/exe) $@ <hermetic_tools.extra_args> This way, the tool can be executed anywhere even when $0 is clobbered, e.g. when another symlink is created against the tool. The only remaining requirement is that this other symlink must have the same tool_name. In particular, this allows using PATH={hermetic_tools} to run integration tests. Bug: 338263410 Change-Id: If0ec635ef0c0bc7cfa4cf3758a5d6a8eedd8f556
-rw-r--r--BUILD.bazel22
-rwxr-xr-xbuild-tools/kleaf_internal_do_not_use_path/linux-x86/rsync3
-rwxr-xr-xbuild-tools/kleaf_internal_do_not_use_path/linux-x86/tar8
-rw-r--r--kleaf/hermetic_tools.bzl157
-rw-r--r--kleaf/impl/BUILD.bazel6
-rw-r--r--kleaf/impl/arg_wrapper.cpp110
6 files changed, 210 insertions, 96 deletions
diff --git a/BUILD.bazel b/BUILD.bazel
index 958984da..67431f58 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -155,6 +155,7 @@ _TOYS = [
"sort",
"stat",
"tail",
+ "tar",
"tee",
"test",
"timeout",
@@ -172,12 +173,20 @@ _TOYS = [
"whoami",
"xargs",
"xxd",
- # Real tar binary that shouldn't be used directly
- "kleaf_internal_do_not_use/tar",
]
hermetic_tools(
name = "hermetic-tools",
+ extra_args = {
+ "rsync": ["--no-group"],
+ "tar": [
+ "--mtime=@0",
+ "--owner=0",
+ "--group=0",
+ "--numeric-owner",
+ "--sort=name",
+ ],
+ },
symlinks = select({
"//build/kernel/kleaf:debug_cache_dir_conflict_needs_flock": {
":toybox_flock": "flock",
@@ -211,14 +220,7 @@ hermetic_tools(
"@kleaf_host_tools//:bash": "bash",
"@kleaf_host_tools//:sh": "sh",
"@kleaf_host_tools//:perl": "perl",
-
- # Real rsync that shouldn't be used directly
- "@kleaf_host_tools//:rsync": "kleaf_internal_do_not_use/rsync",
- # Embed additional args that calls kleaf_internal_do_not_use/rsync
- "build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync": "rsync",
-
- # Embeds additional args that calls kleaf_internal_do_not_use/tar
- "build-tools/kleaf_internal_do_not_use_path/linux-x86/tar": "tar",
+ "@kleaf_host_tools//:rsync": "rsync",
# Additional symlinks other than in //prebuilts/build-tools
"//prebuilts/kernel-build-tools:linux-x86/bin/depmod": ":".join([
diff --git a/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync b/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync
deleted file mode 100755
index 5abcde96..00000000
--- a/build-tools/kleaf_internal_do_not_use_path/linux-x86/rsync
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-${0%/*}/kleaf_internal_do_not_use/rsync "$@" --no-group
diff --git a/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar b/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar
deleted file mode 100755
index 46c7d65a..00000000
--- a/build-tools/kleaf_internal_do_not_use_path/linux-x86/tar
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-${0%/*}/kleaf_internal_do_not_use/tar "$@" \
- --mtime=@0 \
- --owner=0 \
- --group=0 \
- --numeric-owner \
- --sort=name
diff --git a/kleaf/hermetic_tools.bzl b/kleaf/hermetic_tools.bzl
index 13aa70d4..b5093a2a 100644
--- a/kleaf/hermetic_tools.bzl
+++ b/kleaf/hermetic_tools.bzl
@@ -57,41 +57,66 @@ def _get_single_file(ctx, target):
))
return files_list[0]
-def _handle_hermetic_symlinks(ctx):
- hermetic_symlinks_dict = {}
- for actual_target, tool_names in ctx.attr.symlinks.items():
- for tool_name in tool_names.split(":"):
- out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, tool_name))
- target_file = _get_single_file(ctx, actual_target)
- ctx.actions.symlink(
- output = out,
- target_file = target_file,
- is_executable = True,
- progress_message = "Creating symlinks to in-tree tools {}/{}".format(
- ctx.label,
- tool_name,
- ),
- )
- hermetic_symlinks_dict[tool_name] = out
-
- return hermetic_symlinks_dict
+def _handle_tool(ctx, tool_name, actual_target):
+ out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, tool_name))
+ target_file = _get_single_file(ctx, actual_target)
+
+ if tool_name not in ctx.attr.extra_args:
+ ctx.actions.symlink(
+ output = out,
+ target_file = target_file,
+ is_executable = True,
+ progress_message = "Creating symlink to in-tree tool {}/{}".format(
+ ctx.label,
+ tool_name,
+ ),
+ )
+ return [out]
+
+ internal_symlink = ctx.actions.declare_file("{}/kleaf_internal_do_not_use/{}".format(ctx.attr.name, tool_name))
+ ctx.actions.symlink(
+ output = internal_symlink,
+ target_file = target_file,
+ is_executable = True,
+ progress_message = "Creating internal symlink to in-tree tool {}/{}".format(
+ ctx.label,
+ tool_name,
+ ),
+ )
-def _hermetic_tools_impl(ctx):
- deps = [] + ctx.files.deps
+ ctx.actions.symlink(
+ output = out,
+ target_file = ctx.executable._arg_wrapper,
+ is_executable = True,
+ progress_message = "Creating symlink to in-tree tool {}/{}".format(
+ ctx.label,
+ tool_name,
+ ),
+ )
+ extra_args = "\n".join(ctx.attr.extra_args[tool_name])
+ extra_args_file = ctx.actions.declare_file("{}/kleaf_internal_do_not_use/{}_args.txt".format(ctx.attr.name, tool_name))
+ ctx.actions.write(extra_args_file, extra_args)
+ return [out, internal_symlink, extra_args_file]
+
+def _handle_hermetic_symlinks(ctx, symlinks_attr):
all_outputs = []
+ for actual_target, tool_names in symlinks_attr.items():
+ for tool_name in tool_names.split(":"):
+ tool_outs = _handle_tool(ctx, tool_name, actual_target)
+ all_outputs.extend(tool_outs)
- hermetic_outs_dict = _handle_hermetic_symlinks(ctx)
+ return all_outputs
- hermetic_outs = hermetic_outs_dict.values()
- all_outputs += hermetic_outs
- deps += hermetic_outs
+def _hermetic_tools_impl(ctx):
+ all_outputs = _handle_hermetic_symlinks(ctx, ctx.attr.symlinks)
if ctx.attr._disable_symlink_source[BuildSettingInfo].value:
transitive_deps = []
else:
transitive_deps = [target.files for target in ctx.attr.symlinks]
- deps_depset = depset(deps, transitive = transitive_deps)
+ transitive_deps += [target.files for target in ctx.attr.deps]
+ transitive_deps.append(ctx.attr._arg_wrapper.files)
fail_hard = """
# error on failures
@@ -125,76 +150,58 @@ def _hermetic_tools_impl(ctx):
""".format(path = hermetic_base_short)
hermetic_toolchain_info = _HermeticToolchainInfo(
- deps = deps_depset,
+ deps = depset(all_outputs, transitive = transitive_deps),
setup = setup,
run_setup = run_setup,
run_additional_setup = run_additional_setup,
)
- default_info_files = [
- file
- for file in all_outputs
- if "kleaf_internal_do_not_use" not in file.path
- ]
-
infos = [
- DefaultInfo(files = depset(default_info_files)),
+ DefaultInfo(files = depset(all_outputs)),
platform_common.ToolchainInfo(
hermetic_toolchain_info = hermetic_toolchain_info,
),
- OutputGroupInfo(
- **{file.basename: depset([file]) for file in all_outputs}
- ),
+ OutputGroupInfo(**{
+ file.basename: depset([file])
+ for file in all_outputs
+ if "kleaf_internal_do_not_use" not in file.path
+ }),
]
return infos
-_hermetic_tools = rule(
+hermetic_tools = rule(
implementation = _hermetic_tools_impl,
- doc = "",
+ doc = "Provide tools for a hermetic build.",
attrs = {
- "deps": attr.label_list(doc = "Additional_deps", allow_files = True),
+ "deps": attr.label_list(
+ doc = "additional dependencies. These aren't added to the `PATH`.",
+ allow_files = True,
+ ),
"symlinks": attr.label_keyed_string_dict(
- doc = "symlinks to labels",
allow_files = True,
+ doc = """A dictionary, where keys are labels to an executable, and
+ values are names to the tool, separated with `:`. e.g.
+
+ ```
+ {"//label/to:toybox": "cp:realpath"}
+ ```
+ """,
+ ),
+ "extra_args": attr.string_list_dict(
+ doc = """Keys are names to the tool (see `symlinks`). Values are
+ extra arguments added to the tool at the end.""",
),
"_disable_symlink_source": attr.label(
default = "//build/kernel/kleaf:incompatible_disable_hermetic_tools_symlink_source",
),
+ "_arg_wrapper": attr.label(
+ default = "//build/kernel/kleaf/impl:arg_wrapper",
+ executable = True,
+ # Prevent inadvertent exec transition that messes up the
+ # path calculation. Exec transition needs to be done on the whole
+ # hermetic_tools target.
+ cfg = "target",
+ ),
},
)
-
-def hermetic_tools(
- name,
- deps = None,
- symlinks = None,
- **kwargs):
- """Provide tools for a hermetic build.
-
- Args:
- name: Name of the target.
- symlinks: A dictionary, where keys are labels to an executable, and
- values are names to the tool, separated with `:`. e.g.
-
- ```
- {"//label/to:toybox": "cp:realpath"}
- ```
- deps: additional dependencies. These aren't added to the `PATH`.
- **kwargs: Additional attributes to the internal rule, e.g.
- [`visibility`](https://docs.bazel.build/versions/main/visibility.html).
- See complete list
- [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common
- """
-
- if symlinks == None:
- symlinks = {}
-
- if deps == None:
- deps = []
-
- _hermetic_tools(
- name = name,
- deps = deps,
- symlinks = symlinks,
- **kwargs
- )
diff --git a/kleaf/impl/BUILD.bazel b/kleaf/impl/BUILD.bazel
index 51964f90..56e26176 100644
--- a/kleaf/impl/BUILD.bazel
+++ b/kleaf/impl/BUILD.bazel
@@ -485,3 +485,9 @@ python_runtime_files(
name = "python_runtime_files",
visibility = ["//build/kernel:__pkg__"],
)
+
+cc_binary(
+ name = "arg_wrapper",
+ srcs = ["arg_wrapper.cpp"],
+ visibility = ["//build/kernel:__pkg__"],
+)
diff --git a/kleaf/impl/arg_wrapper.cpp b/kleaf/impl/arg_wrapper.cpp
new file mode 100644
index 00000000..ee9c95ce
--- /dev/null
+++ b/kleaf/impl/arg_wrapper.cpp
@@ -0,0 +1,110 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// 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.
+
+// Helper wrapper for hermetic tools to wrap arguments.
+//
+// This roughly equivalent to:
+// 1. readlink /proc/self/exe, then dirname multiple times to determine the path
+// internal_dir =
+// <execroot>/build/kernel/hermetic-tools/kleaf_internal_do_not_use
+// 2. tool_name = basename($0)
+// 3. call <internal_dir>/<tool_name> $@ \\
+// $(cat <internal_dir>/<tool_name>_args.txt)
+//
+// This is a C++ binary instead of a shell / Python script so that
+// /proc/self/exe is a proper anchor to find internal_dir. If this were a
+// script, /proc/self/exe would be the path to the interpreter.
+// This also avoids using any hermetic tools in order to determine the path to
+// them.
+
+#include <linux/limits.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace {
+
+// <execroot>/build/kernel/hermetic-tools/kleaf_internal_do_not_use
+std::filesystem::path get_kleaf_internal_dir() {
+ std::error_code ec;
+ auto my_path = std::filesystem::read_symlink("/proc/self/exe", ec);
+ if (ec.value() != 0) {
+ std::cerr << "ERROR: read_symlink /proc/self/exe: " << ec.message()
+ << std::endl;
+ exit(EX_SOFTWARE);
+ }
+ return my_path.parent_path().parent_path().parent_path() / "hermetic-tools" /
+ "kleaf_internal_do_not_use";
+}
+
+// Loads <tool_name>_args.txt from hermetic_tools.extra_args
+std::vector<std::string> load_arg_file(const std::filesystem::path& path) {
+ std::ifstream ifs(path);
+ if (!ifs) {
+ int saved_errno = errno;
+ std::cerr << "Unable to open " << path << ": " << strerror(saved_errno)
+ << std::endl;
+ exit(EX_SOFTWARE);
+ }
+ std::vector<std::string> args;
+ for (std::string arg; std::getline(ifs, arg);) {
+ args.push_back(arg);
+ }
+ return args;
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ auto internal_dir = get_kleaf_internal_dir();
+
+ if (argc < 1) {
+ std::cerr << "ERROR: argc == " << argc << " < 1" << std::endl;
+ return EX_SOFTWARE;
+ }
+ std::string tool_name(std::filesystem::path(argv[0]).filename());
+
+ // The actual executable we are going to call. Cast to string to use
+ // in new_argv.
+ std::string real_executable = internal_dir / tool_name;
+
+ std::vector<char*> new_argv;
+ new_argv.push_back(real_executable.data());
+
+ for (int i = 1; i < argc; i++) {
+ new_argv.push_back(argv[i]);
+ }
+
+ auto extra_args_file = internal_dir / (tool_name + "_args.txt");
+ auto preset_args = load_arg_file(extra_args_file);
+ for (auto& preset_arg : preset_args) {
+ new_argv.push_back(preset_arg.data());
+ }
+ new_argv.push_back(nullptr);
+
+ if (-1 != execv(real_executable.c_str(), new_argv.data())) {
+ int saved_errno = errno;
+ std::cerr << "ERROR: execv: " << real_executable << ": "
+ << strerror(saved_errno) << std::endl;
+ return EX_SOFTWARE;
+ }
+ std::cerr << "ERROR: execv returns!" << std::endl;
+ return EX_SOFTWARE;
+}