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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+70
View File
@@ -0,0 +1,70 @@
[package]
name = "bizinikiwi-wasm-builder"
version = "17.0.0"
authors.workspace = true
description = "Utility for building WASM binaries"
edition.workspace = true
repository.workspace = true
license = "Apache-2.0"
homepage.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
build-helper = { workspace = true }
cargo_metadata = { workspace = true }
console = { workspace = true }
filetime = { workspace = true }
jobserver = { workspace = true }
parity-wasm = { workspace = true }
polkavm-linker = { workspace = true }
pezsp-maybe-compressed-blob = { workspace = true, default-features = true }
strum = { features = ["derive"], workspace = true, default-features = true }
tempfile = { workspace = true }
toml = { workspace = true }
walkdir = { workspace = true }
wasm-opt = { workspace = true }
# Dependencies required for the `metadata-hash` feature.
array-bytes = { optional = true, workspace = true, default-features = true }
codec = { optional = true, workspace = true, default-features = true }
frame-metadata = { features = [
"current",
"unstable",
], optional = true, workspace = true, default-features = true }
merkleized-metadata = { optional = true, workspace = true }
pezsc-executor = { optional = true, workspace = true, default-features = true }
shlex = { workspace = true }
pezsp-core = { optional = true, workspace = true, default-features = true }
pezsp-io = { optional = true, workspace = true, default-features = true }
pezsp-tracing = { optional = true, workspace = true, default-features = true }
pezsp-version = { optional = true, workspace = true, default-features = true }
[features]
# Enable support for generating the metadata hash.
#
# To generate the metadata hash the runtime is build once, executed to build the metadata and then
# build a second time with the `RUNTIME_METADATA_HASH` environment variable set. The environment
# variable then contains the hash and can be used inside the runtime.
#
# This pulls in quite a lot of dependencies and thus, is disabled by default.
metadata-hash = [
"array-bytes",
"codec",
"frame-metadata",
"merkleized-metadata",
"pezsc-executor",
"pezsp-core",
"pezsp-io",
"pezsp-tracing",
"pezsp-version",
]
runtime-benchmarks = [
"pezsc-executor?/runtime-benchmarks",
"pezsp-io?/runtime-benchmarks",
"pezsp-version?/runtime-benchmarks",
]
@@ -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,
),
);
}
+480
View File
@@ -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