mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 19:51:09 +01:00
Previously, we used the FHS "prefix" concept as the basis of the install, but this makes it hard to integrate an installed toolchain with Bazel (or similar) build system where it wants the "root" of the toolchain to have some specific files (`MODULES.bazel` or `BUILD.bazel`), and cannot reference anything outside that directory tree. An easy solution is to make the `lib/carbon` directory the root of the install and never walking up from it. Then we simply have a `bin/carbon` symlink to the busybox that is useful for getting the command into the PATH, but isn't used for anything else. The FHS-constrained install paths surround a root we fully control the layout and files within. While initially motivated by trying to make a single toolchain structure that works both for installation and for Bazel, it actually makes the paths we end up using in the toolchain much simpler. We no longer have awkward `.../lib/carbon/../../lib/carbon/...` sequences in the toolchain which is cleaner and even a (trivial) efficiency gain. As I was doing this I noticed several out-of-date comments that I tried to fix, and I tried to improve some code reuse rather than re-computing paths. --------- Co-authored-by: Geoff Romer <gromer@google.com>
101 lines
3.8 KiB
C++
101 lines
3.8 KiB
C++
// 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 <iterator>
|
|
|
|
#include "common/exe_path.h"
|
|
#include "common/filesystem.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
|
|
namespace Carbon {
|
|
|
|
// The mode is set to the initial filename used for `argv[0]`.
|
|
static auto GetMode(const std::filesystem::path& argv0)
|
|
-> std::optional<std::string> {
|
|
std::string filename = argv0.filename();
|
|
if (filename != "carbon" && filename != "carbon-busybox") {
|
|
return filename;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto GetBusyboxInfo(const char* argv0) -> ErrorOr<BusyboxInfo> {
|
|
// Need storage due to `unsetenv` affecting `getenv` lifetime; using `path`
|
|
// for `GetMode`.
|
|
std::filesystem::path argv0_path = argv0;
|
|
|
|
// Check for an override of `argv[0]` from the environment and apply it.
|
|
if (const char* argv0_override = getenv(Argv0OverrideEnv)) {
|
|
argv0_path = argv0_override;
|
|
unsetenv(Argv0OverrideEnv);
|
|
}
|
|
|
|
BusyboxInfo info = {.bin_path = FindExecutablePath(argv0_path.c_str()),
|
|
.mode = GetMode(argv0_path)};
|
|
|
|
// Now search through any symlinks to locate the installed busybox binary.
|
|
while (true) {
|
|
if (info.bin_path.filename() == "carbon-busybox") {
|
|
// Check for bazel structure. For example, this makes work:
|
|
// /bin/sh -c "exec -a carbon ./bazel-bin/toolchain/carbon"
|
|
// /bin/sh -c "exec -a llvm-symbolizer ./bazel-bin/toolchain/carbon"
|
|
//
|
|
// This will never occur in a "bin" subdirectory, so doesn't need to be
|
|
// handled in the other return path.
|
|
std::string busybox_path = info.bin_path.parent_path().string() +
|
|
"/prefix/lib/carbon/carbon-busybox";
|
|
if (auto access = Filesystem::Cwd().Access(busybox_path);
|
|
access.ok() && *access) {
|
|
info.bin_path = busybox_path;
|
|
}
|
|
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 = info.bin_path.filename() == "carbon"
|
|
? parent_path / ".." / "lib" / "carbon"
|
|
: parent_path / ".." / "..";
|
|
auto busybox_path = lib_path / "carbon-busybox";
|
|
if (auto access = Filesystem::Cwd().Access(busybox_path);
|
|
access.ok() && *access) {
|
|
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.
|
|
auto readlink = Filesystem::Cwd().Readlink(info.bin_path);
|
|
if (!readlink.ok()) {
|
|
return ErrorBuilder()
|
|
<< "expected carbon-busybox symlink at `" << info.bin_path << "`";
|
|
}
|
|
|
|
// Do a path join, to handle relative symlinks.
|
|
info.bin_path = parent_path / *readlink;
|
|
}
|
|
}
|
|
|
|
} // namespace Carbon
|