tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gen_build_defs.py (11648B)


      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 
     12 import json
     13 import logging
     14 import os
     15 import sys
     16 
     17 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
     18                                             'test')))
     19 
     20 from common import DIR_SRC_ROOT, SDK_ROOT, GN_SDK_ROOT, get_host_os
     21 
     22 assert GN_SDK_ROOT.startswith(DIR_SRC_ROOT)
     23 assert GN_SDK_ROOT[-1] != '/'
     24 GN_SDK_GN_ROOT = GN_SDK_ROOT[len(DIR_SRC_ROOT):]
     25 assert 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 
     32 import("/{GN_SDK_GN_ROOT}/fidl_library.gni")
     33 import("/{GN_SDK_GN_ROOT}/fuchsia_sdk_package.gni")
     34 import("/{GN_SDK_GN_ROOT}/fuchsia_sdk_pkg.gni")
     35 
     36 """
     37 
     38 
     39 def 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 
     45 def 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 
     81 def MetaRootRelativePaths(sdk_relative_paths, meta_root):
     82  return [os.path.relpath(path, meta_root) for path in sdk_relative_paths]
     83 
     84 
     85 def 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 
    108 def 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 
    126 def 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 
    152 def 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 
    180 def 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 
    260 def 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 
    286 def 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 
    317 def 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 
    345 def 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 
    354 def 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 
    373 import("/%s/%s")
    374      """ % (GN_SDK_GN_ROOT, gn_file))
    375 
    376  ProcessSdkManifest()
    377 
    378 
    379 if __name__ == '__main__':
    380  sys.exit(main())