use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use anyhow::{bail, Context, Result};
use fs_extra::dir::CopyOptions;
use risc0_build::risc0_data;
use crate::utils::CommandExt;
pub enum ToolchainRepo {
Rust,
Cpp,
}
impl ToolchainRepo {
pub const fn url(&self) -> &str {
match self {
Self::Rust => "https://github.com/risc0/rust.git",
Self::Cpp => "https://github.com/risc0/toolchain.git",
}
}
pub const fn language(&self) -> &str {
match self {
Self::Rust => "rust",
Self::Cpp => "c",
}
}
pub fn asset_name(&self, target: &str) -> String {
match self {
Self::Rust => format!("rust-toolchain-{target}.tar.gz"),
Self::Cpp => match target {
"aarch64-apple-darwin" => "riscv32im-osx-arm64.tar.xz".to_string(),
"x86_64-unknown-linux-gnu" => "riscv32im-linux-x86_64.tar.xz".to_string(),
_ => panic!("binaries for {target} are not available"),
},
}
}
}
pub const RUST_BRANCH: &str = "risc0";
pub const RUSTUP_TOOLCHAIN_NAME: &str = "risc0";
#[derive(Clone, Debug)]
pub struct RustupToolchain {
pub name: String,
pub path: PathBuf,
}
impl RustupToolchain {
fn find_by_name(name: &str) -> Result<Option<Self>, anyhow::Error> {
let out = Command::new("rustup")
.args(["toolchain", "list", "--verbose"])
.capture_stdout()?;
let path_raw = out
.lines()
.find(|line| line.trim().starts_with(name))
.and_then(|line| line.strip_prefix(name))
.map(|line| line.trim());
if let Some(path) = path_raw {
Ok(Some(Self {
name: name.to_string(),
path: path.into(),
}))
} else {
Ok(None)
}
}
pub fn link(name: &str, dir: &Path) -> Result<Self> {
eprintln!(
"Activating rustup toolchain {} at {}...",
name,
dir.display()
);
#[cfg(not(target_os = "windows"))]
let rustc_exe = "rustc";
#[cfg(target_os = "windows")]
let rustc_exe = "rustc.exe";
let rustc_path = dir.join("bin").join(rustc_exe);
if !rustc_path.is_file() {
bail!(
"Invalid toolchain directory: rustc executable not found at {}",
rustc_path.display()
);
}
if Self::find_by_name(name)?.is_some() {
Command::new("rustup")
.args(["toolchain", "remove", name])
.run()
.context("Could not remove existing toolchain")?;
}
Command::new("rustup")
.args(["toolchain", "link", name])
.arg(dir)
.run_verbose()
.context("Could not link toolchain: rustup not installed?")?;
eprintln!("rustup toolchain {name} was linked and is now available!");
Ok(Self {
name: name.to_string(),
path: dir.into(),
})
}
}
#[derive(Clone, Debug)]
pub struct CppToolchain {
pub path: PathBuf,
}
impl CppToolchain {
fn get_subdir(path: &Path) -> Result<PathBuf> {
let sub_dir: Vec<_> = std::fs::read_dir(path)?.collect();
if sub_dir.len() != 1 {
bail!(
"Expected {} to only have 1 subdirectory, found {}",
path.display(),
sub_dir.len()
);
}
let entry = sub_dir[0].as_ref().unwrap();
Ok(entry.path())
}
pub fn link(path: &Path) -> Result<Self> {
let cpp_download_dir = Self::get_subdir(path)?;
let r0_data = risc0_data()?;
fs_extra::dir::copy(
cpp_download_dir.clone(),
&r0_data,
&CopyOptions::new().overwrite(true).copy_inside(true),
)?;
let cpp_install_dir = &r0_data.join("cpp");
if cpp_install_dir.exists() {
fs::remove_dir_all(cpp_install_dir)?;
}
fs::rename(
r0_data.join(cpp_download_dir.file_name().unwrap()),
cpp_install_dir,
)?;
Ok(Self {
path: cpp_install_dir.into(),
})
}
}