multiversx_sc_meta_lib/contract/
meta_config.rs1use 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 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 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) {
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 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 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
195fn 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}