multiversx_sc_meta_lib/contract/sc_config/
wasm_build.rs

1use 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/// For convenience, for building rustflags.
314#[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}