• Home
  • 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
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