File
Blob: scripts/project.py
| 1 | """Shared paths and the pinned firmware toolchain; never modifies the shell profile.""" |
| 2 | import os |
| 3 | from pathlib import Path |
| 4 | import subprocess |
| 5 | |
| 6 | ROOT = Path(__file__).resolve().parents[1] |
| 7 | IDF_REVISION = "2c211b236707889e8400c4dc5644dd5c4ee071e0" |
| 8 | DSP_REVISION = "3c8ac0fdfec83740b783e200862c8d0c056de0ad" |
| 9 | CODEC_REVISION = "da256e5f462a8e010667d35314a5ba5cdc4a8d9a" |
| 10 | VENDOR = ROOT / ".tools/vendor" |
| 11 | RUST_RELEASE = "1.97.0.0" |
| 12 | RUST_TOOLCHAIN = "esp-radio" |
| 13 | ESPUP_VERSION = "0.17.1" |
| 14 | ESPUP_SHA256 = "dbe54e9907b687809dbe1b955731569ed6df2b525362710d676256c5c8cf9ccd" |
| 15 | CLANG_FORMAT_VERSION = "23.1.1" |
| 16 | |
| 17 | |
| 18 | def sdk_root(): |
| 19 | configured = os.environ.get("RADIO_SDK_ROOT") |
| 20 | if configured: |
| 21 | return Path(configured).expanduser().resolve() |
| 22 | existing = ROOT / ".tools/validation-sdk" |
| 23 | return (existing if existing.exists() else ROOT / ".tools/sdk").resolve() |
| 24 | |
| 25 | |
| 26 | def build_dir(): |
| 27 | return sdk_root() / "rust-radio-build" |
| 28 | |
| 29 | |
| 30 | def verify_checkout(path, revision): |
| 31 | actual = subprocess.check_output(["git", "-C", str(path), "rev-parse", "HEAD"], text=True).strip() |
| 32 | if actual != revision: |
| 33 | raise SystemExit(f"{path.name} revision differs from the pinned version; use a separate checkout") |
| 34 | changes = subprocess.check_output(["git", "-C", str(path), "status", "--porcelain", "--untracked-files=no"], text=True) |
| 35 | if changes: |
| 36 | raise SystemExit(f"{path.name} has tracked modifications; use a clean pinned checkout") |
| 37 | |
| 38 | |
| 39 | def rust_libclang(): |
| 40 | """Find libclang bundled with the named Xtensa Rust toolchain.""" |
| 41 | compiler = subprocess.run( |
| 42 | ["rustup", "which", "--toolchain", RUST_TOOLCHAIN, "rustc"], |
| 43 | capture_output=True, text=True, |
| 44 | ) |
| 45 | if not compiler.returncode: |
| 46 | toolchain = Path(compiler.stdout.strip()).parent.parent |
| 47 | libraries = sorted(toolchain.glob("xtensa-esp32-elf-clang/*/esp-clang/lib/libclang.so")) |
| 48 | for library in reversed(libraries): |
| 49 | if library.is_file(): |
| 50 | return library.parent |
| 51 | raise SystemExit("Xtensa libclang is missing. Run make setup first.") |
| 52 | |
| 53 | |
| 54 | def idf_environment(): |
| 55 | """Return ESP-IDF's Python and the build environment, including Xtensa libclang.""" |
| 56 | sdk = sdk_root() |
| 57 | tools = sdk / "tools" |
| 58 | try: |
| 59 | python = next((tools / "python_env").glob("idf5.5_py*_env/bin/python")) |
| 60 | gcc = next((tools / "tools/xtensa-esp-elf").rglob("xtensa-esp32s3-elf-gcc")) |
| 61 | ninja = next((tools / "tools/ninja").rglob("ninja")) |
| 62 | rom = next((tools / "tools/esp-rom-elfs").glob("*")) |
| 63 | except StopIteration: |
| 64 | raise SystemExit("ESP-IDF tools are missing. Run make setup first.") from None |
| 65 | env = os.environ.copy() |
| 66 | env.update(IDF_PATH=str(sdk / "esp-idf"), IDF_TOOLS_PATH=str(tools), IDF_TARGET="esp32s3", |
| 67 | IDF_PYTHON_ENV_PATH=str(python.parent.parent), PIP_CACHE_DIR=str(sdk / "pip-cache"), |
| 68 | ESP_ROM_ELF_DIR=str(rom), LIBCLANG_PATH=str(rust_libclang()), |
| 69 | ESP_IDF_ESPUP_CLANG_SYMLINK="ignore") |
| 70 | env["PATH"] = os.pathsep.join([str(python.parent), str(gcc.parent), str(ninja.parent), env["PATH"]]) |
| 71 | return python, env |