diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 347e63342a6..2e16988e76b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -55,11 +55,25 @@ jobs: exit 1 fi + - name: "Test: Wasm-builder recommended toolchain matches rust-toolchain.toml" + run: | + TOOLCHAIN=$(grep 'channel' rust-toolchain.toml | cut -d '"' -f 2) + CARGO_TOOLCHAIN="utils/wasm-builder/src/cargo_toolchain.rs" + if ! grep -q "$TOOLCHAIN" $CARGO_TOOLCHAIN; then + echo "Please update PINNED_NIGHTLY_TOOLCHAIN constant in $CARGO_TOOLCHAIN to match rust-toolchain.toml." + exit 1 + fi + - name: "Install: Rust stable toolchain" uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + components: llvm-tools - name: "Check: Compiling gstd on stable" - run: cargo +stable check -p gstd + run: | + cargo +stable check -p gstd --target wasm32-unknown-unknown + cargo +stable check --manifest-path utils/wasm-builder/test-program/Cargo.toml - name: "Check: crates-io packages" run: cargo run --release -p crates-io check diff --git a/examples/out-of-memory/Cargo.toml b/examples/out-of-memory/Cargo.toml index 3b039d61913..c075c8d99e3 100644 --- a/examples/out-of-memory/Cargo.toml +++ b/examples/out-of-memory/Cargo.toml @@ -8,7 +8,7 @@ homepage.workspace = true repository.workspace = true [dependencies] -gstd.workspace = true +gstd = { workspace = true, features = ["oom-handler"] } [build-dependencies] gear-wasm-builder.workspace = true diff --git a/examples/out-of-memory/build.rs b/examples/out-of-memory/build.rs index bd6ee0ee6b9..dfebd15e251 100644 --- a/examples/out-of-memory/build.rs +++ b/examples/out-of-memory/build.rs @@ -16,6 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use gear_wasm_builder::WasmBuilder; + fn main() { - gear_wasm_builder::build(); + // We are forcing recommended nightly toolchain due to the need to compile this + // program with `oom-handler` feature. The WASM binary of this program is then + // used by the `oom_handler_works` pallet test. + WasmBuilder::new() + .exclude_features(vec!["std"]) + .with_forced_recommended_toolchain() // NOTE: Don't use this in production programs! + .build(); } diff --git a/utils/wasm-builder/src/builder_error.rs b/utils/wasm-builder/src/builder_error.rs index 5c71a01dd64..6e405bb7891 100644 --- a/utils/wasm-builder/src/builder_error.rs +++ b/utils/wasm-builder/src/builder_error.rs @@ -40,4 +40,11 @@ pub enum BuilderError { #[error("cargo toolchain is invalid `{0}`")] CargoToolchainInvalid(String), + + #[error( + "recommended toolchain `{0}` not found, install it using the command:\n\ + rustup toolchain install {0} --component llvm-tools --target wasm32-unknown-unknown\n\n\ + after installation, do not forget to set `channel = \"{0}\"` in `rust-toolchain.toml` file" + )] + RecommendedToolchainNotFound(String), } diff --git a/utils/wasm-builder/src/cargo_command.rs b/utils/wasm-builder/src/cargo_command.rs index 7c2a9bd62aa..2115955e900 100644 --- a/utils/wasm-builder/src/cargo_command.rs +++ b/utils/wasm-builder/src/cargo_command.rs @@ -18,7 +18,7 @@ use crate::cargo_toolchain::Toolchain; use anyhow::{ensure, Context, Result}; -use std::{path::PathBuf, process::Command}; +use std::{env, path::PathBuf, process::Command}; use crate::builder_error::BuilderError; @@ -31,6 +31,8 @@ pub struct CargoCommand { target_dir: PathBuf, features: Vec, toolchain: Toolchain, + check_recommended_toolchain: bool, + force_recommended_toolchain: bool, paths_to_remap: Vec<(PathBuf, &'static str)>, } @@ -44,7 +46,9 @@ impl CargoCommand { rustc_flags: vec!["-C", "link-arg=--import-memory", "-C", "linker-plugin-lto"], target_dir: "target".into(), features: vec![], - toolchain: Toolchain::nightly(), + toolchain: Toolchain::try_from_rustup().expect("Failed to get toolchain from rustup"), + check_recommended_toolchain: false, + force_recommended_toolchain: false, paths_to_remap: vec![], } } @@ -71,9 +75,14 @@ impl CargoCommand { self.features = features.into(); } - /// Set toolchain. - pub(crate) fn set_toolchain(&mut self, toolchain: Toolchain) { - self.toolchain = toolchain; + /// Sets whether to check the version of the recommended toolchain. + pub(crate) fn set_check_recommended_toolchain(&mut self, check_recommended_toolchain: bool) { + self.check_recommended_toolchain = check_recommended_toolchain; + } + + /// Sets whether to force the version of the recommended toolchain. + pub(crate) fn set_force_recommended_toolchain(&mut self, force_recommended_toolchain: bool) { + self.force_recommended_toolchain = force_recommended_toolchain; } /// Set paths to remap. @@ -85,10 +94,23 @@ impl CargoCommand { /// Execute the `cargo` command with invoking supplied arguments. pub fn run(&self) -> Result<()> { + if self.check_recommended_toolchain { + self.toolchain.check_recommended_toolchain()?; + } + + let toolchain = if self.force_recommended_toolchain { + Toolchain::recommended_nightly() + } else { + self.toolchain.clone() + }; + let mut cargo = Command::new(&self.path); + if self.force_recommended_toolchain { + self.clean_up_environment(&mut cargo); + } cargo .arg("run") - .arg(self.toolchain.nightly_toolchain_str().as_ref()) + .arg(toolchain.raw_toolchain_str().as_ref()) .arg("cargo") .arg("rustc") .arg("--target=wasm32-unknown-unknown") @@ -135,6 +157,45 @@ impl CargoCommand { Ok(()) } + fn clean_up_environment(&self, command: &mut Command) { + // Inherited build script environment variables must be removed + // so that they cannot change the behavior of the cargo package manager. + + // https://doc.rust-lang.org/cargo/reference/environment-variables.html + // `RUSTC_WRAPPER` and `RUSTC_WORKSPACE_WRAPPER` are not removed due to tools like sccache. + const INHERITED_ENV_VARS: &[&str] = &[ + "CARGO", + "CARGO_MANIFEST_DIR", + "CARGO_MANIFEST_LINKS", + "CARGO_MAKEFLAGS", + "OUT_DIR", + "TARGET", + "HOST", + "NUM_JOBS", + "OPT_LEVEL", + "PROFILE", + "RUSTC", + "RUSTDOC", + "RUSTC_LINKER", + "CARGO_ENCODED_RUSTFLAGS", + ]; + + for env_var in INHERITED_ENV_VARS { + command.env_remove(env_var); + } + + const INHERITED_ENV_VARS_WITH_PREFIX: &[&str] = + &["CARGO_FEATURE_", "CARGO_CFG_", "DEP_", "CARGO_PKG_"]; + + for (env_var, _) in env::vars() { + for prefix in INHERITED_ENV_VARS_WITH_PREFIX { + if env_var.starts_with(prefix) { + command.env_remove(&env_var); + } + } + } + } + fn remove_cargo_encoded_rustflags(&self, command: &mut Command) { // substrate's wasm-builder removes these vars so do we // check its source for details diff --git a/utils/wasm-builder/src/cargo_toolchain.rs b/utils/wasm-builder/src/cargo_toolchain.rs index d0612079427..5fc6ab8fccb 100644 --- a/utils/wasm-builder/src/cargo_toolchain.rs +++ b/utils/wasm-builder/src/cargo_toolchain.rs @@ -35,9 +35,12 @@ static TOOLCHAIN_CHANNELS: &[&str] = &[ pub(crate) struct Toolchain(String); impl Toolchain { - /// Returns `Toolchain` representing the most recent nightly version. - pub fn nightly() -> Self { - Self("nightly".into()) + /// This is a version of nightly toolchain, tested on our CI. + const PINNED_NIGHTLY_TOOLCHAIN: &'static str = "nightly-2023-09-05"; + + /// Returns `Toolchain` representing the recommended nightly version. + pub fn recommended_nightly() -> Self { + Self(Self::PINNED_NIGHTLY_TOOLCHAIN.into()) } /// Fetches `Toolchain` via rustup. @@ -93,28 +96,13 @@ impl Toolchain { self.0.as_str().into() } - /// Returns toolchain string specification without target triple - /// and with raw `` substituted by `nightly`. - /// - /// `nightly[-]` - /// - /// ` = YYYY-MM-DD` - pub fn nightly_toolchain_str(&'_ self) -> Cow<'_, str> { - if !self.is_nightly() { - let date_start_idx = self - .0 - .find('-') - .unwrap_or_else(|| self.raw_toolchain_str().len()); - let mut toolchain_str = self.0.clone(); - toolchain_str.replace_range(..date_start_idx, "nightly"); - toolchain_str.into() - } else { - self.raw_toolchain_str() - } - } - - // Returns bool representing nightly toolchain. - fn is_nightly(&self) -> bool { - self.0.starts_with("nightly") + /// Checks whether the toolchain is recommended. + pub fn check_recommended_toolchain(&self) -> Result<()> { + let toolchain = Self::PINNED_NIGHTLY_TOOLCHAIN; + anyhow::ensure!( + self.raw_toolchain_str() == toolchain, + BuilderError::RecommendedToolchainNotFound(toolchain.into()), + ); + Ok(()) } } diff --git a/utils/wasm-builder/src/lib.rs b/utils/wasm-builder/src/lib.rs index 1cb0d880e24..47606f07e71 100644 --- a/utils/wasm-builder/src/lib.rs +++ b/utils/wasm-builder/src/lib.rs @@ -20,7 +20,7 @@ #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] #![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] -use crate::{cargo_command::CargoCommand, cargo_toolchain::Toolchain, wasm_project::WasmProject}; +use crate::{cargo_command::CargoCommand, wasm_project::WasmProject}; use anyhow::{Context, Result}; use gmeta::{Metadata, MetadataRepr}; use regex::Regex; @@ -82,6 +82,24 @@ impl WasmBuilder { self } + /// Add check of recommended toolchain. + pub fn with_recommended_toolchain(mut self) -> Self { + self.cargo.set_check_recommended_toolchain(true); + self + } + + /// Force the recommended toolchain to be used, but do not check whether the + /// current toolchain is recommended. + /// + /// NOTE: For internal use only, not recommended for production programs. + /// + /// An example usage can be found in `examples/out-of-memory/build.rs`. + #[doc(hidden)] + pub fn with_forced_recommended_toolchain(mut self) -> Self { + self.cargo.set_force_recommended_toolchain(true); + self + } + /// Build the program and produce an output WASM binary. pub fn build(self) { if env::var("__GEAR_WASM_BUILDER_NO_BUILD").is_ok() { @@ -101,7 +119,6 @@ impl WasmBuilder { fn build_project(mut self) -> Result<()> { self.wasm_project.generate()?; - self.cargo.set_toolchain(Toolchain::try_from_rustup()?); self.cargo .set_manifest_path(self.wasm_project.manifest_path()); self.cargo.set_target_dir(self.wasm_project.target_dir()); @@ -229,3 +246,27 @@ pub fn build_metawasm() { .exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec()) .build(); } + +/// Shorthand function to be used in `build.rs`. +pub fn recommended_nightly() { + WasmBuilder::new() + .exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec()) + .with_recommended_toolchain() + .build(); +} + +/// Shorthand function to be used in `build.rs`. +pub fn recommended_nightly_with_metadata() { + WasmBuilder::with_meta(T::repr()) + .exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec()) + .with_recommended_toolchain() + .build(); +} + +/// Shorthand function to be used in `build.rs`. +pub fn recommended_nightly_metawasm() { + WasmBuilder::new_metawasm() + .exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec()) + .with_recommended_toolchain() + .build(); +} diff --git a/utils/wasm-builder/tests/smoke.rs b/utils/wasm-builder/tests/smoke.rs index a4ce501aea2..f67cbceebb0 100644 --- a/utils/wasm-builder/tests/smoke.rs +++ b/utils/wasm-builder/tests/smoke.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use gear_wasm_builder::TARGET; -use std::{fs, process::Command}; +use std::{fs, process::Command, sync::OnceLock}; struct CargoRunner(Command); @@ -26,6 +26,13 @@ impl CargoRunner { Self(Command::new("cargo")) } + fn stable() -> Self { + let mut cmd = Command::new("cargo"); + cmd.arg("+stable"); + + Self(cmd) + } + fn args(mut self, args: [&str; SIZE]) -> Self { self.0.args(args); self @@ -42,35 +49,65 @@ impl CargoRunner { } } +fn install_stable_toolchain() { + static STABLE_TOOLCHAIN: OnceLock<()> = OnceLock::new(); + + STABLE_TOOLCHAIN.get_or_init(|| { + let status = Command::new("rustup") + .arg("toolchain") + .arg("install") + .arg("stable") + .arg("--component") + .arg("llvm-tools") + .arg("--target") + .arg("wasm32-unknown-unknown") + .status() + .expect("rustup run error"); + assert!(status.success()); + }); +} + #[ignore] #[test] fn test_debug() { + install_stable_toolchain(); + CargoRunner::new().args(["test"]).run(); + CargoRunner::stable().args(["test"]).run(); } #[ignore] #[test] fn build_debug() { - CargoRunner::new().args(["build"]).run() + install_stable_toolchain(); + + CargoRunner::new().args(["build"]).run(); + CargoRunner::stable().args(["build"]).run(); } #[ignore] #[test] fn test_release() { - CargoRunner::new().args(["test", "--release"]).run() + install_stable_toolchain(); + + CargoRunner::new().args(["test", "--release"]).run(); + CargoRunner::stable().args(["test", "--release"]).run(); } #[ignore] #[test] fn build_release() { - CargoRunner::new().args(["build", "--release"]).run() + install_stable_toolchain(); + + CargoRunner::new().args(["build", "--release"]).run(); + CargoRunner::stable().args(["build", "--release"]).run(); } #[test] fn build_release_for_target() { CargoRunner::new() .args(["build", "--release", "--target", TARGET]) - .run() + .run(); } #[ignore]