diff --git a/scripts/update-source-file-header.py b/scripts/update-source-file-header.py index 80b896a8..eb226f90 100644 --- a/scripts/update-source-file-header.py +++ b/scripts/update-source-file-header.py @@ -1,96 +1,150 @@ #!/usr/bin/env python3 +# MobileGL - scripts/update-source-file-header.py +# Copyright (c) 2025-2026 MobileGL-Dev +# Licensed under the GNU Lesser General Public License v3.0: +# https://www.gnu.org/licenses/gpl-3.0.txt +# https://www.gnu.org/licenses/lgpl-3.0.txt +# SPDX-License-Identifier: LGPL-3.0-only +# End of Source File Header +"""Add the license header to source files that lack it. + + python3 scripts/update-source-file-header.py # every tracked source file it covers + python3 scripts/update-source-file-header.py FILE ... # only these files + python3 scripts/update-source-file-header.py --check # list what needs attention, exit 1 if anything does + +Covered under MobileGL/, scripts/ and tools/: C, C++ and .inc files with // comments and Python +scripts with # comments. +An existing header is kept as written, since its copyright years belong to the file; only a first +line that names a different path is corrected. Generated files and files under another license are +left alone. Files with mixed line endings are reported, never rewritten. +""" + import os +import re +import subprocess import sys -import pathlib -from glob import glob -try: - import colorama - colorama.init() - CYAN = colorama.Fore.CYAN + colorama.Style.BRIGHT - GREEN = colorama.Fore.GREEN - YELLOW = colorama.Fore.YELLOW - RED = colorama.Fore.RED - RESET = colorama.Style.RESET_ALL -except ImportError: - CYAN = GREEN = YELLOW = RED = RESET = '' - -OK = "✔" -FAIL = "✖" -ARROW = "╰─>" -SIMPLE_ARROW = "─>" - -HEADER_TEMPLATE = """// MobileGL - {file_path} -// Copyright (c) 2025-2026 MobileGL-Dev -// Licensed under the GNU Lesser General Public License v3.0: -// https://www.gnu.org/licenses/gpl-3.0.txt -// https://www.gnu.org/licenses/lgpl-3.0.txt -// SPDX-License-Identifier: LGPL-3.0-only -// End of Source File Header +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +MARKER = "End of Source File Header" +HEADER = """{c} MobileGL - {path} +{c} Copyright (c) 2025-2026 MobileGL-Dev +{c} Licensed under the GNU Lesser General Public License v3.0: +{c} https://www.gnu.org/licenses/gpl-3.0.txt +{c} https://www.gnu.org/licenses/lgpl-3.0.txt +{c} SPDX-License-Identifier: LGPL-3.0-only +{c} End of Source File Header """ +OWN_SPDX = "SPDX-License-Identifier: LGPL-3.0-only" +DIRS = ("MobileGL/", "scripts/", "tools/") +SLASH_EXT = (".h", ".hpp", ".cpp", ".cc", ".c", ".inc") +HASH_EXT = (".py",) +FOREIGN_LICENSE = re.compile(r"Apache License|Permission is hereby granted|MIT License|BSD License|" + r"Mozilla Public License|zlib License") +SPDX_RE = re.compile(r"SPDX-License-Identifier:\s*(\S+)") +COPYRIGHT_RE = re.compile(r"Copyright\b.*") +# Built against apitrace's glws/glproc interfaces; whether they are adapted from apitrace's own +# sources is not settled, so they carry no MobileGL header until it is. +EXEMPT = ("tools/trace_replay/apitrace_",) -HEADER_MARKER = """End of Source File Header -""" +# plan() statuses: the first three need no action, the last four do. +QUIET = ("ok", "foreign", "empty") +FIXABLE = ("add", "fix-path") +MANUAL = ("mixed-eol", "bad-header") -SCRIPT_DIR = pathlib.Path(__file__).parent.resolve() -TARGET_DIR = SCRIPT_DIR / "../MobileGL" -FILE_PATTERNS = ["*.h", "*.hpp", "*.cpp", "*.cc", "*.c"] -if not TARGET_DIR.exists(): - print(f"{YELLOW}Target directory '{TARGET_DIR}' does not exist.{RESET}") - sys.exit(1) +def is_generated(path): + return "/generated/" in path or "/gen/" in path or os.path.basename(path).startswith("generated_") -def find_source_files(): - candidates = [] - for root, dirs, files in os.walk(TARGET_DIR): - dirs[:] = [d for d in dirs if d not in ("build", "out")] - for pattern in FILE_PATTERNS: - for f in glob(os.path.join(root, pattern)): - candidates.append(f) - return candidates -candidates = find_source_files() -total = len(candidates) -print(f"{YELLOW}Found {total} candidate(s).{RESET}") +def comment_prefix(path): + if path.endswith(HASH_EXT): + return "#" + if path.endswith(SLASH_EXT): + return "//" + return None -if total == 0: - sys.exit(0) -def draw_progress(current, total, width=36): - percent = int(current * 100 / total) if total else 0 - filled = int(current * width / total) if total else 0 - empty = width - filled - bar = "█" * filled + "░" * empty - print(f"\r{CYAN}Progress{RESET} [{current}/{total}] {percent:3d}% |{bar}|", end='') +def covered(path): + return (path.startswith(DIRS) and comment_prefix(path) is not None + and not is_generated(path) and not path.startswith(EXEMPT)) -processed = 0 -skipped = 0 -for idx, file_path in enumerate(candidates, start=1): - draw_progress(idx, total) - try: - with open(file_path, "r", encoding="utf-8", errors="ignore") as f: - content = f.read() - rel_path = os.path.relpath(file_path, SCRIPT_DIR.parent) - new_header = HEADER_TEMPLATE.format(file_path=rel_path.replace("\\", "/")) +def plan(path, text): + """Return (status, new_text). new_text is set only for the FIXABLE statuses.""" + if not text.strip(): + return "empty", None + bom = "" if text.startswith("") else "" + text = text[len(bom):] + if "\r\n" in text and re.search(r"(?