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]