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 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 static CONTRACT_BUILD_JOBS: OnceLock<
166 Mutex<HashMap<(PathBuf, Vec<String>), PathBuf>>,
167 > = OnceLock::new();
168 let mut contract_build_jobs = CONTRACT_BUILD_JOBS
169 .get_or_init(|| Mutex::new(HashMap::new()))
170 .lock()
171 .unwrap();
172
173 let mut blob_paths = Vec::new();
174 for (manifest, features) in contract_manifests {
175 let key = (manifest.clone(), features.clone());
176 let contract_binary_path = match contract_build_jobs.entry(key) {
177 Entry::Occupied(entry) => entry.get().clone(),
178 Entry::Vacant(entry) => {
179 let contract_binary_path =
180 build_contract(manifest, features.clone(), target_dir.clone());
181 let path_with_features =
182 add_features_to_filename(contract_binary_path, features);
183 entry.insert(path_with_features.clone());
184 path_with_features
185 }
186 };
187 blob_paths.push(contract_binary_path);
188 }
189 blob_paths
190}
191
192fn add_features_to_filename(
193 contract_binary_path: PathBuf,
194 features: &Vec<String>,
195) -> PathBuf {
196 let mut path_with_features = contract_binary_path.clone();
198 let filename = path_with_features
199 .file_stem()
200 .expect("no file name")
201 .to_string_lossy()
202 .into_owned();
203 let extension = path_with_features
204 .extension()
205 .expect("no file name")
206 .to_string_lossy()
207 .into_owned();
208 path_with_features.pop();
209
210 let features_str = features.join("-");
211 let mut new_filename =
212 format!("{}-features-{}", filename, features_str.replace("/", "-"));
213 if features.is_empty() {
214 new_filename.push_str("no");
215 }
216 new_filename.push_str(&format!(".{extension}"));
217 path_with_features.push(new_filename);
218 std::fs::copy(contract_binary_path, path_with_features.as_path())
219 .expect("failed copying binary");
220 path_with_features
221}
222
223fn build_contract(
226 cargo_toml: &Path,
227 features: Vec<String>,
228 target_dir: PathBuf,
229) -> PathBuf {
230 let manifest_path = ManifestPath::new(cargo_toml).unwrap_or_else(|err| {
231 panic!("Invalid manifest path {}: {err}", cargo_toml.display())
232 });
233 let args = ExecuteArgs {
234 manifest_path,
235 verbosity: Verbosity::Default,
236 build_mode: BuildMode::Debug,
237 features: Features::from(features),
238 network: Network::Online,
239 build_artifact: BuildArtifacts::CodeOnly,
240 unstable_flags: UnstableFlags::default(),
241 keep_debug_symbols: false,
242 extra_lints: false,
243 output_type: OutputType::HumanReadable,
244 image: ImageVariant::Default,
245 metadata_spec: None,
246 target_dir: Some(target_dir),
247 };
248
249 match contract_build::execute(args) {
250 Ok(build_result) => {
251 build_result
252 .dest_binary
253 .expect("PolkaVM code artifact not generated")
254 .canonicalize()
255 .expect("Invalid dest bundle path")
256 }
257 Err(err) => {
258 panic!("contract build for {} failed: {err}", cargo_toml.display())
259 }
260 }
261}