sp1_cli/commands/
install_toolchain.rs

1use 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        // Check if rust is installed.
31        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        // Setup client with optional token.
44        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        // Setup variables.
61        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        // Download the toolchain.
115        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        // Remove the existing toolchain from rustup, if it exists.
119        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        // Unpack the toolchain.
138        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        // Move the toolchain to a randomly named directory in the 'toolchains' folder
145        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        // Link the new toolchain directory to rustup
153        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        // Ensure permissions.
165        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}