Skip to main content

wapm_cli/data/lock/
migrate.rs

1//! Code for migrating between Lockfile versions
2//!
3//! Lock file versions are stored as the first line in the lockfile, of the form:
4//! `# Lockfile vN` where `N` is 1 or more digits.
5
6use crate::data::lock::lockfile::{LockfileV2, LockfileV3, LockfileV4};
7use crate::data::lock::lockfile_command::LockfileCommand;
8use crate::data::lock::lockfile_module::{LockfileModuleV2, LockfileModuleV3, LockfileModuleV4};
9use crate::data::manifest::PACKAGES_DIR_NAME;
10use crate::dataflow::lockfile_packages::LockfileError;
11use crate::dataflow::normalize_global_namespace_package_name;
12
13use lazy_static::lazy_static;
14use regex::Regex;
15use std::collections::*;
16use std::path::{Path, PathBuf};
17
18/// All the lockfile versions
19pub enum LockfileVersion {
20    V1(LockfileV2),
21    V2(LockfileV2),
22    V3(LockfileV3),
23    V4(LockfileV4),
24}
25
26lazy_static! {
27    static ref VER_NUM_RE: Regex = Regex::new(r"^# Lockfile v(\d+)$").unwrap();
28}
29
30impl LockfileVersion {
31    pub fn from_lockfile_string(raw_string: &str) -> Result<Self, LockfileError> {
32        let first_line = raw_string
33            .lines()
34            .next()
35            .ok_or(LockfileError::InvalidOrMissingVersion)?;
36        let lockfile_version = (*VER_NUM_RE)
37            .captures_iter(first_line)
38            .map(|captures| captures[1].parse::<usize>())
39            .next()
40            .ok_or(LockfileError::InvalidOrMissingVersion)?
41            .map_err(|_| LockfileError::InvalidOrMissingVersion)?;
42
43        match lockfile_version {
44            1 => {
45                let lockfile_v1 = toml::from_str::<LockfileV2>(raw_string)
46                    .map_err(|e| LockfileError::LockfileTomlParseError(e.to_string()))?;
47                Ok(LockfileVersion::V1(lockfile_v1))
48            }
49            2 => {
50                let lockfile_v2 = toml::from_str::<LockfileV2>(raw_string)
51                    .map_err(|e| LockfileError::LockfileTomlParseError(e.to_string()))?;
52                Ok(LockfileVersion::V2(lockfile_v2))
53            }
54            3 => {
55                let lockfile_v3 = toml::from_str::<LockfileV3>(raw_string)
56                    .map_err(|e| LockfileError::LockfileTomlParseError(e.to_string()))?;
57                Ok(LockfileVersion::V3(lockfile_v3))
58            }
59            4 => {
60                let lockfile_v4 = toml::from_str::<LockfileV4>(raw_string)
61                    .map_err(|e| LockfileError::LockfileTomlParseError(e.to_string()))?;
62                Ok(LockfileVersion::V4(lockfile_v4))
63            }
64            0 => Err(LockfileError::InvalidOrMissingVersion),
65            _ => Err(LockfileError::VersionTooHigh),
66        }
67    }
68}
69
70pub fn fix_up_v1_package_names(lockfile: &mut LockfileV2) {
71    for command in lockfile.commands.iter_mut() {
72        let (_, command): (&String, &mut LockfileCommand) = command;
73        let package_name =
74            normalize_global_namespace_package_name(command.package_name.as_str().into());
75        command.package_name = package_name.to_string();
76    }
77    lockfile.modules = lockfile
78        .modules
79        .clone()
80        .into_iter()
81        .map(|(package_name, mut versions)| {
82            let correct_name =
83                normalize_global_namespace_package_name(package_name.as_str().into()).to_string();
84
85            for (_version, modules) in versions.iter_mut() {
86                for (_module_name, module) in modules.iter_mut() {
87                    let module: &mut LockfileModuleV2 = module;
88                    module.package_name = correct_name.clone();
89                }
90            }
91            (correct_name, versions)
92        })
93        .collect();
94}
95
96pub fn convert_lockfilev2_to_v3(lockfile: LockfileV2) -> LockfileV3 {
97    let mut modules: BTreeMap<String, _> = Default::default();
98    for (k1, version_map) in lockfile.modules.into_iter() {
99        let mut ver_map: BTreeMap<semver::Version, BTreeMap<_, _>> = Default::default();
100        for (k2, module_map) in version_map.into_iter() {
101            let mut name_map: BTreeMap<String, LockfileModuleV3> = Default::default();
102            for (k3, module_data) in module_map.into_iter() {
103                let module = LockfileModuleV3 {
104                    name: module_data.name,
105                    package_version: module_data.package_version,
106                    package_name: module_data.package_name,
107                    source: module_data.source,
108                    resolved: module_data.resolved,
109                    abi: module_data.abi,
110                    entry: module_data.entry.clone(),
111                    root: {
112                        let mut entry = PathBuf::from(module_data.entry);
113                        // remove the module.wasm file
114                        entry.pop();
115                        entry.to_string_lossy().to_string()
116                    },
117                    prehashed_module_key: None,
118                };
119                name_map.insert(k3, module);
120            }
121            ver_map.insert(k2, name_map);
122        }
123        modules.insert(k1, ver_map);
124    }
125    LockfileV3 {
126        modules,
127        commands: lockfile.commands,
128    }
129}
130
131pub fn convert_lockfilev3_to_v4(lockfile: LockfileV3, directory: &Path) -> LockfileV4 {
132    let dir_prefix = directory.join(PACKAGES_DIR_NAME);
133
134    let mut modules: BTreeMap<String, _> = Default::default();
135    for (k1, version_map) in lockfile.modules.into_iter() {
136        let mut ver_map: BTreeMap<semver::Version, BTreeMap<_, _>> = Default::default();
137        for (k2, module_map) in version_map.into_iter() {
138            let mut name_map: BTreeMap<String, LockfileModuleV4> = Default::default();
139            for (k3, module_data) in module_map.into_iter() {
140                let package_path = format!(
141                    "{}@{}",
142                    &module_data.package_name, &module_data.package_version
143                );
144                let module = LockfileModuleV4 {
145                    name: module_data.name,
146                    package_version: module_data.package_version,
147                    package_name: module_data.package_name,
148                    resolved: module_data.resolved,
149                    resolved_source: module_data.source,
150                    abi: module_data.abi,
151                    source: {
152                        let full_prefix = dir_prefix.join(&package_path);
153                        // Somtimes .entry will be absolute and other times relative
154                        // We make it absolute to make it easier to work with and avoid errors
155                        let path = directory.join(&module_data.entry);
156                        path.strip_prefix(&full_prefix)
157                            .expect("Corrupt data in Lockfile, please delete it and try again")
158                            .to_string_lossy()
159                            .to_string()
160                    },
161                    package_path,
162                    prehashed_module_key: module_data.prehashed_module_key,
163                };
164                name_map.insert(k3, module);
165            }
166            ver_map.insert(k2, name_map);
167        }
168        modules.insert(k1, ver_map);
169    }
170    LockfileV4 {
171        modules,
172        commands: lockfile.commands,
173    }
174}
175
176#[cfg(test)]
177mod test {
178    use super::*;
179    use crate::data::lock::lockfile::{LockfileV2, LockfileV3, LockfileV4};
180    use std::path::Path;
181
182    #[test]
183    fn test_fix_up_v1_lockfile() {
184        let v1_lockfile_toml = toml! {
185            [modules.sqlite."0.1.1".sqlite]
186            name = "sqlite"
187            package_version = "0.1.1"
188            package_name = "sqlite"
189            source = "registry+sqlite"
190            resolved = "https://registry-cdn.wapm.dev/packages/_/sqlite/sqlite-0.1.1.tar.gz"
191            abi = "emscripten"
192            entry = "wapm_packages\\_\\sqlite@0.1.1\\sqlite.wasm"
193            [commands.sqlite]
194            name = "sqlite"
195            package_name = "sqlite"
196            package_version = "0.1.1"
197            module = "sqlite"
198            is_top_level_dependency = true
199        };
200
201        let mut lockfile: LockfileV2 = toml::from_str(&v1_lockfile_toml.to_string()).unwrap();
202
203        let v2_lockfile_toml = toml! {
204            [modules."_/sqlite"."0.1.1".sqlite]
205            name = "sqlite"
206            package_version = "0.1.1"
207            package_name = "_/sqlite"
208            source = "registry+sqlite"
209            resolved = "https://registry-cdn.wapm.dev/packages/_/sqlite/sqlite-0.1.1.tar.gz"
210            abi = "emscripten"
211            entry = "wapm_packages\\_\\sqlite@0.1.1\\sqlite.wasm"
212            [commands.sqlite]
213            name = "sqlite"
214            package_name = "_/sqlite"
215            package_version = "0.1.1"
216            module = "sqlite"
217            is_top_level_dependency = true
218        };
219
220        let expected_v2_lockfile: LockfileV2 =
221            toml::from_str(&v2_lockfile_toml.to_string()).unwrap();
222
223        fix_up_v1_package_names(&mut lockfile);
224
225        assert_eq!(expected_v2_lockfile, lockfile);
226    }
227
228    #[test]
229    fn upgrade_to_v3() {
230        let v1_lockfile_toml = toml! {
231            [modules.sqlite."0.1.1".sqlite]
232            name = "sqlite"
233            package_version = "0.1.1"
234            package_name = "sqlite"
235            source = "registry+sqlite"
236            resolved = "https://registry-cdn.wapm.dev/packages/_/sqlite/sqlite-0.1.1.tar.gz"
237            abi = "emscripten"
238            entry = "wapm_packages/_/sqlite@0.1.1/sqlite.wasm"
239            [commands.sqlite]
240            name = "sqlite"
241            package_name = "sqlite"
242            package_version = "0.1.1"
243            module = "sqlite"
244            is_top_level_dependency = true
245        };
246
247        let mut lockfile_v1: LockfileV2 = toml::from_str(&v1_lockfile_toml.to_string()).unwrap();
248
249        let v3_lockfile_toml = toml! {
250            [modules."_/sqlite"."0.1.1".sqlite]
251            name = "sqlite"
252            package_version = "0.1.1"
253            package_name = "_/sqlite"
254            source = "registry+sqlite"
255            resolved = "https://registry-cdn.wapm.dev/packages/_/sqlite/sqlite-0.1.1.tar.gz"
256            abi = "emscripten"
257            entry = "wapm_packages/_/sqlite@0.1.1/sqlite.wasm"
258            root = "wapm_packages/_/sqlite@0.1.1"
259            [commands.sqlite]
260            name = "sqlite"
261            package_name = "_/sqlite"
262            package_version = "0.1.1"
263            module = "sqlite"
264            is_top_level_dependency = true
265        };
266
267        let expected_v3_lockfile: LockfileV3 =
268            toml::from_str(&v3_lockfile_toml.to_string()).unwrap();
269
270        fix_up_v1_package_names(&mut lockfile_v1);
271        let converted_lockfile_v3 = convert_lockfilev2_to_v3(lockfile_v1);
272
273        assert_eq!(expected_v3_lockfile, converted_lockfile_v3);
274    }
275
276    #[test]
277    fn upgrade_to_v4() {
278        let directory = Path::new("/home/shiba/project");
279
280        let v3_lockfile_toml = toml! {
281            [modules."_/sqlite"."0.1.1".sqlite]
282            name = "sqlite"
283            package_version = "0.1.1"
284            package_name = "_/sqlite"
285            source = "registry+sqlite"
286            resolved = "https://registry-cdn.wapm.dev/packages/_/sqlite/sqlite-0.1.1.tar.gz"
287            abi = "emscripten"
288            entry = "/home/shiba/project/wapm_packages/_/sqlite@0.1.1/sqlite.wasm"
289            root = "/home/shiba/project/wapm_packages/_/sqlite@0.1.1"
290            [commands.sqlite]
291            name = "sqlite"
292            package_name = "_/sqlite"
293            package_version = "0.1.1"
294            module = "sqlite"
295            is_top_level_dependency = true
296        };
297
298        let v3_lockfile: LockfileV3 = toml::from_str(&v3_lockfile_toml.to_string()).unwrap();
299
300        let v4_lockfile_toml = toml! {
301            [modules."_/sqlite"."0.1.1".sqlite]
302            name = "sqlite"
303            package_version = "0.1.1"
304            package_name = "_/sqlite"
305            package_path = "_/sqlite@0.1.1"
306            resolved = "https://registry-cdn.wapm.dev/packages/_/sqlite/sqlite-0.1.1.tar.gz"
307            resolved_source = "registry+sqlite"
308            abi = "emscripten"
309            source = "sqlite.wasm"
310            [commands.sqlite]
311            name = "sqlite"
312            package_name = "_/sqlite"
313            package_version = "0.1.1"
314            module = "sqlite"
315            is_top_level_dependency = true
316        };
317
318        let v4_lockfile: LockfileV4 = toml::from_str(&v4_lockfile_toml.to_string()).unwrap();
319
320        let v4_lockfile_converted = convert_lockfilev3_to_v4(v3_lockfile, directory);
321
322        assert_eq!(v4_lockfile, v4_lockfile_converted);
323    }
324}