1#!/usr/bin/python3 2# 3# Copyright 2017 The ANGLE Project Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# 7# run_code_generation.py: 8# Runs ANGLE format table and other script code generation scripts. 9 10import argparse 11from concurrent import futures 12import hashlib 13import json 14import os 15import subprocess 16import sys 17import platform 18 19script_dir = sys.path[0] 20root_dir = os.path.abspath(os.path.join(script_dir, '..')) 21 22hash_dir = os.path.join(script_dir, 'code_generation_hashes') 23 24 25def get_child_script_dirname(script): 26 # All script names are relative to ANGLE's root 27 return os.path.dirname(os.path.abspath(os.path.join(root_dir, script))) 28 29 30def get_executable_name(script): 31 with open(script, 'r') as f: 32 # Check shebang 33 binary = os.path.basename(f.readline().strip().replace(' ', '/')) 34 assert binary in ['python3', 'vpython3'] 35 if platform.system() == 'Windows': 36 return binary + '.bat' 37 else: 38 return binary 39 40 41def paths_from_auto_script(script, param): 42 script_dir = get_child_script_dirname(script) 43 # python3 (not vpython3) to get inputs/outputs faster 44 exe = 'python3' 45 try: 46 res = subprocess.check_output([exe, os.path.basename(script), param], 47 cwd=script_dir).decode().strip() 48 except Exception: 49 print('Error with auto_script %s: %s, executable %s' % (param, script, exe)) 50 raise 51 if res == '': 52 return [] 53 return [ 54 os.path.relpath(os.path.join(script_dir, path), root_dir).replace("\\", "/") 55 for path in res.split(',') 56 ] 57 58 59# auto_script is a standard way for scripts to return their inputs and outputs. 60def auto_script(script): 61 info = { 62 'inputs': paths_from_auto_script(script, 'inputs'), 63 'outputs': paths_from_auto_script(script, 'outputs') 64 } 65 return info 66 67 68generators = { 69 'ANGLE format': 70 'src/libANGLE/renderer/gen_angle_format_table.py', 71 'ANGLE load functions table': 72 'src/libANGLE/renderer/gen_load_functions_table.py', 73 'ANGLE shader preprocessor': 74 'src/compiler/preprocessor/generate_parser.py', 75 'ANGLE shader translator': 76 'src/compiler/translator/generate_parser.py', 77 'D3D11 blit shader selection': 78 'src/libANGLE/renderer/d3d/d3d11/gen_blit11helper.py', 79 'D3D11 format': 80 'src/libANGLE/renderer/d3d/d3d11/gen_texture_format_table.py', 81 'DXGI format': 82 'src/libANGLE/renderer/gen_dxgi_format_table.py', 83 'DXGI format support': 84 'src/libANGLE/renderer/gen_dxgi_support_tables.py', 85 'Emulated HLSL functions': 86 'src/compiler/translator/hlsl/gen_emulated_builtin_function_tables.py', 87 'Extension files': 88 'src/libANGLE/gen_extensions.py', 89 'GL copy conversion table': 90 'src/libANGLE/gen_copy_conversion_table.py', 91 'GL CTS (dEQP) build files': 92 'scripts/gen_vk_gl_cts_build.py', 93 'GL/EGL/WGL loader': 94 'scripts/generate_loader.py', 95 'GL/EGL entry points': 96 'scripts/generate_entry_points.py', 97 'GLenum value to string map': 98 'scripts/gen_gl_enum_utils.py', 99 'GL format map': 100 'src/libANGLE/gen_format_map.py', 101 'interpreter utils': 102 'scripts/gen_interpreter_utils.py', 103 'Metal format table': 104 'src/libANGLE/renderer/metal/gen_mtl_format_table.py', 105 'Metal default shaders': 106 'src/libANGLE/renderer/metal/shaders/gen_mtl_internal_shaders.py', 107 'OpenGL dispatch table': 108 'src/libANGLE/renderer/gl/generate_gl_dispatch_table.py', 109 'overlay fonts': 110 'src/libANGLE/gen_overlay_fonts.py', 111 'overlay widgets': 112 'src/libANGLE/gen_overlay_widgets.py', 113 'packed enum': 114 'src/common/gen_packed_gl_enums.py', 115 'proc table': 116 'scripts/gen_proc_table.py', 117 'restricted traces': 118 'src/tests/restricted_traces/gen_restricted_traces.py', 119 'SPIR-V helpers': 120 'src/common/spirv/gen_spirv_builder_and_parser.py', 121 'Static builtins': 122 'src/compiler/translator/gen_builtin_symbols.py', 123 'uniform type': 124 'src/common/gen_uniform_type_table.py', 125 'Vulkan format': 126 'src/libANGLE/renderer/vulkan/gen_vk_format_table.py', 127 'Vulkan internal shader programs': 128 'src/libANGLE/renderer/vulkan/gen_vk_internal_shaders.py', 129 'Vulkan mandatory format support table': 130 'src/libANGLE/renderer/vulkan/gen_vk_mandatory_format_support_table.py', 131} 132 133 134# Fast and supports --verify-only without hashes. 135hashless_generators = { 136 'ANGLE features': 'include/platform/gen_features.py', 137 'Test spec JSON': 'infra/specs/generate_test_spec_json.py', 138} 139 140 141def md5(fname): 142 hash_md5 = hashlib.md5() 143 with open(fname, "r") as f: 144 for chunk in iter(lambda: f.read(4096), ""): 145 hash_md5.update(chunk.encode()) 146 return hash_md5.hexdigest() 147 148 149def get_hash_file_name(name): 150 return name.replace(' ', '_').replace('/', '_') + '.json' 151 152 153def any_hash_dirty(name, filenames, new_hashes, old_hashes): 154 found_dirty_hash = False 155 156 for fname in filenames: 157 if not os.path.isfile(os.path.join(root_dir, fname)): 158 print('File not found: "%s". Code gen dirty for %s' % (fname, name)) 159 found_dirty_hash = True 160 else: 161 new_hashes[fname] = md5(fname) 162 if (not fname in old_hashes) or (old_hashes[fname] != new_hashes[fname]): 163 print('Hash for "%s" dirty for %s generator.' % (fname, name)) 164 found_dirty_hash = True 165 return found_dirty_hash 166 167 168def any_old_hash_missing(all_new_hashes, all_old_hashes): 169 result = False 170 for file, old_hashes in all_old_hashes.items(): 171 if file not in all_new_hashes: 172 print('"%s" does not exist. Code gen dirty.' % file) 173 result = True 174 else: 175 for name, _ in old_hashes.items(): 176 if name not in all_new_hashes[file]: 177 print('Hash for %s is missing from "%s". Code gen is dirty.' % (name, file)) 178 result = True 179 return result 180 181 182def update_output_hashes(script, outputs, new_hashes): 183 for output in outputs: 184 if not os.path.isfile(output): 185 print('Output is missing from %s: %s' % (script, output)) 186 sys.exit(1) 187 new_hashes[output] = md5(output) 188 189 190def load_hashes(): 191 hashes = {} 192 for file in os.listdir(hash_dir): 193 hash_fname = os.path.join(hash_dir, file) 194 with open(hash_fname) as hash_file: 195 try: 196 hashes[file] = json.load(hash_file) 197 except ValueError: 198 raise Exception("Could not decode JSON from %s" % file) 199 return hashes 200 201 202def main(): 203 all_old_hashes = load_hashes() 204 all_new_hashes = {} 205 any_dirty = False 206 207 parser = argparse.ArgumentParser(description='Generate ANGLE internal code.') 208 parser.add_argument( 209 '-v', 210 '--verify-no-dirty', 211 dest='verify_only', 212 action='store_true', 213 help='verify hashes are not dirty') 214 parser.add_argument( 215 '-g', '--generator', action='append', nargs='*', type=str, dest='specified_generators'), 216 217 args = parser.parse_args() 218 219 ranGenerators = generators 220 runningSingleGenerator = False 221 if (args.specified_generators): 222 ranGenerators = {k: v for k, v in generators.items() if k in args.specified_generators[0]} 223 runningSingleGenerator = True 224 225 if len(ranGenerators) == 0: 226 print("No valid generators specified.") 227 return 1 228 229 # Just get 'inputs' and 'outputs' from scripts but this runs the scripts so it's a bit slow 230 infos = {} 231 with futures.ThreadPoolExecutor(max_workers=8) as executor: 232 for _, script in sorted(ranGenerators.items()): 233 infos[script] = executor.submit(auto_script, script) 234 235 for name, script in sorted(ranGenerators.items()): 236 info = infos[script].result() 237 fname = get_hash_file_name(name) 238 filenames = info['inputs'] + info['outputs'] + [script] 239 new_hashes = {} 240 if fname not in all_old_hashes: 241 all_old_hashes[fname] = {} 242 if any_hash_dirty(name, filenames, new_hashes, all_old_hashes[fname]): 243 any_dirty = True 244 245 if not args.verify_only: 246 print('Running ' + name + ' code generator') 247 248 exe = get_executable_name(script) 249 subprocess.check_call([exe, os.path.basename(script)], 250 cwd=get_child_script_dirname(script)) 251 252 # Update the hash dictionary. 253 all_new_hashes[fname] = new_hashes 254 255 if not runningSingleGenerator and any_old_hash_missing(all_new_hashes, all_old_hashes): 256 any_dirty = True 257 258 # Handle hashless_generators separately as these don't have hash maps. 259 hashless_generators_dirty = False 260 for name, script in sorted(hashless_generators.items()): 261 cmd = [get_executable_name(script), os.path.basename(script)] 262 rc = subprocess.call(cmd + ['--verify-only'], cwd=get_child_script_dirname(script)) 263 if rc != 0: 264 print(name + ' generator dirty') 265 # Don't set any_dirty as we don't need git cl format in this case. 266 hashless_generators_dirty = True 267 268 if not args.verify_only: 269 print('Running ' + name + ' code generator') 270 subprocess.check_call(cmd, cwd=get_child_script_dirname(script)) 271 272 if args.verify_only: 273 return int(any_dirty or hashless_generators_dirty) 274 275 if any_dirty: 276 args = ['git.bat'] if os.name == 'nt' else ['git'] 277 args += ['cl', 'format'] 278 print('Calling git cl format') 279 subprocess.check_call(args) 280 281 # Update the output hashes again since they can be formatted. 282 for name, script in sorted(ranGenerators.items()): 283 info = auto_script(script) 284 fname = get_hash_file_name(name) 285 update_output_hashes(name, info['outputs'], all_new_hashes[fname]) 286 287 for fname, new_hashes in all_new_hashes.items(): 288 hash_fname = os.path.join(hash_dir, fname) 289 with open(hash_fname, "w") as f: 290 json.dump(new_hashes, f, indent=2, sort_keys=True, separators=(',', ':\n ')) 291 f.write('\n') # json.dump doesn't end with newline 292 293 return 0 294 295 296if __name__ == '__main__': 297 sys.exit(main()) 298