ink_e2e/
contract_build.rs1use crate::log_info;
16use contract_build::{
17 BuildArtifacts,
18 BuildMode,
19 ExecuteArgs,
20 Features,
21 ImageVariant,
22 ManifestPath,
23 MetadataSpec,
24 Network,
25 OutputType,
26 UnstableFlags,
27 Verbosity,
28};
29use itertools::Itertools;
30use std::{
31 collections::{
32 hash_map::Entry,
33 HashMap,
34 },
35 env,
36 path::{
37 Path,
38 PathBuf,
39 },
40 sync::{
41 Mutex,
42 OnceLock,
43 },
44};
45
46pub fn build_root_and_contract_dependencies(features: Vec<String>) -> Vec<PathBuf> {
51 let contract_project = ContractProject::new();
52 let contract_manifests = contract_project.root_with_contract_dependencies();
53 build_contracts(&contract_manifests, features)
54}
55
56struct ContractProject {
59 root_package: Option<PathBuf>,
60 contract_dependencies: Vec<PathBuf>,
61}
62
63impl ContractProject {
64 fn new() -> Self {
65 let mut cmd = cargo_metadata::MetadataCommand::new();
66 if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR")
67 .filter(|target_dir| Path::new(&target_dir).is_absolute())
68 {
69 cmd.env("CARGO_TARGET_DIR", &target_dir);
70 }
71 let metadata = cmd
72 .exec()
73 .unwrap_or_else(|err| panic!("Error invoking `cargo metadata`: {}", err));
74
75 fn maybe_contract_package(package: &cargo_metadata::Package) -> Option<PathBuf> {
76 package
77 .features
78 .iter()
79 .any(|(feat, _)| {
80 feat == "ink-as-dependency"
81 && !package.name.eq("ink")
82 && !package.name.eq("ink_env")
83 })
84 .then(|| package.manifest_path.clone().into_std_path_buf())
85 }
86
87 let root_package = metadata
88 .resolve
89 .as_ref()
90 .and_then(|resolve| resolve.root.as_ref())
91 .and_then(|root_package_id| {
92 metadata
93 .packages
94 .iter()
95 .find(|package| &package.id == root_package_id)
96 })
97 .and_then(maybe_contract_package);
98 log_info(&format!("found root package: {:?}", root_package));
99
100 let contract_dependencies: Vec<PathBuf> = metadata
101 .packages
102 .iter()
103 .filter_map(maybe_contract_package)
104 .collect();
105
106 log_info(&format!(
107 "found those contract dependencies: {:?}",
108 contract_dependencies
109 ));
110 Self {
111 root_package,
112 contract_dependencies,
113 }
114 }
115
116 fn root_with_additional_contracts<P>(
117 &self,
118 additional_contracts: impl IntoIterator<Item = P>,
119 ) -> Vec<PathBuf>
120 where
121 PathBuf: From<P>,
122 {
123 let mut all_manifests: Vec<_> = self.root_package.iter().cloned().collect();
124 let mut additional_contracts: Vec<_> = additional_contracts
125 .into_iter()
126 .map(PathBuf::from)
127 .collect();
128 all_manifests.append(&mut additional_contracts);
129 all_manifests.into_iter().unique().collect()
130 }
131
132 fn root_with_contract_dependencies(&self) -> Vec<PathBuf> {
133 self.root_with_additional_contracts(&self.contract_dependencies)
134 }
135}
136
137fn build_contracts(
142 contract_manifests: &[PathBuf],
143 features: Vec<String>,
144) -> Vec<PathBuf> {
145 static CONTRACT_BUILD_JOBS: OnceLock<Mutex<HashMap<PathBuf, PathBuf>>> =
146 OnceLock::new();
147 let mut contract_build_jobs = CONTRACT_BUILD_JOBS
148 .get_or_init(|| Mutex::new(HashMap::new()))
149 .lock()
150 .unwrap();
151
152 let mut blob_paths = Vec::new();
153 for manifest in contract_manifests {
154 let contract_binary_path = match contract_build_jobs.entry(manifest.clone()) {
155 Entry::Occupied(entry) => entry.get().clone(),
156 Entry::Vacant(entry) => {
157 let contract_binary_path = build_contract(manifest, features.clone());
158 entry.insert(contract_binary_path.clone());
159 contract_binary_path
160 }
161 };
162 blob_paths.push(contract_binary_path);
163 }
164 blob_paths
165}
166
167fn build_contract(
170 path_to_cargo_toml: &Path,
171 additional_features: Vec<String>,
172) -> PathBuf {
173 let manifest_path = ManifestPath::new(path_to_cargo_toml).unwrap_or_else(|err| {
174 panic!(
175 "Invalid manifest path {}: {err}",
176 path_to_cargo_toml.display()
177 )
178 });
179 let mut features = Features::default();
181 additional_features.iter().for_each(|f| features.push(f));
182 let args = ExecuteArgs {
183 manifest_path,
184 verbosity: Verbosity::Default,
185 build_mode: BuildMode::Debug,
186 features,
187 network: Network::Online,
188 build_artifact: BuildArtifacts::All,
189 unstable_flags: UnstableFlags::default(),
190 keep_debug_symbols: false,
191 extra_lints: false,
192 output_type: OutputType::HumanReadable,
193 image: ImageVariant::Default,
194 metadata_spec: MetadataSpec::Ink,
195 };
196
197 match contract_build::execute(args) {
198 Ok(build_result) => {
199 build_result
200 .dest_binary
201 .expect("PolkaVM code artifact not generated")
202 .canonicalize()
203 .expect("Invalid dest bundle path")
204 }
205 Err(err) => {
206 panic!(
207 "contract build for {} failed: {err}",
208 path_to_cargo_toml.display()
209 )
210 }
211 }
212}