diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2022-01-08 14:57:38 +0330 |
---|---|---|
committer | Ali Mohammad Pur <Ali.mpfard@gmail.com> | 2022-01-16 10:32:50 +0330 |
commit | 8d30e14d28d8732760bfd471b46771a9d2ff2f48 (patch) | |
tree | 81695956cb9a5107a9d324e63f1398f14f011c0d /Meta/lint-ports.py | |
parent | fc02370dc75f8c40ae3d9960b44eaaf8634d7e8e (diff) | |
download | serenity-8d30e14d28d8732760bfd471b46771a9d2ff2f48.zip |
Meta: Ensure that all port patches are documented in the linter
This adds a list of ports without descriptions to filter out the
existing ports with unexplained ports, this list should *not* be
appended to!
It also adds a (for now) disabled check that ensures all ports have
patches made with (or compatible with) git.
Diffstat (limited to 'Meta/lint-ports.py')
-rwxr-xr-x | Meta/lint-ports.py | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/Meta/lint-ports.py b/Meta/lint-ports.py index 3f8c40ab24..e1aea73551 100755 --- a/Meta/lint-ports.py +++ b/Meta/lint-ports.py @@ -4,6 +4,8 @@ import os import re import sys import subprocess +from pathlib import Path +from tempfile import NamedTemporaryFile # Matches e.g. "| [`bash`](bash/) | GNU Bash | 5.0 | https://www.gnu.org/software/bash/ |" # and captures "bash" in group 1, "bash/" in group 2, "<spaces>" in group 3, "GNU Bash" in group 4, "5.0" in group 5 @@ -26,6 +28,114 @@ IGNORE_FILES = { '.hosted_defs.sh' } +# Matches port names in Ports/foo/ReadMe.md +PORT_NAME_REGEX = re.compile(r'([ .()[\]{}\w-]+)\.patch') +PORTS_MISSING_DESCRIPTIONS = { + 'Another-World', + 'binutils', + 'chester', + 'cmatrix', + 'c-ray', + 'curl', + 'dash', + 'diffutils', + 'dosbox-staging', + 'dropbear', + 'ed', + 'emu2', + 'epsilon', + 'figlet', + 'flex', + 'fontconfig', + 'freeciv', + 'freedink', + 'freetype', + 'gawk', + 'gcc', + 'gdb', + 'genemu', + 'gettext', + 'git', + 'gltron', + 'gmp', + 'gnucobol', + 'gnupg', + 'gnuplot', + 'gsl', + 'harfbuzz', + 'indent', + 'jq', + 'klong', + 'libassuan', + 'libgcrypt', + 'libgd', + 'libgpg-error', + 'libiconv', + 'libicu', + 'libjpeg', + 'libksba', + 'libmodplug', + 'liboggz', + 'libpng', + 'libpuffy', + 'libsodium', + 'libvorbis', + 'libzip', + 'lua', + 'm4', + 'make', + 'mandoc', + 'mbedtls', + 'milkytracker', + 'mrsh', + 'mruby', + 'nano', + 'ncurses', + 'neofetch', + 'nethack', + 'ninja', + 'npiet', + 'npth', + 'ntbtls', + 'nyancat', + 'oksh', + 'openssh', + 'openssl', + 'openttd', + 'opentyrian', + 'p7zip', + 'patch', + 'pcre2', + 'pfetch', + 'php', + 'pkgconf', + 'pt2-clone', + 'qt6-qtbase', + 'ruby', + 'sam', + 'scummvm', + 'SDL2_image', + 'SDL2_mixer', + 'SDL2_net', + 'SDL2_ttf', + 'sl', + 'sqlite', + 'tcl', + 'tinycc', + 'tr', + 'tuxracer', + 'vitetris', + 'wget', + 'xz', + 'zsh', + 'zstd', +} + +# FIXME: Once everything is converted into `git format-patch`-style patches, +# enable this to allow only `git format-patch` patches. +REQUIRE_GIT_PATCHES = False +GIT_PATCH_SUBJECT_RE = re.compile(r'Subject: (.*)\n') + def read_port_table(filename): """Open a file and find all PORT_TABLE_REGEX matches. @@ -132,6 +242,163 @@ def check_package_files(ports): return all_good +def get_and_check_port_patch_list(ports): + """Checks all port patches and returns the port list/properties + + Args: + ports (list): List of all ports to check + + Returns: + all_good (bool): No errors encountered + all_properties (dict): Mapping of port to port properties + """ + all_port_properties = {} + all_good = True + + for port in ports: + patches_directory = f"{port}/patches" + + if not os.path.exists(patches_directory): + continue + + if not os.path.isdir(patches_directory): + print(f"Ports/{port}/patches exists, but is not a directory. This is not right!") + all_good = False + continue + + patches_path = Path(patches_directory) + patches_readme_path = patches_path / "ReadMe.md" + patch_files = set(patches_path.glob("*.patch")) + non_patch_files = set(patches_path.glob("*")) - patch_files - {patches_readme_path} + + port_properties = { + "patches_path": patches_path, + "patches_readme_path": patches_readme_path, + "patch_files": patch_files, + "non_patch_files": non_patch_files + } + all_port_properties[port] = port_properties + + if len(non_patch_files) != 0: + print("Ports/{port}/patches contains the following non-patch files:", + ', '.join(x.name for x in non_patch_files)) + all_good = False + + return all_good, all_port_properties + + +def check_descriptions_for_port_patches(patches): + """Ensure that ports containing patches have them documented. + + Args: + patches (dict): Dictionary mapping ports to all their patches + + Returns: + bool: no errors encountered + """ + + all_good = True + for port, properties in patches.items(): + patches_readme_path = properties["patches_readme_path"] + patch_files = properties["patch_files"] + + readme_file_exists = patches_readme_path.exists() + if len(patch_files) == 0: + print(f"Ports/{port}/patches exists, but contains no patches", end="") + if readme_file_exists: + print(", yet it contains a ReadMe.md") + else: + print() + all_good = False + continue + + if not readme_file_exists: + if port not in PORTS_MISSING_DESCRIPTIONS: + print(f"Ports/{port}/patches contains patches but no ReadMe.md describing them") + all_good = False + continue + + with open(str(patches_readme_path), 'r', encoding='utf-8') as f: + readme_contents = [] + for line in f: + if not line.startswith('#'): + continue + match = PORT_NAME_REGEX.search(line) + if match: + readme_contents.append(match.group(1)) + + patch_names = set(Path(x).stem for x in patch_files) + + patches_ok = True + for patch_name in patch_names: + if patch_name not in readme_contents: + if port not in PORTS_MISSING_DESCRIPTIONS: + print(f"Ports/{port}/patches/{patch_name}.patch does not appear to be described in" + " the corresponding ReadMe.md") + all_good = False + patches_ok = False + + for patch_name in readme_contents: + if patch_name not in patch_names: + if port not in PORTS_MISSING_DESCRIPTIONS: + print(f"Ports/{port}/patches/{patch_name}.patch is described in ReadMe.md, " + "but does not actually exist") + all_good = False + patches_ok = False + + if port in PORTS_MISSING_DESCRIPTIONS and patches_ok: + print(f"Ports/{port}/patches are all described correctly, but the port is marked " + "as MISSING_DESCRIPTIONS, make sure to remove it from the list in lint-ports.py") + all_good = False + + return all_good + + +def try_parse_git_patch(path_to_patch): + with open(path_to_patch, 'rb') as f: + contents_of_patch = f.read() + + with NamedTemporaryFile('r+b') as message_file: + res = subprocess.run( + f"git mailinfo {message_file.name} /dev/null", + shell=True, + capture_output=True, + input=contents_of_patch) + + if res.returncode != 0: + return None + + message = message_file.read().decode('utf-8') + subject = GIT_PATCH_SUBJECT_RE.search(res.stdout.decode("utf-8")) + if subject: + message = subject.group(1) + "\n" + message + + return message + + +def check_patches_are_git_patches(patches): + """Ensure that all patches are patches made by (or compatible with) `git format-patch`. + + Args: + patches (dict): Dictionary mapping ports to all their patches + + Returns: + bool: no errors encountered + """ + + all_good = True + + for port, properties in patches.items(): + for patch_path in properties["patch_files"]: + result = try_parse_git_patch(patch_path) + if not result: + print(f"Ports/{port}/patches: {patch_path.stem} does not appear to be a valid " + "git patch.") + all_good = False + continue + return all_good + + def check_available_ports(from_table, ports): """Check AvailablePorts.md for correct properties. @@ -208,6 +475,15 @@ def run(): if not check_available_ports(from_table, ports): all_good = False + patch_list_good, port_properties = get_and_check_port_patch_list(ports.keys()) + all_good = all_good and patch_list_good + + if not check_descriptions_for_port_patches(port_properties): + all_good = False + + if REQUIRE_GIT_PATCHES and not check_patches_are_git_patches(port_properties): + all_good = False + if not all_good: sys.exit(1) |