File
Blob: scripts/setup_tooling.py
| 1 | #!/usr/bin/env python3 |
| 2 | """Set up the pinned Linux x86_64 S3 toolchain without changing global defaults.""" |
| 3 | import argparse |
| 4 | import hashlib |
| 5 | import os |
| 6 | from pathlib import Path |
| 7 | import platform |
| 8 | import shutil |
| 9 | import subprocess |
| 10 | import sys |
| 11 | import urllib.request |
| 12 | from project import (ROOT, IDF_REVISION, RUST_RELEASE, RUST_TOOLCHAIN, |
| 13 | ESPUP_VERSION, ESPUP_SHA256, sdk_root, verify_checkout, idf_environment, |
| 14 | rust_libclang, VENDOR, DSP_REVISION, CODEC_REVISION) |
| 15 | |
| 16 | |
| 17 | def run(command, **kwargs): |
| 18 | subprocess.run([str(part) for part in command], check=True, **kwargs) |
| 19 | |
| 20 | |
| 21 | def checkout(path, url, revision): |
| 22 | if not path.exists(): |
| 23 | path.parent.mkdir(parents=True, exist_ok=True) |
| 24 | run(["git", "init", path]) |
| 25 | run(["git", "-C", path, "remote", "add", "origin", url]) |
| 26 | run(["git", "-C", path, "fetch", "--depth", "1", "origin", revision]) |
| 27 | run(["git", "-C", path, "checkout", "--detach", "FETCH_HEAD"]) |
| 28 | verify_checkout(path, revision) |
| 29 | |
| 30 | |
| 31 | def main(): |
| 32 | parser = argparse.ArgumentParser(description=__doc__) |
| 33 | parser.add_argument("--check", action="store_true", help="Verify native source pins, Rust and libclang without downloading") |
| 34 | args = parser.parse_args() |
| 35 | if platform.system() != "Linux" or platform.machine() != "x86_64": |
| 36 | raise SystemExit("This setup helper supports Linux x86_64. See firmware/README.md for toolchain details.") |
| 37 | required = ["git", "cmake", "make", "gcc", "g++", "pkg-config", "rustup", "cargo"] |
| 38 | missing = [command for command in required if not shutil.which(command)] |
| 39 | if missing: |
| 40 | raise SystemExit("Install these prerequisites first: " + ", ".join(missing)) |
| 41 | sdk = sdk_root() |
| 42 | if args.check: |
| 43 | verify_checkout(sdk / "esp-idf", IDF_REVISION) |
| 44 | verify_checkout(VENDOR / "esp-dsp", DSP_REVISION) |
| 45 | verify_checkout(VENDOR / "esp-adf-libs", CODEC_REVISION) |
| 46 | idf_environment() |
| 47 | version = subprocess.check_output(["rustup", "run", RUST_TOOLCHAIN, "rustc", "--version"], text=True) |
| 48 | if f"({RUST_RELEASE})" not in version: |
| 49 | raise SystemExit("Rust compiler differs from the pinned release") |
| 50 | print("Pinned ESP-IDF, DSP, codecs, and Xtensa Rust toolchains are ready.") |
| 51 | return |
| 52 | checkout(VENDOR / "esp-dsp", "https://github.com/espressif/esp-dsp.git", DSP_REVISION) |
| 53 | checkout(VENDOR / "esp-adf-libs", "https://github.com/espressif/esp-adf-libs.git", CODEC_REVISION) |
| 54 | checkout(sdk / "esp-idf", "https://github.com/espressif/esp-idf.git", IDF_REVISION) |
| 55 | run(["git", "-C", sdk / "esp-idf", "submodule", "update", "--init", "--recursive", "--depth", "1"]) |
| 56 | version = subprocess.run(["rustup", "run", RUST_TOOLCHAIN, "rustc", "--version"], capture_output=True, text=True) |
| 57 | try: |
| 58 | rust_libclang() |
| 59 | clang_ready = True |
| 60 | except SystemExit: |
| 61 | clang_ready = False |
| 62 | if version.returncode or f"({RUST_RELEASE})" not in version.stdout or not clang_ready: |
| 63 | espup = ROOT / ".tools/espup" |
| 64 | espup.parent.mkdir(exist_ok=True) |
| 65 | if not espup.exists() or hashlib.sha256(espup.read_bytes()).hexdigest() != ESPUP_SHA256: |
| 66 | url = f"https://github.com/esp-rs/espup/releases/download/v{ESPUP_VERSION}/espup-x86_64-unknown-linux-gnu" |
| 67 | with urllib.request.urlopen(url, timeout=60) as response: |
| 68 | binary = response.read() |
| 69 | if hashlib.sha256(binary).hexdigest() != ESPUP_SHA256: |
| 70 | raise SystemExit("espup download checksum mismatch") |
| 71 | espup.write_bytes(binary) |
| 72 | espup.chmod(0o755) |
| 73 | # --std installs Rust and Xtensa libclang; ESP-IDF supplies GCC. |
| 74 | run([espup, "install", "--std", "--targets", "esp32s3", "--toolchain-version", RUST_RELEASE, |
| 75 | "--name", RUST_TOOLCHAIN, "--export-file", ROOT / ".tools/export-esp-radio.sh"]) |
| 76 | rust_libclang() |
| 77 | try: |
| 78 | idf_environment() |
| 79 | except SystemExit: |
| 80 | env = os.environ.copy() |
| 81 | env.update(IDF_TOOLS_PATH=str(sdk / "tools"), PIP_CACHE_DIR=str(sdk / "pip-cache")) |
| 82 | run(["bash", sdk / "esp-idf/install.sh", "esp32s3"], env=env) |
| 83 | esptool = ROOT / ".tools/esptool/bin/python" |
| 84 | if not esptool.exists(): |
| 85 | run([sys.executable, "-m", "venv", esptool.parent.parent]) |
| 86 | run([esptool, "-m", "pip", "install", "esptool==5.4.0", "pyserial==3.5"]) |
| 87 | run(["cargo", "fetch", "--locked", "--manifest-path", ROOT / "firmware/Cargo.toml"]) |
| 88 | print("Firmware tooling ready. Existing Rust defaults and shell profiles were preserved.") |
| 89 | |
| 90 | |
| 91 | if __name__ == "__main__": |
| 92 | main() |