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))
52                            .unwrap(),
53                    );
54                    headers
55                })
56                .build()?
57        } else {
58            client_builder.build()?
59        };
60
61        // Setup variables.
62        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        // Download the toolchain.
116        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        // Remove the existing toolchain from rustup, if it exists.
120        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        // Unpack the toolchain.
139        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        // Move the toolchain to a randomly named directory in the 'toolchains' folder
146        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        // Link the new toolchain directory to rustup
154        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        // Ensure permissions.
166        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}