rust
Version:
Install the rust toolchain using npm
140 lines (119 loc) • 4.24 kB
JavaScript
import os from "node:os";
import path from "node:path";
import fs from "node:fs/promises";
import { $ } from "zx";
import { fileURLToPath } from "node:url";
async function getToolchain(dirname) {
const packageJsonPath = path.join(dirname, "package.json");
const packageJsonText = await fs.readFile(packageJsonPath, "utf-8");
const packageJson = JSON.parse(packageJsonText);
if (packageJson.version.endsWith("-nightly")) {
return "nightly";
} else if (packageJson.version.endsWith("-beta")) {
return "beta";
} else if (packageJson.version.endsWith("-stable")) {
return "stable";
} else if (/1\.\d+\.\d+/.test(packageJson.version)) {
return packageJson.version;
} else {
throw new Error("unknown toolchain");
}
}
const getTargetName = (toolchain) => {
const rawOsType = os.type();
const rawArchitecture = os.arch();
// We want to use rust-style target triples as the canonical key
// for a platform, so translate the "os" library's concepts into rust ones
let osType = "";
switch (rawOsType) {
case "Windows_NT":
throw new Error("Windows is not supported");
case "Darwin":
osType = "apple-darwin";
break;
case "Linux":
osType = "unknown-linux-gnu";
break;
}
let arch = "";
switch (rawArchitecture) {
case "x64":
arch = "x86_64";
break;
case "arm64":
arch = "aarch64";
break;
}
// Assume the above succeeded and build a target triple to look things up with.
// If any of it failed, this lookup will fail and we'll handle it like normal.
return `${toolchain}-${arch}-${osType}`;
};
const install = async () => {
let installGlobally;
if (!process.env.RUST_INSTALL_LOCATION) {
installGlobally = true;
} else if (process.env.RUST_INSTALL_LOCATION === "global") {
installGlobally = true;
} else if (process.env.RUST_INSTALL_LOCATION === "local") {
installGlobally = false;
} else {
throw new Error(
`unknown install location: ${process.env.RUST_INSTALL_LOCATION}`
);
}
console.log("downloading rustup");
const resp = await fetch("https://sh.rustup.rs");
const text = await resp.text();
const __filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(__filename);
const rustupPath = path.join(dirname, "node_modules", "rustup.sh");
const toolchain = await getToolchain(dirname);
await fs.mkdir(path.join(dirname, "node_modules"), { recursive: true });
await fs.writeFile(rustupPath, text);
await $`chmod +x ${rustupPath}`;
const installDir = path.join(dirname, "node_modules", ".rustup");
const isSudo = !!process.env.SUDO_USER;
// If we are in sudo mode, we need to downgrade for rustup to work properly
const sudoCommand = isSudo ? `sudo -u ${process.env.SUDO_USER}` : ""
console.log("installing rust");
// In some cases like pnpm, there's a wonky directory structure,
// so we can't install the rust toolchain locally, instead we
// install it globally. This isn't ideal, but it works
// well enough for deployment situations like Vercel.
if (installGlobally) {
await $`${sudoCommand} ${rustupPath} -y --default-toolchain ${toolchain}`;
const homedir = os.homedir();
await $`source ${homedir}/.cargo/env`;
return;
}
const envVar = `RUSTUP_HOME=${installDir}`;
await $`${envVar} ${sudoCommand} ${rustupPath} -y --default-toolchain ${toolchain}`;
const targetTriple = getTargetName(toolchain);
const binPath = path.join(
dirname,
"node_modules",
".rustup",
"toolchains",
targetTriple,
"bin"
);
console.log(`linking binaries in ${binPath}`);
await fs.mkdir(path.join(dirname, "..", ".bin"), { recursive: true });
console.log("created .bin directory");
// We link manually because we may not install the binaries, so we don't want symlinks pointing to nowhere.
await Promise.all([
fs.symlink(
path.join(binPath, "cargo"),
path.join(dirname, "..", ".bin", "cargo")
),
fs.symlink(
path.join(binPath, "rustc"),
path.join(dirname, "..", ".bin", "rustc")
),
fs.symlink(
path.join(binPath, "rustdoc"),
path.join(dirname, "..", ".bin", "rustdoc")
),
]);
};
export { install };