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

Fix up busybox detection for relative symlinks (#4522)

Handling for relative symlinks is new (comment talks about relative
symlinks). Relocated to add tests though, to do extra checking of logic.
Jon Ross-Perkins 1 год назад
Родитель
Сommit
c59faea0e8

+ 21 - 0
toolchain/install/BUILD

@@ -71,6 +71,26 @@ cc_library(
     ],
 )
 
+cc_library(
+    name = "busybox_info",
+    hdrs = ["busybox_info.h"],
+    deps = [
+        "//common:error",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_test(
+    name = "busybox_info_test",
+    srcs = ["busybox_info_test.cpp"],
+    deps = [
+        ":busybox_info",
+        "//common:check",
+        "//testing/base:gtest_main",
+        "@googletest//:gtest",
+    ],
+)
+
 # This target doesn't include prelude libraries. To get a target that has the
 # prelude available, use //toolchain.
 cc_binary(
@@ -78,6 +98,7 @@ cc_binary(
     srcs = ["busybox_main.cpp"],
     env = cc_env(),
     deps = [
+        ":busybox_info",
         ":install_paths",
         "//common:all_llvm_targets",
         "//common:bazel_working_dir",

+ 47 - 0
toolchain/install/busybox_info.h

@@ -0,0 +1,47 @@
+// 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
+
+#ifndef CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_
+#define CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_
+
+#include <filesystem>
+#include <optional>
+#include <string>
+
+#include "common/error.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace Carbon {
+
+struct BusyboxInfo {
+  // The path to `carbon-busybox`.
+  std::filesystem::path bin_path;
+  // The mode, such as `carbon` or `clang`.
+  std::optional<std::string> mode;
+};
+
+// Returns the busybox information, given argv[0]. This primarily handles
+// resolving symlinks that point at the busybox.
+inline auto GetBusyboxInfo(llvm::StringRef argv0) -> ErrorOr<BusyboxInfo> {
+  BusyboxInfo info = BusyboxInfo{argv0.str(), std::nullopt};
+  while (true) {
+    std::string filename = info.bin_path.filename();
+    if (filename == "carbon-busybox") {
+      return info;
+    }
+    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;
+  }
+}
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_INSTALL_BUSYBOX_INFO_H_

+ 156 - 0
toolchain/install/busybox_info_test.cpp

@@ -0,0 +1,156 @@
+// 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
+
+#include "toolchain/install/busybox_info.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <fstream>
+
+#include "common/check.h"
+
+namespace Carbon {
+namespace {
+
+using ::testing::Eq;
+
+class BusyboxInfoTest : public ::testing::Test {
+ protected:
+  // Set up a temp directory for the test case.
+  explicit BusyboxInfoTest() {
+    const char* tmpdir = std::getenv("TEST_TMPDIR");
+    CARBON_CHECK(tmpdir);
+    dir_ = MakeDir(
+        std::filesystem::path(tmpdir) /
+        ::testing::UnitTest::GetInstance()->current_test_info()->name());
+  }
+
+  // Delete the test case's temp directory.
+  ~BusyboxInfoTest() override {
+    std::error_code ec;
+    std::filesystem::remove_all(dir_, ec);
+    CARBON_CHECK(!ec, "error removing {0}: {1}", dir_, ec.message());
+  }
+
+  // Creates a stub file. Returns the input file for easier use.
+  auto MakeFile(std::filesystem::path file) -> std::filesystem::path {
+    std::ofstream out(file.c_str());
+    out << "stub";
+    CARBON_CHECK(out, "error creating {0}", file);
+    return file;
+  }
+
+  // Creates a symlink to the target. Returns the input file for easier use.
+  auto MakeSymlink(std::filesystem::path file, auto target)
+      -> std::filesystem::path {
+    std::error_code ec;
+    std::filesystem::create_symlink(target, file, ec);
+    CARBON_CHECK(!ec, "error creating {0}: {1}", file, ec.message());
+    return file;
+  }
+
+  // Creates a directory. 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);
+    CARBON_CHECK(!ec, "error creating {0}: {1}", dir, ec.message());
+    return dir;
+  }
+
+  // The test's temp directory, deleted on destruction.
+  std::filesystem::path dir_;
+};
+
+TEST_F(BusyboxInfoTest, Direct) {
+  auto busybox = MakeFile(dir_ / "carbon-busybox");
+
+  auto info = GetBusyboxInfo(busybox.string());
+  ASSERT_TRUE(info.ok()) << info.error();
+  EXPECT_THAT(info->bin_path, Eq(busybox));
+  EXPECT_THAT(info->mode, Eq(std::nullopt));
+}
+
+TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectory) {
+  MakeFile(dir_ / "carbon-busybox");
+  auto target = MakeSymlink(dir_ / "carbon", "carbon-busybox");
+
+  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"));
+}
+
+TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectoryWithDot) {
+  MakeFile(dir_ / "carbon-busybox");
+  auto target = MakeSymlink(dir_ / "carbon", "./carbon-busybox");
+
+  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"));
+}
+
+TEST_F(BusyboxInfoTest, ExtraSymlink) {
+  MakeFile(dir_ / "carbon-busybox");
+  MakeSymlink(dir_ / "carbon", "carbon-busybox");
+  auto target = MakeSymlink(dir_ / "c", "carbon");
+
+  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"));
+}
+
+TEST_F(BusyboxInfoTest, BusyboxIsSymlink) {
+  MakeFile(dir_ / "actual-busybox");
+  auto target = MakeSymlink(dir_ / "carbon-busybox", "actual-busybox");
+
+  auto info = GetBusyboxInfo(target.string());
+  ASSERT_TRUE(info.ok()) << info.error();
+  EXPECT_THAT(info->bin_path, Eq(target));
+  EXPECT_THAT(info->mode, Eq(std::nullopt));
+}
+
+TEST_F(BusyboxInfoTest, BusyboxIsSymlinkToNowhere) {
+  auto target = MakeSymlink(dir_ / "carbon-busybox", "nonexistent");
+
+  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(std::nullopt));
+}
+
+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");
+
+  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"));
+}
+
+TEST_F(BusyboxInfoTest, NotBusyboxFile) {
+  auto target = MakeFile(dir_ / "file");
+
+  auto info = GetBusyboxInfo(target.string());
+  EXPECT_FALSE(info.ok());
+}
+
+TEST_F(BusyboxInfoTest, NotBusyboxSymlink) {
+  MakeFile(dir_ / "file");
+  auto target = MakeSymlink(dir_ / "carbon", "file");
+
+  auto info = GetBusyboxInfo(target.string());
+  EXPECT_FALSE(info.ok());
+}
+
+}  // namespace
+}  // namespace Carbon

