Skip to content

Commit

Permalink
Support stable rust for compiling the runtime (paritytech#13580)
Browse files Browse the repository at this point in the history
* Support stable rust for compiling the runtime

This pull request brings support for compiling the runtime with stable Rust. This requires at least
rust 1.68.0 to work on stable. The code is written in a way that it is backwards compatible and
should automatically work when someone compiles with 1.68.0+ stable.

* We always support nightlies!

* 🤦

* Sort by version

* Review feedback

* Review feedback

* Fix version parsing

* Apply suggestions from code review

Co-authored-by: Koute <koute@users.noreply.github.com>

---------

Co-authored-by: Koute <koute@users.noreply.github.com>
  • Loading branch information
2 people authored and pgherveou committed Mar 30, 2023
1 parent 2f1d4fa commit 0b985aa
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 51 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions primitives/io/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repository = "https://github.com/paritytech/substrate/"
description = "I/O for Substrate runtimes"
documentation = "https://docs.rs/sp-io"
readme = "README.md"
build = "build.rs"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
Expand Down Expand Up @@ -37,6 +38,9 @@ ed25519-dalek = { version = "1.0.1", default-features = false, optional = true }
# Force the usage of ed25519, this is being used in `ed25519-dalek`.
ed25519 = { version = "1.5.2", optional = true }

[build-dependencies]
rustversion = "1.0.6"

[features]
default = ["std"]
std = [
Expand Down
27 changes: 27 additions & 0 deletions primitives/io/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#[rustversion::before(1.68)]
fn main() {
if !cfg!(feature = "std") {
println!("cargo:rustc-cfg=enable_alloc_error_handler");
}
}

#[rustversion::since(1.68)]
fn main() {}
4 changes: 2 additions & 2 deletions primitives/io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(alloc_error_handler))]
#![cfg_attr(enable_alloc_error_handler, feature(alloc_error_handler))]
#![cfg_attr(
feature = "std",
doc = "Substrate runtime standard library as compiled when linked with Rust's standard library."
Expand Down Expand Up @@ -1643,7 +1643,7 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! {
}

/// A default OOM handler for WASM environment.
#[cfg(all(not(feature = "disable_oom"), not(feature = "std")))]
#[cfg(all(not(feature = "disable_oom"), enable_alloc_error_handler))]
#[alloc_error_handler]
pub fn oom(_: core::alloc::Layout) -> ! {
#[cfg(feature = "improved_panic_error_reporting")]
Expand Down
11 changes: 8 additions & 3 deletions utils/wasm-builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@ Wasm builder requires the following prerequisites for building the Wasm binary:

- rust nightly + `wasm32-unknown-unknown` toolchain

If a specific rust nightly is installed with `rustup`, it is important that the wasm target is installed
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`.
or

- rust stable and version at least 1.68.0 + `wasm32-unknown-unknown` toolchain

If a specific rust is installed with `rustup`, it is important that the wasm target is
installed as well. For example if installing the rust 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`.

License: Apache-2.0
124 changes: 81 additions & 43 deletions utils/wasm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@
//!
//! - rust nightly + `wasm32-unknown-unknown` toolchain
//!
//! If a specific rust nightly is installed with `rustup`, it is important that the wasm target is
//! installed as well. For example if installing the rust nightly from 20.02.2020 using `rustup
//! or
//!
//! - rust stable and version at least 1.68.0 + `wasm32-unknown-unknown` toolchain
//!
//! If a specific rust is installed with `rustup`, it is important that the wasm target is
//! installed as well. For example if installing the rust 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`.
Expand All @@ -111,9 +115,11 @@ use std::{
path::{Path, PathBuf},
process::Command,
};
use version::Version;

mod builder;
mod prerequisites;
mod version;
mod wasm_project;

pub use builder::{WasmBuilder, WasmBuilderSelectProject};
Expand Down Expand Up @@ -172,53 +178,59 @@ fn copy_file_if_changed(src: PathBuf, dst: PathBuf) {
}
}

