Skip to content

Commit fc0e346

Browse files
avrabeclaude
andauthored
feat(checksum_updater): manage native loom (PerPlatformVersionedAsset) (#530)
The updater still had loom as UniversalWasm{loom.wasm} + AssetExists, which kept it pinned at v0.3.0 — the last release shipping loom.wasm. #514 migrated the registry + toolchain to native per-platform binaries (loom-v{version}-{triple}.tar.gz, .zip on Windows), so the auto-updater could no longer advance loom past 0.3.0. loom's toolchain reads the registry url_suffix as the FULL versioned filename (tool_registry.bzl loom: `filename: "{suffix}"`, like wkg/wrpc). The existing PerPlatformAsset can't express that — its stored suffix is static, with no {version} — so it would write a stale filename on the next loom release. Add a PerPlatformVersionedAsset pattern whose resolved filename (with {version} and {platform}) is both the download asset and the verbatim url_suffix, so the two can never drift. Thread `version` into get_url_suffix (single caller) and re-point loom to the new pattern with VersionFilter::Any (GitHub /releases/latest correctly returns v1.1.14). Verified: `update --tools loom --force` selects v1.1.14, downloads all 4 native tarballs from the correct URLs, and reproduces the hand-authored #513 registry block (url_suffix + sha256) byte-for-byte — zero functional diff, proving the updater output matches what the loom toolchain consumes. New unit test test_per_platform_versioned_asset_loom_native asserts the exact strings; full lib suite 24/24 pass. No registry change needed (loom already at 1.1.14); this only lets future loom releases be picked up automatically. With this, every PulseEngine tool the updater tracks is auto-managed (remaining exclusion: `go`, null github_repo — separate mechanism). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1444a00 commit fc0e346

2 files changed

Lines changed: 111 additions & 29 deletions

File tree

tools/checksum_updater/src/tool_config.rs

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@ pub enum VersionFilter {
3434
/// Accept the newest release that actually ships the expected asset.
3535
///
3636
/// Used for universal-wasm tools whose later releases may stop shipping the
37-
/// consumable artifact (e.g. loom: v0.3.0 ships `loom.wasm` but v1.x ship
38-
/// only compliance reports). Without this the updater would pick the latest
39-
/// tag and fail to download. The asset name comes from the tool's
37+
/// consumable `.wasm` artifact, so plain "latest" would pick a tag with no
38+
/// downloadable asset. The asset name comes from the tool's
4039
/// `UrlPattern::UniversalWasm`. Resolved in the update engine (it needs each
4140
/// release's asset list), not via `accepts()`.
41+
///
42+
/// (loom used this while it shipped `loom.wasm` through v0.3.0; from v1.x it
43+
/// ships per-platform native binaries and moved to
44+
/// `PerPlatformVersionedAsset` + `Any`. file-ops-component is the current
45+
/// user.)
4246
AssetExists,
4347
}
4448

@@ -79,6 +83,22 @@ pub enum UrlPattern {
7983
pattern: String,
8084
platform_mapping: HashMap<String, String>,
8185
},
86+
/// Per-platform asset whose stored `url_suffix` is the COMPLETE filename
87+
/// *including the version*, e.g. loom's `loom-v1.1.14-aarch64-apple-darwin.tar.gz`.
88+
/// Required when the toolchain reads `url_suffix` as the whole filename
89+
/// (`filename: "{suffix}"` in tool_registry.bzl, as loom/wkg/wrpc do) rather
90+
/// than appending a fragment to a version-templated pattern. `PerPlatformAsset`
91+
/// can't express this because its stored suffix is static (no `{version}`),
92+
/// so the next release would write a stale, wrong filename.
93+
///
94+
/// `filename_pattern` is the asset filename with `{version}` and `{platform}`
95+
/// placeholders; `platform_mapping` maps each platform to its `{platform}`
96+
/// value (including the file extension). The full resolved filename is both
97+
/// the download asset and the verbatim registry `url_suffix`.
98+
PerPlatformVersionedAsset {
99+
filename_pattern: String,
100+
platform_mapping: HashMap<String, String>,
101+
},
82102
}
83103

84104
impl Default for ToolConfig {
@@ -349,21 +369,36 @@ impl ToolConfig {
349369
},
350370
);
351371

