referrerpolicy=no-referrer-when-downgrade

substrate_wasm_builder/
prerequisites.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use 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
29/// Colorizes an error message, if color output is enabled.
30fn 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
38/// Colorizes an auxiliary message, if color output is enabled.
39fn 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
47/// Checks that all prerequisites are installed.
48///
49/// Returns the versioned cargo command on success.
50pub(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	/// Creates a minimal dummy crate.
88	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		// Chdir to temp to avoid including project's .cargo/config.toml
153		// by accident - it can happen in some CI environments.
154		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		// manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock
166		let target_dir = self.temp.path().join("target").display().to_string();
167		cmd.env("CARGO_TARGET_DIR", &target_dir);
168
169		// Make sure the host's flags aren't used here, e.g. if an alternative linker is specified
170		// in the RUSTFLAGS then the check we do here will break unless we clear these.
171		cmd.env_remove("CARGO_ENCODED_RUSTFLAGS");
172		cmd.env_remove("RUSTFLAGS");
173		// Make sure if we're called from within a `build.rs` the host toolchain won't override a
174		// rustup toolchain we've picked.
175		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			// Apparently this can happen when we're running on a non Tier 1 platform.
239			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}