fix(esptool): parse version from release tag when filename is generic (#1217) - #1218
Conversation
…#1217) `extract_esptool_version()` only ever looked at the metadata URL's filename. That is correct for the pioarduino *registry* shape .../releases/download/0.0.1/esptoolpy-v5.3.0.zip where `0.0.1` is the registry tag and the real version is in the filename. But `platform-espressif32 53.03.10` publishes the opposite shape .../releases/download/v4.8.5/esptool.zip with a generic filename and the version in the release tag. The parse returned "unknown" for every build on that platform, so the tasmota download URL became `.../download/vunknown/...`, 404'd, and `resolve_esptool()` degraded to a bare `esptool` PATH lookup. That fallback then failed inside the daemon, breaking every ESP32 build on FastLED master since 2026-07-30 with a misleading "pip install esptool" hint — esptool was installed, just not on the daemon's PATH. Split the digit scan into `dotted_version_in()` and try the filename first, then the parent path segment. Ordering is load-bearing: the filename must keep winning or the registry-tag case regresses, so that invariant now has its own test rather than being implied. Tests: 12 pass, including the new generic-filename case and an explicit ordering guard. The existing fall-back-to-unknown case is unchanged — neither component of `https://example.com/esptool.zip` carries a dotted version. Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe ESPTool URL parser now detects dotted numeric versions in filenames, falls back to the parent URL segment, and returns ChangesESPTool version extraction
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The module doc still described only the version-in-filename shape, which is what made the version-in-release-tag case easy to miss. Co-Authored-By: Claude <noreply@anthropic.com>
540e836 to
b54ca7f
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/fbuild-library/src/library/esptool.rs`:
- Around line 294-303: Update extract_esptool_version to remove URL query and
fragment components, or use the parsed URL path, before splitting path segments
and calling dotted_version_in. Preserve filename-version precedence and ensure
registry-tag fallback reads the actual path segment. Add regression tests
covering URLs with query and fragment data, including the
v4.8.5/esptool.zip?channel=1.2 case and from_metadata_url behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 606f7a87-50b4-4328-89b7-50d158d75b97
📒 Files selected for processing (1)
crates/fbuild-library/src/library/esptool.rs
| fn extract_esptool_version(url: &str) -> String { | ||
| let mut segments = url.rsplit('/'); | ||
| let filename = segments.next().unwrap_or(url); | ||
| if let Some(version) = dotted_version_in(filename) { | ||
| return version; | ||
| } | ||
| // Only consulted when the filename carries no version of its own, so the | ||
| // registry-tag case above is unaffected. | ||
| if let Some(version) = segments.next().and_then(dotted_version_in) { | ||
| return version; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Parse the URL path before extracting the version.
extract_esptool_version splits the raw URL with rsplit('/'). Query and fragment data are not part of the filename. For example, .../v4.8.5/esptool.zip?channel=1.2 returns "1.2" and skips the valid release-tag fallback. from_metadata_url then stores the wrong version, which can cause provisioning to request the wrong artifact.
Strip the query and fragment, or parse the URL path before splitting it. Add regression tests for both cases.
Proposed fix
fn extract_esptool_version(url: &str) -> String {
- let mut segments = url.rsplit('/');
+ let path = url.split_once('?').map_or(url, |(path, _)| path);
+ let path = path.split_once('#').map_or(path, |(path, _)| path);
+ let mut segments = path.rsplit('/');+ #[test]
+ fn extract_version_ignores_query_and_fragment() {
+ assert_eq!(
+ extract_esptool_version(
+ "https://github.com/pioarduino/esptool/releases/download/v4.8.5/esptool.zip?channel=1.2"
+ ),
+ "4.8.5"
+ );
+ assert_eq!(
+ extract_esptool_version(
+ "https://github.com/pioarduino/esptool/releases/download/v4.8.5/esptool.zip#mirror/1.2"
+ ),
+ "4.8.5"
+ );
+ }Also applies to: 329-354
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/fbuild-library/src/library/esptool.rs` around lines 294 - 303, Update
extract_esptool_version to remove URL query and fragment components, or use the
parsed URL path, before splitting path segments and calling dotted_version_in.
Preserve filename-version precedence and ensure registry-tag fallback reads the
actual path segment. Add regression tests covering URLs with query and fragment
data, including the v4.8.5/esptool.zip?channel=1.2 case and from_metadata_url
behavior.
Fixes #1217 (fault 1 — the version parse, which is what clears the outage). Follow-ups split out to #1219 and #1220 so they survive this closing #1217.
Problem
extract_esptool_version()only looked at the metadata URL's filename. Two URL shapes exist in the wild and they put the version in opposite places:.../releases/download/0.0.1/esptoolpy-v5.3.0.zip0.0.1is just the registry tag)platform-espressif32 53.03.10.../releases/download/v4.8.5/esptool.zipOnly the first was handled. For the second the parse returned
"unknown", so the tasmota download URL became.../releases/download/vunknown/esptool-linux-amd64.zip→ 404 →resolve_esptool()warned and returnedNone→ the linker degraded to a bareesptoolPATH lookup (esp32_linker.rs:73).That silent degradation is what broke FastLED master: every ESP32 build has failed since 2026-07-30 at
elf2image, three minutes in, with...and a hint suggesting
pip install esptool— even though esptool 5.1.0 was installed, just not on the daemon's PATH.Change
Split the digit scan into
dotted_version_in(), then try filename → parent path segment.The ordering is load-bearing. The filename must keep winning, or the registry case regresses to the
0.0.1tag. That invariant was previously only implied by a single test; it now has an explicit one (filename_version_still_wins_over_release_tag) using a URL where both components carry a dotted version.Second commit updates two doc comments that described only the filename shape — the module-flow step and the
from_metadata_urlconstructor. That stale doc is a good part of why the second shape was easy to miss.Tests
soldr cargo test -p fbuild-library --lib esptool→ 12 passed.extract_version_from_release_tag_when_filename_is_generic— new, the outage case, asserts4.8.5filename_version_still_wins_over_release_tag— new, ordering guardextract_version_falls_back_to_unknown— unchanged and still passing; neither component ofhttps://example.com/esptool.zipcarries a dotted version, so the genuine unknown case still returns"unknown"rather than being papered overcargo fmt --checkandclippyclean.Deliberately not in scope
Kept narrow so it's reviewable and cherry-pickable for a patch release. The rest of #1217's findings are now tracked separately:
IDLE_TIMEOUT= 43200s). This is why the PATH fallback failed rather than merely being slow, and it affectspython/objcopytoo, not just esptool.FBUILD_*_PATHoverride (the only prefix surviving the daemonenv_clear).With this PR provisioning succeeds and the fallback is never reached, so the outage clears — but the fallback is still fragile for any future URL shape, which is what those two cover.