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