Skip to content

fix(esptool): parse version from release tag when filename is generic (#1217) - #1218

Merged
zackees merged 2 commits into
mainfrom
fix/1217-esptool-version-from-release-tag
Jul 31, 2026
Merged

fix(esptool): parse version from release tag when filename is generic (#1217)#1218
zackees merged 2 commits into
mainfrom
fix/1217-esptool-version-from-release-tag

Conversation

@zackees

@zackees zackees commented Jul 31, 2026

Copy link
Copy Markdown
Member

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:

Publisher URL Version lives in
pioarduino registry .../releases/download/0.0.1/esptoolpy-v5.3.0.zip filename (0.0.1 is just the registry tag)
platform-espressif32 53.03.10 .../releases/download/v4.8.5/esptool.zip release tag (filename is generic)

Only 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 returned None → the linker degraded to a bare esptool PATH 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

build error: build failed: esptool not found — cannot convert firmware.elf to firmware.bin.
Error: failed to spawn ["esptool", "--chip", "esp32", "elf2image", ...]: No such file or directory

...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.1 tag. 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_url constructor. That stale doc is a good part of why the second shape was easy to miss.

Tests

soldr cargo test -p fbuild-library --lib esptool12 passed.

  • extract_version_from_release_tag_when_filename_is_generic — new, the outage case, asserts 4.8.5
  • filename_version_still_wins_over_release_tag — new, ordering guard
  • extract_version_falls_back_to_unknown — unchanged and still passing; neither component of https://example.com/esptool.zip carries a dotted version, so the genuine unknown case still returns "unknown" rather than being papered over

cargo fmt --check and clippy clean.

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:

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.

…#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>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The ESPTool URL parser now detects dotted numeric versions in filenames, falls back to the parent URL segment, and returns "unknown" when neither location contains a version. Tests cover fallback and precedence.

Changes

ESPTool version extraction

Layer / File(s) Summary
Version parser and test coverage
crates/fbuild-library/src/library/esptool.rs
extract_esptool_version checks the filename before the parent URL segment and otherwise returns "unknown". Tests cover generic filenames and filename precedence.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • FastLED/fbuild#988: Introduces the ESPTool provisioning flow that relies on this version extraction logic.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements [#1217] objective 1 but omits the requested provisioning error, path override, and per-request environment fixes. Implement the remaining [#1217] fixes, or update the linked issue scope so this PR covers only release-tag parsing.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed All changes are limited to esptool version extraction and related tests, matching the stated objective from [#1217].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes parsing the esptool version from a release tag when the filename is generic.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1217-esptool-version-from-release-tag

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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>
@zackees
zackees force-pushed the fix/1217-esptool-version-from-release-tag branch from 540e836 to b54ca7f Compare July 31, 2026 17:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3e02dc9 and 8d70b7f.

📒 Files selected for processing (1)
  • crates/fbuild-library/src/library/esptool.rs

Comment on lines +294 to +303
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

regression (2.5.4): ESP32 elf2image dies on bare esptool — version parser never resolves pioarduino URL, and the PATH fallback it relies on broke

1 participant