From 23382db1b4de64ca035d91d7c87d83c63a474bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 19 Oct 2020 14:45:09 +0200 Subject: [PATCH] Make `wasm-builder` print the rustc version (#7351) * Make `wasm-builder` print the rustc version This makes `wasm-builder` print the rustc version that is being used to compile the project. This is rather useful, because people can check faster if the used rustc version is maybe known for being broken with Substrate. * Apply suggestions from code review * Add some comments --- substrate/utils/wasm-builder/src/lib.rs | 53 +++++++-- .../utils/wasm-builder/src/prerequisites.rs | 104 +++++++++++++----- .../utils/wasm-builder/src/wasm_project.rs | 28 +++-- 3 files changed, 142 insertions(+), 43 deletions(-) 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) => {},