blob: 83ca6a327564949cd033121dcac036cf6fab4b33 [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',
Primiano Tucci4e49c022017-12-21 18:22:44 +010040 '//:perfetto_tests',
Primiano Tucci3b729102018-01-08 18:16:36 +000041 '//:perfetto',
Primiano Tucci4e49c022017-12-21 18:22:44 +010042 '//:traced',
Primiano Tucci6067e732018-01-08 16:19:40 +000043 '//:traced_probes',
Primiano Tucci4e49c022017-12-21 18:22:44 +010044]
Sami Kyostilab27619f2017-12-13 19:22:16 +000045
Primiano Tucci6067e732018-01-08 16:19:40 +000046# Defines a custom init_rc argument to be applied to the corresponding output
47# blueprint target.
Primiano Tucci5a304532018-01-09 14:15:43 +000048target_initrc = {
49 '//:traced': 'perfetto.rc',
50}
Primiano Tucci6067e732018-01-08 16:19:40 +000051
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',
Sami Kyostilab5b71692018-01-12 12:16:44 +000061 'binder',
Sami Kyostila865d1d32017-12-12 18:37:04 +000062 'log',
Sami Kyostilab5b71692018-01-12 12:16:44 +000063 'services',
Primiano Tucciedf099c2018-01-08 18:27:56 +000064 'utils',
Sami Kyostila865d1d32017-12-12 18:37:04 +000065]
66
67# Name of the module which settings such as compiler flags for all other
68# modules.
69defaults_module = module_prefix + 'defaults'
70
71# Location of the project in the Android source tree.
72tree_path = 'external/perfetto'
73
Primiano Tucciedf099c2018-01-08 18:27:56 +000074# Compiler flags which are passed through to the blueprint.
75cflag_whitelist = r'^-DPERFETTO.*$'
76
Sami Kyostila865d1d32017-12-12 18:37:04 +000077
78def enable_gmock(module):
79 module.static_libs.append('libgmock')
80
81
Hector Dearman3e712a02017-12-19 16:39:59 +000082def enable_gtest_prod(module):
83 module.static_libs.append('libgtest_prod')
84
85
Sami Kyostila865d1d32017-12-12 18:37:04 +000086def enable_gtest(module):
87 assert module.type == 'cc_test'
88
89
90def enable_protobuf_full(module):
91 module.shared_libs.append('libprotobuf-cpp-full')
92
93
94def enable_protobuf_lite(module):
95 module.shared_libs.append('libprotobuf-cpp-lite')
96
97
98def enable_protoc_lib(module):
99 module.shared_libs.append('libprotoc')
100
101
102def enable_libunwind(module):
Sami Kyostilafc074d42017-12-15 10:33:42 +0000103 # libunwind is disabled on Darwin so we cannot depend on it.
104 pass
Sami Kyostila865d1d32017-12-12 18:37:04 +0000105
106
107# Android equivalents for third-party libraries that the upstream project
108# depends on.
109builtin_deps = {
110 '//buildtools:gmock': enable_gmock,
111 '//buildtools:gtest': enable_gtest,
Hector Dearman3e712a02017-12-19 16:39:59 +0000112 '//gn:gtest_prod_config': enable_gtest_prod,
Sami Kyostila865d1d32017-12-12 18:37:04 +0000113 '//buildtools:gtest_main': enable_gtest,
114 '//buildtools:libunwind': enable_libunwind,
115 '//buildtools:protobuf_full': enable_protobuf_full,
116 '//buildtools:protobuf_lite': enable_protobuf_lite,
117 '//buildtools:protoc_lib': enable_protoc_lib,
118}
119
120# ----------------------------------------------------------------------------
121# End of configuration.
122# ----------------------------------------------------------------------------
123
124
125class Error(Exception):
126 pass
127
128
129class ThrowingArgumentParser(argparse.ArgumentParser):
130 def __init__(self, context):
131 super(ThrowingArgumentParser, self).__init__()
132 self.context = context
133
134 def error(self, message):
135 raise Error('%s: %s' % (self.context, message))
136
137
138class Module(object):
139 """A single module (e.g., cc_binary, cc_test) in a blueprint."""
140
141 def __init__(self, mod_type, name):
142 self.type = mod_type
143 self.name = name
144 self.srcs = []
145 self.comment = None
146 self.shared_libs = []
147 self.static_libs = []
148 self.tools = []
149 self.cmd = None
Primiano Tucci6067e732018-01-08 16:19:40 +0000150 self.init_rc = []
Sami Kyostila865d1d32017-12-12 18:37:04 +0000151 self.out = []
152 self.export_include_dirs = []
153 self.generated_headers = []
154 self.defaults = []
155 self.cflags = []
156 self.local_include_dirs = []
157
158 def to_string(self, output):
159 if self.comment:
160 output.append('// %s' % self.comment)
161 output.append('%s {' % self.type)
162 self._output_field(output, 'name')
163 self._output_field(output, 'srcs')
164 self._output_field(output, 'shared_libs')
165 self._output_field(output, 'static_libs')
166 self._output_field(output, 'tools')
167 self._output_field(output, 'cmd', sort=False)
Primiano Tucci6067e732018-01-08 16:19:40 +0000168 self._output_field(output, 'init_rc')
Sami Kyostila865d1d32017-12-12 18:37:04 +0000169 self._output_field(output, 'out')
170 self._output_field(output, 'export_include_dirs')
171 self._output_field(output, 'generated_headers')
172 self._output_field(output, 'defaults')
173 self._output_field(output, 'cflags')
174 self._output_field(output, 'local_include_dirs')
175 output.append('}')
176 output.append('')
177
178 def _output_field(self, output, name, sort=True):
179 value = getattr(self, name)
180 if not value:
181 return
182 if isinstance(value, list):
183 output.append(' %s: [' % name)
184 for item in sorted(value) if sort else value:
185 output.append(' "%s",' % item)
186 output.append(' ],')
187 else:
188 output.append(' %s: "%s",' % (name, value))
189
190
191class Blueprint(object):
192 """In-memory representation of an Android.bp file."""
193
194 def __init__(self):
195 self.modules = {}
196
197 def add_module(self, module):
198 """Adds a new module to the blueprint, replacing any existing module
199 with the same name.
200
201 Args:
202 module: Module instance.
203 """
204 self.modules[module.name] = module
205
206 def to_string(self, output):
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000207 for m in sorted(self.modules.itervalues(), key=lambda m: m.name):
Sami Kyostila865d1d32017-12-12 18:37:04 +0000208 m.to_string(output)
209
210
211def label_to_path(label):
212 """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
213 assert label.startswith('//')
214 return label[2:]
215
216
217def label_to_module_name(label):
218 """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
Primiano Tucci4e49c022017-12-21 18:22:44 +0100219 module = re.sub(r'^//:?', '', label)
220 module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
221 if not module.startswith(module_prefix) and label not in default_targets:
Sami Kyostila865d1d32017-12-12 18:37:04 +0000222 return module_prefix + module
223 return module
224
225
226def label_without_toolchain(label):
227 """Strips the toolchain from a GN label.
228
229 Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
230 gcc_like_host) without the parenthesised toolchain part.
231 """
232 return label.split('(')[0]
233
234
235def is_supported_source_file(name):
236 """Returns True if |name| can appear in a 'srcs' list."""
237 return os.path.splitext(name)[1] in ['.c', '.cc', '.proto']
238
239
240def is_generated_by_action(desc, label):
241 """Checks if a label is generated by an action.
242
243 Returns True if a GN output label |label| is an output for any action,
244 i.e., the file is generated dynamically.
245 """
246 for target in desc.itervalues():
247 if target['type'] == 'action' and label in target['outputs']:
248 return True
249 return False
250
251
252def apply_module_dependency(blueprint, desc, module, dep_name):
253 """Recursively collect dependencies for a given module.
254
255 Walk the transitive dependencies for a GN target and apply them to a given
256 module. This effectively flattens the dependency tree so that |module|
257 directly contains all the sources, libraries, etc. in the corresponding GN
258 dependency tree.
259
260 Args:
261 blueprint: Blueprint instance which is being generated.
262 desc: JSON GN description.
263 module: Module to which dependencies should be added.
264 dep_name: GN target of the dependency.
265 """
Sami Kyostila865d1d32017-12-12 18:37:04 +0000266 # If the dependency refers to a library which we can replace with an Android
267 # equivalent, stop recursing and patch the dependency in.
268 if label_without_toolchain(dep_name) in builtin_deps:
269 builtin_deps[label_without_toolchain(dep_name)](module)
270 return
271
272 # Similarly some shared libraries are directly mapped to Android
273 # equivalents.
274 target = desc[dep_name]
275 for lib in target.get('libs', []):
276 android_lib = 'lib' + lib
277 if lib in library_whitelist and not android_lib in module.shared_libs:
278 module.shared_libs.append(android_lib)
279
280 type = target['type']
281 if type == 'action':
282 create_modules_from_target(blueprint, desc, dep_name)
283 # Depend both on the generated sources and headers -- see
284 # make_genrules_for_action.
285 module.srcs.append(':' + label_to_module_name(dep_name))
286 module.generated_headers.append(
287 label_to_module_name(dep_name) + '_headers')
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000288 elif type == 'static_library' and label_to_module_name(
289 dep_name) != module.name:
290 create_modules_from_target(blueprint, desc, dep_name)
291 module.static_libs.append(label_to_module_name(dep_name))
Primiano Tucci6067e732018-01-08 16:19:40 +0000292 elif type == 'shared_library' and label_to_module_name(
293 dep_name) != module.name:
294 module.shared_libs.append(label_to_module_name(dep_name))
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000295 elif type in ['group', 'source_set', 'executable', 'static_library'
296 ] and 'sources' in target:
Sami Kyostila865d1d32017-12-12 18:37:04 +0000297 # Ignore source files that are generated by actions since they will be
298 # implicitly added by the genrule dependencies.
299 module.srcs.extend(
300 label_to_path(src) for src in target['sources']
301 if is_supported_source_file(src)
302 and not is_generated_by_action(desc, src))
303
304
305def make_genrules_for_action(blueprint, desc, target_name):
306 """Generate genrules for a GN action.
307
308 GN actions are used to dynamically generate files during the build. The
309 Soong equivalent is a genrule. This function turns a specific kind of
310 genrule which turns .proto files into source and header files into a pair
Sami Kyostila71625d72017-12-18 10:29:49 +0000311 equivalent genrules.
Sami Kyostila865d1d32017-12-12 18:37:04 +0000312
313 Args:
314 blueprint: Blueprint instance which is being generated.
315 desc: JSON GN description.
316 target_name: GN target for genrule generation.
317
318 Returns:
319 A (source_genrule, header_genrule) module tuple.
320 """
321 target = desc[target_name]
322
323 # We only support genrules which call protoc (with or without a plugin) to
324 # turn .proto files into header and source files.
325 args = target['args']
326 if not args[0].endswith('/protoc'):
327 raise Error('Unsupported action in target %s: %s' % (target_name,
328 target['args']))
329
330 # We create two genrules for each action: one for the protobuf headers and
331 # another for the sources. This is because the module that depends on the
332 # generated files needs to declare two different types of dependencies --
333 # source files in 'srcs' and headers in 'generated_headers' -- and it's not
334 # valid to generate .h files from a source dependency and vice versa.
Sami Kyostila71625d72017-12-18 10:29:49 +0000335 source_module = Module('genrule', label_to_module_name(target_name))
Sami Kyostila865d1d32017-12-12 18:37:04 +0000336 source_module.srcs.extend(label_to_path(src) for src in target['sources'])
337 source_module.tools = ['aprotoc']
338
Sami Kyostila71625d72017-12-18 10:29:49 +0000339 header_module = Module('genrule',
Sami Kyostila865d1d32017-12-12 18:37:04 +0000340 label_to_module_name(target_name) + '_headers')
341 header_module.srcs = source_module.srcs[:]
342 header_module.tools = source_module.tools[:]
343 header_module.export_include_dirs = ['.']
344
345 # TODO(skyostil): Is there a way to avoid hardcoding the tree path here?
346 # TODO(skyostil): Find a way to avoid creating the directory.
347 cmd = [
348 'mkdir -p $(genDir)/%s &&' % tree_path, '$(location aprotoc)',
349 '--cpp_out=$(genDir)/%s' % tree_path,
350 '--proto_path=%s' % tree_path
351 ]
352 namespaces = ['pb']
353
354 parser = ThrowingArgumentParser('Action in target %s (%s)' %
355 (target_name, ' '.join(target['args'])))
356 parser.add_argument('--proto_path')
357 parser.add_argument('--cpp_out')
358 parser.add_argument('--plugin')
359 parser.add_argument('--plugin_out')
360 parser.add_argument('protos', nargs=argparse.REMAINDER)
361
362 args = parser.parse_args(args[1:])
363 if args.plugin:
364 _, plugin = os.path.split(args.plugin)
365 # TODO(skyostil): Can we detect this some other way?
366 if plugin == 'ipc_plugin':
367 namespaces.append('ipc')
368 elif plugin == 'protoc_plugin':
369 namespaces = ['pbzero']
370 for dep in target['deps']:
371 if desc[dep]['type'] != 'executable':
372 continue
373 _, executable = os.path.split(desc[dep]['outputs'][0])
374 if executable == plugin:
375 cmd += [
376 '--plugin=protoc-gen-plugin=$(location %s)' %
377 label_to_module_name(dep)
378 ]
379 source_module.tools.append(label_to_module_name(dep))
380 # Also make sure the module for the tool is generated.
381 create_modules_from_target(blueprint, desc, dep)
382 break
383 else:
384 raise Error('Unrecognized protoc plugin in target %s: %s' %
385 (target_name, args[i]))
386 if args.plugin_out:
387 plugin_args = args.plugin_out.split(':')[0]
388 cmd += ['--plugin_out=%s:$(genDir)/%s' % (plugin_args, tree_path)]
389
390 cmd += ['$(in)']
391 source_module.cmd = ' '.join(cmd)
392 header_module.cmd = source_module.cmd
393 header_module.tools = source_module.tools[:]
394
395 for ns in namespaces:
396 source_module.out += [
397 '%s/%s' % (tree_path, src.replace('.proto', '.%s.cc' % ns))
398 for src in source_module.srcs
399 ]
400 header_module.out += [
401 '%s/%s' % (tree_path, src.replace('.proto', '.%s.h' % ns))
402 for src in header_module.srcs
403 ]
404 return source_module, header_module
405
406
407def create_modules_from_target(blueprint, desc, target_name):
408 """Generate module(s) for a given GN target.
409
410 Given a GN target name, generate one or more corresponding modules into a
411 blueprint.
412
413 Args:
414 blueprint: Blueprint instance which is being generated.
415 desc: JSON GN description.
416 target_name: GN target for module generation.
417 """
418 target = desc[target_name]
419 if target['type'] == 'executable':
420 if 'host' in target['toolchain']:
421 module_type = 'cc_binary_host'
422 elif target.get('testonly'):
423 module_type = 'cc_test'
424 else:
425 module_type = 'cc_binary'
426 modules = [Module(module_type, label_to_module_name(target_name))]
427 elif target['type'] == 'action':
428 modules = make_genrules_for_action(blueprint, desc, target_name)
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000429 elif target['type'] == 'static_library':
430 modules = [
431 Module('cc_library_static', label_to_module_name(target_name))
432 ]
Primiano Tucci6067e732018-01-08 16:19:40 +0000433 elif target['type'] == 'shared_library':
434 modules = [
435 Module('cc_library_shared', label_to_module_name(target_name))
436 ]
Sami Kyostila865d1d32017-12-12 18:37:04 +0000437 else:
438 raise Error('Unknown target type: %s' % target['type'])
439
440 for module in modules:
441 module.comment = 'GN target: %s' % target_name
Primiano Tucci6067e732018-01-08 16:19:40 +0000442 if target_name in target_initrc:
443 module.init_rc = [target_initrc[target_name]]
444
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000445 # Don't try to inject library/source dependencies into genrules because
446 # they are not compiled in the traditional sense.
Sami Kyostila71625d72017-12-18 10:29:49 +0000447 if module.type != 'genrule':
Primiano Tucciedf099c2018-01-08 18:27:56 +0000448 for flag in target.get('cflags', []):
449 if re.match(cflag_whitelist, flag):
450 module.cflags.append(flag)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000451 module.defaults = [defaults_module]
Sami Kyostilaebba0fe2017-12-19 14:01:52 +0000452 apply_module_dependency(blueprint, desc, module, target_name)
453 for dep in resolve_dependencies(desc, target_name):
454 apply_module_dependency(blueprint, desc, module, dep)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000455
456 blueprint.add_module(module)
457
458
459def resolve_dependencies(desc, target_name):
460 """Return the transitive set of dependent-on targets for a GN target.
461
462 Args:
463 blueprint: Blueprint instance which is being generated.
464 desc: JSON GN description.
465
466 Returns:
467 A set of transitive dependencies in the form of GN targets.
468 """
469
470 if label_without_toolchain(target_name) in builtin_deps:
471 return set()
472 target = desc[target_name]
473 resolved_deps = set()
474 for dep in target.get('deps', []):
475 resolved_deps.add(dep)
476 # Ignore the transitive dependencies of actions because they are
477 # explicitly converted to genrules.
478 if desc[dep]['type'] == 'action':
479 continue
Primiano Tucci6067e732018-01-08 16:19:40 +0000480 # Dependencies on shared libraries shouldn't propagate any transitive
481 # dependencies but only depend on the shared library target
482 if desc[dep]['type'] == 'shared_library':
483 continue
Sami Kyostila865d1d32017-12-12 18:37:04 +0000484 resolved_deps.update(resolve_dependencies(desc, dep))
485 return resolved_deps
486
487
488def create_blueprint_for_targets(desc, targets):
489 """Generate a blueprint for a list of GN targets."""
490 blueprint = Blueprint()
491
492 # Default settings used by all modules.
493 defaults = Module('cc_defaults', defaults_module)
494 defaults.local_include_dirs = ['include']
495 defaults.cflags = [
496 '-Wno-error=return-type',
497 '-Wno-sign-compare',
498 '-Wno-sign-promo',
499 '-Wno-unused-parameter',
500 ]
501
502 blueprint.add_module(defaults)
503 for target in targets:
504 create_modules_from_target(blueprint, desc, target)
505 return blueprint
506
507
Sami Kyostilab27619f2017-12-13 19:22:16 +0000508def repo_root():
509 """Returns an absolute path to the repository root."""
510
511 return os.path.join(
512 os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
513
514
515def create_build_description():
516 """Creates the JSON build description by running GN."""
517
518 out = os.path.join(repo_root(), 'out', 'tmp.gen_android_bp')
519 try:
520 try:
521 os.makedirs(out)
522 except OSError as e:
523 if e.errno != errno.EEXIST:
524 raise
525 subprocess.check_output(
526 ['gn', 'gen', out, '--args=%s' % gn_args], cwd=repo_root())
527 desc = subprocess.check_output(
528 ['gn', 'desc', out, '--format=json', '--all-toolchains', '//*'],
529 cwd=repo_root())
530 return json.loads(desc)
531 finally:
532 shutil.rmtree(out)
533
534
Sami Kyostila865d1d32017-12-12 18:37:04 +0000535def main():
536 parser = argparse.ArgumentParser(
537 description='Generate Android.bp from a GN description.')
538 parser.add_argument(
Sami Kyostilab27619f2017-12-13 19:22:16 +0000539 '--desc',
Sami Kyostila865d1d32017-12-12 18:37:04 +0000540 help=
541 'GN description (e.g., gn desc out --format=json --all-toolchains "//*"'
542 )
543 parser.add_argument(
Sami Kyostilab27619f2017-12-13 19:22:16 +0000544 '--output',
545 help='Blueprint file to create',
546 default=os.path.join(repo_root(), 'Android.bp'),
547 )
548 parser.add_argument(
Sami Kyostila865d1d32017-12-12 18:37:04 +0000549 'targets',
550 nargs=argparse.REMAINDER,
551 help='Targets to include in the blueprint (e.g., "//:perfetto_tests")')
552 args = parser.parse_args()
553
Sami Kyostilab27619f2017-12-13 19:22:16 +0000554 if args.desc:
555 with open(args.desc) as f:
556 desc = json.load(f)
557 else:
558 desc = create_build_description()
Sami Kyostila865d1d32017-12-12 18:37:04 +0000559
Sami Kyostilab27619f2017-12-13 19:22:16 +0000560 blueprint = create_blueprint_for_targets(desc, args.targets
561 or default_targets)
Sami Kyostila865d1d32017-12-12 18:37:04 +0000562 output = [
563 """// Copyright (C) 2017 The Android Open Source Project
564//
565// Licensed under the Apache License, Version 2.0 (the "License");
566// you may not use this file except in compliance with the License.
567// You may obtain a copy of the License at
568//
569// http://www.apache.org/licenses/LICENSE-2.0
570//
571// Unless required by applicable law or agreed to in writing, software
572// distributed under the License is distributed on an "AS IS" BASIS,
573// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
574// See the License for the specific language governing permissions and
575// limitations under the License.
576//
577// This file is automatically generated by %s. Do not edit.
578""" % (__file__)
579 ]
580 blueprint.to_string(output)
Sami Kyostilab27619f2017-12-13 19:22:16 +0000581 with open(args.output, 'w') as f:
582 f.write('\n'.join(output))
Sami Kyostila865d1d32017-12-12 18:37:04 +0000583
584
585if __name__ == '__main__':
586 sys.exit(main())