use contract_build::{
BuildArtifacts,
BuildMode,
ExecuteArgs,
Features,
ImageVariant,
ManifestPath,
Network,
OptimizationPasses,
OutputType,
Target,
UnstableFlags,
Verbosity,
DEFAULT_MAX_MEMORY_PAGES,
};
use std::{
collections::{
hash_map::Entry,
HashMap,
},
path::{
Path,
PathBuf,
},
sync::{
Mutex,
OnceLock,
},
};
pub fn build_root_and_contract_dependencies() -> Vec<PathBuf> {
let contract_project = ContractProject::new();
let contract_manifests = contract_project.root_with_contract_dependencies();
build_contracts(&contract_manifests)
}
struct ContractProject {
root_package: Option<PathBuf>,
contract_dependencies: Vec<PathBuf>,
}
impl ContractProject {
fn new() -> Self {
let cmd = cargo_metadata::MetadataCommand::new();
let metadata = cmd
.exec()
.unwrap_or_else(|err| panic!("Error invoking `cargo metadata`: {}", err));
fn maybe_contract_package(package: &cargo_metadata::Package) -> Option<PathBuf> {
package
.features
.iter()
.any(|(feat, _)| feat == "ink-as-dependency")
.then(|| package.manifest_path.clone().into_std_path_buf())
}
let root_package = metadata
.resolve
.as_ref()
.and_then(|resolve| resolve.root.as_ref())
.and_then(|root_package_id| {
metadata
.packages
.iter()
.find(|package| &package.id == root_package_id)
})
.and_then(maybe_contract_package);
let contract_dependencies: Vec<PathBuf> = metadata
.packages
.iter()
.filter_map(maybe_contract_package)
.collect();
Self {
root_package,
contract_dependencies,
}
}
fn root_with_additional_contracts<P>(
&self,
additional_contracts: impl IntoIterator<Item = P>,
) -> Vec<PathBuf>
where
PathBuf: From<P>,
{
let mut all_manifests: Vec<_> = self.root_package.iter().cloned().collect();
let mut additional_contracts: Vec<_> = additional_contracts
.into_iter()
.map(PathBuf::from)
.collect();
all_manifests.append(&mut additional_contracts);
all_manifests
}
fn root_with_contract_dependencies(&self) -> Vec<PathBuf> {
self.root_with_additional_contracts(&self.contract_dependencies)
}
}
fn build_contracts(contract_manifests: &[PathBuf]) -> Vec<PathBuf> {
static CONTRACT_BUILD_JOBS: OnceLock<Mutex<HashMap<PathBuf, PathBuf>>> =
OnceLock::new();
let mut contract_build_jobs = CONTRACT_BUILD_JOBS
.get_or_init(|| Mutex::new(HashMap::new()))
.lock()
.unwrap();
let mut wasm_paths = Vec::new();
for manifest in contract_manifests {
let wasm_path = match contract_build_jobs.entry(manifest.clone()) {
Entry::Occupied(entry) => entry.get().clone(),
Entry::Vacant(entry) => {
let wasm_path = build_contract(manifest);
entry.insert(wasm_path.clone());
wasm_path
}
};
wasm_paths.push(wasm_path);
}
wasm_paths
}
fn build_contract(path_to_cargo_toml: &Path) -> PathBuf {
let manifest_path = ManifestPath::new(path_to_cargo_toml).unwrap_or_else(|err| {
panic!(
"Invalid manifest path {}: {err}",
path_to_cargo_toml.display()
)
});
let args = ExecuteArgs {
manifest_path,
verbosity: Verbosity::Default,
build_mode: BuildMode::Debug,
features: Features::default(),
network: Network::Online,
build_artifact: BuildArtifacts::All,
unstable_flags: UnstableFlags::default(),
optimization_passes: Some(OptimizationPasses::default()),
keep_debug_symbols: false,
extra_lints: false,
output_type: OutputType::HumanReadable,
skip_wasm_validation: false,
target: Target::Wasm,
max_memory_pages: DEFAULT_MAX_MEMORY_PAGES,
image: ImageVariant::Default,
};
match contract_build::execute(args) {
Ok(build_result) => {
build_result
.dest_wasm
.expect("Wasm code artifact not generated")
.canonicalize()
.expect("Invalid dest bundle path")
}
Err(err) => {
panic!(
"contract build for {} failed: {err}",
path_to_cargo_toml.display()
)
}
}
}