OpenMiInk/third_party/kde-ci-utilities/validate-json-files.py
Hellings b8c8ff98e2 Hi
2026-10-16 09:05:56 -04:00

105 lines
4.1 KiB
Python
Executable file

#!/usr/bin/python3
# Version in sysadmin/ci-utilities should be single source of truth
# SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
# SPDX-License-Identifier: BSD-2-Clause
import argparse
import os
import subprocess
import json
from typing import Dict, List
import yaml
parser = argparse.ArgumentParser(description='Check JSON schema for files in repository')
parser.add_argument('--schemafile', required=True, help='Path to the schema file')
parser.add_argument('--check-all', default=False, action='store_true', help='If all files should be checked or only the changed files')
parser.add_argument('--verbose', default=False, action='store_true')
args = parser.parse_args()
def get_changed_files() -> List[str]:
result = subprocess.run(['git', 'diff', '--cached', '--name-only'], capture_output=True, text=True)
return [file for file in result.stdout.splitlines() if file.endswith('.json')]
def get_all_files() -> List[str]:
files = []
for root, _, filenames in os.walk('.'):
for filename in filenames:
if filename.endswith('.json'):
files.append(os.path.join(root, filename))
return files
def filter_excluded_included_json_files(files: List[str]) -> List[str]:
config_file = '.kde-ci.yml'
# Check if the file exists
if os.path.exists(config_file):
with open(config_file, 'r') as file:
config = yaml.safe_load(file)
else:
if args.verbose:
print(f'{config_file} does not exist in current directory')
config = {}
# Extract excluded files, used for tests that intentionally have broken files
excluded_files = ['compile_commands.json', 'ci-utilities', 'sysadmin-repo-metadata']
if 'Options' in config and 'json-validate-ignore' in config['Options']:
excluded_files += config['Options']['json-validate-ignore']
# Find JSON files
filtered_files = []
for file_path in files:
if not any(excluded_file in file_path for excluded_file in excluded_files):
filtered_files.append(file_path)
# "include" overrides the "ignore" files, so if a file is both included and excluded, it will be included
if 'Options' in config and 'json-validate-include' in config['Options']:
filtered_files += config['Options']['json-validate-include']
return filtered_files
if args.check_all:
files = get_all_files()
else:
files = get_changed_files()
files = filter_excluded_included_json_files(files)
if not files:
exit(0)
fallback_schema = args.schemafile
if not fallback_schema.startswith(('http://', 'https://')):
fallback_schema = os.path.abspath(fallback_schema)
files_schema_dict: Dict[str, list[str]] = {}
has_error = False
for jsonfilepath in files:
with open(jsonfilepath) as f:
try:
jsoncontent = json.load(f)
except json.decoder.JSONDecodeError as e:
print(f"Unable to load {jsonfilepath}: {e}")
has_error = True
continue
schema_to_use: str = jsoncontent['$schema'] if '$schema' in jsoncontent else fallback_schema
# In case the wildcard catches a schema definition, there is no point to validate it
if schema_to_use.startswith("http://json-schema.org/") or schema_to_use.startswith("https://json-schema.org/"):
print(f"skipping validation of {jsonfilepath} due to it being json schema")
continue
if not schema_to_use.startswith(('http://', 'https://')):
schema_to_use = os.path.abspath(os.path.join(os.path.dirname(jsonfilepath), schema_to_use))
if not schema_to_use in files_schema_dict:
files_schema_dict[schema_to_use] = [jsonfilepath]
files_schema_dict[schema_to_use].append(jsonfilepath)
for schema, files in files_schema_dict.items():
if args.verbose:
files_option = ' '.join(files)
print(f"Validating {files_option} with {schema}")
result = subprocess.run(['check-jsonschema', *files, '--schemafile', schema])
# Don't exit right away in case we have multiple schemas to validate
if result.returncode != 0:
has_error = True
if has_error:
exit(1)