-
Notifications
You must be signed in to change notification settings - Fork 7.7k
move it to main for testing #11921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
JakubAndrysek
wants to merge
6
commits into
espressif:master
from
JakubAndrysek:wokwi-embed-launchpad
+858
−25
Closed
move it to main for testing #11921
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
b79916c
feat(docs): add Python script for building Arduino examples and updat…
JakubAndrysek 93fa774
refactor(ci): streamline Arduino CLI and ESP32 core installation in d…
JakubAndrysek 0fd405b
fix(docs): update esp-docs dependency to specific git reference and a…
JakubAndrysek 95dfaf9
feat(docs): enhance example build script and CI workflow with diagram…
JakubAndrysek fccdd8e
feat(ci): enhance CI workflow to cache and upload compiled binaries f…
JakubAndrysek 0c98e04
refactor(ci): clean up unused variables and enhance debugging in CI w…
JakubAndrysek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,327 @@ | ||
| #!/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 <arduino_cli_path> -au <arduino_user_path> [options] | ||
| """ | ||
|
|
||
| import argparse | ||
| from argparse import RawDescriptionHelpFormatter | ||
| # from esp_docs.generic_extensions.docs_embed.tool.wokwi_tool import DiagramSync | ||
| 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") | ||
| 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" | ||
| 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", | ||
| "launchpad.toml", | ||
| "diagram*.json", | ||
| ] | ||
| DOCS_BINARIES_DIR = Path("docs/_static/binaries") | ||
| GENERATE_DIAGRAMS = False | ||
| GENERATE_LAUNCHPAD_CONFIG = False | ||
|
|
||
|
|
||
| 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/<sketch_path>/<target>/\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="generate_launchpad_config", | ||
| action="store_true", | ||
| help="Generate LaunchPad config with configured storage URL", | ||
| ) | ||
| 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: | ||
Check noticeCode scanning / CodeQL Insufficient Logging Low
Try-catch except, Pass/Continue detected.
|
||
| 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: | ||
Check noticeCode scanning / CodeQL Insufficient Logging Low
Try-catch except, Pass/Continue detected.
|
||
| 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})...") | ||
| # 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 | ||
|
|
||
|
|
||
| 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, GENERATE_LAUNCHPAD_CONFIG | ||
| args = parse_args(argv) | ||
| if args.cleanup: | ||
| cleanup_binaries() | ||
| return | ||
| validate_prerequisites(args) | ||
| GENERATE_DIAGRAMS = args.generate_diagrams | ||
| GENERATE_LAUNCHPAD_CONFIG = args.generate_launchpad_config | ||
| 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:]) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check failure
Code scanning / CodeQL
Uncontrolled command line Critical