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    /// Triggers a build in all wasm crates.
105    ///
106    /// The following operations are performed in this order:
107    /// 1. check installed tools
108    /// 2. update the wasm crates, if needed
109    /// 3. build each wasm crate, and copy the outputs to the output directory  
110    pub fn build(&mut self, mut build_args: BuildArgs) {
111        check_tools_installed(&mut build_args);
112        adjust_target_dir_wasm(&mut build_args);
113
114        for contract_variant in &self.sc_config.contracts {
115            contract_variant
116                .build_contract(&build_args, &self.output_dir)
117                .unwrap();
118        }
119    }
120
121    /// Cleans the wasm crates and all other outputs.
122    pub fn clean(&self) {
123        self.clean_contract_crates();
124        self.remove_output_dir();
125    }
126
127    fn clean_contract_crates(&self) {
128        for contract_variant in &self.sc_config.contracts {
129            contract_variant.cargo_clean();
130        }
131    }
132
133    /// Updates the Cargo.lock on all wasm crates.
134    pub fn update(&self) {
135        for contract_variant in &self.sc_config.contracts {
136            contract_variant.cargo_update();
137        }
138    }
139
140    fn remove_output_dir(&self) {
141        fs::remove_dir_all(&self.output_dir).expect("failed to remove output directory");
142    }
143
144    fn is_expected_crate(&self, dir_name: &str) -> bool {
145        if !dir_name.starts_with("wasm") {
146            return true;
147        }
148
149        if dir_name == WASM_NO_MANAGED_EI {
150            return true;
151        }
152
153        self.sc_config
154            .contracts
155            .iter()
156            .any(|contract| contract.wasm_crate_dir_name().as_str() == dir_name)
157    }
158
159    fn remove_unexpected_wasm_crates(&self) {
160        let list_iter = fs::read_dir("..").expect("error listing contract directory");
161        for path_result in list_iter {
162            let path = path_result.expect("error processing file name in contract directory");
163            if path
164                .metadata()
165                .expect("error retrieving file metadata")
166                .is_dir()
167            {
168                let file_name = path.file_name();
169                let dir_name = file_name.to_str().expect("error processing dir name");
170                if !self.is_expected_crate(dir_name) {
171                    print_removing_wasm_crate(dir_name);
172                    fs::remove_dir_all(path.path()).unwrap_or_else(|_| {
173                        panic!("failed to remove unexpected directory {dir_name}")
174                    });
175                }
176            }
177        }
178    }
179}
180
181fn adjust_target_dir_wasm(build_args: &mut BuildArgs) {
182    if build_args.target_dir_wasm.is_some() {
183        return;
184    }
185
186    if let Some(workspace) = find_current_workspace() {
187        let target = workspace.join("target").canonicalize().unwrap();
188        if let Some(target_str) = target.as_os_str().to_str() {
189            build_args.target_dir_wasm = Some(target_str.to_string());
190            print_workspace_target_dir(target_str);
191        }
192    }
193}
194
195/// This one is useful for some of the special unmanaged EI tests in the framework.
196/// Will do nothing for regular contracts.
197fn copy_to_wasm_unmanaged_ei() {
198    let wasm_no_managed_ei_path = Path::new("..")
199        .join("wasm-no-managed-ei")
200        .join("src")
201        .join("lib.rs");
202
203    if wasm_no_managed_ei_path.exists() {
204        let wasm_lib_path = Path::new("..").join("wasm").join("src").join("lib.rs");
205        fs::copy(wasm_lib_path, wasm_no_managed_ei_path).unwrap();
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use std::path::Path;
212
213    use crate::{cargo_toml::DependencyRawValue, contract::sc_config::ContractVariantProfile};
214
215    const EXPECTED_CARGO_TOML_CONTENTS: &str =
216        "# Code generated by the multiversx-sc build system. DO NOT EDIT.
217
218# ##########################################
219# ############## AUTO-GENERATED #############
220# ##########################################
221
222[package]
223name = \"test\"
224version = \"0.0.0\"
225edition = \"2021\"
226publish = false
227
228[lib]
229crate-type = [\"cdylib\"]
230
231[profile.release]
232codegen-units = 1
233opt-level = \"z\"
234lto = true
235debug = false
236panic = \"abort\"
237overflow-checks = false
238
239[profile.dev]
240panic = \"abort\"
241
242[dependencies.test-crate-name]
243path = \"..\"
244
245[dependencies.multiversx-sc-wasm-adapter]
246version = \"x.y.z\"
247path = \"../../../../framework/wasm-adapter\"
248
249[workspace]
250members = [\".\"]
251";
252
253    #[test]
254    fn test_generate_cargo() {
255        let path = Path::new("..")
256            .join("..")
257            .join("..")
258            .join("framework")
259            .join("base");
260        let wasm_cargo_toml_data = super::WasmCargoTomlData {
261            name: "test".to_string(),
262            edition: "2021".to_string(),
263            profile: ContractVariantProfile::default(),
264            framework_dependency: DependencyRawValue {
265                version: Some("x.y.z".to_owned()),
266                path: Option::Some(path),
267                ..Default::default()
268            },
269            contract_features: Vec::<String>::new(),
270            contract_default_features: None,
271        };
272        let crate_name = "test-crate-name".to_string();
273        let generated_contents =
274            super::generate_wasm_cargo_toml(&wasm_cargo_toml_data, &crate_name);
275
276        assert_eq!(
277            generated_contents.to_string_pretty(),
278            EXPECTED_CARGO_TOML_CONTENTS.to_string()
279        );
280    }
281}