diff --git a/.vscode/gdb_launch.json b/.vscode/gdb_launch.json index a50b08910451..cb9ddf8a311d 100644 --- a/.vscode/gdb_launch.json +++ b/.vscode/gdb_launch.json @@ -14,7 +14,7 @@ "type": "by-gdb", "request": "launch", "name": "carbon compile (gdb)", - "program": "bazel-bin/toolchain/install/prefix_root/lib/carbon/carbon-busybox", + "program": "bazel-bin/toolchain/carbon", "programArgs": "compile --phase=lower --dump-sem-ir --stream-errors ${relativeFile}", "cwd": "${workspaceFolder}" } diff --git a/.vscode/lldb_launch.json b/.vscode/lldb_launch.json index fc0e35cd7840..929976f77dbc 100644 --- a/.vscode/lldb_launch.json +++ b/.vscode/lldb_launch.json @@ -18,7 +18,7 @@ "type": "lldb-dap", "request": "launch", "name": "carbon compile (lldb)", - "program": "bazel-bin/toolchain/install/prefix_root/lib/carbon/carbon-busybox", + "program": "bazel-bin/toolchain/carbon", "args": [ "compile", "--phase=lower", diff --git a/docs/project/contribution_tools.md b/docs/project/contribution_tools.md index 68e4ec3ff0f1..b6e432a43053 100644 --- a/docs/project/contribution_tools.md +++ b/docs/project/contribution_tools.md @@ -345,7 +345,7 @@ bazel build -c dbg //toolchain Then debugging works with LLDB: ```shell -lldb bazel-bin/toolchain/install/prefix_root/lib/carbon/carbon-busybox +lldb bazel-bin/toolchain/carbon ``` Any installed version of LLDB at least as recent as the installed Clang used for diff --git a/toolchain/BUILD b/toolchain/BUILD index a6a22fe04c41..17bbfd17211d 100644 --- a/toolchain/BUILD +++ b/toolchain/BUILD @@ -2,9 +2,21 @@ # Exceptions. See /LICENSE for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +load("//bazel/cc_toolchains:defs.bzl", "cc_env") +load("run_tool.bzl", "run_tool") + +# Support `bazel run` and create a convenience symlink for the tool in the +# toolchain's install. +run_tool( + name = "carbon", + data = ["//toolchain/install:install_data"], + env = cc_env(), + tool = "//toolchain/install:prefix_root/bin/carbon", +) + # A convenience target for running the toolchain with the full prelude # available. alias( name = "toolchain", - actual = "//toolchain/install:run_carbon", + actual = ":carbon", ) diff --git a/toolchain/docs/adding_features.md b/toolchain/docs/adding_features.md index cf23795105df..bfd0c49b56c3 100644 --- a/toolchain/docs/adding_features.md +++ b/toolchain/docs/adding_features.md @@ -467,8 +467,10 @@ example, with `toolchain/parse/testdata/basics/empty.carbon`: - Executes an individual test. - `bazel run //toolchain -- compile --phase=parse --dump-parse-tree toolchain/parse/testdata/basics/empty.carbon` - Explicitly runs `carbon` with the provided arguments. -- `bazel-bin/toolchain/install/run_carbon compile --phase=parse --dump-parse-tree toolchain/parse/testdata/basics/empty.carbon` - - Similar to the previous command, but without using `bazel`. +- `bazel-bin/toolchain/carbon compile --phase=parse --dump-parse-tree toolchain/parse/testdata/basics/empty.carbon` + - Similar to the previous command, but without using `bazel run`. This can + be useful with a debugger or other tool that needs to directly run the + binary. - `bazel run //toolchain -- -v compile --phase=check toolchain/check/testdata/basics/run.carbon` - Runs using `-v` for verbose log output, and running through the `check` phase. diff --git a/toolchain/install/BUILD b/toolchain/install/BUILD index 9cb16c9fd92d..0e85002cce97 100644 --- a/toolchain/install/BUILD +++ b/toolchain/install/BUILD @@ -5,9 +5,8 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") load("//bazel/cc_toolchains:defs.bzl", "cc_env") load("//bazel/manifest:defs.bzl", "manifest") -load("install_filegroups.bzl", "install_busybox_wrapper", "install_filegroup", "install_symlink", "install_target", "make_install_filegroups") +load("install_filegroups.bzl", "install_filegroup", "install_symlink", "install_target", "make_install_filegroups") load("pkg_helpers.bzl", "pkg_naming_variables", "pkg_tar_and_test") -load("run_tool.bzl", "run_tool") package(default_visibility = ["//visibility:public"]) @@ -89,6 +88,7 @@ cc_test( "//common:check", "//testing/base:gtest_main", "@googletest//:gtest", + "@llvm-project//llvm:Support", ], ) @@ -130,9 +130,10 @@ lld_aliases = [ # based on the FHS (Filesystem Hierarchy Standard). install_dirs = { "bin": [ - install_busybox_wrapper( + install_symlink( "carbon", "../lib/carbon/carbon-busybox", + is_driver = True, ), ], "lib/carbon": [ @@ -151,13 +152,10 @@ install_dirs = { "@llvm-project//lld:lld", executable = True, ), - install_busybox_wrapper( + install_symlink( "clang", "../../carbon-busybox", - [ - "clang", - "--", - ], + is_driver = True, ), ] + [install_symlink(name, "lld") for name in lld_aliases], } @@ -194,11 +192,3 @@ pkg_tar_and_test( package_variables = ":packaging_variables", stamp = -1, # Allow `--stamp` builds to produce file timestamps. ) - -# Support `bazel run` on specific binaries. -run_tool( - name = "run_carbon", - data = [":install_data"], - env = cc_env(), - tool = "prefix_root/bin/carbon", -) diff --git a/toolchain/install/busybox_info.h b/toolchain/install/busybox_info.h index 72fad4a20a14..926350e9e2ef 100644 --- a/toolchain/install/busybox_info.h +++ b/toolchain/install/busybox_info.h @@ -6,6 +6,7 @@ #define CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_ #include +#include #include #include @@ -14,6 +15,8 @@ namespace Carbon { +constexpr const char* Argv0OverrideEnv = "CARBON_ARGV0_OVERRIDE"; + struct BusyboxInfo { // The path to `carbon-busybox`. std::filesystem::path bin_path; @@ -21,24 +24,76 @@ struct BusyboxInfo { std::optional mode; }; -// Returns the busybox information, given argv[0]. This primarily handles -// resolving symlinks that point at the busybox. +// Returns the busybox information, given argv[0]. +// +// Extracts the desired mode for the busybox from the initial command name. +// +// Checks if the path in argv0 is an executable in a valid Carbon install, or a +// symlink to such an executable, and sets `bin_path` to the path of +// `lib/carbon/carbon-busybox` within that install. +// +// If unable to locate a plausible busybox binary, returns an error instead. inline auto GetBusyboxInfo(llvm::StringRef argv0) -> ErrorOr { - BusyboxInfo info = BusyboxInfo{argv0.str(), std::nullopt}; + // Check for an override of `argv[0]` from the environment and apply it. + if (const char* argv0_override = getenv(Argv0OverrideEnv)) { + argv0 = argv0_override; + } + + BusyboxInfo info = {.bin_path = argv0.str(), .mode = std::nullopt}; + std::filesystem::path filename = info.bin_path.filename(); + // The mode is set to the initial filename used for `argv[0]`. + if (filename != "carbon" && filename != "carbon-busybox") { + info.mode = filename; + } + + // Now search through any symlinks to locate the installed busybox binary. while (true) { - std::string filename = info.bin_path.filename(); + filename = info.bin_path.filename(); if (filename == "carbon-busybox") { return info; } + + // If we've not already reached the busybox, look for it relative to the + // current binary path. This can help more immediately locate an + // installation tree, and avoids walking through a final layer of symlinks + // which may point to content-addressed storage or other parts of a build + // output tree. + // + // We break this into two cases we need to handle: + // - Carbon's CLI will be: `/bin/carbon` + // - Other tools will be: `/lib/carbon//bin/` + // + // We also check that the current path is within a `bin` directory to + // provide best-effort checking for accidentally walking up from symlinks + // that aren't within an installation-shaped tree. + auto parent_path = info.bin_path.parent_path(); + // Strip any `.` path components at the end to simplify processing. + while (parent_path.filename() == ".") { + parent_path = parent_path.parent_path(); + } + if (parent_path.filename() == "bin") { + auto lib_path = filename == "carbon" + ? parent_path / ".." / "lib" / "carbon" + : parent_path / ".." / ".."; + auto busybox_path = lib_path / "carbon-busybox"; + std::error_code ec; + if (std::filesystem::exists(busybox_path, ec)) { + info.bin_path = busybox_path; + return info; + } + } + + // Try to walk through another layer of symlinks and see if we can find the + // installation there or are linked directly to the busybox. std::error_code ec; auto symlink_target = std::filesystem::read_symlink(info.bin_path, ec); if (ec) { return ErrorBuilder() << "expected carbon-busybox symlink at `" << info.bin_path << "`"; } - info.mode = filename; + // Do a path join, to handle relative symlinks. - info.bin_path = info.bin_path.parent_path() / symlink_target; + info.bin_path = parent_path / symlink_target; } } diff --git a/toolchain/install/busybox_info_test.cpp b/toolchain/install/busybox_info_test.cpp index 1d01bb735183..56c5a5dde0ea 100644 --- a/toolchain/install/busybox_info_test.cpp +++ b/toolchain/install/busybox_info_test.cpp @@ -11,6 +11,7 @@ #include #include "common/check.h" +#include "llvm/ADT/ScopeExit.h" namespace Carbon { namespace { @@ -52,14 +53,35 @@ class BusyboxInfoTest : public ::testing::Test { return file; } - // Creates a directory. Returns the input file for easier use. + // Creates a directory, recursively if needed. Returns the input file for + // easier use. auto MakeDir(std::filesystem::path dir) -> std::filesystem::path { std::error_code ec; - std::filesystem::create_directory(dir, ec); + std::filesystem::create_directories(dir, ec); CARBON_CHECK(!ec, "error creating {0}: {1}", dir, ec.message()); return dir; } + // Creates a synthetic install tree to test a batch of interactions. + // Optionally accepts a symlink target for the busybox in the install tree. + // Returns the input prefix for easy use. + auto MakeInstallTree(std::filesystem::path prefix, + std::optional busybox_target = {}) + -> std::filesystem::path { + MakeDir(prefix / "lib/carbon"); + if (busybox_target) { + MakeSymlink(prefix / "lib/carbon/carbon-busybox", *busybox_target); + } else { + MakeFile(prefix / "lib/carbon/carbon-busybox"); + } + MakeDir(prefix / "lib/carbon/llvm/bin"); + MakeSymlink(prefix / "lib/carbon/llvm/bin/clang++", "clang"); + MakeSymlink(prefix / "lib/carbon/llvm/bin/clang", "../../carbon-busybox"); + MakeDir(prefix / "bin"); + MakeSymlink(prefix / "bin/carbon", "../lib/carbon/carbon-busybox"); + return prefix; + } + // The test's temp directory, deleted on destruction. std::filesystem::path dir_; }; @@ -80,7 +102,7 @@ TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectory) { auto info = GetBusyboxInfo(target.string()); ASSERT_TRUE(info.ok()) << info.error(); EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); - EXPECT_THAT(info->mode, Eq("carbon")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); } TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectoryWithDot) { @@ -90,18 +112,35 @@ TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectoryWithDot) { auto info = GetBusyboxInfo(target.string()); ASSERT_TRUE(info.ok()) << info.error(); EXPECT_THAT(info->bin_path, Eq(dir_ / "./carbon-busybox")); - EXPECT_THAT(info->mode, Eq("carbon")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); } TEST_F(BusyboxInfoTest, ExtraSymlink) { MakeFile(dir_ / "carbon-busybox"); - MakeSymlink(dir_ / "carbon", "carbon-busybox"); - auto target = MakeSymlink(dir_ / "c", "carbon"); + MakeSymlink(dir_ / "c", "carbon-busybox"); + auto target = MakeSymlink(dir_ / "carbon", "c"); auto info = GetBusyboxInfo(target.string()); ASSERT_TRUE(info.ok()) << info.error(); EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); - EXPECT_THAT(info->mode, Eq("carbon")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); +} + +TEST_F(BusyboxInfoTest, OriginalSymlinkNameFormsMode) { + MakeFile(dir_ / "carbon-busybox"); + MakeSymlink(dir_ / "carbon", "carbon-busybox"); + auto clang_target = MakeSymlink(dir_ / "clang", "carbon"); + auto clang_plusplus_target = MakeSymlink(dir_ / "clang++", "clang"); + + auto info = GetBusyboxInfo(clang_target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); + EXPECT_THAT(info->mode, Eq("clang")); + + info = GetBusyboxInfo(clang_plusplus_target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox")); + EXPECT_THAT(info->mode, Eq("clang++")); } TEST_F(BusyboxInfoTest, BusyboxIsSymlink) { @@ -124,31 +163,28 @@ TEST_F(BusyboxInfoTest, BusyboxIsSymlinkToNowhere) { } TEST_F(BusyboxInfoTest, RelativeSymlink) { - MakeDir(dir_ / "lib"); - MakeDir(dir_ / "lib/carbon"); - MakeFile(dir_ / "lib/carbon/carbon-busybox"); - MakeDir(dir_ / "bin"); - auto target = - MakeSymlink(dir_ / "bin/carbon", "../lib/carbon/carbon-busybox"); + MakeDir(dir_ / "dir1"); + MakeFile(dir_ / "dir1/carbon-busybox"); + MakeDir(dir_ / "dir2"); + auto target = MakeSymlink(dir_ / "dir2/carbon", "../dir1/carbon-busybox"); auto info = GetBusyboxInfo(target.string()); ASSERT_TRUE(info.ok()) << info.error(); - EXPECT_THAT(info->bin_path, Eq(dir_ / "bin/../lib/carbon/carbon-busybox")); - EXPECT_THAT(info->mode, Eq("carbon")); + EXPECT_THAT(info->bin_path, Eq(dir_ / "dir2/../dir1/carbon-busybox")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); } TEST_F(BusyboxInfoTest, AbsoluteSymlink) { - MakeDir(dir_ / "lib"); - MakeDir(dir_ / "lib/carbon"); - auto busybox = MakeFile(dir_ / "lib/carbon/carbon-busybox"); + MakeDir(dir_ / "dir1"); + auto busybox = MakeFile(dir_ / "dir1/carbon-busybox"); ASSERT_TRUE(busybox.is_absolute()); - MakeDir(dir_ / "bin"); - auto target = MakeSymlink(dir_ / "bin/carbon", busybox); + MakeDir(dir_ / "dir2"); + auto target = MakeSymlink(dir_ / "dir2/carbon", busybox); auto info = GetBusyboxInfo(target.string()); ASSERT_TRUE(info.ok()) << info.error(); EXPECT_THAT(info->bin_path, Eq(busybox)); - EXPECT_THAT(info->mode, Eq("carbon")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); } TEST_F(BusyboxInfoTest, NotBusyboxFile) { @@ -166,5 +202,104 @@ TEST_F(BusyboxInfoTest, NotBusyboxSymlink) { EXPECT_FALSE(info.ok()); } +TEST_F(BusyboxInfoTest, LayerSymlinksInstallTree) { + auto actual_busybox = MakeFile(dir_ / "actual-busybox"); + + // Create a facsimile of the install prefix with even the busybox as a + // symlink. Also include potential relative sibling symlinks like `clang++` to + // `clang`. + auto prefix = MakeInstallTree(dir_ / "test_prefix", actual_busybox); + + auto info = GetBusyboxInfo((prefix / "bin/carbon").string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(prefix / "bin/../lib/carbon/carbon-busybox")); + EXPECT_THAT(info->mode, Eq(std::nullopt)); + + info = GetBusyboxInfo((prefix / "lib/carbon/llvm/bin/clang").string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, + Eq(prefix / "lib/carbon/llvm/bin/../../carbon-busybox")); + EXPECT_THAT(info->mode, Eq("clang")); + + info = GetBusyboxInfo((prefix / "lib/carbon/llvm/bin/clang++").string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, + Eq(prefix / "lib/carbon/llvm/bin/../../carbon-busybox")); + EXPECT_THAT(info->mode, Eq("clang++")); +} + +TEST_F(BusyboxInfoTest, StopSearchAtFirstSymlinkWithRelativeBusybox) { + // Some install of Carbon under `opt`. + auto opt_prefix = MakeInstallTree(dir_ / "opt"); + + // A second install, but with its symlinks pointing into the `opt` tree rather + // than at its busybox. + MakeDir(dir_ / "lib/carbon"); + MakeFile(dir_ / "lib/carbon/carbon-busybox"); + MakeDir(dir_ / "bin"); + auto target = MakeSymlink(dir_ / "bin/carbon", "../opt/bin/carbon"); + MakeDir(dir_ / "lib/carbon/llvm/bin"); + auto clang_target = MakeSymlink(dir_ / "lib/carbon/llvm/bin/clang", + opt_prefix / "lib/carbon/llvm/bin/clang"); + + // Starting from the second install uses the relative busybox rather than + // traversing the symlink further. + auto info = GetBusyboxInfo(target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(dir_ / "bin/../lib/carbon/carbon-busybox")); + info = GetBusyboxInfo(clang_target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, + Eq(dir_ / "lib/carbon/llvm/bin/../../carbon-busybox")); +} + +TEST_F(BusyboxInfoTest, RejectSymlinkInUnrelatedInstall) { + // Add two installs of Carbon nested inside each other in a realistic + // scenario: `/usr` and `/usr/local`. + auto usr_prefix = MakeInstallTree(dir_ / "usr"); + auto usr_local_prefix = MakeInstallTree(dir_ / "usr/local"); + + // Now add a stray symlink directly in `.../usr/local` to the local install. + // + // This has the interesting property that both of these "work" and find the + // same busybox but probably wanted to find different ones: + // - `.../usr/local/../lib/carbon/carbon-busybox` + // - `.../usr/bin/../lib/carbon/carbon-busybox` + auto stray_target = MakeSymlink(dir_ / "usr/local/carbon", "bin/carbon"); + + // Check that the busybox doesn't use the relative busybox in this case, and + // walks the symlink to find the correct installation. + auto info = GetBusyboxInfo(stray_target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, + Eq(dir_ / "usr/local/bin/../lib/carbon/carbon-busybox")); + + // Ensure this works even with intervening `.` directory components. + stray_target = MakeSymlink(dir_ / "usr/local/carbon2", "bin/././carbon"); + + // Check that the busybox doesn't use the relative busybox in this case, and + // walks the symlink to find the correct installation. + info = GetBusyboxInfo(stray_target.string()); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, + Eq(dir_ / "usr/local/bin/../lib/carbon/carbon-busybox")); +} + +TEST_F(BusyboxInfoTest, EnvBinaryPathOverride) { + // The test should not have this environment variable set. + ASSERT_THAT(getenv(Argv0OverrideEnv), Eq(nullptr)); + // Clean up this environment variable when this test finishes. + auto _ = llvm::make_scope_exit([] { unsetenv(Argv0OverrideEnv); }); + + // Set the environment to our actual busybox. + auto busybox = MakeFile(dir_ / "carbon-busybox"); + setenv(Argv0OverrideEnv, busybox.c_str(), /*overwrite=*/1); + + auto info = GetBusyboxInfo("/some/nonexistent/path"); + ASSERT_TRUE(info.ok()) << info.error(); + EXPECT_THAT(info->bin_path, Eq(busybox)); + EXPECT_THAT(info->mode, Eq(std::nullopt)); +} + } // namespace } // namespace Carbon diff --git a/toolchain/install/busybox_main.cpp b/toolchain/install/busybox_main.cpp index 3928f7bd0366..1609f8fed129 100644 --- a/toolchain/install/busybox_main.cpp +++ b/toolchain/install/busybox_main.cpp @@ -40,7 +40,7 @@ static auto Main(int argc, char** argv) -> ErrorOr { llvm::SmallVector args; args.reserve(argc + 1); - if (busybox_info.mode && busybox_info.mode != "carbon") { + if (busybox_info.mode) { args.append({*busybox_info.mode, "--"}); } args.append(argv + 1, argv + argc); diff --git a/toolchain/install/install_filegroups.bzl b/toolchain/install/install_filegroups.bzl index 95df1db34a4f..eb938af81e40 100644 --- a/toolchain/install/install_filegroups.bzl +++ b/toolchain/install/install_filegroups.bzl @@ -5,25 +5,7 @@ """Rules for constructing install information.""" load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_filegroup", "pkg_files", "pkg_mklink", "strip_prefix") -load("symlink_helpers.bzl", "busybox_wrapper", "symlink_file", "symlink_filegroup") - -def install_busybox_wrapper(name, busybox, busybox_args = []): - """Adds a busybox wrapper for install. - - Used in the `install_dirs` dict. - - Args: - name: The filename to use. - busybox: A relative path for the busybox. - busybox_args: Arguments needed to simulate busybox when a symlink isn't - actually used. - """ - return { - "busybox": busybox, - "busybox_args": busybox_args, - "is_driver": True, - "name": name, - } +load("symlink_helpers.bzl", "symlink_file", "symlink_filegroup") def install_filegroup(name, filegroup_target): """Adds a filegroup for install. @@ -40,7 +22,7 @@ def install_filegroup(name, filegroup_target): "name": name, } -def install_symlink(name, symlink_to): +def install_symlink(name, symlink_to, is_driver = False): """Adds a symlink for install. Used in the `install_dirs` dict. @@ -48,9 +30,11 @@ def install_symlink(name, symlink_to): Args: name: The filename to use. symlink_to: A relative path for the symlink. + is_driver: False if it should be included in the `no_driver_name` + filegroup. """ return { - "is_driver": False, + "is_driver": is_driver, "name": name, "symlink": symlink_to, } @@ -122,19 +106,6 @@ def make_install_filegroups(name, no_driver_name, pkg_name, install_dirs, prefix attributes = pkg_attributes(mode = mode), renames = {entry["target"]: path}, ) - elif "busybox" in entry: - busybox_wrapper( - name = prefixed_path, - symlink = entry["busybox"], - busybox_args = entry["busybox_args"], - ) - - # For the distributed package, we retain relative symlinks. - pkg_mklink( - name = pkg_path, - link_name = path, - target = entry["busybox"], - ) elif "filegroup" in entry: symlink_filegroup( name = prefixed_path, diff --git a/toolchain/install/run_tool.bzl b/toolchain/install/run_tool.bzl deleted file mode 100644 index e9410b51e079..000000000000 --- a/toolchain/install/run_tool.bzl +++ /dev/null @@ -1,67 +0,0 @@ -# Part of the Carbon Language project, under the Apache License v2.0 with LLVM -# Exceptions. See /LICENSE for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -"""Supports running a tool from the install filegroup.""" - -_RUN_TOOL_TMPL = """#!/usr/bin/env python3 - -import os -import sys - -# These will be relative locations in bazel-out. -_SCRIPT_LOCATION = "{0}" -_TOOL_LOCATION = "{1}" - -# Drop shared parent portions so that we're working with a minimum suffix. -# This is so that both `bazel-out` and manual `bazel-bin` invocations work. -script_parts = _SCRIPT_LOCATION.split('/') -tool_parts = _TOOL_LOCATION.split('/') -while script_parts and tool_parts and script_parts[0] == tool_parts[0]: - del script_parts[0] - del tool_parts[0] -script_suffix = '/' + '/'.join(script_parts) -tool_suffix = '/' + '/'.join(tool_parts) - -# Make sure we have the expected structure. -if not __file__.endswith(script_suffix): - exit( - "Unable to figure out path:\\n" - f" __file__: {{__file__}}\\n" - f" script: {{script_suffix}}\\n" - ) - -# Run the tool using the absolute path, forwarding arguments. -tool_path = __file__.removesuffix(script_suffix) + tool_suffix -os.execv(tool_path, [tool_path] + sys.argv[1:]) -""" - -def _run_tool_impl(ctx): - content = _RUN_TOOL_TMPL.format(ctx.outputs.executable.path, ctx.file.tool.path) - ctx.actions.write( - output = ctx.outputs.executable, - is_executable = True, - content = content, - ) - return [ - DefaultInfo( - runfiles = ctx.runfiles(files = ctx.files.data), - ), - RunEnvironmentInfo(environment = ctx.attr.env), - ] - -run_tool = rule( - doc = "Helper for running a tool in a filegroup.", - implementation = _run_tool_impl, - attrs = { - "data": attr.label_list(allow_files = True), - "env": attr.string_dict(), - "tool": attr.label( - allow_single_file = True, - executable = True, - cfg = "target", - mandatory = True, - ), - }, - executable = True, -) diff --git a/toolchain/install/symlink_helpers.bzl b/toolchain/install/symlink_helpers.bzl index c39fdb541cbe..01284cc70014 100644 --- a/toolchain/install/symlink_helpers.bzl +++ b/toolchain/install/symlink_helpers.bzl @@ -4,68 +4,6 @@ """Rules for symlinking in ways that assist install_filegroups.""" -_SYMLINK_BUSYBOX_TMPL = """#!/usr/bin/env python3 - -from pathlib import Path -import os -import sys - -_RELATIVE_PATH = "{0}" -_BUSYBOX_ARGS = {1} - -# Run the tool using the absolute path, forwarding arguments. -tool_path = Path(__file__).parent / _RELATIVE_PATH -os.execv(tool_path, [tool_path] + _BUSYBOX_ARGS + sys.argv[1:]) -""" - -def _busybox_wrapper_impl(ctx): - """Symlinking busybox things needs special logic. - - This is because Bazel doesn't cache the actual symlink, resulting in - essentially resolved symlinks being produced in place of the expected tool. - As a consequence, we can't rely on the symlink name when dealing with - busybox entries. - - An example repro of this using a local build cache is: - bazel build //toolchain - bazel clean - bazel build //toolchain - - We could in theory get reasonable behavior with - `ctx.actions.declare_symlink`, but that's disallowed in our `.bazelrc` for - cross-environment compatibility. - - The particular approach here uses the Python script as a launching pad so - that the busybox still receives an appropriate location in argv[0], allowing - it to find other files in the lib directory. Arguments are inserted to get - equivalent behavior as if symlink resolution had occurred. - - The underlying bug is noted at: - https://github.com/bazelbuild/bazel/issues/23620 - """ - content = _SYMLINK_BUSYBOX_TMPL.format( - ctx.attr.symlink, - ctx.attr.busybox_args, - ) - ctx.actions.write( - output = ctx.outputs.executable, - is_executable = True, - content = content, - ) - return [] - -busybox_wrapper = rule( - doc = "Helper for running a busybox with symlink-like characteristics.", - implementation = _busybox_wrapper_impl, - attrs = { - "busybox_args": attr.string_list( - doc = "Optional arguments to pass for equivalent behavior to a symlink.", - ), - "symlink": attr.string(mandatory = True), - }, - executable = True, -) - def _symlink_file_impl(ctx): executable = None if ctx.attr.symlink_binary: diff --git a/toolchain/run_tool.bzl b/toolchain/run_tool.bzl new file mode 100644 index 000000000000..41b77f42fd5e --- /dev/null +++ b/toolchain/run_tool.bzl @@ -0,0 +1,40 @@ +# Part of the Carbon Language project, under the Apache License v2.0 with LLVM +# Exceptions. See /LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +"""Supports running a tool from the install filegroup.""" + +def _run_tool_impl(ctx): + tool_files = ctx.attr.tool.files.to_list() + if len(tool_files) != 1: + fail("Expected 1 tool file, found {0}".format(len(tool_files))) + ctx.actions.symlink( + output = ctx.outputs.executable, + target_file = tool_files[0], + is_executable = True, + ) + return [ + DefaultInfo( + runfiles = ctx.runfiles(files = ctx.files.data), + ), + RunEnvironmentInfo( + environment = ctx.attr.env | + {"CARBON_ARGV0_OVERRIDE": tool_files[0].short_path}, + ), + ] + +run_tool = rule( + doc = "Helper for running a tool in a filegroup.", + implementation = _run_tool_impl, + attrs = { + "data": attr.label_list(allow_files = True), + "env": attr.string_dict(), + "tool": attr.label( + allow_single_file = True, + executable = True, + cfg = "target", + mandatory = True, + ), + }, + executable = True, +) diff --git a/utils/nvim/carbon.lua b/utils/nvim/carbon.lua index 35269725b5e5..ef8887108aa2 100644 --- a/utils/nvim/carbon.lua +++ b/utils/nvim/carbon.lua @@ -19,7 +19,7 @@ local util = require 'lspconfig.util' if not configs.carbon then configs.carbon = { default_config = { - cmd = { "./bazel-bin/toolchain/install/run_carbon language-server" }, + cmd = { "./bazel-bin/toolchain/carbon language-server" }, filetypes = { "carbon" }, root_dir = util.find_git_ancestor, } diff --git a/utils/vscode/package.json b/utils/vscode/package.json index 41db15032d47..fc2ef598174e 100644 --- a/utils/vscode/package.json +++ b/utils/vscode/package.json @@ -43,7 +43,7 @@ "carbon.carbonPath": { "type": "string", "description": "The path to the 'carbon' binary.", - "default": "./bazel-bin/toolchain/install/run_carbon" + "default": "./bazel-bin/toolchain/carbon" } } } diff --git a/utils/vscode/src/extension.ts b/utils/vscode/src/extension.ts index d89c1216ae9f..c58773b33cb7 100644 --- a/utils/vscode/src/extension.ts +++ b/utils/vscode/src/extension.ts @@ -26,7 +26,7 @@ export function activate(context: ExtensionContext) { // fallback. command: settings.get( 'carbonPath', - context.asAbsolutePath('./bazel-bin/toolchain/install/run_carbon') + context.asAbsolutePath('./bazel-bin/toolchain/carbon') ), args: ['language-server'], };