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