Skip to content

Commit 0680331

Browse files
committed
Publish dev versions of the ESP-IDF component from the nightly branch
Projects that need the component before the next release can now pin a dev version from the registry instead of generating the component from an ExecuTorch checkout. A manual run of publish-esp-idf.yml on the nightly branch with version_suffix -dev publishes X.Y.Z-dev.YYYYMMDD of that branch's commit, through the same packing, testing, review and registry checks as a release. The generator's new --version-suffix -dev stamps HEAD's commit date and checks the checkout as it does for a release; --allow-dev-stamp stays the unchecked stamp for local builds. A dev version's kernel bundles need executorch from the component's exact commit. ExecuTorch's nightly wheels are built from the nightly branch's commits, so the plan refuses -dev on any other ref, and before the build the new wheel mode of test_espressif_idf_nightly.sh checks that the nightly wheels of the version's date, for each of the four platforms they are built for, record that commit. It also refuses a version dated today, since a second nightly the same day, which has happened, replaces that day's wheels; the runbook has the workflow run between 00:00 UTC and the next nightly. Pull requests that touch the workflow or that script rehearse the check on the latest finished days' nightlies. ExecuTorch's wheels declare no torch, so the component's VERSION now records the torch requirement install_requirements.py builds the wheels against, and the index that keeps it, and the new torch mode checks before upload that it resolves there on each platform. The component's README gives the pip commands for releases and dev versions. The nightly index keeps about two months of wheels, so a project moves to a newer dev version, or to the release, before its pin ages out; firmware builds from a committed kernel bundle are unaffected. Dev versions are semver pre-releases, which a version range picks up only if it opts in, so they go to production without reaching projects on releases. They read 1.6.0-dev.YYYYMMDD, as the nightly wheels do, until main's version.txt moves past 1.6.0, and sort below 1.6.0 meanwhile; exact pins are unaffected. Dev runs queue in the nightly branch's concurrency group, so one cannot replace a pending release run. Before the first dev upload, a repo admin adds nightly to the deployment branches of the esp-component-registry environment. The workflow accepts -dev once this change reaches the nightly branch. Authored with Claude Code.
1 parent a7badd6 commit 0680331

7 files changed

Lines changed: 186 additions & 25 deletions

File tree

‎.ci/scripts/test_espressif_idf_nightly.sh‎

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,27 @@
3030
# pytorch/executorch, builds the smoke consumer for ESP32-S3 with it, fails
3131
# if the registry serves files other than <dir>'s archive, and runs the
3232
# build on the emulator.
33+
# test_espressif_idf_nightly.sh wheel <dir> <dev version> <commit>
34+
# Checks that the ExecuTorch nightly wheels of the version's date were all
35+
# built from <commit>, so a project can install the executorch its kernel
36+
# bundles need, and refuses today's nightly, which a later one could
37+
# replace. Logs go under <dir>.
38+
# test_espressif_idf_nightly.sh torch <dir>
39+
# Needs only <dir> from prepare: checks that the torch the component's
40+
# VERSION names resolves from its index on every platform.
3341

3442
set -euo pipefail
3543

3644
et_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
37-
usage="usage: $0 prepare <dir> [stamp options] | build <dir> <shard> <shards> | registry <dir> <URL>"
45+
usage="usage: $0 prepare <dir> [stamp options] | build <dir> <shard> <shards> | registry <dir> <URL> | wheel <dir> <version> <commit> | torch <dir>"
3846
mode=${1:?${usage}}
3947
dir=${2:?${usage}}
4048
mkdir -p "${dir}"
4149
dir=$(cd "${dir}" && pwd)
4250
cd "${et_root}"
4351
targets=(esp32 esp32s3 esp32c3 esp32c5 esp32c6 esp32p4)
52+
# The platforms ExecuTorch's wheels are built for.
53+
wheel_platforms=(manylinux_2_28_x86_64 manylinux_2_28_aarch64 macosx_14_0_arm64 win_amd64)
4454

