on master help sign in
Merge pull request #6406 from commercialhaskell/latest-version-fast
4fe6e23Adam Bergmark
etc/commenter/latest-version/latest-version.cabal+1−1
@@ -12,7 +12,7 @@ cabal-version: >=1.10
12extra-source-files: README.md
12extra-source-files: README.md
13
13
14executable latest-version
14executable latest-version
15− ghc-options: -Wall
15+ ghc-options: -Wall -threaded
16 hs-source-dirs: src
16 hs-source-dirs: src
17 main-is: Main.hs
17 main-is: Main.hs
18 default-language: Haskell2010
18 default-language: Haskell2010
etc/commenter/latest-version/src/Main.hs+17−7
@@ -9,10 +9,20 @@ import System.Environment
9import qualified Data.Map as Map
9import qualified Data.Map as Map
10
10
11main :: IO ()
11main :: IO ()
12−main =
12+main = do
13− runPantryApp $
13+ args <- getArgs
14− liftIO . putStrLn
14+ (onlyVersion, packages) <- pure $ case args of
15+ "only-version" : packages -> (True, packages)
16+ packages -> (False, packages)
17+ runPantryApp $ liftIO . putStrLn . unlines_ =<< mapM (latestVersion onlyVersion) packages
18+ where
19+ -- unlines adds an extra trailing newline which can be annoying...
20+ unlines_ = intercalate "\n"
21+
22+latestVersion :: (HasPantryConfig env, HasLogFunc env) => Bool -> String -> RIO env String
23+latestVersion onlyVersion name = fmap (displayVersion onlyVersion) . getVersion . mkPackageName $ name
24+ where
15− . intercalate "." . map show . versionNumbers
25+ showVersion = intercalate "." . map show . versionNumbers . fst . head . Map.toDescList
16− . fst . head . Map.toDescList
17− =<< getHackagePackageVersions YesRequireHackageIndex IgnorePreferredVersions
26+ getVersion = getHackagePackageVersions YesRequireHackageIndex UsePreferredVersions
18− . mkPackageName =<< head <$> liftIO getArgs
27+ displayVersion True v = showVersion v
28+ displayVersion False v = name <> "-" <> showVersion v
etc/commenter/latest-version/stack.yaml.locknew file+13
@@ -0,0 +1,13 @@
1+# This file was autogenerated by Stack.
2+# You should not edit this file by hand.
3+# For more information, please see the documentation at:
4+# https://docs.haskellstack.org/en/stable/lock_files
5+
6+packages: []
7+snapshots:
8+- completed:
9+ size: 586296
10+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/18.yaml
11+ sha256: 63539429076b7ebbab6daa7656cfb079393bf644971156dc349d7c0453694ac2
12+ original:
13+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/18.yaml
etc/commenter/src/lib.rs+84−43
@@ -30,8 +30,8 @@ pub fn add(lib: Vec<String>, test: Vec<String>, bench: Vec<String>) {
30}
30}
31
31
32enum VersionTag {
32enum VersionTag {
33− Manual(String),
33+ Manual(Version),
34− Auto(String),
34+ Auto(Version),
35}
35}
36
36
37impl VersionTag {
37impl VersionTag {
@@ -42,7 +42,7 @@ impl VersionTag {
42 }
42 }
43 }
43 }
44
44
45− fn version(&self) -> &str {
45+ fn version(&self) -> &Version {
46 match self {
46 match self {
47 VersionTag::Manual(s) => s,
47 VersionTag::Manual(s) => s,
48 VersionTag::Auto(s) => s,
48 VersionTag::Auto(s) => s,
@@ -52,41 +52,50 @@ impl VersionTag {
52
52
53pub fn outdated() {
53pub fn outdated() {
54 let mut all: Vec<String> = vec![];
54 let mut all: Vec<String> = vec![];
55− let versioned = handle(false, |_loc, lines| {
55+ let (versioned, disabled) = handle(false, |_loc, lines| {
56 all.extend(lines);
56 all.extend(lines);
57 vec![]
57 vec![]
58 });
58 });
59+
60+ for DisabledPackage { package } in disabled {
61+ println!("WARN: {package} is disabled without a noted version");
62+ }
63+
59− let mut map: BTreeMap<String, VersionTag> = BTreeMap::new();
64+ let mut map: BTreeMap<Package, VersionTag> = BTreeMap::new();
60 for VersionedPackage { package, version } in versioned {
65 for VersionedPackage { package, version } in versioned {
61 map.insert(package, VersionTag::Manual(version));
66 map.insert(package, VersionTag::Manual(version));
62 }
67 }
63− let mut support: BTreeMap<(String, String), BTreeSet<(String, String)>> = BTreeMap::new();
68+ let mut support: BTreeMap<(Package, Version), BTreeSet<(Package, Version)>> = BTreeMap::new();
64 for v in all.into_iter() {
69 for v in all.into_iter() {
65 let caps = regex!("tried ([^ ]+)-([^,-]+),").captures(&v).unwrap();
70 let caps = regex!("tried ([^ ]+)-([^,-]+),").captures(&v).unwrap();
66− let package = caps.get(1).unwrap().as_str().to_owned();
71+ let package = Package(caps.get(1).unwrap().as_str().to_owned());
67− let version = caps.get(2).unwrap().as_str().to_owned();
72+ let version = Version(caps.get(2).unwrap().as_str().to_owned());
68 map.insert(package.clone(), VersionTag::Auto(version.clone()));
73 map.insert(package.clone(), VersionTag::Auto(version.clone()));
69
74
70 if let Some(caps) = regex!("does not support: ([^ ]+)-([^-]+)").captures(&v) {
75 if let Some(caps) = regex!("does not support: ([^ ]+)-([^-]+)").captures(&v) {
71− let dep_package = caps.get(1).unwrap().as_str().to_owned();
76+ let dep_package = Package(caps.get(1).unwrap().as_str().to_owned());
72− let dep_version = caps.get(2).unwrap().as_str().to_owned();
77+ let dep_version = Version(caps.get(2).unwrap().as_str().to_owned());
73 let entry = support.entry((dep_package, dep_version)).or_default();
78 let entry = support.entry((dep_package, dep_version)).or_default();
74 entry.insert((package, version));
79 entry.insert((package, version));
75 }
80 }
76 }
81 }
77
82
78− let entries = map.len() + support.len();
83+ let latest_versions = {
79− let mut i = 0;
84+ let mut packages: Vec<Package> = map.iter().map(|(package, _)| package.clone()).collect();
85+ packages.append(
86+ &mut support
87+ .iter()
88+ .map(|((package, _), _)| package.clone())
89+ .collect(),
90+ );
91+ latest_version(packages.into_iter())
92+ };
80
93
81 for (package, version) in map {
94 for (package, version) in map {
82 if is_boot(&package) {
95 if is_boot(&package) {
83 continue;
96 continue;
84 }
97 }
85− if i % 100 == 0 {
86− println!("{:02}%", ((i as f64 / entries as f64) * 100.0).floor());
87− }
88− i += 1;
89− let latest = latest_version(&package);
98+ let latest = latest_versions.get(&package).unwrap();
90 if version.version() != latest {
99 if version.version() != latest {
91 println!(
100 println!(
92 "{package} mismatch, {tag}: {version}, hackage: {latest}",
101 "{package} mismatch, {tag}: {version}, hackage: {latest}",
@@ -101,12 +110,8 @@ pub fn outdated() {
101 continue;
110 continue;
102 }
111 }
103
112
104− if i % 100 == 0 {
105− println!("{:02}%", ((i as f64 / entries as f64) * 100.0).floor());
106− }
107− i += 1;
108− let latest = latest_version(&package);
113+ let latest = latest_versions.get(&package).unwrap();
109− if version != latest {
114+ if &version != latest {
110 let max = 3;
115 let max = 3;
111 let dependents_stripped = dependents.len().saturating_sub(max);
116 let dependents_stripped = dependents.len().saturating_sub(max);
112 let dependents = dependents
117 let dependents = dependents
@@ -128,7 +133,7 @@ pub fn outdated() {
128 }
133 }
129}
134}
130
135
131−fn is_boot(package: &str) -> bool {
136+fn is_boot(package: &Package) -> bool {
132 [
137 [
133 "Cabal",
138 "Cabal",
134 "base",
139 "base",
@@ -151,13 +156,13 @@ fn is_boot(package: &str) -> bool {
151 "text",
156 "text",
152 "time",
157 "time",
153 ]
158 ]
154− .contains(&package)
159+ .contains(&&*package.0)
155}
160}
156
161
157−fn latest_version(pkg: &str) -> String {
162+fn latest_version(packages: impl Iterator<Item = Package>) -> BTreeMap<Package, Version> {
158 String::from_utf8(
163 String::from_utf8(
159 Command::new("latest-version")
164 Command::new("latest-version")
160− .args([pkg])
165+ .args(packages.map(|p| p.0))
161 .output()
166 .output()
162 .unwrap()
167 .unwrap()
163 .stdout,
168 .stdout,
@@ -165,6 +170,12 @@ fn latest_version(pkg: &str) -> String {
165 .unwrap()
170 .unwrap()
166 .trim()
171 .trim()
167 .to_owned()
172 .to_owned()
173+ .lines()
174+ .map(|s| {
175+ let VersionedPackage { package, version } = parse_versioned_package_canonical(s).unwrap();
176+ (package, version)
177+ })
178+ .collect()
168}
179}
169
180
170enum State {
181enum State {
@@ -178,37 +189,67 @@ enum State {
178}
189}
179
190
180struct VersionedPackage {
191struct VersionedPackage {
181− package: String,
192+ package: Package,
182− version: String,
193+ version: Version,
183}
194}
184
195
196+fn parse_versioned_package_canonical(s: &str) -> Option<VersionedPackage> {
197+ if let Some(caps) = regex!(r#"^(.+)-([\d.]+)$"#).captures(s) {
198+ let package = Package(caps.get(1).unwrap().as_str().to_owned());
199+ let version = Version(caps.get(2).unwrap().as_str().to_owned());
200+ Some(VersionedPackage { package, version })
201+ } else {
202+ None
203+ }
204+}
205+
185−fn parse_versioned_package(s: &str) -> Option<VersionedPackage> {
206+fn parse_versioned_package_yaml(s: &str) -> Option<VersionedPackage> {
186 if let Some(caps) = regex!(r#"- *([^ ]+) < *0 *# *([\d.]+)"#).captures(s) {
207 if let Some(caps) = regex!(r#"- *([^ ]+) < *0 *# *([\d.]+)"#).captures(s) {
187− let package = caps.get(1).unwrap().as_str().to_owned();
208+ let package = Package(caps.get(1).unwrap().as_str().to_owned());
188− let version = caps.get(2).unwrap().as_str().to_owned();
209+ let version = Version(caps.get(2).unwrap().as_str().to_owned());
189 Some(VersionedPackage { package, version })
210 Some(VersionedPackage { package, version })
190 } else if let Some(caps) = regex!(r#"- *([^ ]+) *# *([\d.]+)"#).captures(s) {
211 } else if let Some(caps) = regex!(r#"- *([^ ]+) *# *([\d.]+)"#).captures(s) {
191− let package = caps.get(1).unwrap().as_str().to_owned();
212+ let package = Package(caps.get(1).unwrap().as_str().to_owned());
192− let version = caps.get(2).unwrap().as_str().to_owned();
213+ let version = Version(caps.get(2).unwrap().as_str().to_owned());
193 Some(VersionedPackage { package, version })
214 Some(VersionedPackage { package, version })
194 } else {
215 } else {
195 None
216 None
196 }
217 }
197}
218}
198
219
220+struct DisabledPackage {
221+ package: String,
222+}
223+
224+fn parse_disabled_package(s: &str) -> Option<DisabledPackage> {
225+ if !regex!(r#"- *([^ ]+) < *0 *# tried"#).is_match(s) {
226+ if let Some(caps) = regex!(r#"- *([^ ]+) < *0 *# *[^\d]"#).captures(s) {
227+ let package = caps.get(1).unwrap().as_str().to_owned();
228+ Some(DisabledPackage { package })
229+ } else {
230+ None
231+ }
232+ } else {
233+ None
234+ }
235+}
236+
199−fn handle<F>(write: bool, mut f: F) -> Vec<VersionedPackage>
237+fn handle<F>(write: bool, mut f: F) -> (Vec<VersionedPackage>, Vec<DisabledPackage>)
200where
238where
201 F: FnMut(Location, Vec<String>) -> Vec<String>,
239 F: FnMut(Location, Vec<String>) -> Vec<String>,
202{
240{
203 let path = "build-constraints.yaml";
241 let path = "build-constraints.yaml";
204 let mut new_lines: Vec<String> = vec![];
242 let mut new_lines: Vec<String> = vec![];
205 let mut versioned_packages: Vec<VersionedPackage> = vec![];
243 let mut versioned_packages: Vec<VersionedPackage> = vec![];
244+ let mut disabled_packages: Vec<DisabledPackage> = vec![];
206
245
207 let mut state = State::LookingForLibBounds;
246 let mut state = State::LookingForLibBounds;
208 let mut buf = vec![];
247 let mut buf = vec![];
209 for line in read_lines(path).map(|s| s.unwrap()) {
248 for line in read_lines(path).map(|s| s.unwrap()) {
210− if let Some(versioned_package) = parse_versioned_package(&line) {
249+ if let Some(versioned_package) = parse_versioned_package_yaml(&line) {
211 versioned_packages.push(versioned_package);
250 versioned_packages.push(versioned_package);
251+ } else if let Some(disabled_package) = parse_disabled_package(&line) {
252+ disabled_packages.push(disabled_package);
212 }
253 }
213
254
214 match state {
255 match state {
@@ -279,7 +320,7 @@ where
279 file.flush().unwrap();
320 file.flush().unwrap();
280 }
321 }
281
322
282− versioned_packages
323+ (versioned_packages, disabled_packages)
283}
324}
284
325
285enum Location {
326enum Location {
@@ -298,7 +339,7 @@ where
298
339
299#[derive(Deserialize)]
340#[derive(Deserialize)]
300struct SnapshotYaml {
341struct SnapshotYaml {
301− // flags: BTreeMap<PackageName, BTreeMap<PackageFlag, bool>>,
342+ // flags: BTreeMap<Package, BTreeMap<PackageFlag, bool>>,
302 // publish_time
343 // publish_time
303 packages: Vec<SnapshotPackage>,
344 packages: Vec<SnapshotPackage>,
304 // hidden
345 // hidden
@@ -312,9 +353,9 @@ struct SnapshotPackage {
312}
353}
313
354
314#[derive(PartialOrd, Ord, PartialEq, Eq, Clone)]
355#[derive(PartialOrd, Ord, PartialEq, Eq, Clone)]
315−struct PackageName(String);
356+struct Package(String);
316
357
317−impl fmt::Display for PackageName {
358+impl fmt::Display for Package {
318 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
319 self.0.fmt(f)
360 self.0.fmt(f)
320 }
361 }
@@ -331,7 +372,7 @@ impl fmt::Display for Version {
331
372
332// zstd-0.1.3.0@sha256:4c0a372251068eb6086b8c3a0a9f347488f08b570a7705844ffeb2c720c97223,3723
373// zstd-0.1.3.0@sha256:4c0a372251068eb6086b8c3a0a9f347488f08b570a7705844ffeb2c720c97223,3723
333struct PackageWithVersionAndSha {
374struct PackageWithVersionAndSha {
334− name: PackageName,
375+ name: Package,
335 version: Version,
376 version: Version,
336}
377}
337
378
@@ -343,7 +384,7 @@ impl<'de> serde::Deserialize<'de> for PackageWithVersionAndSha {
343 let s: String = String::deserialize(deserializer)?;
384 let s: String = String::deserialize(deserializer)?;
344 let r = regex!(r#"^(.+?)-([.\d]+)@sha256:[\da-z]+,\d+$"#);
385 let r = regex!(r#"^(.+?)-([.\d]+)@sha256:[\da-z]+,\d+$"#);
345 if let Some(caps) = r.captures(&s) {
386 if let Some(caps) = r.captures(&s) {
346− let name = PackageName(caps.get(1).unwrap().as_str().to_owned());
387+ let name = Package(caps.get(1).unwrap().as_str().to_owned());
347 let version = Version(caps.get(2).unwrap().as_str().to_owned());
388 let version = Version(caps.get(2).unwrap().as_str().to_owned());
348 Ok(Self { name, version })
389 Ok(Self { name, version })
349 } else {
390 } else {
@@ -366,7 +407,7 @@ where
366}
407}
367
408
368struct Snapshot {
409struct Snapshot {
369− packages: BTreeMap<PackageName, Diff<Version>>,
410+ packages: BTreeMap<Package, Diff<Version>>,
370}
411}
371
412
372#[derive(Clone, Copy)]
413#[derive(Clone, Copy)]