Skip to main content

multiversx_sc_meta/folder_structure/
relevant_directory.rs

1use crate::version::FrameworkVersion;
2use multiversx_sc_meta_lib::cargo_toml::{CargoTomlContents, DependencyReference};
3use std::{
4    fs::{self, DirEntry},
5    path::{Path, PathBuf},
6};
7
8/// Used for retrieving crate versions.
9pub const FRAMEWORK_CRATE_NAMES: &[&str] = &[
10    "multiversx-sc",
11    "multiversx-sc-meta",
12    "multiversx-sc-meta-lib",
13    "multiversx-sc-scenario",
14    "multiversx-sc-snippets",
15    "multiversx-sc-wasm-adapter",
16    "multiversx-sc-modules",
17    "elrond-wasm",
18    "elrond-wasm-debug",
19    "elrond-wasm-modules",
20    "elrond-wasm-node",
21    "elrond-interact-snippets",
22];
23
24pub const CARGO_TOML_FILE_NAME: &str = "Cargo.toml";
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum DirectoryType {
28    Contract,
29    Lib,
30}
31
32#[derive(Debug, Clone)]
33pub struct RelevantDirectory {
34    pub path: PathBuf,
35    pub version: DependencyReference,
36    pub upgrade_in_progress: Option<(FrameworkVersion, FrameworkVersion)>,
37    pub dir_type: DirectoryType,
38}
39
40impl RelevantDirectory {
41    pub fn dir_name(&self) -> String {
42        self.path.file_name().unwrap().to_str().unwrap().to_string()
43    }
44
45    pub fn dir_name_underscores(&self) -> String {
46        self.dir_name().replace('-', "_")
47    }
48
49    /// Gets the local meta path.
50    pub fn meta_path(&self) -> PathBuf {
51        self.path.join("meta")
52    }
53
54    /// Panics if meta crate path is missing.
55    pub fn assert_meta_path_exists(&self) {
56        let meta_path = self.meta_path();
57        assert!(
58            meta_path.exists(),
59            "Contract meta crate not found at {}",
60            meta_path.as_path().display()
61        );
62    }
63
64    /// Gets the local output path.
65    pub fn output_path(&self) -> PathBuf {
66        self.path.join("output")
67    }
68}
69
70pub struct RelevantDirectories(pub(crate) Vec<RelevantDirectory>);
71
72impl RelevantDirectories {
73    pub fn find_all(path_ref: &Path, ignore: &[String]) -> Self {
74        let canonicalized = fs::canonicalize(path_ref).unwrap_or_else(|err| {
75            panic!(
76                "error canonicalizing input path {}: {}",
77                path_ref.display(),
78                err,
79            )
80        });
81        let mut dirs = Vec::new();
82        populate_directories(canonicalized.as_path(), ignore, &mut dirs);
83        RelevantDirectories(dirs)
84    }
85
86    pub fn len(&self) -> usize {
87        self.0.len()
88    }
89
90    pub fn is_empty(&self) -> bool {
91        self.0.is_empty()
92    }
93
94    #[allow(dead_code)]
95    pub fn iter(&self) -> impl Iterator<Item = &RelevantDirectory> {
96        self.0.iter()
97    }
98
99    pub fn iter_contract_crates(&self) -> impl Iterator<Item = &RelevantDirectory> {
100        self.0
101            .iter()
102            .filter(|dir| dir.dir_type == DirectoryType::Contract)
103    }
104
105    pub fn count_for_version(&self, version: &FrameworkVersion) -> usize {
106        self.0
107            .iter()
108            .filter(|dir| dir.version.eq_framework_version(version))
109            .count()
110    }
111
112    pub fn iter_version(
113        &mut self,
114        version: &'static FrameworkVersion,
115    ) -> impl Iterator<Item = &RelevantDirectory> {
116        self.0
117            .iter()
118            .filter(move |dir| dir.version.eq_framework_version(version))
119    }
120
121    /// Marks all appropriate directories as ready for upgrade.
122    pub fn start_upgrade(&mut self, from_version: FrameworkVersion, to_version: FrameworkVersion) {
123        for dir in self.0.iter_mut() {
124            if dir.version.eq_framework_version(&from_version) {
125                dir.upgrade_in_progress = Some((from_version.clone(), to_version.clone()));
126            }
127        }
128    }
129
130    /// Updates the versions of all directories being upgraded (in memory)
131    /// and resets upgrade status.
132    pub fn finish_upgrade(&mut self) {
133        for dir in self.0.iter_mut() {
134            if let Some((_, to_version)) = &dir.upgrade_in_progress {
135                if let DependencyReference::Version(version_req) = &mut dir.version {
136                    version_req.semver = to_version.clone();
137                }
138                dir.upgrade_in_progress = None;
139            }
140        }
141    }
142}
143
144fn populate_directories(path: &Path, ignore: &[String], result: &mut Vec<RelevantDirectory>) {
145    let is_contract = is_marked_contract_crate_dir(path);
146
147    if !is_contract && path.is_dir() {
148        let read_dir = fs::read_dir(path).expect("error reading directory");
149        for child_result in read_dir {
150            let child = child_result.unwrap();
151            if can_continue_recursion(&child, ignore) {
152                populate_directories(child.path().as_path(), ignore, result);
153            }
154        }
155    }
156
157    if let Some(version) = find_framework_dependency(path) {
158        let dir_type = if is_contract {
159            DirectoryType::Contract
160        } else {
161            DirectoryType::Lib
162        };
163        result.push(RelevantDirectory {
164            path: path.to_owned(),
165            version,
166            upgrade_in_progress: None,
167            dir_type,
168        });
169    }
170}
171
172fn is_marked_contract_crate_dir(path: &Path) -> bool {
173    path.join("multiversx.json").is_file() || path.join("elrond.json").is_file()
174}
175
176fn can_continue_recursion(dir_entry: &DirEntry, blacklist: &[String]) -> bool {
177    if !dir_entry.file_type().unwrap().is_dir() {
178        return false;
179    }
180
181    if let Some(dir_name_str) = dir_entry.file_name().to_str() {
182        if blacklist.iter().any(|ignored| ignored == dir_name_str) {
183            return false;
184        }
185
186        // do not explore hidden folders
187        !dir_name_str.starts_with('.')
188    } else {
189        false
190    }
191}
192
193fn load_cargo_toml_contents(dir_path: &Path) -> Option<CargoTomlContents> {
194    let cargo_toml_path = dir_path.join(CARGO_TOML_FILE_NAME);
195    if cargo_toml_path.is_file() {
196        Some(CargoTomlContents::load_from_file(cargo_toml_path))
197    } else {
198        None
199    }
200}
201
202impl RelevantDirectory {
203    #[allow(unused)]
204    pub fn cargo_toml_contents(&self) -> Option<CargoTomlContents> {
205        load_cargo_toml_contents(self.path.as_path())
206    }
207}
208
209fn find_framework_dependency(dir_path: &Path) -> Option<DependencyReference> {
210    if let Some(cargo_toml_contents) = load_cargo_toml_contents(dir_path) {
211        for &crate_name in FRAMEWORK_CRATE_NAMES {
212            if let Some(dep_raw) = cargo_toml_contents.dependency_raw_value(crate_name) {
213                return Some(dep_raw.interpret());
214            }
215        }
216    }
217
218    None
219}