Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / config / stale_package_remover.rs
4.4 kB 127 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2025 The Gleam contributors 3 4use crate::manifest::Manifest; 5use crate::requirement::Requirement; 6use ecow::EcoString; 7use hexpm::version::Version; 8use std::collections::{HashMap, HashSet}; 9 10#[derive(Debug)] 11pub struct StalePackageRemover<'a> { 12 // These are the packages for which the requirement or their parents 13 // requirement has not changed. 14 fresh: HashSet<&'a str>, 15 locked: HashMap<EcoString, &'a Vec<EcoString>>, 16} 17 18impl<'a> StalePackageRemover<'a> { 19 pub fn fresh_and_locked( 20 requirements: &'a HashMap<EcoString, Requirement>, 21 manifest: &'a Manifest, 22 ) -> HashMap<EcoString, Version> { 23 let locked = manifest 24 .packages 25 .iter() 26 .map(|p| (p.name.clone(), &p.requirements)) 27 .collect(); 28 Self { 29 fresh: HashSet::new(), 30 locked, 31 } 32 .run(requirements, manifest) 33 } 34 35 fn run( 36 &mut self, 37 requirements: &'a HashMap<EcoString, Requirement>, 38 manifest: &'a Manifest, 39 ) -> HashMap<EcoString, Version> { 40 // Record all the requirements that have not changed 41 for (name, requirement) in requirements { 42 if manifest.requirements.get(name) != Some(requirement) { 43 continue; // This package has changed, don't record it 44 } 45 46 // Recursively record the package and its deps as being fresh 47 self.record_tree_fresh(name); 48 } 49 50 // Return all the previously resolved packages that have not been 51 // recorded as fresh 52 manifest 53 .packages 54 .iter() 55 .filter(|package| { 56 let new = requirements.contains_key(package.name.as_str()) 57 && !manifest.requirements.contains_key(package.name.as_str()); 58 let fresh = self.fresh.contains(package.name.as_str()); 59 let locked = !new && fresh; 60 if !locked { 61 tracing::info!(name = package.name.as_str(), "unlocking_stale_package"); 62 } 63 locked 64 }) 65 .map(|package| (package.name.clone(), package.version.clone())) 66 .collect() 67 } 68 69 fn record_tree_fresh(&mut self, name: &'a str) { 70 // Record the top level package 71 let _ = self.fresh.insert(name); 72 73 let Some(deps) = self.locked.get(name) else { 74 // If the package is not in the manifest then it means that the package is an optional 75 // dependency that has not been included. That or someone has been editing the manifest 76 // and broken it, but let's hope that's not the case. 77 return; 78 }; 79 80 // Record each of its deps recursively 81 for package in *deps { 82 self.record_tree_fresh(package); 83 } 84 } 85} 86 87#[cfg(test)] 88mod tests { 89 use super::*; 90 use crate::manifest::{Base16Checksum, Manifest, ManifestPackage, ManifestPackageSource}; 91 use crate::requirement::Requirement; 92 use hexpm::version::{Range, Version}; 93 use std::collections::HashMap; 94 95 // https://github.com/gleam-lang/gleam/issues/4152 96 #[test] 97 fn optional_package_not_in_manifest() { 98 let requirements = HashMap::from_iter([( 99 "required_package".into(), 100 Requirement::Hex { 101 version: Range::new("1.0.0".into()).unwrap(), 102 }, 103 )]); 104 let manifest = Manifest { 105 requirements: requirements.clone(), 106 packages: vec![ManifestPackage { 107 name: "required_package".into(), 108 version: Version::new(1, 0, 0), 109 build_tools: vec!["gleam".into()], 110 otp_app: None, 111 requirements: vec![ 112 // NOTE: this package isn't in the manifest. This will have been because it is 113 // an optional dep of `required_package`. 114 "optional_package".into(), 115 ], 116 source: ManifestPackageSource::Hex { 117 outer_checksum: Base16Checksum(vec![]), 118 }, 119 }], 120 }; 121 122 assert_eq!( 123 StalePackageRemover::fresh_and_locked(&requirements, &manifest), 124 HashMap::from_iter([("required_package".into(), Version::new(1, 0, 0))]) 125 ); 126 } 127}