summaryrefslogtreecommitdiff
path: root/Meta/lint-ports.py
diff options
context:
space:
mode:
authorAli Mohammad Pur <ali.mpfard@gmail.com>2022-01-08 14:57:38 +0330
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2022-01-16 10:32:50 +0330
commit8d30e14d28d8732760bfd471b46771a9d2ff2f48 (patch)
tree81695956cb9a5107a9d324e63f1398f14f011c0d /Meta/lint-ports.py
parentfc02370dc75f8c40ae3d9960b44eaaf8634d7e8e (diff)
downloadserenity-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-xMeta/lint-ports.py276
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)