From 5bee7eee6fcae0087c77eacf70737c0bf6fd6d31 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Thu, 4 Sep 2025 00:06:58 -0500 Subject: [PATCH] sanity_checks: require LICENSE.build if patch dir has license blocks Check the patch directories of ports for Meson- or Python-style header comment blocks that reference a license other than MIT. If found, require the port to include a LICENSE.build file describing the port's license, since otherwise create_release.py will add a default MIT LICENSE.build to the patch ZIP. --- tools/sanity_checks.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/sanity_checks.py b/tools/sanity_checks.py index 19bec3cf3..0567a9547 100755 --- a/tools/sanity_checks.py +++ b/tools/sanity_checks.py @@ -157,6 +157,7 @@ 'icu': 'icu4c', 'libtomcrypt': 'crypt', } +MIT_LICENSE_BLOCKS = {'expat', 'freeglut', 'glew', 'google-brotli'} FORMAT_CHECK_FILES = {'meson.build', 'meson_options.txt', 'meson.options'} SUBPROJECTS_METADATA_FILES = {'subprojects/.gitignore'} PERMITTED_KEYS = {'versions', 'dependency_names', 'program_names'} @@ -637,6 +638,7 @@ def get_default_options(self, project: dict[str, T.Any]) -> dict[str, str | None def check_files(self, subproject: str, patch_path: Path) -> None: not_permitted: list[Path] = [] check_format: list[Path] = [] + license_blocks: list[Path] = [] for f in patch_path.rglob('*'): if f.is_dir(): continue @@ -644,6 +646,8 @@ def check_files(self, subproject: str, patch_path: Path) -> None: check_format.append(f) if not self.is_permitted_file(subproject, f.name): not_permitted.append(f) + if self.has_license_block(f): + license_blocks.append(f) if not_permitted: not_permitted_str = ', '.join([str(f) for f in not_permitted]) self.fail(f'Not permitted files found: {not_permitted_str}') @@ -651,6 +655,23 @@ def check_files(self, subproject: str, patch_path: Path) -> None: format_meson(check_format, check=True) except FormattingError: self.fail('Unformatted files found. Run tools/format.py to format these files.') + if license_blocks and subproject not in MIT_LICENSE_BLOCKS and not (patch_path / 'LICENSE.build').exists(): + license_blocks_str = ', '.join(str(f) for f in license_blocks) + self.fail(f"Found files {license_blocks_str} with license headers in a project without a LICENSE.build. The LICENSE.build file in the patch ZIP defaults to MIT unless the patch directory has its own LICENSE.build, which should state the license for the wrap's build files.") + + def has_license_block(self, path: Path) -> bool: + for line in path.read_text(encoding='utf-8').splitlines(): + lower = line.strip().lower() + if lower and not lower.startswith('#'): + # first non-comment line + return False + if 'spdx-license-identifier:' in lower: + # allow pure MIT, matching the repo default + if not lower.endswith('spdx-license-identifier: mit'): + return True + elif 'license' in lower: + return True + return False @unittest.skipUnless('TEST_MESON_VERSION_DEPS' in os.environ, 'Run manually only') def test_meson_version_deps(self) -> None: