Skip to content

refactor(backend): unified AssetMatcher with checksum fetching#7370

Merged
jdx merged 11 commits intomainfrom
refactor/shared-asset-matcher
Dec 17, 2025
Merged

refactor(backend): unified AssetMatcher with checksum fetching#7370
jdx merged 11 commits intomainfrom
refactor/shared-asset-matcher

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Dec 17, 2025

Summary

  • Consolidate asset_detector.rs into asset_matcher.rs for unified asset handling
  • Add AssetMatcher builder API for flexible asset selection
  • Add checksum fetching helpers that auto-discover and parse checksum files from release assets
  • Integrate checksum fetching in GitHub/GitLab backends

Changes

AssetMatcher Module

  • Platform detection: AssetPicker, AssetOs, AssetArch, AssetLibc, DetectedPlatform
  • Builder API: AssetMatcher::new().with_os().with_arch().pick_from()
  • Checksum helpers: ChecksumFetcher, Asset, ChecksumResult
  • Convenience functions: detect_asset_for_target(), detect_platform_from_url(), detect_best_asset()

GitHub/GitLab Backend Integration

  • Auto-fetch checksums from release assets when API digest unavailable
  • GitLab support (GitLab API doesn't provide digests like GitHub)
  • Handles SHA256SUMS, *.sha256, and other common checksum formats

Cleanup

  • Delete asset_detector.rs (merged into asset_matcher.rs)
  • Update tool_stub.rs to import from asset_matcher
  • 25 tests covering all functionality

Test plan

  • All 25 asset_matcher tests pass
  • Build succeeds
  • Lint passes

🤖 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.

  • Backend:
    • New asset_matcher module: Adds AssetMatcher builder 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).
    • Checksum utilities: Introduces ChecksumFetcher, Asset, ChecksumResult; parses common checksum files (e.g., SHA256SUMS, *.sha256).
  • GitHub/GitLab integration:
    • Switch asset resolution to asset_matcher::detect_asset_for_target.
    • Auto-fetch checksums from release assets when API digest is missing; store digest in lock info.
    • Supports pattern-based asset selection with templating for targets.
  • Cleanup:
    • Remove asset_detector.rs; update module exports and imports (e.g., tool_stub.rs).
    • Add comprehensive tests for matching and checksum parsing.

Written by Cursor Bugbot for commit 832b7d0. This will update automatically on new commits. Configure here.

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>
Copilot AI review requested due to automatic review settings December 17, 2025 22:00
@jdx jdx changed the title feat(backend): add unified AssetMatcher for asset selection refactor(backend): add unified AssetMatcher for asset selection Dec 17, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AssetMatcher with 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.

Comment on lines +335 to +340
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");
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.

fn matches_any_pattern(&self, asset: &str) -> bool {
let expanded = self.expand_patterns();
expanded.iter().any(|p| asset == p || asset.contains(p))
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
expanded.iter().any(|p| asset == p || asset.contains(p))
expanded
.iter()
.any(|p| asset == p || asset.starts_with(p) || asset.ends_with(p))

Copilot uses AI. Check for mistakes.
Comment on lines +454 to +459
if let Some(ref version) = self.version {
let p = pattern.replace("{version}", version);
if !expanded.contains(&p) {
expanded.push(p);
}
}
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
return Some(MatchedAsset {
name: asset.clone(),
url: None,
score: 1000,
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
return Some(MatchedAsset {
name: asset.clone(),
url: None,
score: 900,
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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>
@jdx
Copy link
Owner Author

jdx commented Dec 17, 2025

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>
autofix-ci bot and others added 2 commits December 17, 2025 22:35
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>
@jdx jdx changed the title refactor(backend): add unified AssetMatcher for asset selection refactor(backend): unified AssetMatcher with checksum fetching Dec 17, 2025
…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>
@github-actions
Copy link

github-actions bot commented Dec 17, 2025

Hyperfine Performance

mise x -- echo

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>
@jdx jdx enabled auto-merge (squash) December 17, 2025 23:19
@jdx jdx disabled auto-merge December 17, 2025 23:46
@jdx jdx merged commit 050aad5 into main Dec 17, 2025
25 of 26 checks passed
@jdx jdx deleted the refactor/shared-asset-matcher branch December 17, 2025 23:46
jdx pushed a commit that referenced this pull request Dec 18, 2025
### 🚀 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)
jekis913 added a commit to jekis913/mise that referenced this pull request Dec 18, 2025
* 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
muzimuzhi added a commit to muzimuzhi/mise that referenced this pull request Dec 30, 2025
The `asset_detector.rs` was consolidated into `asset_matcher.rs` in
050aad5 (refactor(backend): unified AssetMatcher with checksum fetching
(jdx#7370), 2025-12-17).
muzimuzhi added a commit to muzimuzhi/mise that referenced this pull request Dec 30, 2025
The `asset_detector.rs` has been consolidated into `asset_matcher.rs` in
050aad5 (refactor(backend): unified AssetMatcher with checksum fetching
(jdx#7370), 2025-12-17).
muzimuzhi added a commit to muzimuzhi/mise that referenced this pull request Dec 30, 2025
The `asset_detector.rs` has been consolidated into `asset_matcher.rs` in
050aad5 (refactor(backend): unified AssetMatcher with checksum fetching
(jdx#7370), 2025-12-17).
muzimuzhi added a commit to muzimuzhi/mise that referenced this pull request Dec 30, 2025
The `asset_detector.rs` has been consolidated into `asset_matcher.rs` in
050aad5 (refactor(backend): unified AssetMatcher with checksum fetching
(jdx#7370), 2025-12-17).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants