From 7b3042d4108fe550e7af12e9d1c71e84f3c572af Mon Sep 17 00:00:00 2001 From: RJ Ascani Date: Thu, 5 Nov 2026 12:51:06 -0800 Subject: [PATCH] Publish the ESP-IDF component to the registry publish-esp-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 manual run on release/X.Y publishes a respin, X.Y.Z~N. Other v* tags publish nothing. The workflow packs the component once, under the generator's release-stamp checks, 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 (apart from the examples' manifests, which the registry rewrites to drop override_path), 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 as -DET_COMPONENT_REGISTRY_URL. 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, 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. prepare now records the commit and generator path in that archive at pack time, because compote applies them only when it packs. Manual runs validate without uploading unless dry_run is cleared. Pull requests that touch the workflow run it as a dry run that stamps 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 names production as esp-nn's 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. The README's runbook covers the one-time setup, in order: the protected environment first, since GitHub creates an unprotected one as soon as a workflow names it, then the staging namespace, then each registry's trusted uploader, which names pytorch/executorch as the repository and esp-component-registry as the environment, with no branch, because the registry cannot pin a workflow and tag pushes carry no branch. Authored with Claude Code. --- .ci/scripts/esp_idf_mirror.py | 2 +- .../espressif_idf_upload_requirements.txt | 460 ++++++++++++++++++ .ci/scripts/test_espressif_idf_nightly.sh | 199 ++++++-- .ci/scripts/upload_espressif_idf_component.sh | 41 ++ .github/workflows/nightly-espressif-idf.yml | 12 +- .github/workflows/publish-esp-idf.yml | 292 +++++++++++ examples/espressif/README.md | 45 ++ .../idf_component_consumer/CMakeLists.txt | 16 +- .../idf_component.yml.in | 6 +- 9 files changed, 1028 insertions(+), 45 deletions(-) create mode 100644 .ci/scripts/espressif_idf_upload_requirements.txt create mode 100755 .ci/scripts/upload_espressif_idf_component.sh create mode 100644 .github/workflows/publish-esp-idf.yml diff --git a/.ci/scripts/esp_idf_mirror.py b/.ci/scripts/esp_idf_mirror.py index 75a9e036f95..cef0791381e 100644 --- a/.ci/scripts/esp_idf_mirror.py +++ b/.ci/scripts/esp_idf_mirror.py @@ -68,7 +68,7 @@ def main() -> None: { "spec": requirement.version_spec, "source": requirement.source.type, - "registry_url": None, + "registry_url": requirement.registry_url, "is_public": requirement.is_public, "require": requirement.is_required, "name": None if is_idf else name, diff --git a/.ci/scripts/espressif_idf_upload_requirements.txt b/.ci/scripts/espressif_idf_upload_requirements.txt new file mode 100644 index 00000000000..5ce9301582f --- /dev/null +++ b/.ci/scripts/espressif_idf_upload_requirements.txt @@ -0,0 +1,460 @@ +# Hash-locked requirements of upload_espressif_idf_component.sh, whose jobs can +# publish to the ESP Component Registry. Regenerate with pip-tools under Python +# 3.11, the upload jobs' Python, from the single line idf-component-manager==X.Y.Z: +# pip-compile --allow-unsafe --generate-hashes --no-emit-index-url --strip-extras +annotated-types==0.8.0 \ + --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 + # via pydantic +certifi==2026.10.23 \ + --hash=sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd \ + --hash=sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1 + # via requests +charset-normalizer==3.5.2 \ + --hash=sha256:01077390b03f7988f11d700a2194e69b119741a86b1a638b1db88891e3eced8e \ + --hash=sha256:01b0c0d2262a9e28e8484a278c7e1b5d650e3ac8cf2683d2967e25899f208bdf \ + --hash=sha256:04851f73ae72b8413dddadb16a49dfee95263553741fd42d546f7d66907e6be5 \ + --hash=sha256:0521c5665880b33d603717defa76c094048900010897909952397feb3039da56 \ + --hash=sha256:0774bf9bf620249fee3e0b8b9fd3065de213be30f3aa94ce2494b3b638949e26 \ + --hash=sha256:0891b9d3903c5571c03771ca669a4b0ec5618ca722a5c957d3d29cd4e5062848 \ + --hash=sha256:0c951d5e6dd9c2ff60609476752bee49da4206adde960ebc247766937f72e718 \ + --hash=sha256:0fed1d06615f022ee3b13caf5e8b180cfea32bb2c5aded8a9d44277afc040f93 \ + --hash=sha256:114e4d0c92d618409ed82a99e22b5c5e768fe995f2973f78265f4524f49d4640 \ + --hash=sha256:11912e4bb14baae7c5d8791aa55ba0a3a03ec6729073307b0f57270abaa713d3 \ + --hash=sha256:11a4d68a6ecda3292cb1e50239e111543ba5d709bb62a6b4ea1afcfa729d8875 \ + --hash=sha256:124fbf1a8ff966d87ae05bb8bd45a71f966055ed8bba320d0c7cf450bc5f4d0e \ + --hash=sha256:1461ac396c4fdb983a675f20aa555624f0ee18ac83d832b9244ffff3d8055275 \ + --hash=sha256:1503bccbeb36d5527790c3930327704c39af22de3112f1b1666a9f3ce15ee204 \ + --hash=sha256:15bb4005af6320d259dc7593ca84a38d7fe06a421dbcf7b910ae23979101e787 \ + --hash=sha256:15c44f7edfd477b06f517a5cc317fc1707edb9de2c865f43d4b6513907473234 \ + --hash=sha256:16fa0eccf81304b79c5cd87f9271c3b85dd9dd99245e4422ae9c0dd45e0f99d3 \ + --hash=sha256:183b88127acdb4fabe59d951ab424faf1af7b63cdbb5f776186c1ea2ffcaed98 \ + --hash=sha256:195c26fb65950f8fce54e26349852b7bdd7c5f120aeefbcc440b8a20faaed4a3 \ + --hash=sha256:1afb975bd5d68d5ce9f6b6d44fdf2f7e34b895a35e95708a7a91b20a3b51d187 \ + --hash=sha256:1b4cbc7c3491ccb4aa17fcd8165649d01cf39f76de1696da8631b5f71b85401d \ + --hash=sha256:1bc0baf5ef96b6ede57d47f4b8fe4d9d84019c3bfcbeb20a41edc6a6ee341f1f \ + --hash=sha256:1c50fe28bbc2ced33386f298650d91218076c05420e6cbd790b913adc41659e7 \ + --hash=sha256:1db38f4c5496827c1a501846d64d14c3b80c7e6714e406cd7dc36a9899fa1011 \ + --hash=sha256:211d5a3eb6af8f513b8d4ca19a8c1b7accab1b5f0d3175f9826b03c1a920dc1f \ + --hash=sha256:23851fb4e1b85ed3f6c2a27b777cdfe2e19fb5b38429a8faf38c7542b7665869 \ + --hash=sha256:254eb48b9fa5ee9898a3c445825a1f340fe53712a098904b39b0bddba8ea3cb1 \ + --hash=sha256:2625388c6c754520c37abaf3b41eb34d1cc4a373f457898f08606c8e362b891d \ + --hash=sha256:281cb91036248400f4cc957495cccd44c275c2e0c5854f7e45ac5cf7dc193847 \ + --hash=sha256:28a15fdad492a99b6eccfaaed66ef3f74050680545ea61ec8b2f4c538f1f1320 \ + --hash=sha256:28b4f0d66fb834ff90f28209ac7bce77868c45d8c93e26f906709d9b7c2e1af9 \ + --hash=sha256:2a925889534b3748302dae5dead07cc13480de1dac3aea80a941b729b471ef93 \ + --hash=sha256:2b7b3bbfb4fe8ef40600792d762fbaa9057559f9d3fad209525b7a22b99e91fd \ + --hash=sha256:2c9ad19a6cfcd5ea5c0d41161d22f9df1dcc277e9bef2751391334546a314c00 \ + --hash=sha256:2cc961b171b3f3440f410489ab3573e86aea8736134ebbb40ea1338b7f0831bc \ + --hash=sha256:2ce45c6627b22c47e390bc91a41c3d13032192e699fa0bea96e9671b373d69b0 \ + --hash=sha256:2e06a3a98f916dd41d27f3105e02e7a40181c98c94b9158733d03a6f80506c09 \ + --hash=sha256:304d5463e65a35d7bb0850550e0780395395f6fcf452f04db7d5ca7cecc425ac \ + --hash=sha256:304d8e4d493af723536393eee0c689eb7813f4a474c8b479dee63f1fdd98f621 \ + --hash=sha256:30fcd120b732aa79317f08dee04d7de0847822e4cf7ee0e9f445bb958832252c \ + --hash=sha256:31f3930700408d211f13378ccbe1c40845d8da54bd0681fac3a9b5aae81c7aa8 \ + --hash=sha256:34276fd796040bf0993ab33a369aa572e6979c7aab225a88893667ad8eac8f7a \ + --hash=sha256:355ad8011081dec5412240c087a9a0c9d4d5039f3ed11a3f13e18c2b29b56c51 \ + --hash=sha256:38a873987f3be698494da8b2e3085e29da02da7b633dce73e79c699a113d7bf0 \ + --hash=sha256:39de2a259fc954455c57274dc94c79d5842774e1247a016aff30bc0efed0f4ef \ + --hash=sha256:3d14b50de6bf4d0edf857a9386836846f982b8f524e188e2e68b96d702bcf4aa \ + --hash=sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6 \ + --hash=sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649 \ + --hash=sha256:3ddacd27458c45bdacd6bd6db644bfb730efbf9e830310186e3045c9c5be8fb2 \ + --hash=sha256:3df041de8887954562c9b261cba85ca0e9ded74048daf125f45edcfaa4832229 \ + --hash=sha256:40ab6bffa02ae10a0581e6c198be7d2d8ca5c2a0c64e4ed3465d766df457573e \ + --hash=sha256:4275811936e2f06feff5e598fb42a1b7ae852da8e39605211892b56b81a34efd \ + --hash=sha256:443eae2bf318abeaf6f15d785138f71fd6de770e99a92158b8b814265e079115 \ + --hash=sha256:447441e76ec720b15e64418d32e092297340387053047c7c694f579efb0ee1d9 \ + --hash=sha256:4495c5002a7b28557e7e222e77e0b661183e432b7d6d2e788101e3f240e05b8c \ + --hash=sha256:44bd4fbb29dfbeba60e7d2bd000c59e4b21ddb3cc53912b14048d37092706d7c \ + --hash=sha256:4685902cf26edf013ed7a3da0f426ebba7a00ebb9541386d835afbf002c11cab \ + --hash=sha256:498dc3188ca05a68231ac3fdbfc7f57eb67e1343c30e0fea17f8218c1599b253 \ + --hash=sha256:4c2b5031f63e331e3839b40aed2dd6f191e9c07edbde303e7876846ea1946995 \ + --hash=sha256:4d48f2d08b9de5864e2c8744d4461b862fb149a18274abc8b698c45975573438 \ + --hash=sha256:4f87960d57feabfb618e4e0af6e7371645fa26a277860739d6e5d6e0012c92f0 \ + --hash=sha256:50e3adfb96fc189eb27b1cf62d3b598b89b4bb0420d93a3d3e42e137409011be \ + --hash=sha256:51cf45226a9b588d0d2b4880c62d686934b63ab0bd79ca23ab0e9762eb27441b \ + --hash=sha256:52aa6992700996af31f375de0c6bacd402b0097fe40b53c426b9f51a90ebabc7 \ + --hash=sha256:55ea99acb17b9325618de155a0cd6a2e8f5d10be008113e1d433bbb58db543b2 \ + --hash=sha256:56bc200a365efb37383b7852e4cc5898d3b2da5987289b543956cf8cad71018a \ + --hash=sha256:588461c2e8384d309bd63e5826019b6977bc66d629b99ac8737bb795d7b2cb5a \ + --hash=sha256:58ca3755ee7ff7f59b57789ec9833c9de9ea275405cdd240eda1f193112e398a \ + --hash=sha256:58f361dcbab699cf8f42db3f47c8e7fd1036f138c23a5d08de9fde5f425a730c \ + --hash=sha256:598a11a2c7ebaa5334bf698bf29568c9c390abac6a154d8170fedecd1cea38c5 \ + --hash=sha256:59f63901b0031c3136cf64704dcb21de0bbae62ce2c9529bc39d27665463de37 \ + --hash=sha256:5cde776b7cc66e4f6c99612cea4aa7269aa65863f7a15841b2c264f103822f4e \ + --hash=sha256:5e2b6b57e9733d39f0c9fd3185efa6b8e29652c4cd8fe94180272cf6ed9a78c4 \ + --hash=sha256:5fb29fb8cd1a46c27a1bf9613ad5ec2599310d46b4025d9556404a6b6a292800 \ + --hash=sha256:6045373d5a89a5ec71afde535db987ca28e76dfa276c2d4c818265b375d4b055 \ + --hash=sha256:619799369eeef6366ed3e8755a5670f4f2f0fb6b30a0fd7264dc0fdc2357058e \ + --hash=sha256:62588a277bfb59def052abd940703fa35107152bf479781a878617d60faf8fb5 \ + --hash=sha256:62603db9a7caa0802eaa28c1c46fecd7b3a263a774069c24c3c28c302448721c \ + --hash=sha256:65cd72beeeca9d3aaea1201e5923859f308f952f9c71de93f06063c79f0f7a3b \ + --hash=sha256:68eb192d85ab8e5f6ec69c2bc6ac0179fbf04a5ac1569d12fbef74883fe102d0 \ + --hash=sha256:6bd128f206a7752ae1f2ab6c61bf8a24ba28913a10df8b14c2637b973ff97a80 \ + --hash=sha256:6be488a102b8cf28d0391d8c4ba7748938ae28b78ad901f8585520fca33ead1a \ + --hash=sha256:7218e8f32b0956cfcd048fd42d9d5779809745ca1d86113ca56f66e7ae1549c4 \ + --hash=sha256:7441d755b7ab94f8d4eb3e43ec05482d760842fd263d003a99102d742cd835e2 \ + --hash=sha256:749e97e1b32313717a565abbe321bc2190bc8b35f1a67e4cdbc7c56c8d8ffe58 \ + --hash=sha256:75a3ceed0724d625d64b86ca20aba182e4df462e04c2414fc941c0f523f06aac \ + --hash=sha256:780fbe7cab297b81dad9fb8dc5eb003c0468ffb0d9e5f65068c53a34661a96bc \ + --hash=sha256:78456a747de8dc58360ffa581f30a002baf5aa28cb262536545e91f113ed7639 \ + --hash=sha256:7967d08cf06dee78443b874f98c98036f624f3a4e73e11f9f64f5be4d25393cf \ + --hash=sha256:7a881931aa470808df94a8c380eed2bbbc76cd9dc622310f99665658c821eb6d \ + --hash=sha256:7dcd882da75ef9adf94903b1e3b9419e8aa8fb4c7396822b834b9ef7fb96954f \ + --hash=sha256:7e841fb9010836c992c9f12fcbd43a831de93a5f726fc1ccd8ca1d0268c5014c \ + --hash=sha256:7fdde2c9fd9e3eca40631e024664cf2584272cc8f96308cbe5fdfc930f51d8bc \ + --hash=sha256:8024d00c3faf3fc0c16e07a69f4405e8eac7cc0ab15f65fe6cf43827c4cf72b4 \ + --hash=sha256:80d02b6f04e92601a081dd97b23d3128033098bff5d35d392ddcc0476ea11253 \ + --hash=sha256:838dcc90063569a0448120554591a1d6c4a4ffe11babf048908793154ab86ade \ + --hash=sha256:849df64e889b2e17230d58410a03dba311a65b163508fd33679b2b737d4b7858 \ + --hash=sha256:87475fabc8d9996fd9c27debb395e642e8c838d78a00b6e932227a0e06b81e26 \ + --hash=sha256:87e50a3e7cb90af586b6c5faf23e302a970415ac73bd7bd90a515a04b427ef96 \ + --hash=sha256:89b53f3cda69831909888e0494f4fa0bcd3537e3e138dabeb620bd6ad946bae8 \ + --hash=sha256:8a893cc101149f80a653f82062ebc95b34525a2614382e1da5458fe7c6997249 \ + --hash=sha256:8b2bfab86aa71ae13aa41a6a26aab338e0db2b8bc75434b05aea89e011ff35a4 \ + --hash=sha256:8d86d6fc60743dc916eb79e2eb1ec4818e21e427731543af40a3021851174a13 \ + --hash=sha256:915563965d418f986e7e145accc592eae9e1a1be3566ff98a05d7a9ec42a76e1 \ + --hash=sha256:92888bb3187c5ba50500b00b3b310c9f2c651709d28036077680cb5255450a03 \ + --hash=sha256:93223adc95033dd47133a46ccfc316a0139176fd79085762e27202ec56018f03 \ + --hash=sha256:9373ad13ef0d2c0fb761e04e55bfdee5a08b52cef2c882c8fbe9935b1517152e \ + --hash=sha256:9409a8bf35cf78353942504b24a57de3d75b708997a1e4bd8db71ac8633ce364 \ + --hash=sha256:9b7f416ff0978e2f2249330527f0ad6fa02f4932e6199692d3b52da2048c19e4 \ + --hash=sha256:9bde855991b7e362c146535e3136a50bfaffc0487d38b33ca7e5edefc6e23849 \ + --hash=sha256:9cae88599c7219005d879f98e5ed53341e9a122af585e1091200358a3003d2a0 \ + --hash=sha256:9cf9b1a857e25c4baceeb3624e92a56df3668f398c4acba74e174d81fb4d1d3a \ + --hash=sha256:9f56f72050826f63dcee7a7f55b0a77168cb3bfc553fd405e7f8f9ece75a4036 \ + --hash=sha256:a090bb2c68df85450502e3e20d665e3a5af9c65a84d6508ed477badd49166fd3 \ + --hash=sha256:a192e2c40070d92c3ccf777e3a5c4ff515573cd2bb7ed0c537fdadbbec5bbf21 \ + --hash=sha256:a19a731138fc27d5682277d3b9df22855cea1239bce7fcec5f78f42ef2d1f3c3 \ + --hash=sha256:a66c3bc5ab1f0ff2164fc9965ddd611ff0802173f4b9d24554c563f6ab7e1d6e \ + --hash=sha256:a815775b6c38d4e0ff7bcffbeba67feded90202bb6a226b8dd35f1c855217413 \ + --hash=sha256:a89012d6d5476ee112d20d998570ed58df2260a852afb1758809cd6900411d21 \ + --hash=sha256:ae4f5fea5b8b8ccff88238cc8569303e5ee95efae67fa62922a311397a71f346 \ + --hash=sha256:b6856554c4f44d79fc2307d5768854310a8f0096e501c75637542c82292b0429 \ + --hash=sha256:b6b751274acb69d77b3323d6b7dbaa3c7fdfc1eb829b7eb61d262f32e1af9685 \ + --hash=sha256:b736353c0a625bbd5fcec108576e2385db3496f4f771f785ff32e108d3c3bc45 \ + --hash=sha256:b7fd005a73d9e657273b7a10dc71a9e03c8fb9ee6999798d6918ce095b81ac7f \ + --hash=sha256:b91363207bd9dc966a691e959bb47f64b30f7ac4b072be9968b366982f7db77c \ + --hash=sha256:ba0b1d2620edf869789c3879223f52bf2afc5d31b3cb47cc57b3a12c05e2aa9d \ + --hash=sha256:bbbfc8e28816f19d7c0f1816664980c0a9875d01b27cdf8eedddb639d9e108ad \ + --hash=sha256:bd16aabe4a02a297c23417aa17ac6299dbd8c49f673bcd645b4929b11f5a4400 \ + --hash=sha256:c0afc6800ba57ccc350374c5bd6150419915d95ce93cdbab2d783d75eaf30ecb \ + --hash=sha256:c6708715abcf3c73b99508253e961a9967f02fe536532834149574eda6de0d1c \ + --hash=sha256:c7c9ab723cde841fefb34efbad91e87f00a674b1fe1cd0784fde742bf2c154dc \ + --hash=sha256:c8f3d67aeaf55f017982b73683f0e7342ba2f6635a78f69ce89ebb26aa411e5c \ + --hash=sha256:c9790464842f85f437dbbb54417eda1e0e6bfc52dd8d22d6fd1c994b73b2dc74 \ + --hash=sha256:ca403d7e4798f525fdfc78e258820419cbbd0f0ecbab9de7840e3c017cf6b8cf \ + --hash=sha256:d008d90a7f2471519aef0c90dfbe73b3e6e4d5e66ac48e19154c17e89e98b604 \ + --hash=sha256:d19fbd981a488e22cd04883659ca6b08f50b5974f9fd7c95655ef6a043e5893f \ + --hash=sha256:d1befeed746d247c81127bb14de9dc3d30edb6e5976d34f83f86ed262b1d9105 \ + --hash=sha256:d2374b62878abb00cd8309b32af6c0b715cd02dec0ca74ef12e5069bdc64144a \ + --hash=sha256:d376bbd28b3a8999db1a103b3b388aee6f1ddeb3e51bc2172993efdcd86e064d \ + --hash=sha256:d4a7319f304a774bed22115bc891618e45f85065ab44ea6acd07d274e750519a \ + --hash=sha256:d6734d2ef8a50fbf8445c139477da401f50d62a0606bf00e20ec6d87773fefb1 \ + --hash=sha256:d760fe2a4d7c3b226cb9026d6a842868d52a7901bd98420e1baf14e80da85cf5 \ + --hash=sha256:d913de495d90407cd859d263bee2e5d1a4ed3eb6573c04e70d9ec619a7cbed7f \ + --hash=sha256:db19d07e2e0129e974a0e65d0064fc222a446cd5122c2fd4184d2af9fc734a9e \ + --hash=sha256:dca9ab98072a5a54ebacebdc45f53e645336b320c667410b061be1ca588ae709 \ + --hash=sha256:ddc7dacc8ece3a182e7f15cb862d1fd616b46d076cb1ae9dd232b2c38b655874 \ + --hash=sha256:ddf19c062bea7a0cc80f519243d2c01dd091be0cf952a0750d4ad576709559f5 \ + --hash=sha256:def79fa35ef0cef8d2accec024f4fdc7ead3012ff02f5215c783f39f03ef8cfc \ + --hash=sha256:df29a0a7107f7011e77f4eebdddec4c7331e24d787a0b21a46d63bdf7445da95 \ + --hash=sha256:e09a3942ecbdee5cce73ea9d42da82b81b72ac1bf031ce069b93b5adf4eac8cd \ + --hash=sha256:e242bb1c5e76e97dfa9e7f209a71e93a01d7f19ffdd5cfbb2e2d55b4f08f8ab0 \ + --hash=sha256:e243bd13217235fc7290c621941c3f5cc8b66e4872495be821d7436ba2fb838d \ + --hash=sha256:e2af3aad578aa6bd1384bcf4750fc285e5a9de53f40b7d41e5a0bf748edeb2b3 \ + --hash=sha256:e4e81e09c1578b8df602e3db08b0b3ea0a6947ad612f52bf8dc5ea8d47691f0c \ + --hash=sha256:e54da4baf05720032d527874d40b65fa4d7e5c6c6a43d0c3adbeffcaf275a2b3 \ + --hash=sha256:e80e6c2f55656b4824d72065abb4ddd6a525c74bd78a0aab5d9fc2cf4fb5af50 \ + --hash=sha256:ed2a239c0ea213acc1908150a3037257083c7c083128f1a4cec2ec4b97dca491 \ + --hash=sha256:ed905975ab14056a2e5eb1c376cb2e1ebc5396baf84163939c518556fccde9f5 \ + --hash=sha256:ee21e28f0430bd6dc9086c6e525d5e818a44a5ad19720c8a0ef766792f3eb5e5 \ + --hash=sha256:ee43c17b173d46a3212baa6ead3ae258eeabdae48c263a01ccf0218c366dd655 \ + --hash=sha256:ef4fcbf3327382cd4c9f540babd61248208af7b93eec4de397b4d5f58a09e288 \ + --hash=sha256:eff0ac9dbe711a4aee69bf04a83896aa9b85f19641264053a9f6d48573abb7dd \ + --hash=sha256:f0aa869112ef88429ae17820d99c3dd9504c9e9c671d3c246f3d7442cb051084 \ + --hash=sha256:f3c96f633825733f735c5a9cf21d21a257d8e1edf0b1cee0a064b9c424ca0f7d \ + --hash=sha256:f5833ad231be5eb6553de524a70f48d71b2c8563101750531e0b80184e175cd4 \ + --hash=sha256:f5ec61164adcec446f8969a3358ec3f9b26bbda3b9213e5586d219afa8df2915 \ + --hash=sha256:f7d486c83842422badd511868fd8a9a20e9407ace71564b6af47ce7e60a336c1 \ + --hash=sha256:fb9e68df06293761f9fe66ade60a9bc6d0f5e42b8acf2939a9158af86ab0e5bd \ + --hash=sha256:fc14a032f813bf5fe624d991960ea83e9715adc27e4c1830a2361eb1d02ac341 \ + --hash=sha256:fcff63213e8e6e47770541a4607175404f47cbb3ebea7b6058cc82d524a0e424 \ + --hash=sha256:fd1fbe0f116b6e55da77aca2c6ddcddcfac2186cbf78bdebf40fc156efca389d \ + --hash=sha256:fe9753dfee015c570d73df76f899f18444d41388bffcde097deba51c4fadbb9f + # via requests +click==8.5.1 \ + --hash=sha256:8bb5376d2006950386f8c6427613334a2acadc5f559cb80e638d00b4df957e37 \ + --hash=sha256:9bbfada76703fd1d784b83eea012e3e8c6a001a876543ef104fa56aaaeb072b4 + # via rich-click +esp-pylib==1.2.1 \ + --hash=sha256:c0854cc57b86e2745e9f7ac820d5d5e8a9f2c2d93caf12623c419b8d08d852f6 \ + --hash=sha256:fb05017676200e38dc182013c994e62d9088d377081c88562e14a2f5aaae6456 + # via idf-component-manager +idf-component-manager==3.1.2 \ + --hash=sha256:424494493b47b61ea0f7666d3018a71deecb77f21186bc8b6761b0b6f98e8f90 \ + --hash=sha256:75ad9e4452d1bf42bea0a77d72af679714e2bb147627d926cee114122b6e33a0 + # via the line above +idna==3.20 \ + --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \ + --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c + # via requests +jsonref==1.1.0 \ + --hash=sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552 \ + --hash=sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9 + # via idf-component-manager +markdown-it-py==4.2.0 \ + --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ + --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a + # via rich +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +pathvalidate==3.3.1 \ + --hash=sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f \ + --hash=sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177 + # via idf-component-manager +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9 \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1 \ + --hash=sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 + # via idf-component-manager +pydantic==2.14.3 \ + --hash=sha256:1054f237f4d4733e5a30e1e2333bd33a2004ab9ab1510bd053a9a31bb48e232d \ + --hash=sha256:3f7b347782ab7e4cf4c798496ae3a82787d9f21ec12676a9c564355eeef8daa8 + # via + # idf-component-manager + # pydantic-settings +pydantic-core==2.50.4 \ + --hash=sha256:0001d6920e3a5d92758cda460d6dc9f918a78ad6d5d092f1c8b5395e6b775356 \ + --hash=sha256:0574866e05f6adeecc520d87e87dd06210c0a51706341f6ef8421572f5a64444 \ + --hash=sha256:09450778e3ab58d3f565899fcfbf6c82926e31af29e28e799d1d50941316e0cb \ + --hash=sha256:0af56dbf8822ba9f4efcbbfdc15478f8b6fb08f1609860cb50aa2cc8476b3c34 \ + --hash=sha256:0b059a7968b1911be5a2d9a8234827ab0bef9d1e465d4b42da525894143550e8 \ + --hash=sha256:0ec20aca2b067369913716847f7dfe5f4dddf16a46965b7a70bbad5defaaba86 \ + --hash=sha256:0f87ae9db5ba8d8de40dcb93982cf8dd544e6552a80d3345af51941cc2146b80 \ + --hash=sha256:1239daaf063e62213ca1f34d96663900bd7510c42a786e843fd328af70e46445 \ + --hash=sha256:13b6dd62b5279f5b9d72f3bf918a35443ee8bd549241d9dc52d79a2c3b6d7016 \ + --hash=sha256:15bece43a02ad543c621c8305e67dedb5f27d378c5ba741e5a2c36523f8ef37f \ + --hash=sha256:16902a147b3da4840612164a5e67947104d63e978f7a57c395877a5e26690b9c \ + --hash=sha256:1afefe44ec042fbabfb4483ea258c04cf75ff4ff328d754f09b321fdaa232aca \ + --hash=sha256:1de7be9a66fc8536770481ab64a05aae5067c725f2b29a09543af00e6a417b97 \ + --hash=sha256:1e20cbde2b6deb33a51d08cd168454dd9044ec7d4474056f4d98d63f51578783 \ + --hash=sha256:2039e323acee9abfec1fb5f661776e8965fe5928f3b7f8431900a774cba5c317 \ + --hash=sha256:219114f8dcdcbe4720d82ef016b1287ce5a0a67d7c69ca61524eb72e82aac344 \ + --hash=sha256:22e874666bdbd96d6bf629d32cd8ab376fedf1fd62585b52a42b9fa9fa6c20b6 \ + --hash=sha256:23a9ba564b2db709ff16eca990992ba95a19069972b34bff87c81d4b473cd755 \ + --hash=sha256:25e7e1080f9f7ce7cd9d114b31470ce912771bd02f38c824721d3c35d312abff \ + --hash=sha256:26cb012c4b8ca9f847bd0ac15978bd3d0f55b050bfbe6c47355edfc7e945029b \ + --hash=sha256:29358a8ca74e7d81158d14e2b10c904bd18773293ae3788716c0626fe0342347 \ + --hash=sha256:2980a701be88f9128a7470189fa3bcda5387f76c298a6f8e000fc29eb2a6f4e7 \ + --hash=sha256:2a60ec2a57ac3ef3767e526e9bf706ec75aa3d79c4ea134d9d4fcf7c62a297a7 \ + --hash=sha256:2f42584e5785385f681163a42506cab0d51295e17599a648396c52a7046c1372 \ + --hash=sha256:2f5cc5bcb94063fbbdfd285a2b302da34af07491337c10bb35de1d5220fa1583 \ + --hash=sha256:2f6767122b37c780bb756a720a4eef2bc6a050a0a1143428834b3cbc89448bd2 \ + --hash=sha256:300753f71c73302e218124baf995f00058e085cd01869bd9610bcbd0bd4500c1 \ + --hash=sha256:30d4dc35bc0eea52202a90b3820445ee0072c820241bfa710a623a078c9b267b \ + --hash=sha256:32a991aebd165f227b7c7c60bb9d66c2c7de9c24308552da264c9938fde0f8d6 \ + --hash=sha256:32d6dda86383645ec428ea64dfc277036f5cbc60a72238eab28b91a3fdb9a113 \ + --hash=sha256:33b21f69540a12c2053cd1d95766d8bd9667a3897f7c712a642de8aa37eb0865 \ + --hash=sha256:39f489ce31272073e6e146f6781690f9c1ee8c126836e6403361ef91154d89fe \ + --hash=sha256:3a48a5254d15a0ad050318bfc7a9566d4ec740118187e8f303e47a6840287eb7 \ + --hash=sha256:3b8ee2589a082739c522c1fe4be227412fcf108b903abb6672ae2f5570558882 \ + --hash=sha256:3de6c2a4df2af0ac9ab4dc1488746fe8b1a90e27955d870d9edda3187d20092b \ + --hash=sha256:3e0b395648c6cb871bceb2906907f255dc876ec425f2d5b024e129985cea8519 \ + --hash=sha256:3e547ceec1da7382f01f9e13963023e53367a71802c88778b21b9645a077cbdf \ + --hash=sha256:41b2cd8b29dbb5a4c9828a70f50d1abfc92011115c813213d3a91205c6f731b3 \ + --hash=sha256:41f4f8ae714e4fea1d635880f8a1af715ecf080eed4d13c5b04a0f3210f9c61a \ + --hash=sha256:474f7e598c7902165662a5d5160d3fce31037890a2975a9871ebf7c05862300b \ + --hash=sha256:477af785d1846e4e96bf56983b92ea7553aaeccdeb8acdaa55c7cee9eda0ae32 \ + --hash=sha256:49eb1241a969a5994db6880c18c012b6cbe2340343d8f529721571806583e500 \ + --hash=sha256:4c29b8f2d6417069bb1f257e6b9113f5d64dc2f554cc200ea90b4fe2c5cdb733 \ + --hash=sha256:4eb15be588407148ef5937dc8d9256a71f6b115e6cddadb7aa20b6ee384680dc \ + --hash=sha256:4eda7e3fdef382b7d4492745988a5baaf6953923a337f8b9dde5f76fa8afde20 \ + --hash=sha256:523466eba8ee10f7480f3c2057563145978011c583130d2cc01fb1e7af808e25 \ + --hash=sha256:57fa0a9490785139f1a4e4f43891830d55dfe4408845c0c9cb5eff4aeaf75f69 \ + --hash=sha256:5b26e5f1793b96cdc60242bf38da86ff24a27a708aa396214cac9b534bf571c7 \ + --hash=sha256:5bc1811ef2b4d4ba63e8e452f6e332b6374bd1d2cbbfdf66826c8cb1f71dd10d \ + --hash=sha256:5c074454dc6d4f6f4ba6561a56bf33a14db7651d882a5a5321ba3ae8868e082d \ + --hash=sha256:5c1013647be741647895c102831dcf79c34209662dbb683df9efb864417ed7d5 \ + --hash=sha256:601fce6cea9ddeaa85329e12e7f351def38d233075f42a3012b9c48071b02941 \ + --hash=sha256:6118e5a5550e36582e19bac1a403711a748f9f18251b054c326d3db02aaa7a18 \ + --hash=sha256:629eae995185540406e3635df5d5cf1d44f19ea240840b835d3f44541ff694d8 \ + --hash=sha256:6348ad12fa741bcc9c2f173e99fbf27cbc038826d8c31a4a7d836eca27878ba2 \ + --hash=sha256:65376dbe1ab4dbc768ca2b72b7c13993562c035f5925fd3bc7ff9c8c585c515d \ + --hash=sha256:6619d49a89a15cd04334f76fec530c155923bfc60da74ff5d815a52629e1aa6c \ + --hash=sha256:664e74848c2f5fc27db8505fe24e1ecd3ac669412913bf73d176916f86cc08e2 \ + --hash=sha256:67f7cc397e1d5556a801d1cba74de0d27d827eb242e6c036c0ca273c745414f1 \ + --hash=sha256:696613a0c0e7b2b35356e389ba4195ce20cfe7a2fc2e992c87d974d973eabec7 \ + --hash=sha256:6c5687b59b6ba0f663c4f2f15470f68507d6b45ccd3a19ee6d224f95cbee70d8 \ + --hash=sha256:6c7d7b0b596319e9ce79ef96ce57a14054a2eac629c0588a0539187a218be799 \ + --hash=sha256:70dbc9f83c41f37a4faa62dae5203853847223a891da446f48ef2efcfd5df806 \ + --hash=sha256:71395a43da5584bb70556f380387e15f845c0dcf76373c1afbfa0025737576ac \ + --hash=sha256:71ade6d2e445839350d589ff99bbddfde16f3f32db28da5c894a09f99c8b2a03 \ + --hash=sha256:71e03d2ea1a221d020cfc5b6cc8bbbedef23a06704c70fcf4d0afb42965b22d1 \ + --hash=sha256:7210398a1607ad607dc73196e575b3148b00a333cbb602ddbe1b60ba54554f43 \ + --hash=sha256:7472c39480ccc11dda69a196d1b7c7a6b48eb6594b77f05d945599d3e47f731c \ + --hash=sha256:7646353a5a7c553c04167aca628e2550b538f492765b03ccf34321a560acc43c \ + --hash=sha256:76e0065998159244ff4c097a6c05ff08019c6fbb6e597a85846e73044f0215b3 \ + --hash=sha256:789a6453e5ffd18a7ab6b8c3a0dd8403053829197ec258ecb90c604c21ffcc99 \ + --hash=sha256:789f88dea57841cdebef392b5d6e90444d767117e9ff2e0d9172df5c9de6690c \ + --hash=sha256:7ac2769a7ddd661e07e26c12220adc31b17014c48234b2a5a88993b8d4109e09 \ + --hash=sha256:817953730e4a73a09008b010c4a42396cefe799616a22df2ac21d3927b3080cf \ + --hash=sha256:83d2beac85b757bfc404c5c0612df51c4ef33bbb7be65cf4a96b4da7cbc4ff4f \ + --hash=sha256:86c7c4615634b5c57a79770f5b738354e45ae219a89949aeae84f19169a05283 \ + --hash=sha256:8923fcd49f875e8a0448d7223d1fdf1a9973ae2d1f20d4158753ceb8909facd2 \ + --hash=sha256:8cceae48ae7f94990c2f4e36291e9ec9868735e8b0f19e4489a17b0422338466 \ + --hash=sha256:92f8f63df729061bb14558ed740e7a222f6e2aa6c2e6a5bcfb4e706676562049 \ + --hash=sha256:930ad463d3fb7a340d323bcc496ec32dae469ec9974c29ff569f19806b6b03b4 \ + --hash=sha256:94733cb447c37b0ce6f71b39ca1288d5f966a084023a5617b4cc35a852586859 \ + --hash=sha256:9be1cfe715ecaa060ffd5d599ef410b94ef2697a0eed4cee562f788a2aa2175b \ + --hash=sha256:a0c5210d038418a115e7cf8706bfa611bd14add2f5787274dba45aadca3155e0 \ + --hash=sha256:a2f8716344bbbd10443391be0b1f2003b34949e3c4e6883005c2bc498ab6b64b \ + --hash=sha256:a4bb174bd65f795c39ad6642d9d5bdc43351b306b7d0a654ab0d0abb38be245f \ + --hash=sha256:a70d5effebd81ebd8e15a8aabdc7980832367b2b00c36543bac378977004cc80 \ + --hash=sha256:a9ee990bf2cfbd81574c5d4f9f6c9d480a14b5b43d2c4f2ca13f4c6f48089394 \ + --hash=sha256:ab2822bcdcefb54d1e8ca29f5e3ec3afe160fb794bb3a3a73b09d11ed2671104 \ + --hash=sha256:ad92d255a165cde5f9882ff69699eaf5495e0a1f32e97566bbbb80f11e46781f \ + --hash=sha256:aead3b74b68396bf0feab5d57e050b730ded7df55853c1e048a87e00aaf033c6 \ + --hash=sha256:aeb2e123cbf978a905e9f55155c1b8316fbbfa7cf64d04521fe174bab302d100 \ + --hash=sha256:aec3c2d741a8e3ee993de7f95f37afb51e31b169c95dbd29da4cf6688f340914 \ + --hash=sha256:b0cdb655fbd1562c2dfc9986f9103a106f7058cd0915afc6e656d4b1d73f3387 \ + --hash=sha256:b26f39a5be3cd91a2035818be3d0ffdf5282c3e1f5f53d3b80b7024021acee52 \ + --hash=sha256:b2f2d13239487cecaae98c9f4d5904ad0737643cdf8873c88ec6a78b12214f20 \ + --hash=sha256:b300a30304c4c6aeab03ed06809819d9eceacdd58441d3c7dffe41edfd006091 \ + --hash=sha256:b42f64825f5d750228f08318a9bf4d723b7ecb4c472aedcb3013c3ec2dc1f949 \ + --hash=sha256:b67d38e8bb0ade7e6e84575e4bb153fab35b122458077273aeb08fdf12be9d2c \ + --hash=sha256:b867f9fd183d76977f810c626d539265183c25b2284762893a31e8d6cacc81c0 \ + --hash=sha256:b99bed995ed7c41515c11fe2beb86f8a6cc298e5537cae2f736de2f982f74ef0 \ + --hash=sha256:bd46bcc28ed85d3cf71581b42d90b4ebd1f2062f81879519fed209f8a4d1cfc6 \ + --hash=sha256:bee28aab5a580eec673f7b68dfa8dece3dd37bd207ef8d22fafe23a8e9837296 \ + --hash=sha256:c04e9d920e2674f54651fd260a15c5d2843526c73d5242ee4311325ddc9573b3 \ + --hash=sha256:c4c5a1770ecfe8bdb6c6f09f8d81ea4de04f7efb012fac1efa4ad315dccc1fd1 \ + --hash=sha256:c733087ecf7dde06b1d071679bfd5f3ba03266ba77814c1c7253bc48c9adedd9 \ + --hash=sha256:c813ada7a651193acda85b6bc1cf12abd4b37cd25c7c6a75d7183512cc374969 \ + --hash=sha256:c81d98e27d9d9261909dcccab1ba5d6ea1c98b0bb9470e368604614fd1d36ddd \ + --hash=sha256:c81e35862aced259177a537ce1a6264c3a97bae1ced184ca9dc10ea754563688 \ + --hash=sha256:c856e439a9d54d514e81a7131229ba151cdf81df7c408e0f6c89340e5003dfe2 \ + --hash=sha256:c9e70012926e97069cdd09221861aff412aa94e4cac2e019055cdb79485b150c \ + --hash=sha256:ca83a32b2b41653d89f09a3d22b770bca96da72273bef8f7a68136bdd1e8b823 \ + --hash=sha256:cbf0777d54672990b10cb8ca9b0523499e182d7e2ea39f86b138519a902aa601 \ + --hash=sha256:d49127f137f871cde3c5650af33bbbdcee02679dae76e020902fa106f522b8ae \ + --hash=sha256:d690121e80ab16e623415351ab24713534d4e500b3cadff216409cb6826063d4 \ + --hash=sha256:d6a166978f22f10cb91413f70519861605219346decf65bc79008f9257c94afe \ + --hash=sha256:d6e3485a5e5c01fb5339c1ee3d4b9e21e2135a2a9d44caafb71acd994487ee7c \ + --hash=sha256:d7d406709ae924cf0b08b8282ca2f0e1c90526911d93bc040557cbc83b74c55f \ + --hash=sha256:d8201c9f9035520cdcc7dcad383b039ec8123969b87e01cf27c3dd883c02313b \ + --hash=sha256:da779ae50b46f3f7d1e1b5f362fdc0ad376a49fd55f6f3122dc2b6149936cdf7 \ + --hash=sha256:dcf2c7eaaa4cafe8e49b017e5bb49e76e084ab6f0cd712ee4019c812b5cf68b8 \ + --hash=sha256:dd8bed00731a21e3106e5dd6bbffb6c775a2c0bbc7c0c9face534d830057bdc9 \ + --hash=sha256:df1c174796cd63eb6b139f7daf783affe1f3ed659605850e843b24764c21bfc5 \ + --hash=sha256:dfcf6e51000b34f7026322cd9ea9bc340d745637e27e0bbc22f504b4f6b967ee \ + --hash=sha256:e498900bfbf7ddddd862f810440c09e9b81674739e8a11d619b40906dccd4a9a \ + --hash=sha256:e512c75ec5cf4475c1aa50ad975ecdf253f4963b06e152e1a11edd6de4010753 \ + --hash=sha256:e5bd109d095aaae989a0463d5ef0fcc43e9ddd5de125e656d3c119a79e466380 \ + --hash=sha256:e6b4b08d09054310a4dc389fa61ee22a7a5a292cb23ab8a985136ca8828bf924 \ + --hash=sha256:e6da138a1ecc1341c8b788a5ee822670b03788e1f5decf7af9d461de6e508b8e \ + --hash=sha256:ea2f61b0bc12f3592288503961b764579dea0658852d96a0b3d823614d808821 \ + --hash=sha256:ed9c0b6f236648dbf30d57e9f57e1f74878caafa04833a19f529b5ba5164b7a8 \ + --hash=sha256:edbca8789fd10f0de534b660a6b4c71f1fd18039679c918dad392ab044bb37e4 \ + --hash=sha256:edc17549ab592e33cfcaf285f047bb75fcd98f608aa9309af1187fd736ce9641 \ + --hash=sha256:ef8a9ddb2e2a4eae4abb72dc0710eca6c2726779b0664bd10c3ce9fcfaed8c4e \ + --hash=sha256:f08ee8248aad2d054e5bc4c73554d4505464698f6d754f16198dbc5764e9821d \ + --hash=sha256:f1d62fb172db35c6478c9323da34ff37d1f3d37d7d44337a9d94ba8c583e5730 \ + --hash=sha256:f8fd23653a252cefabea11cd041efd7b80bdf403e43a36dea53f9cb0aaa21f0f \ + --hash=sha256:faa4e96385c820ce9d7f7a623486e23d4134ae3c9ba2071e44e0ab5a5a5002c4 \ + --hash=sha256:fb2bb5b557080da398b866408117d98fe9731a12334afd0cdff221b458cdc5dc \ + --hash=sha256:fc04affef30be98c1f36f0c422a4061ef618fdc1dde79f5ad5458dd496fecffa \ + --hash=sha256:ffb616d8d55965839670301dc3bae3abacff560a7e137a89056058a8a9da4d96 + # via + # idf-component-manager + # pydantic +pydantic-settings==2.16.2 \ + --hash=sha256:3dc08ffabed2bbd5dc2dd55ead7b78b4e4c5cd776aecd04310db5f1224caaf46 \ + --hash=sha256:8d523e5c129b097686bde019bd6109bff53f4c8c3f63f1ddf9933ffa42e5ab4f + # via idf-component-manager +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \ + --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c + # via rich +pyparsing==3.3.3 \ + --hash=sha256:928ae7e20211f3b6f3915a72f06a0cfd29ab9d24279dd6346b6b1a7146397d36 \ + --hash=sha256:ece8c00a69cf01b45d0b1dedabb469c90d8caf996d4fda40f147627a122849a4 + # via idf-component-manager +python-dotenv==1.2.4 \ + --hash=sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc \ + --hash=sha256:f0d53e69935a851c0dcc78f3ab7aaccd8cabef0b92382b576b824212902873c0 + # via pydantic-settings +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ + --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed + # via + # idf-component-manager + # requests-file + # requests-toolbelt +requests-file==3.0.1 \ + --hash=sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2 \ + --hash=sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576 + # via idf-component-manager +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 + # via idf-component-manager +rich==15.0.0 \ + --hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \ + --hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36 + # via + # esp-pylib + # idf-component-manager + # rich-click +rich-click==1.9.9 \ + --hash=sha256:324cba7513cd4187ee92b2eef21f071714e45be062458c8b157bd7e0c81103e3 \ + --hash=sha256:365e7a9d0adb42e41ea832a0a12e02c44c079a26536dee688125eb9814f97274 + # via idf-component-manager +ruamel-yaml==0.19.1 \ + --hash=sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93 \ + --hash=sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993 + # via idf-component-manager +truststore==0.10.5 \ + --hash=sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd \ + --hash=sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c + # via idf-component-manager +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # idf-component-manager + # pydantic + # pydantic-core + # pydantic-settings + # typing-inspection +typing-inspection==0.4.5 \ + --hash=sha256:7f16c411e7930dc79596d42ab44672a3fe5f60eb34b781a1647e7872363120c7 \ + --hash=sha256:bc210535442b39e1bf2805e0c7a17dfd6a4ace760c84c7f7d16245d23a6b1c39 + # via + # pydantic + # pydantic-settings +urllib3==2.8.0 \ + --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \ + --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63 + # via requests diff --git a/.ci/scripts/test_espressif_idf_nightly.sh b/.ci/scripts/test_espressif_idf_nightly.sh index d0b963921cb..36bb3fe95d9 100755 --- a/.ci/scripts/test_espressif_idf_nightly.sh +++ b/.ci/scripts/test_espressif_idf_nightly.sh @@ -5,13 +5,15 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. -# The ExecuTorch ESP-IDF component's nightly matrix. +# The ExecuTorch ESP-IDF component's nightly matrix and release checks. # -# test_espressif_idf_nightly.sh prepare +# test_espressif_idf_nightly.sh prepare [stamp options] # In the ExecuTorch environment: exports the test models (smoke, the # esp_nn:: mul, an ESP-NN delegate program and the Cadence-quantized wake -# word), generates and packs the component into a file:// component mirror, -# and makes each model's kernel bundle, all under . all_ops is the +# word), generates the component with build_idf_component.sh's stamp +# options (default --allow-dev-stamp), packs it into /dist and a +# file:// component mirror, and makes each model's kernel bundle, all under +# . all_ops is the # smoke model with a bundle that registers every op of every kernel # library. # test_espressif_idf_nightly.sh build @@ -22,12 +24,19 @@ # own Python environment, checks that each configuration's settings took # effect, and runs each build on its emulator unless the rules disable its # test. Reports every failure, then fails. +# test_espressif_idf_nightly.sh registry +# Needs only from prepare: installs ESP-IDF 6.1, waits up to 15 +# minutes for the given component registry to list 's version of +# pytorch/executorch, builds the smoke consumer for ESP32-S3 with it, fails +# if the registry serves files other than 's archive, and runs the +# build on the emulator. set -euo pipefail et_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) -mode=${1:?usage: $0 prepare | build } -dir=${2:?usage: $0 prepare | build } +usage="usage: $0 prepare [stamp options] | build | registry " +mode=${1:?${usage}} +dir=${2:?${usage}} mkdir -p "${dir}" dir=$(cd "${dir}" && pwd) cd "${et_root}" @@ -45,6 +54,12 @@ logged() { } prepare() { + local stamp=("${@:3}") + if (( ${#stamp[@]} == 0 )); then + stamp=(--allow-dev-stamp) + fi + # Checks the stamp against git before the model exports. + bash examples/espressif/build_idf_component.sh --version "${stamp[@]}" > /dev/null export ESPRESSIF_TOOLS_DIR="${ESPRESSIF_TOOLS_DIR:-${RUNNER_TEMP:-/tmp}/executorch-espressif}" local logs="${dir}/logs" models="${dir}/models" mkdir -p "${logs}" "${models}" @@ -75,11 +90,13 @@ print(entry["atol"], entry["rtol"]) rm -r "${models}"/*.bpte "${dir}/espnn" local component_dir="${dir}/component/executorch" - logged "${logs}/generate.log" bash examples/espressif/build_idf_component.sh \ - --allow-dev-stamp --out "${component_dir}" + logged "${logs}/generate.log" bash examples/espressif/build_idf_component.sh "${stamp[@]}" \ + --out "${component_dir}" local version version=$(sed -n 's/^version: "\(.*\)"$/\1/p' "${component_dir}/idf_component.yml") - (cd "${component_dir}" && compote component pack --name executorch --dest-dir "${dir}/dist") + # The registry links each version to its commit and to the generator. + (cd "${component_dir}" && compote component pack --name executorch --dest-dir "${dir}/dist" \ + --commit-sha "$(git -C "${et_root}" rev-parse HEAD)" --repository-path examples/espressif) python .ci/scripts/esp_idf_mirror.py "${dir}/dist/executorch_${version}.tgz" "${dir}/mirror" \ --namespace pytorch --name executorch local model @@ -112,38 +129,16 @@ build() { mkdir -p "${work}" work=$(cd "${work}" && pwd) - # ESP-IDF's QEMU needs these libraries, and setup.sh fails its install check - # without them. - if ! python -c ' -import ctypes -ctypes.CDLL("libSDL2-2.0.so.0") -ctypes.CDLL("libslirp.so.0")' 2> /dev/null; then - sudo apt-get update - sudo apt-get install -y --no-install-recommends libsdl2-2.0-0 libslirp0 - fi - logged "${work}/setup.log" bash examples/espressif/setup.sh --minimal \ - --idf-version="${idf_version}" --targets="$(IFS=,; echo "${targets[*]}")" - # shellcheck source=/dev/null - source "${ESPRESSIF_TOOLS_DIR}/setup_path.sh" - - # A fresh ESP-IDF Python environment, which cannot import executorch, and + install_idf "${idf_version}" "$(IFS=,; echo "${targets[*]}")" "${work}" # idf-build-apps in a venv of its own, so it changes nothing ESP-IDF runs. - export IDF_PYTHON_ENV_PATH="${work}/idf_python_env" - mkdir -p "${IDF_PYTHON_ENV_PATH}" - logged "${work}/idf_python_env.log" python "${IDF_PATH}/tools/idf_tools.py" install-python-env "${IDF_PYTHON_ENV_PATH}/bin/python" -m venv "${work}/idf_build_apps" "${work}/idf_build_apps/bin/python" -m pip install --quiet "idf-build-apps>=3.1,<4" export IDF_COMPONENT_STORAGE_URL="file://${dir}/mirror;default" local version version=$(cat "${dir}/version") - # Built from a copy, so the component manager's state and the consumer's - # generated manifest stay out of the source tree. local sources="${work}/sources" - rm -rf "${sources}" - mkdir -p "${sources}/examples/espressif/idf_component_smoke" - cp -r examples/espressif/idf_component_consumer "${sources}/examples/espressif/" - cp -r examples/espressif/idf_component_smoke/main "${sources}/examples/espressif/idf_component_smoke/" + copy_consumer "${sources}" local app=examples/espressif/idf_component_consumer (cd "${sources}" && "${work}/idf_build_apps/bin/idf-build-apps" find -p "${app}" --target all \ --default-build-targets "${targets[@]}" --config-rules 'sdkconfig.ci.*=' \ @@ -200,6 +195,12 @@ PY failures+=("${name} (run)") fi done 3< "${work}/shard.txt" + # Exercises the check that registry mode runs after an upload. + if [[ -d "${project}/managed_components/pytorch__executorch" ]] \ + && ! served_matches "${dir}/dist/executorch_${version}.tgz" \ + "${project}/managed_components/pytorch__executorch" "the mirror"; then + failures+=("served files") + fi if (( ${#failures[@]} )); then printf 'FAIL: %s\n' "${failures[@]}" >&2 @@ -208,6 +209,133 @@ PY echo "PASS: shard ${shard}/${shards} of the ESP-IDF ${idf_version} matrix built and ran." } +# Installs ESP-IDF for the targets and makes a fresh ESP-IDF Python +# environment, which cannot import executorch: ESP-IDF version, comma-separated +# targets, work dir. +install_idf() { + local idf_version=$1 targets_csv=$2 work=$3 + # ESP-IDF's QEMU needs these libraries, and setup.sh fails its install check + # without them. + if [[ ",${targets_csv}," == *,esp32,* ]] && ! python -c ' +import ctypes +ctypes.CDLL("libSDL2-2.0.so.0") +ctypes.CDLL("libslirp.so.0")' 2> /dev/null; then + sudo apt-get update + sudo apt-get install -y --no-install-recommends libsdl2-2.0-0 libslirp0 + fi + logged "${work}/setup.log" bash examples/espressif/setup.sh --minimal \ + --idf-version="${idf_version}" --targets="${targets_csv}" + # shellcheck source=/dev/null + source "${ESPRESSIF_TOOLS_DIR}/setup_path.sh" + export IDF_PYTHON_ENV_PATH="${work}/idf_python_env" + mkdir -p "${IDF_PYTHON_ENV_PATH}" + logged "${work}/idf_python_env.log" python "${IDF_PATH}/tools/idf_tools.py" install-python-env +} + +# Copies the consumer project to a directory, so the component manager's state +# and the consumer's generated manifest stay out of the source tree. +copy_consumer() { + local sources=$1 + rm -rf "${sources}" + mkdir -p "${sources}/examples/espressif/idf_component_smoke" + cp -r examples/espressif/idf_component_consumer "${sources}/examples/espressif/" + cp -r examples/espressif/idf_component_smoke/main "${sources}/examples/espressif/idf_component_smoke/" +} + +registry() { + local registry_url=${3:?${usage}} + local work="${ESPRESSIF_BUILD_DIR:-${et_root}/cmake-out-esp-idf-registry}" + export ESPRESSIF_TOOLS_DIR="${ESPRESSIF_TOOLS_DIR:-${RUNNER_TEMP:-/tmp}/executorch-espressif}" + mkdir -p "${work}" + work=$(cd "${work}" && pwd) + install_idf v6.1 esp32s3 "${work}" + local version + version=$(cat "${dir}/version") + local storage + if ! storage=$(curl -sSf --retry 3 "${registry_url%/}/api/" | python -c ' +import json, sys +print(json.load(sys.stdin)["components_base_url"].rstrip("/"))'); then + echo "FAIL: ${registry_url}/api/ did not name the registry's storage" >&2 + exit 1 + fi + # The registry's storage is behind a CDN that caches for five minutes. + local tries=0 + until curl -sSf "${storage}/components/pytorch/executorch.json" 2> /dev/null | python -c ' +import json, sys +try: + versions = [v["version"] for v in json.load(sys.stdin)["versions"]] +except ValueError: + versions = [] +sys.exit(sys.argv[1] not in versions)' "${version}"; do + if (( ++tries == 30 )); then + echo "FAIL: ${registry_url} did not list pytorch/executorch ${version}" >&2 + exit 1 + fi + echo "Waiting for ${registry_url} to list pytorch/executorch ${version}..." + sleep 30 + done + copy_consumer "${work}/sources" + local project="${work}/sources/examples/espressif/idf_component_consumer" + local build="${work}/builds/esp32s3-default" log="${work}/emulator.log" + local idf_args=(-C "${project}" -B "${build}" -DSDKCONFIG="${build}/sdkconfig" + "-DSDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.ci.default" + -DET_COMPONENT_VERSION="${version}" -DET_CONSUMER_MODELS_DIR="${dir}/models" + -DET_COMPONENT_REGISTRY_URL="${registry_url}") + IDF_TARGET=esp32s3 logged "${work}/build.log" "${IDF_PYTHON_ENV_PATH}/bin/python" \ + "${IDF_PATH}/tools/idf.py" "${idf_args[@]}" build + # --allow-existing uploads would not replace an archive already there. + served_matches "${dir}/dist/executorch_${version}.tgz" \ + "${project}/managed_components/pytorch__executorch" "${registry_url}" + local model + model=$(sed -n 's/^CONFIG_ET_CONSUMER_MODEL="\(.*\)"$/\1/p' "${build}/sdkconfig") + run_firmware "${build}" esp32s3 "${log}" "${idf_args[@]}" + if ! grep -qF "${model}: PASS" "${log}" || ! grep -qF "ET_COMPONENT_SMOKE: PASS" "${log}"; then + tail -n 30 "${log}" >&2 + echo "FAIL: the consumer from ${registry_url} did not pass on the emulator" >&2 + exit 1 + fi + echo "PASS: pytorch/executorch ${version} from ${registry_url} built and ran." +} + +# Checks that a project got the files of the tested archive from a registry or +# mirror: archive, the project's managed_components/pytorch__executorch, source. +# The registry rewrites the examples' manifests, dropping override_path, so +# those are the only files that may differ. +served_matches() { + python - "$@" <<'PY' +import hashlib +import sys +import tarfile +from pathlib import Path + + +def compared(path): + return path not in (".component_hash", "CHECKSUMS.json") and not ( + path.startswith("examples/") and path.endswith("/idf_component.yml") + ) + + +with tarfile.open(sys.argv[1]) as archive: + tested = { + Path(m.name).as_posix(): hashlib.sha256(archive.extractfile(m).read()).hexdigest() + for m in archive + if m.isfile() + } +served_dir = Path(sys.argv[2]) +served = { + p.relative_to(served_dir).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest() + for p in served_dir.rglob("*") + if p.is_file() +} +differ = sorted( + p for p in tested.keys() | served.keys() if compared(p) and tested.get(p) != served.get(p) +) +if differ: + sys.exit(f"FAIL: {sys.argv[3]} served files unlike the tested archive's: {differ[:10]}") +print(f"OK: {sys.argv[3]} served the tested archive's files") +PY +} + # Checks that every setting of a sdkconfig.ci.* file reached the build's # sdkconfig, since ESP-IDF only warns about a symbol it does not know: # defaults file, sdkconfig. @@ -256,7 +384,8 @@ run_firmware() { } case "${mode}" in - prepare) prepare ;; + prepare) prepare "$@" ;; build) build "$@" ;; - *) echo "unknown mode ${mode}; use prepare or build" >&2; exit 1 ;; + registry) registry "$@" ;; + *) echo "unknown mode ${mode}; ${usage}" >&2; exit 1 ;; esac diff --git a/.ci/scripts/upload_espressif_idf_component.sh b/.ci/scripts/upload_espressif_idf_component.sh new file mode 100755 index 00000000000..2f1aecd19ba --- /dev/null +++ b/.ci/scripts/upload_espressif_idf_component.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +# Uploads the component archive that test_espressif_idf_nightly.sh prepare made +# to an ESP Component Registry as pytorch/executorch, and succeeds if the +# registry already has that version. +# +# upload_espressif_idf_component.sh [--dry-run] +# +# Without --dry-run the job needs `id-token: write`: like +# espressif/upload-components-ci-action, this authenticates with a GitHub OIDC +# token for the registry, which accepts it when the component's trusted +# uploader names this repository and the job's environment. --dry-run +# validates the archive and needs no token. + +set -euo pipefail + +usage="usage: $0 [--dry-run]" +if (( $# < 2 || $# > 3 )) || [[ $# == 3 && "$3" != --dry-run ]]; then + echo "${usage}" >&2 + exit 1 +fi +dir=$1 registry=$2 dry_run=("${@:3}") +version=$(cat "${dir}/version") + +python -m pip install --progress-bar off --require-hashes \ + -r "$(dirname "${BASH_SOURCE[0]}")/espressif_idf_upload_requirements.txt" +export IDF_COMPONENT_REGISTRY_URL="${registry}" +if (( ${#dry_run[@]} == 0 )); then + token=$(curl -sSfG --data-urlencode "audience=${registry}" \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN:?needs id-token: write}" \ + "${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq -er .value) + echo "::add-mask::${token}" + export IDF_COMPONENT_API_TOKEN="${token}" +fi +compote component upload --namespace pytorch --name executorch \ + --archive "${dir}/dist/executorch_${version}.tgz" --allow-existing "${dry_run[@]}" diff --git a/.github/workflows/nightly-espressif-idf.yml b/.github/workflows/nightly-espressif-idf.yml index be9142e1bdb..796e57d4798 100644 --- a/.github/workflows/nightly-espressif-idf.yml +++ b/.github/workflows/nightly-espressif-idf.yml @@ -13,6 +13,8 @@ on: - .ci/scripts/test_espressif_idf_nightly.sh - .ci/scripts/test_espressif_idf_configure.py - .ci/scripts/esp_idf_mirror.py + - .ci/scripts/upload_espressif_idf_component.sh + - .ci/scripts/espressif_idf_upload_requirements.txt - examples/espressif/build_idf_component.sh - examples/espressif/setup.sh - examples/espressif/export_smoke_model.py @@ -54,6 +56,10 @@ jobs: source .ci/scripts/utils.sh install_executorch --use-pt-pinned-commit --minimal bash .ci/scripts/test_espressif_idf_nightly.sh prepare "${RUNNER_ARTIFACT_DIR}" + # The release stamp's checks, which publishing otherwise first runs at a + # real tag, at a throwaway local one. + git tag -f "v$(cat version.txt)" + bash examples/espressif/build_idf_component.sh --version --version-suffix "" registry-dry-run: name: Registry validation (upload --dry-run) @@ -72,10 +78,8 @@ jobs: set -euo pipefail CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]') conda activate "${CONDA_ENV}" - pip install --progress-bar off "idf-component-manager~=3.1" - version=$(cat "${RUNNER_ARTIFACT_DIR}/version") - compote component upload --namespace pytorch --name executorch \ - --archive "${RUNNER_ARTIFACT_DIR}/dist/executorch_${version}.tgz" --dry-run --allow-existing + bash .ci/scripts/upload_espressif_idf_component.sh "${RUNNER_ARTIFACT_DIR}" \ + https://components.espressif.com --dry-run build: name: ESP-IDF ${{ matrix.idf-version }} (${{ matrix.shard }}/4) diff --git a/.github/workflows/publish-esp-idf.yml b/.github/workflows/publish-esp-idf.yml new file mode 100644 index 00000000000..5ddc7518eb6 --- /dev/null +++ b/.github/workflows/publish-esp-idf.yml @@ -0,0 +1,292 @@ +name: Publish ESP-IDF component + +# Publishes pytorch/executorch to the ESP Component Registry: RC tags to the +# staging registry, release tags to staging and then production. The archive +# is built and tested on the ESP-IDF releases the component supports before +# any upload, and each upload is followed by a consumer build that resolves +# the version from that registry. See the release runbook in +# examples/espressif/README.md. + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version_suffix: + description: 'Version suffix: empty at a release tag, -rc.N at an RC tag, ~N for a packaging respin on release/X.Y' + type: string + default: '' + registry: + description: 'Registry to publish to; production uploads to staging first' + type: choice + options: + - staging + - production + default: staging + dry_run: + description: 'Validate the archive with the registry without uploading' + type: boolean + default: true + pull_request: + paths: + - .github/workflows/publish-esp-idf.yml + - .ci/scripts/upload_espressif_idf_component.sh + - .ci/scripts/espressif_idf_upload_requirements.txt + +# Runs for one tag or branch go one at a time; runs for different ones, such as +# an RC awaiting approval and its release, do not hold each other up. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + plan: + name: Plan the release + runs-on: ubuntu-22.04 + outputs: + package: ${{ steps.plan.outputs.package }} + rehearsal: ${{ steps.plan.outputs.rehearsal }} + suffix: ${{ steps.plan.outputs.suffix }} + version: ${{ steps.plan.outputs.version }} + staging: ${{ steps.plan.outputs.staging }} + production: ${{ steps.plan.outputs.production }} + steps: + - id: plan + shell: bash + env: + EVENT: ${{ github.event_name }} + REF_NAME: ${{ github.ref_name }} + VERSION_SUFFIX: ${{ inputs.version_suffix }} + REGISTRY: ${{ inputs.registry }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + set -euo pipefail + package=true rehearsal=false suffix='' version='' staging=false production=false + case "${EVENT}" in + push) + if [[ ! "${REF_NAME}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)(-rc([1-9][0-9]*))?$ ]]; then + echo "::notice::${REF_NAME} is not a release or RC tag; nothing to publish." + package=false + elif [[ -n "${BASH_REMATCH[3]}" ]]; then + suffix="-rc.${BASH_REMATCH[3]}" version="${BASH_REMATCH[1]}-rc.${BASH_REMATCH[3]}" + staging=true + else + version="${BASH_REMATCH[1]}" staging=true production=true + fi + ;; + workflow_dispatch) + if [[ ! "${VERSION_SUFFIX}" =~ ^(-rc\.[1-9][0-9]*|~[1-9][0-9]*)?$ ]]; then + echo "::error::version_suffix must be empty, -rc.N or ~N, not '${VERSION_SUFFIX}'." + exit 1 + fi + if [[ "${VERSION_SUFFIX}" == -rc.* && "${REGISTRY}" == production ]]; then + echo "::error::release candidates go to the staging registry only." + exit 1 + fi + suffix="${VERSION_SUFFIX}" + if [[ "${DRY_RUN}" != true ]]; then + staging=true + [[ "${REGISTRY}" == production ]] && production=true + fi + ;; + *) + rehearsal=true + ;; + esac + { + echo "package=${package}" + echo "rehearsal=${rehearsal}" + echo "suffix=${suffix}" + echo "version=${version}" + echo "staging=${staging}" + echo "production=${production}" + } | tee -a "${GITHUB_OUTPUT}" + + docker-image: + name: Resolve CI docker image + needs: plan + if: needs.plan.outputs.package == 'true' + uses: ./.github/workflows/_docker-image.yml + + package: + name: Generate and pack the component + needs: [plan, docker-image] + uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main + permissions: + id-token: write + contents: read + with: + runner: mt-l-x86iavx512-8-64 + docker-image: ${{ needs.docker-image.outputs.docker-registry }}/ci-image:executorch-ubuntu-22.04-clang12-${{ needs.docker-image.outputs.ci-docker-hash }} + submodules: recursive + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + # The version stamp checks tags and, for a respin, release/X.Y's history. + fetch-depth: 0 + single-branch: false + timeout: 60 + upload-artifact: esp-idf-release + script: | + set -euo pipefail + CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]') + conda activate "${CONDA_ENV}" + source .ci/scripts/utils.sh + install_executorch --use-pt-pinned-commit --minimal + if [[ "${{ needs.plan.outputs.rehearsal }}" == true ]]; then + # A pull request rehearses the release stamp at a throwaway local tag. + git tag -f "v$(cat version.txt)" + fi + # A tag names its version; a dispatch appends its suffix to version.txt. + expected="${{ needs.plan.outputs.version }}" + expected="${expected:-$(cat version.txt)${{ needs.plan.outputs.suffix }}}" + bash .ci/scripts/test_espressif_idf_nightly.sh prepare "${RUNNER_ARTIFACT_DIR}" \ + --version-suffix "${{ needs.plan.outputs.suffix }}" + version=$(cat "${RUNNER_ARTIFACT_DIR}/version") + if [[ "${version}" != "${expected}" ]]; then + echo "FAIL: generated version ${version}, expected ${expected}" >&2 + exit 1 + fi + echo "Packed pytorch/executorch ${version}" + # Nothing downstream reads the unpacked component. + rm -r "${RUNNER_ARTIFACT_DIR}/component" + + dry-run: + name: Registry validation on ${{ matrix.registry }} (upload --dry-run) + needs: [docker-image, package] + strategy: + fail-fast: false + matrix: + registry: [https://components-staging.espressif.com, https://components.espressif.com] + uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main + permissions: + id-token: write + contents: read + with: + runner: mt-l-x86iavx512-8-64 + docker-image: ${{ needs.docker-image.outputs.docker-registry }}/ci-image:executorch-ubuntu-22.04-clang12-${{ needs.docker-image.outputs.ci-docker-hash }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + timeout: 20 + download-artifact: esp-idf-release + script: | + set -euo pipefail + CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]') + conda activate "${CONDA_ENV}" + bash .ci/scripts/upload_espressif_idf_component.sh "${RUNNER_ARTIFACT_DIR}" \ + "${{ matrix.registry }}" --dry-run + + build: + name: ESP-IDF ${{ matrix.idf-version }} (${{ matrix.shard }}/4) + needs: [docker-image, package] + strategy: + fail-fast: false + matrix: + idf-version: [v6.0.1, v6.1] + shard: [1, 2, 3, 4] + uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main + permissions: + id-token: write + contents: read + with: + runner: mt-l-x86iavx512-8-64 + docker-image: ${{ needs.docker-image.outputs.docker-registry }}/ci-image:executorch-ubuntu-22.04-clang12-${{ needs.docker-image.outputs.ci-docker-hash }} + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} + timeout: 120 + download-artifact: esp-idf-release + script: | + set -euo pipefail + CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]') + conda activate "${CONDA_ENV}" + ESPRESSIF_IDF_VERSION="${{ matrix.idf-version }}" \ + bash .ci/scripts/test_espressif_idf_nightly.sh build "${RUNNER_ARTIFACT_DIR}" \ + "${{ matrix.shard }}" 4 + + upload-staging: + name: Upload to the staging registry + needs: [plan, dry-run, build] + if: needs.plan.outputs.staging == 'true' + runs-on: ubuntu-22.04 + environment: esp-component-registry + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: esp-idf-release + path: ${{ runner.temp }}/esp-idf-release + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.11' + - run: | + bash .ci/scripts/upload_espressif_idf_component.sh "${RUNNER_TEMP}/esp-idf-release" \ + https://components-staging.espressif.com + + verify-staging: + name: Build the consumer from the staging registry + needs: [docker-image, upload-staging] + uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main + permissions: + id-token: write + contents: read + with: + runner: mt-l-x86iavx512-8-64 + docker-image: ${{ needs.docker-image.outputs.docker-registry }}/ci-image:executorch-ubuntu-22.04-clang12-${{ needs.docker-image.outputs.ci-docker-hash }} + ref: ${{ github.sha }} + timeout: 60 + download-artifact: esp-idf-release + script: | + set -euo pipefail + CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]') + conda activate "${CONDA_ENV}" + bash .ci/scripts/test_espressif_idf_nightly.sh registry "${RUNNER_ARTIFACT_DIR}" \ + https://components-staging.espressif.com + + upload-production: + name: Upload to the production registry + needs: [plan, verify-staging] + if: needs.plan.outputs.production == 'true' + runs-on: ubuntu-22.04 + environment: esp-component-registry + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: esp-idf-release + path: ${{ runner.temp }}/esp-idf-release + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.11' + - run: | + bash .ci/scripts/upload_espressif_idf_component.sh "${RUNNER_TEMP}/esp-idf-release" \ + https://components.espressif.com + + verify-production: + name: Build the consumer from the production registry + needs: [docker-image, upload-production] + uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main + permissions: + id-token: write + contents: read + with: + runner: mt-l-x86iavx512-8-64 + docker-image: ${{ needs.docker-image.outputs.docker-registry }}/ci-image:executorch-ubuntu-22.04-clang12-${{ needs.docker-image.outputs.ci-docker-hash }} + ref: ${{ github.sha }} + timeout: 60 + download-artifact: esp-idf-release + script: | + set -euo pipefail + CONDA_ENV=$(conda env list --json | jq -r '.envs | .[-1]') + conda activate "${CONDA_ENV}" + bash .ci/scripts/test_espressif_idf_nightly.sh registry "${RUNNER_ARTIFACT_DIR}" \ + https://components.espressif.com diff --git a/examples/espressif/README.md b/examples/espressif/README.md index 0bd2c1a86ad..8d31fdb39ea 100644 --- a/examples/espressif/README.md +++ b/examples/espressif/README.md @@ -58,6 +58,51 @@ every op of every kernel library on ESP32-S3 and ESP32-C6. macOS and Windows jobs configure `idf_component_smoke` with the same component (`.github/workflows/nightly-espressif-idf.yml`). +### Publishing the component + +`.github/workflows/publish-esp-idf.yml` publishes the component to the ESP +Component Registry as `pytorch/executorch`. It packs the component once, +builds that archive in every `idf_component_consumer` configuration on +ESP-IDF 6.0.1 and 6.1, runs each build except the build-only one for +ESP32-P4 before rev3 on its emulator, and validates the archive with both +registries before uploading it. After each upload, a consumer build +resolves the new version from that registry, checks that the registry +serves the tested files, and runs it on the emulator. + +- An RC tag `vX.Y.Z-rcN` publishes `X.Y.Z-rc.N` to the staging registry, + https://components-staging.espressif.com. +- A release tag `vX.Y.Z` publishes `X.Y.Z` to staging, then to + https://components.espressif.com. +- A fix to the component after a release is a respin, `X.Y.Z~N`. Commit it + to `release/X.Y`, changing nothing since the tag outside + `examples/espressif/`, `.ci/`, `.github/` and `docs/`, and run the + workflow on `release/X.Y` with `version_suffix` `~N`. A release whose + publishing failed in CI is published as a respin once the CI is fixed. +- A manual run only validates unless `dry_run` is cleared. With `registry` + `production`, it uploads to staging first. Uploads skip versions the + registry already has, so **Re-run failed jobs** retries with the same + archive; **Re-run all jobs** packs a new one, which passes only if its + files match those the registry already serves. + +Each upload waits for a reviewer of the `esp-component-registry` +environment, the only place the registries accept uploads from: on each +registry, `pytorch/executorch`'s trusted uploader names this repository and +that environment. Before approving the staging upload, dispatch the nightly +matrix (`nightly-espressif-idf.yml`) on the tag and wait for it to pass; it +also covers ESP-IDF master, the all-ops build and the macOS and Windows +configure jobs. Approve the production upload once the release owner has +built a project against the version on staging. + +The one-time setup goes in this order, because GitHub creates a missing +environment, unprotected, as soon as a workflow names it: + +1. Create the `esp-component-registry` environment with required + reviewers, self-review prevented, no admin bypass, and deployments + limited to `v*` tags and `release/*` branches. +2. Create the `pytorch` namespace on the staging registry. +3. On each registry, make this repository and that environment + `pytorch/executorch`'s trusted uploader. + The executor runner below remains the reference for the prebuilt-library flow and for benchmarking. diff --git a/examples/espressif/idf_component_consumer/CMakeLists.txt b/examples/espressif/idf_component_consumer/CMakeLists.txt index 3aa1fba4d3e..f01705bf932 100644 --- a/examples/espressif/idf_component_consumer/CMakeLists.txt +++ b/examples/espressif/idf_component_consumer/CMakeLists.txt @@ -10,18 +10,26 @@ # project's ET_SMOKE_* variables, as .ci/scripts/test_espressif_idf_consumer.sh # does, or ET_CONSUMER_MODELS_DIR, from which CONFIG_ET_CONSUMER_MODEL (set by # the sdkconfig.ci.* configurations) picks the model, as -# .ci/scripts/test_espressif_idf_nightly.sh does. +# .ci/scripts/test_espressif_idf_nightly.sh does. ET_COMPONENT_REGISTRY_URL +# picks the registry instead of the component manager's default. cmake_minimum_required(VERSION 3.16) if(NOT ET_COMPONENT_VERSION) message(FATAL_ERROR "Set ET_COMPONENT_VERSION") endif() +set(ET_REGISTRY_LINE "") +if(ET_COMPONENT_REGISTRY_URL) + set(ET_REGISTRY_LINE "\n registry_url: ${ET_COMPONENT_REGISTRY_URL}") +endif() # The component manager reads main/idf_component.yml before any component's # CMakeLists.txt runs, and does not expand variables in versions. -file(CONFIGURE OUTPUT "${CMAKE_CURRENT_LIST_DIR}/main/idf_component.yml" - CONTENT - "dependencies:\n pytorch/executorch: \"==${ET_COMPONENT_VERSION}\"\n" +file( + CONFIGURE + OUTPUT + "${CMAKE_CURRENT_LIST_DIR}/main/idf_component.yml" + CONTENT + "dependencies:\n pytorch/executorch:\n version: \"==${ET_COMPONENT_VERSION}\"${ET_REGISTRY_LINE}\n" ) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(et_component_smoke) diff --git a/examples/espressif/idf_component_template/idf_component.yml.in b/examples/espressif/idf_component_template/idf_component.yml.in index 1bc3580f595..1496c2e89b2 100644 --- a/examples/espressif/idf_component_template/idf_component.yml.in +++ b/examples/espressif/idf_component_template/idf_component.yml.in @@ -22,4 +22,8 @@ tags: - inference dependencies: idf: ">=6.0" - espressif/esp-nn: "^1.4" + ## Named, because the staging registry records a dependency without a + ## registry_url as one of its own. + espressif/esp-nn: + version: "^1.4" + registry_url: https://components.espressif.com