diff --git a/esphome/__main__.py b/esphome/__main__.py index 93d222a850..75f06fb9fe 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -528,44 +528,37 @@ def compile_program(args: ArgsProtocol, config: ConfigType) -> int: def _check_and_emit_buildinfo(): """Check if firmware was rebuilt and emit buildinfo.""" + import json firmware_path = CORE.firmware_bin - buildinfo_ld_path = CORE.relative_build_path("buildinfo.ld") + buildinfo_json_path = CORE.relative_build_path("buildinfo.json") # Check if both files exist - if not firmware_path.exists() or not buildinfo_ld_path.exists(): + if not firmware_path.exists() or not buildinfo_json_path.exists(): return - # Check if firmware is newer than buildinfo linker script (indicating a relink occurred) - if firmware_path.stat().st_mtime <= buildinfo_ld_path.stat().st_mtime: + # Check if firmware is newer than buildinfo (indicating a relink occurred) + if firmware_path.stat().st_mtime <= buildinfo_json_path.stat().st_mtime: return - # Read buildinfo values from linker script + # Read buildinfo from JSON try: - with open(buildinfo_ld_path, encoding="utf-8") as f: - content = f.read() - except OSError as e: + with open(buildinfo_json_path, encoding="utf-8") as f: + buildinfo = json.load(f) + except (OSError, json.JSONDecodeError) as e: _LOGGER.debug("Failed to read buildinfo: %s", e) return - config_hash_match = re.search(r"ESPHOME_CONFIG_HASH = 0x([0-9a-fA-F]+);", content) - build_time_match = re.search(r"ESPHOME_BUILD_TIME = (\d+);", content) + config_hash = buildinfo.get("config_hash") + build_time = buildinfo.get("build_time") - if not config_hash_match or not build_time_match: + if config_hash is None or build_time is None: return - config_hash = config_hash_match.group(1) - build_time = int(build_time_match.group(1)) - # Emit buildinfo - print("=== ESPHome Build Info ===") - print(f"Config Hash: 0x{config_hash}") - print( - f"Build Time: {build_time} ({time.strftime('%Y-%m-%d %H:%M:%S %z', time.localtime(build_time))})" + _LOGGER.info( + "Build Info: config_hash=0x%08x build_time=%s", config_hash, build_time ) - print("===========================") - - # TODO: Future commit will create JSON manifest with OTA metadata here def upload_using_esptool( diff --git a/esphome/writer.py b/esphome/writer.py index c94fb8e304..798f921fd2 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -1,5 +1,6 @@ from collections.abc import Callable import importlib +import json import logging import os from pathlib import Path @@ -249,8 +250,16 @@ def copy_src_tree(): write_file_if_changed( CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h() ) - # Write buildinfo linker script and copy the PlatformIO script - write_file(CORE.relative_build_path("buildinfo.ld"), generate_buildinfo_ld()) + # Write buildinfo linker script, JSON metadata, and copy the PlatformIO script + config_hash, build_time, build_time_str = get_buildinfo() + write_file( + CORE.relative_build_path("buildinfo.ld"), + generate_buildinfo_ld(config_hash, build_time, build_time_str), + ) + write_file( + CORE.relative_build_path("buildinfo.json"), + json.dumps({"config_hash": config_hash, "build_time": build_time}), + ) copy_file_if_changed( Path(__file__).parent / "core" / "buildinfo.py.script", CORE.relative_build_path("buildinfo.py"), @@ -306,17 +315,27 @@ def _encode_string_symbols( return symbols -def generate_buildinfo_ld() -> str: - """Generate buildinfo linker script with config hash and build time.""" +def get_buildinfo() -> tuple[int, int, str]: + """Calculate buildinfo values from current config. + + Returns: + Tuple of (config_hash, build_time, build_time_str) + """ from esphome import yaml_util # Use the same clean YAML representation as 'esphome config' command config_str = yaml_util.dump(CORE.config, show_secrets=True) config_hash = fnv1a_32bit_hash(config_str) - config_hash_str = f"{config_hash:08x}" - build_time = int(time.time()) build_time_str = time.strftime("%b %d %Y, %H:%M:%S", time.localtime(build_time)) + return config_hash, build_time, build_time_str + + +def generate_buildinfo_ld( + config_hash: int, build_time: int, build_time_str: str +) -> str: + """Generate buildinfo linker script with config hash and build time.""" + config_hash_str = f"{config_hash:08x}" # Generate symbols for all 4 variants: 32LE, 32BE, 64LE, 64BE all_variants: list[str] = []