multiversx_sc_meta_lib/contract/sc_config/
wasm_build.rs1use crate::tools::{build_target, wasm_opt};
2use core::panic;
3use std::{
4 collections::HashMap,
5 ffi::OsStr,
6 fs,
7 path::{Path, PathBuf},
8 process::Command,
9};
10
11use super::execute_command::{execute_spawn_command, ExecuteCommandError};
12use super::ContractVariant;
13use crate::{
14 abi_json::ContractAbiJson,
15 cli::BuildArgs,
16 ei::EIVersion,
17 ei_check_json::EiCheckJson,
18 mxsc_file_json::{save_mxsc_file_json, MxscFileJson},
19 print_util::*,
20 report_info_json::ReportInfoJson,
21 tools::{self, WasmInfo, WasmReport},
22};
23
24impl ContractVariant {
25 pub fn build_contract(
26 &self,
27 build_args: &BuildArgs,
28 output_path: &Path,
29 ) -> Result<(), ExecuteCommandError> {
30 let mut build_command = self.compose_build_command(build_args);
31
32 print_build_command(self.wasm_output_name(build_args), &build_command);
33
34 let output_build_command = execute_spawn_command(&mut build_command, "cargo");
35
36 match output_build_command {
37 Ok(_) => {}
38 Err(ExecuteCommandError::JobFailed(_)) => {
39 if !self.is_target_installed() {
40 self.install_wasm_target();
41
42 execute_spawn_command(&mut build_command, "cargo")
43 .expect("error building contract");
44 }
45 }
46 Err(_) => {
47 panic!("error building contract");
48 }
49 }
50
51 self.finalize_build(build_args, output_path);
52
53 Ok(())
54 }
55
56 fn compose_build_command(&self, build_args: &BuildArgs) -> Command {
57 let mut command = Command::new("cargo");
58 command
59 .arg(self.settings.rustc_version.to_cli_arg())
60 .arg("build")
61 .arg(format!("--target={}", &self.settings.rustc_target))
62 .arg("--release")
63 .current_dir(self.wasm_crate_path());
64 if build_args.locked {
65 command.arg("--locked");
66 }
67 if let Some(target_dir_wasm) = &build_args.target_dir_wasm {
68 command.args(["--target-dir", target_dir_wasm]);
69 }
70 let rustflags = self.compose_rustflags(build_args);
71 if !rustflags.is_empty() {
72 command.env("RUSTFLAGS", rustflags);
73 }
74 command
75 }
76
77 fn compose_rustflags(&self, build_args: &BuildArgs) -> Rustflags {
78 let mut rustflags = Rustflags::default();
79
80 if !build_args.wasm_symbols {
81 rustflags.push_flag("-C link-arg=-s");
82 }
83
84 rustflags.push_flag(&format!(
85 "-C link-arg=-zstack-size={}",
86 self.settings.stack_size
87 ));
88
89 if build_args.emit_mir {
90 rustflags.push_flag("--emit=mir");
91 }
92
93 if build_args.emit_llvm_ir {
94 rustflags.push_flag("--emit=llvm-ir");
95 }
96 rustflags
97 }
98
99 fn is_target_installed(&self) -> bool {
100 build_target::is_target_installed(&self.settings.rustc_version, &self.settings.rustc_target)
101 }
102
103 fn install_wasm_target(&self) {
104 build_target::install_target(
105 Some(&self.settings.rustc_version),
106 &self.settings.rustc_target,
107 );
108 }
109
110 fn finalize_build(&self, build_args: &BuildArgs, output_path: &Path) {
111 self.copy_contracts_to_output(build_args, output_path);
112 self.run_wasm_opt(build_args, output_path);
113 self.run_wasm2wat(build_args, output_path);
114 let report = self.extract_wasm_info(build_args, output_path);
115 self.run_twiggy(build_args, output_path);
116 self.pack_mxsc_file(build_args, output_path, &report);
117 }
118
119 fn copy_contracts_to_output(&self, build_args: &BuildArgs, output_path: &Path) {
120 let source_wasm_path = self.wasm_compilation_output_path(&build_args.target_dir_wasm);
121 let output_wasm_path = output_path.join(self.wasm_output_name(build_args));
122 print_copy_contract(
123 &source_wasm_path.to_string_lossy(),
124 &output_wasm_path.to_string_lossy(),
125 );
126 fs::copy(&source_wasm_path, output_wasm_path).unwrap_or_else(|err| {
127 panic!(
128 "failed to copy compiled contract to output directory, source: {}, err: {err}",
129 source_wasm_path.display()
130 )
131 });
132 }
133
134 fn pack_mxsc_file(&self, build_args: &BuildArgs, output_path: &Path, wasm_report: &WasmReport) {
135 let output_wasm_path = output_path.join(self.wasm_output_name(build_args));
136 let compiled_bytes = fs::read(output_wasm_path).expect("failed to open compiled contract");
137 let output_mxsc_path = output_path.join(self.mxsc_file_output_name(build_args));
138 print_pack_mxsc_file(&output_mxsc_path.to_string_lossy());
139 print_contract_size(compiled_bytes.len());
140 let mut abi = ContractAbiJson::from(&self.abi);
141 let build_info = core::mem::take(&mut abi.build_info).unwrap();
142 let ei_check_json = EiCheckJson::new(&self.settings.check_ei, wasm_report.ei_check);
143 let report = ReportInfoJson::new(wasm_report, ei_check_json, compiled_bytes.len());
144 let mxsc_file_json = MxscFileJson {
145 build_info,
146 abi,
147 code: hex::encode(compiled_bytes),
148 report,
149 };
150
151 save_mxsc_file_json(&mxsc_file_json, output_mxsc_path);
152 }
153
154 fn check_wasm_opt(&self, build_args: &BuildArgs) -> bool {
156 if let Some(config_wasm_opt_version) = &self.settings.wasm_opt_version {
157 let contract_name = &self.contract_name;
158
159 assert!(build_args.wasm_opt, "Contract {contract_name} requires wasm-opt version {config_wasm_opt_version}, and cannot be built without.");
160
161 let opt_version = wasm_opt::wasm_opt_version();
162
163 if opt_version.is_none() {
164 print_wasm_opt_not_installed(wasm_opt::WASM_OPT_NAME);
166 }
167
168 let installed_wasm_opt_version = opt_version
169 .unwrap_or_else(|| panic!("Missing wasm-opt. Contract {contract_name} requires wasm-opt version {config_wasm_opt_version}, and cannot be built without."));
170
171 assert_eq!(config_wasm_opt_version, &installed_wasm_opt_version,
172 "Incorrect wasm-opt version installed. Contract {contract_name} requires wasm-opt version {config_wasm_opt_version}");
173
174 true
175 } else {
176 if !build_args.wasm_opt {
177 return false;
178 }
179
180 if wasm_opt::wasm_opt_version().is_none() {
181 print_wasm_opt_not_installed(wasm_opt::WASM_OPT_NAME);
182 return false;
183 }
184
185 true
186 }
187 }
188
189 fn run_wasm_opt(&self, build_args: &BuildArgs, output_path: &Path) {
190 let should_run_wasm_opt = self.check_wasm_opt(build_args);
191
192 if !should_run_wasm_opt {
193 return;
194 }
195
196 let output_wasm_path = output_path.join(self.wasm_output_name(build_args));
197
198 print_call_wasm_opt(&output_wasm_path.to_string_lossy());
199 tools::run_wasm_opt(&output_wasm_path.to_string_lossy());
200 }
201
202 fn run_wasm2wat(&self, build_args: &BuildArgs, output_path: &Path) {
203 if !build_args.wat {
204 return;
205 }
206
207 let output_wasm_path = output_path.join(self.wasm_output_name(build_args));
208 let output_wat_path = output_path.join(self.wat_output_name(build_args));
209
210 print_call_wasm2wat(
211 &output_wasm_path.to_string_lossy(),
212 &output_wat_path.to_string_lossy(),
213 );
214 tools::wasm_to_wat(
215 &output_wasm_path.to_string_lossy(),
216 &output_wat_path.to_string_lossy(),
217 );
218 }
219
220 fn extract_wasm_info(&self, build_args: &BuildArgs, output_path: &Path) -> WasmReport {
221 let output_wasm_path = output_path.join(self.wasm_output_name(build_args));
222
223 let abi = ContractAbiJson::from(&self.abi);
224 let mut endpoints: HashMap<&str, bool> = HashMap::new();
225
226 if abi.constructor.is_some() {
227 endpoints.insert("init", false);
228 }
229
230 if abi.upgrade_constructor.is_some() {
231 endpoints.insert("upgrade", false);
232 }
233
234 for endpoint in &abi.endpoints {
235 if let crate::abi_json::EndpointMutabilityAbiJson::Readonly = endpoint.mutability {
236 endpoints.insert(&endpoint.name, true);
237 } else {
238 endpoints.insert(&endpoint.name, false);
239 }
240 }
241
242 if !build_args.extract_imports {
243 return WasmInfo::extract_wasm_report(
244 &output_wasm_path,
245 build_args.extract_imports,
246 self.settings.check_ei.as_ref(),
247 &endpoints,
248 self.settings.opcode_version,
249 );
250 }
251
252 let output_imports_json_path = output_path.join(self.imports_json_output_name(build_args));
253
254 print_extract_imports(&output_imports_json_path.to_string_lossy());
255
256 let wasm_report = WasmInfo::extract_wasm_report(
257 &output_wasm_path,
258 true,
259 self.settings.check_ei.as_ref(),
260 &endpoints,
261 self.settings.opcode_version,
262 );
263
264 write_imports_output(&output_imports_json_path, wasm_report.imports.as_slice());
265 print_ei_check(&wasm_report, &self.settings.check_ei);
266
267 wasm_report
268 }
269}
270
271fn write_imports_output(dest_path: &PathBuf, import_names: &[String]) {
272 let json = serde_json::to_string_pretty(import_names).unwrap();
273 fs::write(dest_path, json).expect("failed to write imports json file");
274}
275
276fn print_ei_check(wasm_report: &WasmReport, check_ei: &Option<EIVersion>) {
277 if let Some(ei) = check_ei {
278 print_check_ei(ei.name());
279
280 if wasm_report.ei_check {
281 print_check_ei_ok();
282 } else {
283 for import_name in &wasm_report.imports {
284 if !ei.contains_vm_hook(import_name.as_str()) {
285 print_invalid_vm_hook(import_name.as_str(), ei.name());
286 }
287 }
288
289 println!();
290 }
291 } else {
292 print_ignore_ei_check();
293 }
294}
295
296impl ContractVariant {
297 fn run_twiggy(&self, build_args: &BuildArgs, output_path: &Path) {
298 if build_args.has_twiggy_call() {
299 let output_wasm_path = output_path.join(self.wasm_output_name(build_args));
300
301 if build_args.twiggy_top {
302 let output_twiggy_top_path = output_path.join(self.twiggy_top_name(build_args));
303
304 tools::twiggy::run_twiggy_top(&output_wasm_path, &output_twiggy_top_path);
305 }
306 if build_args.twiggy_paths {
307 let output_twiggy_paths_path = output_path.join(self.twiggy_paths_name(build_args));
308
309 tools::twiggy::run_twiggy_paths(&output_wasm_path, &output_twiggy_paths_path);
310 }
311 if build_args.twiggy_monos {
312 let output_twiggy_monos_path = output_path.join(self.twiggy_monos_name(build_args));
313
314 tools::twiggy::run_twiggy_monos(&output_wasm_path, &output_twiggy_monos_path);
315 }
316 if build_args.twiggy_dominators {
317 let output_twiggy_dominators_path =
318 output_path.join(self.twiggy_dominators_name(build_args));
319
320 tools::twiggy::run_twiggy_dominators(
321 &output_wasm_path,
322 &output_twiggy_dominators_path,
323 );
324 }
325 }
326 }
327}
328
329#[derive(Default)]
331struct Rustflags(String);
332
333impl Rustflags {
334 fn push_flag(&mut self, s: &str) {
335 if !self.0.is_empty() {
336 self.0.push(' ');
337 }
338 self.0.push_str(s);
339 }
340
341 fn is_empty(&self) -> bool {
342 self.0.is_empty()
343 }
344}
345
346impl AsRef<OsStr> for Rustflags {
347 fn as_ref(&self) -> &OsStr {
348 self.0.as_ref()
349 }
350}