refactor(backend): unified AssetMatcher with checksum fetching#7370
refactor(backend): unified AssetMatcher with checksum fetching#7370
Conversation
Add a new `AssetMatcher` module that provides a high-level, builder-style
API for selecting the best asset from a list of available downloads.
Features:
- **Auto-detection**: Uses platform heuristics (OS, arch, libc) to score
and rank assets, leveraging the existing AssetPicker
- **Pattern matching**: Support for explicit patterns with placeholders
like `{os}`, `{arch}`, `{version}`, `{ext}`
- **Filtering**: Custom predicates and exclusion lists for fine-grained
asset selection
- **Related assets**: Methods to find checksum and signature files for
a given asset
Example usage:
```rust
// Auto-detect best asset for current platform
let asset = AssetMatcher::new()
.for_current_platform()
.pick_from(&assets)?;
// Match using explicit pattern
let asset = AssetMatcher::new()
.with_pattern("tool-{version}-{os}-{arch}.tar.gz")
.with_version("1.0.0")
.pick_from(&assets)?;
// Find checksum file for an asset
let checksum = AssetMatcher::new()
.find_checksum_for("tool-1.0.0-linux-x64.tar.gz", &assets);
```
This provides a unified interface that backends can use instead of
implementing their own asset matching logic. The existing AssetPicker
in asset_detector.rs remains unchanged and is used internally.
Includes 11 comprehensive tests covering:
- Auto-detection for various platforms
- Pattern matching with version substitution
- Checksum and signature file discovery
- Custom filtering and exclusion
- Real-world assets (ripgrep)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a new AssetMatcher module that provides a high-level, builder-style API for selecting the best asset from available downloads. The matcher combines auto-detection (using platform heuristics), explicit pattern matching with placeholders, and custom filtering to find appropriate installation assets.
Key Changes:
- Added
AssetMatcherwith builder pattern for flexible asset selection - Supports auto-detection via existing
AssetPicker, explicit patterns with{os},{arch},{version}placeholders, and custom filters - Includes utility methods to find related checksum and signature files
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/backend/mod.rs |
Registers the new asset_matcher module |
src/backend/asset_matcher.rs |
Complete implementation of AssetMatcher with builder API, pattern expansion, and 11 comprehensive tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let base_name = asset_name | ||
| .trim_end_matches(".tar.gz") | ||
| .trim_end_matches(".tar.xz") | ||
| .trim_end_matches(".tar.bz2") | ||
| .trim_end_matches(".zip") | ||
| .trim_end_matches(".tgz"); |
There was a problem hiding this comment.
The checksum matching logic will fail for nested extensions. For example, file.tar.gz will only have .gz stripped (resulting in file.tar), not .tar.gz, because trim_end_matches stops at the first non-match. Use a proper extension parsing approach or strip extensions in the correct order (longest first).
|
|
||
| fn matches_any_pattern(&self, asset: &str) -> bool { | ||
| let expanded = self.expand_patterns(); | ||
| expanded.iter().any(|p| asset == p || asset.contains(p)) |
There was a problem hiding this comment.
Using asset.contains(p) for pattern matching is overly permissive and can lead to false positives. For example, if pattern is 'linux', it would match 'unknown-linux-musl' when exact matching may be intended. Consider using proper pattern boundaries or restricting to exact matches only.
| expanded.iter().any(|p| asset == p || asset.contains(p)) | |
| expanded | |
| .iter() | |
| .any(|p| asset == p || asset.starts_with(p) || asset.ends_with(p)) |
| if let Some(ref version) = self.version { | ||
| let p = pattern.replace("{version}", version); | ||
| if !expanded.contains(&p) { | ||
| expanded.push(p); | ||
| } | ||
| } |
There was a problem hiding this comment.
The expanded.contains(&p) check performs a linear search through the entire vector for every pattern. Consider using a HashSet to track already-expanded patterns, or simply allow duplicates if they don't impact the matching logic.
| return Some(MatchedAsset { | ||
| name: asset.clone(), | ||
| url: None, | ||
| score: 1000, |
There was a problem hiding this comment.
The magic number 1000 is used as the priority score for pattern matches. Define this as a named constant (e.g., PATTERN_MATCH_SCORE) to improve code clarity and maintainability.
src/backend/asset_matcher.rs
Outdated
| return Some(MatchedAsset { | ||
| name: asset.clone(), | ||
| url: None, | ||
| score: 900, |
There was a problem hiding this comment.
The magic number 900 is used as the score for regex pattern matches. Define this as a named constant (e.g., REGEX_MATCH_SCORE) to improve code clarity and make the scoring hierarchy explicit.
Update the GitHub/GitLab backend to use the new AssetMatcher module instead of the lower-level asset_detector::detect_asset_for_target. This demonstrates the adoption of the unified AssetMatcher API and validates that it's a drop-in replacement for the existing functionality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
bugbot run |
…ab backends Add ChecksumFetcher to asset_matcher.rs that can: - Find checksum files (SHA256SUMS, *.sha256, etc.) from release assets - Fetch and parse checksum file contents - Extract checksums for specific asset files - Support both SHASUMS format and single-file checksum formats Integrate checksum fetching in UnifiedGitBackend: - GitHub: Falls back to fetching from checksum files when API digest unavailable - GitLab: Always tries to fetch checksums (GitLab API doesn't provide digests) This provides real value by automatically discovering and using checksums from release assets, improving security verification without manual config. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Consolidate platform detection and asset matching into a single module: - Move AssetPicker, platform detection types (AssetOs, AssetArch, AssetLibc), and detect_platform_from_url into asset_matcher.rs - Update tool_stub.rs to import from asset_matcher - Delete asset_detector.rs - Add tests from asset_detector to asset_matcher (now 25 tests total) This simplifies the module structure by having all asset-related functionality in one place. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ettings Bug fixes for AssetMatcher: 1. target_libc setting was ignored: create_picker() only passed os/arch to AssetPicker::new(), which re-derived libc from compile-time flags. Now AssetPicker::with_libc() accepts optional libc parameter. 2. prefer_archive and allow_binary had no effect: These fields were stored but never used in matching logic. Now filter_assets() respects them: - allow_binary=false filters out non-archive files - prefer_archive=true returns only archives when archives exist Added 4 tests verifying these settings work correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Hyperfine Performance
|
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.10 x -- echo |
19.7 ± 0.4 | 18.9 | 21.9 | 1.01 ± 0.02 |
mise x -- echo |
19.4 ± 0.2 | 18.9 | 20.5 | 1.00 |
mise env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.10 env |
18.8 ± 0.5 | 18.2 | 24.7 | 1.00 |
mise env |
18.9 ± 0.3 | 18.4 | 21.1 | 1.01 ± 0.03 |
mise hook-env
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.10 hook-env |
19.0 ± 0.2 | 18.5 | 21.0 | 1.00 |
mise hook-env |
19.1 ± 0.3 | 18.5 | 22.4 | 1.00 ± 0.02 |
mise ls
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|---|---|---|---|---|
mise-2025.12.10 ls |
16.6 ± 0.6 | 16.1 | 23.7 | 1.00 |
mise ls |
16.7 ± 0.2 | 16.3 | 17.5 | 1.01 ± 0.04 |
xtasks/test/perf
| Command | mise-2025.12.10 | mise | Variance |
|---|---|---|---|
| install (cached) | 107ms | 108ms | +0% |
| ls (cached) | 64ms | 65ms | -1% |
| bin-paths (cached) | 71ms | 71ms | +0% |
| task-ls (cached) | 277ms | 278ms | +0% |
1. Global checksum search used flawed OR condition:
The contains("checksum") check was OR'd with pattern match, causing
any file with "checksum" in name to be tried on every iteration.
Now: exact pattern match first, then "checksum" files as last resort.
2. Checksum parsing returned wrong hash for missing files:
When target file wasn't found in SHASUMS, code fell through and
returned first hash (wrong file). Now returns None instead.
3. min_score setting was ignored by pick_from():
match_by_auto_detection() never checked min_score threshold.
Now rejects assets scoring below the minimum.
Added 3 tests and fixed existing test to verify correct behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
### 🚀 Features - **(alias)** rename alias to tool-alias, add shell-alias command by @jdx in [#7357](#7357) - **(upgrade)** display summary of upgraded tools by @jdx in [#7372](#7372) - **(vfox)** embed vfox plugin Lua code in binary by @jdx in [#7369](#7369) ### 🐛 Bug Fixes - **(aqua)** add start_operations for progress reporting by @jdx in [#7354](#7354) - **(github)** improve asset detection for distro-specific and Swift artifacts by @jdx in [#7347](#7347) - **(github)** clean up static_helpers.rs and fix archive bin= option by @jdx in [#7366](#7366) - **(http)** add start_operations for progress reporting by @jdx in [#7355](#7355) - **(lockfile)** place lockfile alongside config file by @jdx in [#7360](#7360) - **(progress)** add start_operations to core plugins by @jdx in [#7351](#7351) - **(ruby-install)** Use ruby_install_bin to update by @calebhearth in [#7350](#7350) - **(rust)** add release_url for rust versions by @jdx in [#7373](#7373) - **(schema)** add `tool_alias`, mark `alias` as deprecated by @SKalt in [#7358](#7358) - **(toolset)** filter tools by OS in list_current_versions by @jdx in [#7356](#7356) - **(ubi)** only show deprecation warning during installation by @jdx in [#7380](#7380) - **(ui)** remove noisy "record size" message during install by @jdx in [#7381](#7381) - update mise-versions URL to use /tools/ prefix by @jdx in [#7378](#7378) ### 🚜 Refactor - **(backend)** unified AssetMatcher with checksum fetching by @jdx in [#7370](#7370) - **(backend)** deprecate ubi backend in favor of github by @jdx in [#7374](#7374) - **(toolset)** decompose mod.rs into smaller modules by @jdx in [#7371](#7371) ### 🧪 Testing - **(e2e)** fix and rename ubi and vfox_embedded_override tests by @jdx in [052ea40](052ea40) ### 📦 Registry - add vfox-gcloud backend for gcloud by @jdx in [#7349](#7349) - convert amplify to use github backend by @jdx in [#7365](#7365) - add github backend for djinni tool by @jdx in [#7363](#7363) - switch glab to native gitlab backend by @jdx in [#7364](#7364) - add s5cmd by @jdx in [#7376](#7376) ### Chore - **(registry)** disable flaky tests for gitu and ktlint by @jdx in [64151cb](64151cb) - resolve clippy warnings and add stricter CI check by @jdx in [#7367](#7367) - suppress dead_code warnings in asset_matcher module by @jdx in [#7377](#7377) ### New Contributors - @calebhearth made their first contribution in [#7350](#7350)
* upstream/main: fix(ui): remove noisy "record size" message during install (jdx#7381) test(e2e): fix and rename ubi and vfox_embedded_override tests fix: update mise-versions URL to use /tools/ prefix (jdx#7378) fix(ubi): only show deprecation warning during installation (jdx#7380) registry: add s5cmd (jdx#7376) chore: suppress dead_code warnings in asset_matcher module (jdx#7377) refactor(backend): deprecate ubi backend in favor of github (jdx#7374) fix(rust): add release_url for rust versions (jdx#7373) feat(vfox): embed vfox plugin Lua code in binary (jdx#7369) refactor(backend): unified AssetMatcher with checksum fetching (jdx#7370) feat(upgrade): display summary of upgraded tools (jdx#7372) fix(github): clean up static_helpers.rs and fix archive bin= option (jdx#7366) refactor(toolset): decompose mod.rs into smaller modules (jdx#7371) chore: resolve clippy warnings and add stricter CI check (jdx#7367) registry: switch glab to native gitlab backend (jdx#7364) fix(ruby-install): Use ruby_install_bin to update (jdx#7350) registry: add github backend for djinni tool (jdx#7363) registry: convert amplify to use github backend (jdx#7365) chore(registry): disable flaky tests for gitu and ktlint
Summary
asset_detector.rsintoasset_matcher.rsfor unified asset handlingAssetMatcherbuilder API for flexible asset selectionChanges
AssetMatcher Module
AssetPicker,AssetOs,AssetArch,AssetLibc,DetectedPlatformAssetMatcher::new().with_os().with_arch().pick_from()ChecksumFetcher,Asset,ChecksumResultdetect_asset_for_target(),detect_platform_from_url(),detect_best_asset()GitHub/GitLab Backend Integration
Cleanup
asset_detector.rs(merged intoasset_matcher.rs)tool_stub.rsto import fromasset_matcherTest plan
🤖 Generated with Claude Code
Note
Replaces asset_detector with a new AssetMatcher that unifies asset selection and adds checksum discovery, integrating it into GitHub/GitLab backends.
asset_matchermodule: AddsAssetMatcherbuilder API (pattern/auto-detect/filtering), platform detection (AssetPicker,AssetOs/Arch/Libc,DetectedPlatform), and helpers (detect_asset_for_target,detect_best_asset,detect_platform_from_url).ChecksumFetcher,Asset,ChecksumResult; parses common checksum files (e.g.,SHA256SUMS,*.sha256).asset_matcher::detect_asset_for_target.asset_detector.rs; update module exports and imports (e.g.,tool_stub.rs).Written by Cursor Bugbot for commit 832b7d0. This will update automatically on new commits. Configure here.