Files
codex/.github/scripts/voice_windows_tools.py
2026-09-10 20:17:17 -07:00

81 lines
3.4 KiB
Python

"""Expose verified Windows build-tool installations as local Bazel inputs.
This declares the installed support trees; the caller authenticates their
inputs and verifies the installed package inventory first.
"""
import argparse
import itertools
import json
from pathlib import Path
import stat
def export_repository(root: Path, target: str, pkg_config: Path):
if target not in ("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc"):
raise ValueError("unsupported Windows target")
root = root.resolve(strict=True)
pkg_config = pkg_config.resolve(strict=True)
names = {
"shell": "cygwin/bin/bash.exe",
"make": "cygwin/bin/make.exe",
"cygpath": "cygwin/bin/cygpath.exe",
"automake": "cygwin/bin/automake-1.18",
"pkg_config": pkg_config.relative_to(root).as_posix(),
}
if not names["pkg_config"].startswith("pkgconf-image/"):
raise ValueError("pkg-config must belong to the extracted image")
outputs = ("BUILD.bazel", "MODULE.bazel", "voice-tools.json")
if any((root / name).exists() for name in outputs):
raise ValueError("Bazel tool repository outputs must be fresh")
# A nested package boundary would silently remove files from Bazel's glob.
# Reparse points could escape the installation or hide support directories.
for directory in ("cygwin", "pkgconf-image"):
tree = root / directory
if not tree.is_dir():
raise ValueError(f"missing tool tree: {directory}")
for path in itertools.chain((tree,), tree.rglob("*")):
info = path.lstat()
if (
stat.S_ISLNK(info.st_mode)
or getattr(info, "st_file_attributes", 0)
& stat.FILE_ATTRIBUTE_REPARSE_POINT
or not (stat.S_ISDIR(info.st_mode) or stat.S_ISREG(info.st_mode))
or path.name.casefold() in ("build", "build.bazel")
):
raise ValueError(f"unsupported tool-tree entry: {path}")
if any(not (root / name).is_file() for name in names.values()):
raise ValueError("required Windows build tool missing")
definitions = [
'package(default_visibility = ["//visibility:public"])',
'filegroup(name = "cygwin", srcs = glob(["cygwin/**"], allow_empty = False))',
'filegroup(name = "pkgconf", srcs = glob(["pkgconf-image/**"], allow_empty = False))',
'filegroup(name = "tools", srcs = [":cygwin", ":pkgconf", "voice-tools.json"])',
]
definitions.extend(
f"filegroup(name = {json.dumps(name)}, srcs = [{json.dumps(path)}])"
for name, path in names.items()
)
metadata = {
"schemaVersion": 1,
"target": target,
"cygwinArchitecture": "x86_64",
"tools": names,
}
for name, contents in (
("BUILD.bazel", "\n".join(definitions) + "\n"),
("MODULE.bazel", 'module(name = "voice_windows_tools")\n'),
("voice-tools.json", json.dumps(metadata, indent=2) + "\n"),
):
with (root / name).open("x", encoding="utf-8") as output:
output.write(contents)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--target", required=True)
parser.add_argument("--pkg-config", type=Path, required=True)
args = parser.parse_args()
export_repository(args.root, args.target, args.pkg_config)