import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as plugins from '../plugins.js'; export class ToolchainManager { public static TOOLCHAIN_DIR = '/tmp/tsrust_toolchain'; public static RUSTUP_HOME = '/tmp/tsrust_toolchain/rustup'; public static CARGO_HOME = '/tmp/tsrust_toolchain/cargo'; public static BIN_DIR = '/tmp/tsrust_toolchain/cargo/bin'; private shell: plugins.smartshell.Smartshell; constructor() { this.shell = new plugins.smartshell.Smartshell({ executor: 'bash', }); } /** * Returns the Rust host triple for the current platform. */ public getHostTriple(): string { const platform = os.platform(); const arch = os.arch(); if (platform === 'linux' && arch === 'x64') return 'x86_64-unknown-linux-gnu'; if (platform === 'linux' && arch === 'arm64') return 'aarch64-unknown-linux-gnu'; if (platform === 'darwin' && arch === 'x64') return 'x86_64-apple-darwin'; if (platform === 'darwin' && arch === 'arm64') return 'aarch64-apple-darwin'; throw new Error( `Unsupported platform: ${platform}/${arch}. Please install Rust manually via https://rustup.rs/` ); } /** * Checks if the bundled toolchain is already installed at TOOLCHAIN_DIR. */ public async isInstalled(): Promise { const cargoPath = path.join(ToolchainManager.BIN_DIR, 'cargo'); try { const stat = await fs.promises.stat(cargoPath); return stat.isFile(); } catch { return false; } } /** * Downloads rustup-init and installs a minimal Rust toolchain to TOOLCHAIN_DIR. */ public async install(): Promise { const triple = this.getHostTriple(); const rustupInitUrl = `https://static.rust-lang.org/rustup/dist/${triple}/rustup-init`; const rustupInitPath = path.join(ToolchainManager.TOOLCHAIN_DIR, 'rustup-init'); // Ensure toolchain directory exists await fs.promises.mkdir(ToolchainManager.TOOLCHAIN_DIR, { recursive: true }); console.log(`Downloading rustup-init for ${triple}...`); const downloadResult = await this.shell.exec( `curl -sSf -o ${rustupInitPath} ${rustupInitUrl} && chmod +x ${rustupInitPath}` ); if (downloadResult.exitCode !== 0) { throw new Error(`Failed to download rustup-init: ${downloadResult.stdout}`); } console.log('Installing minimal Rust toolchain to /tmp/tsrust_toolchain/...'); const installCmd = [ `RUSTUP_HOME="${ToolchainManager.RUSTUP_HOME}"`, `CARGO_HOME="${ToolchainManager.CARGO_HOME}"`, rustupInitPath, '-y', '--default-toolchain stable', '--profile minimal', '--no-modify-path', ].join(' '); const installResult = await this.shell.exec(installCmd); if (installResult.exitCode !== 0) { throw new Error(`Failed to install Rust toolchain: ${installResult.stdout}`); } // Verify installation const verifyResult = await this.shell.execSilent( `${this.getEnvPrefix()}cargo --version` ); if (verifyResult.exitCode !== 0) { throw new Error('Rust toolchain installation verification failed.'); } console.log(`Installed: ${verifyResult.stdout.trim()}`); // Clean up rustup-init binary await fs.promises.unlink(rustupInitPath).catch(() => {}); } /** * Ensures the bundled toolchain is installed. Downloads if not present. */ public async ensureInstalled(): Promise { if (await this.isInstalled()) { console.log('Using bundled Rust toolchain from /tmp/tsrust_toolchain/'); return; } await this.install(); } /** * Returns a shell prefix string that sets RUSTUP_HOME, CARGO_HOME, and PATH * to point at the bundled toolchain. Prepend this to any shell command. */ public getEnvPrefix(): string { return ( `export RUSTUP_HOME="${ToolchainManager.RUSTUP_HOME}" CARGO_HOME="${ToolchainManager.CARGO_HOME}" && ` + `export PATH="${ToolchainManager.BIN_DIR}:$PATH" && ` ); } }