blob: 79416d46d1687e4debec6130b216dc2180b45a5b [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
Primiano Tucciedf099c2018-01-08 18:27:56 +000029import errno
Sami Kyostila865d1d32017-12-12 18:37:04 +000030import json
31import os
32import re
Sami Kyostilab27619f2017-12-13 19:22:16 +000033import shutil
34import subprocess
Sami Kyostila865d1d32017-12-12 18:37:04 +000035import sys
36
Sami Kyostilab27619f2017-12-13 19:22:16 +000037# Default targets to translate to the blueprint file.
Primiano Tucci4e49c022017-12-21 18:22:44 +010038default_targets = [
Primiano Tucciedf099c2018-01-08 18:27:56 +000039 '//:libtraced_shared',
40 '//:perfetto',
Primiano Tucci4e49c022017-12-21 18:22:44 +010041 '//:perfetto_tests',
Primiano Tucci3b729102018-01-08 18:16:36 +000042 '//:perfetto',
Primiano Tucci4e49c022017-12-21 18:22:44 +010043 '//:traced',
Primiano Tucci6067e732018-01-08 16:19:40 +000044 '//:traced_probes',
Primiano Tucci4e49c022017-12-21 18:22:44 +010045 '//src/tracing:consumer_cmd',
46]
Sami Kyostilab27619f2017-12-13 19:22:16 +000047
Primiano Tucci6067e732018-01-08 16:19:40 +000048# Defines a custom init_rc argument to be applied to the corresponding output
49# blueprint target.
50target_initrc = {} # TODO(primiano): populate in upcoming CLs.
51
Sami Kyostilab27619f2017-12-13 19:22:16 +000052# Arguments for the GN output directory.
Primiano Tucciedf099c2018-01-08 18:27:56 +000053gn_args = 'target_os="android" target_cpu="arm" is_debug=false build_with_android=true'
Sami Kyostilab27619f2017-12-13 19:22:16 +000054
Sami Kyostila865d1d32017-12-12 18:37:04 +000055# All module names are prefixed with this string to avoid collisions.
56module_prefix = 'perfetto_'
57
58# Shared libraries which are directly translated to Android system equivalents.
59library_whitelist = [
60 'android',
61 'log',
Primiano Tucciedf099c2018-01-08 18:27:56 +000062 'utils',
Sami Kyostila865d1d32017-12-12 18:37:04 +000063]
64
65# Name of the module which settings such as compiler flags for all other
66# modules.
67defaults_module = module_prefix + 'defaults'
68
69# Location of the project in the Android source tree.
70tree_path = 'external/perfetto'
71
Primiano Tucciedf099c2018-01-08 18:27:56 +000072# Compiler flags which are passed through to the blueprint.
73cflag_whitelist = r'^-DPERFETTO.*$'
74
Sami Kyostila865d1d32017-12-12 18:37:04 +000075
76def enable_gmock(module):
77 module.static_libs.append('libgmock')
78
79
Hector Dearman3e712a02017-12-19 16:39:59 +000080def enable_gtest_prod(module):
81 module.static_libs.append('libgtest_prod')
82
83
Sami Kyostila865d1d32017-12-12 18:37:04 +000084def enable_gtest(module):
85 assert module.type == 'cc_test'
86
87
88def enable_protobuf_full(module):
89 module.shared_libs.append('libprotobuf-cpp-full')
90
91
92def enable_protobuf_lite(module):
93 module.shared_libs.append('libprotobuf-cpp-lite')
94
95
96def enable_protoc_lib(module):
97 module.shared_libs.append('libprotoc')
98
99
100def enable_libunwind(module):
Sami Kyostilafc074d42017-12-15 10:33:42 +0000101 # libunwind is disabled on Darwin so we cannot depend on it.
102 pass
Sami Kyostila865d1d32017-12-12 18:37:04 +0000103
104
105# Android equivalents for third-party libraries that the upstream project
106# depends on.
107builtin_deps = {
108 '//buildtools:gmock': enable_gmock,
109 '//buildtools:gtest': enable_gtest,
Hector Dearman3e712a02017-12-19 16:39:59 +0000110 '//gn:gtest_prod_config': enable_gtest_prod,
Sami Kyostila865d1d32017-12-12 18:37:04 +0000111 '//buildtools:gtest_main': enable_gtest,
112 '//buildtools:libunwind': enable_libunwind,
113 '//buildtools:protobuf_full': enable_protobuf_full,
114 '//buildtools:protobuf_lite': enable_protobuf_lite,
115 '//buildtools:protoc_lib': enable_protoc_lib,
116}
117
118# ----------------------------------------------------------------------------
119# End of configuration.
120# ----------------------------------------------------------------------------
121
122
123class Error(Exception):
124 pass
125
126
127class ThrowingArgumentParser(argparse.ArgumentParser):
128 def __init__(self, context):
129 super(ThrowingArgumentParser, self).__init__()
130 self.context = context
131
132 def error(self, message):
133 raise Error('%s: %s' % (self.context, message))
134
135
136class Module(object):
137 """A single module (e.g., cc_binary, cc_test) in a blueprint."""
138
139 def __init__(self, mod_type, name):
140 self.type = mod_type
141 self.name = name
142 self.srcs = []
143 self.comment = None
144 self.shared_libs = []
145 self.static_libs = []
146 self.tools = []
147 self.cmd = None
Primiano Tucci6067e732018-01-08 16:19:40 +0000148 self.init_rc = []
Sami Kyostila865d1d32017-12-12 18:37:04 +0000149 self.out = []
150 self.export_include_dirs = []
151 self.generated_headers = []
152 self.defaults = []
153 self.cflags = []
154 self.local_include_dirs = []
155
156 def to_string(self, output):
157 if self.comment:
158 output.append('// %s' % self.comment)
159 output.append('%s {' % self.type)
160 self._output_field(output, 'name')
161 self._output_field(output, 'srcs')
162 self._output_field(output, 'shared_libs')
163 self._output_field(output, 'static_libs')
164 self._output_field(output, 'tools')
165 self._output_field(output, 'cmd', sort=False)
Primiano Tucci6067e732018-01-08 16:19:40 +0000166 self._output_field(output, 'init_rc')
Sami Kyostila865d1d32017-12-12 18:37:04 +0000167 self._output_field(output, 'out')
168 self._output_field(output, 'export_include_dirs')
169 self._output_field(output, 'generated_headers')
170 self._output_field(output, 'defaults')
171 self._output_field(output, 'cflags')
172 self._output_field(output, 'local_include_dirs')
173 output.append('}')
174 output.append('')
175
176 def _output_field(self, output, name, sort=True):
177 value = getattr(self, name)
178 if not value:
179 return
180 if isinstance(value, list):
181 output.append(' %s: [' % name)
182 for item in sorted(value) if sort else value:
183 output.append(' "%s",' % item)
184 output.append(' ],')
185 else:
186 output.append(' %s: "%s",' % (name, value))
187
188
189class Blueprint(object):
190 """In-memory representation of an Android.bp file."""
191
192 def __init__(self):
193 self.modules = {}
194
195 def add_module(self, module):
196 """Adds a new module to the blueprint, replacing any existing module
197 with the same name.
198
199 Args:
200 module: Module instance.
201 """
202 self.modules[module.name] = module
203
204 def to_string(self, output):
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000205 for m in sorted(self.modules.itervalues(), key=lambda m: m.name):
Sami Kyostila865d1d32017-12-12 18:37:04 +0000206 m.to_string(output)
207
208
209def label_to_path(label):
210 """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
211 assert label.startswith('//')
212 return label[2:]
213
214
215def label_to_module_name(label):
216 """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
Primiano Tucci4e49c022017-12-21 18:22:44 +0100217 module = re.sub(r'^//:?', '', label)
218 module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
219 if not module.startswith(module_prefix) and label not in default_targets:
Sami Kyostila865d1d32017-12-12 18:37:04 +0000220 return module_prefix + module
221 return module
222
223
224def label_without_toolchain(label):
225 """Strips the toolchain from a GN label.
226
227 Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
228 gcc_like_host) without the parenthesised toolchain part.
229 """
230 return label.split('(')[0]
231
232
233def is_supported_source_file(name):
234 """Returns True if |name| can appear in a 'srcs' list."""
235 return os.path.splitext(name)[1] in ['.c', '.cc', '.proto']
236
237
238def is_generated_by_action(desc, label):
239 """Checks if a label is generated by an action.
240
241 Returns True if a GN output label |label| is an output for any action,
242 i.e., the file is generated dynamically.
243 """
244 for target in desc.itervalues():
245 if target['type'] == 'action' and label in target['outputs']:
246 return True
247 return False
248
249
250def apply_module_dependency(blueprint, desc, module, dep_name):
251 """Recursively collect dependencies for a given module.
252
253 Walk the transitive dependencies for a GN target and apply them to a given
254 module. This effectively flattens the dependency tree so that |module|
255 directly contains all the sources, libraries, etc. in the corresponding GN
256 dependency tree.
257
258 Args:
259 blueprint: Blueprint instance which is being generated.
260 desc: JSON GN description.
261 module: Module to which dependencies should be added.
262 dep_name: GN target of the dependency.
263 """
Sami Kyostila865d1d32017-12-12 18:37:04 +0000264 # If the dependency refers to a library which we can replace with an Android
265 # equivalent, stop recursing and patch the dependency in.
266 if label_without_toolchain(dep_name) in builtin_deps:
267 builtin_deps[label_without_toolchain(dep_name)](module)
268 return
269
270 # Similarly some shared libraries are directly mapped to Android
271 # equivalents.
272 target = desc[dep_name]
273 for lib in target.get('libs', []):
274 android_lib = 'lib' + lib
275 if lib in library_whitelist and not android_lib in module.shared_libs:
276 module.shared_libs.append(android_lib)
277
278 type = target['type']
279 if type == 'action':
280 create_modules_from_target(blueprint, desc, dep_name)
281 # Depend both on the generated sources and headers -- see
282 # make_genrules_for_action.
283 module.srcs.append(':' + label_to_module_name(dep_name))
284 module.generated_headers.append(
285 label_to_module_name(dep_name) + '_headers')
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000286 elif type == 'static_library' and label_to_module_name(
287 dep_name) != module.name:
288 create_modules_from_target(blueprint, desc, dep_name)
289 module.static_libs.append(label_to_module_name(dep_name))
Primiano Tucci6067e732018-01-08 16:19:40 +0000290 elif type == 'shared_library' and label_to_module_name(
291 dep_name) != module.name:
292 module.shared_libs.append(label_to_module_name(dep_name))
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000293 elif type in ['group', 'source_set', 'executable', 'static_library'
294 ] and 'sources' in target:
Sami Kyostila865d1d32017-12-12 18:37:04 +0000295 # Ignore source files that are generated by actions since they will be
296 # implicitly added by the genrule dependencies.
297 module.srcs.extend(
298 label_to_path(src) for src in target['sources']
299 if is_supported_source_file(src)
300 and not is_generated_by_action(desc, src))
301
302
303def make_genrules_for_action(blueprint, desc, target_name):
304 """Generate genrules for a GN action.
305
306 GN actions are used to dynamically generate files during the build. The
307 Soong equivalent is a genrule. This function turns a specific kind of
308 genrule which turns .proto files into source and header files into a pair
Sami Kyostila71625d72017-12-18 10:29:49 +0000309 equivalent genrules.
Sami Kyostila865d1d32017-12-12 18:37:04 +0000310
311 Args:
312 blueprint: Blueprint instance which is being generated.
313 desc: JSON GN description.
314 target_name: GN target for genrule generation.
315
316 Returns:
317 A (source_genrule, header_genrule) module tuple.
318 """
319 target = desc[target_name]
320
321 # We only support genrules which call protoc (with or without a plugin) to
322 # turn .proto files into header and source files.
323 args = target['args']
324 if not args[0].endswith('/protoc'):
325 raise Error('Unsupported action in target %s: %s' % (target_name,
326 target['args']))
327
328 # We create two genrules for each action: one for the protobuf headers and
329 # another for the sources. This is because the module that depends on the
330 # generated files needs to declare two different types of dependencies --
331 # source files in 'srcs' and headers in 'generated_headers' -- and it's not
332 # valid to generate .h files from a source dependency and vice versa.
Sami Kyostila71625d72017-12-18 10:29:49 +0000333 source_module = Module('genrule', label_to_module_name(target_name))
Sami Kyostila865d1d32017-12-12 18:37:04 +0000334 source_module.srcs.extend(label_to_path(src) for src in target['sources'])
335 source_module.tools = ['aprotoc']
336
Sami Kyostila71625d72017-12-18 10:29:49 +0000337 header_module = Module('genrule',
Sami Kyostila865d1d32017-12-12 18:37:04 +0000338 label_to_module_name(target_name) + '_headers')
339 header_module.srcs = source_module.srcs[:]
340 header_module.tools = source_module.tools[:]
341 header_module.export_include_dirs = ['.']
342
343 # TODO(skyostil): Is there a way to avoid hardcoding the tree path here?
344 # TODO(skyostil): Find a way to avoid creating the directory.
345 cmd = [
346 'mkdir -p $(genDir)/%s &&' % tree_path, '$(location aprotoc)',
347 '--cpp_out=$(genDir)/%s' % tree_path,
348 '--proto_path=%s' % tree_path
349 ]
350 namespaces = ['pb']
351
352 parser = ThrowingArgumentParser('Action in target %s (%s)' %
353 (target_name, ' '.join(target['args'])))
354 parser.add_argument('--proto_path')
355 parser.add_argument('--cpp_out')
356 parser.add_argument('--plugin')
357 parser.add_argument('--plugin_out')
358 parser.add_argument('protos', nargs=argparse.REMAINDER)
359
360 args = parser.parse_args(args[1:])
361 if args.plugin:
362 _, plugin = os.path.split(args.plugin)
363 # TODO(skyostil): Can we detect this some other way?
364 if plugin == 'ipc_plugin':
365 namespaces.append('ipc')
366 elif plugin == 'protoc_plugin':
367 namespaces = ['pbzero']
368 for dep in target['deps']:
369 if desc[dep]['type'] != 'executable':
370 continue
371 _, executable = os.path.split(desc[dep]['outputs'][0])
372 if executable == plugin:
373 cmd += [
374 '--plugin=protoc-gen-plugin=$(location %s)' %
375 label_to_module_name(dep)
376 ]
377 source_module.tools.append(label_to_module_name(dep))
378 # Also make sure the module for the tool is generated.
379 create_modules_from_target(blueprint, desc, dep)
380 break
381 else:
382 raise Error('Unrecognized protoc plugin in target %s: %s' %
383 (target_name, args[i]))
384 if args.plugin_out:
385 plugin_args = args.plugin_out.split(':')[0]
386 cmd += ['--plugin_out=%s:$(genDir)/%s' % (plugin_args, tree_path)]
387
388 cmd += ['$(in)']
389 source_module.cmd = ' '.join(cmd)
390 header_module.cmd = source_module.cmd
391 header_module.tools = source_module.tools[:]
392
393 for ns in namespaces:
394 source_module.out += [
395 '%s/%s' % (tree_path, src.replace('.proto', '.%s.cc' % ns))
396 for src in source_module.srcs
397 ]
398 header_module.out += [
399 '%s/%s' % (tree_path, src.replace('.proto', '.%s.h' % ns))
400 for src in header_module.srcs
401 ]
402 return source_module, header_module
403
404
405def create_modules_from_target(blueprint, desc, target_name):
406 """Generate module(s) for a given GN target.
407
408 Given a GN target name, generate one or more corresponding modules into a
409 blueprint.
410
411 Args:
412 blueprint: Blueprint instance which is being generated.
413 desc: JSON GN description.
414 target_name: GN target for module generation.
415 """
416 target = desc[target_name]
417 if target['type'] == 'executable':
418 if 'host' in target['toolchain']:
419 module_type = 'cc_binary_host'
420 elif target.get('testonly'):
421 module_type = 'cc_test'
422 else:
423 module_type = 'cc_binary'
424 modules = [Module(module_type, label_to_module_name(target_name))]
425 elif target['type'] == 'action':
426 modules = make_genrules_for_action(blueprint, desc, target_name)
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000427 elif target['type'] == 'static_library':
428 modules = [
429 Module('cc_library_static', label_to_module_name(target_name))
430 ]
Primiano Tucci6067e732018-01-08 16:19:40 +0000431 elif target['type'] == 'shared_library':
432 modules = [
433 Module('cc_library_shared', label_to_module_name(target_name))
434 ]
Sami Kyostila865d1d32017-12-12 18:37:04 +0000435 else:
436 raise Error('Unknown target type: %s' % target['type'])
437
438 for module in modules:
439 module.comment = 'GN target: %s' % target_name
Primiano Tucci6067e732018-01-08 16:19:40 +0000440 if target_name in target_initrc:
441 module.init_rc = [target_initrc[target_name]]
442
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000443 # Don't try to inject library/source dependencies into genrules because
444 # they are not compiled in the traditional sense.
Sami Kyostila71625d72017-12-18 10:29:49 +0000445 if module.type != 'genrule':
Primiano Tucciedf099c2018-01-08 18:27:56 +0000446 for flag in target.get('cflags', []):
447 if re.match(cflag_whitelist, flag):
448 module.cflags.append(flag)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000449 module.defaults = [defaults_module]
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000450 apply_module_dependency(blueprint, desc, module, target_name)
451 for dep in resolve_dependencies(desc, target_name):
452 apply_module_dependency(blueprint, desc, module, dep)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000453
454 blueprint.add_module(module)
455
456
457def resolve_dependencies(desc, target_name):
458 """Return the transitive set of dependent-on targets for a GN target.
459
460 Args:
461 blueprint: Blueprint instance which is being generated.
462 desc: JSON GN description.
463
464 Returns:
465 A set of transitive dependencies in the form of GN targets.
466 """
467
468 if label_without_toolchain(target_name) in builtin_deps:
469 return set()
470 target = desc[target_name]
471 resolved_deps = set()
472 for dep in target.get('deps', []):
473 resolved_deps.add(dep)
474 # Ignore the transitive dependencies of actions because they are
475 # explicitly converted to genrules.
476 if desc[dep]['type'] == 'action':
477 continue
Primiano Tucci6067e732018-01-08 16:19:40 +0000478 # Dependencies on shared libraries shouldn't propagate any transitive
479 # dependencies but only depend on the shared library target
480 if desc[dep]['type'] == 'shared_library':
481 continue
Sami Kyostila865d1d32017-12-12 18:37:04 +0000482 resolved_deps.update(resolve_dependencies(desc, dep))
483 return resolved_deps
484
485
486def create_blueprint_for_targets(desc, targets):
487 """Generate a blueprint for a list of GN targets."""
488 blueprint = Blueprint()
489
490 # Default settings used by all modules.
491 defaults = Module('cc_defaults', defaults_module)
492 defaults.local_include_dirs = ['include']
493 defaults.cflags = [
494 '-Wno-error=return-type',
495 '-Wno-sign-compare',
496 '-Wno-sign-promo',
497 '-Wno-unused-parameter',
498 ]
499
500 blueprint.add_module(defaults)
501 for target in targets:
502 create_modules_from_target(blueprint, desc, target)
503 return blueprint
504
505
Sami Kyostilab27619f2017-12-13 19:22:16 +0000506def repo_root():
507 """Returns an absolute path to the repository root."""
508
509 return os.path.join(
510 os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
511
512
513def create_build_description():
514 """Creates the JSON build description by running GN."""
515
516 out = os.path.join(repo_root(), 'out', 'tmp.gen_android_bp')
517 try:
518 try:
519 os.makedirs(out)
520 except OSError as e:
521 if e.errno != errno.EEXIST:
522 raise
523 subprocess.check_output(
524 ['gn', 'gen', out, '--args=%s' % gn_args], cwd=repo_root())
525 desc = subprocess.check_output(
526 ['gn', 'desc', out, '--format=json', '--all-toolchains', '//*'],
527 cwd=repo_root())
528 return json.loads(desc)
529 finally:
530 shutil.rmtree(out)
531
532
Sami Kyostila865d1d32017-12-12 18:37:04 +0000533def main():
534 parser = argparse.ArgumentParser(
535 description='Generate Android.bp from a GN description.')
536 parser.add_argument(
Sami Kyostilab27619f2017-12-13 19:22:16 +0000537 '--desc',
Sami Kyostila865d1d32017-12-12 18:37:04 +0000538 help=
539 'GN description (e.g., gn desc out --format=json --all-toolchains "//*"'
540 )
541 parser.add_argument(
Sami Kyostilab27619f2017-12-13 19:22:16 +0000542 '--output',
543 help='Blueprint file to create',
544 default=os.path.join(repo_root(), 'Android.bp'),
545 )
546 parser.add_argument(
Sami Kyostila865d1d32017-12-12 18:37:04 +0000547 'targets',
548 nargs=argparse.REMAINDER,
549 help='Targets to include in the blueprint (e.g., "//:perfetto_tests")')
550 args = parser.parse_args()
551
Sami Kyostilab27619f2017-12-13 19:22:16 +0000552 if args.desc:
553 with open(args.desc) as f:
554 desc = json.load(f)
555 else:
556 desc = create_build_description()
Sami Kyostila865d1d32017-12-12 18:37:04 +0000557
Sami Kyostilab27619f2017-12-13 19:22:16 +0000558 blueprint = create_blueprint_for_targets(desc, args.targets
559 or default_targets)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000560 output = [
561 """// Copyright (C) 2017 The Android Open Source Project
562//
563// Licensed under the Apache License, Version 2.0 (the "License");
564// you may not use this file except in compliance with the License.
565// You may obtain a copy of the License at
566//
567// http://www.apache.org/licenses/LICENSE-2.0
568//
569// Unless required by applicable law or agreed to in writing, software
570// distributed under the License is distributed on an "AS IS" BASIS,
571// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
572// See the License for the specific language governing permissions and
573// limitations under the License.
574//
575// This file is automatically generated by %s. Do not edit.
576""" % (__file__)
577 ]
578 blueprint.to_string(output)
Sami Kyostilab27619f2017-12-13 19:22:16 +0000579 with open(args.output, 'w') as f:
580 f.write('\n'.join(output))
Sami Kyostila865d1d32017-12-12 18:37:04 +0000581
582
583if __name__ == '__main__':
584 sys.exit(main())