feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,392 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
|
||||
use crate::RuntimeTarget;
|
||||
|
||||
/// Extra information when generating the `metadata-hash`.
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
pub(crate) struct MetadataExtraInfo {
|
||||
pub decimals: u8,
|
||||
pub token_symbol: String,
|
||||
}
|
||||
|
||||
/// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env.
|
||||
fn get_manifest_dir() -> PathBuf {
|
||||
env::var("CARGO_MANIFEST_DIR")
|
||||
.expect("`CARGO_MANIFEST_DIR` is always set for `build.rs` files; qed")
|
||||
.into()
|
||||
}
|
||||
|
||||
/// First step of the [`WasmBuilder`] to select the project to build.
|
||||
pub struct WasmBuilderSelectProject {
|
||||
/// This parameter just exists to make it impossible to construct
|
||||
/// this type outside of this crate.
|
||||
_ignore: (),
|
||||
}
|
||||
|
||||
impl WasmBuilderSelectProject {
|
||||
/// Use the current project as project for building the WASM binary.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CARGO_MANIFEST_DIR` variable is not set. This variable
|
||||
/// is always set by `Cargo` in `build.rs` files.
|
||||
pub fn with_current_project(self) -> WasmBuilder {
|
||||
WasmBuilder {
|
||||
rust_flags: Vec::new(),
|
||||
file_name: None,
|
||||
project_cargo_toml: get_manifest_dir().join("Cargo.toml"),
|
||||
features_to_enable: Vec::new(),
|
||||
disable_runtime_version_section_check: false,
|
||||
export_heap_base: false,
|
||||
import_memory: false,
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
enable_metadata_hash: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use the given `path` as project for building the WASM binary.
|
||||
///
|
||||
/// Returns an error if the given `path` does not points to a `Cargo.toml`.
|
||||
pub fn with_project(self, path: impl Into<PathBuf>) -> Result<WasmBuilder, &'static str> {
|
||||
let path = path.into();
|
||||
|
||||
if path.ends_with("Cargo.toml") && path.exists() {
|
||||
Ok(WasmBuilder {
|
||||
rust_flags: Vec::new(),
|
||||
file_name: None,
|
||||
project_cargo_toml: path,
|
||||
features_to_enable: Vec::new(),
|
||||
disable_runtime_version_section_check: false,
|
||||
export_heap_base: false,
|
||||
import_memory: false,
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
enable_metadata_hash: None,
|
||||
})
|
||||
} else {
|
||||
Err("Project path must point to the `Cargo.toml` of the project")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The builder for building a wasm binary.
|
||||
///
|
||||
/// The builder itself is separated into multiple structs to make the setup type safe.
|
||||
///
|
||||
/// Building a wasm binary:
|
||||
///
|
||||
/// 1. Call [`WasmBuilder::new`] to create a new builder.
|
||||
/// 2. Select the project to build using the methods of [`WasmBuilderSelectProject`].
|
||||
/// 3. Set additional `RUST_FLAGS` or a different name for the file containing the WASM code using
|
||||
/// methods of [`WasmBuilder`].
|
||||
/// 4. Build the WASM binary using [`Self::build`].
|
||||
pub struct WasmBuilder {
|
||||
/// Flags that should be appended to `RUST_FLAGS` env variable.
|
||||
rust_flags: Vec<String>,
|
||||
/// The name of the file that is being generated in `OUT_DIR`.
|
||||
///
|
||||
/// Defaults to `wasm_binary.rs`.
|
||||
file_name: Option<String>,
|
||||
/// The path to the `Cargo.toml` of the project that should be built
|
||||
/// for wasm.
|
||||
project_cargo_toml: PathBuf,
|
||||
/// Features that should be enabled when building the wasm binary.
|
||||
features_to_enable: Vec<String>,
|
||||
/// Should the builder not check that the `runtime_version` section exists in the wasm binary?
|
||||
disable_runtime_version_section_check: bool,
|
||||
|
||||
/// Whether `__heap_base` should be exported (WASM-only).
|
||||
export_heap_base: bool,
|
||||
/// Whether `--import-memory` should be added to the link args (WASM-only).
|
||||
import_memory: bool,
|
||||
|
||||
/// Whether to enable the metadata hash generation.
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
enable_metadata_hash: Option<MetadataExtraInfo>,
|
||||
}
|
||||
|
||||
impl WasmBuilder {
|
||||
/// Create a new instance of the builder.
|
||||
pub fn new() -> WasmBuilderSelectProject {
|
||||
WasmBuilderSelectProject { _ignore: () }
|
||||
}
|
||||
|
||||
/// Build the WASM binary using the recommended default values.
|
||||
///
|
||||
/// This is the same as calling:
|
||||
/// ```no_run
|
||||
/// bizinikiwi_wasm_builder::WasmBuilder::new()
|
||||
/// .with_current_project()
|
||||
/// .import_memory()
|
||||
/// .export_heap_base()
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn build_using_defaults() {
|
||||
WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.import_memory()
|
||||
.export_heap_base()
|
||||
.build();
|
||||
}
|
||||
|
||||
/// Init the wasm builder with the recommended default values.
|
||||
///
|
||||
/// In contrast to [`Self::build_using_defaults`] it does not build the WASM binary directly.
|
||||
///
|
||||
/// This is the same as calling:
|
||||
/// ```no_run
|
||||
/// bizinikiwi_wasm_builder::WasmBuilder::new()
|
||||
/// .with_current_project()
|
||||
/// .import_memory()
|
||||
/// .export_heap_base();
|
||||
/// ```
|
||||
pub fn init_with_defaults() -> Self {
|
||||
WasmBuilder::new().with_current_project().import_memory().export_heap_base()
|
||||
}
|
||||
|
||||
/// Enable exporting `__heap_base` as global variable in the WASM binary.
|
||||
///
|
||||
/// This adds `-C link-arg=--export=__heap_base` to `RUST_FLAGS`.
|
||||
pub fn export_heap_base(mut self) -> Self {
|
||||
self.export_heap_base = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the name of the file that will be generated in `OUT_DIR`.
|
||||
///
|
||||
/// This file needs to be included to get access to the build WASM binary.
|
||||
///
|
||||
/// If this function is not called, `file_name` defaults to `wasm_binary.rs`
|
||||
pub fn set_file_name(mut self, file_name: impl Into<String>) -> Self {
|
||||
self.file_name = Some(file_name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Instruct the linker to import the memory into the WASM binary.
|
||||
///
|
||||
/// This adds `-C link-arg=--import-memory` to `RUST_FLAGS`.
|
||||
pub fn import_memory(mut self) -> Self {
|
||||
self.import_memory = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Append the given `flag` to `RUST_FLAGS`.
|
||||
///
|
||||
/// `flag` is appended as is, so it needs to be a valid flag.
|
||||
pub fn append_to_rust_flags(mut self, flag: impl Into<String>) -> Self {
|
||||
self.rust_flags.push(flag.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable the given feature when building the wasm binary.
|
||||
///
|
||||
/// `feature` needs to be a valid feature that is defined in the project `Cargo.toml`.
|
||||
pub fn enable_feature(mut self, feature: impl Into<String>) -> Self {
|
||||
self.features_to_enable.push(feature.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable generation of the metadata hash.
|
||||
///
|
||||
/// This will compile the runtime once, fetch the metadata, build the metadata hash and
|
||||
/// then compile again with the env `RUNTIME_METADATA_HASH` set. For more information
|
||||
/// about the metadata hash see [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html).
|
||||
///
|
||||
/// - `token_symbol`: The symbol of the main native token of the chain.
|
||||
/// - `decimals`: The number of decimals of the main native token.
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
pub fn enable_metadata_hash(mut self, token_symbol: impl Into<String>, decimals: u8) -> Self {
|
||||
self.enable_metadata_hash =
|
||||
Some(MetadataExtraInfo { token_symbol: token_symbol.into(), decimals });
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable the check for the `runtime_version` wasm section.
|
||||
///
|
||||
/// By default the `wasm-builder` will ensure that the `runtime_version` section will
|
||||
/// exists in the build wasm binary. This `runtime_version` section is used to get the
|
||||
/// `RuntimeVersion` without needing to call into the wasm binary. However, for some
|
||||
/// use cases (like tests) you may want to disable this check.
|
||||
pub fn disable_runtime_version_section_check(mut self) -> Self {
|
||||
self.disable_runtime_version_section_check = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the WASM binary.
|
||||
pub fn build(mut self) {
|
||||
let target = RuntimeTarget::new();
|
||||
|
||||
if target == RuntimeTarget::Wasm {
|
||||
if self.export_heap_base {
|
||||
self.rust_flags.push("-C link-arg=--export=__heap_base".into());
|
||||
}
|
||||
|
||||
if self.import_memory {
|
||||
self.rust_flags.push("-C link-arg=--import-memory".into());
|
||||
}
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!"));
|
||||
let file_path =
|
||||
out_dir.join(self.file_name.clone().unwrap_or_else(|| "wasm_binary.rs".into()));
|
||||
|
||||
if check_skip_build() {
|
||||
// If we skip the build, we still want to make sure to be called when an env variable
|
||||
// changes
|
||||
generate_rerun_if_changed_instructions();
|
||||
|
||||
provide_dummy_wasm_binary_if_not_exist(&file_path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
build_project(
|
||||
target,
|
||||
file_path,
|
||||
self.project_cargo_toml,
|
||||
self.rust_flags.join(" "),
|
||||
self.features_to_enable,
|
||||
self.file_name,
|
||||
!self.disable_runtime_version_section_check,
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
self.enable_metadata_hash,
|
||||
);
|
||||
|
||||
// As last step we need to generate our `rerun-if-changed` stuff. If a build fails, we don't
|
||||
// want to spam the output!
|
||||
generate_rerun_if_changed_instructions();
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the name of the skip build environment variable for the current crate.
|
||||
fn generate_crate_skip_build_env_name() -> String {
|
||||
format!(
|
||||
"SKIP_{}_WASM_BUILD",
|
||||
env::var("CARGO_PKG_NAME")
|
||||
.expect("Package name is set")
|
||||
.to_uppercase()
|
||||
.replace('-', "_"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks if the build of the WASM binary should be skipped.
|
||||
fn check_skip_build() -> bool {
|
||||
env::var(crate::SKIP_BUILD_ENV).is_ok() ||
|
||||
env::var(generate_crate_skip_build_env_name()).is_ok() ||
|
||||
// If we are running in docs.rs, let's skip building.
|
||||
// https://docs.rs/about/builds#detecting-docsrs
|
||||
env::var("DOCS_RS").is_ok()
|
||||
}
|
||||
|
||||
/// Provide a dummy WASM binary if there doesn't exist one.
|
||||
fn provide_dummy_wasm_binary_if_not_exist(file_path: &Path) {
|
||||
if !file_path.exists() {
|
||||
crate::write_file_if_changed(
|
||||
file_path,
|
||||
"pub const WASM_BINARY_PATH: Option<&str> = None;\
|
||||
pub const WASM_BINARY: Option<&[u8]> = None;\
|
||||
pub const WASM_BINARY_BLOATY: Option<&[u8]> = None;",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the `rerun-if-changed` instructions for cargo to make sure that the WASM binary is
|
||||
/// rebuilt when needed.
|
||||
fn generate_rerun_if_changed_instructions() {
|
||||
// Make sure that the `build.rs` is called again if one of the following env variables changes.
|
||||
println!("cargo:rerun-if-env-changed={}", crate::SKIP_BUILD_ENV);
|
||||
println!("cargo:rerun-if-env-changed={}", crate::FORCE_WASM_BUILD_ENV);
|
||||
println!("cargo:rerun-if-env-changed={}", generate_crate_skip_build_env_name());
|
||||
}
|
||||
|
||||
/// Build the currently built project as wasm binary.
|
||||
///
|
||||
/// The current project is determined by using the `CARGO_MANIFEST_DIR` environment variable.
|
||||
///
|
||||
/// `file_name` - The name + path of the file being generated. The file contains the
|
||||
/// constant `WASM_BINARY`, which contains the built wasm binary.
|
||||
///
|
||||
/// `project_cargo_toml` - The path to the `Cargo.toml` of the project that should be built.
|
||||
///
|
||||
/// `default_rustflags` - Default `RUSTFLAGS` that will always be set for the build.
|
||||
///
|
||||
/// `features_to_enable` - Features that should be enabled for the project.
|
||||
///
|
||||
/// `wasm_binary_name` - The optional wasm binary name that is extended with
|
||||
/// `.compact.compressed.wasm`. If `None`, the project name will be used.
|
||||
///
|
||||
/// `check_for_runtime_version_section` - Should the wasm binary be checked for the
|
||||
/// `runtime_version` section?
|
||||
fn build_project(
|
||||
target: RuntimeTarget,
|
||||
file_name: PathBuf,
|
||||
project_cargo_toml: PathBuf,
|
||||
default_rustflags: String,
|
||||
features_to_enable: Vec<String>,
|
||||
wasm_binary_name: Option<String>,
|
||||
check_for_runtime_version_section: bool,
|
||||
#[cfg(feature = "metadata-hash")] enable_metadata_hash: Option<MetadataExtraInfo>,
|
||||
) {
|
||||
// Init jobserver as soon as possible
|
||||
crate::wasm_project::get_jobserver();
|
||||
let cargo_cmd = match crate::prerequisites::check(target) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(err_msg) => {
|
||||
eprintln!("{err_msg}");
|
||||
process::exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
let (wasm_binary, bloaty) = crate::wasm_project::create_and_compile(
|
||||
target,
|
||||
&project_cargo_toml,
|
||||
&default_rustflags,
|
||||
cargo_cmd,
|
||||
features_to_enable,
|
||||
wasm_binary_name,
|
||||
check_for_runtime_version_section,
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
enable_metadata_hash,
|
||||
);
|
||||
|
||||
let (wasm_binary, wasm_binary_bloaty) = if let Some(wasm_binary) = wasm_binary {
|
||||
(wasm_binary.wasm_binary_path_escaped(), bloaty.bloaty_path_escaped())
|
||||
} else {
|
||||
(bloaty.bloaty_path_escaped(), bloaty.bloaty_path_escaped())
|
||||
};
|
||||
|
||||
crate::write_file_if_changed(
|
||||
file_name,
|
||||
format!(
|
||||
r#"
|
||||
pub const WASM_BINARY_PATH: Option<&str> = Some("{wasm_binary_path}");
|
||||
pub const WASM_BINARY: Option<&[u8]> = Some(include_bytes!("{wasm_binary}"));
|
||||
pub const WASM_BINARY_BLOATY: Option<&[u8]> = Some(include_bytes!("{wasm_binary_bloaty}"));
|
||||
"#,
|
||||
wasm_binary_path = wasm_binary,
|
||||
wasm_binary = wasm_binary,
|
||||
wasm_binary_bloaty = wasm_binary_bloaty,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Wasm builder is a utility for building a project as a Wasm binary
|
||||
//!
|
||||
//! The Wasm builder is a tool that integrates the process of building the WASM binary of your
|
||||
//! project into the main `cargo` build process.
|
||||
//!
|
||||
//! ## Project setup
|
||||
//!
|
||||
//! A project that should be compiled as a Wasm binary needs to:
|
||||
//!
|
||||
//! 1. Add a `build.rs` file.
|
||||
//! 2. Add `wasm-builder` as dependency into `build-dependencies`.
|
||||
//!
|
||||
//! The `build.rs` file needs to contain the following code:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use bizinikiwi_wasm_builder::WasmBuilder;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! // Builds the WASM binary using the recommended defaults.
|
||||
//! // If you need more control, you can call `new` or `init_with_defaults`.
|
||||
//! WasmBuilder::build_using_defaults();
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! As the final step, you need to add the following to your project:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
//! ```
|
||||
//!
|
||||
//! This will include the generated Wasm binary as two constants `WASM_BINARY` and
|
||||
//! `WASM_BINARY_BLOATY`. The former is a compact Wasm binary and the latter is the Wasm binary as
|
||||
//! being generated by the compiler. Both variables have `Option<&'static [u8]>` as type.
|
||||
//! Additionally it will create the `WASM_BINARY_PATH` which is the path to the WASM blob on the
|
||||
//! filesystem.
|
||||
//!
|
||||
//! ### Feature
|
||||
//!
|
||||
//! Wasm builder supports to enable cargo features while building the Wasm binary. By default it
|
||||
//! will enable all features in the wasm build that are enabled for the native build except the
|
||||
//! `default` and `std` features. Besides that, wasm builder supports the special `runtime-wasm`
|
||||
//! feature. This `runtime-wasm` feature will be enabled by the wasm builder when it compiles the
|
||||
//! Wasm binary. If this feature is not present, it will not be enabled.
|
||||
//!
|
||||
//! ## Environment variables
|
||||
//!
|
||||
//! By using environment variables, you can configure which Wasm binaries are built and how:
|
||||
//!
|
||||
//! - `BIZINIKIWI_RUNTIME_TARGET` - Sets the target for building runtime. Supported values are `wasm`
|
||||
//! or `riscv` (experimental, do not use it in production!). By default the target is equal to
|
||||
//! `wasm`.
|
||||
//! - `SKIP_WASM_BUILD` - Skips building any Wasm binary. This is useful when only native should be
|
||||
//! recompiled. If this is the first run and there doesn't exist a Wasm binary, this will set both
|
||||
//! variables to `None`.
|
||||
//! - `WASM_BUILD_TYPE` - Sets the build type for building Wasm binaries. Supported values are
|
||||
//! `release` or `debug`. By default the build type is equal to the build type used by the main
|
||||
//! build.
|
||||
//! - `FORCE_WASM_BUILD` - Can be set to force a Wasm build. On subsequent calls the value of the
|
||||
//! variable needs to change. As wasm-builder instructs `cargo` to watch for file changes this
|
||||
//! environment variable should only be required in certain circumstances.
|
||||
//! - `WASM_BUILD_RUSTFLAGS` - Extend `RUSTFLAGS` given to `cargo build` while building the wasm
|
||||
//! binary.
|
||||
//! - `WASM_BUILD_NO_COLOR` - Disable color output of the wasm build.
|
||||
//! - `WASM_TARGET_DIRECTORY` - Will copy any build Wasm binary to the given directory. The path
|
||||
//! needs to be absolute.
|
||||
//! - `WASM_BUILD_TOOLCHAIN` - The toolchain that should be used to build the Wasm binaries. The
|
||||
//! format needs to be the same as used by cargo, e.g. `nightly-2024-12-26`.
|
||||
//! - `WASM_BUILD_WORKSPACE_HINT` - Hint the workspace that is being built. This is normally not
|
||||
//! required as we walk up from the target directory until we find a `Cargo.toml`. If the target
|
||||
//! directory is changed for the build, this environment variable can be used to point to the
|
||||
//! actual workspace.
|
||||
//! - `WASM_BUILD_STD` - Sets whether the Rust's standard library crates (`core` and `alloc`) will
|
||||
//! also be built. This is necessary to make sure the standard library crates only use the exact
|
||||
//! WASM feature set that our executor supports. Enabled by default for RISC-V target and WASM
|
||||
//! target (but only if Rust < 1.84). Disabled by default for WASM target and Rust >= 1.84.
|
||||
//! - `WASM_BUILD_CARGO_ARGS` - This can take a string as space separated list of `cargo` arguments.
|
||||
//! It was added specifically for the use case of enabling JSON diagnostic messages during the
|
||||
//! build phase, to be used by IDEs that parse them, but it might be useful for other cases too.
|
||||
//! - `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to
|
||||
//! prevent network access. Useful in offline environments.
|
||||
//!
|
||||
//! Each project can be skipped individually by using the environment variable
|
||||
//! `SKIP_PROJECT_NAME_WASM_BUILD`. Where `PROJECT_NAME` needs to be replaced by the name of the
|
||||
//! cargo project, e.g. `kitchensink-runtime` will be `NODE_RUNTIME`.
|
||||
//!
|
||||
//! ## Prerequisites:
|
||||
//!
|
||||
//! Wasm builder requires the following prerequisites for building the Wasm binary:
|
||||
//! - Rust >= 1.68 and Rust < 1.84:
|
||||
//! - `wasm32-unknown-unknown` target
|
||||
//! - `rust-src` component
|
||||
//! - Rust >= 1.84:
|
||||
//! - `wasm32v1-none` target
|
||||
//!
|
||||
//! 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
|
||||
//! 26.12.2024 using `rustup install nightly-2024-12-26`, the WASM target
|
||||
//! (`wasm32-unknown-unknown` or `wasm32v1-none`) needs to be installed as well
|
||||
//! `rustup target add wasm32-unknown-unknown --toolchain nightly-2024-12-26`.
|
||||
//! To install the `rust-src` component, use `rustup component add rust-src
|
||||
//! --toolchain nightly-2024-12-26`.
|
||||
|
||||
use prerequisites::DummyCrate;
|
||||
use std::{
|
||||
env, fs,
|
||||
io::BufRead,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use version::Version;
|
||||
|
||||
mod builder;
|
||||
#[cfg(feature = "metadata-hash")]
|
||||
mod metadata_hash;
|
||||
mod prerequisites;
|
||||
mod version;
|
||||
mod wasm_project;
|
||||
|
||||
pub use builder::{WasmBuilder, WasmBuilderSelectProject};
|
||||
|
||||
/// Environment variable that tells us to skip building the wasm binary.
|
||||
const SKIP_BUILD_ENV: &str = "SKIP_WASM_BUILD";
|
||||
|
||||
/// Environment variable that tells us whether we should avoid network requests
|
||||
const OFFLINE: &str = "CARGO_NET_OFFLINE";
|
||||
|
||||
/// Environment variable to force a certain build type when building the wasm binary.
|
||||
/// Expects "debug", "release" or "production" as value.
|
||||
///
|
||||
/// When unset the WASM binary uses the same build type as the main cargo build with
|
||||
/// the exception of a debug build: In this case the wasm build defaults to `release` in
|
||||
/// order to avoid a slowdown when not explicitly requested.
|
||||
const WASM_BUILD_TYPE_ENV: &str = "WASM_BUILD_TYPE";
|
||||
|
||||
/// Environment variable to extend the `RUSTFLAGS` variable given to the wasm build.
|
||||
const WASM_BUILD_RUSTFLAGS_ENV: &str = "WASM_BUILD_RUSTFLAGS";
|
||||
|
||||
/// Environment variable to set the target directory to copy the final wasm binary.
|
||||
///
|
||||
/// The directory needs to be an absolute path.
|
||||
const WASM_TARGET_DIRECTORY: &str = "WASM_TARGET_DIRECTORY";
|
||||
|
||||
/// Environment variable to disable color output of the wasm build.
|
||||
const WASM_BUILD_NO_COLOR: &str = "WASM_BUILD_NO_COLOR";
|
||||
|
||||
/// Environment variable to set the toolchain used to compile the wasm binary.
|
||||
const WASM_BUILD_TOOLCHAIN: &str = "WASM_BUILD_TOOLCHAIN";
|
||||
|
||||
/// Environment variable that makes sure the WASM build is triggered.
|
||||
const FORCE_WASM_BUILD_ENV: &str = "FORCE_WASM_BUILD";
|
||||
|
||||
/// Environment variable that hints the workspace we are building.
|
||||
const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT";
|
||||
|
||||
/// Environment variable to set whether we'll build `core`/`alloc`.
|
||||
const WASM_BUILD_STD: &str = "WASM_BUILD_STD";
|
||||
|
||||
/// Environment variable to set additional cargo arguments that might be useful
|
||||
/// during the build phase.
|
||||
const WASM_BUILD_CARGO_ARGS: &str = "WASM_BUILD_CARGO_ARGS";
|
||||
|
||||
/// The target to use for the runtime. Valid values are `wasm` (default) or `riscv`.
|
||||
const RUNTIME_TARGET: &str = "BIZINIKIWI_RUNTIME_TARGET";
|
||||
|
||||
/// Write to the given `file` if the `content` is different.
|
||||
fn write_file_if_changed(file: impl AsRef<Path>, content: impl AsRef<str>) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy `src` to `dst` if the `dst` does not exist or is different.
|
||||
fn copy_file_if_changed(src: PathBuf, dst: PathBuf) {
|
||||
let src_file = fs::read_to_string(&src).ok();
|
||||
let dst_file = fs::read_to_string(&dst).ok();
|
||||
|
||||
if src_file != dst_file {
|
||||
fs::copy(&src, &dst).unwrap_or_else(|_| {
|
||||
panic!("Copying `{}` to `{}` can not fail; qed", src.display(), dst.display())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a cargo command that should be used to invoke the compilation.
|
||||
fn get_cargo_command(target: RuntimeTarget) -> 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 wasm_toolchain = env::var(WASM_BUILD_TOOLCHAIN).ok();
|
||||
|
||||
// First check if the user requested a specific toolchain
|
||||
if let Some(cmd) =
|
||||
wasm_toolchain.map(|t| CargoCommand::new_with_args("rustup", &["run", &t, "cargo"]))
|
||||
{
|
||||
cmd
|
||||
} else if env_cargo.supports_bizinikiwi_runtime_env(target) {
|
||||
env_cargo
|
||||
} else if default_cargo.supports_bizinikiwi_runtime_env(target) {
|
||||
default_cargo
|
||||
} else {
|
||||
// If no command before provided us with a cargo that supports our Bizinikiwi wasm env, we
|
||||
// try to search one with rustup. If that fails as well, we return the default cargo and let
|
||||
// the perquisites check fail.
|
||||
get_rustup_command(target).unwrap_or(default_cargo)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the newest rustup command that supports compiling a runtime.
|
||||
///
|
||||
/// Stable versions are always favored over nightly versions even if the nightly versions are
|
||||
/// newer.
|
||||
fn get_rustup_command(target: RuntimeTarget) -> Option<CargoCommand> {
|
||||
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()) {
|
||||
// Split by a space to get rid of e.g. " (default)" at the end.
|
||||
let rustup_version = line.split(" ").next().unwrap();
|
||||
let cmd = CargoCommand::new_with_args("rustup", &["run", &rustup_version, "cargo"]);
|
||||
|
||||
if !cmd.supports_bizinikiwi_runtime_env(target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(cargo_version) = cmd.version() else { continue };
|
||||
|
||||
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"]))
|
||||
}
|
||||
|
||||
/// Wraps a specific command which represents a cargo invocation.
|
||||
#[derive(Debug, Clone)]
|
||||
struct CargoCommand {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
version: Option<Version>,
|
||||
}
|
||||
|
||||
impl CargoCommand {
|
||||
fn new(program: &str) -> Self {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn command(&self) -> Command {
|
||||
let mut cmd = Command::new(&self.program);
|
||||
cmd.args(&self.args);
|
||||
cmd
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// Returns whether this version of the toolchain supports nightly features.
|
||||
fn supports_nightly_features(&self) -> bool {
|
||||
self.version.map_or(false, |version| version.is_nightly) ||
|
||||
env::var("RUSTC_BOOTSTRAP").is_ok()
|
||||
}
|
||||
|
||||
/// Check if the supplied cargo command supports our runtime environment.
|
||||
fn supports_bizinikiwi_runtime_env(&self, target: RuntimeTarget) -> bool {
|
||||
match target {
|
||||
RuntimeTarget::Wasm => self.supports_bizinikiwi_runtime_env_wasm(),
|
||||
RuntimeTarget::Riscv => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the supplied cargo command supports our Bizinikiwi 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_bizinikiwi_runtime_env_wasm(&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
|
||||
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
|
||||
}
|
||||
|
||||
/// Returns whether this version of the toolchain supports the `wasm32v1-none` target.
|
||||
fn supports_wasm32v1_none_target(&self) -> bool {
|
||||
self.version.map_or(false, |version| {
|
||||
// Check if major and minor are greater or equal than 1.84.
|
||||
version.major > 1 || (version.major == 1 && version.minor >= 84)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns whether the `wasm32v1-none` target is installed in this version of the toolchain.
|
||||
fn is_wasm32v1_none_target_installed(&self) -> bool {
|
||||
let dummy_crate = DummyCrate::new(self, RuntimeTarget::Wasm, true);
|
||||
dummy_crate.is_target_installed("wasm32v1-none").unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns whether the `wasm32v1-none` target is available in this version of the toolchain.
|
||||
fn is_wasm32v1_none_target_available(&self) -> bool {
|
||||
// Check if major and minor are greater or equal than 1.84 and that the `wasm32v1-none`
|
||||
// target is installed for this toolchain.
|
||||
self.supports_wasm32v1_none_target() && self.is_wasm32v1_none_target_installed()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a [`CargoCommand`] and the version of `rustc` the cargo command uses.
|
||||
#[derive(Clone)]
|
||||
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()
|
||||
}
|
||||
|
||||
/// Fetches a boolean environment variable. Will exit the process if the value is invalid.
|
||||
fn get_bool_environment_variable(name: &str) -> Option<bool> {
|
||||
let value = env::var_os(name)?;
|
||||
|
||||
// We're comparing `OsString`s here so we can't use a `match`.
|
||||
if value == "1" {
|
||||
Some(true)
|
||||
} else if value == "0" {
|
||||
Some(false)
|
||||
} else {
|
||||
build_helper::warning!(
|
||||
"the '{name}' environment variable has an invalid value; it must be either '1' or '0'",
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
enum RuntimeTarget {
|
||||
Wasm,
|
||||
Riscv,
|
||||
}
|
||||
|
||||
impl RuntimeTarget {
|
||||
/// Creates a new instance.
|
||||
fn new() -> Self {
|
||||
let Some(value) = env::var_os(RUNTIME_TARGET) else {
|
||||
return Self::Wasm;
|
||||
};
|
||||
|
||||
if value == "wasm" {
|
||||
Self::Wasm
|
||||
} else if value == "riscv" {
|
||||
Self::Riscv
|
||||
} else {
|
||||
build_helper::warning!(
|
||||
"RUNTIME_TARGET environment variable must be set to either \"wasm\" or \"riscv\""
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Figures out the target parameter value for rustc.
|
||||
fn rustc_target(self, cargo_command: &CargoCommand) -> String {
|
||||
match self {
|
||||
RuntimeTarget::Wasm =>
|
||||
if cargo_command.is_wasm32v1_none_target_available() {
|
||||
"wasm32v1-none".into()
|
||||
} else {
|
||||
"wasm32-unknown-unknown".into()
|
||||
},
|
||||
RuntimeTarget::Riscv => {
|
||||
let path = polkavm_linker::target_json_32_path().expect("riscv not found");
|
||||
path.into_os_string().into_string().unwrap()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Figures out the target directory name used by cargo.
|
||||
fn rustc_target_dir(self, cargo_command: &CargoCommand) -> &'static str {
|
||||
match self {
|
||||
RuntimeTarget::Wasm =>
|
||||
if cargo_command.is_wasm32v1_none_target_available() {
|
||||
"wasm32v1-none".into()
|
||||
} else {
|
||||
"wasm32-unknown-unknown".into()
|
||||
},
|
||||
RuntimeTarget::Riscv => "riscv32emac-unknown-none-polkavm",
|
||||
}
|
||||
}
|
||||
|
||||
/// Figures out the build-std argument.
|
||||
fn rustc_target_build_std(self, cargo_command: &CargoCommand) -> Option<&'static str> {
|
||||
if !crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or_else(
|
||||
|| match self {
|
||||
RuntimeTarget::Wasm => !cargo_command.is_wasm32v1_none_target_available(),
|
||||
RuntimeTarget::Riscv => true,
|
||||
},
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// This is a nightly-only flag.
|
||||
|
||||
// We only build `core` and `alloc` crates since wasm-builder disables `std` featue for
|
||||
// runtime. Thus the runtime is `#![no_std]` crate.
|
||||
|
||||
Some("build-std=core,alloc")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::builder::MetadataExtraInfo;
|
||||
use codec::{Decode, Encode};
|
||||
use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed};
|
||||
use merkleized_metadata::{generate_metadata_digest, ExtraInfo};
|
||||
use pezsc_executor::WasmExecutor;
|
||||
use pezsp_core::traits::{CallContext, CodeExecutor, RuntimeCode, WrappedRuntimeCode};
|
||||
use std::path::Path;
|
||||
|
||||
/// The host functions that we provide when calling into the wasm file.
|
||||
///
|
||||
/// Any other host function will return an error.
|
||||
type HostFunctions = (
|
||||
// The allocator functions.
|
||||
pezsp_io::allocator::HostFunctions,
|
||||
// Logging is good to have for debugging issues.
|
||||
pezsp_io::logging::HostFunctions,
|
||||
// Give access to the "state", actually the state will be empty, but some chains put constants
|
||||
// into the state and this would panic at metadata generation. Thus, we give them an empty
|
||||
// state to not panic.
|
||||
pezsp_io::storage::HostFunctions,
|
||||
// The hashing functions.
|
||||
pezsp_io::hashing::HostFunctions,
|
||||
);
|
||||
|
||||
/// Generate the metadata hash.
|
||||
///
|
||||
/// The metadata hash is generated as specced in
|
||||
/// [RFC78](https://polkadot-fellows.github.io/RFCs/approved/0078-merkleized-metadata.html).
|
||||
///
|
||||
/// Returns the metadata hash.
|
||||
pub fn generate_metadata_hash(wasm: &Path, extra_info: MetadataExtraInfo) -> [u8; 32] {
|
||||
pezsp_tracing::try_init_simple();
|
||||
|
||||
let wasm = std::fs::read(wasm).expect("Wasm file was just created and should be readable.");
|
||||
|
||||
let executor = WasmExecutor::<HostFunctions>::builder()
|
||||
.with_allow_missing_host_functions(true)
|
||||
.build();
|
||||
|
||||
let runtime_code = RuntimeCode {
|
||||
code_fetcher: &WrappedRuntimeCode(wasm.into()),
|
||||
heap_pages: None,
|
||||
// The hash is only used for caching and thus, not that important for our use case here.
|
||||
hash: vec![1, 2, 3],
|
||||
};
|
||||
|
||||
let metadata = executor
|
||||
.call(
|
||||
&mut pezsp_io::TestExternalities::default().ext(),
|
||||
&runtime_code,
|
||||
"Metadata_metadata_at_version",
|
||||
&15u32.encode(),
|
||||
CallContext::Offchain,
|
||||
)
|
||||
.0
|
||||
.expect("`Metadata::metadata_at_version` should exist.");
|
||||
|
||||
let metadata = Option::<Vec<u8>>::decode(&mut &metadata[..])
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("Metadata V15 support is required.");
|
||||
|
||||
let metadata = RuntimeMetadataPrefixed::decode(&mut &metadata[..])
|
||||
.expect("Invalid encoded metadata?")
|
||||
.1;
|
||||
|
||||
let runtime_version = executor
|
||||
.call(
|
||||
&mut pezsp_io::TestExternalities::default().ext(),
|
||||
&runtime_code,
|
||||
"Core_version",
|
||||
&[],
|
||||
CallContext::Offchain,
|
||||
)
|
||||
.0
|
||||
.expect("`Core_version` should exist.");
|
||||
let runtime_version = pezsp_version::RuntimeVersion::decode(&mut &runtime_version[..])
|
||||
.expect("Invalid `RuntimeVersion` encoding");
|
||||
|
||||
let base58_prefix = extract_ss58_prefix(&metadata);
|
||||
|
||||
let extra_info = ExtraInfo {
|
||||
spec_version: runtime_version.spec_version,
|
||||
spec_name: runtime_version.spec_name.into(),
|
||||
base58_prefix,
|
||||
decimals: extra_info.decimals,
|
||||
token_symbol: extra_info.token_symbol,
|
||||
};
|
||||
|
||||
generate_metadata_digest(&metadata, extra_info)
|
||||
.expect("Failed to generate the metadata digest")
|
||||
.hash()
|
||||
}
|
||||
|
||||
/// Extract the `SS58` from the constants in the given `metadata`.
|
||||
fn extract_ss58_prefix(metadata: &RuntimeMetadata) -> u16 {
|
||||
let RuntimeMetadata::V15(ref metadata) = metadata else {
|
||||
panic!("Metadata version 15 required")
|
||||
};
|
||||
|
||||
let system = metadata
|
||||
.pallets
|
||||
.iter()
|
||||
.find(|p| p.name == "System")
|
||||
.expect("Each FRAME runtime has the `System` pallet; qed");
|
||||
|
||||
system
|
||||
.constants
|
||||
.iter()
|
||||
.find_map(|c| {
|
||||
(c.name == "SS58Prefix")
|
||||
.then(|| u16::decode(&mut &c.value[..]).expect("SS58 is an `u16`; qed"))
|
||||
})
|
||||
.expect("`SS58PREFIX` exists in the `System` constants; qed")
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{write_file_if_changed, CargoCommand, CargoCommandVersioned, RuntimeTarget};
|
||||
|
||||
use console::style;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// Colorizes an error message, if color output is enabled.
|
||||
fn colorize_error_message(message: &str) -> String {
|
||||
if super::color_output_enabled() {
|
||||
style(message).red().bold().to_string()
|
||||
} else {
|
||||
message.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Colorizes an auxiliary message, if color output is enabled.
|
||||
fn colorize_aux_message(message: &str) -> String {
|
||||
if super::color_output_enabled() {
|
||||
style(message).yellow().bold().to_string()
|
||||
} else {
|
||||
message.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that all prerequisites are installed.
|
||||
///
|
||||
/// Returns the versioned cargo command on success.
|
||||
pub(crate) fn check(target: RuntimeTarget) -> Result<CargoCommandVersioned, String> {
|
||||
let cargo_command = crate::get_cargo_command(target);
|
||||
match target {
|
||||
RuntimeTarget::Wasm => {
|
||||
if !cargo_command.supports_bizinikiwi_runtime_env(target) {
|
||||
return Err(colorize_error_message(
|
||||
"Cannot compile a 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)
|
||||
},
|
||||
RuntimeTarget::Riscv => {
|
||||
if !cargo_command.supports_bizinikiwi_runtime_env(target) {
|
||||
return Err(colorize_error_message(
|
||||
"Cannot compile a RISC-V runtime: no compatible Rust compiler found!\n\
|
||||
Install a toolchain from here and try again: https://github.com/paritytech/rustc-rv32e-toolchain/",
|
||||
));
|
||||
}
|
||||
|
||||
let dummy_crate = DummyCrate::new(&cargo_command, target, false);
|
||||
let version = dummy_crate.get_rustc_version();
|
||||
Ok(CargoCommandVersioned::new(cargo_command, version))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DummyCrate<'a> {
|
||||
cargo_command: &'a CargoCommand,
|
||||
temp: tempfile::TempDir,
|
||||
manifest_path: PathBuf,
|
||||
target: RuntimeTarget,
|
||||
ignore_target: bool,
|
||||
}
|
||||
|
||||
impl<'a> DummyCrate<'a> {
|
||||
/// Creates a minimal dummy crate.
|
||||
pub(crate) fn new(
|
||||
cargo_command: &'a CargoCommand,
|
||||
target: RuntimeTarget,
|
||||
ignore_target: bool,
|
||||
) -> Self {
|
||||
let temp = tempdir().expect("Creating temp dir does not fail; qed");
|
||||
let project_dir = temp.path();
|
||||
fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed");
|
||||
|
||||
let manifest_path = project_dir.join("Cargo.toml");
|
||||
match target {
|
||||
RuntimeTarget::Wasm => {
|
||||
write_file_if_changed(
|
||||
&manifest_path,
|
||||
r#"
|
||||
[package]
|
||||
name = "dummy-crate"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[workspace]
|
||||
"#,
|
||||
);
|
||||
|
||||
write_file_if_changed(
|
||||
project_dir.join("src/lib.rs"),
|
||||
r#"
|
||||
#![no_std]
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_: &core::panic::PanicInfo<'_>) -> ! {
|
||||
loop {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
},
|
||||
RuntimeTarget::Riscv => {
|
||||
write_file_if_changed(
|
||||
&manifest_path,
|
||||
r#"
|
||||
[package]
|
||||
name = "dummy-crate"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
"#,
|
||||
);
|
||||
|
||||
write_file_if_changed(
|
||||
project_dir.join("src/main.rs"),
|
||||
"#![allow(missing_docs)] fn main() {}",
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
DummyCrate { cargo_command, temp, manifest_path, target, ignore_target }
|
||||
}
|
||||
|
||||
fn prepare_command(&self, subcommand: &str) -> Command {
|
||||
let mut cmd = self.cargo_command.command();
|
||||
// Chdir to temp to avoid including project's .cargo/config.toml
|
||||
// by accident - it can happen in some CI environments.
|
||||
cmd.current_dir(&self.temp);
|
||||
cmd.arg(subcommand);
|
||||
if !self.ignore_target {
|
||||
cmd.arg(format!("--target={}", self.target.rustc_target(self.cargo_command)));
|
||||
}
|
||||
cmd.args(&["--manifest-path", &self.manifest_path.display().to_string()]);
|
||||
|
||||
if super::color_output_enabled() {
|
||||
cmd.arg("--color=always");
|
||||
}
|
||||
|
||||
// manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock
|
||||
let target_dir = self.temp.path().join("target").display().to_string();
|
||||
cmd.env("CARGO_TARGET_DIR", &target_dir);
|
||||
|
||||
// Make sure the host's flags aren't used here, e.g. if an alternative linker is specified
|
||||
// in the RUSTFLAGS then the check we do here will break unless we clear these.
|
||||
cmd.env_remove("CARGO_ENCODED_RUSTFLAGS");
|
||||
cmd.env_remove("RUSTFLAGS");
|
||||
// Make sure if we're called from within a `build.rs` the host toolchain won't override a
|
||||
// rustup toolchain we've picked.
|
||||
cmd.env_remove("RUSTC");
|
||||
cmd
|
||||
}
|
||||
|
||||
fn get_rustc_version(&self) -> String {
|
||||
let mut run_cmd = self.prepare_command("rustc");
|
||||
run_cmd.args(&["-q", "--", "--version"]);
|
||||
run_cmd
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.unwrap_or_else(|| "unknown rustc version".into())
|
||||
}
|
||||
|
||||
fn get_sysroot(&self) -> Option<String> {
|
||||
let mut sysroot_cmd = self.prepare_command("rustc");
|
||||
sysroot_cmd.args(&["-q", "--", "--print", "sysroot"]);
|
||||
sysroot_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
}
|
||||
|
||||
fn get_toolchain(&self) -> Option<String> {
|
||||
let sysroot = self.get_sysroot()?;
|
||||
Path::new(sysroot.trim())
|
||||
.file_name()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn is_target_installed(&self, target: &str) -> Option<bool> {
|
||||
let sysroot = self.get_sysroot()?;
|
||||
let target_libdir_path =
|
||||
Path::new(sysroot.trim()).join("lib").join("rustlib").join(target).join("lib");
|
||||
Some(target_libdir_path.exists())
|
||||
}
|
||||
|
||||
fn try_build(&self) -> Result<(), Option<String>> {
|
||||
let Ok(result) = self.prepare_command("build").output() else { return Err(None) };
|
||||
if !result.status.success() {
|
||||
return Err(Some(String::from_utf8_lossy(&result.stderr).into()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_wasm_toolchain_installed(
|
||||
cargo_command: CargoCommand,
|
||||
) -> Result<CargoCommandVersioned, String> {
|
||||
let target = RuntimeTarget::Wasm;
|
||||
let rustc_target = target.rustc_target(&cargo_command);
|
||||
|
||||
let dummy_crate = DummyCrate::new(&cargo_command, target, false);
|
||||
let toolchain = dummy_crate.get_toolchain().unwrap_or("<unknown>".to_string());
|
||||
|
||||
if let Err(error) = dummy_crate.try_build() {
|
||||
let basic_error_message = colorize_error_message(
|
||||
&format!("Rust WASM target for toolchain {toolchain} is not properly installed; please install it!")
|
||||
);
|
||||
return match error {
|
||||
None => Err(basic_error_message),
|
||||
Some(error) if error.contains(&format!("the `{rustc_target}` target may not be installed")) => {
|
||||
Err(colorize_error_message(&format!("Cannot compile the WASM runtime: the `{rustc_target}` target is not installed!\n\
|
||||
You can install it with `rustup target add {rustc_target} --toolchain {toolchain}` if you're using `rustup`.")))
|
||||
},
|
||||
// Apparently this can happen when we're running on a non Tier 1 platform.
|
||||
Some(ref error) if error.contains("linker `rust-lld` not found") =>
|
||||
Err(colorize_error_message("Cannot compile the WASM runtime: `rust-lld` not found!")),
|
||||
Some(error) => Err(format!(
|
||||
"{}\n\n{}\n{}\n{}{}\n",
|
||||
basic_error_message,
|
||||
colorize_aux_message("Further error information:"),
|
||||
colorize_aux_message(&"-".repeat(60)),
|
||||
error,
|
||||
colorize_aux_message(&"-".repeat(60)),
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
let version = dummy_crate.get_rustc_version();
|
||||
|
||||
let target = RuntimeTarget::new();
|
||||
assert!(target == RuntimeTarget::Wasm);
|
||||
if target.rustc_target_build_std(&cargo_command).is_some() {
|
||||
if let Some(sysroot) = dummy_crate.get_sysroot() {
|
||||
let src_path =
|
||||
Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust");
|
||||
if !src_path.exists() {
|
||||
let toolchain = dummy_crate.get_toolchain().unwrap_or("<toolchain>".to_string());
|
||||
return Err(colorize_error_message(
|
||||
&format!("Cannot compile the WASM runtime: no standard library sources found at {}!\n\
|
||||
You can install them with `rustup component add rust-src --toolchain {toolchain}` if you're using `rustup`.", src_path.display()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cargo_command.supports_wasm32v1_none_target() &&
|
||||
!cargo_command.is_wasm32v1_none_target_installed()
|
||||
{
|
||||
build_helper::warning!("You are building WASM runtime using `wasm32-unknown-unknown` target, although Rust >= 1.84 supports `wasm32v1-none` target!");
|
||||
build_helper::warning!("You can install it with `rustup target add wasm32v1-none --toolchain {toolchain}` if you're using `rustup`.");
|
||||
build_helper::warning!("After installing `wasm32v1-none` target, you must rebuild WASM runtime from scratch, use `cargo clean` before building.");
|
||||
}
|
||||
|
||||
Ok(CargoCommandVersioned::new(cargo_command, version))
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// The version of rustc/cargo.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Version {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub is_nightly: bool,
|
||||
pub year: Option<u32>,
|
||||
pub month: Option<u32>,
|
||||
pub day: Option<u32>,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
/// Returns if `self` is a stable version.
|
||||
pub fn is_stable(&self) -> bool {
|
||||
!self.is_nightly
|
||||
}
|
||||
|
||||
/// Return if `self` is a nightly version.
|
||||
pub fn is_nightly(&self) -> bool {
|
||||
self.is_nightly
|
||||
}
|
||||
|
||||
/// Extract from the given `version` string.
|
||||
pub fn extract(version: &str) -> Option<Self> {
|
||||
let mut is_nightly = false;
|
||||
let version_parts = version
|
||||
.trim()
|
||||
.split(" ")
|
||||
.nth(1)?
|
||||
.split(".")
|
||||
.filter_map(|v| {
|
||||
if let Some(rest) = v.strip_suffix("-nightly") {
|
||||
is_nightly = true;
|
||||
rest.parse().ok()
|
||||
} else {
|
||||
v.parse().ok()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<u32>>();
|
||||
|
||||
if version_parts.len() != 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let date_parts = version
|
||||
.split(" ")
|
||||
.nth(3)
|
||||
.map(|date| {
|
||||
date.split("-")
|
||||
.filter_map(|v| v.trim().strip_suffix(")").unwrap_or(v).parse().ok())
|
||||
.collect::<Vec<u32>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Some(Version {
|
||||
major: version_parts[0],
|
||||
minor: version_parts[1],
|
||||
patch: version_parts[2],
|
||||
is_nightly,
|
||||
year: date_parts.get(0).copied(),
|
||||
month: date_parts.get(1).copied(),
|
||||
day: date_parts.get(2).copied(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Ordering is done in the following way:
|
||||
///
|
||||
/// 1. `stable` > `nightly`
|
||||
/// 2. Then compare major, minor and patch.
|
||||
/// 3. Last compare the date.
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self == other {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
// Ensure that `stable > nightly`
|
||||
if self.is_stable() && other.is_nightly() {
|
||||
return Ordering::Greater;
|
||||
} else if self.is_nightly() && other.is_stable() {
|
||||
return Ordering::Less;
|
||||
}
|
||||
|
||||
let to_compare = [
|
||||
(Some(self.major), Some(other.major)),
|
||||
(Some(self.minor), Some(other.minor)),
|
||||
(Some(self.patch), Some(other.patch)),
|
||||
(self.year, other.year),
|
||||
(self.month, other.month),
|
||||
(self.day, other.day),
|
||||
];
|
||||
|
||||
to_compare
|
||||
.iter()
|
||||
.find_map(|(l, r)| if l != r { l.partial_cmp(&r) } else { None })
|
||||
// We already checked this right at the beginning, so we should never return here
|
||||
// `Equal`.
|
||||
.unwrap_or(Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn version_compare_and_extract_works() {
|
||||
let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)").unwrap();
|
||||
let version_1_66_1 = Version::extract("cargo 1.66.1 (d65d197ad 2022-11-15)").unwrap();
|
||||
let version_1_66_0_nightly =
|
||||
Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-15)").unwrap();
|
||||
let version_1_66_0_nightly_older_date =
|
||||
Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-14)").unwrap();
|
||||
let version_1_65_0 = Version::extract("cargo 1.65.0 (d65d197ad 2022-10-15)").unwrap();
|
||||
let version_1_65_0_older_date =
|
||||
Version::extract("cargo 1.65.0 (d65d197ad 2022-10-14)").unwrap();
|
||||
|
||||
assert!(version_1_66_1 > version_1_66_0);
|
||||
assert!(version_1_66_1 > version_1_65_0);
|
||||
assert!(version_1_66_1 > version_1_66_0_nightly);
|
||||
assert!(version_1_66_1 > version_1_66_0_nightly_older_date);
|
||||
assert!(version_1_66_1 > version_1_65_0_older_date);
|
||||
|
||||
assert!(version_1_66_0 > version_1_65_0);
|
||||
assert!(version_1_66_0 > version_1_66_0_nightly);
|
||||
assert!(version_1_66_0 > version_1_66_0_nightly_older_date);
|
||||
assert!(version_1_66_0 > version_1_65_0_older_date);
|
||||
|
||||
assert!(version_1_65_0 > version_1_66_0_nightly);
|
||||
assert!(version_1_65_0 > version_1_66_0_nightly_older_date);
|
||||
assert!(version_1_65_0 > version_1_65_0_older_date);
|
||||
|
||||
let mut versions = vec![
|
||||
version_1_66_0,
|
||||
version_1_66_0_nightly,
|
||||
version_1_66_0_nightly_older_date,
|
||||
version_1_65_0_older_date,
|
||||
version_1_65_0,
|
||||
version_1_66_1,
|
||||
];
|
||||
versions.sort_by(|a, b| b.cmp(a));
|
||||
|
||||
let expected_versions_order = vec![
|
||||
version_1_66_1,
|
||||
version_1_66_0,
|
||||
version_1_65_0,
|
||||
version_1_65_0_older_date,
|
||||
version_1_66_0_nightly,
|
||||
version_1_66_0_nightly_older_date,
|
||||
];
|
||||
assert_eq!(expected_versions_order, versions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_with_newline() {
|
||||
let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)\n").unwrap();
|
||||
assert_eq!(
|
||||
Version {
|
||||
major: 1,
|
||||
minor: 66,
|
||||
patch: 0,
|
||||
is_nightly: false,
|
||||
year: Some(2022),
|
||||
month: Some(11),
|
||||
day: Some(15),
|
||||
},
|
||||
version_1_66_0,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_without_hash_and_date() {
|
||||
// Apparently there are installations that print without the hash and date.
|
||||
let version_1_69_0 = Version::extract("cargo 1.69.0-nightly").unwrap();
|
||||
assert_eq!(
|
||||
Version {
|
||||
major: 1,
|
||||
minor: 69,
|
||||
patch: 0,
|
||||
is_nightly: true,
|
||||
year: None,
|
||||
month: None,
|
||||
day: None,
|
||||
},
|
||||
version_1_69_0,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_rustc_version() {
|
||||
let version = Version::extract("rustc 1.73.0 (cc66ad468 2023-10-03)").unwrap();
|
||||
assert_eq!(
|
||||
version,
|
||||
Version {
|
||||
major: 1,
|
||||
minor: 73,
|
||||
patch: 0,
|
||||
is_nightly: false,
|
||||
year: Some(2023),
|
||||
month: Some(10),
|
||||
day: Some(03),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user