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))
52 .unwrap(),
53 );
54 headers
55 })
56 .build()?
57 } else {
58 client_builder.build()?
59 };
60
61 let root_dir = home_dir().unwrap().join(".sp1");
63 match fs::read_dir(&root_dir) {
64 Ok(entries) =>
65 {
66 #[allow(clippy::manual_flatten)]
67 for entry in entries {
68 if let Ok(entry) = entry {
69 let entry_path = entry.path();
70 let entry_name = entry_path.file_name().unwrap();
71 if entry_path.is_dir()
72 && entry_name != "bin"
73 && entry_name != "circuits"
74 && entry_name != "toolchains"
75 {
76 if let Err(err) = fs::remove_dir_all(&entry_path) {
77 println!("Failed to remove directory {:?}: {}", entry_path, err);
78 }
79 } else if entry_path.is_file() {
80 if let Err(err) = fs::remove_file(&entry_path) {
81 println!("Failed to remove file {:?}: {}", entry_path, err);
82 }
83 }
84 }
85 }
86 }
87 Err(_) => println!("No existing ~/.sp1 directory to remove."),
88 }
89 println!("Successfully cleaned up ~/.sp1 directory.");
90 match fs::create_dir_all(&root_dir) {
91 Ok(_) => println!("Successfully created ~/.sp1 directory."),
92 Err(err) => println!("Failed to create ~/.sp1 directory: {}", err),
93 };
94
95 assert!(
96 is_supported_target(),
97 "Unsupported architecture. Please build the toolchain from source."
98 );
99 let target = get_target();
100 let toolchain_asset_name = format!("rust-toolchain-{}.tar.gz", target);
101 let toolchain_archive_path = root_dir.join(toolchain_asset_name.clone());
102 let toolchain_dir = root_dir.join(&target);
103 let rt = tokio::runtime::Runtime::new()?;
104
105 let toolchain_download_url =
106 rt.block_on(get_toolchain_download_url(&client, target.to_string()));
107
108 let artifact_exists = rt.block_on(url_exists(&client, toolchain_download_url.as_str()));
109 if !artifact_exists {
110 return Err(anyhow::anyhow!(
111 "Unsupported architecture. Please build the toolchain from source."
112 ));
113 }
114
115 let mut file = fs::File::create(toolchain_archive_path)?;
117 rt.block_on(download_file(&client, toolchain_download_url.as_str(), &mut file)).unwrap();
118
119 let mut child = Command::new("rustup")
121 .current_dir(&root_dir)
122 .args(["toolchain", "remove", RUSTUP_TOOLCHAIN_NAME])
123 .stdout(std::process::Stdio::piped())
124 .spawn()?;
125 let res = child.wait();
126 match res {
127 Ok(_) => {
128 let mut stdout = child.stdout.take().unwrap();
129 let mut content = String::new();
130 stdout.read_to_string(&mut content).unwrap();
131 if !content.contains("no toolchain installed") {
132 println!("Successfully removed existing toolchain.");
133 }
134 }
135 Err(_) => println!("Failed to remove existing toolchain."),
136 }
137
138 fs::create_dir_all(toolchain_dir.clone())?;
140 Command::new("tar")
141 .current_dir(&root_dir)
142 .args(["-xzf", &toolchain_asset_name, "-C", &toolchain_dir.to_string_lossy()])
143 .status()?;
144
145 let toolchains_dir = root_dir.join("toolchains");
147 fs::create_dir_all(&toolchains_dir)?;
148 let random_string: String =
149 rand::thread_rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect();
150 let new_toolchain_dir = toolchains_dir.join(random_string);
151 fs::rename(&toolchain_dir, &new_toolchain_dir)?;
152
153 Command::new("rustup")
155 .current_dir(&root_dir)
156 .args([
157 "toolchain",
158 "link",
159 RUSTUP_TOOLCHAIN_NAME,
160 &new_toolchain_dir.to_string_lossy(),
161 ])
162 .status()?;
163 println!("Successfully linked toolchain to rustup.");
164
165 let bin_dir = new_toolchain_dir.join("bin");
167 let rustlib_bin_dir = new_toolchain_dir.join(format!("lib/rustlib/{}/bin", target));
168 for entry in fs::read_dir(bin_dir)?.chain(fs::read_dir(rustlib_bin_dir)?) {
169 let entry = entry?;
170 if entry.path().is_file() {
171 let mut perms = entry.metadata()?.permissions();
172 perms.set_mode(0o755);
173 fs::set_permissions(entry.path(), perms)?;
174 }
175 }
176
177 Ok(())
178 }
179}