| """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") |
| |
| |
| 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) |
|
|