blob: a4296b539dd4df874c54542bc4d6d7ca6bae08a9 [file] [log] [blame]
#!/usr/bin/python3
#
# Copyright 2020 The ANGLE Project Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# gen_restricted_traces.py:
# Generates integration code for the restricted trace tests.
import getpass
import glob
import fnmatch
import json
import os
import sys
GNI_TEMPLATE = """\
# GENERATED FILE - DO NOT EDIT.
# Generated by {script_name} using data from {data_source_name}
#
# Copyright 2020 The ANGLE Project Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# A list of all restricted trace tests, paired with their context.
# Can be consumed by tests/BUILD.gn.
angle_restricted_traces = [
{test_list}
]
"""
HEADER_TEMPLATE = """\
// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// {filename}: Types and enumerations for trace tests.
#ifndef ANGLE_RESTRICTED_TRACES_AUTOGEN_H_
#define ANGLE_RESTRICTED_TRACES_AUTOGEN_H_
#include <cstdint>
#include <vector>
#include <KHR/khrplatform.h>
#include <EGL/egl.h>
#include "restricted_traces_export.h"
namespace trace_angle
{{
using GenericProc = void (*)();
using LoadProc = GenericProc(KHRONOS_APIENTRY *)(const char *);
ANGLE_TRACE_LOADER_EXPORT void LoadEGL(LoadProc loadProc);
ANGLE_TRACE_LOADER_EXPORT void LoadGLES(LoadProc loadProc);
}} // namespace trace_angle
namespace angle
{{
static constexpr size_t kTraceInfoMaxNameLen = 32;
static constexpr uint32_t kDefaultReplayContextClientMajorVersion = 3;
static constexpr uint32_t kDefaultReplayContextClientMinorVersion = 1;
static constexpr uint32_t kDefaultReplayDrawSurfaceColorSpace = EGL_COLORSPACE_LINEAR;
struct TraceInfo
{{
uint32_t contextClientMajorVersion;
uint32_t contextClientMinorVersion;
uint32_t startFrame;
uint32_t endFrame;
uint32_t drawSurfaceWidth;
uint32_t drawSurfaceHeight;
uint32_t drawSurfaceColorSpace;
char name[kTraceInfoMaxNameLen];
}};
ANGLE_TRACE_EXPORT const TraceInfo &GetTraceInfo(const char *traceName);
}} // namespace angle
#endif // ANGLE_RESTRICTED_TRACES_AUTOGEN_H_
"""
SOURCE_TEMPLATE = """\
// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// {filename}: Types and enumerations for trace tests.
#include "{filename}.h"
#include "common/PackedEnums.h"
#include "common/system_utils.h"
{trace_includes}
namespace angle
{{
namespace
{{
constexpr size_t kNumTraces = {num_traces};
struct TracePair
{{
const char name[kTraceInfoMaxNameLen];
TraceInfo info;
}};
constexpr TracePair kTraceInfos[kNumTraces] = {{
{trace_infos}
}};
}}
const TraceInfo &GetTraceInfo(const char *traceName)
{{
// Could be improved using std::lower_bound.
for (const TracePair &tracePair : kTraceInfos)
{{
if (strncmp(tracePair.name, traceName, kTraceInfoMaxNameLen) == 0)
{{
return tracePair.info;
}}
}}
UNREACHABLE();
return kTraceInfos[0].info;
}}
}} // namespace angle
"""
CIPD_TRACE_PREFIX = 'angle/traces'
EXPERIMENTAL_CIPD_PREFIX = 'experimental/google.com/%s/angle/traces'
DEPS_PATH = '../../../DEPS'
DEPS_START = '# === ANGLE Restricted Trace Generated Code Start ==='
DEPS_END = '# === ANGLE Restricted Trace Generated Code End ==='
DEPS_TEMPLATE = """\
'src/tests/restricted_traces/{trace}': {{
'packages': [
{{
'package': '{trace_prefix}/{trace}',
'version': 'version:{version}',
}},
],
'dep_type': 'cipd',
'condition': 'checkout_angle_restricted_traces',
}},
"""
def reject_duplicate_keys(pairs):
found_keys = {}
for key, value in pairs:
if key in found_keys:
raise ValueError("duplicate key: %r" % (key,))
else:
found_keys[key] = value
return found_keys
# TODO(http://anglebug.com/5878): Revert back to non-autogen'ed file names for the angledata.gz.
def get_angledata_filename(trace):
angledata_files = glob.glob('%s/%s*angledata.gz' % (trace, trace))
assert len(angledata_files) == 1, "Trace '%s' has %d angledata.gz files" % (
trace, len(angledata_files))
return angledata_files[0].replace('\\', '/')
def gen_gni(traces, gni_file, format_args):
test_list = []
for trace in traces:
context = get_context(trace)
angledata_file = get_angledata_filename(trace)
with open('%s/%s_capture_context%s_files.txt' % (trace, trace, context)) as f:
files = f.readlines()
f.close()
files = ['"%s/%s"' % (trace, file.strip()) for file in files]
test_list += ['["%s", %s, [%s], "%s"]' % (trace, context, ','.join(files), angledata_file)]
format_args['test_list'] = ',\n'.join(test_list)
gni_data = GNI_TEMPLATE.format(**format_args)
with open(gni_file, "w") as out_file:
out_file.write(gni_data)
return True
def contains_string(trace, string):
"""Determines if the trace contains a string"""
for file in os.listdir(trace):
if fnmatch.fnmatch(file, '*.h'):
with open(os.path.join(trace, file)) as f:
if string in f.read():
return True
return False
def contains_context_version(trace):
"""Determines if the trace contains the major/minor context version"""
return contains_string(trace, 'kReplayContextClientMajorVersion')
def contains_colorspace(trace):
"""Determines if the trace contains an EGL surface color space"""
return contains_string(trace, 'kReplayDrawSurfaceColorSpace')
def get_trace_info(trace):
# Some traces don't contain major/minor version, so use defaults
info = []
if contains_context_version(trace):
info += [
f'{trace}::kReplayContextClientMajorVersion',
f'{trace}::kReplayContextClientMinorVersion'
]
else:
info += [
'kDefaultReplayContextClientMajorVersion', 'kDefaultReplayContextClientMinorVersion'
]
info += [
f'{trace}::kReplayFrameStart', f'{trace}::kReplayFrameEnd',
f'{trace}::kReplayDrawSurfaceWidth', f'{trace}::kReplayDrawSurfaceHeight'
]
if contains_colorspace(trace):
info += [f'{trace}::kReplayDrawSurfaceColorSpace']
else:
info += ['kDefaultReplayDrawSurfaceColorSpace']
info += [f'"{trace}"']
return ", ".join(info)
def get_context(trace):
"""Returns the context number used by trace txt file"""
for file in os.listdir(trace):
# Load up the only header present for each trace
if fnmatch.fnmatch(file, '*.txt'):
# Strip the extension to isolate the context by scanning
# for numbers leading up to the last one, i.e.:
# app_capture_context123_files.txt
# ^^
# start---||---end
start = len(file) - 11
end = start + 1
while file[start - 1].isdigit():
start -= 1
context = file[start:end]
assert context.isnumeric(), 'Failed to find trace context number'
return context
def get_header_name(trace):
return '%s/%s_capture_context%s.h' % (trace, trace, get_context(trace))
def get_source_name(trace):
return '%s/%s_capture_context%s.cpp' % (trace, trace, get_context(trace))
def gen_header(header_file, format_args):
header_data = HEADER_TEMPLATE.format(**format_args)
with open(header_file, "w") as out_file:
out_file.write(header_data)
return True
def gen_source(source_file, format_args):
source_data = SOURCE_TEMPLATE.format(**format_args)
with open(source_file, "w") as out_file:
out_file.write(source_data)
return True
def gen_git_ignore(traces):
ignores = ['%s/' % trace for trace in traces]
with open('.gitignore', 'w') as out_file:
out_file.write('\n'.join(sorted(ignores)))
return True
def read_json(json_file):
with open(json_file) as map_file:
return json.loads(map_file.read(), object_pairs_hook=reject_duplicate_keys)
def update_deps(trace_pairs):
# Generate substitution string
replacement = ""
for (trace, version) in trace_pairs:
if 'x' in version:
version = version.strip('x')
trace_prefix = EXPERIMENTAL_CIPD_PREFIX % getpass.getuser()
else:
trace_prefix = CIPD_TRACE_PREFIX
sub = {'trace': trace, 'version': version, 'trace_prefix': trace_prefix}
replacement += DEPS_TEMPLATE.format(**sub)
# Update DEPS to download CIPD dependencies
new_deps = ""
with open(DEPS_PATH) as f:
in_deps = False
for line in f:
if in_deps:
if DEPS_END in line:
new_deps += replacement
new_deps += line
in_deps = False
else:
if DEPS_START in line:
new_deps += line
in_deps = True
else:
new_deps += line
f.close()
with open(DEPS_PATH, 'w') as f:
f.write(new_deps)
f.close()
return True
def main():
json_file = 'restricted_traces.json'
gni_file = 'restricted_traces_autogen.gni'
header_file = 'restricted_traces_autogen.h'
source_file = 'restricted_traces_autogen.cpp'
json_data = read_json(json_file)
if 'traces' not in json_data:
print('Trace data missing traces key.')
return 1
trace_pairs = [trace.split(' ') for trace in json_data['traces']]
traces = [trace_pair[0] for trace_pair in trace_pairs]
# auto_script parameters.
if len(sys.argv) > 1:
inputs = [json_file]
# Note: we do not include DEPS in the list of outputs to simplify the integration.
# Otherwise we'd continually need to regenerate on any roll.
outputs = [gni_file, header_file, source_file, '.gitignore']
if sys.argv[1] == 'inputs':
print(','.join(inputs))
elif sys.argv[1] == 'outputs':
print(','.join(outputs))
else:
print('Invalid script parameters.')
return 1
return 0
format_args = {
'script_name': os.path.basename(__file__),
'data_source_name': json_file,
}
if not gen_gni(traces, gni_file, format_args):
print('.gni file generation failed.')
return 1
includes = ['#include "%s"' % get_header_name(trace) for trace in traces]
trace_infos = ['{"%s", {%s}}' % (trace, get_trace_info(trace)) for trace in traces]
format_args['filename'] = 'restricted_traces_autogen'
format_args['num_traces'] = len(trace_infos)
format_args['trace_includes'] = '\n'.join(includes)
format_args['trace_infos'] = ',\n'.join(trace_infos)
if not gen_header(header_file, format_args):
print('.h file generation failed.')
return 1
if not gen_source(source_file, format_args):
print('.cpp file generation failed.')
return 1
if not gen_git_ignore(traces):
print('.gitignore file generation failed')
return 1
if not update_deps(trace_pairs):
print('DEPS file update failed')
return 1
return 0
if __name__ == '__main__':
sys.exit(main())