4555
# Runs a command with its output in a log, and shows the log's tail if it fails.
4656
logged() {
@@ -353,6 +363,56 @@ settings_applied() {
353363
[[ "${applied}" == true ]]
354364
}
355365

366+
wheel() {
367+
local version=${3:?${usage}} commit=${4:?${usage}} wheels platform
368+
if [[ "${version}" != *-dev.* ]]; then
369+
echo "FAIL: ${version} is not a dev version" >&2
370+
exit 1
371+
fi
372+
# A second nightly the same day would replace the wheels of this one.
373+
if [[ "${version##*-dev.}" == "$(date -u +%Y%m%d)" ]]; then
374+
echo "FAIL: ${version} is today's nightly, which a later nightly today could replace;" \
375+
"publish it after 00:00 UTC, before the next nightly." >&2
376+
exit 1
377+
fi
378+
wheels=$(mktemp -d)
379+
# shellcheck disable=SC2064 # wheels is local, so expand it now.
380+
trap "rm -rf '${wheels}'" EXIT
381+
mkdir -p "${dir}/logs"
382+
for platform in "${wheel_platforms[@]}"; do
383+
logged "${dir}/logs/nightly-wheel-${platform}.log" python -m pip download --no-deps \
384+
--only-binary=:all: --platform "${platform}" --python-version 3.11 \
385+
--implementation cp --index-url https://download.pytorch.org/whl/nightly/cpu \
386+
--dest "${wheels}/${platform}" "executorch==${version/-dev./.dev}"
387+
python - "${wheels}/${platform}"/executorch-*.whl "${commit}" <<'PY'
388+
import re
389+
import sys
390+
import zipfile
391+
392+
wheel, commit = sys.argv[1:]
393+
version_py = zipfile.ZipFile(wheel).read("executorch/version.py").decode()
394+
built = re.search(r"^git_version: .* = '(\w+)'", version_py, re.MULTILINE)
395+
built = built.group(1) if built else None
396+
if built != commit:
397+
sys.exit(f"FAIL: {wheel} was built from {built}, not the component's {commit}")
398+
print(f"OK: {wheel} was built from {commit}")
399+
PY
400+
done
401+
}
402+
403+
torch() {
404+
local version_file="${dir}/component/executorch/VERSION" requirement index platform
405+
requirement=$(sed -n 's/^torch_requirement: *//p' "${version_file}")
406+
index=$(sed -n 's/^torch_index: *//p' "${version_file}")
407+
mkdir -p "${dir}/logs"
408+
for platform in "${wheel_platforms[@]}"; do
409+
logged "${dir}/logs/torch-${platform}.log" python -m pip install --dry-run --no-deps \
410+
--only-binary=:all: --platform "${platform}" --python-version 3.11 \
411+
--implementation cp --target "$(mktemp -d)" --index-url "${index}" "${requirement}"
412+
done
413+
echo "OK: ${requirement} resolves from ${index} on ${wheel_platforms[*]}"
414+
}
415+
356416
# Runs a build on its emulator: build dir, target, log, then idf.py arguments.
357417
run_firmware() {
358418
local build=$1 target=$2 log=$3
@@ -386,5 +446,7 @@ case "${mode}" in
386446
prepare) prepare "$@" ;;
387447
build) build "$@" ;;
388448
registry) registry "$@" ;;
449+
wheel) wheel "$@" ;;
450+
torch) torch ;;
389451
*) echo "unknown mode ${mode}; ${usage}" >&2; exit 1 ;;
390452
esac

‎.github/workflows/publish-esp-idf.yml‎

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
name: Publish ESP-IDF component
22

33
# Publishes pytorch/executorch to the ESP Component Registry: RC tags to the
4-
# staging registry, release tags to staging and then production. The archive
4+
# staging registry, release tags to staging and then production, and on demand
5+
# a dev version of a nightly branch commit for early adopters. The archive
56
# is built and tested on the ESP-IDF releases the component supports before
67
# any upload, and each upload is followed by a consumer build that resolves
78
# the version from that registry. See the release runbook in
@@ -14,7 +15,7 @@ on:
1415
workflow_dispatch:
1516
inputs:
1617
version_suffix:
17-
description: 'Version suffix: empty at a release tag, -rc.N at an RC tag, ~N for a packaging respin on release/X.Y'
18+
description: 'Version suffix: empty at a release tag, -rc.N at an RC tag, ~N for a packaging respin on release/X.Y, -dev for the dev version of the nightly branch'
1819
type: string
1920
default: ''
2021
registry:
@@ -33,6 +34,7 @@ on:
3334
- .github/workflows/publish-esp-idf.yml
3435
- .ci/scripts/upload_espressif_idf_component.sh
3536
- .ci/scripts/espressif_idf_upload_requirements.txt
37+
- .ci/scripts/test_espressif_idf_nightly.sh
3638

