• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/python3
2
3# Copyright 2022 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_features.py:
8#  Code generation for ANGLE features.
9#  NOTE: don't run this script directly. Run scripts/run_code_generation.py.
10
11from collections import namedtuple
12import json
13import os
14import re
15import sys
16
17feature_files = {
18    'd3d_features.json': ('D3D', 'FeaturesD3D'),
19    'frontend_features.json': ('Frontend', 'FrontendFeatures'),
20    'gl_features.json': ('OpenGL', 'FeaturesGL'),
21    'mtl_features.json': ('Metal', 'FeaturesMtl'),
22    'vk_features.json': ('Vulkan', 'FeaturesVk'),
23}
24feature_list_header_file = '../../util/autogen/angle_features_autogen.h'
25feature_list_source_file = '../../util/autogen/angle_features_autogen.cpp'
26
27template_header = u"""// GENERATED FILE - DO NOT EDIT.
28// Generated by {script_name} using data from {input_file_name}.
29//
30{description}
31
32#ifndef ANGLE_PLATFORM_AUTOGEN_{NAME}_H_
33#define ANGLE_PLATFORM_AUTOGEN_{NAME}_H_
34
35#include "platform/Feature.h"
36
37namespace angle
38{{
39
40struct {name} : FeatureSetBase
41{{
42    {name}();
43    ~{name}();
44
45{features}
46}};
47
48inline {name}::{name}()  = default;
49inline {name}::~{name}() = default;
50
51}}  // namespace angle
52
53#endif  // ANGLE_PLATFORM_AUTOGEN_{NAME}_H_
54"""
55
56template_feature = u"""    FeatureInfo {var_name} = {{
57        "{display_name}",
58        FeatureCategory::{category},
59        {description},
60        &members,{issue}
61    }};
62"""
63
64template_feature_list_header = u"""// GENERATED FILE - DO NOT EDIT.
65// Generated by {script_name} using data from {input_file_name}.
66//
67// Copyright 2022 The ANGLE Project Authors. All rights reserved.
68// Use of this source code is governed by a BSD-style license that can be
69// found in the LICENSE file.
70//
71// angle_features_autogen.h: List of ANGLE features to help enable/disable them in tests.
72
73#ifndef ANGLE_SRC_TESTS_TEST_UTIL_AUTOGEN_ANGLE_FEATURES_AUTOGEN_H_
74#define ANGLE_SRC_TESTS_TEST_UTIL_AUTOGEN_ANGLE_FEATURES_AUTOGEN_H_
75
76#include "../util_export.h"
77
78namespace angle
79{{
80enum class Feature
81{{
82{features}
83
84    InvalidEnum,
85    EnumCount = InvalidEnum,
86}};
87
88ANGLE_UTIL_EXPORT extern const char *GetFeatureName(Feature feature);
89
90}}  // namespace angle
91
92#endif  // ANGLE_SRC_TESTS_TEST_UTIL_AUTOGEN_ANGLE_FEATURES_AUTOGEN_H_
93"""
94
95template_feature_enum = u"""{VarName},"""
96
97template_feature_list_source = u"""// GENERATED FILE - DO NOT EDIT.
98// Generated by {script_name} using data from {input_file_name}.
99//
100// Copyright 2022 The ANGLE Project Authors. All rights reserved.
101// Use of this source code is governed by a BSD-style license that can be
102// found in the LICENSE file.
103//
104// angle_features_autogen.cpp: List of ANGLE features to help enable/disable them in tests.
105
106#include "angle_features_autogen.h"
107
108#include "common/PackedEnums.h"
109
110namespace angle
111{{
112namespace
113{{
114constexpr PackedEnumMap<Feature, const char *> kFeatureNames = {{{{
115{features}
116}}}};
117}}  // anonymous namespace
118
119const char *GetFeatureName(Feature feature)
120{{
121    return kFeatureNames[feature];
122}}
123
124}}  // namespace angle
125"""
126
127template_feature_string = u"""    {{Feature::{VarName}, "{display_name}"}},"""
128
129
130def make_camel_case(json_name):
131    assert '_' in json_name, 'feature names in the json file are expected to be in snake_case'
132    return re.sub('_(.)', lambda m: m.group(1).upper(), json_name)
133
134
135def make_header_name(class_name):
136    return class_name + '_autogen.h'
137
138
139def header_path(class_name):
140    return 'autogen/' + make_header_name(class_name)
141
142
143def write_or_verify_file(filename, content, verify_only):
144    if verify_only:
145        try:
146            with open(filename) as f:
147                # Note: .gitattributes "* text=auto" handles LF <-> CRLF on Windows
148                return f.read() == content
149        except FileNotFoundError:
150            return False
151    else:
152        with open(filename, 'w') as fout:
153            fout.write(content)
154            return True
155
156
157def main():
158    if len(sys.argv) == 2 and sys.argv[1] == 'inputs':
159        print(','.join(list(feature_files.keys())))
160        return
161    if len(sys.argv) == 2 and sys.argv[1] == 'outputs':
162        print(','.join([header_path(class_name) for (_, class_name) in feature_files.values()]) +
163              ',' + feature_list_header_file + ',' + feature_list_source_file)
164        return
165
166    # --verify-only enables dirty checks without relying on checked in hashes.
167    # Compares the content of the existing file with the generated content.
168    verify_only = '--verify-only' in sys.argv
169
170    name_map = {}
171
172    for src_file, (category_prefix, class_name) in feature_files.items():
173        with open(src_file) as fin:
174            src = json.loads(fin.read())
175
176        features_json = src['features']
177        features = []
178
179        # Go over the list of features and write the header file that declares the features struct
180        for feature_json in features_json:
181            json_name = feature_json['name']
182            var_name = make_camel_case(json_name)
183            # Use the same (camelCase) name for display as well
184            display_name = var_name
185            issue = feature_json.get('issue', None)
186            feature = template_feature.format(
187                var_name=var_name,
188                display_name=display_name,
189                category=category_prefix + feature_json['category'],
190                description='\n        '.join(
191                    '"' + line + '"' for line in feature_json['description']),
192                issue='' if issue is None else ' "' + issue + '"')
193
194            features.append(feature)
195
196            # Keep track of the feature names.  Sometimes the same feature name is present in
197            # multiple backends.  That's ok for the purposes of feature overriding.
198            name_map[var_name] = display_name
199
200        description = '\n'.join(
201            ['//' + (' ' + line if line else '') for line in src['description']])
202        header_file = make_header_name(class_name)
203
204        header = template_header.format(
205            script_name=os.path.basename(__file__),
206            input_file_name=src_file,
207            description=description.replace(src_file, header_file),
208            name=class_name,
209            NAME=class_name.upper(),
210            features='\n'.join(features))
211
212        if not write_or_verify_file(header_path(class_name), header, verify_only):
213            return 1
214
215    # Generate helpers for use by tests to override a feature or not.
216    feature_enums = []
217    feature_strings = []
218    for var_name, display_name in sorted(name_map.items(), key=lambda item: item[0].lower()):
219        VarName = var_name[0].upper() + var_name[1:]
220
221        feature_enums.append(template_feature_enum.format(VarName=VarName))
222
223        feature_strings.append(
224            template_feature_string.format(VarName=VarName, display_name=display_name))
225
226    if not write_or_verify_file(
227            feature_list_header_file,
228            template_feature_list_header.format(
229                script_name=os.path.basename(__file__),
230                input_file_name='*_features.json',
231                features='\n'.join('    ' + v for v in feature_enums)), verify_only):
232        return 1
233
234    if not write_or_verify_file(
235            feature_list_source_file,
236            template_feature_list_source.format(
237                script_name=os.path.basename(__file__),
238                input_file_name='*_features.json',
239                features='\n'.join(feature_strings)), verify_only):
240        return 1
241
242
243if __name__ == '__main__':
244    sys.exit(main())
245