1#!/usr/bin/env python3
2# Copyright (c) 2019 The Khronos Group Inc.
3# Copyright (c) 2019 Valve Corporation
4# Copyright (c) 2019 LunarG, Inc.
5# Copyright (c) 2019 Google Inc.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11#     http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18#
19# Author: Mike Schuchardt <mikes@lunarg.com>
20
21import argparse
22import filecmp
23import os
24import shutil
25import subprocess
26import sys
27import tempfile
28
29import common_codegen
30
31# files to exclude from --verify check
32verify_exclude = ['.clang-format']
33
34def main(argv):
35    parser = argparse.ArgumentParser(description='Generate source code for this repository')
36    parser.add_argument('registry', metavar='REGISTRY_PATH', help='path to the Vulkan-Headers registry directory')
37    group = parser.add_mutually_exclusive_group()
38    group.add_argument('-i', '--incremental', action='store_true', help='only update repo files that change')
39    group.add_argument('-v', '--verify', action='store_true', help='verify repo files match generator output')
40    args = parser.parse_args(argv)
41
42    gen_cmds = [*[[common_codegen.repo_relative('scripts/lvl_genvk.py'),
43                   '-registry', os.path.abspath(os.path.join(args.registry,  'vk.xml')),
44                   '-quiet',
45                   filename] for filename in ["chassis.cpp",
46                                              "chassis.h",
47                                              "layer_chassis_dispatch.cpp",
48                                              "layer_chassis_dispatch.h",
49                                              "object_tracker.cpp",
50                                              "object_tracker.h",
51                                              "parameter_validation.cpp",
52                                              "parameter_validation.h",
53                                              "thread_safety.cpp",
54                                              "thread_safety.h",
55                                              "vk_dispatch_table_helper.h",
56                                              "vk_enum_string_helper.h",
57                                              "vk_extension_helper.h",
58                                              "vk_layer_dispatch_table.h",
59                                              "vk_object_types.h",
60                                              "vk_safe_struct.cpp",
61                                              "vk_safe_struct.h",
62                                              "vk_typemap_helper.h"]],
63                [common_codegen.repo_relative('scripts/vk_validation_stats.py'),
64                 os.path.abspath(os.path.join(args.registry, 'validusage.json')),
65                 '-export_header'],
66                [common_codegen.repo_relative('scripts/external_revision_generator.py'),
67                 '--json_file', common_codegen.repo_relative('scripts/known_good.json'),
68                 '--json_keys', 'repos,0,commit',
69                 '-s', 'SPIRV_TOOLS_COMMIT_ID',
70                 '-o', 'spirv_tools_commit_id.h']]
71
72    repo_dir = common_codegen.repo_relative('layers/generated')
73
74    # get directory where generators will run
75    if args.verify or args.incremental:
76        # generate in temp directory so we can compare or copy later
77        temp_obj = tempfile.TemporaryDirectory(prefix='VulkanVL_generated_source_')
78        temp_dir = temp_obj.name
79        gen_dir = temp_dir
80    else:
81        # generate directly in the repo
82        gen_dir = repo_dir
83
84    # run each code generator
85    for cmd in gen_cmds:
86        print(' '.join(cmd))
87        try:
88            subprocess.check_call([sys.executable] + cmd, cwd=gen_dir)
89        except Exception as e:
90            print('ERROR:', str(e))
91            return 1
92
93    # optional post-generation steps
94    if args.verify:
95        # compare contents of temp dir and repo
96        temp_files = set(os.listdir(temp_dir))
97        repo_files = set(os.listdir(repo_dir))
98        files_match = True
99        for filename in sorted((temp_files | repo_files) - set(verify_exclude)):
100            if filename not in repo_files:
101                print('ERROR: Missing repo file', filename)
102                files_match = False
103            elif filename not in temp_files:
104                print('ERROR: Missing generator for', filename)
105                files_match = False
106            elif not filecmp.cmp(os.path.join(temp_dir, filename),
107                               os.path.join(repo_dir, filename),
108                               shallow=False):
109                print('ERROR: Repo files do not match generator output for', filename)
110                files_match = False
111
112        # return code for test scripts
113        if files_match:
114            print('SUCCESS: Repo files match generator output')
115            return 0
116        return 1
117
118    elif args.incremental:
119        # copy missing or differing files from temp directory to repo
120        for filename in os.listdir(temp_dir):
121            temp_filename = os.path.join(temp_dir, filename)
122            repo_filename = os.path.join(repo_dir, filename)
123            if not os.path.exists(repo_filename) or \
124               not filecmp.cmp(temp_filename, repo_filename, shallow=False):
125                print('update', repo_filename)
126                shutil.copyfile(temp_filename, repo_filename)
127
128    return 0
129
130if __name__ == '__main__':
131    sys.exit(main(sys.argv[1:]))
132
133