xref: /aosp_15_r20/external/angle/build/fuchsia/gen_build_defs.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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