Просмотр исходного кода

Replace #4505 with a different set of workarounds (#4527)

This restores the symlinks for the installation, but teaches the busybox
info search to look for a relative path to the busybox binary itself
before walking through symlinks. This let's it find the tree structure
when directly invoking `prefix_root/bin/carbon` or similar, either
inside of a Bazel rule or from the command line, and mirrors how we
expect the installed tree to look. This works even when Bazel resolves
the symlink target fully, and potentially to something nonsensical like
a CAS file.

In order to make a convenient Bazel target that can be used with `bazel
run //toolchain`, this adds an override to explicitly set the desired
argv[0] to use when selecting a mode for the busybox and a busybox
binary. Currently, the workaround uses an environment variable because
that required the least amount of plumbing, and seems a useful override
mechanism generally, but I'm open to other approaches.

This should allow a few things to work a bit more nicely:
- It should handle sibling symlinks like `clang++` to `clang` or
  `ld.lld` to `lld`, where that symlink in turn points at the busybox.
  We want to use *initial* `argv[0]` value to select the mode there.
- It avoids bouncing through Python (or other subprocesses) when
  invoking the `carbon` binary in Bazel rules, which will be nice for
  building the example code and benchmarking.

It does come at a cost of removing one feature: the initial symlink
can't be some unrelated alias like `my_carbon_symlink` -- we expect the
*first* argv[0] name to have the meaningful filename for selecting
a busybox mode.

It also trades the complexity of the Python script for some complexity
in the busybox search in order to look for a relative `carbon-busybox`
binary. On the whole, I think that tradeoff is worthwhile, but it isn't
free.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Chandler Carruth 1 год назад
Родитель
Сommit
13502b7c89

+ 1 - 1
.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}"
     }

+ 1 - 1
.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",

+ 1 - 1
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

+ 13 - 1
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",
 )

+ 4 - 2
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.

+ 6 - 16
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",
-)

+ 61 - 6
toolchain/install/busybox_info.h

@@ -6,6 +6,7 @@
 #define CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_
 
 #include <filesystem>
+#include <iterator>
 #include <optional>
 #include <string>
 
@@ -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<std::string> 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> {
-  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: `<prefix>/bin/carbon`
+    // - Other tools will be: `<prefix>/lib/carbon/<group>/bin/<tool>`
+    //
+    // 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;
   }
 }
 

+ 156 - 21
toolchain/install/busybox_info_test.cpp

@@ -11,6 +11,7 @@
 #include <fstream>
 
 #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<std::filesystem::path> 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

+ 1 - 1
toolchain/install/busybox_main.cpp

@@ -40,7 +40,7 @@ static auto Main(int argc, char** argv) -> ErrorOr<int> {
 
   llvm::SmallVector<llvm::StringRef> 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);

+ 5 - 34
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,

+ 0 - 67
toolchain/install/run_tool.bzl

@@ -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,
-)

+ 0 - 62
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:

+ 40 - 0
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,
+)

+ 1 - 1
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,
     }

+ 1 - 1
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"
         }
       }
     }

+ 1 - 1
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'],
   };