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