Skip to content

Commit db8b2f2

Browse files
committed
Publish the ESP-IDF component to the registry
publish-espressif-idf.yml publishes pytorch/executorch to the ESP Component Registry. An RC tag vX.Y.Z-rcN publishes X.Y.Z-rc.N to the staging registry, a release tag vX.Y.Z publishes X.Y.Z to staging and then production, and a respin tag vX.Y.Z-esp-idf.N, which the generator requires for X.Y.Z~N since git refs cannot hold ~, publishes that respin the same way. Other v* tags publish nothing. The workflow packs the component once, under the generator's release-stamp checks and with the compiler cache off, so the flatc that generates the archive's headers and the executorch that exports its example's model are built fresh, and asserts the version matches the tag. Before anything is uploaded, it builds and runs that archive in every consumer configuration on ESP-IDF 6.0.1 and 6.1 and validates it with both registries. Each upload waits for a reviewer of the esp-component-registry environment. After each upload, the new registry mode of test_espressif_idf_nightly.sh polls until the registry lists the version, builds the consumer with pytorch/executorch resolved from that registry, checks that the registry served the tested archive's files, comparing every idf_component.yml as YAML, since the registry rewrites the component's and each example's with sorted keys and no comments, and without the examples' override_path, which it drops, and runs it on the emulator. Production waits for that check on staging. The nightly's build mode runs the same file check against the mirror, so it is exercised before the first upload. The consumer takes the registry for pytorch/executorch as -DET_COMPONENT_REGISTRY_URL. The jobs after an approval cannot read the tested archive from the run's GitHub artifact, which lasts 2 days, less than the approvals can take. The package job puts the tested files in one tar, which every later job reads, a job records the tar's sha256 as an output before any approval, and another copies the tar to the run's S3 prefix in gha-artifacts, where it lasts 14 days, under a name with that hash. The upload and registry-check jobs fetch it over HTTPS and refuse it unless the hash matches, since other jobs can write to that bucket. The build and registry-check jobs keep their logs there too, named per job and attempt, because an upload to a name already taken fails. Uploads go through .ci/scripts/upload_espressif_idf_component.sh, which the nightly's prepare job now also runs for its dry run, after running fetch on a local copy of the tar, so a change to either is tested every day. It installs the component manager from a hash-locked requirements file, wheels only, and the two upload jobs pin their actions by commit, since anything they run could mint a registry token. It authenticates with the job's GitHub OIDC token for the registry, the way espressif/upload-components-ci-action does, and uploads the archive that was tested, with --allow-existing so a failed run can be rerun. A version already on the registry is kept, so a rerun that packed different bytes fails the registry check instead of shipping them. Since compote then skips the upload and its validation silently, the script says when the registry already has the version. prepare now records the commit and generator in that archive at pack time, because compote applies them only when it packs. Manual runs, which must be on a release, RC or respin tag since the generator stamps those only at their tag, take their version from that tag, so the approved ref always names what is uploaded, and validate without uploading unless dry_run is cleared, and every run that uploads nothing runs the staging upload job with --dry-run outside the environment, so its runner, actions and archive fetch work before a release needs them. Pull requests that touch the workflow run it that way, stamping a release version at a throwaway local tag, because workflow_dispatch cannot run before the workflow is on main, and the nightly runs the same release-stamp checks every day, so a change that would break them shows up before a real tag. Runs share a concurrency group per tag or branch, so a run waiting for an approval never cancels another tag's. The manifest pins esp-nn to the production registry, because the staging registry records a dependency without one as its own, and staging has no esp-nn; the component manager treats that as the same source as an unnamed dependency on the default registry, and the component's README says where esp-nn comes from. The README's runbook covers which rerun works when, release branches cut before the workflow landed, the concurrency group, and the one-time setup, in order: rulesets that leave v* tags, which the environment trusts, and release/* branches, which respin tags must be on, to release managers, then the protected environment, which deploys only from v* tags, since GitHub creates an unprotected one as soon as a workflow names it, then the staging namespace, which that registry grants separately, then each registry's trusted uploader, which names pytorch/executorch as the repository, publish-espressif-idf.yml as the workflow and esp-component-registry as the environment, with no branch, because tag pushes carry none. Reviewers check that a run's ref and commit are the release's, or for a respin that its tag's commit is on release/X.Y with reviewed changes; they gate the staging upload on the nightly's macOS and Windows configure jobs, with ESP-IDF master advisory, and approve the production upload only once executorch X.Y.Z is on PyPI, which projects need to make kernel bundles for it. The first release follows an RC that this workflow published, so the upload and registry checks have run before a release depends on them. Authored with Claude Code.
1 parent bd0f3ae commit db8b2f2

10 files changed

Lines changed: 1239 additions & 79 deletions

File tree

‎.ci/scripts/esp_idf_mirror.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def main() -> None:
6868
{
6969
"spec": requirement.version_spec,
7070
"source": requirement.source.type,
71-
"registry_url": None,
71+
"registry_url": requirement.registry_url,
7272
"is_public": requirement.is_public,
7373
"require": requirement.is_required,
7474
"name": None if is_idf else name,

‎.ci/scripts/espressif_idf_upload_requirements.txt‎

Lines changed: 460 additions & 0 deletions
Large diffs are not rendered by default.

‎.ci/scripts/test_espressif_idf_nightly.sh‎

Lines changed: 224 additions & 66 deletions
Large diffs are not rendered by default.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env bash
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
# All rights reserved.
4+
#
5+
# This source code is licensed under the BSD-style license found in the
6+
# LICENSE file in the root directory of this source tree.
7+
8+
# Uploads the component archive that test_espressif_idf_nightly.sh prepare made
9+
# to an ESP Component Registry as pytorch/executorch, and succeeds if the
10+
# registry already has that version.
11+
#
12+
# upload_espressif_idf_component.sh <dir> <registry URL> [--dry-run]
13+
#
14+
# Without --dry-run the job needs `id-token: write`: like
15+
# espressif/upload-components-ci-action, this authenticates with a GitHub OIDC
16+
# token for the registry, which accepts it when the component's trusted
17+
# uploader names this repository and the job's environment. --dry-run
18+
# validates the archive and needs no token.
19+
20+
set -euo pipefail
21+
22+
usage="usage: $0 <dir> <registry URL> [--dry-run]"
23+
if (( $# < 2 || $# > 3 )) || [[ $# == 3 && "$3" != --dry-run ]]; then
24+
echo "${usage}" >&2
25+
exit 1
26+
fi
27+
dir=$1 registry=$2 dry_run=("${@:3}")
28+
version=$(cat "${dir}/version")
29+
30+
# Wheels only: pip would install an sdist's build dependencies unhashed, in a
31+
# job that can mint a registry token.
32+
python -m pip install --progress-bar off --require-hashes --only-binary :all: \
33+
-r "$(dirname "${BASH_SOURCE[0]}")/espressif_idf_upload_requirements.txt"
34+
export IDF_COMPONENT_REGISTRY_URL="${registry}"
35+
if (( ${#dry_run[@]} == 0 )); then
36+
token=$(curl -sSfG --data-urlencode "audience=${registry}" \
37+
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN:?needs id-token: write}" \
38+
"${ACTIONS_ID_TOKEN_REQUEST_URL:?needs id-token: write}" | jq -er .value)
39+
echo "::add-mask::${token}"
40+
export IDF_COMPONENT_API_TOKEN="${token}"
41+
fi
42+
# --allow-existing silently skips a version the registry already has, so a
43+
# re-run after an upload, or a dry run of a published version, validates
44+
# nothing; report it.
45+
if listing=$(curl -sSf "${registry%/}/api/components/pytorch/executorch" 2> /dev/null) \
46+
&& jq -e --arg version "${version}" 'any(.versions[]; .version == $version)' \
47+
<<< "${listing}" > /dev/null; then
48+
echo "::notice::pytorch/executorch ${version} is already on ${registry}, so compote skips it."
49+
fi
50+
compote component upload --namespace pytorch --name executorch \
51+
--archive "${dir}/dist/executorch_${version}.tgz" --allow-existing "${dry_run[@]}"

‎.github/workflows/nightly-espressif-idf.yml‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,20 @@ jobs:
5050
source .ci/scripts/utils.sh
5151
install_executorch --use-pt-pinned-commit --minimal
5252
bash .ci/scripts/test_espressif_idf_nightly.sh prepare "${RUNNER_ARTIFACT_DIR}"
53+
# Runs the release stamp's checks at a throwaway local tag, so they do
54+
# not first run at a real one. The check of the executorch package,
55+
# which --version stops before, runs only when publishing.
56+
git tag -f "v$(cat version.txt)"
57+
bash examples/espressif/build_idf_component.sh --version --version-suffix ""
58+
# Exercises fetch, which otherwise runs only when publishing, on a
59+
# local copy of the tar that publishing keeps.
60+
tar -cf "${RUNNER_TEMP}/release.tar" -C "${RUNNER_ARTIFACT_DIR}" version dist models
61+
bash .ci/scripts/test_espressif_idf_nightly.sh fetch "${RUNNER_TEMP}/fetched" \
62+
"file://${RUNNER_TEMP}/release.tar" "$(sha256sum "${RUNNER_TEMP}/release.tar" | cut -d' ' -f1)"
5363
# The registry's validation of the packed archive, last, since it
5464
# installs the component manager into this environment.
55-
pip install --progress-bar off "idf-component-manager~=3.1"
56-
version=$(cat "${RUNNER_ARTIFACT_DIR}/version")
57-
compote component upload --namespace pytorch --name executorch \
58-
--archive "${RUNNER_ARTIFACT_DIR}/dist/executorch_${version}.tgz" --dry-run --allow-existing
65+
bash .ci/scripts/upload_espressif_idf_component.sh "${RUNNER_ARTIFACT_DIR}" \
66+
https://components.espressif.com --dry-run
5967
6068
build:
6169
name: ESP-IDF ${{ matrix.idf-version }} (${{ matrix.shard }}/4)

0 commit comments

Comments
 (0)