Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at devShellTools-shell 121 lines 4.1 kB view raw
1from argparse import ArgumentParser 2from dataclasses import dataclass 3from functools import cached_property 4import json 5from pathlib import Path 6import shutil 7 8from libfdt import Fdt, FdtException, FDT_ERR_NOSPACE, FDT_ERR_NOTFOUND, fdt_overlay_apply 9 10 11@dataclass 12class Overlay: 13 name: str 14 filter: str 15 dtbo_file: Path 16 17 @cached_property 18 def fdt(self) -> Fdt: 19 with self.dtbo_file.open("rb") as fd: 20 return Fdt(fd.read()) 21 22 @cached_property 23 def compatible(self) -> set[str]: 24 return get_compatible(self.fdt) 25 26 27def get_compatible(fdt) -> set[str]: 28 root_offset = fdt.path_offset("/") 29 30 try: 31 return set(fdt.getprop(root_offset, "compatible").as_stringlist()) 32 except FdtException as e: 33 if e.err == -FDT_ERR_NOTFOUND: 34 return set() 35 else: 36 raise e 37 38 39def apply_overlay(dt: Fdt, dto: Fdt) -> Fdt: 40 while True: 41 # we need to copy the buffers because they can be left in an inconsistent state 42 # if the operation fails (ref: fdtoverlay source) 43 result = dt.as_bytearray().copy() 44 err = fdt_overlay_apply(result, dto.as_bytearray().copy()) 45 46 if err == 0: 47 new_dt = Fdt(result) 48 # trim the extra space from the final tree 49 new_dt.pack() 50 return new_dt 51 52 if err == -FDT_ERR_NOSPACE: 53 # not enough space, add some blank space and try again 54 # magic number of more space taken from fdtoverlay 55 dt.resize(dt.totalsize() + 65536) 56 continue 57 58 raise FdtException(err) 59 60def process_dtb(rel_path: Path, source: Path, destination: Path, overlays_data: list[Overlay]): 61 source_dt = source / rel_path 62 print(f"Processing source device tree {rel_path}...") 63 with source_dt.open("rb") as fd: 64 dt = Fdt(fd.read()) 65 66 for overlay in overlays_data: 67 if overlay.filter and overlay.filter not in str(rel_path): 68 print(f" Skipping overlay {overlay.name}: filter does not match") 69 continue 70 71 dt_compatible = get_compatible(dt) 72 if len(dt_compatible) == 0: 73 print(f" Device tree {rel_path} has no compatible string set. Assuming it's compatible with overlay") 74 elif not overlay.compatible.intersection(dt_compatible): 75 print(f" Skipping overlay {overlay.name}: {overlay.compatible} is incompatible with {dt_compatible}") 76 continue 77 78 print(f" Applying overlay {overlay.name}") 79 dt = apply_overlay(dt, overlay.fdt) 80 81 print(f"Saving final device tree {rel_path}...") 82 dest_path = destination / rel_path 83 dest_path.parent.mkdir(parents=True, exist_ok=True) 84 with dest_path.open("wb") as fd: 85 fd.write(dt.as_bytearray()) 86 87def main(): 88 parser = ArgumentParser(description='Apply a list of overlays to a directory of device trees') 89 parser.add_argument("--source", type=Path, help="Source directory") 90 parser.add_argument("--destination", type=Path, help="Destination directory") 91 parser.add_argument("--overlays", type=Path, help="JSON file with overlay descriptions") 92 args = parser.parse_args() 93 94 source: Path = args.source 95 destination: Path = args.destination 96 overlays: Path = args.overlays 97 98 with overlays.open() as fd: 99 overlays_data = [ 100 Overlay( 101 name=item["name"], 102 filter=item["filter"], 103 dtbo_file=Path(item["dtboFile"]), 104 ) 105 for item in json.load(fd) 106 ] 107 108 for dirpath, dirnames, filenames in source.walk(): 109 for filename in filenames: 110 rel_path = (dirpath / filename).relative_to(source) 111 if filename.endswith(".dtb"): 112 process_dtb(rel_path, source, destination, overlays_data) 113 else: 114 # Copy other files through 115 dest_path = destination / rel_path 116 dest_path.parent.mkdir(parents=True, exist_ok=True) 117 shutil.copy(source / rel_path, dest_path) 118 119 120if __name__ == '__main__': 121 main()