#![cfg_attr(feature = "strict", deny(warnings))]
#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")]
#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")]
use crate::{cargo_command::CargoCommand, wasm_project::WasmProject};
use anyhow::{Context, Result};
use gmeta::{Metadata, MetadataRepr};
use regex::Regex;
use std::{env, path::PathBuf, process};
use wasm_project::ProjectType;
pub use wasm_project::{PreProcessor, PreProcessorResult, PreProcessorTarget};
mod builder_error;
mod cargo_command;
mod cargo_toolchain;
pub mod code_validator;
mod crate_info;
pub mod optimize;
mod smart_fs;
mod stack_end;
mod wasm_project;
pub const TARGET: &str = env!("TARGET");
pub struct WasmBuilder {
wasm_project: WasmProject,
cargo: CargoCommand,
excluded_features: Vec<&'static str>,
}
impl WasmBuilder {
pub fn new() -> Self {
WasmBuilder::create(WasmProject::new(ProjectType::Program(None)))
}
pub fn new_metawasm() -> Self {
WasmBuilder::create(WasmProject::new(ProjectType::Metawasm))
}
pub fn with_meta(metadata: MetadataRepr) -> Self {
WasmBuilder::create(WasmProject::new(ProjectType::Program(Some(metadata))))
}
fn create(wasm_project: WasmProject) -> Self {
WasmBuilder {
wasm_project,
cargo: CargoCommand::new(),
excluded_features: vec![],
}
}
pub fn exclude_features(mut self, features: impl Into<Vec<&'static str>>) -> Self {
self.excluded_features = features.into();
self
}
pub fn with_pre_processor(mut self, pre_processor: Box<dyn PreProcessor>) -> Self {
self.wasm_project.add_preprocessor(pre_processor);
self
}
pub fn with_recommended_toolchain(mut self) -> Self {
self.cargo.set_check_recommended_toolchain(true);
self
}
#[doc(hidden)]
pub fn with_forced_recommended_toolchain(mut self) -> Self {
self.cargo.set_force_recommended_toolchain(true);
self
}
pub fn build(self) {
if env::var("__GEAR_WASM_BUILDER_NO_BUILD").is_ok() || is_intellij_sync() {
self.wasm_project.provide_dummy_wasm_binary_if_not_exist();
return;
}
if let Err(e) = self.build_project() {
eprintln!("error: {e}");
e.chain()
.skip(1)
.for_each(|cause| eprintln!("| {cause}"));
process::exit(1);
}
}
fn build_project(mut self) -> Result<()> {
self.wasm_project.generate()?;
self.cargo
.set_manifest_path(self.wasm_project.manifest_path());
self.cargo.set_target_dir(self.wasm_project.target_dir());
let profile = self.wasm_project.profile();
let profile = if profile == "debug" { "dev" } else { profile };
self.cargo.set_profile(profile.to_string());
self.cargo.set_features(&self.enabled_features()?);
if env::var("GEAR_WASM_BUILDER_PATH_REMAPPING").is_ok() {
self.cargo.set_paths_to_remap(&self.paths_to_remap()?);
}
self.cargo.run()?;
self.wasm_project.postprocess()
}
fn manifest_path(&self) -> Result<String> {
let manifest_path = env::var("CARGO_MANIFEST_DIR")?;
Ok(manifest_path)
}
fn enabled_features(&self) -> Result<Vec<String>> {
let project_features = self.wasm_project.features();
let enabled_features_iter = env::vars().filter_map(|(key, _)| {
key.strip_prefix("CARGO_FEATURE_")
.map(|feature| feature.to_lowercase())
});
let mut matched_features = Vec::new();
let mut unmatched_features = Vec::new();
for enabled_feature in enabled_features_iter {
let enabled_feature_regex =
Regex::new(&format!("^{}$", enabled_feature.replace('_', "[-_]")))?;
if self
.excluded_features
.iter()
.any(|excluded_feature| enabled_feature_regex.is_match(excluded_feature))
{
continue;
}
if let Some(project_feature) = project_features
.iter()
.find(|project_feature| enabled_feature_regex.is_match(project_feature))
{
matched_features.push(project_feature.clone());
} else {
unmatched_features.push(enabled_feature);
}
}
if !unmatched_features.is_empty() && unmatched_features != ["default"] {
println!(
"cargo:warning=Package {}: features `{}` are not available and will be ignored",
self.manifest_path()?,
unmatched_features.join(", ")
);
}
Ok(matched_features
.into_iter()
.filter(|feature| feature != "gcli")
.collect())
}
fn paths_to_remap(&self) -> Result<Vec<(PathBuf, &'static str)>> {
let home_dir = dirs::home_dir().context("unable to get home directory")?;
let project_dir = self.wasm_project.original_dir();
let cargo_dir = std::env::var_os("CARGO_HOME")
.map(PathBuf::from)
.context("unable to get cargo home directory")?;
let cargo_checkouts_dir = cargo_dir.join("git").join("checkouts");
Ok(vec![
(home_dir, "/home"),
(project_dir, "/code"),
(cargo_dir, "/cargo"),
(cargo_checkouts_dir, "/deps"),
])
}
}
impl Default for WasmBuilder {
fn default() -> Self {
Self::new()
}
}
fn is_intellij_sync() -> bool {
env::var("RUSTC_WRAPPER")
.unwrap_or_default()
.contains("intellij")
}
const FEATURES_TO_EXCLUDE_BY_DEFAULT: &[&str] = &["std"];
pub fn build() {
WasmBuilder::new()
.exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec())
.build();
}
pub fn build_with_metadata<T: Metadata>() {
WasmBuilder::with_meta(T::repr())
.exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec())
.build();
}
pub fn build_metawasm() {
WasmBuilder::new_metawasm()
.exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec())
.build();
}
pub fn recommended_nightly() {
WasmBuilder::new()
.exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec())
.with_recommended_toolchain()
.build();
}
pub fn recommended_nightly_with_metadata<T: Metadata>() {
WasmBuilder::with_meta(T::repr())
.exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec())
.with_recommended_toolchain()
.build();
}
pub fn recommended_nightly_metawasm() {
WasmBuilder::new_metawasm()
.exclude_features(FEATURES_TO_EXCLUDE_BY_DEFAULT.to_vec())
.with_recommended_toolchain()
.build();
}