Skip to content

Commit 3d08409

Browse files
committed
Add a nightly matrix for the ESP-IDF component
nightly-espressif-idf.yml runs the component across every target, ESP-IDF release and configuration that the per-PR jobs only sample. A prepare job generates and packs the component and exports the smoke, esp_nn:: mul, ESP-NN delegate and Cadence wake word models with their kernel bundles, plus an all_ops bundle that registers every op of every kernel library. Twelve build jobs (ESP-IDF 6.0.1, 6.1 and master, four shards each) take that artifact and nothing else: they serve the archive from a file:// mirror, list the idf_component_consumer configurations with idf-build-apps (each sdkconfig.ci.* file for ESP32, -S3, -C3, -C5, -C6 and -P4, filtered by .build-test-rules.yml), build their shard from the mirror with ESP-IDF's own Python environment, check that every setting of its sdkconfig.ci.* file reached the build's sdkconfig, since ESP-IDF only warns about an unknown symbol, and run each build on QEMU or esp-emu. Each job records the ESP-IDF commit it tested and uploads its build and emulator logs to S3, where they stay 14 days instead of GitHub's 2. The configurations are default (the smoke model alone); nolog (logging compiled out), perf (ExecuTorch built without -Os, and the project for speed) and debug (debug assertions, and full program verification, which the app then runs on each model as it loads it), which each run all four models, so the delegate and the quantized, Cadence and esp_nn:: kernels run under each setting; wakeword, delegate and oplib, each a model with its own bundle, on ESP32-S3 and -C6, since the four-model configurations already run those models everywhere; all_ops, also on ESP32-S3 and -C6, with an 8 MB flash layout, so every vendored kernel compiles under ESP-IDF's flags and every registration resolves when the image links; plus a build-only ESP32-P4 configuration for silicon before rev3, which esp-emu does not run. The consumer takes the model directory as ET_CONSUMER_MODELS_DIR, from which CONFIG_ET_CONSUMER_MODEL picks a list of models and their combined bundle. It has its own sdkconfig.defaults instead of overriding SDKCONFIG_DEFAULTS. The script builds each listed configuration itself with its own sdkconfig, because idf-build-apps keeps one sdkconfig in the project directory, which the next build overwrites before the emulator run needs it, and runs idf-build-apps from a venv of its own so ESP-IDF's environment stays as ESP-IDF installs it. Jobs on macOS and Windows configure the two-model idf_component_smoke project with the packed component, once with its bundle and once in automatic mode, and check that both select the same kernels. The registry validation (compote component upload --dry-run --allow-existing) runs here on the packed archive. setup.sh accepts an ESP-IDF branch such as master. The workflow also runs for the ciflow/nightly label, and on pull requests that change the matrix itself (the workflow, its two scripts and idf_component_consumer/), since workflow_dispatch only works from the default branch and test-espressif.yml already builds and runs the packed component on every pull request that can change it. Pull requests skip ESP-IDF master, which breaks on its own schedule. Authored with Claude Code.
1 parent e69d00d commit 3d08409

22 files changed