352-
// PulseEngine universal-wasm components. These publish a single
353-
// platform-independent .wasm asset per release. The AssetExists filter
354-
// picks the newest release that actually ships the asset — important for
355-
// loom, whose v1.x releases ship only compliance reports (no loom.wasm),
356-
// so the updater correctly stays at v0.3.0 instead of failing on v1.x.
372+
// loom, the optimizer. v0.3.0 was the last release shipping the
373+
// universal `loom.wasm`; from v1.x loom ships per-platform native
374+
// tarballs (`loom-v{version}-{triple}.tar.gz`, `.zip` on Windows) and the
375+
// toolchain (tool_registry.bzl loom: `filename: "{suffix}"`) reads the
376+
// full versioned filename as the registry `url_suffix`. That needs
377+
// PerPlatformVersionedAsset — UniversalWasm/AssetExists kept the updater
378+
// stuck at v0.3.0 (#514 migrated the registry + toolchain to native).
357379
tools.insert(
358380
"loom".to_string(),
359381
ToolConfigEntry {
360382
github_repo: "pulseengine/loom".to_string(),
361-
platforms: vec!["wasm".to_string()],
362-
url_pattern: UrlPattern::UniversalWasm {
363-
asset_name: "loom.wasm".to_string(),
383+
platforms: vec![
384+
"darwin_amd64".to_string(),
385+
"darwin_arm64".to_string(),
386+
"linux_amd64".to_string(),
387+
"windows_amd64".to_string(),
388+
],
389+
url_pattern: UrlPattern::PerPlatformVersionedAsset {
390+
filename_pattern: "loom-v{version}-{platform}".to_string(),
391+
platform_mapping: {
392+
let mut map = HashMap::new();
393+
map.insert("darwin_amd64".to_string(), "x86_64-apple-darwin.tar.gz".to_string());
394+
map.insert("darwin_arm64".to_string(), "aarch64-apple-darwin.tar.gz".to_string());
395+
map.insert("linux_amd64".to_string(), "x86_64-unknown-linux-gnu.tar.gz".to_string());
396+
map.insert("windows_amd64".to_string(), "x86_64-pc-windows-msvc.zip".to_string());
397+
map
398+
},
364399
},
365400
tag_prefix: Some("v".to_string()),
366-
version_filter: VersionFilter::AssetExists,
401+
version_filter: VersionFilter::Any,
367402
},
368403
);
369404

@@ -543,6 +578,35 @@ impl ToolConfigEntry {
543578
.replace("{version}", version)
544579
.replace("{asset}", asset))
545580
}
581+
UrlPattern::PerPlatformVersionedAsset { .. } => {
582+
let filename = self.versioned_filename(version, platform)?;
583+
Ok(format!(
584+
"https://github.com/{}/releases/download/v{}/{}",
585+
self.github_repo, version, filename
586+
))
587+
}
588+
}
589+
}
590+
591+
/// Resolve the full per-platform filename for a `PerPlatformVersionedAsset`
592+
/// tool. This is both the download asset name and the verbatim registry
593+
/// `url_suffix`, so the two can never drift.
594+
fn versioned_filename(&self, version: &str, platform: &str) -> Result<String> {
595+
match &self.url_pattern {
596+
UrlPattern::PerPlatformVersionedAsset {
597+
filename_pattern,
598+
platform_mapping,
599+
} => {
600+
let platform_value = platform_mapping
601+
.get(platform)
602+
.with_context(|| format!("Unsupported platform: {}", platform))?;
603+
Ok(filename_pattern
604+
.replace("{version}", version)
605+
.replace("{platform}", platform_value))
606+
}
607+
_ => Err(anyhow::anyhow!(
608+
"versioned_filename called on non-versioned pattern"
609+
)),
546610
}
547611
}
548612

@@ -570,8 +634,9 @@ impl ToolConfigEntry {
570634
}
571635
}
572636

