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