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.
6 # Generates a single BUILD.gn file with build targets generated using the
7 # manifest files in the SDK.
14 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
17 from common import DIR_SRC_ROOT, SDK_ROOT, get_host_os
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.
24 import("//third_party/fuchsia-gn-sdk/src/fidl_library.gni")
25 import("//third_party/fuchsia-gn-sdk/src/fuchsia_sdk_pkg.gni")
30 def ReformatTargetName(dep_name):
31 """"Substitutes characters in |dep_name| which are not valid in GN target
32 names (e.g. dots become hyphens)."""
36 def FormatGNTarget(fields):
37 """Returns a GN target definition as a string.
39 |fields|: The GN fields to include in the target body.
40 'target_name' and 'type' are mandatory."""
42 output = '%s("%s") {\n' % (fields['type'], fields['target_name'])
43 del fields['target_name']
46 # Ensure that fields with no ordering requirement are sorted.
47 for field in ['sources', 'public_deps']:
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.
59 val_serialized = '[ \"%s\" ]' % val[0]
61 val_serialized = '[\n ' + ',\n '.join(['\"%s\"' % x
62 for x in val]) + '\n ]'
64 raise Exception('Could not serialize %r' % val)
66 output += ' %s = %s\n' % (key, val_serialized)
72 def MetaRootRelativePaths(sdk_relative_paths, meta_root):
73 return [os.path.relpath(path, meta_root) for path in sdk_relative_paths]
76 def 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."""
80 meta_root = json['root']
82 converted = {'target_name': ReformatTargetName(json['name'])}
85 converted['public_deps'] = MetaRootRelativePaths(json['deps'],
86 os.path.dirname(meta_root))
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']
99 def ConvertFidlLibrary(json):
100 """Converts a fidl_library manifest entry to a GN target.
103 json: The parsed manifest JSON.
105 The GN target definition, represented as a string."""
107 meta_root = json['root']
109 converted = ConvertCommonFields(json)
110 converted['type'] = 'fidl_library'
111 converted['sources'] = MetaRootRelativePaths(json['sources'], meta_root)
112 converted['library_name'] = json['name']
117 def ConvertCcPrebuiltLibrary(json):
118 """Converts a cc_prebuilt_library manifest entry to a GN target.
121 json: The parsed manifest JSON.
123 The GN target definition, represented as a string."""
125 meta_root = json['root']
127 converted = ConvertCommonFields(json)
128 converted['type'] = 'fuchsia_sdk_pkg'
130 converted['sources'] = MetaRootRelativePaths(json['headers'], meta_root)
132 converted['include_dirs'] = MetaRootRelativePaths([json['include_dir']],
135 if json['format'] == 'shared':
136 converted['shared_libs'] = [json['name']]
138 converted['static_libs'] = [json['name']]
143 def ConvertCcSourceLibrary(json):
144 """Converts a cc_source_library manifest entry to a GN target.
147 json: The parsed manifest JSON.
149 The GN target definition, represented as a string."""
151 meta_root = json['root']
153 converted = ConvertCommonFields(json)
154 converted['type'] = 'fuchsia_sdk_pkg'
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)
162 converted['sources'] += MetaRootRelativePaths(json['files'], meta_root)
163 converted['sources'] = list(set(converted['sources']))
165 converted['include_dirs'] = MetaRootRelativePaths([json['include_dir']],
171 def ConvertLoadableModule(json):
172 """Converts a loadable module manifest entry to GN targets.
175 json: The parsed manifest JSON.
177 A list of GN target definitions."""
180 if name != 'vulkan_layers':
181 raise RuntimeError('Unsupported loadable_module: %s' % name)
183 # Copy resources and binaries
184 resources = json['resources']
186 binaries = json['binaries']
188 def _filename_no_ext(name):
189 return os.path.splitext(os.path.basename(name))[0]
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']
199 for res in resources:
200 layer_name = _filename_no_ext(res)
202 # Filter binaries for a matching name.
203 filtered = [n for n in binary_names if _filename_no_ext(n) == layer_name]
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.
211 # Replace hardcoded arch in the found binary filename.
212 binary = filtered[0].replace('/' + arch + '/', "/${target_cpu}/")
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)
219 vulkan_targets.append(target)
223 all_target['target_name'] = 'all'
224 all_target['type'] = 'group'
225 all_target['data_deps'] = []
226 for target in vulkan_targets:
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)
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)
240 group_target['target_name'] = target['name']
241 group_target['type'] = 'group'
242 group_target['data_deps'] = [
243 ':' + target['name'] + '_config', ':' + target['name'] + '_lib'
245 converted.append(group_target)
246 all_target['data_deps'].append(':' + target['name'])
247 converted.append(all_target)
251 def ConvertNoOp(json):
252 """Null implementation of a conversion function. No output is generated."""
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,
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,
270 'device_profile': ConvertNoOp,
271 'documentation': ConvertNoOp,
272 'ffx_tool': ConvertNoOp,
273 'host_tool': ConvertNoOp,
274 'image': ConvertNoOp,
275 'sysroot': ConvertNoOp,
279 def ConvertMeta(meta_path):
280 parsed = json.load(open(meta_path))
281 if 'type' not in parsed:
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))
290 converted = convert_function(parsed)
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)
299 # Loadable modules have multiple targets
300 if convert_function != ConvertLoadableModule:
301 buildfile.write(FormatGNTarget(converted) + '\n\n')
303 for target in converted:
304 buildfile.write(FormatGNTarget(target) + '\n\n')
307 def ProcessSdkManifest():
308 toplevel_meta = json.load(
309 open(os.path.join(SDK_ROOT, 'meta', 'manifest.json')))
311 for part in toplevel_meta['parts']:
312 meta_path = os.path.join(SDK_ROOT, part['meta'])
313 ConvertMeta(meta_path)
318 # Exit if there's no Fuchsia support for this platform.
322 logging.warning('Fuchsia SDK is not supported on this platform.')
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.
335 import("//third_party/fuchsia-gn-sdk/src/{}")
341 if __name__ == '__main__':