This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||||
EXPECTED_PLATFORMIO_CORE = "6.1.18"
|
||||
EXPECTED_PLATFORM = "https://github.com/pioarduino/platform-espressif32/releases/download/51.03.07/platform-espressif32.zip"
|
||||
EXPECTED_ENVS = {"production", "debug", "test"}
|
||||
EXPECTED_BOARD = "ttgo-lora32-v1"
|
||||
EXPECTED_FRAMEWORK = "arduino"
|
||||
PROPOSED_EXACT_PINS = {
|
||||
"sandeepmistry/LoRa": "0.8.0",
|
||||
"bblanchon/ArduinoJson": "6.21.6",
|
||||
"adafruit/Adafruit SSD1306": "2.5.17",
|
||||
"adafruit/Adafruit GFX Library": "1.12.6",
|
||||
"knolleary/PubSubClient": "2.8.0",
|
||||
"throwtheswitch/Unity": "2.6.1",
|
||||
}
|
||||
|
||||
|
||||
def command_text(command: list[str]) -> str:
|
||||
return " ".join(f'"{part}"' if any(ch.isspace() for ch in part) else part for part in command)
|
||||
|
||||
|
||||
def run(command: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
env = os.environ.copy()
|
||||
env["PYTHONIOENCODING"] = "utf-8"
|
||||
env["PYTHONUTF8"] = "1"
|
||||
env["PIO_FORCE_COLOR"] = "0"
|
||||
print(f"$ {command_text(command)}", flush=True)
|
||||
result = subprocess.run(
|
||||
command,
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
print(result.stdout, end="")
|
||||
return result
|
||||
|
||||
|
||||
def split_multiline(value: str) -> list[str]:
|
||||
return [line.strip() for line in value.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def parse_platformio() -> configparser.RawConfigParser:
|
||||
parser = configparser.RawConfigParser()
|
||||
parser.optionxform = str
|
||||
with (ROOT / "platformio.ini").open(encoding="utf-8") as handle:
|
||||
parser.read_file(handle)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
|
||||
version_result = run([sys.executable, "-m", "platformio", "--version"])
|
||||
if version_result.returncode != 0:
|
||||
errors.append("PlatformIO version command failed.")
|
||||
else:
|
||||
match = re.search(r"version\s+([0-9.]+)", version_result.stdout)
|
||||
observed = match.group(1) if match else "unknown"
|
||||
if observed != EXPECTED_PLATFORMIO_CORE:
|
||||
errors.append(f"Expected PlatformIO Core {EXPECTED_PLATFORMIO_CORE}, observed {observed}.")
|
||||
|
||||
config_result = run([sys.executable, "-m", "platformio", "project", "config", "--json-output"])
|
||||
if config_result.returncode != 0:
|
||||
errors.append("PlatformIO project config command failed.")
|
||||
|
||||
pkg_result = run([sys.executable, "-m", "platformio", "pkg", "list"])
|
||||
if pkg_result.returncode != 0:
|
||||
errors.append("PlatformIO package resolution failed.")
|
||||
|
||||
parser = parse_platformio()
|
||||
env_sections = {section.split(":", 1)[1] for section in parser.sections() if section.startswith("env:")}
|
||||
missing = EXPECTED_ENVS - env_sections
|
||||
if missing:
|
||||
errors.append(f"Missing required PlatformIO environments: {', '.join(sorted(missing))}")
|
||||
|
||||
base_platform = parser.get("env", "platform", fallback="")
|
||||
if base_platform != EXPECTED_PLATFORM:
|
||||
errors.append("Base PlatformIO platform is not the expected pinned pioarduino release URL.")
|
||||
|
||||
base_board = parser.get("env", "board", fallback="")
|
||||
base_framework = parser.get("env", "framework", fallback="")
|
||||
if base_board != EXPECTED_BOARD:
|
||||
errors.append(f"Expected board {EXPECTED_BOARD}, observed {base_board}.")
|
||||
if base_framework != EXPECTED_FRAMEWORK:
|
||||
errors.append(f"Expected framework {EXPECTED_FRAMEWORK}, observed {base_framework}.")
|
||||
|
||||
lib_deps = split_multiline(parser.get("env", "lib_deps", fallback=""))
|
||||
for dep in lib_deps:
|
||||
if "@" not in dep:
|
||||
errors.append(f"Dependency lacks an explicit version requirement: {dep}")
|
||||
elif "@^" in dep:
|
||||
warnings.append(f"Ranged dependency allowed for now, exact pin proposed separately: {dep}")
|
||||
|
||||
for env_name in EXPECTED_ENVS:
|
||||
section = f"env:{env_name}"
|
||||
if not parser.has_section(section):
|
||||
continue
|
||||
if parser.get(section, "platform", fallback=base_platform) != EXPECTED_PLATFORM:
|
||||
errors.append(f"{section} platform differs from the pinned base platform.")
|
||||
if parser.get(section, "board", fallback=base_board) != EXPECTED_BOARD:
|
||||
errors.append(f"{section} board differs from {EXPECTED_BOARD}.")
|
||||
if parser.get(section, "framework", fallback=base_framework) != EXPECTED_FRAMEWORK:
|
||||
errors.append(f"{section} framework differs from Arduino.")
|
||||
|
||||
print("Proposed exact direct dependency pins for a separate commit:")
|
||||
for package, version in PROPOSED_EXACT_PINS.items():
|
||||
print(f"- {package}@{version}")
|
||||
|
||||
for warning in warnings:
|
||||
print(f"WARNING: {warning}")
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user