3739
# Runs for one tag or branch go one at a time; runs for different ones, such as
3840
# an RC awaiting approval and its release, do not hold each other up.
@@ -59,6 +61,7 @@ jobs:
5961
shell: bash
6062
env:
6163
EVENT: ${{ github.event_name }}
64+
REF: ${{ github.ref }}
6265
REF_NAME: ${{ github.ref_name }}
6366
VERSION_SUFFIX: ${{ inputs.version_suffix }}
6467
REGISTRY: ${{ inputs.registry }}
@@ -79,8 +82,13 @@ jobs:
7982
fi
8083
;;
8184
workflow_dispatch)
82-
if [[ ! "${VERSION_SUFFIX}" =~ ^(-rc\.[1-9][0-9]*|~[1-9][0-9]*)?$ ]]; then
83-
echo "::error::version_suffix must be empty, -rc.N or ~N, not '${VERSION_SUFFIX}'."
85+
if [[ ! "${VERSION_SUFFIX}" =~ ^(-rc\.[1-9][0-9]*|~[1-9][0-9]*|-dev)?$ ]]; then
86+
echo "::error::version_suffix must be empty, -rc.N, ~N or -dev, not '${VERSION_SUFFIX}'."
87+
exit 1
88+
fi
89+
# A dev version's users install the nightly wheel of its commit.
90+
if [[ "${VERSION_SUFFIX}" == -dev && "${REF}" != refs/heads/nightly ]]; then
91+
echo "::error::a dev version is published from the nightly branch, not ${REF}."
8492
exit 1
8593
fi
8694
if [[ "${VERSION_SUFFIX}" == -rc.* && "${REGISTRY}" == production ]]; then
@@ -133,22 +141,45 @@ jobs:
133141
set -euo pipefail
134142
CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]')
135143
conda activate "${CONDA_ENV}"
144+
nightly=.ci/scripts/test_espressif_idf_nightly.sh
145+
suffix="${{ needs.plan.outputs.suffix }}"
146+
# A tag names its version; a dispatch appends its suffix to version.txt.
147+
expected="${{ needs.plan.outputs.version }}"
148+
expected="${expected:-$(cat version.txt)${suffix}}"
149+
# Before the build: a dev version's users install the nightly wheels of
150+
# its commit.
151+
if [[ "${suffix}" == -dev ]]; then
152+
expected=$(bash examples/espressif/build_idf_component.sh --version --version-suffix -dev)
153+
bash "${nightly}" wheel "${RUNNER_ARTIFACT_DIR}" "${expected}" "$(git rev-parse HEAD)"
154+
fi
155+
if [[ "${{ needs.plan.outputs.rehearsal }}" == true ]]; then
156+
# A pull request rehearses the dev wheel check on the last finished
157+
# days' nightlies, one of which must pass, since a day's wheels can
158+
# fail to build.
159+
checked=false
160+
for commit in $(git rev-list --first-parent --max-count=4 origin/nightly); do
161+
day=$(TZ=UTC git log -1 --format=%cd --date=format-local:%Y%m%d "${commit}")
162+
if [[ "${day}" != "$(date -u +%Y%m%d)" ]] && bash "${nightly}" wheel \
163+
"${RUNNER_ARTIFACT_DIR}" "$(git show "${commit}:version.txt")-dev.${day}" "${commit}"; then
164+
checked=true
165+
break
166+
fi
167+
done
168+
[[ "${checked}" == true ]]
169+
fi
136170
source .ci/scripts/utils.sh
137171
install_executorch --use-pt-pinned-commit --minimal
138172
if [[ "${{ needs.plan.outputs.rehearsal }}" == true ]]; then
139173
# A pull request rehearses the release stamp at a throwaway local tag.
140174
git tag -f "v$(cat version.txt)"
141175
fi
142-
# A tag names its version; a dispatch appends its suffix to version.txt.
143-
expected="${{ needs.plan.outputs.version }}"
144-
expected="${expected:-$(cat version.txt)${{ needs.plan.outputs.suffix }}}"
145-
bash .ci/scripts/test_espressif_idf_nightly.sh prepare "${RUNNER_ARTIFACT_DIR}" \
146-
--version-suffix "${{ needs.plan.outputs.suffix }}"
176+
bash "${nightly}" prepare "${RUNNER_ARTIFACT_DIR}" --version-suffix "${suffix}"
147177
version=$(cat "${RUNNER_ARTIFACT_DIR}/version")
148178
if [[ "${version}" != "${expected}" ]]; then
149179
echo "FAIL: generated version ${version}, expected ${expected}" >&2
150180
exit 1
151181
fi
182+
bash "${nightly}" torch "${RUNNER_ARTIFACT_DIR}"
152183
echo "Packed pytorch/executorch ${version}"
153184
# Nothing downstream reads the unpacked component.
154185
rm -r "${RUNNER_ARTIFACT_DIR}/component"

