• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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