sp1_cli/commands/
build_toolchain.rs1use anyhow::{Context, Result};
2use clap::Parser;
3use std::{
4 path::PathBuf,
5 process::{Command, Stdio},
6};
7
8use crate::{get_target, LATEST_SUPPORTED_TOOLCHAIN_VERSION_TAG, RUSTUP_TOOLCHAIN_NAME};
9
10trait CommandExecutor {
13 fn run(&mut self) -> Result<()>;
16}
17
18impl CommandExecutor for Command {
19 fn run(&mut self) -> Result<()> {
20 let status = self
21 .stderr(Stdio::inherit())
22 .stdout(Stdio::inherit())
23 .stdin(Stdio::inherit())
24 .status()
25 .with_context(|| format!("while executing {:?}", self.get_program()))?;
26
27 status
28 .success()
29 .then_some(())
30 .with_context(|| format!("{:?} failed: {status}", self.get_program()))
31 }
32}
33
34#[derive(Parser)]
35#[command(name = "build-toolchain", about = "Build the cargo-prove toolchain.")]
36pub struct BuildToolchainCmd {}
37
38impl BuildToolchainCmd {
39 pub fn run(&self) -> Result<()> {
40 let github_access_token = std::env::var("GITHUB_ACCESS_TOKEN");
42 let build_dir = std::env::var("SP1_BUILD_DIR");
43
44 let rust_dir = match build_dir {
46 Ok(build_dir) => {
47 println!("Detected SP1_BUILD_DIR, skipping cloning rust.");
48 PathBuf::from(build_dir).join("rust")
49 }
50 Err(_) => {
51 let temp_dir = std::env::temp_dir();
52 let dir = temp_dir.join("sp1-rust");
53 if dir.exists() {
54 std::fs::remove_dir_all(&dir)?;
55 }
56
57 println!("No SP1_BUILD_DIR detected, cloning rust.");
58 let repo_url = match github_access_token {
59 Ok(github_access_token) => {
60 println!("Detected GITHUB_ACCESS_TOKEN, using it to clone rust.");
61 format!("https://{github_access_token}@github.com/succinctlabs/rust")
62 }
63 Err(_) => {
64 println!("No GITHUB_ACCESS_TOKEN detected. If you get throttled by Github, set it to bypass the rate limit.");
65 "ssh://git@github.com/succinctlabs/rust".to_string()
66 }
67 };
68 Command::new("git")
69 .args([
70 "clone",
71 &repo_url,
72 "--depth=1",
73 "--single-branch",
74 &format!("--branch={LATEST_SUPPORTED_TOOLCHAIN_VERSION_TAG}"),
75 "sp1-rust",
76 ])
77 .current_dir(&temp_dir)
78 .run()?;
79 Command::new("git").args(["reset", "--hard"]).current_dir(&dir).run()?;
80 Command::new("git")
81 .args(["submodule", "update", "--init", "--recursive", "--progress"])
82 .current_dir(&dir)
83 .run()?;
84 dir
85 }
86 };
87
88 let bootstrap_toml = include_str!("bootstrap.toml");
90 let bootstrap_file = rust_dir.join("bootstrap.toml");
91 std::fs::write(&bootstrap_file, bootstrap_toml)
92 .with_context(|| format!("while writing configuration to {bootstrap_file:?}"))?;
93
94 let temp_dir = std::env::temp_dir().join("rustc-targets");
97 if !temp_dir.exists() {
98 std::fs::create_dir_all(&temp_dir)?;
99 }
100 std::fs::File::create(temp_dir.join("riscv32im-succinct-zkvm-elf.json"))?;
101
102 Command::new("python3")
104 .env("RUST_TARGET_PATH", &temp_dir)
105 .env("CARGO_TARGET_RISCV32IM_SUCCINCT_ZKVM_ELF_RUSTFLAGS", "-Cpasses=lower-atomic")
106 .env("CARGO_TARGET_RISCV64IM_SUCCINCT_ZKVM_ELF_RUSTFLAGS", "-Cpasses=lower-atomic")
107 .args([
108 "x.py",
109 "build",
110 "--stage",
111 "2",
112 "compiler/rustc",
113 "library",
114 "--target",
115 &format!(
116 "riscv32im-succinct-zkvm-elf,riscv64im-succinct-zkvm-elf,{}",
117 get_target()
118 ),
119 ])
120 .current_dir(&rust_dir)
121 .run()?;
122
123 match Command::new("rustup").args(["toolchain", "remove", RUSTUP_TOOLCHAIN_NAME]).run() {
125 Ok(_) => println!("Successfully removed existing toolchain."),
126 Err(_) => println!("No existing toolchain to remove."),
127 }
128
129 let mut toolchain_dir = None;
131 for wentry in std::fs::read_dir(rust_dir.join("build"))? {
132 let entry = wentry?;
133 let toolchain_dir_candidate = entry.path().join("stage2");
134 if toolchain_dir_candidate.is_dir() {
135 toolchain_dir = Some(toolchain_dir_candidate);
136 break;
137 }
138 }
139 let toolchain_dir = toolchain_dir.unwrap();
140 println!(
141 "Found built toolchain directory at {}.",
142 toolchain_dir.as_path().to_str().unwrap()
143 );
144
145 Command::new("rustup")
147 .args(["toolchain", "link", RUSTUP_TOOLCHAIN_NAME])
148 .arg(&toolchain_dir)
149 .run()?;
150 println!("Successfully linked the toolchain to rustup.");
151
152 let target = get_target();
154 let tar_gz_path = format!("rust-toolchain-{target}.tar.gz");
155 Command::new("tar")
156 .args([
157 "--exclude",
158 "lib/rustlib/src",
159 "--exclude",
160 "lib/rustlib/rustc-src",
161 "-hczvf",
162 &tar_gz_path,
163 "-C",
164 toolchain_dir.to_str().unwrap(),
165 ".",
166 ])
167 .run()?;
168 println!("Successfully compressed the toolchain to {tar_gz_path}.");
169
170 Ok(())
171 }
172}
173
174#[cfg(all(test, unix))]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn run_errors_on_nonzero_exit() {
180 let err = Command::new("sh").args(["-c", "exit 42"]).run().unwrap_err();
181 assert!(err.to_string().contains("failed"), "unexpected error: {err}");
182 }
183
184 #[test]
185 fn run_succeeds_on_zero_exit() {
186 Command::new("sh").args(["-c", "exit 0"]).run().unwrap();
187 }
188
189 #[test]
190 fn run_errors_do_not_expose_command_credentials() {
191 let mut nonzero_exit = Command::new("sh");
192 nonzero_exit.args(["-c", "exit 42"]);
193 let mut spawn_failure = Command::new("sh");
194 spawn_failure.current_dir(std::env::current_exe().unwrap());
195
196 for mut command in [nonzero_exit, spawn_failure] {
197 let error = command
198 .arg("https://test-url-token@github.com/succinctlabs/rust")
199 .env("GITHUB_ACCESS_TOKEN", "test-env-token")
200 .run()
201 .unwrap_err();
202 let message = format!("{error:#}");
203 assert!(message.contains("\"sh\""), "missing program name: {message}");
204 assert!(!message.contains("test-url-token"), "exposed argument: {message}");
205 assert!(!message.contains("test-env-token"), "exposed environment: {message}");
206 }
207 }
208}