• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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