aboutsummaryrefslogtreecommitdiff
path: root/scripts/generate_source.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/generate_source.py')
-rwxr-xr-xscripts/generate_source.py295
1 files changed, 203 insertions, 92 deletions
diff --git a/scripts/generate_source.py b/scripts/generate_source.py
index 336e5ffb..82bc213e 100755
--- a/scripts/generate_source.py
+++ b/scripts/generate_source.py
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
-# Copyright (c) 2019-2025 The Khronos Group Inc.
-# Copyright (c) 2019-2025 Valve Corporation
-# Copyright (c) 2019-2025 LunarG, Inc.
-# Copyright (c) 2019-2025 Google Inc.
-# Copyright (c) 2023-2025 RasterGrid Kft.
+# Copyright (c) 2019 The Khronos Group Inc.
+# Copyright (c) 2019 Valve Corporation
+# Copyright (c) 2019 LunarG, Inc.
+# Copyright (c) 2019 Google Inc.
+# Copyright (c) 2021-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# Copyright (c) 2023-2023 RasterGrid Kft.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,118 +23,218 @@
import argparse
import filecmp
import os
-import re
-import json
import shutil
-import subprocess
import sys
+import re
import tempfile
-
+import json
import common_codegen
-# files to exclude from --verify check
-verify_exclude = ['.clang-format']
+from xml.etree import ElementTree
+
+# Because we have special logic to import the Registry from input arguments and the BaseGenerator comes from the registry, we have to delay defining it until *after*
+# the Registry has been imported. Yes this is awkward, but it was the least awkward way to make --verify work.
+generators = {}
+
+def RunGenerators(api: str, registry: str, video_registry: str, directory: str, styleFile: str, targetFilter: str, flatOutput: bool):
+
+ try:
+ common_codegen.RunShellCmd(f'clang-format --version')
+ has_clang_format = True
+ except:
+ has_clang_format = False
+ if not has_clang_format:
+ print("WARNING: Unable to find clang-format!")
+
+ # These live in the Vulkan-Docs repo, but are pulled in via the
+ # Vulkan-Headers/registry folder
+ # At runtime we inject python path to find these helper scripts
+ scripts = os.path.dirname(registry)
+ scripts_directory_path = os.path.dirname(os.path.abspath(__file__))
+ registry_headers_path = os.path.join(scripts_directory_path, scripts)
+ sys.path.insert(0, registry_headers_path)
+ try:
+ from reg import Registry
+ except:
+ print("ModuleNotFoundError: No module named 'reg'") # normal python error message
+ print(f'{registry_headers_path} is not pointing to the Vulkan-Headers registry directory.')
+ print("Inside Vulkan-Headers there is a registry/reg.py file that is used.")
+ sys.exit(1) # Return without call stack so easy to spot error
+
+ from base_generator import BaseGeneratorOptions
+ from generators.mock_icd_generator import MockICDOutputGenerator
+ from generators.vulkan_tools_helper_file_generator import HelperFileOutputGenerator
+ from generators.vulkaninfo_generator import VulkanInfoGenerator
+
+ # These set fields that are needed by both OutputGenerator and BaseGenerator,
+ # but are uniform and don't need to be set at a per-generated file level
+ from base_generator import (SetTargetApiName, SetMergedApiNames)
+ SetTargetApiName(api)
+
+ # Generated directory and dispatch table helper file name may be API specific (e.g. Vulkan SC)
+ mock_icd_generated_directory = 'icd/generated'
+ vulkaninfo_generated_directory = 'vulkaninfo/generated'
+
+ generators.update({
+ 'vk_typemap_helper.h': {
+ 'generator' : HelperFileOutputGenerator,
+ 'genCombined': False,
+ 'directory' : mock_icd_generated_directory,
+ },
+ 'function_declarations.h': {
+ 'generator' : MockICDOutputGenerator,
+ 'genCombined': False,
+ 'directory' : mock_icd_generated_directory,
+ },
+ 'function_definitions.h': {
+ 'generator' : MockICDOutputGenerator,
+ 'genCombined': False,
+ 'directory' : mock_icd_generated_directory,
+ },
+ 'vulkaninfo.hpp': {
+ 'generator' : VulkanInfoGenerator,
+ 'genCombined': False,
+ 'directory' : vulkaninfo_generated_directory,
+ },
+ })
+
+ unknownTargets = [x for x in (targetFilter if targetFilter else []) if x not in generators.keys()]
+ if unknownTargets:
+ print(f'ERROR: No generator options for unknown target(s): {", ".join(unknownTargets)}', file=sys.stderr)
+ return 1
+
+ # Filter if --target is passed in
+ targets = [x for x in generators.keys() if not targetFilter or x in targetFilter]
+
+ for index, target in enumerate(targets, start=1):
+ print(f'[{index}|{len(targets)}] Generating {target}')
+
+ # First grab a class constructor object and create an instance
+ generator = generators[target]['generator']
+ gen = generator()
+
+ # This code and the 'genCombined' generator metadata is used by downstream
+ # users to generate code with all Vulkan APIs merged into the target API variant
+ # (e.g. Vulkan SC) when needed. The constructed apiList is also used to filter
+ # out non-applicable extensions later below.
+ apiList = [api]
+ if api != 'vulkan' and generators[target]['genCombined']:
+ SetMergedApiNames('vulkan')
+ apiList.append('vulkan')
+ else:
+ SetMergedApiNames(None)
+
+ # For people who want to generate all the files in a single director
+ if flatOutput:
+ outDirectory = os.path.abspath(os.path.join(directory))
+ else:
+ outDirectory = os.path.abspath(os.path.join(directory, generators[target]['directory']))
+
+ options = BaseGeneratorOptions(
+ customFileName = target,
+ customDirectory = outDirectory)
+
+ if not os.path.exists(outDirectory):
+ os.makedirs(outDirectory)
+
+ # Create the registry object with the specified generator and generator
+ # options. The options are set before XML loading as they may affect it.
+ reg = Registry(gen, options)
+
+ # Parse the specified registry XML into an ElementTree object
+ tree = ElementTree.parse(registry)
+
+ # Load the XML tree into the registry object
+ reg.loadElementTree(tree)
+
+ # Set the path to the video registry so that videoStd is available
+ reg.genOpts.videoXmlPath = video_registry
+
+ # Finally, use the output generator to create the requested target
+ reg.apiGen()
+
+ # Run clang-format on the file
+ if has_clang_format and styleFile:
+ common_codegen.RunShellCmd(f'clang-format -i --style=file:{styleFile} {os.path.join(outDirectory, target)}')
+
def main(argv):
+
+ # files to exclude from --verify check
+ verify_exclude = ['.clang-format'] # None currently
+
parser = argparse.ArgumentParser(description='Generate source code for this repository')
+ parser.add_argument('registry', metavar='REGISTRY_PATH', help='path to the Vulkan-Headers registry directory')
parser.add_argument('--api',
default='vulkan',
choices=['vulkan', 'vulkansc'],
help='Specify API name to generate')
parser.add_argument('--generated-version', help='sets the header version used to generate the repo')
- parser.add_argument('registry', metavar='REGISTRY_PATH', help='path to the Vulkan-Headers registry directory')
group = parser.add_mutually_exclusive_group()
+ group.add_argument('--target', nargs='+', help='only generate file names passed in')
group.add_argument('-i', '--incremental', action='store_true', help='only update repo files that change')
group.add_argument('-v', '--verify', action='store_true', help='verify repo files match generator output')
+ group.add_argument('-o', action='store', dest='directory', help='Create target and related files in specified directory')
args = parser.parse_args(argv)
- # output paths and the list of files in the path
- files_to_gen = {str(os.path.join('icd','generated')) : ['vk_typemap_helper.h',
- 'function_definitions.h',
- 'function_declarations.h'],
- str(os.path.join('vulkaninfo','generated')): ['vulkaninfo.hpp']}
-
- #base directory for the source repository
- repo_dir = common_codegen.repo_relative('')
-
- # Update the api_version in the respective json files
- if args.generated_version:
- json_files = []
- json_files.append(common_codegen.repo_relative('icd/VkICD_mock_icd.json.in'))
- for json_file in json_files:
- with open(json_file) as f:
- data = json.load(f)
-
- data["ICD"]["api_version"] = args.generated_version
-
- with open(json_file, mode='w', encoding='utf-8', newline='\n') as f:
- f.write(json.dumps(data, indent=4))
-
- # get directory where generators will run if needed
- if args.verify or args.incremental:
- # generate in temp directory so we can compare or copy later
- temp_obj = tempfile.TemporaryDirectory(prefix='VulkanLoader_generated_source_')
- temp_dir = temp_obj.name
- for path in files_to_gen.keys():
- os.makedirs(os.path.join(temp_dir, path))
+ repo_dir = common_codegen.repo_relative('.')
registry = os.path.abspath(os.path.join(args.registry, 'vk.xml'))
- if not os.path.isfile(registry):
+ video_registry = os.path.abspath(os.path.join(args.registry, 'video.xml'))
+ if not os.path.isfile(registry) and not os.path.isfile(registry):
registry = os.path.abspath(os.path.join(args.registry, 'Vulkan-Headers/registry/vk.xml'))
if not os.path.isfile(registry):
print(f'cannot find vk.xml in {args.registry}')
return -1
+ video_registry = os.path.abspath(os.path.join(args.registry, 'Vulkan-Headers/registry/video.xml'))
+ if not os.path.isfile(video_registry):
+ print(f'{video_registry} does not exist')
+ return -1
+
+ # Need pass style file incase running with --verify and it can't find the file automatically in the temp directory
+ style_file = os.path.join(repo_dir, '.clang-format')
- # run each code generator
- for path, filenames in files_to_gen.items():
- for filename in filenames:
- if args.verify or args.incremental:
- output_path = os.path.join(temp_dir, path)
- else:
- output_path = common_codegen.repo_relative(path)
-
- cmd = [common_codegen.repo_relative(os.path.join('scripts','kvt_genvk.py')),
- '-api', args.api,
- '-registry', registry,
- '-quiet', '-directory', output_path, filename]
- print(' '.join(cmd))
- try:
- if args.verify or args.incremental:
- subprocess.check_call([sys.executable] + cmd, cwd=temp_dir)
- else:
- subprocess.check_call([sys.executable] + cmd, cwd=repo_dir)
-
- except Exception as e:
- print('ERROR:', str(e))
- return 1
+ # get directory where generators will run
+ if args.verify or args.incremental:
+ # generate in temp directory so we can compare or copy later
+ temp_obj = tempfile.TemporaryDirectory(prefix='vulkan_tools_codegen_')
+ temp_dir = temp_obj.name
+ gen_dir = temp_dir
+ elif args.directory:
+ gen_dir = args.directory
+ else:
+ # generate directly in the repo
+ gen_dir = repo_dir
+
+ RunGenerators(api=args.api,registry=registry, video_registry=video_registry, directory=gen_dir, styleFile=style_file, targetFilter=args.target, flatOutput=False)
# optional post-generation steps
if args.verify:
# compare contents of temp dir and repo
temp_files = {}
- for path in files_to_gen.keys():
- temp_files[path] = set()
- temp_files[path].update(set(os.listdir(os.path.join(temp_dir, path))))
-
repo_files = {}
- for path in files_to_gen.keys():
- repo_files[path] = set()
- repo_files[path].update(set(os.listdir(os.path.join(repo_dir, path))) - set(verify_exclude))
+ for details in generators.values():
+ if details['directory'] not in temp_files:
+ temp_files[details['directory']] = set()
+ temp_files[details['directory']].update(set(os.listdir(os.path.join(temp_dir, details['directory']))))
+ if details['directory'] not in repo_files:
+ repo_files[details['directory']] = set()
+ repo_files[details['directory']].update(set(os.listdir(os.path.join(repo_dir, details['directory']))) - set(verify_exclude))
+ # compare contents of temp dir and repo
files_match = True
- for path in files_to_gen.keys():
- for filename in sorted((temp_files[path] | repo_files[path])):
- if filename not in repo_files[path]:
- print('ERROR: Missing repo file', filename)
- files_match = False
- elif filename not in temp_files[path]:
- print('ERROR: Missing generator for', filename)
- files_match = False
- elif not filecmp.cmp(os.path.join(temp_dir, path, filename),
- os.path.join(repo_dir, path, filename),
- shallow=False):
- print('ERROR: Repo files do not match generator output for', filename)
- files_match = False
+ for filename, details in generators.items():
+ if filename not in repo_files[details['directory']]:
+ print('ERROR: Missing repo file', filename)
+ files_match = False
+ elif filename not in temp_files[details['directory']]:
+ print('ERROR: Missing generator for', filename)
+ files_match = False
+ elif not filecmp.cmp(os.path.join(temp_dir, details['directory'], filename),
+ os.path.join(repo_dir, details['directory'], filename),
+ shallow=False):
+ print('ERROR: Repo files do not match generator output for', filename)
+ files_match = False
# return code for test scripts
if files_match:
@@ -143,17 +244,27 @@ def main(argv):
elif args.incremental:
# copy missing or differing files from temp directory to repo
- for path in files_to_gen.keys():
- for filename in os.listdir(os.path.join(temp_dir,path)):
- temp_filename = os.path.join(temp_dir, path, filename)
- repo_filename = os.path.join(repo_dir, path, filename)
- if not os.path.exists(repo_filename) or \
- not filecmp.cmp(temp_filename, repo_filename, shallow=False):
- print('update', repo_filename)
- shutil.copyfile(temp_filename, repo_filename)
+ for filename, details in generators.items():
+ temp_filename = os.path.join(temp_dir, details['directory'], filename)
+ repo_filename = os.path.join(repo_dir, details['directory'], filename)
+ if not os.path.exists(repo_filename) or \
+ not filecmp.cmp(temp_filename, repo_filename, shallow=False):
+ print('update', repo_filename)
+ shutil.copyfile(temp_filename, repo_filename)
# write out the header version used to generate the code to a checked in CMake file
if args.generated_version:
+ json_files = []
+ json_files.append(common_codegen.repo_relative('icd/VkICD_mock_icd.json.in'))
+ for json_file in json_files:
+ with open(json_file) as f:
+ data = json.load(f)
+
+ data["ICD"]["api_version"] = args.generated_version
+
+ with open(json_file, mode='w', encoding='utf-8', newline='\n') as f:
+ f.write(json.dumps(data, indent=4))
+
# Update the CMake project version
with open(common_codegen.repo_relative('CMakeLists.txt'), "r+") as f:
data = f.read()