blob: bcc0f432eb456c23f486b004139298c0599bf409 [file] [log] [blame]
Sami Kyostila865d1d32017-12-12 18:37:04 +00001#!/usr/bin/env python
2# Copyright (C) 2017 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# This tool translates a collection of BUILD.gn files into a mostly equivalent
17# Android.bp file for the Android Soong build system. The input to the tool is a
18# JSON description of the GN build definition generated with the following
19# command:
20#
21# gn desc out --format=json --all-toolchains "//*" > desc.json
22#
23# The tool is then given a list of GN labels for which to generate Android.bp
24# build rules. The dependencies for the GN labels are squashed to the generated
25# Android.bp target, except for actions which get their own genrule. Some
26# libraries are also mapped to their Android equivalents -- see |builtin_deps|.
27
28import argparse
29import json
30import os
31import re
Sami Kyostilab27619f2017-12-13 19:22:16 +000032import shutil
33import subprocess
Sami Kyostila865d1d32017-12-12 18:37:04 +000034import sys
35
Sami Kyostilab27619f2017-12-13 19:22:16 +000036# Default targets to translate to the blueprint file.
Primiano Tucci4e49c022017-12-21 18:22:44 +010037default_targets = [
38 '//:perfetto_tests',
39 '//:traced',
40 '//src/tracing:consumer_cmd',
41]
Sami Kyostilab27619f2017-12-13 19:22:16 +000042
43# Arguments for the GN output directory.
44gn_args = 'target_os="android" target_cpu="arm" is_debug=false'
45
Sami Kyostila865d1d32017-12-12 18:37:04 +000046# All module names are prefixed with this string to avoid collisions.
47module_prefix = 'perfetto_'
48
49# Shared libraries which are directly translated to Android system equivalents.
50library_whitelist = [
51 'android',
52 'log',
53]
54
55# Name of the module which settings such as compiler flags for all other
56# modules.
57defaults_module = module_prefix + 'defaults'
58
59# Location of the project in the Android source tree.
60tree_path = 'external/perfetto'
61
62
63def enable_gmock(module):
64 module.static_libs.append('libgmock')
65
66
Hector Dearman3e712a02017-12-19 16:39:59 +000067def enable_gtest_prod(module):
68 module.static_libs.append('libgtest_prod')
69
70
Sami Kyostila865d1d32017-12-12 18:37:04 +000071def enable_gtest(module):
72 assert module.type == 'cc_test'
73
74
75def enable_protobuf_full(module):
76 module.shared_libs.append('libprotobuf-cpp-full')
77
78
79def enable_protobuf_lite(module):
80 module.shared_libs.append('libprotobuf-cpp-lite')
81
82
83def enable_protoc_lib(module):
84 module.shared_libs.append('libprotoc')
85
86
87def enable_libunwind(module):
Sami Kyostilafc074d42017-12-15 10:33:42 +000088 # libunwind is disabled on Darwin so we cannot depend on it.
89 pass
Sami Kyostila865d1d32017-12-12 18:37:04 +000090
91
92# Android equivalents for third-party libraries that the upstream project
93# depends on.
94builtin_deps = {
95 '//buildtools:gmock': enable_gmock,
96 '//buildtools:gtest': enable_gtest,
Hector Dearman3e712a02017-12-19 16:39:59 +000097 '//gn:gtest_prod_config': enable_gtest_prod,
Sami Kyostila865d1d32017-12-12 18:37:04 +000098 '//buildtools:gtest_main': enable_gtest,
99 '//buildtools:libunwind': enable_libunwind,
100 '//buildtools:protobuf_full': enable_protobuf_full,
101 '//buildtools:protobuf_lite': enable_protobuf_lite,
102 '//buildtools:protoc_lib': enable_protoc_lib,
103}
104
105# ----------------------------------------------------------------------------
106# End of configuration.
107# ----------------------------------------------------------------------------
108
109
110class Error(Exception):
111 pass
112
113
114class ThrowingArgumentParser(argparse.ArgumentParser):
115 def __init__(self, context):
116 super(ThrowingArgumentParser, self).__init__()
117 self.context = context
118
119 def error(self, message):
120 raise Error('%s: %s' % (self.context, message))
121
122
123class Module(object):
124 """A single module (e.g., cc_binary, cc_test) in a blueprint."""
125
126 def __init__(self, mod_type, name):
127 self.type = mod_type
128 self.name = name
129 self.srcs = []
130 self.comment = None
131 self.shared_libs = []
132 self.static_libs = []
133 self.tools = []
134 self.cmd = None
135 self.out = []
136 self.export_include_dirs = []
137 self.generated_headers = []
138 self.defaults = []
139 self.cflags = []
140 self.local_include_dirs = []
141
142 def to_string(self, output):
143 if self.comment:
144 output.append('// %s' % self.comment)
145 output.append('%s {' % self.type)
146 self._output_field(output, 'name')
147 self._output_field(output, 'srcs')
148 self._output_field(output, 'shared_libs')
149 self._output_field(output, 'static_libs')
150 self._output_field(output, 'tools')
151 self._output_field(output, 'cmd', sort=False)
152 self._output_field(output, 'out')
153 self._output_field(output, 'export_include_dirs')
154 self._output_field(output, 'generated_headers')
155 self._output_field(output, 'defaults')
156 self._output_field(output, 'cflags')
157 self._output_field(output, 'local_include_dirs')
158 output.append('}')
159 output.append('')
160
161 def _output_field(self, output, name, sort=True):
162 value = getattr(self, name)
163 if not value:
164 return
165 if isinstance(value, list):
166 output.append(' %s: [' % name)
167 for item in sorted(value) if sort else value:
168 output.append(' "%s",' % item)
169 output.append(' ],')
170 else:
171 output.append(' %s: "%s",' % (name, value))
172
173
174class Blueprint(object):
175 """In-memory representation of an Android.bp file."""
176
177 def __init__(self):
178 self.modules = {}
179
180 def add_module(self, module):
181 """Adds a new module to the blueprint, replacing any existing module
182 with the same name.
183
184 Args:
185 module: Module instance.
186 """
187 self.modules[module.name] = module
188
189 def to_string(self, output):
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000190 for m in sorted(self.modules.itervalues(), key=lambda m: m.name):
Sami Kyostila865d1d32017-12-12 18:37:04 +0000191 m.to_string(output)
192
193
194def label_to_path(label):
195 """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
196 assert label.startswith('//')
197 return label[2:]
198
199
200def label_to_module_name(label):
201 """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
Primiano Tucci4e49c022017-12-21 18:22:44 +0100202 module = re.sub(r'^//:?', '', label)
203 module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
204 if not module.startswith(module_prefix) and label not in default_targets:
Sami Kyostila865d1d32017-12-12 18:37:04 +0000205 return module_prefix + module
206 return module
207
208
209def label_without_toolchain(label):
210 """Strips the toolchain from a GN label.
211
212 Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
213 gcc_like_host) without the parenthesised toolchain part.
214 """
215 return label.split('(')[0]
216
217
218def is_supported_source_file(name):
219 """Returns True if |name| can appear in a 'srcs' list."""
220 return os.path.splitext(name)[1] in ['.c', '.cc', '.proto']
221
222
223def is_generated_by_action(desc, label):
224 """Checks if a label is generated by an action.
225
226 Returns True if a GN output label |label| is an output for any action,
227 i.e., the file is generated dynamically.
228 """
229 for target in desc.itervalues():
230 if target['type'] == 'action' and label in target['outputs']:
231 return True
232 return False
233
234
235def apply_module_dependency(blueprint, desc, module, dep_name):
236 """Recursively collect dependencies for a given module.
237
238 Walk the transitive dependencies for a GN target and apply them to a given
239 module. This effectively flattens the dependency tree so that |module|
240 directly contains all the sources, libraries, etc. in the corresponding GN
241 dependency tree.
242
243 Args:
244 blueprint: Blueprint instance which is being generated.
245 desc: JSON GN description.
246 module: Module to which dependencies should be added.
247 dep_name: GN target of the dependency.
248 """
Sami Kyostila865d1d32017-12-12 18:37:04 +0000249 # If the dependency refers to a library which we can replace with an Android
250 # equivalent, stop recursing and patch the dependency in.
251 if label_without_toolchain(dep_name) in builtin_deps:
252 builtin_deps[label_without_toolchain(dep_name)](module)
253 return
254
255 # Similarly some shared libraries are directly mapped to Android
256 # equivalents.
257 target = desc[dep_name]
258 for lib in target.get('libs', []):
259 android_lib = 'lib' + lib
260 if lib in library_whitelist and not android_lib in module.shared_libs:
261 module.shared_libs.append(android_lib)
262
263 type = target['type']
264 if type == 'action':
265 create_modules_from_target(blueprint, desc, dep_name)
266 # Depend both on the generated sources and headers -- see
267 # make_genrules_for_action.
268 module.srcs.append(':' + label_to_module_name(dep_name))
269 module.generated_headers.append(
270 label_to_module_name(dep_name) + '_headers')
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000271 elif type == 'static_library' and label_to_module_name(
272 dep_name) != module.name:
273 create_modules_from_target(blueprint, desc, dep_name)
274 module.static_libs.append(label_to_module_name(dep_name))
275 elif type in ['group', 'source_set', 'executable', 'static_library'
276 ] and 'sources' in target:
Sami Kyostila865d1d32017-12-12 18:37:04 +0000277 # Ignore source files that are generated by actions since they will be
278 # implicitly added by the genrule dependencies.
279 module.srcs.extend(
280 label_to_path(src) for src in target['sources']
281 if is_supported_source_file(src)
282 and not is_generated_by_action(desc, src))
283
284
285def make_genrules_for_action(blueprint, desc, target_name):
286 """Generate genrules for a GN action.
287
288 GN actions are used to dynamically generate files during the build. The
289 Soong equivalent is a genrule. This function turns a specific kind of
290 genrule which turns .proto files into source and header files into a pair
Sami Kyostila71625d72017-12-18 10:29:49 +0000291 equivalent genrules.
Sami Kyostila865d1d32017-12-12 18:37:04 +0000292
293 Args:
294 blueprint: Blueprint instance which is being generated.
295 desc: JSON GN description.
296 target_name: GN target for genrule generation.
297
298 Returns:
299 A (source_genrule, header_genrule) module tuple.
300 """
301 target = desc[target_name]
302
303 # We only support genrules which call protoc (with or without a plugin) to
304 # turn .proto files into header and source files.
305 args = target['args']
306 if not args[0].endswith('/protoc'):
307 raise Error('Unsupported action in target %s: %s' % (target_name,
308 target['args']))
309
310 # We create two genrules for each action: one for the protobuf headers and
311 # another for the sources. This is because the module that depends on the
312 # generated files needs to declare two different types of dependencies --
313 # source files in 'srcs' and headers in 'generated_headers' -- and it's not
314 # valid to generate .h files from a source dependency and vice versa.
Sami Kyostila71625d72017-12-18 10:29:49 +0000315 source_module = Module('genrule', label_to_module_name(target_name))
Sami Kyostila865d1d32017-12-12 18:37:04 +0000316 source_module.srcs.extend(label_to_path(src) for src in target['sources'])
317 source_module.tools = ['aprotoc']
318
Sami Kyostila71625d72017-12-18 10:29:49 +0000319 header_module = Module('genrule',
Sami Kyostila865d1d32017-12-12 18:37:04 +0000320 label_to_module_name(target_name) + '_headers')
321 header_module.srcs = source_module.srcs[:]
322 header_module.tools = source_module.tools[:]
323 header_module.export_include_dirs = ['.']
324
325 # TODO(skyostil): Is there a way to avoid hardcoding the tree path here?
326 # TODO(skyostil): Find a way to avoid creating the directory.
327 cmd = [
328 'mkdir -p $(genDir)/%s &&' % tree_path, '$(location aprotoc)',
329 '--cpp_out=$(genDir)/%s' % tree_path,
330 '--proto_path=%s' % tree_path
331 ]
332 namespaces = ['pb']
333
334 parser = ThrowingArgumentParser('Action in target %s (%s)' %
335 (target_name, ' '.join(target['args'])))
336 parser.add_argument('--proto_path')
337 parser.add_argument('--cpp_out')
338 parser.add_argument('--plugin')
339 parser.add_argument('--plugin_out')
340 parser.add_argument('protos', nargs=argparse.REMAINDER)
341
342 args = parser.parse_args(args[1:])
343 if args.plugin:
344 _, plugin = os.path.split(args.plugin)
345 # TODO(skyostil): Can we detect this some other way?
346 if plugin == 'ipc_plugin':
347 namespaces.append('ipc')
348 elif plugin == 'protoc_plugin':
349 namespaces = ['pbzero']
350 for dep in target['deps']:
351 if desc[dep]['type'] != 'executable':
352 continue
353 _, executable = os.path.split(desc[dep]['outputs'][0])
354 if executable == plugin:
355 cmd += [
356 '--plugin=protoc-gen-plugin=$(location %s)' %
357 label_to_module_name(dep)
358 ]
359 source_module.tools.append(label_to_module_name(dep))
360 # Also make sure the module for the tool is generated.
361 create_modules_from_target(blueprint, desc, dep)
362 break
363 else:
364 raise Error('Unrecognized protoc plugin in target %s: %s' %
365 (target_name, args[i]))
366 if args.plugin_out:
367 plugin_args = args.plugin_out.split(':')[0]
368 cmd += ['--plugin_out=%s:$(genDir)/%s' % (plugin_args, tree_path)]
369
370 cmd += ['$(in)']
371 source_module.cmd = ' '.join(cmd)
372 header_module.cmd = source_module.cmd
373 header_module.tools = source_module.tools[:]
374
375 for ns in namespaces:
376 source_module.out += [
377 '%s/%s' % (tree_path, src.replace('.proto', '.%s.cc' % ns))
378 for src in source_module.srcs
379 ]
380 header_module.out += [
381 '%s/%s' % (tree_path, src.replace('.proto', '.%s.h' % ns))
382 for src in header_module.srcs
383 ]
384 return source_module, header_module
385
386
387def create_modules_from_target(blueprint, desc, target_name):
388 """Generate module(s) for a given GN target.
389
390 Given a GN target name, generate one or more corresponding modules into a
391 blueprint.
392
393 Args:
394 blueprint: Blueprint instance which is being generated.
395 desc: JSON GN description.
396 target_name: GN target for module generation.
397 """
398 target = desc[target_name]
399 if target['type'] == 'executable':
400 if 'host' in target['toolchain']:
401 module_type = 'cc_binary_host'
402 elif target.get('testonly'):
403 module_type = 'cc_test'
404 else:
405 module_type = 'cc_binary'
406 modules = [Module(module_type, label_to_module_name(target_name))]
407 elif target['type'] == 'action':
408 modules = make_genrules_for_action(blueprint, desc, target_name)
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000409 elif target['type'] == 'static_library':
410 modules = [
411 Module('cc_library_static', label_to_module_name(target_name))
412 ]
Sami Kyostila865d1d32017-12-12 18:37:04 +0000413 else:
414 raise Error('Unknown target type: %s' % target['type'])
415
416 for module in modules:
417 module.comment = 'GN target: %s' % target_name
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000418 # Don't try to inject library/source dependencies into genrules because
419 # they are not compiled in the traditional sense.
Sami Kyostila71625d72017-12-18 10:29:49 +0000420 if module.type != 'genrule':
Sami Kyostila865d1d32017-12-12 18:37:04 +0000421 module.defaults = [defaults_module]
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000422 apply_module_dependency(blueprint, desc, module, target_name)
423 for dep in resolve_dependencies(desc, target_name):
424 apply_module_dependency(blueprint, desc, module, dep)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000425
426 blueprint.add_module(module)
427
428
429def resolve_dependencies(desc, target_name):
430 """Return the transitive set of dependent-on targets for a GN target.
431
432 Args:
433 blueprint: Blueprint instance which is being generated.
434 desc: JSON GN description.
435
436 Returns:
437 A set of transitive dependencies in the form of GN targets.
438 """
439
440 if label_without_toolchain(target_name) in builtin_deps:
441 return set()
442 target = desc[target_name]
443 resolved_deps = set()
444 for dep in target.get('deps', []):
445 resolved_deps.add(dep)
446 # Ignore the transitive dependencies of actions because they are
447 # explicitly converted to genrules.
448 if desc[dep]['type'] == 'action':
449 continue
450 resolved_deps.update(resolve_dependencies(desc, dep))
451 return resolved_deps
452
453
454def create_blueprint_for_targets(desc, targets):
455 """Generate a blueprint for a list of GN targets."""
456 blueprint = Blueprint()
457
458 # Default settings used by all modules.
459 defaults = Module('cc_defaults', defaults_module)
460 defaults.local_include_dirs = ['include']
461 defaults.cflags = [
462 '-Wno-error=return-type',
463 '-Wno-sign-compare',
464 '-Wno-sign-promo',
465 '-Wno-unused-parameter',
466 ]
467
468 blueprint.add_module(defaults)
469 for target in targets:
470 create_modules_from_target(blueprint, desc, target)
471 return blueprint
472
473
Sami Kyostilab27619f2017-12-13 19:22:16 +0000474def repo_root():
475 """Returns an absolute path to the repository root."""
476
477 return os.path.join(
478 os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
479
480
481def create_build_description():
482 """Creates the JSON build description by running GN."""
483
484 out = os.path.join(repo_root(), 'out', 'tmp.gen_android_bp')
485 try:
486 try:
487 os.makedirs(out)
488 except OSError as e:
489 if e.errno != errno.EEXIST:
490 raise
491 subprocess.check_output(
492 ['gn', 'gen', out, '--args=%s' % gn_args], cwd=repo_root())
493 desc = subprocess.check_output(
494 ['gn', 'desc', out, '--format=json', '--all-toolchains', '//*'],
495 cwd=repo_root())
496 return json.loads(desc)
497 finally:
498 shutil.rmtree(out)
499
500
Sami Kyostila865d1d32017-12-12 18:37:04 +0000501def main():
502 parser = argparse.ArgumentParser(
503 description='Generate Android.bp from a GN description.')
504 parser.add_argument(
Sami Kyostilab27619f2017-12-13 19:22:16 +0000505 '--desc',
Sami Kyostila865d1d32017-12-12 18:37:04 +0000506 help=
507 'GN description (e.g., gn desc out --format=json --all-toolchains "//*"'
508 )
509 parser.add_argument(
Sami Kyostilab27619f2017-12-13 19:22:16 +0000510 '--output',
511 help='Blueprint file to create',
512 default=os.path.join(repo_root(), 'Android.bp'),
513 )
514 parser.add_argument(
Sami Kyostila865d1d32017-12-12 18:37:04 +0000515 'targets',
516 nargs=argparse.REMAINDER,
517 help='Targets to include in the blueprint (e.g., "//:perfetto_tests")')
518 args = parser.parse_args()
519
Sami Kyostilab27619f2017-12-13 19:22:16 +0000520 if args.desc:
521 with open(args.desc) as f:
522 desc = json.load(f)
523 else:
524 desc = create_build_description()
Sami Kyostila865d1d32017-12-12 18:37:04 +0000525
Sami Kyostilab27619f2017-12-13 19:22:16 +0000526 blueprint = create_blueprint_for_targets(desc, args.targets
527 or default_targets)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000528 output = [
529 """// Copyright (C) 2017 The Android Open Source Project
530//
531// Licensed under the Apache License, Version 2.0 (the "License");
532// you may not use this file except in compliance with the License.
533// You may obtain a copy of the License at
534//
535// http://www.apache.org/licenses/LICENSE-2.0
536//
537// Unless required by applicable law or agreed to in writing, software
538// distributed under the License is distributed on an "AS IS" BASIS,
539// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
540// See the License for the specific language governing permissions and
541// limitations under the License.
542//
543// This file is automatically generated by %s. Do not edit.
544""" % (__file__)
545 ]
546 blueprint.to_string(output)
Sami Kyostilab27619f2017-12-13 19:22:16 +0000547 with open(args.output, 'w') as f:
548 f.write('\n'.join(output))
Sami Kyostila865d1d32017-12-12 18:37:04 +0000549
550
551if __name__ == '__main__':
552 sys.exit(main())