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.
This commit is contained in:
Ali Mohammad Pur 2022-01-08 14:57:38 +03:30 committed by Ali Mohammad Pur
parent fc02370dc7
commit 8d30e14d28
Notes: sideshowbarker 2024-07-17 20:47:44 +09:00

View file

@ -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)