Lines changed: 676 additions & 14 deletions
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python3
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+
"""Configure idf_component_smoke with two models on this host.
9+
10+
For the nightly macOS and Windows jobs. Installs ESP-IDF and its ESP32-S3 tools
11+
under --work-dir/<--idf-version> with ESP-IDF's own Python environment, then runs
12+
`idf.py reconfigure` on idf_component_smoke with the smoke and mul models and
13+
the component test_espressif_idf_nightly.sh prepare packed: once with the
14+
kernel bundle prepare made, and once in automatic mode, where this Python
15+
(which must import executorch) makes the bundle. Both bundles must select the
16+
same kernels.
17+
"""
18+
19+
import argparse
20+
import json
21+
import os
22+
import shutil
23+
import subprocess
24+
import sys
25+
import tarfile
26+
from pathlib import Path
27+
28+
ET_ROOT = Path(__file__).resolve().parents[2]
29+
30+
31+
def run(cmd, env):
32+
print("+", " ".join(str(c) for c in cmd), flush=True)
33+
subprocess.run([str(c) for c in cmd], env=env, check=True)
34+
35+
36+
def idf_environment(work, idf_version):
37+
idf_path = work / "esp-idf"
38+
env = dict(
39+
os.environ,
40+
IDF_PATH=str(idf_path),
41+
IDF_TOOLS_PATH=str(work / "idf-tools"),
42+
IDF_PYTHON_ENV_PATH=str(work / "idf_python_env"),
43+
)
44+
# windows_job runs under Git Bash, whose MSYSTEM makes idf_tools.py refuse to run.
45+
env.pop("MSYSTEM", None)
46+
if not idf_path.exists():
47+
run(
48+
["git", "-c", "core.longpaths=true", "clone", "--depth", "1"]
49+
+ ["--branch", idf_version, "--recurse-submodules", "--shallow-submodules"]
50+
+ ["https://github.com/espressif/esp-idf.git", idf_path],
51+
env,
52+
)
53+
idf_tools = [sys.executable, idf_path / "tools/idf_tools.py"]
54+
run(
55+
idf_tools + ["install", "--targets", "esp32s3", "required", "cmake", "ninja"],
56+
env,
57+
)
58+
# install-python-env fails if IDF_PYTHON_ENV_PATH doesn't exist yet.
59+
Path(env["IDF_PYTHON_ENV_PATH"]).mkdir(exist_ok=True)
60+
run(idf_tools + ["install-python-env"], env)
61+
exported = subprocess.run(
62+
[str(c) for c in idf_tools + ["export", "--format", "key-value"]],
63+
env=env,
64+
check=True,
65+
capture_output=True,
66+
text=True,
67+
).stdout
68+
for line in exported.splitlines():
69+
key, _, value = line.partition("=")
70+
if key == "PATH":
71+
value = value.replace("%PATH%", env["PATH"]).replace("$PATH", env["PATH"])
72+
if key:
73+
env[key] = value
74+
scripts = "Scripts/python.exe" if os.name == "nt" else "bin/python"
75+
return env, Path(env["IDF_PYTHON_ENV_PATH"]) / scripts
76+
77+
78+
def main(argv):
79+
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
80+
parser.add_argument(
81+
"--inputs", type=Path, required=True, help="prepare's directory"
82+
)
83+
parser.add_argument("--work-dir", type=Path, required=True)
84+
parser.add_argument("--idf-version", default="v6.1")
85+
args = parser.parse_args(argv)
86+
work = args.work_dir.resolve() / args.idf_version
87+
work.mkdir(parents=True, exist_ok=True)
88+
inputs = args.inputs.resolve()
89+
env, idf_python = idf_environment(work, args.idf_version)
90+
env["IDF_TARGET"] = "esp32s3"
91+
version = (inputs / "version").read_text().strip()
92+
component = work / "executorch"
93+
shutil.rmtree(component, ignore_errors=True)
94+
with tarfile.open(inputs / "dist" / f"executorch_{version}.tgz") as archive:
95+
archive.extractall(component, filter="data")
96+
97+
models = ";".join(
98+
(inputs / "models" / f"{m}.pte").as_posix() for m in ("smoke", "mul")
99+
)
100+
common = [
101+
idf_python,
102+
Path(env["IDF_PATH"]) / "tools/idf.py",
103+
"-C",
104+
ET_ROOT / "examples/espressif/idf_component_smoke",
105+
f"-DET_COMPONENT_DIR={component.as_posix()}",
106+
f"-DET_SMOKE_MODELS={models}",
107+
]
108+
bundled = work / "bundled"
109+
run(
110+
common
111+
+ ["-B", bundled, f"-DSDKCONFIG={(bundled / 'sdkconfig').as_posix()}"]
112+
+ [f"-DET_SMOKE_KERNELS={(inputs / 'models/smoke_mul_kernels').as_posix()}"]
113+
+ ["reconfigure"],
114+
env,
115+
)
116+
automatic = work / "automatic"
117+
run(
118+
common
119+
+ ["-B", automatic, f"-DSDKCONFIG={(automatic / 'sdkconfig').as_posix()}"]
120+
+ [f"-DEXECUTORCH_PYTHON={Path(sys.executable).as_posix()}", "reconfigure"],
121+
env,
122+
)
123+
124+
def selection(bundle):
125+
kernels = json.loads((bundle / "kernels.json").read_text())
126+
return {k: kernels[k] for k in ("models", "libraries", "prim_ops")}
127+
128+
made = selection(automatic / "executorch_kernels")
129+
expected = selection(inputs / "models/smoke_mul_kernels")
130+
if made != expected:
131+
sys.exit(
132+
f"FAIL: automatic mode selected {made}, but prepare's bundle has {expected}"
133+
)
134+
print("PASS: idf_component_smoke configured with a bundle and in automatic mode.")
135+
136+
137+
if __name__ == "__main__":
138+
main(sys.argv[1:])

‎.ci/scripts/test_espressif_idf_consumer.sh‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,7 @@ sources="${build_dir}/consumer sources"
132132
rm -rf "${sources}"
133133
mkdir -p "${sources}/idf_component_smoke"
134134
cp -r examples/espressif/idf_component_consumer "${sources}/"
135-
cp -r examples/espressif/idf_component_smoke/main \
136-
examples/espressif/idf_component_smoke/sdkconfig.defaults "${sources}/idf_component_smoke/"
135+
cp -r examples/espressif/idf_component_smoke/main "${sources}/idf_component_smoke/"
137136
project="${sources}/idf_component_consumer"
138137
firmware_root="${build_dir}/consumer builds"
139138

0 commit comments

Comments
 (0)