"""Prepare the known-working lwIP component layout for ESP-IDF v6.0.2.""" import filecmp from pathlib import Path import shutil import sys def replace_once(text, old, new, label): if text.count(old) != 1: raise RuntimeError(f"Unexpected {label}; expected ESP-IDF v6.0.2 sources") return text.replace(old, new, 1) def write_changed(path, data): path.parent.mkdir(parents=True, exist_ok=True) if not path.exists() or path.read_bytes() != data: path.write_bytes(data) def prepare(source, destination, arp): source, destination, arp = source.resolve(), destination.resolve(), arp.resolve() if not arp.is_file(): raise RuntimeError(f"Missing modified ARP source: {arp}") if source == destination or source in destination.parents or destination in source.parents: raise RuntimeError("Source and generated directories must be separate") if any(c in arp.as_posix() for c in ('"', ';', '$')): raise RuntimeError("ARP path contains characters that require CMake escaping") cmake = (source / "CMakeLists.txt").read_text() cmake = replace_once(cmake, '"lwip/src/core/ipv4/etharp.c"', f'"{arp.as_posix()}"', "lwIP CMakeLists.txt") cmake = replace_once(cmake, 'if(NOT ${target} STREQUAL "linux")\n set(priv_requires vfs)\nendif()', 'set(priv_requires)\n\nif(NOT ${target} STREQUAL "linux")\n list(APPEND priv_requires vfs)\nendif()', "lwIP dependencies") sockets_path = "lwip/src/include/lwip/sockets.h" sockets = (source / sockets_path).read_text() if "#ifndef SIOCATMARK" not in sockets: line = "#define SIOCATMARK _IOR('s', 7, unsigned long) /* at oob mark? */" sockets = replace_once(sockets, line, "#endif\n#ifndef SIOCATMARK\n" + line, "SIOCATMARK definition") fcntl_path = "port/linux/include/sys/fcntl.h" fcntl = (source / fcntl_path).read_text() if "#define F_GETFD" not in fcntl: fcntl = fcntl.rstrip("\n") + "\n\n#ifndef F_GETFD\n#define F_GETFD 1\n#endif\n" changed = {"CMakeLists.txt": cmake.encode(), sockets_path: sockets.encode(), fcntl_path: fcntl.encode()} # Validate all substitutions before writing. Do not copy or compile stock ARP. destination.mkdir(parents=True, exist_ok=True) for path in source.rglob("*"): relative = path.relative_to(source) if ".git" in relative.parts or not path.is_file(): continue name = relative.as_posix() if name in changed or name == "lwip/src/core/ipv4/etharp.c": continue target = destination / relative target.parent.mkdir(parents=True, exist_ok=True) if not target.is_file() or not filecmp.cmp(path, target, shallow=False): shutil.copy2(path, target) for name, data in changed.items(): write_changed(destination / name, data) stock_arp = destination / "lwip/src/core/ipv4/etharp.c" if stock_arp.exists(): stock_arp.unlink() if __name__ == "__main__": try: if len(sys.argv) != 4: raise RuntimeError("Usage: prepare.py SDK_LWIP BUILD_LWIP PATCHED_ETHARP") prepare(*(Path(value) for value in sys.argv[1:])) except (OSError, RuntimeError) as error: sys.exit(str(error))