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 10import glob 11import fnmatch 12import json 13import os 14import sys 15 16GNI_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 27angle_restricted_traces = [ 28{test_list} 29] 30""" 31 32HEADER_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 78namespace trace_angle 79{{ 80using GenericProc = void (*)(); 81using LoadProc = GenericProc(KHRONOS_APIENTRY *)(const char *); 82ANGLE_TRACE_LOADER_EXPORT void LoadGLES(LoadProc loadProc); 83}} // namespace trace_angle 84 85namespace angle 86{{ 87enum class RestrictedTraceID 88{{ 89{trace_ids}, InvalidEnum, EnumCount = InvalidEnum 90}}; 91 92static constexpr size_t kTraceInfoMaxNameLen = 32; 93 94static constexpr uint32_t kDefaultReplayContextClientMajorVersion = 3; 95static constexpr uint32_t kDefaultReplayContextClientMinorVersion = 1; 96 97struct 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 108ANGLE_TRACE_EXPORT const TraceInfo &GetTraceInfo(RestrictedTraceID traceID); 109}} // namespace angle 110 111#endif // ANGLE_RESTRICTED_TRACES_H_ 112""" 113 114SOURCE_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 131namespace angle 132{{ 133namespace 134{{ 135constexpr angle::PackedEnumMap<RestrictedTraceID, TraceInfo> kTraceInfos = {{ 136{trace_infos} 137}}; 138}} 139 140const TraceInfo &GetTraceInfo(RestrictedTraceID traceID) 141{{ 142 return kTraceInfos[traceID]; 143}} 144}} // namespace angle 145""" 146 147CIPD_TRACE_PREFIX = 'angle/traces' 148DEPS_PATH = '../../../DEPS' 149DEPS_START = '# === ANGLE Restricted Trace Generated Code Start ===' 150DEPS_END = '# === ANGLE Restricted Trace Generated Code End ===' 151DEPS_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 165def 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. 176def 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 183def 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 201def 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 211def 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 229def 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 248def get_header_name(trace): 249 return "%s/%s_capture_context%s.h" % (trace, trace, get_context(trace)) 250 251 252def get_source_name(trace): 253 return "%s/%s_capture_context%s.cpp" % (trace, trace, get_context(trace)) 254 255 256def 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 263def 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 270def 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 277def 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 282def 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 314def 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 381if __name__ == '__main__': 382 sys.exit(main()) 383