Skip to content

Commit afd8dd9

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 dry-run job now shares. 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, 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 3d08409 commit afd8dd9

10 files changed

Lines changed: 1215 additions & 55 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: 200 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
# This source code is licensed under the BSD-style license found in the
66
# LICENSE file in the root directory of this source tree.
77

8-
# The ExecuTorch ESP-IDF component's nightly matrix.
8+
# The ExecuTorch ESP-IDF component's nightly matrix and release checks.
99
#
10-
# test_espressif_idf_nightly.sh prepare <dir>
10+
# test_espressif_idf_nightly.sh prepare <dir> [stamp options]
1111
# In the ExecuTorch environment: exports the test models (smoke, the
1212
# esp_nn:: mul, an ESP-NN delegate program and the Cadence-quantized wake
13-
# word), generates and packs the component into <dir>/dist, and makes each
14-
# model's kernel bundle, one for smoke and mul, and one for all four, all
15-
# under <dir>/models. all_ops is the smoke model with a bundle that
13+
# word), generates the component with build_idf_component.sh's stamp
14+
# options (default --allow-dev-stamp), packs it into <dir>/dist, and makes
15+
# each model's kernel bundle, one for smoke and mul, and one for all four,
16+
# all under <dir>/models. all_ops is the smoke model with a bundle that
1617
# registers every op of every kernel library.
1718
# test_espressif_idf_nightly.sh build <dir> <shard> <shards>
1819
# Needs only <dir> from prepare: installs ESPRESSIF_IDF_VERSION's ESP-IDF
@@ -21,14 +22,25 @@
2122
# idf-build-apps (sdkconfig.ci.* times every target, filtered by
2223
# .build-test-rules.yml), builds shard <shard> of <shards> with ESP-IDF's
2324
# own Python environment, checks that each configuration's settings took
24-
# effect, and runs each build on its emulator unless the rules disable its
25-
# test. Reports every failure, then fails.
25+
# effect, runs each build on its emulator unless the rules disable its
26+
# test, and checks that the mirror served the packed archive's files.
27+
# Reports every failure, then fails.
28+
# test_espressif_idf_nightly.sh fetch <dir> <URL> <sha256>
29+
# Extracts into <dir> the tar of prepare's version, dist and models that
30+
# the publish workflow keeps at <URL>, if its hash is <sha256>.
31+
# test_espressif_idf_nightly.sh registry <dir> <registry URL>
32+
# Needs only <dir> from prepare: installs ESP-IDF 6.1, waits up to 15
33+
# minutes for the given component registry to list <dir>'s version of
34+
# pytorch/executorch, builds the smoke consumer for ESP32-S3 with it, fails
35+
# if the registry serves files other than <dir>'s archive, and runs the
36+
# build on the emulator.
2637

2738
set -euo pipefail
2839

2940
et_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
30-
mode=${1:?usage: $0 prepare <dir> | build <dir> <shard> <shards>}
31-
dir=${2:?usage: $0 prepare <dir> | build <dir> <shard> <shards>}
41+
usage="usage: $0 prepare <dir> [stamp options] | build <dir> <shard> <shards> | fetch <dir> <URL> <sha256> | registry <dir> <URL>"
42+
mode=${1:?${usage}}
43+
dir=${2:?${usage}}
3244
mkdir -p "${dir}"
3345
dir=$(cd "${dir}" && pwd)
3446
cd "${et_root}"
@@ -46,6 +58,12 @@ logged() {
4658
}
4759

