blob: 7dececab1f04a537e6d8864b954eebfe7df9ad62 [file] [edit]
"""Generates Starlark source lists for Gnulib license tiers using pygnulib."""
import argparse
import pathlib
import sys
from pygnulib import constants
from pygnulib.GLConfig import GLConfig
from pygnulib.GLModuleSystem import GLModule, GLModuleSystem
sys.dont_write_bytecode = True
ALL_COMPATIBLE_LICENSES = frozenset({
"GPLv2+ build tool",
"GPLed build tool",
"public domain",
"unlimited",
"unmodifiable license text",
})
LGPL2_COMPATIBLE_LICENSES = ALL_COMPATIBLE_LICENSES | frozenset({
"LGPLv2+",
})
LGPL3_COMPATIBLE_LICENSES = ALL_COMPATIBLE_LICENSES | frozenset({
"LGPLv2+",
"LGPLv3+ or GPLv2+",
"LGPLv3+",
"LGPL",
})
INFRASTRUCTURE_PREFIXES = (
"pygnulib/",
"build-aux/",
"top/",
"config/",
)
INFRASTRUCTURE_EXACT_FILES = frozenset({
"gnulib-tool",
"gnulib-tool.py",
".gnulib-tool.py",
"gnulib-tool.sh",
"m4/gnulib-tool.m4",
"doc/INSTALL",
"doc/COPYING.LESSERv2",
"doc/COPYING.LESSERv3",
"doc/COPYINGv2",
"doc/COPYINGv3",
})
def _find_gnulib_dir(inputs: list[pathlib.Path]) -> pathlib.Path:
matches = [input_path for input_path in inputs if input_path.name == "gnulib-tool.py"]
if len(matches) != 1:
raise ValueError(
f"Expected exactly 1 'gnulib-tool.py' among inputs, found {len(matches)}: {matches}"
)
return matches[0].parent
def _format_starlark_tuple(name: str, items: list[str]) -> str:
lines = [f"{name} = ("]
for item in items:
lines.append(f' "{item}",')
lines.append(")")
return "\n".join(lines)
def generate_gnulib_srcs(inputs: list[pathlib.Path], output: pathlib.Path) -> None:
gnulib_dir = _find_gnulib_dir(inputs)
constants.init_DIRS(str(gnulib_dir))
existing_files: set[str] = set()
for input_path in inputs:
relative_path = input_path.relative_to(gnulib_dir).as_posix()
existing_files.add(relative_path)
config = GLConfig()
module_system = GLModuleSystem(config)
non_test_modules: list[GLModule] = []
for relative_path in sorted(existing_files):
if not relative_path.startswith("modules/"):
continue
module_name = relative_path.removeprefix("modules/")
if not module_system.file_is_module(module_name):
continue
module = module_system.find(module_name)
if module is not None and module.isNonTests():
non_test_modules.append(module)
all_module_files: set[str] = set()
for module in non_test_modules:
all_module_files.update(module.getFiles())
infrastructure_files = {
relative_path
for relative_path in existing_files - all_module_files
if relative_path.startswith(INFRASTRUCTURE_PREFIXES)
or relative_path in INFRASTRUCTURE_EXACT_FILES
}
def collect_tier_files(tier_name: str, allowed_licenses: frozenset[str]) -> list[str]:
allowed_modules = [
module for module in non_test_modules if module.getLicense() in allowed_licenses
]
allowed_module_set = set(allowed_modules)
for module in allowed_modules:
for dependency in module.getDependenciesWithoutConditions():
if dependency not in allowed_module_set:
raise ValueError(
f"Module '{module.name}' (license: '{module.getLicense()}') in {tier_name} "
f"depends on '{dependency.name}' (license: '{dependency.getLicense()}'), "
f"which is outside the allowed module set for {tier_name}."
)
tier_files = set(infrastructure_files)
for module in allowed_modules:
module_files = {f"modules/{module.name}", *module.getFiles()}
tier_files |= module_files & existing_files
return sorted(tier_files)
lgpl2_srcs = collect_tier_files("LGPL2_SRCS", LGPL2_COMPATIBLE_LICENSES)
lgpl3_srcs = collect_tier_files("LGPL3_SRCS", LGPL3_COMPATIBLE_LICENSES)
header = "\n".join([
"# Generated by //third_party/gnulib:write_gnulib_srcs. DO NOT EDIT.",
"# Regenerate with: bazel run //third_party/gnulib:write_gnulib_srcs",
'"""Generated Gnulib license-scoped source file lists."""',
])
content = "\n\n".join([
header,
_format_starlark_tuple("LGPL2_SRCS", lgpl2_srcs),
_format_starlark_tuple("LGPL3_SRCS", lgpl3_srcs),
]) + "\n"
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(content, encoding="utf-8")
def main() -> None:
parser = argparse.ArgumentParser(
description="Generate Starlark source lists for Gnulib license tiers.",
fromfile_prefix_chars="@",
)
parser.add_argument(
"--output",
"-o",
required=True,
type=pathlib.Path,
help="Output .bzl file path",
)
parser.add_argument(
"inputs",
nargs="*",
type=pathlib.Path,
help="Input files from the Gnulib repository",
)
args = parser.parse_args()
generate_gnulib_srcs(args.inputs, args.output)
if __name__ == "__main__":
main()