mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 10:22:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			219 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| import os
 | |
| import re
 | |
| import sys
 | |
| import subprocess
 | |
| 
 | |
| # 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
 | |
| # and "https://www.gnu.org/software/bash/" in group 6.
 | |
| PORT_TABLE_REGEX = re.compile(
 | |
|     r'^\| \[`([^`]+)`\]\(([^\)]+)\)([^\|]+) \| ([^\|]+) \| ([^\|]+?) \| ([^\|]+) \|+$', re.MULTILINE
 | |
| )
 | |
| 
 | |
| # Matches non-abbreviated git hashes
 | |
| GIT_HASH_REGEX = re.compile(r'^[0-9a-f]{40}$')
 | |
| 
 | |
| PORT_TABLE_FILE = 'AvailablePorts.md'
 | |
| IGNORE_FILES = {
 | |
|     '.gitignore',
 | |
|     '.port_include.sh',
 | |
|     PORT_TABLE_FILE,
 | |
|     'build_all.sh',
 | |
|     'build_installed.sh',
 | |
|     'README.md',
 | |
|     '.hosted_defs.sh'
 | |
| }
 | |
| 
 | |
| 
 | |
| def read_port_table(filename):
 | |
|     """Open a file and find all PORT_TABLE_REGEX matches.
 | |
| 
 | |
|     Args:
 | |
|         filename (str): filename
 | |
| 
 | |
|     Returns:
 | |
|         set: all PORT_TABLE_REGEX matches
 | |
|     """
 | |
|     ports = {}
 | |
|     with open(filename, 'r') as fp:
 | |
|         matches = PORT_TABLE_REGEX.findall(fp.read())
 | |
|         for match in matches:
 | |
|             line_len = sum([len(part) for part in match])
 | |
|             ports[match[0]] = {
 | |
|                 "dir_ref": match[1],
 | |
|                 "name": match[2].strip(),
 | |
|                 "version": match[4].strip(),
 | |
|                 "url": match[5].strip(),
 | |
|                 "line_len": line_len
 | |
|             }
 | |
|     return ports
 | |
| 
 | |
| 
 | |
| def read_port_dirs():
 | |
|     """Check Ports directory for unexpected files and check each port has a package.sh file.
 | |
| 
 | |
|     Returns:
 | |
|         list: all ports (set), no errors encountered (bool)
 | |
|     """
 | |
| 
 | |
|     ports = {}
 | |
|     all_good = True
 | |
|     for entry in os.listdir():
 | |
|         if entry in IGNORE_FILES:
 | |
|             continue
 | |
|         if not os.path.isdir(entry):
 | |
|             print(f"Ports/{entry} is neither a port (not a directory) nor an ignored file?!")
 | |
|             all_good = False
 | |
|             continue
 | |
|         if not os.path.exists(entry + '/package.sh'):
 | |
|             print(f"Ports/{entry}/ is missing its package.sh?!")
 | |
|             all_good = False
 | |
|             continue
 | |
|         ports[entry] = get_port_properties(entry)
 | |
| 
 | |
|     return ports, all_good
 | |
| 
 | |
| 
 | |
| PORT_PROPERTIES = ('port', 'version', 'files', 'auth_type')
 | |
| 
 | |
| 
 | |
| def get_port_properties(port):
 | |
|     """Retrieves common port properties from its package.sh file.
 | |
| 
 | |
|     Returns:
 | |
|         dict: keys are values from PORT_PROPERTIES, values are from the package.sh file
 | |
|     """
 | |
| 
 | |
|     props = {}
 | |
|     for prop in PORT_PROPERTIES:
 | |
|         res = subprocess.run(f"cd {port}; exec ./package.sh showproperty {prop}", shell=True, capture_output=True)
 | |
|         if res.returncode == 0:
 | |
|             props[prop] = res.stdout.decode('utf-8').strip()
 | |
|         else:
 | |
|             print((
 | |
|                 f'Executing "./package.sh showproperty {prop}" script for port {port} failed with '
 | |
|                 f'exit code {res.returncode}, output from stderr:\n{res.stderr.decode("utf-8").strip()}'
 | |
|             ))
 | |
|             props[prop] = ''
 | |
|     return props
 | |
| 
 | |
| 
 | |
| def check_package_files(ports):
 | |
|     """Check port package.sh file for required properties.
 | |
| 
 | |
|     Args:
 | |
|         ports (list): List of all ports to check
 | |
| 
 | |
|     Returns:
 | |
|         bool: no errors encountered
 | |
|     """
 | |
