1 #!/usr/bin/python3 2 # 3 # Copyright 2020 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 # gen_restricted_traces.py: 8 # Generates integration code for the restricted trace tests. 9 10 import glob 11 import fnmatch 12 import json 13 import os 14 import sys 15 16 GNI_TEMPLATE = """\ 17 # GENERATED FILE - DO NOT EDIT. 18 # Generated by {script_name} using data from {data_source_name} 19 # 20 # Copyright 2020 The ANGLE Project Authors. All rights reserved. 21 # Use of this source code is governed by a BSD-style license that can be 22 # found in the LICENSE file. 23 # 24 # A list of all restricted trace tests, paired with their context. 25 # Can be consumed by tests/BUILD.gn. 26 27 angle_restricted_traces = [ 28 {test_list} 29 ] 30 """ 31 32 HEADER_TEMPLATE = """\ 33 // GENERATED FILE - DO NOT EDIT. 34 // Generated by {script_name} using data from {data_source_name} 35 // 36 // Copyright 2020 The ANGLE Project Authors. All rights reserved. 37 // Use of this source code is governed by a BSD-style license that can be 38 // found in the LICENSE file. 39 // 40 // {filename}: Types and enumerations for trace tests. 41 42 #ifndef ANGLE_RESTRICTED_TRACES_H_ 43 #define ANGLE_RESTRICTED_TRACES_H_ 44 45 #include <cstdint> 46 #include <vector> 47 #include <KHR/khrplatform.h> 48 49 // See util/util_export.h for details on import/export labels. 50 #if !defined(ANGLE_TRACE_EXPORT) 51 # if defined(_WIN32) 52 # if defined(ANGLE_TRACE_IMPLEMENTATION) 53 # define ANGLE_TRACE_EXPORT __declspec(dllexport) 54 # else 55 # define ANGLE_TRACE_EXPORT __declspec(dllimport) 56 # endif 57 # elif defined(__GNUC__) 58 # define ANGLE_TRACE_EXPORT __attribute__((visibility("default"))) 59 # else 60 # define ANGLE_TRACE_EXPORT 61 # endif 62 #endif // !defined(ANGLE_TRACE_EXPORT) 63 64 #if !defined(ANGLE_TRACE_LOADER_EXPORT) 65 # if defined(_WIN32) 66 # if defined(ANGLE_TRACE_LOADER_IMPLEMENTATION) 67 # define ANGLE_TRACE_LOADER_EXPORT __declspec(dllexport) 68 # else 69 # define ANGLE_TRACE_LOADER_EXPORT __declspec(dllimport) 70 # endif 71 # elif defined(__GNUC__) 72 # define ANGLE_TRACE_LOADER_EXPORT __attribute__((visibility("default"))) 73 # else 74 # define ANGLE_TRACE_LOADER_EXPORT 75 # endif 76 #endif // !defined(ANGLE_TRACE_LOADER_EXPORT) 77 78 namespace trace_angle 79 {{ 80 using GenericProc = void (*)(); 81 using LoadProc = GenericProc(KHRONOS_APIENTRY *)(const char *); 82 ANGLE_TRACE_LOADER_EXPORT void LoadGLES(LoadProc loadProc); 83 }} // namespace trace_angle 84 85 namespace angle 86 {{ 87 enum class RestrictedTraceID 88 {{ 89 {trace_ids}, InvalidEnum, EnumCount = InvalidEnum 90 }}; 91 92 static constexpr size_t kTraceInfoMaxNameLen = 32; 93 94 static constexpr uint32_t kDefaultReplayContextClientMajorVersion = 3; 95 static constexpr uint32_t kDefaultReplayContextClientMinorVersion = 1; 96 97 struct TraceInfo 98 {{ 99 uint32_t contextClientMajorVersion; 100 uint32_t contextClientMinorVersion; 101 uint32_t startFrame; 102 uint32_t endFrame; 103 uint32_t drawSurfaceWidth; 104 uint32_t drawSurfaceHeight; 105 char name[kTraceInfoMaxNameLen]; 106 }}; 107 108 ANGLE_TRACE_EXPORT const TraceInfo &GetTraceInfo(RestrictedTraceID traceID); 109 }} // namespace angle 110 111 #endif // ANGLE_RESTRICTED_TRACES_H_ 112 """ 113 114 SOURCE_TEMPLATE = """\ 115 // GENERATED FILE - DO NOT EDIT. 116 // Generated by {script_name} using data from {data_source_name} 117 // 118 // Copyright 2020 The ANGLE Project Authors. All rights reserved. 119 // Use of this source code is governed by a BSD-style license that can be 120 // found in the LICENSE file. 121 // 122 // {filename}: Types and enumerations for trace tests. 123 124 #include "{filename}.h" 125 126 #include "common/PackedEnums.h" 127 #include "common/system_utils.h" 128 129 {trace_includes} 130 131 namespace angle 132 {{ 133 namespace 134 {{ 135 constexpr angle::PackedEnumMap<RestrictedTraceID, TraceInfo> kTraceInfos = {{ 136 {trace_infos} 137 }}; 138 }} 139 140 const TraceInfo &GetTraceInfo(RestrictedTraceID traceID) 141 {{ 142 return kTraceInfos[traceID]; 143 }} 144 }} // namespace angle 145 """ 146 147 CIPD_TRACE_PREFIX = 'angle/traces' 148 DEPS_PATH = '../../../DEPS' 149 DEPS_START = '# === ANGLE Restricted Trace Generated Code Start ===' 150 DEPS_END = '# === ANGLE Restricted Trace Generated Code End ===' 151 DEPS_TEMPLATE = """\ 152 'src/tests/restricted_traces/{trace}': {{ 153 'packages': [ 154 {{ 155 'package': '{trace_prefix}/{trace}', 156 'version': 'version:{version}', 157 }}, 158 ], 159 'dep_type': 'cipd', 160 'condition': 'checkout_angle_restricted_traces', 161 }}, 162 """ 163 164 165 def reject_duplicate_keys(pairs): 166 found_keys = {} 167 for key, value in pairs: 168 if key in found_keys: 169 raise ValueError("duplicate key: %r" % (key,)) 170 else: 171 found_keys[key] = value 172 return found_keys 173 174 175 # TODO(http://anglebug.com/5878): Revert back to non-autogen'ed file names for the angledata.gz. 176 def get_angledata_filename(trace): 177 angledata_files = glob.glob('%s/%s*angledata.gz' % (trace, trace)) 178 assert len(angledata_files) == 1, "Trace '%s' has %d angledata.gz files" % ( 179 trace, len(angledata_files)) 180 return angledata_files[0] 181 182 183 def gen_gni(traces, gni_file, format_args): 184 test_list = [] 185 for trace in traces: 186 context = get_context(trace) 187 angledata_file = get_angledata_filename(trace) 188 with open('%s/%s_capture_context%s_files.txt' % (trace, trace, context)) as f: 189 files = f.readlines() 190 f.close() 191 files = ['"%s/%s"' % (trace, file.strip()) for file in files] 192 test_list += ['["%s", %s, [%s], "%s"]' % (trace, context, ','.join(files), angledata_file)] 193 194 format_args['test_list'] = ',\n'.join(test_list) 195 gni_data = GNI_TEMPLATE.format(**format_args) 196 with open(gni_file, "w") as out_file: 197 out_file.write(gni_data) 198 return True 199 200 201 def contains_context_version(trace): 202 """Determines if the trace contains the major/minor context version""" 203 for file in os.listdir(trace): 204 if fnmatch.fnmatch(file, '*.h'): 205 with open(os.path.join(trace, file)) as f: 206 if 'kReplayContextClientMajorVersion' in f.read(): 207 return True 208 return False 209 210 211 def get_trace_info(trace): 212 # Some traces don't contain major/minor version, so use defaults 213 info = [] 214 defaults = '' 215 if contains_context_version(trace): 216 info += ["%s::kReplayContextClientMajorVersion", "%s::kReplayContextClientMinorVersion"] 217 else: 218 defaults = "kDefaultReplayContextClientMajorVersion, kDefaultReplayContextClientMinorVersion," 219 220 info += [ 221 "%s::kReplayFrameStart", "%s::kReplayFrameEnd", "%s::kReplayDrawSurfaceWidth", 222 "%s::kReplayDrawSurfaceHeight", "\"%s\"" 223 ] 224 225 merged_info = defaults + ", ".join([element % trace for element in info]) 226 return merged_info 227 228 229 def get_context(trace): 230 """Returns the context number used by trace txt file""" 231 for file in os.listdir(trace): 232 # Load up the only header present for each trace 233 if fnmatch.fnmatch(file, '*.txt'): 234 # Strip the extension to isolate the context by scanning 235 # for numbers leading up to the last one, i.e.: 236 # app_capture_context123_files.txt 237 # ^^ 238 # start---||---end 239 start = len(file) - 11 240 end = start + 1 241 while file[start - 1].isdigit(): 242 start -= 1 243 context = file[start:end] 244 assert context.isnumeric(), "Failed to find trace context number" 245 return context 246 247 248 def get_header_name(trace): 249 return "%s/%s_capture_context%s.h" % (trace, trace, get_context(trace)) 250 251 252 def get_source_name(trace): 253 return "%s/%s_capture_context%s.cpp" % (trace, trace, get_context(trace)) 254 255 256 def gen_header(header_file, format_args): 257 header_data = HEADER_TEMPLATE.format(**format_args) 258 with open(header_file, "w") as out_file: 259 out_file.write(header_data) 260 return True 261 262 263 def gen_source(source_file, format_args): 264 source_data = SOURCE_TEMPLATE.format(**format_args) 265 with open(source_file, "w") as out_file: 266 out_file.write(source_data) 267 return True 268 269 270 def gen_git_ignore(traces): 271 ignores = ['%s/' % trace for trace in traces] 272 with open('.gitignore', 'w') as out_file: 273 out_file.write('\n'.join(sorted(ignores))) 274 return True 275 276 277 def read_json(json_file): 278 with open(json_file) as map_file: 279 return json.loads(map_file.read(), object_pairs_hook=reject_duplicate_keys) 280 281 282 def update_deps(trace_pairs): 283 # Generate substitution string 284 replacement = "" 285 for (trace, version) in trace_pairs: 286 sub = {'trace': trace, 'version': version, 'trace_prefix': CIPD_TRACE_PREFIX} 287 replacement += DEPS_TEMPLATE.format(**sub) 288 289 # Update DEPS to download CIPD dependencies 290 new_deps = "" 291 with open(DEPS_PATH) as f: 292 in_deps = False 293 for line in f: 294 if in_deps: 295 if DEPS_END in line: 296 new_deps += replacement 297 new_deps += line 298 in_deps = False 299 else: 300 if DEPS_START in line: 301 new_deps += line 302 in_deps = True 303 else: 304 new_deps += line 305 f.close() 306 307 with open(DEPS_PATH, 'w') as f: 308 f.write(new_deps) 309 f.close() 310 311 return True 312 313 314 def main(): 315 json_file = 'restricted_traces.json' 316 gni_file = 'restricted_traces_autogen.gni' 317 header_file = 'restricted_traces_autogen.h' 318 source_file = 'restricted_traces_autogen.cpp' 319 320 json_data = read_json(json_file) 321 if 'traces' not in json_data: 322 print('Trace data missing traces key.') 323 return 1 324 trace_pairs = [trace.split(' ') for trace in json_data['traces']] 325 traces = [trace_pair[0] for trace_pair in trace_pairs] 326 327 # auto_script parameters. 328 if len(sys.argv) > 1: 329 inputs = [json_file] 330 331 # Note: we do not include DEPS in the list of outputs to simplify the integration. 332 # Otherwise we'd continually need to regenerate on any roll. 333 outputs = [gni_file, header_file, source_file, '.gitignore'] 334 335 if sys.argv[1] == 'inputs': 336 print(','.join(inputs)) 337 elif sys.argv[1] == 'outputs': 338 print(','.join(outputs)) 339 else: 340 print('Invalid script parameters.') 341 return 1 342 return 0 343 344 format_args = { 345 "script_name": os.path.basename(__file__), 346 "data_source_name": json_file, 347 } 348 349 if not gen_gni(traces, gni_file, format_args): 350 print('.gni file generation failed.') 351 return 1 352 353 includes = ["#include \"%s\"" % get_header_name(trace) for trace in traces] 354 trace_infos = [ 355 "{RestrictedTraceID::%s, {%s}}" % (trace, get_trace_info(trace)) for trace in traces 356 ] 357 358 format_args["filename"] = "restricted_traces_autogen" 359 format_args["trace_ids"] = ",\n".join(traces) 360 format_args["trace_includes"] = "\n".join(includes) 361 format_args["trace_infos"] = ",\n".join(trace_infos) 362 if not gen_header(header_file, format_args): 363 print('.h file generation failed.') 364 return 1 365 366 if not gen_source(source_file, format_args): 367 print('.cpp file generation failed.') 368 return 1 369 370 if not gen_git_ignore(traces): 371 print('.gitignore file generation failed') 372 return 1 373 374 if not update_deps(trace_pairs): 375 print('DEPS file update failed') 376 return 1 377 378 return 0 379 380 381 if __name__ == '__main__': 382 sys.exit(main()) 383