From b79916cfa7b6bc0f67116e03cd622f10d1dbefb9 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Fri, 10 Oct 2025 12:26:47 +0200 Subject: [PATCH 1/6] feat(docs): add Python script for building Arduino examples and update CI workflow --- .github/scripts/docs_build_examples.py | 341 ++++++++++++++++++ .github/scripts/sketch_utils.sh | 84 ++++- .github/workflows/docs_build.yml | 22 +- docs/.gitignore | 1 + .../examples/GPIO/FunctionalInterrupt/ci.json | 121 +++++++ .../ESP32/examples/GPIO/GPIOInterrupt/ci.json | 120 ++++++ 6 files changed, 673 insertions(+), 16 deletions(-) create mode 100755 .github/scripts/docs_build_examples.py create mode 100644 docs/.gitignore create mode 100644 libraries/ESP32/examples/GPIO/FunctionalInterrupt/ci.json create mode 100644 libraries/ESP32/examples/GPIO/GPIOInterrupt/ci.json diff --git a/.github/scripts/docs_build_examples.py b/.github/scripts/docs_build_examples.py new file mode 100755 index 00000000000..db82057e1e7 --- /dev/null +++ b/.github/scripts/docs_build_examples.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 +""" +Python port of .github/scripts/docs_build_examples.sh +Preserves behavior and CLI options of the original bash script. + +Usage: docs_build_examples.py -ai -au [options] +""" + +import argparse +from argparse import RawDescriptionHelpFormatter +import json +import os +import shutil +import subprocess +import sys +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent + +# Determine SDKCONFIG_DIR like the shell script +ARDUINO_ESP32_PATH = os.environ.get("ARDUINO_ESP32_PATH") +GITHUB_WORKSPACE = os.environ.get("GITHUB_WORKSPACE") + +if ARDUINO_ESP32_PATH and (Path(ARDUINO_ESP32_PATH) / "tools" / "esp32-arduino-libs").is_dir(): + SDKCONFIG_DIR = Path(ARDUINO_ESP32_PATH) / "tools" / "esp32-arduino-libs" +elif GITHUB_WORKSPACE and (Path(GITHUB_WORKSPACE) / "tools" / "esp32-arduino-libs").is_dir(): + SDKCONFIG_DIR = Path(GITHUB_WORKSPACE) / "tools" / "esp32-arduino-libs" +else: + SDKCONFIG_DIR = Path("tools/esp32-arduino-libs") + +# Wrapper functions to call sketch_utils.sh +SKETCH_UTILS = SCRIPT_DIR / "sketch_utils.sh" + +KEEP_FILES = ["*.merged.bin", "ci.json"] +DOCS_BINARIES_DIR = Path("docs/_static/binaries") +GENERATE_DIAGRAMS = False +LAUNCHPAD_STORAGE_URL = "" + + +def run_cmd(cmd, check=True, capture_output=False, text=True): + try: + return subprocess.run(cmd, check=check, capture_output=capture_output, text=text) + except subprocess.CalledProcessError as e: + # CalledProcessError is raised only when check=True and the command exits non-zero + print(f"ERROR: Command failed: {' '.join(cmd)}") + print(f"Exit code: {e.returncode}") + if hasattr(e, 'stdout') and e.stdout: + print("--- stdout ---") + print(e.stdout) + if hasattr(e, 'stderr') and e.stderr: + print("--- stderr ---") + print(e.stderr) + # Exit the whole script with the same return code to mimic shell behavior + sys.exit(e.returncode) + except FileNotFoundError: + print(f"ERROR: Command not found: {cmd[0]}") + sys.exit(127) + + +def check_requirements(sketch_dir, sdkconfig_path): + # Call sketch_utils.sh check_requirements + cmd = [str(SKETCH_UTILS), "check_requirements", sketch_dir, str(sdkconfig_path)] + try: + res = run_cmd(cmd, check=False, capture_output=True) + return res.returncode == 0 + except Exception: + return False + + +def install_libs(*args): + cmd = [str(SKETCH_UTILS), "install_libs"] + list(args) + return run_cmd(cmd, check=False) + + +def build_sketch(args_list): + cmd = [str(SKETCH_UTILS), "build"] + args_list + return run_cmd(cmd, check=False) + + +def parse_args(argv): + epilog_text = ( + "Example:\n" + " docs_build_examples.py -ai /usr/local/bin -au ~/.arduino15 -d -l https://storage.example.com\n\n" + "This script finds Arduino sketches that include a 'ci.json' with an 'upload-binary'\n" + "section and builds binaries for the listed targets. The built outputs are placed\n" + "under docs/_static/binaries///\n" + ) + + p = argparse.ArgumentParser( + description="Build examples that have ci.json with upload-binary targets", + formatter_class=RawDescriptionHelpFormatter, + epilog=epilog_text, + ) + p.add_argument( + "-ai", + dest="arduino_cli_path", + help=( + "Path to Arduino CLI installation (directory containing the 'arduino-cli' binary)" + ), + ) + p.add_argument( + "-au", + dest="user_path", + help="Arduino user path (for example: ~/.arduino15)", + ) + p.add_argument( + "-c", + dest="cleanup", + action="store_true", + help="Clean up docs binaries directory and exit", + ) + p.add_argument( + "-d", + dest="generate_diagrams", + action="store_true", + help="Generate diagrams for built examples using docs-embed", + ) + p.add_argument( + "-l", + dest="launchpad_storage_url", + help="LaunchPad storage URL to include in generated config", + ) + return p.parse_args(argv) + + +def validate_prerequisites(args): + if not args.arduino_cli_path: + print("ERROR: Arduino CLI path not provided (-ai option required)") + sys.exit(1) + if not args.user_path: + print("ERROR: Arduino user path not provided (-au option required)") + sys.exit(1) + arduino_cli_exe = Path(args.arduino_cli_path) / "arduino-cli" + if not arduino_cli_exe.exists(): + print(f"ERROR: arduino-cli not found at {arduino_cli_exe}") + sys.exit(1) + if not Path(args.user_path).is_dir(): + print(f"ERROR: Arduino user path does not exist: {args.user_path}") + sys.exit(1) + + +def cleanup_binaries(): + print(f"Cleaning up binaries directory: {DOCS_BINARIES_DIR}") + if not DOCS_BINARIES_DIR.exists(): + print("Binaries directory does not exist, nothing to clean") + return + for root, dirs, files in os.walk(DOCS_BINARIES_DIR): + for fname in files: + fpath = Path(root) / fname + parent = Path(root).name + # Always remove sketch/ci.json + if parent == "sketch" and fname == "ci.json": + fpath.unlink() + continue + keep = False + for pattern in KEEP_FILES: + if Path(fname).match(pattern): + keep = True + break + if not keep: + print(f"Removing: {fpath}") + fpath.unlink() + else: + print(f"Keeping: {fpath}") + # remove empty dirs + for root, dirs, files in os.walk(DOCS_BINARIES_DIR, topdown=False): + if not os.listdir(root): + try: + os.rmdir(root) + except Exception: + pass + print("Cleanup completed") + + +def find_examples_with_upload_binary(): + res = [] + for ino in Path('.').rglob('*.ino'): + sketch_dir = ino.parent + sketch_name = ino.stem + dir_name = sketch_dir.name + if dir_name != sketch_name: + continue + ci_json = sketch_dir / 'ci.json' + if ci_json.exists(): + try: + data = json.loads(ci_json.read_text()) + if 'upload-binary' in data and data['upload-binary']: + res.append(str(ino)) + except Exception: + continue + return res + + +def get_upload_binary_targets(sketch_dir): + ci_json = Path(sketch_dir) / 'ci.json' + try: + data = json.loads(ci_json.read_text()) + targets = data.get('upload-binary', {}).get('targets', []) + return targets + except Exception: + return [] + + +def build_example_for_target(sketch_dir, target, relative_path, args): + print(f"\n > Building example: {relative_path} for target: {target}") + output_dir = DOCS_BINARIES_DIR / relative_path / target + output_dir.mkdir(parents=True, exist_ok=True) + + sdkconfig = SDKCONFIG_DIR / target / 'sdkconfig' + if not check_requirements(str(sketch_dir), sdkconfig): + print(f"Target {target} does not meet the requirements for {Path(sketch_dir).name}. Skipping.") + return True + + # Build the sketch using sketch_utils.sh build - pass args as in shell script + build_args = [ + "-ai", + args.arduino_cli_path, + "-au", + args.user_path, + "-s", + str(sketch_dir), + "-t", + target, + "-b", + str(output_dir), + "--first-only", + ] + res = build_sketch(build_args) + if res.returncode == 0: + print(f"Successfully built {relative_path} for {target}") + ci_json = Path(sketch_dir) / 'ci.json' + if ci_json.exists(): + shutil.copy(ci_json, output_dir / 'ci.json') + if GENERATE_DIAGRAMS: + print(f"Generating diagram for {relative_path} ({target})...") + rc = run_cmd( + [ + "docs-embed", + "--path", + str(output_dir), + "diagram-from-ci", + "--platform", + target, + "--override", + ], + check=False, + ) + if rc.returncode == 0: + print("Diagram generated successfully for {relative_path} ({target})") + else: + print("WARNING: Failed to generate diagram for {relative_path} ({target})") + if LAUNCHPAD_STORAGE_URL: + print(f"Generating LaunchPad config for {relative_path} ({target})...") + rc = run_cmd( + [ + "docs-embed", + "--path", + str(output_dir), + "launchpad-config", + LAUNCHPAD_STORAGE_URL, + "--repo-url-prefix", + "https://github.com/espressif/arduino-esp32/tree/master", + "--override", + ], + check=False, + ) + if rc.returncode == 0: + print("LaunchPad config generated successfully for {relative_path} ({target})") + else: + print("WARNING: Failed to generate LaunchPad config for {relative_path} ({target})") + return True + else: + print(f"ERROR: Failed to build {relative_path} for {target}") + return False + + +def build_all_examples(args): + total_built = 0 + total_failed = 0 + + if DOCS_BINARIES_DIR.exists(): + shutil.rmtree(DOCS_BINARIES_DIR) + print(f"Removed existing build directory: {DOCS_BINARIES_DIR}") + + examples = find_examples_with_upload_binary() + if not examples: + print("No examples found with upload-binary configuration") + return 0 + + print('\nExamples to be built:') + print('====================') + for i, example in enumerate(examples, start=1): + sketch_dir = Path(example).parent + relative_path = str(sketch_dir).lstrip('./') + targets = get_upload_binary_targets(sketch_dir) + if targets: + print(f"{i}. {relative_path} (targets: {' '.join(targets)})") + print() + + for example in examples: + sketch_dir = Path(example).parent + relative_path = str(sketch_dir).lstrip('./') + targets = get_upload_binary_targets(sketch_dir) + if not targets: + print(f"WARNING: No targets found for {relative_path}") + continue + print(f"Building {relative_path} for targets: {targets}") + for target in targets: + ok = build_example_for_target(sketch_dir, target, relative_path, args) + if ok: + total_built += 1 + else: + total_failed += 1 + + print('\nBuild summary:') + print(f" Successfully built: {total_built}") + print(f" Failed builds: {total_failed}") + print(f" Output directory: {DOCS_BINARIES_DIR}") + return total_failed + + +def main(argv): + global GENERATE_DIAGRAMS, LAUNCHPAD_STORAGE_URL + args = parse_args(argv) + if args.cleanup: + cleanup_binaries() + return + validate_prerequisites(args) + GENERATE_DIAGRAMS = args.generate_diagrams + LAUNCHPAD_STORAGE_URL = args.launchpad_storage_url or "" + DOCS_BINARIES_DIR.mkdir(parents=True, exist_ok=True) + result = build_all_examples(args) + if result == 0: + print('\nAll examples built successfully!') + else: + print('\nSome builds failed. Check the output above for details.') + sys.exit(1) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/.github/scripts/sketch_utils.sh b/.github/scripts/sketch_utils.sh index 02990ca8914..4e700947c00 100755 --- a/.github/scripts/sketch_utils.sh +++ b/.github/scripts/sketch_utils.sh @@ -54,6 +54,8 @@ function check_requirements { # check_requirements } function build_sketch { # build_sketch [extra-options] + local first_only=false + while [ -n "$1" ]; do case "$1" in -ai ) @@ -92,6 +94,36 @@ function build_sketch { # build_sketch [ext shift debug_level="DebugLevel=$1" ;; + -b ) + shift + custom_build_dir=$1 + ;; + --first-only ) + first_only=true + ;; + -h ) + echo "Usage: build_sketch [options]" + echo "" + echo "Build an Arduino sketch for ESP32 targets." + echo "" + echo "Required options:" + echo " -ai Arduino IDE path" + echo " -au Arduino user path" + echo " -s Sketch directory containing .ino file and ci.json" + echo " -t Target chip (esp32, esp32s2, esp32c3, esp32s3, esp32c6, esp32h2, esp32p4, esp32c5)" + echo " Required unless -fqbn is specified" + echo "" + echo "Optional options:" + echo " -fqbn Fully qualified board name (alternative to -t)" + echo " -o Board options (PSRAM, USBMode, etc.) [default: chip-specific]" + echo " -i Chunk index for parallel builds [default: none]" + echo " -l Log compilation output to JSON file [default: none]" + echo " -d Debug level (DebugLevel=...) [default: none]" + echo " -b Custom build directory [default: \$ARDUINO_BUILD_DIR or \$HOME/.arduino/tests/\$target/\$sketchname/build.tmp]" + echo " --first-only Build only the first FQBN from ci.json configurations [default: false]" + echo " -h Show this help message" + exit 0 + ;; * ) break ;; @@ -128,7 +160,14 @@ function build_sketch { # build_sketch [ext len=$(jq -r --arg target "$target" '.fqbn[$target] | length' "$sketchdir"/ci.json) if [ "$len" -gt 0 ]; then - fqbn=$(jq -r --arg target "$target" '.fqbn[$target] | sort' "$sketchdir"/ci.json) + if [ "$first_only" = true ]; then + # Get only the first FQBN from the array (original order) + fqbn=$(jq -r --arg target "$target" '.fqbn[$target] | .[0]' "$sketchdir"/ci.json) + len=1 + fqbn="[\"$fqbn\"]" + else + fqbn=$(jq -r --arg target "$target" '.fqbn[$target] | sort' "$sketchdir"/ci.json) + fi fi fi @@ -219,12 +258,14 @@ function build_sketch { # build_sketch [ext fi # The directory that will hold all the artifacts (the build directory) is - # provided through: - # 1. An env variable called ARDUINO_BUILD_DIR. - # 2. Created at the sketch level as "build" in the case of a single - # configuration test. - # 3. Created at the sketch level as "buildX" where X is the number - # of configuration built in case of a multiconfiguration test. + # determined by the following priority: + # 1. Custom build directory via -b flag: + # - If path contains "docs/_static/binaries", use as exact path (for docs builds) + # - Otherwise, append target name to custom directory + # 2. ARDUINO_BUILD_DIR environment variable (if set) + # 3. Default: $HOME/.arduino/tests/$target/$sketchname/build.tmp + # + # For multiple configurations, subsequent builds use .1, .2, etc. suffixes sketchname=$(basename "$sketchdir") local has_requirements @@ -253,22 +294,35 @@ function build_sketch { # build_sketch [ext fi ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp" - if [ -n "$ARDUINO_BUILD_DIR" ]; then - build_dir="$ARDUINO_BUILD_DIR" - elif [ "$len" -eq 1 ]; then - # build_dir="$sketchdir/build" - build_dir="$HOME/.arduino/tests/$target/$sketchname/build.tmp" + + # Determine base build directory + if [ -n "$custom_build_dir" ]; then + # If custom_build_dir contains docs/_static/binaries, use it as exact path + if [[ "$custom_build_dir" == *"docs/_static/binaries"* ]]; then + build_dir_base="$custom_build_dir" + else + build_dir_base="$custom_build_dir/$target" + fi + elif [ -n "$ARDUINO_BUILD_DIR" ]; then + build_dir_base="$ARDUINO_BUILD_DIR" + else + build_dir_base="$HOME/.arduino/tests/$target/$sketchname/build.tmp" fi output_file="$HOME/.arduino/cli_compile_output.txt" sizes_file="$GITHUB_WORKSPACE/cli_compile_$chunk_index.json" mkdir -p "$ARDUINO_CACHE_DIR" + for i in $(seq 0 $((len - 1))); do - if [ "$len" -ne 1 ]; then - # build_dir="$sketchdir/build$i" - build_dir="$HOME/.arduino/tests/$target/$sketchname/build$i.tmp" + # Calculate build directory for this configuration + if [ "$i" -eq 0 ]; then + build_dir="$build_dir_base" + else + build_dir="${build_dir_base}.${i}" fi + + # Prepare build directory rm -rf "$build_dir" mkdir -p "$build_dir" diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index 5253c166f85..b06e2be1f75 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -5,6 +5,7 @@ on: branches: - master - release/v2.x + - wokwi-embed-launchpad paths: - "docs/**" - ".github/workflows/docs_build.yml" @@ -44,8 +45,27 @@ jobs: PATH=/home/runner/.local/bin:$PATH pip3 install -r requirements.txt --prefer-binary PATH=/home/runner/.local/bin:$PATH SPHINXOPTS="-W" build-docs -l en + - name: Install Arduino CLI + run: | + source .github/scripts/install-arduino-cli.sh + + - name: Install ESP32 Core + run: | + source .github/scripts/install-arduino-core-esp32.sh + + - name: Build Examples + run: | + echo "Configuring Arduino CLI and ESP32 core" + source .github/scripts/install-arduino-cli.sh + source .github/scripts/install-arduino-core-esp32.sh + python3 .github/scripts/docs_build_examples.py -ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH + + - name: Cleanup Binaries + run: | + python3 .github/scripts/docs_build_examples.py -c + - name: Archive Docs uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: docs - path: docs + path: docs \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000000..b59d4f5156f --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_static/binaries/ diff --git a/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ci.json b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ci.json new file mode 100644 index 00000000000..adab5922626 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/FunctionalInterrupt/ci.json @@ -0,0 +1,121 @@ +{ + "fqbn":{ + "esp32":[ + "espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=qio" + ], + "esp32c3":[ + "espressif:esp32:esp32c3:PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32c3:PartitionScheme=huge_app,FlashMode=qio" + ], + "esp32c6":[ + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=dio,FlashFreq=40", + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=qio,FlashFreq=40" + ], + "esp32h2":[ + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=dio,FlashFreq=16", + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=qio,FlashFreq=16" + ], + "esp32s2":[ + "espressif:esp32:esp32s2:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32s2:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=qio" + ], + "esp32s3":[ + "espressif:esp32:esp32s3:PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32s3:PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=qio120", + "espressif:esp32:esp32s3:PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=dio" + ], + "esp32p4":[ + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=dio,FlashFreq=40", + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=qio,FlashFreq=40" + ] + }, + "platforms":{ + "qemu":false + }, + "upload-binary":{ + "targets":[ + "esp32s3", + "esp32" + ], + "wokwi-simulation":[ + "esp32s3", + "esp32" + ], + "description":"BlinkRGB LED control example with push button input", + "diagram":{ + "esp32s3":{ + "parts":[ + { + "type":"wokwi-pushbutton", + "id":"btn1", + "top":172.7, + "left":137.9, + "rotate":90, + "attrs":{ + "color":"green" + } + } + ], + "connections":[ + [ + "btn1:1.l", + "esp:0", + "blue", + [ + "v-9.6", + "h-90.97" + ] + ], + [ + "esp:GND.4", + "btn1:2.r", + "black", + [ + + ] + ] + ] + }, + "esp32":{ + "parts":[ + { + "type":"wokwi-pushbutton", + "id":"btn1", + "top":95.6, + "left":131.6, + "rotate":270, + "attrs":{ + "color":"green" + } + } + ], + "connections":[ + [ + "btn1:1.l", + "esp:0", + "blue", + [ + + ] + ], + [ + "esp:GND.2", + "btn1:2.r", + "black", + [ + "v0", + "h52.84" + ] + ] + ] + } + } + } +} diff --git a/libraries/ESP32/examples/GPIO/GPIOInterrupt/ci.json b/libraries/ESP32/examples/GPIO/GPIOInterrupt/ci.json new file mode 100644 index 00000000000..398a66e2ce5 --- /dev/null +++ b/libraries/ESP32/examples/GPIO/GPIOInterrupt/ci.json @@ -0,0 +1,120 @@ +{ + "fqbn":{ + "esp32":[ + "espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=qio" + ], + "esp32c3":[ + "espressif:esp32:esp32c3:PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32c3:PartitionScheme=huge_app,FlashMode=qio" + ], + "esp32c6":[ + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=dio,FlashFreq=40", + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32c6:PartitionScheme=huge_app,FlashMode=qio,FlashFreq=40" + ], + "esp32h2":[ + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=dio,FlashFreq=16", + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32h2:PartitionScheme=huge_app,FlashMode=qio,FlashFreq=16" + ], + "esp32s2":[ + "espressif:esp32:esp32s2:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32s2:PSRAM=enabled,PartitionScheme=huge_app,FlashMode=qio" + ], + "esp32s3":[ + "espressif:esp32:esp32s3:PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32s3:PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=qio120", + "espressif:esp32:esp32s3:PSRAM=opi,USBMode=default,PartitionScheme=huge_app,FlashMode=dio" + ], + "esp32p4":[ + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=dio", + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=dio,FlashFreq=40", + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=qio", + "espressif:esp32:esp32p4:PSRAM=enabled,USBMode=default,PartitionScheme=huge_app,FlashMode=qio,FlashFreq=40" + ] + }, + "platforms":{ + "qemu":false + }, + "upload-binary":{ + "targets":[ + "esp32s3" + ], + "wokwi-simulation":[ + "esp32s3", + "esp32" + ], + "description":"BlinkRGB LED control example with push button input", + "diagram":{ + "esp32s3":{ + "parts":[ + { + "type":"wokwi-pushbutton", + "id":"btn1", + "top":172.7, + "left":137.9, + "rotate":90, + "attrs":{ + "color":"green" + } + } + ], + "connections":[ + [ + "btn1:1.l", + "esp:0", + "blue", + [ + "v-9.6", + "h-90.97" + ] + ], + [ + "esp:GND.4", + "btn1:2.r", + "black", + [ + + ] + ] + ] + }, + "esp32":{ + "parts":[ + { + "type":"wokwi-pushbutton", + "id":"btn1", + "top":95.6, + "left":131.6, + "rotate":270, + "attrs":{ + "color":"green" + } + } + ], + "connections":[ + [ + "btn1:1.l", + "esp:0", + "blue", + [ + + ] + ], + [ + "esp:GND.2", + "btn1:2.r", + "black", + [ + "v0", + "h52.84" + ] + ] + ] + } + } + } +} From 93fa774023de854dc0fdf1b2b9b4e0a8c7fc2d29 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Fri, 10 Oct 2025 12:41:02 +0200 Subject: [PATCH 2/6] refactor(ci): streamline Arduino CLI and ESP32 core installation in docs build workflow --- .github/workflows/docs_build.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index b06e2be1f75..25942a35cf5 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -45,17 +45,8 @@ jobs: PATH=/home/runner/.local/bin:$PATH pip3 install -r requirements.txt --prefer-binary PATH=/home/runner/.local/bin:$PATH SPHINXOPTS="-W" build-docs -l en - - name: Install Arduino CLI - run: | - source .github/scripts/install-arduino-cli.sh - - - name: Install ESP32 Core - run: | - source .github/scripts/install-arduino-core-esp32.sh - - name: Build Examples run: | - echo "Configuring Arduino CLI and ESP32 core" source .github/scripts/install-arduino-cli.sh source .github/scripts/install-arduino-core-esp32.sh python3 .github/scripts/docs_build_examples.py -ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH From 0fd405bd4ada03af86e1926c7a9b2cd8814e2140 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Fri, 10 Oct 2025 12:47:36 +0200 Subject: [PATCH 3/6] fix(docs): update esp-docs dependency to specific git reference and add debug flag for example builds --- .github/workflows/docs_build.yml | 2 +- docs/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index 25942a35cf5..124804b2eb9 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -49,7 +49,7 @@ jobs: run: | source .github/scripts/install-arduino-cli.sh source .github/scripts/install-arduino-core-esp32.sh - python3 .github/scripts/docs_build_examples.py -ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH + python3 .github/scripts/docs_build_examples.py -ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH -d - name: Cleanup Binaries run: | diff --git a/docs/requirements.txt b/docs/requirements.txt index ef2ab88cb65..f3163595000 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ sphinx==4.5.0 -esp-docs>=1.4.0 +esp-docs @ git+https://github.com/JakubAndrysek/esp-docs.git@extensions/wokwi_embed sphinx-copybutton==0.5.0 sphinx-tabs==3.2.0 numpydoc==1.5.0 From 95dfaf9a64dca24c54e30952db10afc41ad3bcc4 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Fri, 10 Oct 2025 13:14:58 +0200 Subject: [PATCH 4/6] feat(docs): enhance example build script and CI workflow with diagram generation and updated requirements --- .github/scripts/docs_build_examples.py | 68 ++++++++++---------------- .github/workflows/docs_build.yml | 20 ++++---- docs/requirements.txt | 12 ++--- 3 files changed, 43 insertions(+), 57 deletions(-) diff --git a/.github/scripts/docs_build_examples.py b/.github/scripts/docs_build_examples.py index db82057e1e7..72aab26853a 100755 --- a/.github/scripts/docs_build_examples.py +++ b/.github/scripts/docs_build_examples.py @@ -8,6 +8,7 @@ import argparse from argparse import RawDescriptionHelpFormatter +from esp_docs.generic_extensions.docs_embed.tool.wokwi_tool import DiagramSync import json import os import shutil @@ -20,6 +21,9 @@ # Determine SDKCONFIG_DIR like the shell script ARDUINO_ESP32_PATH = os.environ.get("ARDUINO_ESP32_PATH") GITHUB_WORKSPACE = os.environ.get("GITHUB_WORKSPACE") +DOCS_DEPLOY_URL_BASE = os.environ.get("DOCS_PROD_URL_BASE") +REPO_URL_PREFIX = os.environ.get("REPO_URL_PREFIX") + if ARDUINO_ESP32_PATH and (Path(ARDUINO_ESP32_PATH) / "tools" / "esp32-arduino-libs").is_dir(): SDKCONFIG_DIR = Path(ARDUINO_ESP32_PATH) / "tools" / "esp32-arduino-libs" @@ -31,10 +35,15 @@ # Wrapper functions to call sketch_utils.sh SKETCH_UTILS = SCRIPT_DIR / "sketch_utils.sh" -KEEP_FILES = ["*.merged.bin", "ci.json"] +KEEP_FILES = [ + "*.merged.bin", + "ci.json", + "launchpad.toml", + "diagram*.json", +] DOCS_BINARIES_DIR = Path("docs/_static/binaries") GENERATE_DIAGRAMS = False -LAUNCHPAD_STORAGE_URL = "" +GENERATE_LAUNCHPAD_CONFIG = False def run_cmd(cmd, check=True, capture_output=False, text=True): @@ -117,8 +126,9 @@ def parse_args(argv): ) p.add_argument( "-l", - dest="launchpad_storage_url", - help="LaunchPad storage URL to include in generated config", + dest="generate_launchpad_config", + action="store_true", + help="Generate LaunchPad config with configured storage URL", ) return p.parse_args(argv) @@ -233,42 +243,18 @@ def build_example_for_target(sketch_dir, target, relative_path, args): shutil.copy(ci_json, output_dir / 'ci.json') if GENERATE_DIAGRAMS: print(f"Generating diagram for {relative_path} ({target})...") - rc = run_cmd( - [ - "docs-embed", - "--path", - str(output_dir), - "diagram-from-ci", - "--platform", - target, - "--override", - ], - check=False, - ) - if rc.returncode == 0: - print("Diagram generated successfully for {relative_path} ({target})") - else: - print("WARNING: Failed to generate diagram for {relative_path} ({target})") - if LAUNCHPAD_STORAGE_URL: + try: + sync = DiagramSync(output_dir) + sync.generate_diagram_from_ci(target) + except Exception as e: + print(f"WARNING: Failed to generate diagram for {relative_path} ({target}): {e}") + if GENERATE_LAUNCHPAD_CONFIG: print(f"Generating LaunchPad config for {relative_path} ({target})...") - rc = run_cmd( - [ - "docs-embed", - "--path", - str(output_dir), - "launchpad-config", - LAUNCHPAD_STORAGE_URL, - "--repo-url-prefix", - "https://github.com/espressif/arduino-esp32/tree/master", - "--override", - ], - check=False, - ) - if rc.returncode == 0: - print("LaunchPad config generated successfully for {relative_path} ({target})") - else: - print("WARNING: Failed to generate LaunchPad config for {relative_path} ({target})") - return True + try: + sync = DiagramSync(output_dir) + sync.generate_launchpad_config(DOCS_DEPLOY_URL_BASE, REPO_URL_PREFIX) + except Exception as e: + print(f"WARNING: Failed to generate LaunchPad config for {relative_path} ({target}): {e}") else: print(f"ERROR: Failed to build {relative_path} for {target}") return False @@ -320,14 +306,14 @@ def build_all_examples(args): def main(argv): - global GENERATE_DIAGRAMS, LAUNCHPAD_STORAGE_URL + global GENERATE_DIAGRAMS, GENERATE_LAUNCHPAD_CONFIG args = parse_args(argv) if args.cleanup: cleanup_binaries() return validate_prerequisites(args) GENERATE_DIAGRAMS = args.generate_diagrams - LAUNCHPAD_STORAGE_URL = args.launchpad_storage_url or "" + GENERATE_LAUNCHPAD_CONFIG = args.generate_launchpad_config DOCS_BINARIES_DIR.mkdir(parents=True, exist_ok=True) result = build_all_examples(args) if result == 0: diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index 124804b2eb9..76000436f30 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -35,16 +35,6 @@ jobs: cache: "pip" python-version: "3.10" - - name: Build - run: | - sudo apt update - sudo apt install python3-pip python3-setuptools - # GitHub CI installs pip3 and setuptools outside the path. - # Update the path to include them and run. - cd ./docs - PATH=/home/runner/.local/bin:$PATH pip3 install -r requirements.txt --prefer-binary - PATH=/home/runner/.local/bin:$PATH SPHINXOPTS="-W" build-docs -l en - - name: Build Examples run: | source .github/scripts/install-arduino-cli.sh @@ -55,6 +45,16 @@ jobs: run: | python3 .github/scripts/docs_build_examples.py -c + - name: Build + run: | + sudo apt update + sudo apt install python3-pip python3-setuptools + # GitHub CI installs pip3 and setuptools outside the path. + # Update the path to include them and run. + cd ./docs + PATH=/home/runner/.local/bin:$PATH pip3 install -r requirements.txt --prefer-binary + PATH=/home/runner/.local/bin:$PATH SPHINXOPTS="-W" build-docs -l en + - name: Archive Docs uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: diff --git a/docs/requirements.txt b/docs/requirements.txt index f3163595000..3aba296fa3f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,7 @@ -sphinx==4.5.0 +sphinx esp-docs @ git+https://github.com/JakubAndrysek/esp-docs.git@extensions/wokwi_embed -sphinx-copybutton==0.5.0 -sphinx-tabs==3.2.0 -numpydoc==1.5.0 -standard-imghdr==3.13.0 -Sphinx-Substitution-Extensions==2022.2.16 +sphinx-copybutton +sphinx-tabs +numpydoc +standard-imghdr +Sphinx-Substitution-Extensions From fccdd8ea9583714cfabdac3b6d28e8e7203aeab5 Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Fri, 10 Oct 2025 15:36:08 +0200 Subject: [PATCH 5/6] feat(ci): enhance CI workflow to cache and upload compiled binaries for improved build efficiency --- .github/workflows/docs_build.yml | 36 +++++++++++++++++++++ .github/workflows/push.yml | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index 76000436f30..5193d88f877 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -35,6 +35,42 @@ jobs: cache: "pip" python-version: "3.10" + - name: Restore compiled binaries from cache + id: cache-restore + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + key: compiled-binaries-consolidated-${{ github.sha }} + restore-keys: | + compiled-binaries-consolidated- + compiled-binaries-Linux-${{ github.sha }}- + compiled-binaries-Linux- + path: compiled_binaries + lookup-only: false + + - name: Download compiled binaries artifact (fallback) + if: steps.cache-restore.outputs.cache-hit != 'true' + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 + with: + name: compiled-binaries-consolidated + path: compiled_binaries + continue-on-error: true + + - name: Copy cached binaries to local directory + run: | + if [ -d "compiled_binaries" ]; then + echo "Found cached binaries, copying to local workspace..." + mkdir -p local_binaries + cp -r compiled_binaries/* local_binaries/ || true + echo "Available binaries:" + find local_binaries -type f -name "*.bin" -o -name "*.elf" | head -20 + echo "Total binary files found: $(find local_binaries -type f \( -name "*.bin" -o -name "*.elf" \) | wc -l)" + + echo "Contents of compiled_binaries directory:" + find compiled_binaries -type f | head -20 + else + echo "No cached binaries found, will build fresh" + fi + - name: Build Examples run: | source .github/scripts/install-arduino-cli.sh diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 690993504c2..774b9879ca9 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -22,6 +22,7 @@ on: branches: - master - release/* + - wokwi-embed-launchpad pull_request: paths: - "cores/**" @@ -167,6 +168,25 @@ jobs: - name: Build selected sketches run: bash ./.github/scripts/on-push.sh ${{ matrix.chunk }} ${{ needs.gen-chunks.outputs.chunk_count }} 1 ${{ env.LOG_LEVEL }} affected_sketches.txt + - name: Collect compiled binaries + run: | + mkdir -p compiled_binaries + # Find and copy all compiled binaries (*.bin, *.elf files) + find . -name "*.bin" -o -name "*.elf" | grep -E "(build|target)" | while read file; do + # Create relative path structure in compiled_binaries + rel_path=$(echo "$file" | sed 's|^\./||') + target_dir="compiled_binaries/$(dirname "$rel_path")" + mkdir -p "$target_dir" + cp "$file" "$target_dir/" + done + + - name: Upload compiled binaries as artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: compiled-binaries-${{ matrix.chunk }} + path: compiled_binaries + retention-days: 1 + #Upload cli compile json as artifact - name: Upload cli compile json uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -175,6 +195,40 @@ jobs: path: cli_compile_${{ matrix.chunk }}.json overwrite: true + # Consolidate compiled binaries from all chunks + consolidate-binaries: + name: Consolidate compiled binaries + needs: [gen-chunks, build-arduino-linux] + if: ${{ needs.gen-chunks.outputs.should_build == '1' }} + runs-on: ubuntu-latest + steps: + - name: Download all binary artifacts + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 + with: + pattern: compiled-binaries-* + merge-multiple: true + path: all_binaries + + - name: List consolidated binaries + run: | + echo "Consolidating binaries from all chunks..." + echo "Downloaded artifacts:" + find all_binaries -type f -name "*.bin" -o -name "*.elf" | head -20 || echo "No binaries found in artifacts" + echo "Total binary files: $(find all_binaries -type f \( -name "*.bin" -o -name "*.elf" \) | wc -l)" + + - name: Create consolidated binary cache + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + key: compiled-binaries-consolidated-${{ github.sha }} + path: all_binaries + + - name: Upload consolidated binaries + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: compiled-binaries-consolidated + path: all_binaries + retention-days: 7 + # Windows and MacOS build-arduino-win-mac: name: Arduino on ${{ matrix.os }} From 0c98e0478c1f7a18edb1034da7ac6a02de76d2ef Mon Sep 17 00:00:00 2001 From: Jakub Andrysek Date: Wed, 15 Oct 2025 12:50:47 +0200 Subject: [PATCH 6/6] refactor(ci): clean up unused variables and enhance debugging in CI workflow --- .github/scripts/docs_build_examples.py | 30 ++--- .github/scripts/on-push.sh | 2 - .github/workflows/docs_build.yml | 145 ++++++++++++++++++++++--- 3 files changed, 143 insertions(+), 34 deletions(-) diff --git a/.github/scripts/docs_build_examples.py b/.github/scripts/docs_build_examples.py index 72aab26853a..32f9fd90008 100755 --- a/.github/scripts/docs_build_examples.py +++ b/.github/scripts/docs_build_examples.py @@ -8,7 +8,7 @@ import argparse from argparse import RawDescriptionHelpFormatter -from esp_docs.generic_extensions.docs_embed.tool.wokwi_tool import DiagramSync +# from esp_docs.generic_extensions.docs_embed.tool.wokwi_tool import DiagramSync import json import os import shutil @@ -241,20 +241,20 @@ def build_example_for_target(sketch_dir, target, relative_path, args): ci_json = Path(sketch_dir) / 'ci.json' if ci_json.exists(): shutil.copy(ci_json, output_dir / 'ci.json') - if GENERATE_DIAGRAMS: - print(f"Generating diagram for {relative_path} ({target})...") - try: - sync = DiagramSync(output_dir) - sync.generate_diagram_from_ci(target) - except Exception as e: - print(f"WARNING: Failed to generate diagram for {relative_path} ({target}): {e}") - if GENERATE_LAUNCHPAD_CONFIG: - print(f"Generating LaunchPad config for {relative_path} ({target})...") - try: - sync = DiagramSync(output_dir) - sync.generate_launchpad_config(DOCS_DEPLOY_URL_BASE, REPO_URL_PREFIX) - except Exception as e: - print(f"WARNING: Failed to generate LaunchPad config for {relative_path} ({target}): {e}") + # if GENERATE_DIAGRAMS: + # print(f"Generating diagram for {relative_path} ({target})...") + # try: + # sync = DiagramSync(output_dir) + # sync.generate_diagram_from_ci(target) + # except Exception as e: + # print(f"WARNING: Failed to generate diagram for {relative_path} ({target}): {e}") + # if GENERATE_LAUNCHPAD_CONFIG: + # print(f"Generating LaunchPad config for {relative_path} ({target})...") + # try: + # sync = DiagramSync(output_dir) + # sync.generate_launchpad_config(DOCS_DEPLOY_URL_BASE, REPO_URL_PREFIX) + # except Exception as e: + # print(f"WARNING: Failed to generate LaunchPad config for {relative_path} ({target}): {e}") else: print(f"ERROR: Failed to build {relative_path} for {target}") return False diff --git a/.github/scripts/on-push.sh b/.github/scripts/on-push.sh index 3eb020c09a3..f91c731f92f 100755 --- a/.github/scripts/on-push.sh +++ b/.github/scripts/on-push.sh @@ -2,8 +2,6 @@ set -e -export ARDUINO_BUILD_DIR="$HOME/.arduino/build.tmp" - function build { local target=$1 local chunk_index=$2 diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index 5193d88f877..c1d1b2838cc 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -42,8 +42,6 @@ jobs: key: compiled-binaries-consolidated-${{ github.sha }} restore-keys: | compiled-binaries-consolidated- - compiled-binaries-Linux-${{ github.sha }}- - compiled-binaries-Linux- path: compiled_binaries lookup-only: false @@ -55,32 +53,145 @@ jobs: path: compiled_binaries continue-on-error: true - - name: Copy cached binaries to local directory + - name: Debug - List cached binaries structure run: | + echo "==========================================" + echo "Debugging cached binaries structure" + echo "==========================================" if [ -d "compiled_binaries" ]; then - echo "Found cached binaries, copying to local workspace..." - mkdir -p local_binaries - cp -r compiled_binaries/* local_binaries/ || true - echo "Available binaries:" - find local_binaries -type f -name "*.bin" -o -name "*.elf" | head -20 - echo "Total binary files found: $(find local_binaries -type f \( -name "*.bin" -o -name "*.elf" \) | wc -l)" - - echo "Contents of compiled_binaries directory:" - find compiled_binaries -type f | head -20 + echo "✓ compiled_binaries directory exists" + echo "" + echo "Directory tree (first 50 entries):" + find compiled_binaries -type f | head -50 | sort + echo "" + echo "Binary files (.bin):" + find compiled_binaries -type f -name "*.bin" | head -30 + echo "" + echo "ELF files:" + find compiled_binaries -type f -name "*.elf" | head -30 + echo "" + echo "Total binary files: $(find compiled_binaries -type f -name "*.bin" | wc -l)" + echo "Total ELF files: $(find compiled_binaries -type f -name "*.elf" | wc -l)" + echo "" + echo "Sample directory structure:" + ls -la compiled_binaries/ || true else - echo "No cached binaries found, will build fresh" + echo "✗ compiled_binaries directory NOT found" + echo "WARNING: No cached binaries available!" fi + echo "==========================================" - - name: Build Examples + - name: Copy cached binaries to docs/_static/binaries/ run: | - source .github/scripts/install-arduino-cli.sh - source .github/scripts/install-arduino-core-esp32.sh - python3 .github/scripts/docs_build_examples.py -ai $ARDUINO_IDE_PATH -au $ARDUINO_USR_PATH -d + echo "Copying cached binaries to docs/_static/binaries/" + mkdir -p docs/_static/binaries + + if [ ! -d "compiled_binaries" ]; then + echo "ERROR: No compiled_binaries directory found!" + echo "Cannot proceed without binaries. Exiting." + exit 1 + fi + + # Find all .bin files in compiled_binaries + echo "Processing binary files..." + bin_count=0 + skip_count=0 + + # Binaries are stored in structure like: + # compiled_binaries/{home}/.arduino/tests/{target}/{SketchName}/build.tmp/{SketchName}.ino.bin + # We need to: + # 1. Extract sketch name and target from path + # 2. Find the sketch in libraries/ directory + # 3. Copy to docs/_static/binaries/libraries/{LibraryName}/examples/{SketchName}/{target}/ + + find compiled_binaries -type f -name "*.bin" | while read -r bin_file; do + echo "Processing: $bin_file" + + # Extract the binary filename (e.g., WiFiClientSecure.ino.bin) + bin_name=$(basename "$bin_file") + + # Extract sketch name (remove .ino.bin or .bin extension) + sketch_name="${bin_name%.ino.bin}" + if [ "$sketch_name" == "$bin_name" ]; then + sketch_name="${bin_name%.bin}" + fi + + # Extract target from path (esp32, esp32s2, esp32s3, esp32c3, esp32c6, esp32h2, esp32p4, esp32c5) + if [[ "$bin_file" =~ /(esp32[a-z0-9]*)/ ]]; then + target="${BASH_REMATCH[1]}" + else + echo " ⚠ Could not determine target from path: $bin_file" + skip_count=$((skip_count + 1)) + continue + fi + + # Search for the sketch in libraries directory + sketch_ino_file=$(find libraries -type f -name "${sketch_name}.ino" 2>/dev/null | head -1) + + if [ -z "$sketch_ino_file" ]; then + echo " ⚠ Could not find sketch '${sketch_name}.ino' in libraries/" + skip_count=$((skip_count + 1)) + continue + fi + + # Extract the relative path from libraries/ + # e.g., libraries/NetworkClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino + # becomes: NetworkClientSecure/examples/WiFiClientSecure + sketch_dir=$(dirname "$sketch_ino_file") + relative_path="${sketch_dir#libraries/}" + + echo " ✓ Found sketch at: $sketch_ino_file" + echo " ✓ Target: $target" + echo " ✓ Relative path: $relative_path" + + # Create output directory + output_dir="docs/_static/binaries/libraries/${relative_path}/${target}" + mkdir -p "$output_dir" + + # Copy the binary + cp "$bin_file" "$output_dir/" + echo " → Copied to: $output_dir/$bin_name" + bin_count=$((bin_count + 1)) + done + + echo "" + echo "==========================================" + echo "Binary copy completed!" + echo " Binaries copied: $bin_count" + echo " Binaries skipped: $skip_count" + echo "==========================================" + echo "" + echo "Final structure in docs/_static/binaries/:" + find docs/_static/binaries -type f -name "*.bin" | head -30 + echo "" + echo "Total binaries in docs/_static/binaries/: $(find docs/_static/binaries -type f -name "*.bin" | wc -l)" - name: Cleanup Binaries run: | python3 .github/scripts/docs_build_examples.py -c + - name: Debug - Final binaries structure after cleanup + run: | + echo "==========================================" + echo "Final structure in docs/_static/binaries/ (after cleanup)" + echo "==========================================" + if [ -d "docs/_static/binaries" ]; then + echo "Directory tree:" + find docs/_static/binaries -type f | sort + echo "" + echo "Files by type:" + echo " .bin files: $(find docs/_static/binaries -type f -name "*.bin" | wc -l)" + echo " .elf files: $(find docs/_static/binaries -type f -name "*.elf" | wc -l)" + echo " .json files: $(find docs/_static/binaries -type f -name "*.json" | wc -l)" + echo " .toml files: $(find docs/_static/binaries -type f -name "*.toml" | wc -l)" + echo "" + echo "Example of final structure (first 20 files):" + find docs/_static/binaries -type f | head -20 + else + echo "WARNING: docs/_static/binaries/ directory not found!" + fi + echo "==========================================" + - name: Build run: | sudo apt update