Files
esphome/esphome/build_gen/build_tool.py
T
J. Nick Koston 6bdec7e907 Warn only for source-like suffix drops, batch ar argv, name non-iterable dependencies
The unmapped-suffix warning fired for every header-only library (the
default +<*> filter matches headers), so ArduinoJson would have warned
on every ESP8266 build. It now names exactly the source-like files
(.CPP, .ino and case-variants of the map) the case-sensitive suffix map
rejects, partial drops included, and stays quiet for headers and
metadata.

The ar shim batches the expanded object list by argv length (rc then q
appends), keeping the command line under the Windows 32767-char limit
the rspfile existed to avoid. A non-iterable dependencies value in a
manifest now warns by library name instead of raising a bare
TypeError.
2026-08-22 12:26:24 -05:00

70 lines
2.6 KiB
Python

"""Tiny cross-platform build steps invoked from the generated ninja file.
Plain script (not ``python -m``): it runs from ninja with whatever Python
started esphome and must not depend on the package being importable.
Subcommands:
ar <ar-binary> <archive> <rspfile> remove stale archive, then ``ar rc``
copy <src> <dst> copy a file
The ar rspfile carries one object path per line (the generating rule must
use ``$in_newline``, never ``$in``).
"""
from pathlib import Path
import shutil
import subprocess
import sys
def main() -> int:
mode = sys.argv[1]
if mode == "ar":
ar, archive, rspfile = sys.argv[2:5]
# Remove first: ``ar rc`` replaces members but never drops ones whose
# source was removed from the build, which would leak stale objects.
Path(archive).unlink(missing_ok=True)
# GNU ar treats backslashes in response files as escapes (corrupts
# Windows paths), so expand the rspfile into argv, stripping the
# simple surrounding quote ninja adds to special paths.
# After stripping the outer pair, undo ninja's POSIX escape for an
# embedded quote ('a'\''b.o' -> a'b.o)
objects = [
line[1:-1].replace("'\\''", "'")
if len(line) >= 2 and line[0] == line[-1] and line[0] in "'\""
else line
for line in Path(rspfile).read_text(encoding="utf-8").splitlines()
if line
]
if not objects:
# An empty archive would "succeed" here and fail far away at link
print(f"ar: no objects listed in {rspfile} for {archive}", file=sys.stderr)
return 1
# Batch by argv length: expanding the rspfile gives back the Windows
# 32767-char command-line limit it existed to avoid. "rc" creates,
# "q" appends the remainder.
op = "rc"
while objects:
batch = [objects.pop(0)]
batch_len = len(batch[0])
while objects and batch_len + len(objects[0]) < 25000:
batch_len += len(objects[0]) + 1
batch.append(objects.pop(0))
rc = subprocess.run(
[ar, op, archive, *batch], check=False, close_fds=False
).returncode
if rc != 0:
return rc
op = "q"
return 0
if mode == "copy":
src, dst = sys.argv[2:4]
shutil.copyfile(src, dst)
return 0
print(f"unknown build_tool mode: {mode}", file=sys.stderr)
return 1
if __name__ == "__main__": # pragma: no cover
sys.exit(main())