sp1_cli/commands/
install_toolchain.rs1use std::{
2 fs::{self},
3 io::Read,
4 process::Command,
5};
6
7use anyhow::Result;
8use clap::Parser;
9use dirs::home_dir;
10use rand::{distributions::Alphanumeric, Rng};
11use reqwest::Client;
12use sp1_sdk::install::download_file;
13
14#[cfg(target_family = "unix")]
15use std::os::unix::fs::PermissionsExt;
16
17use crate::{
18 get_target, get_toolchain_download_url, is_supported_target, url_exists, RUSTUP_TOOLCHAIN_NAME,
19};
20
21#[derive(Parser)]
22#[command(name = "install-toolchain", about = "Install the cargo-prove toolchain.")]
23pub struct InstallToolchainCmd {
24 #[arg(short, long, env = "GITHUB_TOKEN")]
25 pub token: Option<String>,
26}
27
28impl InstallToolchainCmd {
29 pub fn run(&self) -> Result<()> {
30 if Command::new("rustup")
32 .arg("--version")
33 .stdout(std::process::Stdio::null())
34 .stderr(std::process::Stdio::null())
35 .status()
36 .is_err()
37 {
38 return Err(anyhow::anyhow!(
39 "Rust is not installed. Please install Rust from https://rustup.rs/ and try again."
40 ));
41 }
42
43 let client_builder = Client::builder().user_agent("Mozilla/5.0");
45 let client = if let Some(ref token) = self.token {
46 client_builder
47 .default_headers({
48 let mut headers = reqwest::header::HeaderMap::new();
49 headers.insert(
50 reqwest::header::AUTHORIZATION,
51 reqwest::header::HeaderValue::from_str(&format!("token {token}")).unwrap(),
52 );
53 headers
54 })
55 .build()?
56 } else {
57 client_builder.build()?
58 };
59
60 let root_dir = home_dir().unwrap().join(".sp1");
62 match fs::read_dir(&root_dir) {
63 Ok(entries) =>
64 {
65 #[allow(clippy::manual_flatten)]
66 for entry in entries {
67 if let Ok(entry) = entry {
68 let entry_path = entry.path();
69 let entry_name = entry_path.file_name().unwrap();
70 if entry_path.is_dir() &&
71 entry_name != "bin" &&
72 entry_name != "circuits" &&
73 entry_name != "toolchains"
74 {
75 if let Err(err) = fs::remove_dir_all(&entry_path) {
76 println!("Failed to remove directory {entry_path:?}: {err}");
77 }
78 } else if entry_path.is_file() {
79 if let Err(err) = fs::remove_file(&entry_path) {
80 println!("Failed to remove file {entry_path:?}: {err}");
81 }
82 }
83 }
84 }
85 }
86 Err(_) => println!("No existing ~/.sp1 directory to remove."),
87 }
88 println!("Successfully cleaned up ~/.sp1 directory.");
89 match fs::create_dir_all(&root_dir) {
90 Ok(_) => println!("Successfully created ~/.sp1 directory."),
91 Err(err) => println!("Failed to create ~/.sp1 directory: {err}"),
92 };
93
94 assert!(
95 is_supported_target(),
96 "Unsupported architecture. Please build the toolchain from source."
97 );
98 let target = get_target();
99 let toolchain_asset_name = format!("rust-toolchain-{target}.tar.gz");
100 let toolchain_archive_path = root_dir.join(toolchain_asset_name.clone());
101 let toolchain_dir = root_dir.join(&target);
102 let rt = tokio::runtime::Runtime::new()?;
103
104 let toolchain_download_url =
105 rt.block_on(get_toolchain_download_url(&client, target.to_string()));
106
107 let artifact_exists = rt.block_on(url_exists(&client, toolchain_download_url.as_str()));
108 if !artifact_exists {
109 return Err(anyhow::anyhow!(
110 "Unsupported architecture. Please build the toolchain from source."
111 ));
112 }
113
114 let mut file = fs::File::create(toolchain_archive_path)?;
116 rt.block_on(download_file(&client, toolchain_download_url.as_str(), &mut file)).unwrap();
117
118 let mut child = Command::new("rustup")
120 .current_dir(&root_dir)
121 .args(["toolchain", "remove", RUSTUP_TOOLCHAIN_NAME])
122 .stdout(std::process::Stdio::piped())
123 .spawn()?;
124 let res = child.wait();
125 match res {
126 Ok(_) => {
127 let mut stdout = child.stdout.take().unwrap();
128 let mut content = String::new();
129 stdout.read_to_string(&mut content).unwrap();
130 if !content.contains("no toolchain installed") {
131 println!("Successfully removed existing toolchain.");
132 }
133 }
134 Err(_) => println!("Failed to remove existing toolchain."),
135 }
136
137 fs::create_dir_all(toolchain_dir.clone())?;
139 Command::new("tar")
140 .current_dir(&root_dir)
141 .args(["-xzf", &toolchain_asset_name, "-C", &toolchain_dir.to_string_lossy()])
142 .status()?;
143
144 let toolchains_dir = root_dir.join("toolchains");
146 fs::create_dir_all(&toolchains_dir)?;
147 let random_string: String =
148 rand::thread_rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect();
149 let new_toolchain_dir = toolchains_dir.join(random_string);
150 fs::rename(&toolchain_dir, &new_toolchain_dir)?;
151
152 Command::new("rustup")
154 .current_dir(&root_dir)
155 .args([
156 "toolchain",
157 "link",
158 RUSTUP_TOOLCHAIN_NAME,
159 &new_toolchain_dir.to_string_lossy(),
160 ])
161 .status()?;
162 println!("Successfully linked toolchain to rustup.");
163
164 let bin_dir = new_toolchain_dir.join("bin");
166 let rustlib_bin_dir = new_toolchain_dir.join(format!("lib/rustlib/{target}/bin"));
167 for entry in fs::read_dir(bin_dir)?.chain(fs::read_dir(rustlib_bin_dir)?) {
168 let entry = entry?;
169 if entry.path().is_file() {
170 let mut perms = entry.metadata()?.permissions();
171 perms.set_mode(0o755);
172 fs::set_permissions(entry.path(), perms)?;
173 }
174 }
175
176 Ok(())
177 }
178}