+ 7 - 37
toolchain/install/busybox_main.cpp

@@ -5,7 +5,6 @@
 #include <unistd.h>
 
 #include <cstdlib>
-#include <filesystem>
 
 #include "common/bazel_working_dir.h"
 #include "common/error.h"
@@ -15,58 +14,29 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/LLVMDriver.h"
 #include "toolchain/driver/driver.h"
+#include "toolchain/install/busybox_info.h"
 #include "toolchain/install/install_paths.h"
 
 namespace Carbon {
 
-namespace {
-struct BusyboxInfo {
-  // The path to `carbon-busybox`.
-  std::filesystem::path bin_path;
-  // The mode, such as `carbon` or `clang`.
-  std::optional<std::string> mode;
-};
-}  // namespace
-
-// Returns the busybox information, given argv[0]. This primarily handles
-// resolving symlinks that point at the busybox.
-static auto GetBusyboxInfo(llvm::StringRef argv0) -> ErrorOr<BusyboxInfo> {
-  BusyboxInfo info = BusyboxInfo{argv0.str(), std::nullopt};
-  while (true) {
-    std::string filename = info.bin_path.filename();
-    if (filename == "carbon-busybox") {
-      return info;
-    }
-    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;
-    info.bin_path = symlink_target;
-  }
-}
-
 // The actual `main` implementation. Can return an exit code or an `Error`
 // (which causes EXIT_FAILRUE).
 static auto Main(int argc, char** argv) -> ErrorOr<int> {
-  Carbon::InitLLVM init_llvm(argc, argv);
+  InitLLVM init_llvm(argc, argv);
 
   // Start by resolving any symlinks.
-  CARBON_ASSIGN_OR_RETURN(auto busybox_info, Carbon::GetBusyboxInfo(argv[0]));
+  CARBON_ASSIGN_OR_RETURN(auto busybox_info, GetBusyboxInfo(argv[0]));
 
   auto fs = llvm::vfs::getRealFileSystem();
 
   // Resolve paths before calling SetWorkingDirForBazel.
-  std::string exe_path =
-      Carbon::FindExecutablePath(busybox_info.bin_path.string());
-  const auto install_paths = Carbon::InstallPaths::MakeExeRelative(exe_path);
+  std::string exe_path = FindExecutablePath(busybox_info.bin_path.string());
+  const auto install_paths = InstallPaths::MakeExeRelative(exe_path);
   if (install_paths.error()) {
     return Error(*install_paths.error());
   }
 
-  Carbon::SetWorkingDirForBazel();
+  SetWorkingDirForBazel();
 
   llvm::SmallVector<llvm::StringRef> args;
   args.reserve(argc + 1);
@@ -75,7 +45,7 @@ static auto Main(int argc, char** argv) -> ErrorOr<int> {
   }
   args.append(argv + 1, argv + argc);
 
-  Carbon::Driver driver(*fs, &install_paths, llvm::outs(), llvm::errs());
+  Driver driver(*fs, &install_paths, llvm::outs(), llvm::errs());
   bool success = driver.RunCommand(args).success;
   return success ? EXIT_SUCCESS : EXIT_FAILURE;
 }