/// Get a cargo command that compiles with nightly
fn get_nightly_cargo() -> CargoCommand {
/// Get a cargo command that should be used to invoke the compilation.
fn get_cargo_command() -> CargoCommand {
let env_cargo =
CargoCommand::new(&env::var("CARGO").expect("`CARGO` env variable is always set by cargo"));
let default_cargo = CargoCommand::new("cargo");
let rustup_run_nightly = CargoCommand::new_with_args("rustup", &["run", "nightly", "cargo"]);
let wasm_toolchain = env::var(WASM_BUILD_TOOLCHAIN).ok();

// First check if the user requested a specific toolchain
if let Some(cmd) = wasm_toolchain.and_then(|t| get_rustup_nightly(Some(t))) {
if let Some(cmd) =
wasm_toolchain.map(|t| CargoCommand::new_with_args("rustup", &["run", &t, "cargo"]))
{
cmd
} else if env_cargo.is_nightly() {
} else if env_cargo.supports_substrate_wasm_env() {
env_cargo
} else if default_cargo.is_nightly() {
} else if default_cargo.supports_substrate_wasm_env() {
default_cargo
} else if rustup_run_nightly.is_nightly() {
rustup_run_nightly
} else {
// If no command before provided us with a nightly compiler, we try to search one
// with rustup. If that fails as well, we return the default cargo and let the prequisities
// check fail.
get_rustup_nightly(None).unwrap_or(default_cargo)
// If no command before provided us with a cargo that supports our Substrate wasm env, we
// try to search one with rustup. If that fails as well, we return the default cargo and let
// the prequisities check fail.
get_rustup_command().unwrap_or(default_cargo)
}
}

/// Get a nightly from rustup. If `selected` is `Some(_)`, a `CargoCommand` using the given
/// nightly is returned.
fn get_rustup_nightly(selected: Option<String>) -> Option<CargoCommand> {
/// Get the newest rustup command that supports our Substrate wasm env.
///
/// Stable versions are always favored over nightly versions even if the nightly versions are
/// newer.
fn get_rustup_command() -> Option<CargoCommand> {
let host = format!("-{}", env::var("HOST").expect("`HOST` is always set by cargo"));

let version = match selected {
Some(selected) => selected,
None => {
let output = Command::new("rustup").args(&["toolchain", "list"]).output().ok()?.stdout;
let lines = output.as_slice().lines();
let output = Command::new("rustup").args(&["toolchain", "list"]).output().ok()?.stdout;
let lines = output.as_slice().lines();

let mut versions = Vec::new();
for line in lines.filter_map(|l| l.ok()) {
let rustup_version = line.trim_end_matches(&host);

let cmd = CargoCommand::new_with_args("rustup", &["run", &rustup_version, "cargo"]);

let mut latest_nightly = None;
for line in lines.filter_map(|l| l.ok()) {
if line.starts_with("nightly-") && line.ends_with(&host) {
// Rustup prints them sorted
latest_nightly = Some(line.clone());
}
}
if !cmd.supports_substrate_wasm_env() {
continue
}

let Some(cargo_version) = cmd.version() else { continue; };

latest_nightly?.trim_end_matches(&host).into()
},
};
versions.push((cargo_version, rustup_version.to_string()));
}

// Sort by the parsed version to get the latest version (greatest version) at the end of the
// vec.
versions.sort_by_key(|v| v.0);
let version = &versions.last()?.1;

Some(CargoCommand::new_with_args("rustup", &["run", &version, "cargo"]))
}
Expand All @@ -228,17 +240,23 @@ fn get_rustup_nightly(selected: Option<String>) -> Option<CargoCommand> {
struct CargoCommand {
program: String,
args: Vec<String>,
version: Option<Version>,
}

impl CargoCommand {
fn new(program: &str) -> Self {
CargoCommand { program: program.into(), args: Vec::new() }
let version = Self::extract_version(program, &[]);

CargoCommand { program: program.into(), args: Vec::new(), version }
}

fn new_with_args(program: &str, args: &[&str]) -> Self {
let version = Self::extract_version(program, args);

CargoCommand {
program: program.into(),
args: args.iter().map(ToString::to_string).collect(),
version,
}
}

Expand All @@ -248,20 +266,40 @@ impl CargoCommand {
cmd
}

/// Check if the supplied cargo command is a nightly version
fn is_nightly(&self) -> bool {
fn extract_version(program: &str, args: &[&str]) -> Option<Version> {
let version = Command::new(program)
.args(args)
.arg("--version")
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())?;

Version::extract(&version)
}

/// Returns the version of this cargo command or `None` if it failed to extract the version.
fn version(&self) -> Option<Version> {
self.version
}

/// Check if the supplied cargo command supports our Substrate wasm environment.
///
/// This means that either the cargo version is at minimum 1.68.0 or this is a nightly cargo.
///
/// Assumes that cargo version matches the rustc version.
fn supports_substrate_wasm_env(&self) -> bool {
// `RUSTC_BOOTSTRAP` tells a stable compiler to behave like a nightly. So, when this env
// variable is set, we can assume that whatever rust compiler we have, it is a nightly
// compiler. For "more" information, see:
// https://github.com/rust-lang/rust/blob/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libsyntax/feature_gate/check.rs#L891
env::var("RUSTC_BOOTSTRAP").is_ok() ||
self.command()
.arg("--version")
.output()
.map_err(|_| ())
.and_then(|o| String::from_utf8(o.stdout).map_err(|_| ()))
.unwrap_or_default()
.contains("-nightly")
if env::var("RUSTC_BOOTSTRAP").is_ok() {
return true
}

let Some(version) = self.version() else { return false };

// Check if major and minor are greater or equal than 1.68 or this is a nightly.
version.major > 1 || (version.major == 1 && version.minor >= 68) || version.is_nightly
}
}

Expand Down
9 changes: 6 additions & 3 deletions utils/wasm-builder/src/prerequisites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ fn print_error_message(message: &str) -> String {
///
/// Returns the versioned cargo command on success.
pub(crate) fn check() -> Result<CargoCommandVersioned, String> {
let cargo_command = crate::get_nightly_cargo();
let cargo_command = crate::get_cargo_command();

if !cargo_command.is_nightly() {
return Err(print_error_message("Rust nightly not installed, please install it!"))
if !cargo_command.supports_substrate_wasm_env() {
return Err(print_error_message(
"Cannot compile the WASM runtime: no compatible Rust compiler found!\n\
Install at least Rust 1.68.0 or a recent nightly version.",
))
}

check_wasm_toolchain_installed(cargo_command)
Expand Down
Loading

0 comments on commit 0b985aa

Please sign in to comment.