use prerequisites::DummyCrate;
use std::{env, fs, io::BufRead, path::Path, 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};
const SKIP_BUILD_ENV: &str = "SKIP_WASM_BUILD";
const OFFLINE: &str = "CARGO_NET_OFFLINE";
const WASM_BUILD_TYPE_ENV: &str = "WASM_BUILD_TYPE";
const WASM_BUILD_RUSTFLAGS_ENV: &str = "WASM_BUILD_RUSTFLAGS";
const WASM_TARGET_DIRECTORY: &str = "WASM_TARGET_DIRECTORY";
const WASM_BUILD_NO_COLOR: &str = "WASM_BUILD_NO_COLOR";
const WASM_BUILD_TOOLCHAIN: &str = "WASM_BUILD_TOOLCHAIN";
const FORCE_WASM_BUILD_ENV: &str = "FORCE_WASM_BUILD";
const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT";
const WASM_BUILD_STD: &str = "WASM_BUILD_STD";
const WASM_BUILD_CARGO_ARGS: &str = "WASM_BUILD_CARGO_ARGS";
const RUNTIME_TARGET: &str = "SUBSTRATE_RUNTIME_TARGET";
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()));
}
}
fn copy_file_if_changed(src: &Path, dst: &Path) -> bool {
let src_file = fs::read(src).ok();
let dst_file = fs::read(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())
});
true
} else {
false
}
}
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();
if let Some(cmd) =
wasm_toolchain.map(|t| CargoCommand::new_with_args("rustup", &["run", &t, "cargo"]))
{
cmd
} else if env_cargo.supports_substrate_runtime_env(target) {
env_cargo
} else if default_cargo.supports_substrate_runtime_env(target) {
default_cargo
} else {
get_rustup_command(target).unwrap_or(default_cargo)
}
}
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()) {
let rustup_version = line.split(" ").next().unwrap();
let cmd = CargoCommand::new_with_args("rustup", &["run", &rustup_version, "cargo"]);
if !cmd.supports_substrate_runtime_env(target) {
continue;
}
let Some(cargo_version) = cmd.version() else { continue };
versions.push((cargo_version, rustup_version.to_string()));
}
versions.sort_by_key(|v| v.0);
let version = &versions.last()?.1;
Some(CargoCommand::new_with_args("rustup", &["run", &version, "cargo"]))
}
#[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)
}
fn version(&self) -> Option<Version> {
self.version
}
fn supports_nightly_features(&self) -> bool {
self.version.map_or(false, |version| version.is_nightly) ||
env::var("RUSTC_BOOTSTRAP").is_ok()
}
fn supports_substrate_runtime_env(&self, target: RuntimeTarget) -> bool {
match target {
RuntimeTarget::Wasm => self.supports_substrate_runtime_env_wasm(),
RuntimeTarget::Riscv => true,
}
}
fn supports_substrate_runtime_env_wasm(&self) -> bool {
if env::var("RUSTC_BOOTSTRAP").is_ok() {
return true;
}
let Some(version) = self.version() else { return false };
version.major > 1 || (version.major == 1 && version.minor >= 68) || version.is_nightly
}
fn supports_wasm32v1_none_target(&self) -> bool {
self.version.map_or(false, |version| {
version.major > 1 || (version.major == 1 && version.minor >= 84)
})
}
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)
}
fn is_wasm32v1_none_target_available(&self) -> bool {
self.supports_wasm32v1_none_target() && self.is_wasm32v1_none_target_installed()
}
}
#[derive(Clone)]
struct CargoCommandVersioned {
command: CargoCommand,
version: String,
}
impl CargoCommandVersioned {
fn new(command: CargoCommand, version: String) -> Self {
Self { command, version }
}
fn rustc_version(&self) -> &str {
&self.version
}
}
impl std::ops::Deref for CargoCommandVersioned {
type Target = CargoCommand;
fn deref(&self) -> &CargoCommand {
&self.command
}
}
fn color_output_enabled() -> bool {
env::var(crate::WASM_BUILD_NO_COLOR).is_err()
}
fn get_bool_environment_variable(name: &str) -> Option<bool> {
let value = env::var_os(name)?;
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 {
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);
}
}
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 mut args = polkavm_linker::TargetJsonArgs::default();
args.is_64_bit = true;
let path = polkavm_linker::target_json_path(args).expect("riscv not found");
path.into_os_string().into_string().unwrap()
},
}
}
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 => "riscv64emac-unknown-none-polkavm",
}
}
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;
}
Some("build-std=core,alloc")
}
}