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) {
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 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 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
189fn 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}