1#!/usr/bin/env vpython3 2# Copyright 2023 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5# 6# Generates a single BUILD.gn file with build targets generated using the 7# manifest files in the SDK. 8 9import json 10import logging 11import os 12import sys 13 14sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 'test'))) 16 17from common import DIR_SRC_ROOT, SDK_ROOT, get_host_os 18 19# Inserted at the top of the generated BUILD.gn file. 20_GENERATED_PREAMBLE = """# DO NOT EDIT! This file was generated by 21# //build/fuchsia/gen_build_def.py. 22# Any changes made to this file will be discarded. 23 24import("//third_party/fuchsia-gn-sdk/src/fidl_library.gni") 25import("//third_party/fuchsia-gn-sdk/src/fuchsia_sdk_pkg.gni") 26 27""" 28 29 30def ReformatTargetName(dep_name): 31 """"Substitutes characters in |dep_name| which are not valid in GN target 32 names (e.g. dots become hyphens).""" 33 return dep_name 34 35 36def FormatGNTarget(fields): 37 """Returns a GN target definition as a string. 38 39 |fields|: The GN fields to include in the target body. 40 'target_name' and 'type' are mandatory.""" 41 42 output = '%s("%s") {\n' % (fields['type'], fields['target_name']) 43 del fields['target_name'] 44 del fields['type'] 45 46 # Ensure that fields with no ordering requirement are sorted. 47 for field in ['sources', 'public_deps']: 48 if field in fields: 49 fields[field].sort() 50 51 for key, val in fields.items(): 52 if isinstance(val, str): 53 val_serialized = '\"%s\"' % val 54 elif isinstance(val, list): 55 # Serialize a list of strings in the prettiest possible manner. 56 if len(val) == 0: 57 val_serialized = '[]' 58 elif len(val) == 1: 59 val_serialized = '[ \"%s\" ]' % val[0] 60 else: 61 val_serialized = '[\n ' + ',\n '.join(['\"%s\"' % x 62 for x in val]) + '\n ]' 63 else: 64 raise Exception('Could not serialize %r' % val) 65 66 output += ' %s = %s\n' % (key, val_serialized) 67 output += '}' 68 69 return output 70 71 72def MetaRootRelativePaths(sdk_relative_paths, meta_root): 73 return [os.path.relpath(path, meta_root) for path in sdk_relative_paths] 74 75 76def ConvertCommonFields(json): 77 """Extracts fields from JSON manifest data which are used across all 78 target types. Note that FIDL packages do their own processing.""" 79 80 meta_root = json['root'] 81 82 converted = {'target_name': ReformatTargetName(json['name'])} 83 84 if 'deps' in json: 85 converted['public_deps'] = MetaRootRelativePaths(json['deps'], 86 os.path.dirname(meta_root)) 87 88 # FIDL bindings dependencies are relative to the "fidl" sub-directory. 89 if 'fidl_binding_deps' in json: 90 for entry in json['fidl_binding_deps']: 91 converted['public_deps'] += MetaRootRelativePaths([ 92 'fidl/' + dep + ':' + os.path.basename(dep) + '_' + 93 entry['binding_type'] for dep in entry['deps'] 94 ], meta_root) 95 96 return converted 97 98 99def ConvertFidlLibrary(json): 100 """Converts a fidl_library manifest entry to a GN target. 101 102 Arguments: 103 json: The parsed manifest JSON. 104 Returns: 105 The GN target definition, represented as a string.""" 106 107 meta_root = json['root'] 108 109 converted = ConvertCommonFields(json) 110 converted['type'] = 'fidl_library' 111 converted['sources'] = MetaRootRelativePaths(json['sources'], meta_root) 112 converted['library_name'] = json['name'] 113 114 return converted 115 116 117def ConvertCcPrebuiltLibrary(json): 118 """Converts a cc_prebuilt_library manifest entry to a GN target. 119 120 Arguments: 121 json: The parsed manifest JSON. 122 Returns: 123 The GN target definition, represented as a string.""" 124 125 meta_root = json['root'] 126 127 converted = ConvertCommonFields(json) 128 converted['type'] = 'fuchsia_sdk_pkg' 129 130 converted['sources'] = MetaRootRelativePaths(json['headers'], meta_root) 131 132 converted['include_dirs'] = MetaRootRelativePaths([json['include_dir']], 133 meta_root) 134 135 if json['format'] == 'shared': 136 converted['shared_libs'] = [json['name']] 137 else: 138 converted['static_libs'] = [json['name']] 139 140 return converted 141 142 143def ConvertCcSourceLibrary(json): 144 """Converts a cc_source_library manifest entry to a GN target. 145 146 Arguments: 147 json: The parsed manifest JSON. 148 Returns: 149 The GN target definition, represented as a string.""" 150 151 meta_root = json['root'] 152 153 converted = ConvertCommonFields(json) 154 converted['type'] = 'fuchsia_sdk_pkg' 155 156 # Headers and source file paths can be scattered across "sources", "headers", 157 # and "files". Merge them together into one source list. 158 converted['sources'] = MetaRootRelativePaths(json['sources'], meta_root) 159 if 'headers' in json: 160 converted['sources'] += MetaRootRelativePaths(json['headers'], meta_root) 161 if 'files' in json: 162 converted['sources'] += MetaRootRelativePaths(json['files'], meta_root) 163 converted['sources'] = list(set(converted['sources'])) 164 165 converted['include_dirs'] = MetaRootRelativePaths([json['include_dir']], 166 meta_root) 167 168 return converted 169 170 171def ConvertLoadableModule(json): 172 """Converts a loadable module manifest entry to GN targets. 173 174 Arguments: 175 json: The parsed manifest JSON. 176 Returns: 177 A list of GN target definitions.""" 178 179 name = json['name'] 180 if name != 'vulkan_layers': 181 raise RuntimeError('Unsupported loadable_module: %s' % name) 182 183 # Copy resources and binaries 184 resources = json['resources'] 185 186 binaries = json['binaries'] 187 188 def _filename_no_ext(name): 189 return os.path.splitext(os.path.basename(name))[0] 190 191 # Pair each json resource with its corresponding binary. Each such pair 192 # is a "layer". We only need to check one arch because each arch has the 193 # same list of binaries. 194 arch = next(iter(binaries)) 195 binary_names = binaries[arch] 196 local_pkg = json['root'] 197 vulkan_targets = [] 198 199 for res in resources: 200 layer_name = _filename_no_ext(res) 201 202 # Filter binaries for a matching name. 203 filtered = [n for n in binary_names if _filename_no_ext(n) == layer_name] 204 205 if not filtered: 206 # If the binary could not be found then do not generate a 207 # target for this layer. The missing targets will cause a 208 # mismatch with the "golden" outputs. 209 continue 210 211 # Replace hardcoded arch in the found binary filename. 212 binary = filtered[0].replace('/' + arch + '/', "/${target_cpu}/") 213 214 target = {} 215 target['name'] = layer_name 216 target['config'] = os.path.relpath(res, start=local_pkg) 217 target['binary'] = os.path.relpath(binary, start=local_pkg) 218 219 vulkan_targets.append(target) 220 221 converted = [] 222 all_target = {} 223 all_target['target_name'] = 'all' 224 all_target['type'] = 'group' 225 all_target['data_deps'] = [] 226 for target in vulkan_targets: 227 config_target = {} 228 config_target['target_name'] = target['name'] + '_config' 229 config_target['type'] = 'copy' 230 config_target['sources'] = [target['config']] 231 config_target['outputs'] = ['${root_gen_dir}/' + target['config']] 232 converted.append(config_target) 233 lib_target = {} 234 lib_target['target_name'] = target['name'] + '_lib' 235 lib_target['type'] = 'copy' 236 lib_target['sources'] = [target['binary']] 237 lib_target['outputs'] = ['${root_out_dir}/lib/{{source_file_part}}'] 238 converted.append(lib_target) 239 group_target = {} 240 group_target['target_name'] = target['name'] 241 group_target['type'] = 'group' 242 group_target['data_deps'] = [ 243 ':' + target['name'] + '_config', ':' + target['name'] + '_lib' 244 ] 245 converted.append(group_target) 246 all_target['data_deps'].append(':' + target['name']) 247 converted.append(all_target) 248 return converted 249 250 251def ConvertNoOp(json): 252 """Null implementation of a conversion function. No output is generated.""" 253 254 return None 255 256 257"""Maps manifest types to conversion functions.""" 258_CONVERSION_FUNCTION_MAP = { 259 'fidl_library': ConvertFidlLibrary, 260 'cc_source_library': ConvertCcSourceLibrary, 261 'cc_prebuilt_library': ConvertCcPrebuiltLibrary, 262 'loadable_module': ConvertLoadableModule, 263 264 # No need to build targets for these types yet. 265 'companion_host_tool': ConvertNoOp, 266 'component_manifest': ConvertNoOp, 267 'config': ConvertNoOp, 268 'dart_library': ConvertNoOp, 269 'data': ConvertNoOp, 270 'device_profile': ConvertNoOp, 271 'documentation': ConvertNoOp, 272 'ffx_tool': ConvertNoOp, 273 'host_tool': ConvertNoOp, 274 'image': ConvertNoOp, 275 'sysroot': ConvertNoOp, 276} 277 278 279def ConvertMeta(meta_path): 280 parsed = json.load(open(meta_path)) 281 if 'type' not in parsed: 282 return 283 284 convert_function = _CONVERSION_FUNCTION_MAP.get(parsed['type']) 285 if convert_function is None: 286 logging.warning('Unexpected SDK artifact type %s in %s.' % 287 (parsed['type'], meta_path)) 288 return 289 290 converted = convert_function(parsed) 291 if not converted: 292 return 293 output_path = os.path.join(os.path.dirname(meta_path), 'BUILD.gn') 294 if os.path.exists(output_path): 295 os.unlink(output_path) 296 with open(output_path, 'w') as buildfile: 297 buildfile.write(_GENERATED_PREAMBLE) 298 299 # Loadable modules have multiple targets 300 if convert_function != ConvertLoadableModule: 301 buildfile.write(FormatGNTarget(converted) + '\n\n') 302 else: 303 for target in converted: 304 buildfile.write(FormatGNTarget(target) + '\n\n') 305 306 307def ProcessSdkManifest(): 308 toplevel_meta = json.load( 309 open(os.path.join(SDK_ROOT, 'meta', 'manifest.json'))) 310 311 for part in toplevel_meta['parts']: 312 meta_path = os.path.join(SDK_ROOT, part['meta']) 313 ConvertMeta(meta_path) 314 315 316def main(): 317 318 # Exit if there's no Fuchsia support for this platform. 319 try: 320 get_host_os() 321 except: 322 logging.warning('Fuchsia SDK is not supported on this platform.') 323 return 0 324 325 # TODO(crbug/1432399): Remove this when links to these files inside the sdk 326 # directory have been redirected. 327 build_path = os.path.join(SDK_ROOT, 'build') 328 os.makedirs(build_path, exist_ok=True) 329 for gn_file in ['component.gni', 'package.gni']: 330 open(os.path.join(build_path, gn_file), 331 "w").write("""# DO NOT EDIT! This file was generated by 332# //build/fuchsia/gen_build_def.py. 333# Any changes made to this file will be discarded. 334 335import("//third_party/fuchsia-gn-sdk/src/{}") 336 """.format(gn_file)) 337 338 ProcessSdkManifest() 339 340 341if __name__ == '__main__': 342 sys.exit(main()) 343