diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index a3e80d3561..aa63e9596e 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -92,7 +92,7 @@ //! as well. For example if installing the rust nightly from 20.02.2020 using `rustup install nightly-2020-02-20`, //! the wasm target needs to be installed as well `rustup target add wasm32-unknown-unknown --toolchain nightly-2020-02-20`. -use std::{env, fs, path::PathBuf, process::{Command, self}, io::BufRead}; +use std::{env, fs, path::{PathBuf, Path}, process::{Command, self}, io::BufRead}; mod prerequisites; mod wasm_project; @@ -158,14 +158,18 @@ pub fn build_project_with_default_rustflags( panic!("'{}' no valid path to a `Cargo.toml`!", cargo_manifest.display()); } - if let Some(err_msg) = prerequisites::check() { - eprintln!("{}", err_msg); - process::exit(1); - } + let cargo_cmd = match prerequisites::check() { + Ok(cmd) => cmd, + Err(err_msg) => { + eprintln!("{}", err_msg); + process::exit(1); + }, + }; let (wasm_binary, bloaty) = wasm_project::create_and_compile( &cargo_manifest, default_rustflags, + cargo_cmd, ); let (wasm_binary, wasm_binary_bloaty) = if let Some(wasm_binary) = wasm_binary { @@ -181,7 +185,7 @@ pub fn build_project_with_default_rustflags( }; write_file_if_changed( - file_name.into(), + file_name, format!( r#" pub const WASM_BINARY: Option<&[u8]> = Some(include_bytes!("{wasm_binary}")); @@ -199,9 +203,10 @@ fn check_skip_build() -> bool { } /// Write to the given `file` if the `content` is different. -fn write_file_if_changed(file: PathBuf, content: String) { - if fs::read_to_string(&file).ok().as_ref() != Some(&content) { - fs::write(&file, content).unwrap_or_else(|_| panic!("Writing `{}` can not fail!", file.display())); +fn write_file_if_changed(file: impl AsRef, content: impl AsRef) { + if fs::read_to_string(file.as_ref()).ok().as_deref() != Some(content.as_ref()) { + fs::write(file.as_ref(), content.as_ref()) + .unwrap_or_else(|_| panic!("Writing `{}` can not fail!", file.as_ref().display())); } } @@ -268,7 +273,7 @@ fn get_rustup_nightly(selected: Option) -> Option { Some(CargoCommand::new_with_args("rustup", &["run", &version, "cargo"])) } -/// Builder for cargo commands +/// Wraps a specific command which represents a cargo invocation. #[derive(Debug)] struct CargoCommand { program: String, @@ -310,6 +315,34 @@ impl CargoCommand { } } +/// Wraps a [`CargoCommand`] and the version of `rustc` the cargo command uses. +struct CargoCommandVersioned { + command: CargoCommand, + version: String, +} + +impl CargoCommandVersioned { + fn new(command: CargoCommand, version: String) -> Self { + Self { + command, + version, + } + } + + /// Returns the `rustc` version. + fn rustc_version(&self) -> &str { + &self.version + } +} + +impl std::ops::Deref for CargoCommandVersioned { + type Target = CargoCommand; + + fn deref(&self) -> &CargoCommand { + &self.command + } +} + /// Returns `true` when color output is enabled. fn color_output_enabled() -> bool { env::var(crate::WASM_BUILD_NO_COLOR).is_err() diff --git a/substrate/utils/wasm-builder/src/prerequisites.rs b/substrate/utils/wasm-builder/src/prerequisites.rs index 2a9801744c..3df2707d1d 100644 --- a/substrate/utils/wasm-builder/src/prerequisites.rs +++ b/substrate/utils/wasm-builder/src/prerequisites.rs @@ -15,7 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fs; +use crate::{CargoCommandVersioned, CargoCommand, write_file_if_changed}; + +use std::{fs, path::Path}; use tempfile::tempdir; use ansi_term::Color; @@ -31,33 +33,33 @@ fn print_error_message(message: &str) -> String { /// Checks that all prerequisites are installed. /// -/// # Returns -/// Returns `None` if everything was found and `Some(ERR_MSG)` if something could not be found. -pub fn check() -> Option { - if !check_nightly_installed(){ - return Some(print_error_message("Rust nightly not installed, please install it!")) +/// Returns the versioned cargo command on success. +pub(crate) fn check() -> Result { + let cargo_command = crate::get_nightly_cargo(); + + if !cargo_command.is_nightly() { + return Err(print_error_message("Rust nightly not installed, please install it!")) } - check_wasm_toolchain_installed() + check_wasm_toolchain_installed(cargo_command) } -fn check_nightly_installed() -> bool { - crate::get_nightly_cargo().is_nightly() -} +/// Create the project that will be used to check that the wasm toolchain is installed and to +/// extract the rustc version. +fn create_check_toolchain_project(project_dir: &Path) { + let lib_rs_file = project_dir.join("src/lib.rs"); + let main_rs_file = project_dir.join("src/main.rs"); + let build_rs_file = project_dir.join("build.rs"); + let manifest_path = project_dir.join("Cargo.toml"); -fn check_wasm_toolchain_installed() -> Option { - let temp = tempdir().expect("Creating temp dir does not fail; qed"); - fs::create_dir_all(temp.path().join("src")).expect("Creating src dir does not fail; qed"); - - let test_file = temp.path().join("src/lib.rs"); - let manifest_path = temp.path().join("Cargo.toml"); - - fs::write(&manifest_path, + write_file_if_changed( + &manifest_path, r#" [package] name = "wasm-test" version = "1.0.0" edition = "2018" + build = "build.rs" [lib] name = "wasm_test" @@ -65,27 +67,78 @@ fn check_wasm_toolchain_installed() -> Option { [workspace] "#, - ).expect("Writing wasm-test manifest does not fail; qed"); - fs::write(&test_file, "pub fn test() {}") - .expect("Writing to the test file does not fail; qed"); + ); + write_file_if_changed(lib_rs_file, "pub fn test() {}"); + + // We want to know the rustc version of the rustc that is being used by our cargo command. + // The cargo command is determined by some *very* complex algorithm to find the cargo command + // that supports nightly. + // The best solution would be if there is a `cargo rustc --version` command, which sadly + // doesn't exists. So, the only available way of getting the rustc version is to build a project + // and capture the rustc version in this build process. This `build.rs` is exactly doing this. + // It gets the rustc version by calling `rustc --version` and exposing it in the `RUSTC_VERSION` + // environment variable. + write_file_if_changed( + build_rs_file, + r#" + fn main() { + let rustc_cmd = std::env::var("RUSTC").ok().unwrap_or_else(|| "rustc".into()); + + let rustc_version = std::process::Command::new(rustc_cmd) + .arg("--version") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()); + + println!( + "cargo:rustc-env=RUSTC_VERSION={}", + rustc_version.unwrap_or_else(|| "unknown rustc version".into()), + ); + } + "# + ); + // Just prints the `RURSTC_VERSION` environment variable that is being created by the + // `build.rs` script. + write_file_if_changed( + main_rs_file, + r#" + fn main() { + println!("{}", env!("RUSTC_VERSION")); + } + "# + ); +} + +fn check_wasm_toolchain_installed( + cargo_command: CargoCommand, +) -> Result { + let temp = tempdir().expect("Creating temp dir does not fail; qed"); + fs::create_dir_all(temp.path().join("src")).expect("Creating src dir does not fail; qed"); + create_check_toolchain_project(temp.path()); let err_msg = print_error_message("Rust WASM toolchain not installed, please install it!"); - let manifest_path = manifest_path.display().to_string(); - - let mut build_cmd = crate::get_nightly_cargo().command(); + let manifest_path = temp.path().join("Cargo.toml").display().to_string(); + let mut build_cmd = cargo_command.command(); build_cmd.args(&["build", "--target=wasm32-unknown-unknown", "--manifest-path", &manifest_path]); if super::color_output_enabled() { build_cmd.arg("--color=always"); } + let mut run_cmd = cargo_command.command(); + run_cmd.args(&["run", "--manifest-path", &manifest_path]); + build_cmd .output() .map_err(|_| err_msg.clone()) .and_then(|s| if s.status.success() { - Ok(()) + let version = run_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok()); + Ok(CargoCommandVersioned::new( + cargo_command, + version.unwrap_or_else(|| "unknown rustc version".into()), + )) } else { match String::from_utf8(s.stderr) { Ok(ref err) if err.contains("linker `rust-lld` not found") => { @@ -105,5 +158,4 @@ fn check_wasm_toolchain_installed() -> Option { } } ) - .err() } diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 860265febe..c27af71988 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::write_file_if_changed; +use crate::{write_file_if_changed, CargoCommandVersioned}; use std::{ fs, path::{Path, PathBuf}, borrow::ToOwned, process, env, collections::HashSet, @@ -34,6 +34,17 @@ use fs2::FileExt; use itertools::Itertools; +/// Colorize an info message. +/// +/// Returns the colorized message. +fn colorize_info_message(message: &str) -> String { + if super::color_output_enabled() { + ansi_term::Color::Yellow.bold().paint(message).to_string() + } else { + message.into() + } +} + /// Holds the path to the bloaty WASM binary. pub struct WasmBinaryBloaty(PathBuf); @@ -110,9 +121,10 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { /// /// # Returns /// The path to the compact WASM binary and the bloaty WASM binary. -pub fn create_and_compile( +pub(crate) fn create_and_compile( cargo_manifest: &Path, default_rustflags: &str, + cargo_cmd: CargoCommandVersioned, ) -> (Option, WasmBinaryBloaty) { let wasm_workspace_root = get_wasm_workspace_root(); let wasm_workspace = wasm_workspace_root.join("wbuild"); @@ -125,7 +137,7 @@ pub fn create_and_compile( let project = create_project(cargo_manifest, &wasm_workspace, &crate_metadata); create_wasm_workspace_project(&wasm_workspace, &crate_metadata.workspace_root); - build_project(&project, default_rustflags); + build_project(&project, default_rustflags, cargo_cmd); let (wasm_binary, bloaty) = compact_wasm_file( &project, cargo_manifest, @@ -423,7 +435,7 @@ fn create_project(cargo_manifest: &Path, wasm_workspace: &Path, crate_metadata: write_file_if_changed( project_folder.join("src/lib.rs"), - "#![no_std] pub use wasm_project::*;".into(), + "#![no_std] pub use wasm_project::*;", ); if let Some(crate_lock_file) = find_cargo_lock(cargo_manifest) { @@ -452,9 +464,9 @@ fn is_release_build() -> bool { } /// Build the project to create the WASM binary. -fn build_project(project: &Path, default_rustflags: &str) { +fn build_project(project: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned) { let manifest_path = project.join("Cargo.toml"); - let mut build_cmd = crate::get_nightly_cargo().command(); + let mut build_cmd = cargo_cmd.command(); let rustflags = format!( "-C link-arg=--export-table {} {}", @@ -476,7 +488,9 @@ fn build_project(project: &Path, default_rustflags: &str) { build_cmd.arg("--release"); }; - println!("Executing build command: {:?}", build_cmd); + println!("{}", colorize_info_message("Information that should be included in a bug report.")); + println!("{} {:?}", colorize_info_message("Executing build command:"), build_cmd); + println!("{} {}", colorize_info_message("Using rustc version:"), cargo_cmd.rustc_version()); match build_cmd.status().map(|s| s.success()) { Ok(true) => {},