‎examples/espressif/README.md‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ both with `executorch_add_model` and `executorch_add_kernels` (see the
3939
component's `README.md`). Application pipelines built on the runtime
4040
(for example wake word front ends and detectors) stay with the
4141
application.
42-
Consumers need ESP-IDF, plus the `executorch` Python package of the
43-
same release to make kernel bundles — no host CMake build, no flatc.
42+
Consumers need ESP-IDF, plus the `executorch` Python package the
43+
component was built from to make kernel bundles (see the component's
44+
`README.md`) — no host CMake build, no flatc.
4445
`idf_component_smoke/` is a minimal consumer that CI generates, builds
4546
and runs on the emulator (`.ci/scripts/test_espressif_idf_component.sh`).
4647
`idf_component_consumer/` builds the same app from the packed component
@@ -75,6 +76,15 @@ serves the tested files, and runs it on the emulator.
7576
`examples/espressif/`, `.ci/`, `.github/` and `docs/`, and run the
7677
workflow on `release/X.Y` with `version_suffix` `~N`. A release whose
7778
publishing failed in CI is published as a respin once the CI is fixed.
79+
- A dev version, `X.Y.Z-dev.YYYYMMDD`, lets projects use the component
80+
before a release. Run the workflow on the `nightly` branch with
81+
`version_suffix` `-dev`, `registry` `production` and `dry_run` cleared,
82+
between 00:00 UTC and the next nightly (around 11:30 UTC), so that it
83+
publishes the last nightly of a finished day. Projects resolve from
84+
production, where a version range picks up a pre-release only if it opts
85+
in. The run publishes only if that day's ExecuTorch nightly wheels, on
86+
every platform, were built from the same commit, because projects install
87+
them to make their kernel bundles.
7888
- A manual run only validates unless `dry_run` is cleared. With `registry`
7989
`production`, it uploads to staging first. Uploads skip versions the
8090
registry already has, so **Re-run failed jobs** retries with the same
@@ -84,18 +94,20 @@ serves the tested files, and runs it on the emulator.
8494
Each upload waits for a reviewer of the `esp-component-registry`
8595
environment, the only place the registries accept uploads from: on each
8696
registry, `pytorch/executorch`'s trusted uploader names this repository and
87-
that environment. Before approving the staging upload, dispatch the nightly
88-
matrix (`nightly-espressif-idf.yml`) on the tag and wait for it to pass; it
89-
also covers ESP-IDF master, the all-ops build and the macOS and Windows
90-
configure jobs. Approve the production upload once the release owner has
91-
built a project against the version on staging.
97+
that environment. Before approving the staging upload of a release or RC,
98+
dispatch the nightly matrix (`nightly-espressif-idf.yml`) on the tag and
99+
wait for it to pass; it also covers ESP-IDF master, the all-ops build and
100+
the macOS and Windows configure jobs. A dev version has only its own run's
101+
builds. Approve the production upload once the release owner has built a
102+
project against the version on staging.
92103

93104
The one-time setup goes in this order, because GitHub creates a missing
94105
environment, unprotected, as soon as a workflow names it:
95106

96107
1. Create the `esp-component-registry` environment with required
97108
reviewers, self-review prevented, no admin bypass, and deployments
98-
limited to `v*` tags and `release/*` branches.
109+
limited to `v*` tags and `release/*` branches, plus `nightly` before the
110+
first dev version.
99111
2. Create the `pytorch` namespace on the staging registry.
100112
3. On each registry, make this repository and that environment
101113
`pytorch/executorch`'s trusted uploader.

‎examples/espressif/build_idf_component.sh‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
# release/X.Y, after vX.Y.Z, and differ from
2323
# it only under examples/espressif/, .ci/,
2424
# .github/ and docs/.
25+
# --version-suffix -dev X.Y.Z-dev.<HEAD's commit date>, published
26+
# from the nightly branch; the checkout must
27+
# be clean as for a release.
2528
# --allow-dev-stamp X.Y.Z-dev.<HEAD's commit date>, for builds
2629
# from any commit; never published.
2730
#
@@ -135,12 +138,14 @@ ET_VERSION=$(cat "$ET_ROOT/version.txt")
135138
if [[ ! "$ET_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
136139
stamp_error "version.txt must contain an ExecuTorch version in X.Y.Z form."
137140
fi
141+
DEV_SUFFIX="-dev.$(TZ=UTC git -C "$ET_ROOT" log -1 --format=%cd --date=format-local:%Y%m%d)"
138142
if [ "$ALLOW_DEV_STAMP" = true ]; then
139143
if [ "$VERSION_SUFFIX_GIVEN" = true ]; then
140144
stamp_error "--allow-dev-stamp picks the suffix; drop --version-suffix."
141145
fi
142-
VERSION_SUFFIX="-dev.$(TZ=UTC git -C "$ET_ROOT" log -1 --format=%cd --date=format-local:%Y%m%d)"
146+
VERSION_SUFFIX="$DEV_SUFFIX"
143147
fi
148+
STAMP_SUFFIX="$VERSION_SUFFIX"
144149
# A release commit can carry its last RC's tag too.
145150
HEAD_TAGS=$(git -C "$ET_ROOT" tag --points-at HEAD 2>/dev/null || true)
146151
head_has_tag() {
@@ -191,25 +196,28 @@ case "$VERSION_SUFFIX" in
191196
$CHANGED"
192197
fi
193198
;;
199+
-dev)
200+
VERSION_SUFFIX="$DEV_SUFFIX"
201+
;;
194202
*)
195203
# The only other suffix is the one --allow-dev-stamp picks.
196204
if [ "$ALLOW_DEV_STAMP" = false ]; then
197-
stamp_error "--version-suffix must be empty, -rc.N or ~N, not $VERSION_SUFFIX; --allow-dev-stamp makes a development stamp."
205+
stamp_error "--version-suffix must be empty, -rc.N, ~N or -dev, not $VERSION_SUFFIX; --allow-dev-stamp makes an unpublished development stamp."
198206
fi
199207
;;
200208
esac
201209
if [ "$ALLOW_DEV_STAMP" = false ]; then
202210
CHANGES=$(uncommitted_changes)
203211
if [ -n "$CHANGES" ]; then
204-
stamp_error "a release, RC or respin stamp needs a checkout without uncommitted changes to what the component ships, but found:
212+
stamp_error "a published stamp needs a checkout without uncommitted changes to what the component ships, but found:
205213
$CHANGES"
206214
fi
207215
fi
208216
ET_COMPONENT_VERSION="$ET_VERSION$VERSION_SUFFIX"
209217
if [ "$ALLOW_DEV_STAMP" = true ]; then
210218
STAMP_OPTION=" --allow-dev-stamp"
211219
elif [ "$VERSION_SUFFIX_GIVEN" = true ]; then
212-
STAMP_OPTION=" --version-suffix '$VERSION_SUFFIX'"
220+
STAMP_OPTION=" --version-suffix '$STAMP_SUFFIX'"
213221
else
214222
STAMP_OPTION=""
215223
fi
@@ -640,11 +648,25 @@ sed -e "s/@ET_COMPONENT_VERSION@/$ET_COMPONENT_VERSION/" -e "s/@ET_COMMIT@/$ET_S
640648
"$TEMPLATE_DIR/idf_component.yml.in" > "$OUT_DIR/idf_component.yml"
641649

642650
ET_DIRTY=$(uncommitted_changes)
651+
# The torch that install_requirements.py installs to build ExecuTorch's wheels,
652+
# which declare none, and the index that keeps it.
653+
TORCH_REQUIREMENT=$(sed -n 's/.*"\(torch==[^"]*\)" if use_pytorch_nightly.*/\1/p' \
654+
"$ET_ROOT/install_requirements.py")
655+
if [ -z "$TORCH_REQUIREMENT" ]; then
656+
echo "ERROR: install_requirements.py names no torch==X for the wheel builds." >&2; exit 1
657+
fi
658+
if [[ "$TORCH_REQUIREMENT" == *.dev* ]]; then
659+
TORCH_INDEX=https://download.pytorch.org/whl/nightly/cpu
660+
else
661+
TORCH_INDEX=https://download.pytorch.org/whl/cpu
662+
fi
643663
{
644664
echo "# GENERATED by examples/espressif/build_idf_component.sh -- do not edit."
645665
echo "executorch_version: $ET_VERSION"
646666
echo "component_version: $ET_COMPONENT_VERSION"
647667
echo "executorch_commit: $ET_SHA${ET_DIRTY:+ (tree had uncommitted changes)}"
668+
echo "torch_requirement: $TORCH_REQUIREMENT"
669+
echo "torch_index: $TORCH_INDEX"
648670
echo "generator: examples/espressif/build_idf_component.sh --out $OUT_DIR_AS_GIVEN$STAMP_OPTION"
649671
echo "kernel_libraries:"
650672
for entry in "${LIBRARIES[@]}"; do

‎examples/espressif/idf_component_template/README.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ LICENSE file in the root directory of this source tree.
1313
[ExecuTorch](https://github.com/pytorch/executorch) repository.
1414

1515
`VERSION` in this directory records the ExecuTorch commit, the exact
16-
generator command, and the kernel libraries the component ships. `examples/hello_executorch` is a
16+
generator command, the kernel libraries the component ships and the
17+
torch to install with executorch. `examples/hello_executorch` is a
1718
complete project that runs a small model with its kernel bundle.
1819

1920
## Using it
@@ -48,6 +49,26 @@ component, and keep the bundle in your project:
4849
python <this directory>/tools/gen_kernels.py --pte main/model.pte --out main/et_kernels
4950
```
5051

52+
That environment (Python 3.11 to 3.15) needs the executorch this
53+
component was built from, and the torch that `VERSION` names as
54+
`torch_requirement`, from its `torch_index`, since ExecuTorch's wheels
55+
declare no torch. For a release `X.Y.Z` or its respins `X.Y.Z~N`:
56+
57+
```sh
58+
pip install "executorch==X.Y.Z" "<torch_requirement>" --extra-index-url <torch_index>
59+
```
60+
61+
For a dev version `X.Y.Z-dev.YYYYMMDD`, use that day's nightly wheel:
62+
63+
```sh
64+
pip install "executorch==X.Y.Z.devYYYYMMDD" "<torch_requirement>" \
65+
--extra-index-url https://download.pytorch.org/whl/nightly/cpu \
66+
--extra-index-url <torch_index>
67+
```
68+
69+
The nightly index keeps about two months of wheels, so move to a newer
70+
version before yours ages out.
71+
5172
Then declare the models and the bundle, from a component's
5273
`CMakeLists.txt` after its `idf_component_register()` or from the
5374
project's after `project()`, and add `executorch` to the requirements

‎examples/espressif/idf_component_template/tools/gen_kernels.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ def _check_version(component_dir):
102102
raise GenKernelsError(
103103
f"installed executorch {installed} ({installed_sha[:12]}) does not match "
104104
f"the component's {component_version} ({component_sha[:12]}); export and "
105-
"generate with the executorch release the component was built from"
105+
"generate with the executorch the component was built from (see the "
106+
"component's README.md)"
106107
)
107108
return component_version, component_sha, installed, installed_sha
108109

0 commit comments

Comments
 (0)