| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- """Split per-platform install trees into vendor/common/ + vendor/platform/<tag>/.
- Called by vendor_chattolib.sh. Each distribution is classified via its
- dist-info/WHEEL marker: ``Root-Is-Purelib: true`` means the wheel contains no
- compiled code and can be shared across platforms; everything else carries
- platform-specific binaries and goes into the per-platform directory.
- Files are moved individually (as listed in each dist-info/RECORD) rather than by
- top-level directory, so namespace packages shared by several distributions —
- ``google/`` today — end up in the right place.
- Usage: vendor_split.py <build_dir> <vendor_dir> <platform_tag>...
- """
- from __future__ import annotations
- import csv
- import filecmp
- import shutil
- import sys
- from pathlib import Path
- from typing import Dict, List, Set, Tuple
- def _is_pure(dist_info: Path) -> bool:
- wheel = dist_info / "WHEEL"
- if not wheel.is_file():
- # No marker: assume platform-specific, which is the safe direction —
- # the file gets duplicated per platform instead of being shared wrongly.
- return False
- for line in wheel.read_text(encoding="utf-8").splitlines():
- if line.lower().startswith("root-is-purelib:"):
- return line.split(":", 1)[1].strip().lower() == "true"
- return False
- def _record_files(dist_info: Path) -> List[str]:
- record = dist_info / "RECORD"
- if not record.is_file():
- return []
- with record.open(encoding="utf-8", newline="") as handle:
- return [row[0] for row in csv.reader(handle) if row and row[0]]
- def classify(tree: Path) -> Tuple[Set[str], Set[str]]:
- """Return (pure_files, binary_files) as paths relative to ``tree``."""
- pure: Set[str] = set()
- binary: Set[str] = set()
- for dist_info in sorted(tree.glob("*.dist-info")):
- target = pure if _is_pure(dist_info) else binary
- target.update(_record_files(dist_info))
- target.add(f"{dist_info.name}/")
- return pure, binary
- def copy_files(tree: Path, names: Set[str], dest: Path) -> int:
- copied = 0
- for name in sorted(names):
- src = tree / name
- if name.endswith("/"):
- if src.is_dir():
- shutil.copytree(src, dest / name, dirs_exist_ok=True)
- copied += 1
- continue
- if not src.is_file():
- continue # RECORD lists entries that --target does not create
- out = dest / name
- out.parent.mkdir(parents=True, exist_ok=True)
- shutil.copy2(src, out)
- copied += 1
- return copied
- def main(argv: List[str]) -> int:
- if len(argv) < 4:
- print(__doc__, file=sys.stderr)
- return 2
- build_dir, vendor_dir, tags = Path(argv[1]), Path(argv[2]), argv[3:]
- common_dir = vendor_dir / "common"
- reference: Dict[str, Path] = {}
- for tag in tags:
- tree = build_dir / tag
- if not tree.is_dir():
- print(f"❌ Build-Verzeichnis fehlt: {tree}", file=sys.stderr)
- return 1
- pure, binary = classify(tree)
- overlap = {p for p in pure & binary if not p.endswith("/")}
- if overlap:
- print(
- f"❌ {tag}: Dateien gehören zugleich zu einem puren und einem "
- f"binären Paket: {sorted(overlap)[:5]}",
- file=sys.stderr,
- )
- return 1
- n_bin = copy_files(tree, binary, vendor_dir / "platform" / tag)
- print(f" {tag}: {n_bin} plattformspezifische Dateien")
- if not reference:
- n_pure = copy_files(tree, pure, common_dir)
- reference = {"tag": tag, "files": pure} # type: ignore[dict-item]
- print(f" common: {n_pure} plattformunabhängige Dateien (aus {tag})")
- else:
- # The shared part must really be identical, otherwise common/ would
- # silently carry one platform's variant for all of them.
- ref_files: Set[str] = reference["files"] # type: ignore[assignment]
- if pure != ref_files:
- only_here = sorted(pure - ref_files)[:5]
- only_ref = sorted(ref_files - pure)[:5]
- print(
- f"⚠️ {tag}: pure Paketliste weicht von {reference['tag']} ab "
- f"(nur hier: {only_here}, nur dort: {only_ref})",
- file=sys.stderr,
- )
- mismatched = [
- name
- for name in sorted(pure & ref_files)
- if not name.endswith("/")
- and (tree / name).is_file()
- and (common_dir / name).is_file()
- and not filecmp.cmp(tree / name, common_dir / name, shallow=False)
- ]
- if mismatched:
- print(
- f"⚠️ {tag}: {len(mismatched)} gemeinsame Datei(en) unterscheiden "
- f"sich inhaltlich, z.B. {mismatched[:3]}",
- file=sys.stderr,
- )
- return 0
- if __name__ == "__main__":
- sys.exit(main(sys.argv))
|