The open source OpenXR runtime

scripts: Add json merging script

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2635>

authored by Jakob Bornecrantz and committed by Marge Bot 0a639c04 7218ff54

+1
CMakeLists.txt
··· 58 58 include(OptionWithDeps) 59 59 include(SPIR-V) 60 60 include(GNUInstallDirs) 61 + include(MergeJSON) 61 62 if(NOT GIT_DESC) 62 63 include(GetGitRevisionDescription) 63 64 git_describe(GIT_DESC "--always")
+77
cmake/MergeJSON.cmake
··· 1 + # Copyright 2025, NVIDIA CORPORATION. 2 + # SPDX-License-Identifier: BSL-1.0 3 + 4 + # JSON merge function: merges multiple JSON files into one output file 5 + # 6 + # To use this function in your CMakeLists.txt: 7 + # include(${CMAKE_SOURCE_DIR}/scripts/CMakeLists.txt) 8 + # 9 + # Usage: 10 + # merge_json_files( 11 + # OUTPUT <output_file> 12 + # SOURCES <json_file1> <json_file2> ... 13 + # [ALLOW_OVERWRITE] 14 + # [IGNORE_SCHEMA] 15 + # ) 16 + # 17 + # Arguments: 18 + # OUTPUT - Output file path (required) 19 + # SOURCES - List of JSON files to merge (required) 20 + # ALLOW_OVERWRITE - Optional flag to allow duplicate keys 21 + # IGNORE_SCHEMA - Optional flag to ignore "$schema" field in output 22 + # 23 + # The function automatically: 24 + # - Sorts input files alphabetically for consistent results 25 + # - Tracks dependencies so output is regenerated when inputs change 26 + # - Errors on duplicate keys unless ALLOW_OVERWRITE is specified 27 + # 28 + # Example: 29 + # merge_json_files( 30 + # OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/merged.json" 31 + # SOURCES file1.json file2.json file3.json 32 + # IGNORE_SCHEMA 33 + # ) 34 + 35 + function(merge_json_files) 36 + set(options ALLOW_OVERWRITE IGNORE_SCHEMA) 37 + set(oneValueArgs OUTPUT) 38 + set(multiValueArgs SOURCES) 39 + 40 + cmake_parse_arguments( 41 + MERGE_JSON "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} 42 + ) 43 + 44 + if(NOT MERGE_JSON_OUTPUT) 45 + message(FATAL_ERROR "merge_json_files: OUTPUT argument is required") 46 + endif() 47 + 48 + if(NOT MERGE_JSON_SOURCES) 49 + message(FATAL_ERROR "merge_json_files: SOURCES argument is required") 50 + endif() 51 + 52 + # Build command arguments 53 + set(MERGE_COMMAND ${PYTHON_EXECUTABLE} 54 + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/merge_json.py -o 55 + "${MERGE_JSON_OUTPUT}" 56 + ) 57 + 58 + if(MERGE_JSON_ALLOW_OVERWRITE) 59 + list(APPEND MERGE_COMMAND --allow-overwrite) 60 + endif() 61 + 62 + if(MERGE_JSON_IGNORE_SCHEMA) 63 + list(APPEND MERGE_COMMAND --ignore-schema) 64 + endif() 65 + 66 + list(APPEND MERGE_COMMAND ${MERGE_JSON_SOURCES}) 67 + 68 + # Create custom command with proper dependencies 69 + add_custom_command( 70 + OUTPUT "${MERGE_JSON_OUTPUT}" 71 + COMMAND ${MERGE_COMMAND} 72 + VERBATIM 73 + DEPENDS ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/merge_json.py 74 + ${MERGE_JSON_SOURCES} 75 + COMMENT "Merging JSON files into ${MERGE_JSON_OUTPUT}" 76 + ) 77 + endfunction()
+139
cmake/merge_json.py
··· 1 + #!/usr/bin/env python3 2 + # Copyright 2025, NVIDIA CORPORATION. 3 + # SPDX-License-Identifier: BSL-1.0 4 + """ 5 + Helper script to merge multiple JSON files into one. 6 + 7 + This script merges the root JSON objects from multiple input files 8 + while preserving field ordering. Files are processed in sorted order 9 + to ensure consistent results. Fields are merged in the order they 10 + appear across the input files. 11 + """ 12 + 13 + import argparse 14 + import json 15 + import sys 16 + from pathlib import Path 17 + 18 + 19 + def merge_json_files(json_files, output_file, allow_overwrite=False, 20 + ignore_schema=False): 21 + """ 22 + Merge multiple JSON files into a single output file. 23 + 24 + Args: 25 + json_files: List of paths to JSON files to merge 26 + output_file: Path to the output file 27 + allow_overwrite: If True, allow later files to overwrite keys 28 + from earlier files 29 + ignore_schema: If True, ignore the "$schema" field in input files 30 + """ 31 + merged = {} 32 + 33 + # Sort files to ensure consistent ordering 34 + sorted_files = sorted(json_files) 35 + 36 + for json_file in sorted_files: 37 + try: 38 + with open(json_file, 'r', encoding='utf-8') as f: 39 + data = json.load(f) 40 + 41 + if not isinstance(data, dict): 42 + print(f"Error: {json_file} does not contain a JSON " 43 + f"object at root level", 44 + file=sys.stderr) 45 + sys.exit(1) 46 + 47 + # Merge while preserving order - check for duplicates 48 + for key, value in data.items(): 49 + if key == "$schema" and ignore_schema: 50 + continue 51 + if key in merged: 52 + if allow_overwrite: 53 + print(f"Warning: Key '{key}' from {json_file} " 54 + f"overwrites previous definition", 55 + file=sys.stderr) 56 + else: 57 + print(f"Error: Duplicate key '{key}' found in " 58 + f"{json_file}. Use --allow-overwrite to " 59 + f"permit overwriting.", 60 + file=sys.stderr) 61 + sys.exit(1) 62 + merged[key] = value 63 + 64 + except FileNotFoundError: 65 + print(f"Error: File not found: {json_file}", file=sys.stderr) 66 + sys.exit(1) 67 + except json.JSONDecodeError as e: 68 + print(f"Error: Failed to parse JSON from {json_file}: {e}", 69 + file=sys.stderr) 70 + sys.exit(1) 71 + except Exception as e: 72 + print(f"Error: Failed to read {json_file}: {e}", 73 + file=sys.stderr) 74 + sys.exit(1) 75 + 76 + 77 + # Write merged JSON to output file 78 + try: 79 + with open(output_file, 'w', encoding='utf-8') as f: 80 + json.dump(merged, f, indent='\t', ensure_ascii=False) 81 + f.write('\n') # Add trailing newline 82 + 83 + # Printing, left in if we want it in the future. 84 + if False: 85 + print(f"Successfully merged {len(json_files)} file(s) into " 86 + f"{output_file}") 87 + except Exception as e: 88 + print(f"Error: Failed to write to {output_file}: {e}", 89 + file=sys.stderr) 90 + sys.exit(1) 91 + 92 + 93 + def main(): 94 + parser = argparse.ArgumentParser( 95 + description='Merge multiple JSON files into one, preserving ' 96 + 'field ordering.', 97 + formatter_class=argparse.RawDescriptionHelpFormatter, 98 + epilog=""" 99 + Examples: 100 + %(prog)s -o merged.json file1.json file2.json file3.json 101 + %(prog)s -o output.json base.json overrides.json 102 + %(prog)s -o output.json --allow-overwrite base.json overrides.json 103 + %(prog)s -o output.json --ignore-schema schema.json data.json 104 + """ 105 + ) 106 + 107 + parser.add_argument('-o', '--output', 108 + required=True, 109 + metavar='OUTPUT', 110 + help='Output file path for the merged JSON') 111 + 112 + parser.add_argument('--allow-overwrite', 113 + action='store_true', 114 + help='Allow later files to overwrite duplicate ' 115 + 'keys from earlier files') 116 + 117 + parser.add_argument('--ignore-schema', 118 + action='store_true', 119 + help='Ignore the "$schema" field in input files') 120 + 121 + parser.add_argument('json_files', 122 + nargs='+', 123 + metavar='JSON_FILE', 124 + help='JSON files (will be sorted alphabetically)') 125 + 126 + args = parser.parse_args() 127 + 128 + # Validate that we have at least one input file 129 + if not args.json_files: 130 + print("Error: At least one JSON file must be provided", 131 + file=sys.stderr) 132 + sys.exit(1) 133 + 134 + merge_json_files(args.json_files, args.output, args.allow_overwrite, 135 + args.ignore_schema) 136 + 137 + 138 + if __name__ == '__main__': 139 + main()