substrate_wasm_builder/
prerequisites.rs
1use crate::{write_file_if_changed, CargoCommand, CargoCommandVersioned, RuntimeTarget};
19
20use console::style;
21use std::{
22 fs,
23 path::{Path, PathBuf},
24 process::Command,
25};
26
27use tempfile::tempdir;
28
29fn colorize_error_message(message: &str) -> String {
31 if super::color_output_enabled() {
32 style(message).red().bold().to_string()
33 } else {
34 message.into()
35 }
36}
37
38fn colorize_aux_message(message: &str) -> String {
40 if super::color_output_enabled() {
41 style(message).yellow().bold().to_string()
42 } else {
43 message.into()
44 }
45}
46
47pub(crate) fn check(target: RuntimeTarget) -> Result<CargoCommandVersioned, String> {
51 let cargo_command = crate::get_cargo_command(target);
52 match target {
53 RuntimeTarget::Wasm => {
54 if !cargo_command.supports_substrate_runtime_env(target) {
55 return Err(colorize_error_message(
56 "Cannot compile a WASM runtime: no compatible Rust compiler found!\n\
57 Install at least Rust 1.68.0 or a recent nightly version.",
58 ));
59 }
60
61 check_wasm_toolchain_installed(cargo_command)
62 },
63 RuntimeTarget::Riscv => {
64 if !cargo_command.supports_substrate_runtime_env(target) {
65 return Err(colorize_error_message(
66 "Cannot compile a RISC-V runtime: no compatible Rust compiler found!\n\
67 Install a toolchain from here and try again: https://github.com/paritytech/rustc-rv32e-toolchain/",
68 ));
69 }
70
71 let dummy_crate = DummyCrate::new(&cargo_command, target, false);
72 let version = dummy_crate.get_rustc_version();
73 Ok(CargoCommandVersioned::new(cargo_command, version))
74 },
75 }
76}
77
78pub(crate) struct DummyCrate<'a> {
79 cargo_command: &'a CargoCommand,
80 temp: tempfile::TempDir,
81 manifest_path: PathBuf,
82 target: RuntimeTarget,
83 ignore_target: bool,
84}
85
86impl<'a> DummyCrate<'a> {
87 pub(crate) fn new(
89 cargo_command: &'a CargoCommand,
90 target: RuntimeTarget,
91 ignore_target: bool,
92 ) -> Self {
93 let temp = tempdir().expect("Creating temp dir does not fail; qed");
94 let project_dir = temp.path();
95 fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed");
96
97 let manifest_path = project_dir.join("Cargo.toml");
98 match target {
99 RuntimeTarget::Wasm => {
100 write_file_if_changed(
101 &manifest_path,
102 r#"
103 [package]
104 name = "dummy-crate"
105 version = "1.0.0"
106 edition = "2021"
107
108 [lib]
109 crate-type = ["cdylib"]
110
111 [workspace]
112 "#,
113 );
114
115 write_file_if_changed(
116 project_dir.join("src/lib.rs"),
117 r#"
118 #![no_std]
119
120 #[panic_handler]
121 fn panic(_: &core::panic::PanicInfo<'_>) -> ! {
122 loop {}
123 }
124 "#,
125 );
126 },
127 RuntimeTarget::Riscv => {
128 write_file_if_changed(
129 &manifest_path,
130 r#"
131 [package]
132 name = "dummy-crate"
133 version = "1.0.0"
134 edition = "2021"
135
136 [workspace]
137 "#,
138 );
139
140 write_file_if_changed(
141 project_dir.join("src/main.rs"),
142 "#![allow(missing_docs)] fn main() {}",
143 );
144 },
145 }
146
147 DummyCrate { cargo_command, temp, manifest_path, target, ignore_target }
148 }
149
150 fn prepare_command(&self, subcommand: &str) -> Command {
151 let mut cmd = self.cargo_command.command();
152 cmd.current_dir(&self.temp);
155 cmd.arg(subcommand);
156 if !self.ignore_target {
157 cmd.arg(format!("--target={}", self.target.rustc_target(self.cargo_command)));
158 }
159 cmd.args(&["--manifest-path", &self.manifest_path.display().to_string()]);
160
161 if super::color_output_enabled() {
162 cmd.arg("--color=always");
163 }
164
165 let target_dir = self.temp.path().join("target").display().to_string();
167 cmd.env("CARGO_TARGET_DIR", &target_dir);
168
169 cmd.env_remove("CARGO_ENCODED_RUSTFLAGS");
172 cmd.env_remove("RUSTFLAGS");
173 cmd.env_remove("RUSTC");
176 cmd
177 }
178
179 fn get_rustc_version(&self) -> String {
180 let mut run_cmd = self.prepare_command("rustc");
181 run_cmd.args(&["-q", "--", "--version"]);
182 run_cmd
183 .output()
184 .ok()
185 .and_then(|o| String::from_utf8(o.stdout).ok())
186 .unwrap_or_else(|| "unknown rustc version".into())
187 }
188
189 fn get_sysroot(&self) -> Option<String> {
190 let mut sysroot_cmd = self.prepare_command("rustc");
191 sysroot_cmd.args(&["-q", "--", "--print", "sysroot"]);
192 sysroot_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok())
193 }
194
195 fn get_toolchain(&self) -> Option<String> {
196 let sysroot = self.get_sysroot()?;
197 Path::new(sysroot.trim())
198 .file_name()
199 .and_then(|s| s.to_str())
200 .map(|s| s.to_string())
201 }
202
203 pub(crate) fn is_target_installed(&self, target: &str) -> Option<bool> {
204 let sysroot = self.get_sysroot()?;
205 let target_libdir_path =
206 Path::new(sysroot.trim()).join("lib").join("rustlib").join(target).join("lib");
207 Some(target_libdir_path.exists())
208 }
209
210 fn try_build(&self) -> Result<(), Option<String>> {
211 let Ok(result) = self.prepare_command("build").output() else { return Err(None) };
212 if !result.status.success() {
213 return Err(Some(String::from_utf8_lossy(&result.stderr).into()));
214 }
215 Ok(())
216 }
217}
218
219fn check_wasm_toolchain_installed(
220 cargo_command: CargoCommand,
221) -> Result<CargoCommandVersioned, String> {
222 let target = RuntimeTarget::Wasm;
223 let rustc_target = target.rustc_target(&cargo_command);
224
225 let dummy_crate = DummyCrate::new(&cargo_command, target, false);
226 let toolchain = dummy_crate.get_toolchain().unwrap_or("<unknown>".to_string());
227
228 if let Err(error) = dummy_crate.try_build() {
229 let basic_error_message = colorize_error_message(
230 &format!("Rust WASM target for toolchain {toolchain} is not properly installed; please install it!")
231 );
232 return match error {
233 None => Err(basic_error_message),
234 Some(error) if error.contains(&format!("the `{rustc_target}` target may not be installed")) => {
235 Err(colorize_error_message(&format!("Cannot compile the WASM runtime: the `{rustc_target}` target is not installed!\n\
236 You can install it with `rustup target add {rustc_target} --toolchain {toolchain}` if you're using `rustup`.")))
237 },
238 Some(ref error) if error.contains("linker `rust-lld` not found") =>
240 Err(colorize_error_message("Cannot compile the WASM runtime: `rust-lld` not found!")),
241 Some(error) => Err(format!(
242 "{}\n\n{}\n{}\n{}{}\n",
243 basic_error_message,
244 colorize_aux_message("Further error information:"),
245 colorize_aux_message(&"-".repeat(60)),
246 error,
247 colorize_aux_message(&"-".repeat(60)),
248 ))
249 };
250 }
251
252 let version = dummy_crate.get_rustc_version();
253
254 let target = RuntimeTarget::new();
255 assert!(target == RuntimeTarget::Wasm);
256 if target.rustc_target_build_std(&cargo_command).is_some() {
257 if let Some(sysroot) = dummy_crate.get_sysroot() {
258 let src_path =
259 Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust");
260 if !src_path.exists() {
261 let toolchain = dummy_crate.get_toolchain().unwrap_or("<toolchain>".to_string());
262 return Err(colorize_error_message(
263 &format!("Cannot compile the WASM runtime: no standard library sources found at {}!\n\
264 You can install them with `rustup component add rust-src --toolchain {toolchain}` if you're using `rustup`.", src_path.display()),
265 ))
266 }
267 }
268 }
269
270 if cargo_command.supports_wasm32v1_none_target() &&
271 !cargo_command.is_wasm32v1_none_target_installed()
272 {
273 build_helper::warning!("You are building WASM runtime using `wasm32-unknown-unknown` target, although Rust >= 1.84 supports `wasm32v1-none` target!");
274 build_helper::warning!("You can install it with `rustup target add wasm32v1-none --toolchain {toolchain}` if you're using `rustup`.");
275 build_helper::warning!("After installing `wasm32v1-none` target, you must rebuild WASM runtime from scratch, use `cargo clean` before building.");
276 }
277
278 Ok(CargoCommandVersioned::new(cargo_command, version))
279}