Skip to content

Fix package_folder_prefix quoting so the bundle contains its libraries again - #1

Merged
tyeth merged 3 commits into
mainfrom
ci-10x-mpy-check
Sep 10, 2026
Merged

tyeth merged 3 commits into
mainfrom
ci-10x-mpy-check

Conversation

@tyeth

@tyeth tyeth commented Sep 10, 2026

Copy link
Copy Markdown
Member

The bundle was shipping nothing

build.sh and release.yml built the --package_folder_prefix list with a gawk that wrapped it in literal double quotes. circuitpython-build-bundles splits that argument on ", " and matches each entry with str.startswith(), so the entries arrived as \"sensirion_i2c_driver and sensirion_i2c_sen5x\" and matched no folder. Both libraries fell back to legacy autodetection, which finds only the top-level conftest.py.

Measured with build-tools 1.20.1 against the pinned submodules:

--package_folder_prefix detection py bundle
\"sensirion_i2c_driver, sensirion_i2c_sen5x\" (as CI passed it) is_package=False, module_name=conftest 4 KB
sensirion_i2c_driver, sensirion_i2c_sen5x is_package=True, 20 + 33 files 178.5 KB

Two things make it total here rather than partial: with exactly two libraries, both entries are the damaged ones, and both drivers are third-party Sensirion repos with no [tool.setuptools] metadata, so they depend on legacy autodetection and therefore on the prefix.

Upstream adafruit/CircuitPython_Community_Bundle has the identical build.sh, but there the defect is latent: of its 47 prefix entries only 11 belong to libraries still using legacy autodetection — the rest declare tool.setuptools.packages in pyproject.toml, which takes precedence — and neither damaged entry is currently one of the 11. Its bundle is byte-identical with and without the fix. Reported as hardening in adafruit#290.

Getting the fix right took three passes

  1. Drop the literal quotes (764b924).
  2. Guard the empty list (33c7c06). Removing the quotes made one failure mode worse: an empty prefix used to reach an unquoted expression and fail on the missing argument, but as "" it splits to [''], and ''.startswith('') is True, so every folder becomes a package (module_name came out docs). Now a hard error.
  3. Remove the $GITHUB_OUTPUT hop entirely (f53aef0) — the real fix.

That third one matters. The original echo prefix=$( ... ) used an unquoted command substitution, so word splitting collapsed newlines into spaces and only one line could ever reach $GITHUB_OUTPUT. Assigning to a variable and echoing it quoted preserves them, so a directory name containing a newline writes extra variables that chain into the next step's command line. Measured: old form 1 line, new form 3.

Rather than patch that with an allowlist, the prefix is now computed in the Build assets step and passed as "$prefix" — real shell quoting, because bash does the expansion rather than the templating engine. filename_prefix comes in via env: for the same reason. No expression is spliced into any run: block any more, which also closes the pre-existing case of a folder name carrying a quote or backtick.

Also

Takes upstream's actions/checkout@v6 and actions/setup-python@v6 (what the Node 20 deprecation notice was about), and adds workflow_dispatch to build.yml — there was previously no way to re-run it without pushing. The upstream AWS S3 upload step is deliberately not adopted; it targets Adafruit's bucket. The library set is untouched.

Verified

Run 34542512867 is green and produces py (178.5 KB), 9.x-mpy and 10.x-mpy (52.5 KB, mpy-cross 10.0.0), all containing sensirion_i2c_sen5x and sensirion_i2c_driver.

Known wart, not addressed: build-tools' is_package is sticky across files, so the drivers' tests/ and docs/ trees are swept into lib/ too (52.5 KB against 32 KB in 2023). Harmless for circup, which installs per-module.

🤖 Generated with Claude Code

circuitpython-build-bundles splits --package_folder_prefix on ", " and
matches each entry with str.startswith(). The gawk that builds the list
wrapped it in literal double quotes, so the first and last entries came
through as '"sensirion_i2c_driver' and 'sensirion_i2c_sen5x"' and matched
no folder. Both libraries then fell back to legacy autodetection, which
found only the top-level conftest.py, and the bundle shipped one module
instead of the drivers.

With only two libraries here, both entries carry a stray quote, so every
release asset since the tooling moved on has been effectively empty. The
same bug is in the upstream community bundle, where it silently drops the
first and last library of the ls -U ordering.

Also allow build.yml to be started by hand, as there was no way to
re-run it without pushing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tyeth added a commit to tyeth/deepsleep_espnow_wifi_and_ble_env_collector that referenced this pull request Sep 10, 2026
The firmware run this file recorded as failed has been superseded: the
integration-pico2w-ble branch it was missing now exists on
tyeth/hal_rpi_pico, and Build board (custom) produces both Pico 2 W
(34467425010) and Pico W (34494018766) firmware. Take mpy-cross from the
latter -- it is green, expires two days later, and is the same build as
the firmware. Both compilers emit mpy v6.3, so no compiled output changes.
The 'otherwise-red run' caveat is also gone: tests / zephyr is green again
(tyeth/circuitpython#11).

The custom bundle's missing 10.x-mpy turned out to be a build bug, not a
dormant repo: its build.sh passes --package_folder_prefix wrapped in
literal quotes, which startswith() then never matches, so both libraries
fell back to autodetection and the bundle shipped only conftest. Fixed in
good-enough-technology/CircuitPython_GoodEnough_Bundle#1; the 10.x-mpy
reaches circup once that is merged and released.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tyeth and others added 2 commits September 11, 2026 00:22
Removing the literal quotes changed one failure mode for the worse. If the
gawk matches nothing, the prefix is empty; build-tools splits on ", " and
matches with startswith(), and "".startswith("") is True, so every folder
becomes a package (module_name came out as 'docs'). Previously the empty
value reached an unquoted ${{ }} and click failed on the missing argument,
which at least stopped the build. Fail loudly instead.

The character check covers the part quoting cannot: ${{ }} splices the
value into the run: block as text before any shell parses it, so a folder
name containing a quote or backtick escapes the quotes added around the
expansion. Restricting the list to what a module name can contain closes
that, rather than relying on the quotes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… versions

The previous commit guarded the symptom. The structure was the problem:
the prefix went out through $GITHUB_OUTPUT and came back as a ${{ }}
expression, which GitHub splices into the next run: block as script text
before any shell parses it.

That hop is worse than what it replaced. The original 'echo prefix=$( ... )'
used an unquoted command substitution, so word splitting collapsed newlines
into spaces and only ever one line reached $GITHUB_OUTPUT. Assigning to a
variable and echoing it quoted preserves them: a directory name containing
a newline writes extra variables into $GITHUB_OUTPUT, which then chain into
the next step. Measured: old form 1 line, new form 3.

So drop the hop. The prefix is computed in the Build assets step and passed
as "$prefix" -- real shell quoting this time, since bash does the expansion
rather than the templating engine. filename_prefix comes in through env for
the same reason. No expression is now spliced into any run: block, and the
character allowlist added in the previous commit is no longer load-bearing,
so it goes.

Also takes upstream's actions/checkout@v6 and actions/setup-python@v6,
which is what the Node 20 deprecation notice on our runs was about. The
upstream AWS S3 upload step is deliberately not adopted: it targets
Adafruit's bucket.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tyeth
tyeth merged commit e9f0a2d into main Sep 10, 2026
2 checks passed
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.

1 participant