573-
/// Get URL suffix for JSON storage
574-
pub fn get_url_suffix(&self, platform: &str) -> Result<String> {
637+
/// Get URL suffix for JSON storage. `version` is only consulted by patterns
638+
/// whose stored suffix embeds the version (PerPlatformVersionedAsset).
639+
pub fn get_url_suffix(&self, version: &str, platform: &str) -> Result<String> {
575640
match &self.url_pattern {
576641
UrlPattern::StandardTarball { platform_mapping } => {
577642
let platform_name = platform_mapping
@@ -602,6 +667,9 @@ impl ToolConfigEntry {
602667
.get(platform)
603668
.cloned()
604669
.with_context(|| format!("Platform {} not found", platform)),
670+
UrlPattern::PerPlatformVersionedAsset { .. } => {
671+
self.versioned_filename(version, platform)
672+
}
605673
_ => Err(anyhow::anyhow!("Tool does not use URL suffixes")),
606674
}
607675
}
@@ -665,7 +733,9 @@ mod tests {
665733
let config = ToolConfig::new();
666734
let wasm_tools_config = config.get_tool_config("wasm-tools");
667735

668-
let suffix = wasm_tools_config.get_url_suffix("linux_amd64").unwrap();
736+
let suffix = wasm_tools_config
737+
.get_url_suffix("1.240.0", "linux_amd64")
738+
.unwrap();
669739
assert_eq!(suffix, "x86_64-linux.tar.gz");
670740
}
671741

@@ -688,7 +758,7 @@ mod tests {
688758
"https://github.com/bytecodealliance/wit-bindgen/releases/download/v0.58.0/wit-bindgen-0.58.0-x86_64-windows.zip"
689759
);
690760
assert_eq!(
691-
wb.get_url_suffix("windows_amd64").unwrap(),
761+
wb.get_url_suffix("0.58.0", "windows_amd64").unwrap(),
692762
"x86_64-windows.zip"
693763
);
694764
// Unix stays .tar.gz.
@@ -697,7 +767,7 @@ mod tests {
697767
"https://github.com/bytecodealliance/wit-bindgen/releases/download/v0.58.0/wit-bindgen-0.58.0-x86_64-linux.tar.gz"
698768
);
699769
assert_eq!(
700-
wb.get_url_suffix("linux_amd64").unwrap(),
770+
wb.get_url_suffix("0.58.0", "linux_amd64").unwrap(),
701771
"x86_64-linux.tar.gz"
702772
);
703773
assert!(!wb.has_platform_names());
@@ -713,36 +783,48 @@ mod tests {
713783
"https://github.com/bytecodealliance/wasmtime/releases/download/v45.0.1/wasmtime-v45.0.1-x86_64-linux.tar.xz"
714784
);
715785
assert_eq!(
716-
wt.get_url_suffix("linux_amd64").unwrap(),
786+
wt.get_url_suffix("45.0.1", "linux_amd64").unwrap(),
717787
"x86_64-linux.tar.xz"
718788
);
719789
assert_eq!(
720790
wt.generate_download_url("45.0.1", "windows_amd64").unwrap(),
721791
"https://github.com/bytecodealliance/wasmtime/releases/download/v45.0.1/wasmtime-v45.0.1-x86_64-windows.zip"
722792
);
723793
assert_eq!(
724-
wt.get_url_suffix("windows_amd64").unwrap(),
794+
wt.get_url_suffix("45.0.1", "windows_amd64").unwrap(),
725795
"x86_64-windows.zip"
726796
);
727797
}
728798

729799
#[test]
730-
fn test_universal_wasm_loom() {
800+
fn test_per_platform_versioned_asset_loom_native() {
731801
let config = ToolConfig::new();
732802
let loom = config.get_tool_config("loom");
733803

734804
assert_eq!(loom.github_repo, "pulseengine/loom");
735-
assert_eq!(loom.platforms, vec!["wasm".to_string()]);
736-
assert!(matches!(loom.version_filter, VersionFilter::AssetExists));
737-
assert_eq!(loom.universal_asset_name(), Some("loom.wasm"));
805+
assert!(matches!(loom.version_filter, VersionFilter::Any));
806+
assert!(!loom.has_platform_names());
738807

739-
let url = loom.generate_download_url("0.3.0", "wasm").unwrap();
808+
// The stored url_suffix must be the FULL versioned filename the toolchain
809+
// reads verbatim, and must match the hand-authored #513 registry block
810+
// exactly — else the loom toolchain download breaks on the next release.
740811
assert_eq!(
741-
url,
742-
"https://github.com/pulseengine/loom/releases/download/v0.3.0/loom.wasm"
812+
loom.generate_download_url("1.1.14", "darwin_arm64").unwrap(),
813+
"https://github.com/pulseengine/loom/releases/download/v1.1.14/loom-v1.1.14-aarch64-apple-darwin.tar.gz"
814+
);
815+
assert_eq!(
816+
loom.get_url_suffix("1.1.14", "darwin_arm64").unwrap(),
817+
"loom-v1.1.14-aarch64-apple-darwin.tar.gz"
818+
);
819+
assert_eq!(
820+
loom.get_url_suffix("1.1.14", "linux_amd64").unwrap(),
821+
"loom-v1.1.14-x86_64-unknown-linux-gnu.tar.gz"
822+
);
823+
// Windows is .zip (mixed extension).
824+
assert_eq!(
825+
loom.get_url_suffix("1.1.14", "windows_amd64").unwrap(),
826+
"loom-v1.1.14-x86_64-pc-windows-msvc.zip"
743827
);
744-
assert_eq!(loom.get_url_suffix("wasm").unwrap(), "loom.wasm");
745-
assert!(!loom.has_platform_names());
746828
}
747829

748830
#[test]

tools/checksum_updater/src/update_engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ impl UpdateEngine {
501501
} else {
502502
PlatformInfo {
503503
sha256: checksum,
504-
url_suffix: tool_config.get_url_suffix(platform)?,
504+
url_suffix: tool_config.get_url_suffix(version, platform)?,
505505
platform_name: None,
506506
extra: HashMap::new(),
507507
}

0 commit comments

Comments
 (0)