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