| 
 | |
|     all_good = True
 | |
|     for port in ports:
 | |
|         package_file = f"{port}/package.sh"
 | |
|         if not os.path.exists(package_file):
 | |
|             continue
 | |
| 
 | |
|         props = get_port_properties(port)
 | |
| 
 | |
|         if not props['auth_type'] in ('sha256', 'sig', ''):
 | |
|             print(f"Ports/{port} uses invalid signature algorithm '{props['auth_type']}' for 'auth_type'")
 | |
|             all_good = False
 | |
| 
 | |
|         for prop in PORT_PROPERTIES:
 | |
|             if prop == 'auth_type' and re.match('^https://github.com/SerenityPorts/', props["files"]):
 | |
|                 continue
 | |
|             if props[prop] == '':
 | |
|                 print(f"Ports/{port} is missing required property '{prop}'")
 | |
|                 all_good = False
 | |
| 
 | |
|     return all_good
 | |
| 
 | |
| 
 | |
| def check_available_ports(from_table, ports):
 | |
|     """Check AvailablePorts.md for correct properties.
 | |
| 
 | |
|     Args:
 | |
|         from_table (dict): Ports table from AvailablePorts.md
 | |
|         ports (dict): Dictionary with port properties from package.sh
 | |
| 
 | |
|     Returns:
 | |
|         bool: no errors encountered
 | |
|     """
 | |
| 
 | |
|     all_good = True
 | |
| 
 | |
|     previous_line_len = None
 | |
| 
 | |
|     for port in from_table.keys():
 | |
|         if previous_line_len is None:
 | |
|             previous_line_len = from_table[port]["line_len"]
 | |
|         if previous_line_len != from_table[port]["line_len"]:
 | |
|             print(f"Table row for port {port} is improperly aligned with other rows.")
 | |
|             all_good = False
 | |
|         else:
 | |
|             previous_line_len = from_table[port]["line_len"]
 | |
| 
 | |
|         actual_ref = from_table[port]["dir_ref"]
 | |
|         expected_ref = f"{port}/"
 | |
|         if actual_ref != expected_ref:
 | |
|             print((
 | |
|                 f'Directory link target in AvailablePorts.md for port {port} is '
 | |
|                 f'incorrect, expected "{expected_ref}", found "{actual_ref}"'
 | |
|             ))
 | |
|             all_good = False
 | |
| 
 | |
|         actual_version = from_table[port]["version"]
 | |
|         expected_version = ports[port]["version"]
 | |
|         if GIT_HASH_REGEX.match(expected_version):
 | |
|             expected_version = expected_version[0:7]
 | |
|         if expected_version == "git":
 | |
|             expected_version = ""
 | |
|         if actual_version != expected_version:
 | |
|             print((
 | |
|                 f'Version in AvailablePorts.md for port {port} is incorrect, '
 | |
|                 f'expected "{expected_version}", found "{actual_version}"'
 | |
|             ))
 | |
|             all_good = False
 | |
| 
 | |
|     return all_good
 | |
| 
 | |
| 
 | |
| def run():
 | |
|     """Check Ports directory and package files for errors."""
 | |
| 
 | |
|     from_table = read_port_table(PORT_TABLE_FILE)
 | |
|     ports, all_good = read_port_dirs()
 | |
| 
 | |
|     from_table_set = set(from_table.keys())
 | |
|     ports_set = set(ports.keys())
 | |
| 
 | |
|     if from_table_set - ports_set:
 | |
|         all_good = False
 | |
|         print('AvailablePorts.md lists ports that do not appear in the file system:')
 | |
|         for port in sorted(from_table_set - ports_set):
 | |
|             print(f"    {port}")
 | |
| 
 | |
|     if ports_set - from_table_set:
 | |
|         all_good = False
 | |
|         print('AvailablePorts.md is missing the following ports:')
 | |
|         for port in sorted(ports_set - from_table_set):
 | |
|             print(f"    {port}")
 | |
| 
 | |
|     if not check_package_files(ports.keys()):
 | |
|         all_good = False
 | |
| 
 | |
|     if not check_available_ports(from_table, ports):
 | |
|         all_good = False
 | |
| 
 | |
|     if not all_good:
 | |
|         sys.exit(1)
 | |
| 
 | |
|     print('No issues found.')
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     os.chdir(f"{os.path.dirname(__file__)}/../Ports")
 | |
|     run()
 | 
