multiversx_sc_meta_lib/contract/
meta_config.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4};
5
6use multiversx_sc::abi::ContractAbi;
7
8use crate::{
9    cargo_toml::CargoTomlContents,
10    cli::BuildArgs,
11    print_util::{print_removing_wasm_crate, print_workspace_target_dir},
12    tools::{check_tools_installed, find_current_workspace},
13};
14
15use super::{
16    sc_config::ScConfig, wasm_cargo_toml_data::WasmCargoTomlData,
17    wasm_cargo_toml_generate::generate_wasm_cargo_toml,
18};
19
20const OUTPUT_RELATIVE_PATH: &str = "output";
21const SNIPPETS_RELATIVE_PATH: &str = "interactor";
22const WASM_NO_MANAGED_EI: &str = "wasm-no-managed-ei";
23const FRAMEWORK_NAME_BASE: &str = "multiversx-sc";
24
25#[derive(Debug)]
26pub struct MetaConfig {
27    pub load_abi_git_version: bool,
28    pub output_dir: PathBuf,
29    pub snippets_dir: PathBuf,
30    pub original_contract_abi: ContractAbi,
31    pub sc_config: ScConfig,
32}
33
34impl MetaConfig {
35    pub fn create(original_contract_abi: ContractAbi, load_abi_git_version: bool) -> MetaConfig {
36        let sc_config = ScConfig::load_from_crate_or_default("..", &original_contract_abi);
37        let output_relative_path = Path::new("..").join(OUTPUT_RELATIVE_PATH);
38        let snippets_dir = Path::new("..").join(SNIPPETS_RELATIVE_PATH);
39
40        MetaConfig {
41            load_abi_git_version,
42            output_dir: output_relative_path,
43            snippets_dir,
44            original_contract_abi,
45            sc_config,
46        }
47    }
48
49    pub fn reload_sc_config(&mut self) {
50        self.sc_config = ScConfig::load_from_crate_or_default("..", &self.original_contract_abi);
51    }
52
53    /// Generates all code for the wasm crate(s).
54    pub fn generate_wasm_crates(&mut self) {
55        self.remove_unexpected_wasm_crates();
56        self.create_wasm_crate_dirs();
57        self.generate_cargo_toml_for_all_wasm_crates();
58        self.generate_wasm_src_lib();
59        copy_to_wasm_unmanaged_ei();
60    }
61
62    fn create_wasm_crate_dirs(&self) {
63        for contract_variant in &self.sc_config.contracts {
64            contract_variant.create_wasm_crate_dir();
65        }
66    }
67
68    /// Cargo.toml files for all wasm crates are generated from the main contract Cargo.toml,
69    /// by changing the package name.
70    pub fn generate_cargo_toml_for_all_wasm_crates(&mut self) {
71        let main_cargo_toml_contents =
72            CargoTomlContents::load_from_file(Path::new("..").join("Cargo.toml"));
73        let crate_name = main_cargo_toml_contents.package_name();
74
75        for contract in self.sc_config.contracts.iter() {
76            let mut framework_dependency = main_cargo_toml_contents
77                .dependency_raw_value(FRAMEWORK_NAME_BASE)
78                .expect("missing framework dependency in Cargo.toml");
79            if contract.settings.std {
80                framework_dependency.features.insert("std".to_owned());
81            }
82
83            let cargo_toml_data = WasmCargoTomlData {
84                name: contract.wasm_crate_name.clone(),
85                edition: main_cargo_toml_contents.package_edition(),
86                profile: contract.settings.profile.clone(),
87                framework_dependency,
88                contract_features: contract.settings.features.clone(),
89                contract_default_features: contract.settings.default_features,
90            };
91            generate_wasm_cargo_toml(&cargo_toml_data, crate_name.as_str())
92                .save_to_file(contract.cargo_toml_path());
93        }
94    }
95}
96
97impl MetaConfig {
98    fn generate_wasm_src_lib(&self) {
99        for contract_variant in &self.sc_config.contracts {
100            contract_variant.generate_wasm_src_lib_file();
101        }
102    }
103
104    pub fn build(&mut self, mut build_args: BuildArgs) {
105        check_tools_installed(&mut build_args);
106        adjust_target_dir_wasm(&mut build_args);
107
108        for contract_variant in &self.sc_config.contracts {
109            contract_variant
110                .build_contract(&build_args, &self.output_dir)
111                .unwrap();
112        }
113    }
114
115    /// Cleans the wasm crates and all other outputs.
116    pub fn clean(&self) {
117        self.clean_contract_crates();
118        self.remove_output_dir();
119    }
120
121    fn clean_contract_crates(&self) {
122        for contract_variant in &self.sc_config.contracts {
123            contract_variant.cargo_clean();
124        }
125    }
126
127    /// Updates the Cargo.lock on all wasm crates.
128    pub fn update(&self) {
129        for contract_variant in &self.sc_config.contracts {
130            contract_variant.cargo_update();
131        }
132    }
133
134    fn remove_output_dir(&self) {
135        fs::remove_dir_all(&self.output_dir).expect("failed to remove output directory");
136    }
137
138    fn is_expected_crate(&self, dir_name: &str) -> bool {
139        if !dir_name.starts_with("wasm") {
140            return true;
141        }
142
143        if dir_name == WASM_NO_MANAGED_EI {
144            return true;
145        }
146
147        self.sc_config
148            .contracts
149            .iter()
150            .any(|contract| contract.wasm_crate_dir_name().as_str() == dir_name)
151    }
152
153    fn remove_unexpected_wasm_crates(&self) {
154        let list_iter = fs::read_dir("..").expect("error listing contract directory");
155        for path_result in list_iter {
156            let path = path_result.expect("error processing file name in contract directory");
157            if path
158                .metadata()
159                .expect("error retrieving file metadata")
160                .is_dir()
161            {
162                let file_name = path.file_name();
163                let dir_name = file_name.to_str().expect("error processing dir name");
164                if !self.is_expected_crate(dir_name) {
165                    print_removing_wasm_crate(dir_name);
166                    fs::remove_dir_all(path.path()).unwrap_or_else(|_| {
167                        panic!("failed to remove unexpected directory {dir_name}")
168                    });
169                }
170            }
171        }
172    }
173}
174
175fn adjust_target_dir_wasm(build_args: &mut BuildArgs) {
176    if build_args.target_dir_wasm.is_some() {
177        return;
178    }
179
180    if let Some(workspace) = find_current_workspace() {
181        let target = workspace.join("target").canonicalize().unwrap();
182        if let Some(target_str) = target.as_os_str().to_str() {
183            build_args.target_dir_wasm = Some(target_str.to_string());
184            print_workspace_target_dir(target_str);
185        }
186    }
187}
188
189/// This one is useful for some of the special unmanaged EI tests in the framework.
190/// Will do nothing for regular contracts.
191fn copy_to_wasm_unmanaged_ei() {
192    let wasm_no_managed_ei_path = Path::new("..")
193        .join("wasm-no-managed-ei")
194        .join("src")
195        .join("lib.rs");
196
197    if wasm_no_managed_ei_path.exists() {
198        let wasm_lib_path = Path::new("..").join("wasm").join("src").join("lib.rs");
199        fs::copy(wasm_lib_path, wasm_no_managed_ei_path).unwrap();
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use std::path::Path;
206
207    use crate::{cargo_toml::DependencyRawValue, contract::sc_config::ContractVariantProfile};
208
209    const EXPECTED_CARGO_TOML_CONTENTS: &str =
210        "# Code generated by the multiversx-sc build system. DO NOT EDIT.
211
212# ##########################################
213# ############## AUTO-GENERATED #############
214# ##########################################
215
216[package]
217name = \"test\"
218version = \"0.0.0\"
219edition = \"2021\"
220publish = false
221
222[lib]
223crate-type = [\"cdylib\"]
224
225[profile.release]
226codegen-units = 1
227opt-level = \"z\"
228lto = true
229debug = false
230panic = \"abort\"
231overflow-checks = false
232
233[profile.dev]
234panic = \"abort\"
235
236[dependencies.test-crate-name]
237path = \"..\"
238
239[dependencies.multiversx-sc-wasm-adapter]
240version = \"x.y.z\"
241path = \"../../../../framework/wasm-adapter\"
242
243[workspace]
244members = [\".\"]
245";
246
247    #[test]
248    fn test_generate_cargo() {
249        let path = Path::new("..")
250            .join("..")
251            .join("..")
252            .join("framework")
253            .join("base");
254        let wasm_cargo_toml_data = super::WasmCargoTomlData {
255            name: "test".to_string(),
256            edition: "2021".to_string(),
257            profile: ContractVariantProfile::default(),
258            framework_dependency: DependencyRawValue {
259                version: Some("x.y.z".to_owned()),
260                path: Option::Some(path),
261                ..Default::default()
262            },
263            contract_features: Vec::<String>::new(),
264            contract_default_features: None,
265        };
266        let crate_name = "test-crate-name".to_string();
267        let generated_contents =
268            super::generate_wasm_cargo_toml(&wasm_cargo_toml_data, &crate_name);
269
270        assert_eq!(
271            generated_contents.to_string_pretty(),
272            EXPECTED_CARGO_TOML_CONTENTS.to_string()
273        );
274    }
275}