4860
prepare() {
61+
local stamp=("${@:3}")
62+
if (( ${#stamp[@]} == 0 )); then
63+
stamp=(--allow-dev-stamp)
64+
fi
65+
# Checks the stamp against git before the model exports.
66+
bash examples/espressif/build_idf_component.sh --version "${stamp[@]}" > /dev/null
4967
export ESPRESSIF_TOOLS_DIR="${ESPRESSIF_TOOLS_DIR:-${RUNNER_TEMP:-/tmp}/executorch-espressif}"
5068
local logs="${dir}/logs" models="${dir}/models"
5169
mkdir -p "${logs}" "${models}"
@@ -76,11 +94,13 @@ print(entry["atol"], entry["rtol"])
7694
rm -r "${models}"/*.bpte "${dir}/espnn"
7795

7896
local component_dir="${dir}/component/executorch"
79-
logged "${logs}/generate.log" bash examples/espressif/build_idf_component.sh \
80-
--allow-dev-stamp --out "${component_dir}"
97+
logged "${logs}/generate.log" bash examples/espressif/build_idf_component.sh "${stamp[@]}" \
98+
--out "${component_dir}"
8199
local version
82100
version=$(sed -n 's/^version: "\(.*\)"$/\1/p' "${component_dir}/idf_component.yml")
83-
(cd "${component_dir}" && compote component pack --name executorch --dest-dir "${dir}/dist")
101+
# The registry links each version to its commit and to the generator.
102+
(cd "${component_dir}" && compote component pack --name executorch --dest-dir "${dir}/dist" \
103+
--commit-sha "$(git -C "${et_root}" rev-parse HEAD)" --repository-path examples/espressif)
84104
local model
85105
for model in smoke mul delegate wakeword; do
86106
python "${component_dir}/tools/gen_kernels.py" --pte "${models}/${model}.pte" \
@@ -114,28 +134,9 @@ build() {
114134
mkdir -p "${work}"
115135
work=$(cd "${work}" && pwd)
116136

117-
# ESP-IDF's QEMU needs these libraries, and setup.sh fails its install check
118-
# without them.
119-
if ! python -c '
120-
import ctypes
121-
ctypes.CDLL("libSDL2-2.0.so.0")
122-
ctypes.CDLL("libslirp.so.0")' 2> /dev/null; then
123-
sudo apt-get update
124-
sudo apt-get install -y --no-install-recommends libsdl2-2.0-0 libslirp0
125-
fi
126-
logged "${work}/setup.log" bash examples/espressif/setup.sh --minimal \
127-
--idf-version="${idf_version}" --targets="$(IFS=,; echo "${targets[*]}")"
128-
# shellcheck source=/dev/null
129-
source "${ESPRESSIF_TOOLS_DIR}/setup_path.sh"
130-
# A branch such as master moves, so record the commit a failure is against.
131-
echo "ESP-IDF ${idf_version} at $(git -C "${IDF_PATH}" log -1 --format='%H (%cs)')" \
132-
| tee "${work}/idf_commit.log"
133-
134-
# A fresh ESP-IDF Python environment, which cannot import executorch, and
135-
# idf-build-apps in a venv of its own, so it changes nothing ESP-IDF runs.
136-
export IDF_PYTHON_ENV_PATH="${work}/idf_python_env"
137-
mkdir -p "${IDF_PYTHON_ENV_PATH}"
138-
logged "${work}/idf_python_env.log" python "${IDF_PATH}/tools/idf_tools.py" install-python-env
137+
install_idf "${idf_version}" "$(IFS=,; echo "${targets[*]}")" "${work}"
138+
# Runs idf-build-apps from a venv of its own, so it changes nothing ESP-IDF
139+
# runs.
139140
"${IDF_PYTHON_ENV_PATH}/bin/python" -m venv "${work}/idf_build_apps"
140141
"${work}/idf_build_apps/bin/python" -m pip install --quiet "idf-build-apps>=3.1,<4"
141142
local version
@@ -145,13 +146,8 @@ ctypes.CDLL("libslirp.so.0")' 2> /dev/null; then
145146
"${dir}/dist/executorch_${version}.tgz" "${work}/mirror" --namespace pytorch --name executorch
146147
export IDF_COMPONENT_STORAGE_URL="file://${work}/mirror;default"
147148

148-
# Built from a copy, so the component manager's state and the consumer's
149-
# generated manifest stay out of the source tree.
150149
local sources="${work}/sources"
151-
rm -rf "${sources}"
152-
mkdir -p "${sources}/examples/espressif/idf_component_smoke"
153-
cp -r examples/espressif/idf_component_consumer "${sources}/examples/espressif/"
154-
cp -r examples/espressif/idf_component_smoke/main "${sources}/examples/espressif/idf_component_smoke/"
150+
copy_consumer "${sources}"
155151
local app=examples/espressif/idf_component_consumer
156152
(cd "${sources}" && "${work}/idf_build_apps/bin/idf-build-apps" find -p "${app}" --target all \
157153
--default-build-targets "${targets[@]}" --config-rules 'sdkconfig.ci.*=' \
@@ -218,6 +214,11 @@ PY
218214
failures+=("${name} (run)")
219215
fi
220216
done 3< "${work}/shard.txt"
217+
# Exercises the check that registry mode runs after an upload.
218+
if ! served_matches "${dir}/dist/executorch_${version}.tgz" \
219+
"${project}/managed_components/pytorch__executorch" "the mirror"; then
220+
failures+=("served files")
221+
fi
221222

222223
if (( ${#failures[@]} )); then
223224
printf 'FAIL: %s\n' "${failures[@]}" >&2
@@ -226,6 +227,161 @@ PY
226227
echo "PASS: shard ${shard}/${shards} of the ESP-IDF ${idf_version} matrix built and ran."
227228
}
228229

230+
# Installs ESP-IDF for the targets and makes a fresh ESP-IDF Python
231+
# environment, which cannot import executorch: ESP-IDF version, comma-separated
232+
# targets, work dir.
233+
install_idf() {
234+
local idf_version=$1 targets_csv=$2 work=$3
235+
# ESP-IDF's QEMU needs these libraries, and setup.sh fails its install check
236+
# without them.
237+
if [[ ",${targets_csv}," == *,esp32,* ]] && ! python -c '
238+
import ctypes
239+
ctypes.CDLL("libSDL2-2.0.so.0")
240+
ctypes.CDLL("libslirp.so.0")' 2> /dev/null; then
241+
sudo apt-get update
242+
sudo apt-get install -y --no-install-recommends libsdl2-2.0-0 libslirp0
243+
fi
244+
logged "${work}/setup.log" bash examples/espressif/setup.sh --minimal \
245+
--idf-version="${idf_version}" --targets="${targets_csv}"
246+
# shellcheck source=/dev/null
247+
source "${ESPRESSIF_TOOLS_DIR}/setup_path.sh"
248+
# A branch such as master moves, so record the commit a failure is against.
249+
echo "ESP-IDF ${idf_version} at $(git -C "${IDF_PATH}" log -1 --format='%H (%cs)')" \
250+
| tee "${work}/idf_commit.log"
251+
export IDF_PYTHON_ENV_PATH="${work}/idf_python_env"
252+
mkdir -p "${IDF_PYTHON_ENV_PATH}"
253+
logged "${work}/idf_python_env.log" python "${IDF_PATH}/tools/idf_tools.py" install-python-env
254+
}
255+
256+
# Copies the consumer project to a directory, so the component manager's state
257+
# and the consumer's generated manifest stay out of the source tree.
258+
copy_consumer() {
259+
local sources=$1
260+
rm -rf "${sources}"
261+
mkdir -p "${sources}/examples/espressif/idf_component_smoke"
262+
cp -r examples/espressif/idf_component_consumer "${sources}/examples/espressif/"
263+
cp -r examples/espressif/idf_component_smoke/main "${sources}/examples/espressif/idf_component_smoke/"
264+
}
265+
266+
fetch() {
267+
local url=${3:?${usage}} sha256=${4:?${usage}} tar="${dir}/release.tar"
268+
curl -sSf --retry 3 -o "${tar}" "${url}"
269+
if ! echo "${sha256} ${tar}" | sha256sum --check --quiet; then
270+
echo "FAIL: ${url} is not the tested archive, whose sha256 is ${sha256}" >&2
271+
exit 1
272+
fi
273+
tar -xf "${tar}" -C "${dir}"
274+
rm "${tar}"
275+
}
276+
277+
registry() {
278+
local registry_url=${3:?${usage}}
279+
local work="${ESPRESSIF_BUILD_DIR:-${et_root}/cmake-out-esp-idf-registry}"
280+
export ESPRESSIF_TOOLS_DIR="${ESPRESSIF_TOOLS_DIR:-${RUNNER_TEMP:-/tmp}/executorch-espressif}"
281+
mkdir -p "${work}"
282+
work=$(cd "${work}" && pwd)
283+
install_idf v6.1 esp32s3 "${work}"
284+
local version
285+
version=$(cat "${dir}/version")
286+
local storage
287+
if ! storage=$(curl -sSf --retry 3 "${registry_url%/}/api/" | python -c '
288+
import json, sys
289+
print(json.load(sys.stdin)["components_base_url"].rstrip("/"))'); then
290+
echo "FAIL: ${registry_url}/api/ did not name the registry's storage" >&2
291+
exit 1
292+
fi
293+
# The registry's storage is behind a CDN that caches for five minutes.
294+
local tries=0
295+
until curl -sSf "${storage}/components/pytorch/executorch.json" 2> /dev/null | python -c '
296+
import json, sys
297+
try:
298+
versions = [v["version"] for v in json.load(sys.stdin)["versions"]]
299+
except (ValueError, KeyError):
300+
versions = []
301+
sys.exit(sys.argv[1] not in versions)' "${version}"; do
302+
if (( ++tries == 30 )); then
303+
echo "FAIL: ${registry_url} did not list pytorch/executorch ${version}" >&2
304+
exit 1
305+
fi
306+
echo "Waiting for ${registry_url} to list pytorch/executorch ${version}..."
307+
sleep 30
308+
done
309+
copy_consumer "${work}/sources"
310+
local project="${work}/sources/examples/espressif/idf_component_consumer"
311+
local build="${work}/builds/esp32s3-default" log="${work}/emulator.log"
312+
local idf_args=(-C "${project}" -B "${build}" -DSDKCONFIG="${build}/sdkconfig"
313+
"-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.default"
314+
-DET_COMPONENT_VERSION="${version}" -DET_CONSUMER_MODELS_DIR="${dir}/models"
315+
-DET_COMPONENT_REGISTRY_URL="${registry_url}")
316+
IDF_TARGET=esp32s3 logged "${work}/build.log" "${IDF_PYTHON_ENV_PATH}/bin/python" \
317+
"${IDF_PATH}/tools/idf.py" "${idf_args[@]}" build
318+
# Checks that the registry serves the tested files: an --allow-existing
319+
# upload leaves in place a version the registry already had.
320+
served_matches "${dir}/dist/executorch_${version}.tgz" \
321+
"${project}/managed_components/pytorch__executorch" "${registry_url}"
322+
local model
323+
model=$(sed -n 's/^CONFIG_ET_CONSUMER_MODEL="\(.*\)"$/\1/p' "${build}/sdkconfig")
324+
run_firmware "${build}" esp32s3 "${log}" "${idf_args[@]}"
325+
if ! grep -qE "${model}: PASS, [1-9][0-9]*/" "${log}" \
326+
|| ! grep -qF "ET_COMPONENT_SMOKE: PASS" "${log}"; then
327+
tail -n 30 "${log}" >&2
328+
echo "FAIL: the consumer from ${registry_url} did not pass on the emulator" >&2
329+
exit 1
330+
fi
331+
echo "PASS: pytorch/executorch ${version} from ${registry_url} built and ran."
332+
}
333+
334+
# Checks that a project got the files of the tested archive from a registry or
335+
# mirror. The component manager adds .component_hash and CHECKSUMS.json to what
336+
# it downloads, and the registry rewrites every idf_component.yml, the
337+
# component's and each example's, with sorted keys and no comments, and drops
338+
# the examples' override_path, so manifests are compared as YAML without
339+
# override_path.
340+
# Arguments: the archive, the project's managed_components/pytorch__executorch,
341+
# then the registry or mirror's name.
342+
served_matches() {
343+
python - "$@" <<'PY'
344+
import hashlib
345+
import sys
346+
import tarfile
347+
from pathlib import Path
348+
349+
import yaml
350+
351+
352+
def fingerprint(path, data):
353+
if path == "idf_component.yml" or path.endswith("/idf_component.yml"):
354+
manifest = yaml.safe_load(data)
355+
for dependency in (manifest.get("dependencies") or {}).values():
356+
if isinstance(dependency, dict):
357+
dependency.pop("override_path", None)
358+
return manifest
359+
return hashlib.sha256(data).hexdigest()
360+
361+
362+
tested = {}
363+
with tarfile.open(sys.argv[1]) as archive:
364+
for member in archive:
365+
if member.isfile():
366+
path = Path(member.name).as_posix()
367+
tested[path] = fingerprint(path, archive.extractfile(member).read())
368+
served_dir = Path(sys.argv[2])
369+
served = {}
370+
for file in served_dir.rglob("*"):
371+
if file.is_file():
372+
path = file.relative_to(served_dir).as_posix()
373+
served[path] = fingerprint(path, file.read_bytes())
374+
differ = sorted(
375+
p
376+
for p in tested.keys() | served.keys()
377+
if p not in (".component_hash", "CHECKSUMS.json") and tested.get(p) != served.get(p)
378+
)
379+
if differ:
380+
sys.exit(f"FAIL: {sys.argv[3]} served files unlike the tested archive's: {differ[:10]}")
381+
print(f"OK: {sys.argv[3]} served the tested archive's files")
382+
PY
383+
}
384+
229385
# Checks that every setting of a sdkconfig.ci.* file reached the build's
230386
# sdkconfig, since ESP-IDF only warns about a symbol it does not know.
231387
# Arguments: the sdkconfig.ci.* file, then the build's sdkconfig.
@@ -274,7 +430,9 @@ run_firmware() {
274430
}
275431

276432
case "${mode}" in
277-
prepare) prepare ;;
433+
prepare) prepare "$@" ;;
278434
build) build "$@" ;;
279-
*) echo "unknown mode ${mode}; use prepare or build" >&2; exit 1 ;;
435+
fetch) fetch "$@" ;;
436+
registry) registry "$@" ;;
437+
*) echo "unknown mode ${mode}; ${usage}" >&2; exit 1 ;;
280438
esac
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}" | jq -er .value)
39+
echo "::add-mask::${token}"
40+
export IDF_COMPONENT_API_TOKEN="${token}"
41+
fi
42+
# --allow-existing skips a version the registry already has without a word,
43+
# so a re-run after an upload, or a dry run of a published version, validates
44+
# nothing; say so.
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[@]}"

0 commit comments

Comments
 (0)