1#!/usr/bin/env python3 2# Copyright (C) 2022 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 logging as log 31import operator 32import os 33import re 34import sys 35import copy 36from pathlib import Path 37 38import gn_utils 39 40ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 41 42CRONET_LICENSE_NAME = "external_cronet_license" 43 44# Default targets to translate to the blueprint file. 45DEFAULT_TARGETS = [ 46 '//components/cronet/android:cronet', 47 '//components/cronet/android:cronet_android_mainline', 48] 49 50DEFAULT_TESTS = [ 51 '//components/cronet/android:cronet_unittests_android__library', 52 '//net:net_unittests__library', 53 '//components/cronet/android:cronet_tests', 54] 55 56EXTRAS_ANDROID_BP_FILE = "Android.extras.bp" 57 58CRONET_API_MODULE_NAME = "cronet_aml_api_java" 59 60# All module names are prefixed with this string to avoid collisions. 61module_prefix = 'cronet_aml_' 62 63# Shared libraries which are directly translated to Android system equivalents. 64shared_library_allowlist = [ 65 'android', 66 'log', 67] 68 69# Include directories that will be removed from all targets. 70local_include_dirs_denylist = [ 71 'third_party/zlib/', 72] 73 74# Name of the module which settings such as compiler flags for all other 75# modules. 76defaults_module = module_prefix + 'defaults' 77 78# Location of the project in the Android source tree. 79tree_path = 'external/cronet' 80 81# Path for the protobuf sources in the standalone build. 82buildtools_protobuf_src = '//buildtools/protobuf/src' 83 84# Location of the protobuf src dir in the Android source tree. 85android_protobuf_src = 'external/protobuf/src' 86 87# put all args on a new line for better diffs. 88NEWLINE = ' " +\n "' 89 90# Compiler flags which are passed through to the blueprint. 91cflag_allowlist = [ 92 # needed for zlib:zlib 93 "-mpclmul", 94 # needed for zlib:zlib 95 "-mssse3", 96 # needed for zlib:zlib 97 "-msse3", 98 # needed for zlib:zlib 99 "-msse4.2", 100 # flags to reduce binary size 101 "-O1", 102 "-O2", 103 "-O3", 104 "-Oz", 105 "-g1", 106 "-g2", 107 "-fdata-sections", 108 "-ffunction-sections", 109 "-fvisibility=hidden", 110 "-fvisibility-inlines-hidden", 111 "-fstack-protector", 112 "-mno-outline", 113 "-mno-outline-atomics", 114 "-fno-asynchronous-unwind-tables", 115 "-fno-unwind-tables", 116] 117 118# Linker flags which are passed through to the blueprint. 119ldflag_allowlist = [ 120 # flags to reduce binary size 121 "-Wl,--as-needed", 122 "-Wl,--gc-sections", 123 "-Wl,--icf=all", 124] 125 126def get_linker_script_ldflag(script_path): 127 return f'-Wl,--script,{tree_path}/{script_path}' 128 129# Additional arguments to apply to Android.bp rules. 130additional_args = { 131 'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers': [ 132 ('export_include_dirs', { 133 "net/third_party/quiche/src", 134 }) 135 ], 136 'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto__testing_gen_headers': [ 137 ('export_include_dirs', { 138 "net/third_party/quiche/src", 139 }) 140 ], 141 'cronet_aml_third_party_quic_trace_quic_trace_proto__testing_gen_headers': [ 142 ('export_include_dirs', { 143 "third_party/quic_trace/src", 144 }) 145 ], 146 'cronet_aml_net_net': [ 147 ('export_static_lib_headers', { 148 'cronet_aml_net_third_party_quiche_quiche', 149 'cronet_aml_crypto_crypto', 150 }), 151 ], 152 # TODO: fix upstream. Both //base:base and 153 # //base/allocator/partition_allocator:partition_alloc do not create a 154 # dependency on gtest despite using gtest_prod.h. 155 'cronet_aml_base_base': [ 156 ('header_libs', { 157 'libgtest_prod_headers', 158 }), 159 ('export_header_lib_headers', { 160 'libgtest_prod_headers', 161 }), 162 ], 163 'cronet_aml_base_allocator_partition_allocator_partition_alloc': [ 164 ('header_libs', { 165 'libgtest_prod_headers', 166 }), 167 ], 168} 169 170def always_disable(module, arch): 171 return None 172 173def enable_zlib(module, arch): 174 # Requires crrev/c/4109079 175 if arch == 'common': 176 module.shared_libs.add('libz') 177 else: 178 module.target[arch].shared_libs.add('libz') 179 180def enable_boringssl(module, arch): 181 # Do not add boringssl targets to cc_genrules. This happens, because protobuf targets are 182 # originally static_libraries, but later get converted to a cc_genrule. 183 if module.is_genrule(): return 184 if arch == 'common': 185 shared_libs = module.shared_libs 186 else: 187 shared_libs = module.target[arch].shared_libs 188 shared_libs.add('//external/cronet/third_party/boringssl:libcrypto') 189 shared_libs.add('//external/cronet/third_party/boringssl:libssl') 190 191# Android equivalents for third-party libraries that the upstream project 192# depends on. 193builtin_deps = { 194 '//buildtools/third_party/libunwind:libunwind': 195 always_disable, 196 '//buildtools/third_party/libunwind:libunwind__testing': 197 always_disable, 198 '//net/data/ssl/chrome_root_store:gen_root_store_inc': 199 always_disable, 200 '//net/data/ssl/chrome_root_store:gen_root_store_inc__testing': 201 always_disable, 202 '//net/tools/root_store_tool:root_store_tool': 203 always_disable, 204 '//net/tools/root_store_tool:root_store_tool__testing': 205 always_disable, 206 '//third_party/zlib:zlib': 207 enable_zlib, 208 '//third_party/zlib:zlib__testing': 209 enable_zlib, 210 '//third_party/boringssl:boringssl': 211 enable_boringssl, 212 '//third_party/boringssl:boringssl_asm': 213 # Due to FIPS requirements, downstream BoringSSL has a different "shape" than upstream's. 214 # We're guaranteed that if X depends on :boringssl it will also depend on :boringssl_asm. 215 # Hence, always drop :boringssl_asm and handle the translation entirely in :boringssl. 216 always_disable, 217} 218 219# Name of tethering apex module 220tethering_apex = "com.android.tethering" 221 222# Name of cronet api target 223java_api_target_name = "//components/cronet/android:cronet_api_java" 224 225# Visibility set for package default 226package_default_visibility = ":__subpackages__" 227 228# Visibility set for modules used from Connectivity 229connectivity_visibility = "//packages/modules/Connectivity:__subpackages__" 230 231# ---------------------------------------------------------------------------- 232# End of configuration. 233# ---------------------------------------------------------------------------- 234 235def write_blueprint_key_value(output, name, value, sort=True): 236 """Writes a Blueprint key-value pair to the output""" 237 238 if isinstance(value, bool): 239 if value: 240 output.append(' %s: true,' % name) 241 else: 242 output.append(' %s: false,' % name) 243 return 244 if not value: 245 return 246 if isinstance(value, set): 247 value = sorted(value) 248 if isinstance(value, list): 249 output.append(' %s: [' % name) 250 for item in sorted(value) if sort else value: 251 output.append(' "%s",' % item) 252 output.append(' ],') 253 return 254 if isinstance(value, Module.Target): 255 value.to_string(output) 256 return 257 if isinstance(value, dict): 258 kv_output = [] 259 for k, v in value.items(): 260 write_blueprint_key_value(kv_output, k, v) 261 262 output.append(' %s: {' % name) 263 for line in kv_output: 264 output.append(' %s' % line) 265 output.append(' },') 266 return 267 output.append(' %s: "%s",' % (name, value)) 268 269 270 271class Module(object): 272 """A single module (e.g., cc_binary, cc_test) in a blueprint.""" 273 274 class Target(object): 275 """A target-scoped part of a module""" 276 277 def __init__(self, name): 278 self.name = name 279 self.srcs = set() 280 self.shared_libs = set() 281 self.static_libs = set() 282 self.whole_static_libs = set() 283 self.header_libs = set() 284 self.cflags = set() 285 self.stl = None 286 self.cppflags = set() 287 self.local_include_dirs = set() 288 self.generated_headers = set() 289 self.export_generated_headers = set() 290 self.ldflags = set() 291 self.compile_multilib = None 292 if name == 'host': 293 self.compile_multilib = '64' 294 295 def to_string(self, output): 296 nested_out = [] 297 self._output_field(nested_out, 'srcs') 298 self._output_field(nested_out, 'shared_libs') 299 self._output_field(nested_out, 'static_libs') 300 self._output_field(nested_out, 'whole_static_libs') 301 self._output_field(nested_out, 'header_libs') 302 self._output_field(nested_out, 'cflags') 303 self._output_field(nested_out, 'stl') 304 self._output_field(nested_out, 'cppflags') 305 self._output_field(nested_out, 'local_include_dirs') 306 self._output_field(nested_out, 'generated_headers') 307 self._output_field(nested_out, 'export_generated_headers') 308 self._output_field(nested_out, 'ldflags') 309 310 if nested_out: 311 # This is added here to make sure it doesn't add a `host` arch-specific module just for 312 # `compile_multilib` flag. 313 self._output_field(nested_out, 'compile_multilib') 314 output.append(' %s: {' % self.name) 315 for line in nested_out: 316 output.append(' %s' % line) 317 output.append(' },') 318 319 def _output_field(self, output, name, sort=True): 320 value = getattr(self, name) 321 return write_blueprint_key_value(output, name, value, sort) 322 323 324 def __init__(self, mod_type, name, gn_target): 325 self.type = mod_type 326 self.gn_target = gn_target 327 self.name = name 328 self.srcs = set() 329 self.comment = 'GN: ' + gn_target 330 self.shared_libs = set() 331 self.static_libs = set() 332 self.whole_static_libs = set() 333 self.tools = set() 334 self.cmd = None 335 self.host_supported = False 336 self.device_supported = True 337 self.init_rc = set() 338 self.out = set() 339 self.export_include_dirs = set() 340 self.generated_headers = set() 341 self.export_generated_headers = set() 342 self.export_static_lib_headers = set() 343 self.export_header_lib_headers = set() 344 self.defaults = set() 345 self.cflags = set() 346 self.include_dirs = set() 347 self.local_include_dirs = set() 348 self.header_libs = set() 349 self.tool_files = set() 350 # target contains a dict of Targets indexed by os_arch. 351 # example: { 'android_x86': Target('android_x86') 352 self.target = dict() 353 self.target['android'] = self.Target('android') 354 self.target['android_x86'] = self.Target('android_x86') 355 self.target['android_x86_64'] = self.Target('android_x86_64') 356 self.target['android_arm'] = self.Target('android_arm') 357 self.target['android_arm64'] = self.Target('android_arm64') 358 self.target['host'] = self.Target('host') 359 self.target['glibc'] = self.Target('glibc') 360 self.stl = None 361 self.cpp_std = None 362 self.strip = dict() 363 self.data = set() 364 self.apex_available = set() 365 self.min_sdk_version = None 366 self.proto = dict() 367 self.linker_scripts = set() 368 self.ldflags = set() 369 # The genrule_XXX below are properties that must to be propagated back 370 # on the module(s) that depend on the genrule. 371 self.genrule_headers = set() 372 self.genrule_srcs = set() 373 self.genrule_shared_libs = set() 374 self.genrule_header_libs = set() 375 self.version_script = None 376 self.test_suites = set() 377 self.test_config = None 378 self.cppflags = set() 379 self.rtti = False 380 # Name of the output. Used for setting .so file name for libcronet 381 self.libs = set() 382 self.stem = None 383 self.compile_multilib = None 384 self.aidl = dict() 385 self.plugins = set() 386 self.processor_class = None 387 self.sdk_version = None 388 self.javacflags = set() 389 self.c_std = None 390 self.default_applicable_licenses = set() 391 self.default_visibility = [] 392 self.visibility = [] 393 self.gn_type = None 394 395 def to_string(self, output): 396 if self.comment: 397 output.append('// %s' % self.comment) 398 output.append('%s {' % self.type) 399 self._output_field(output, 'name') 400 self._output_field(output, 'srcs') 401 self._output_field(output, 'shared_libs') 402 self._output_field(output, 'static_libs') 403 self._output_field(output, 'whole_static_libs') 404 self._output_field(output, 'tools') 405 self._output_field(output, 'cmd', sort=False) 406 if self.host_supported: 407 self._output_field(output, 'host_supported') 408 if not self.device_supported: 409 self._output_field(output, 'device_supported') 410 self._output_field(output, 'init_rc') 411 self._output_field(output, 'out') 412 self._output_field(output, 'export_include_dirs') 413 self._output_field(output, 'generated_headers') 414 self._output_field(output, 'export_generated_headers') 415 self._output_field(output, 'export_static_lib_headers') 416 self._output_field(output, 'export_header_lib_headers') 417 self._output_field(output, 'defaults') 418 self._output_field(output, 'cflags') 419 self._output_field(output, 'include_dirs') 420 self._output_field(output, 'local_include_dirs') 421 self._output_field(output, 'header_libs') 422 self._output_field(output, 'strip') 423 self._output_field(output, 'tool_files') 424 self._output_field(output, 'data') 425 self._output_field(output, 'stl') 426 self._output_field(output, 'cpp_std') 427 self._output_field(output, 'apex_available') 428 self._output_field(output, 'min_sdk_version') 429 self._output_field(output, 'version_script') 430 self._output_field(output, 'test_suites') 431 self._output_field(output, 'test_config') 432 self._output_field(output, 'proto') 433 self._output_field(output, 'linker_scripts') 434 self._output_field(output, 'ldflags') 435 self._output_field(output, 'cppflags') 436 self._output_field(output, 'libs') 437 self._output_field(output, 'stem') 438 self._output_field(output, 'compile_multilib') 439 self._output_field(output, 'aidl') 440 self._output_field(output, 'plugins') 441 self._output_field(output, 'processor_class') 442 self._output_field(output, 'sdk_version') 443 self._output_field(output, 'javacflags') 444 self._output_field(output, 'c_std') 445 self._output_field(output, 'default_applicable_licenses') 446 self._output_field(output, 'default_visibility') 447 self._output_field(output, 'visibility') 448 if self.rtti: 449 self._output_field(output, 'rtti') 450 451 target_out = [] 452 for arch, target in sorted(self.target.items()): 453 # _output_field calls getattr(self, arch). 454 setattr(self, arch, target) 455 self._output_field(target_out, arch) 456 457 if target_out: 458 output.append(' target: {') 459 for line in target_out: 460 output.append(' %s' % line) 461 output.append(' },') 462 463 output.append('}') 464 output.append('') 465 466 def add_android_shared_lib(self, lib): 467 if self.type == 'cc_binary_host': 468 raise Exception('Adding Android shared lib for host tool is unsupported') 469 elif self.host_supported: 470 self.target['android'].shared_libs.add(lib) 471 else: 472 self.shared_libs.add(lib) 473 474 def is_test(self): 475 if gn_utils.TESTING_SUFFIX in self.name: 476 name_without_prefix = self.name[:self.name.find(gn_utils.TESTING_SUFFIX)] 477 return any([name_without_prefix == label_to_module_name(target) for target in DEFAULT_TESTS]) 478 return False 479 480 def _output_field(self, output, name, sort=True): 481 value = getattr(self, name) 482 return write_blueprint_key_value(output, name, value, sort) 483 484 def is_compiled(self): 485 return self.type not in ('cc_genrule', 'filegroup', 'java_genrule') 486 487 def is_genrule(self): 488 return self.type == "cc_genrule" 489 490 def has_input_files(self): 491 if len(self.srcs) > 0: 492 return True 493 if any([len(target.srcs) > 0 for target in self.target.values()]): 494 return True 495 # Allow cc_static_library with export_generated_headers as those are crucial for 496 # the depending modules 497 return len(self.export_generated_headers) > 0 498 499 500class Blueprint(object): 501 """In-memory representation of an Android.bp file.""" 502 503 def __init__(self): 504 self.modules = {} 505 506 def add_module(self, module): 507 """Adds a new module to the blueprint, replacing any existing module 508 with the same name. 509 510 Args: 511 module: Module instance. 512 """ 513 self.modules[module.name] = module 514 515 def to_string(self, output): 516 for m in sorted(self.modules.values(), key=lambda m: m.name): 517 if m.type != "cc_library_static" or m.has_input_files(): 518 # Don't print cc_library_static with empty srcs. These attributes are already 519 # propagated up the tree. Printing them messes the presubmits because 520 # every module is compiled while those targets are not reachable in 521 # a normal compilation path. 522 m.to_string(output) 523 524 525def label_to_module_name(label): 526 """Turn a GN label (e.g., //:perfetto_tests) into a module name.""" 527 module = re.sub(r'^//:?', '', label) 528 module = re.sub(r'[^a-zA-Z0-9_]', '_', module) 529 530 if not module.startswith(module_prefix): 531 return module_prefix + module 532 return module 533 534 535def is_supported_source_file(name): 536 """Returns True if |name| can appear in a 'srcs' list.""" 537 return os.path.splitext(name)[1] in ['.c', '.cc', '.cpp', '.java', '.proto', '.S'] 538 539 540def get_protoc_module_name(gn): 541 protoc_gn_target_name = gn.get_target('//third_party/protobuf:protoc').name 542 return label_to_module_name(protoc_gn_target_name) 543 544 545def create_proto_modules(blueprint, gn, target): 546 """Generate genrules for a proto GN target. 547 548 GN actions are used to dynamically generate files during the build. The 549 Soong equivalent is a genrule. This function turns a specific kind of 550 genrule which turns .proto files into source and header files into a pair 551 equivalent genrules. 552 553 Args: 554 blueprint: Blueprint instance which is being generated. 555 target: gn_utils.Target object. 556 557 Returns: 558 The source_genrule module. 559 """ 560 assert (target.type == 'proto_library') 561 562 protoc_module_name = get_protoc_module_name(gn) 563 tools = {protoc_module_name} 564 cpp_out_dir = '$(genDir)/%s/%s/' % (tree_path, target.proto_in_dir) 565 target_module_name = label_to_module_name(target.name) 566 567 # In GN builds the proto path is always relative to the output directory 568 # (out/tmp.xxx). 569 cmd = ['$(location %s)' % protoc_module_name] 570 cmd += ['--proto_path=%s/%s' % (tree_path, target.proto_in_dir)] 571 572 if buildtools_protobuf_src in target.proto_paths: 573 cmd += ['--proto_path=%s' % android_protobuf_src] 574 575 # We don't generate any targets for source_set proto modules because 576 # they will be inlined into other modules if required. 577 if target.proto_plugin == 'source_set': 578 return None 579 580 # Descriptor targets only generate a single target. 581 if target.proto_plugin == 'descriptor': 582 out = '{}.bin'.format(target_module_name) 583 584 cmd += ['--descriptor_set_out=$(out)'] 585 cmd += ['$(in)'] 586 587 descriptor_module = Module('cc_genrule', target_module_name, target.name) 588 descriptor_module.cmd = ' '.join(cmd) 589 descriptor_module.out = [out] 590 descriptor_module.tools = tools 591 blueprint.add_module(descriptor_module) 592 593 # Recursively extract the .proto files of all the dependencies and 594 # add them to srcs. 595 descriptor_module.srcs.update( 596 gn_utils.label_to_path(src) for src in target.sources) 597 for dep in target.transitive_proto_deps: 598 current_target = gn.get_target(dep) 599 descriptor_module.srcs.update( 600 gn_utils.label_to_path(src) for src in current_target.sources) 601 602 return descriptor_module 603 604 # We create two genrules for each proto target: one for the headers and 605 # another for the sources. This is because the module that depends on the 606 # generated files needs to declare two different types of dependencies -- 607 # source files in 'srcs' and headers in 'generated_headers' -- and it's not 608 # valid to generate .h files from a source dependency and vice versa. 609 source_module_name = target_module_name + '_gen' 610 source_module = Module('cc_genrule', source_module_name, target.name) 611 blueprint.add_module(source_module) 612 source_module.srcs.update( 613 gn_utils.label_to_path(src) for src in target.sources) 614 615 header_module = Module('cc_genrule', source_module_name + '_headers', 616 target.name) 617 blueprint.add_module(header_module) 618 header_module.srcs = set(source_module.srcs) 619 620 # TODO(primiano): at some point we should remove this. This was introduced 621 # by aosp/1108421 when adding "protos/" to .proto include paths, in order to 622 # avoid doing multi-repo changes and allow old clients in the android tree 623 # to still do the old #include "perfetto/..." rather than 624 # #include "protos/perfetto/...". 625 header_module.export_include_dirs = {'.', 'protos'} 626 # Since the .cc file and .h get created by a different gerule target, they 627 # are not put in the same intermediate path, so local includes do not work 628 # without explictily exporting the include dir. 629 header_module.export_include_dirs.add(target.proto_in_dir) 630 631 # This function does not return header_module so setting apex_available attribute here. 632 header_module.apex_available.add(tethering_apex) 633 634 source_module.genrule_srcs.add(':' + source_module.name) 635 source_module.genrule_headers.add(header_module.name) 636 637 if target.proto_plugin == 'proto': 638 suffixes = ['pb'] 639 source_module.genrule_shared_libs.add('libprotobuf-cpp-lite') 640 cmd += ['--cpp_out=lite=true:' + cpp_out_dir] 641 else: 642 raise Exception('Unsupported proto plugin: %s' % target.proto_plugin) 643 644 cmd += ['$(in)'] 645 source_module.cmd = ' '.join(cmd) 646 header_module.cmd = source_module.cmd 647 source_module.tools = tools 648 header_module.tools = tools 649 650 for sfx in suffixes: 651 source_module.out.update('%s/%s' % 652 (tree_path, src.replace('.proto', '.%s.cc' % sfx)) 653 for src in source_module.srcs) 654 header_module.out.update('%s/%s' % 655 (tree_path, src.replace('.proto', '.%s.h' % sfx)) 656 for src in header_module.srcs) 657 return source_module 658 659 660def create_gcc_preprocess_modules(blueprint, target): 661 # gcc_preprocess.py internally execute host gcc which is not allowed in genrule. 662 # So, this function create multiple modules and realize equivalent processing 663 # TODO: Consider to support gcc_preprocess.py in different way 664 # It's not great to have genrule and cc_object in the dependency from java_library 665 assert (len(target.sources) == 1) 666 source = list(target.sources)[0] 667 assert (Path(source).suffix == '.template') 668 stem = Path(source).stem 669 670 bp_module_name = label_to_module_name(target.name) 671 672 # Rename .template to .cc since cc_object does not accept .template file as srcs 673 rename_module = Module('genrule', bp_module_name + '_rename', target.name) 674 rename_module.srcs.add(gn_utils.label_to_path(source)) 675 rename_module.out.add(stem + '.cc') 676 rename_module.cmd = 'cp $(in) $(out)' 677 blueprint.add_module(rename_module) 678 679 # Preprocess template file and generates java file 680 preprocess_module = Module('cc_object', bp_module_name + '_preprocess', target.name) 681 # -E: stop after preprocessing. 682 # -P: disable line markers, i.e. '#line 309' 683 preprocess_module.cflags.update(['-E', '-P', '-DANDROID']) 684 preprocess_module.srcs.add(':' + rename_module.name) 685 defines = ['-D' + target.args[i+1] for i, arg in enumerate(target.args) if arg == '--define'] 686 preprocess_module.cflags.update(defines) 687 # HACK: Specifying compile_multilib to build cc_object only once. 688 # Without this, soong complain to genrule that depends on cc_object when built for 64bit target. 689 # It seems this is because cc object is a module with per-architecture variants and genrule is a 690 # module with default variant. For 64bit target, cc_object is built multiple times for 32/64bit 691 # modes and genrule doesn't know which one to depend on. 692 preprocess_module.compile_multilib = 'first' 693 blueprint.add_module(preprocess_module) 694 695 # Generates srcjar using soong_zip 696 module = Module('genrule', bp_module_name, target.name) 697 module.srcs.add(':' + preprocess_module.name) 698 module.out.add(stem + '.srcjar') 699 module.cmd = NEWLINE.join([ 700 f'cp $(in) $(genDir)/{stem}.java &&', 701 f'$(location soong_zip) -o $(out) -srcjar -C $(genDir) -f $(genDir)/{stem}.java' 702 ]) 703 module.tools.add('soong_zip') 704 blueprint.add_module(module) 705 return module 706 707 708class BaseActionSanitizer(): 709 def __init__(self, target, arch): 710 # Just to be on the safe side, create a deep-copy. 711 self.target = copy.deepcopy(target) 712 if arch: 713 # Merge arch specific attributes 714 self.target.sources |= arch.sources 715 self.target.inputs |= arch.inputs 716 self.target.outputs |= arch.outputs 717 self.target.script = self.target.script or arch.script 718 self.target.args = self.target.args or arch.args 719 self.target.response_file_contents = \ 720 self.target.response_file_contents or arch.response_file_contents 721 self.target.args = self._normalize_args() 722 723 def get_name(self): 724 return label_to_module_name(self.target.name) 725 726 def _normalize_args(self): 727 # Convert ['--param=value'] to ['--param', 'value'] for consistency. 728 # Escape quotations. 729 normalized_args = [] 730 for arg in self.target.args: 731 arg = arg.replace('"', r'\"') 732 if arg.startswith('-'): 733 normalized_args.extend(arg.split('=')) 734 else: 735 normalized_args.append(arg) 736 return normalized_args 737 738 # There are three types of args: 739 # - flags (--flag) 740 # - value args (--arg value) 741 # - list args (--arg value1 --arg value2) 742 # value args have exactly one arg value pair and list args have one or more arg value pairs. 743 # Note that the set of list args contains the set of value args. 744 # This is because list and value args are identical when the list args has only one arg value pair 745 # Some functions provide special implementations for each type, while others 746 # work on all of them. 747 def _has_arg(self, arg): 748 return arg in self.target.args 749 750 def _get_arg_indices(self, target_arg): 751 return [i for i, arg in enumerate(self.target.args) if arg == target_arg] 752 753 # Whether an arg value pair appears once or more times 754 def _is_list_arg(self, arg): 755 indices = self._get_arg_indices(arg) 756 return len(indices) > 0 and all([not self.target.args[i + 1].startswith('--') for i in indices]) 757 758 def _update_list_arg(self, arg, func, throw_if_absent = True): 759 if self._should_fail_silently(arg, throw_if_absent): 760 return 761 assert(self._is_list_arg(arg)) 762 indices = self._get_arg_indices(arg) 763 for i in indices: 764 self._set_arg_at(i + 1, func(self.target.args[i + 1])) 765 766 # Whether an arg value pair appears exactly once 767 def _is_value_arg(self, arg): 768 return operator.countOf(self.target.args, arg) == 1 and self._is_list_arg(arg) 769 770 def _get_value_arg(self, arg): 771 assert(self._is_value_arg(arg)) 772 i = self.target.args.index(arg) 773 return self.target.args[i + 1] 774 775 # used to check whether a function call should cause an error when an arg is 776 # missing. 777 def _should_fail_silently(self, arg, throw_if_absent): 778 return not throw_if_absent and not self._has_arg(arg) 779 780 def _set_value_arg(self, arg, value, throw_if_absent = True): 781 if self._should_fail_silently(arg, throw_if_absent): 782 return 783 assert(self._is_value_arg(arg)) 784 i = self.target.args.index(arg) 785 self.target.args[i + 1] = value 786 787 def _update_value_arg(self, arg, func, throw_if_absent = True): 788 if self._should_fail_silently(arg, throw_if_absent): 789 return 790 self._set_value_arg(arg, func(self._get_value_arg(arg))) 791 792 def _set_arg_at(self, position, value): 793 self.target.args[position] = value 794 795 def _update_arg_at(self, position, func): 796 self.target.args[position] = func(self.target.args[position]) 797 798 def _delete_value_arg(self, arg, throw_if_absent = True): 799 if self._should_fail_silently(arg, throw_if_absent): 800 return 801 assert(self._is_value_arg(arg)) 802 i = self.target.args.index(arg) 803 self.target.args.pop(i) 804 self.target.args.pop(i) 805 806 def _append_arg(self, arg, value): 807 self.target.args.append(arg) 808 self.target.args.append(value) 809 810 def _sanitize_filepath_with_location_tag(self, arg): 811 if arg.startswith('../../'): 812 arg = self._sanitize_filepath(arg) 813 arg = self._add_location_tag(arg) 814 return arg 815 816 # wrap filename in location tag. 817 def _add_location_tag(self, filename): 818 return '$(location %s)' % filename 819 820 # applies common directory transformation that *should* be universally applicable. 821 # TODO: verify if it actually *is* universally applicable. 822 def _sanitize_filepath(self, filepath): 823 # Careful, order matters! 824 # delete all leading ../ 825 filepath = re.sub('^(\.\./)+', '', filepath) 826 filepath = re.sub('^gen/jni_headers', '$(genDir)', filepath) 827 filepath = re.sub('^gen', '$(genDir)', filepath) 828 return filepath 829 830 # Iterate through all the args and apply function 831 def _update_all_args(self, func): 832 self.target.args = [func(arg) for arg in self.target.args] 833 834 def get_cmd(self): 835 arg_string = NEWLINE.join(self.target.args) 836 cmd = '$(location %s) %s' % ( 837 gn_utils.label_to_path(self.target.script), arg_string) 838 839 if self.use_response_file: 840 # Pipe response file contents into script 841 cmd = 'echo \'%s\' |%s%s' % (self.target.response_file_contents, NEWLINE, cmd) 842 return cmd 843 844 def get_outputs(self): 845 return self.target.outputs 846 847 def get_srcs(self): 848 # gn treats inputs and sources for actions equally. 849 # soong only supports source files inside srcs, non-source files are added as 850 # tool_files dependency. 851 files = self.target.sources.union(self.target.inputs) 852 return {gn_utils.label_to_path(file) for file in files if is_supported_source_file(file)} 853 854 def get_tools(self): 855 return set() 856 857 def get_tool_files(self): 858 # gn treats inputs and sources for actions equally. 859 # soong only supports source files inside srcs, non-source files are added as 860 # tool_files dependency. 861 files = self.target.sources.union(self.target.inputs) 862 tool_files = {gn_utils.label_to_path(file) 863 for file in files if not is_supported_source_file(file)} 864 tool_files.add(gn_utils.label_to_path(self.target.script)) 865 return tool_files 866 867 def _sanitize_args(self): 868 # Handle passing parameters via response file by piping them into the script 869 # and reading them from /dev/stdin. 870 871 self.use_response_file = gn_utils.RESPONSE_FILE in self.target.args 872 if self.use_response_file: 873 # Replace {{response_file_contents}} with /dev/stdin 874 self.target.args = ['/dev/stdin' if it == gn_utils.RESPONSE_FILE else it 875 for it in self.target.args] 876 877 def _sanitize_outputs(self): 878 pass 879 880 def _sanitize_inputs(self): 881 pass 882 883 def sanitize(self): 884 self._sanitize_args() 885 self._sanitize_outputs() 886 self._sanitize_inputs() 887 888 # Whether this target generates header files 889 def is_header_generated(self): 890 return any(os.path.splitext(it)[1] == '.h' for it in self.target.outputs) 891 892class WriteBuildDateHeaderSanitizer(BaseActionSanitizer): 893 def _sanitize_args(self): 894 self._set_arg_at(0, '$(out)') 895 super()._sanitize_args() 896 897class WriteBuildFlagHeaderSanitizer(BaseActionSanitizer): 898 def _sanitize_args(self): 899 self._set_value_arg('--gen-dir', '.') 900 self._set_value_arg('--output', '$(out)') 901 super()._sanitize_args() 902 903class GnRunBinarySanitizer(BaseActionSanitizer): 904 def __init__(self, target, arch): 905 super().__init__(target, arch) 906 self.binary_to_target = { 907 "clang_x64/transport_security_state_generator": 908 "cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing", 909 } 910 self.binary = self.binary_to_target[self.target.args[0]] 911 912 def _replace_gen_with_location_tag(self, arg): 913 if arg.startswith("gen/"): 914 return "$(location %s)" % arg.replace("gen/", "") 915 return arg 916 917 def _replace_binary(self, arg): 918 if arg in self.binary_to_target: 919 return '$(location %s)' % self.binary 920 return arg 921 922 def _remove_python_args(self): 923 self.target.args = [arg for arg in self.target.args if "python3" not in arg] 924 925 def _sanitize_args(self): 926 self._update_all_args(self._sanitize_filepath_with_location_tag) 927 self._update_all_args(self._replace_gen_with_location_tag) 928 self._update_all_args(self._replace_binary) 929 self._remove_python_args() 930 super()._sanitize_args() 931 932 def get_tools(self): 933 tools = super().get_tools() 934 tools.add(self.binary) 935 return tools 936 937 def get_cmd(self): 938 # Remove the script and use the binary right away 939 return NEWLINE.join(self.target.args) 940 941class JniGeneratorSanitizer(BaseActionSanitizer): 942 def __init__(self, target, arch, is_test_target): 943 self.is_test_target = is_test_target 944 super().__init__(target, arch) 945 946 def _add_location_tag_to_filepath(self, arg): 947 if not arg.endswith('.class'): 948 # --input_file supports both .class specifiers or source files as arguments. 949 # Only source files need to be wrapped inside a $(location <label>) tag. 950 arg = self._add_location_tag(arg) 951 return arg 952 953 def _sanitize_args(self): 954 self._set_value_arg('--jar_file', '$(location :current_android_jar)', False) 955 if self._has_arg('--jar_file'): 956 self._append_arg('--javap', '$$(find $${OUT_DIR:-out}/.path -name javap)') 957 self._update_value_arg('--output_dir', self._sanitize_filepath) 958 self._update_value_arg('--includes', self._sanitize_filepath, False) 959 self._delete_value_arg('--prev_output_dir', False) 960 self._update_list_arg('--input_file', self._sanitize_filepath) 961 self._update_list_arg('--input_file', self._add_location_tag_to_filepath) 962 if not self.is_test_target: 963 # Only jarjar platform code 964 self._append_arg('--package_prefix', 'android.net.connectivity') 965 super()._sanitize_args() 966 967 def _sanitize_outputs(self): 968 # fix target.output directory to match #include statements. 969 self.target.outputs = {re.sub('^jni_headers/', '', out) for out in self.target.outputs} 970 super()._sanitize_outputs() 971 972 def get_tool_files(self): 973 tool_files = super().get_tool_files() 974 # android_jar.classes should be part of the tools as it list implicit classes 975 # for the script to generate JNI headers. 976 tool_files.add("base/android/jni_generator/android_jar.classes") 977 978 # Filter android.jar and add :current_android_jar 979 tool_files = {file if not file.endswith('android.jar') else ':current_android_jar' 980 for file in tool_files } 981 return tool_files 982 983class JniRegistrationGeneratorSanitizer(BaseActionSanitizer): 984 def __init__(self, target, arch, is_test_target): 985 self.is_test_target = is_test_target 986 super().__init__(target, arch) 987 988 def _sanitize_inputs(self): 989 self.target.inputs = [file for file in self.target.inputs if not file.startswith('//out/')] 990 991 def _sanitize_outputs(self): 992 self.target.outputs = {re.sub('^jni_headers/', '', out) for out in self.target.outputs} 993 994 def _sanitize_args(self): 995 self._update_value_arg('--depfile', self._sanitize_filepath) 996 self._update_value_arg('--srcjar-path', self._sanitize_filepath) 997 self._update_value_arg('--header-path', self._sanitize_filepath) 998 self._set_value_arg('--sources-files', '$(genDir)/java.sources') 999 # update_jni_registration_module removes them from the srcs of the module 1000 # It might be better to remove sources by '--sources-exclusions' 1001 self._delete_value_arg('--file-exclusions') 1002 if not self.is_test_target: 1003 # Only jarjar platform code 1004 self._append_arg('--package_prefix', 'android.net.connectivity') 1005 super()._sanitize_args() 1006 1007 def get_cmd(self): 1008 # jni_registration_generator.py doesn't work with python2 1009 cmd = "python3 " + super().get_cmd() 1010 # Path in the original sources file does not work in genrule. 1011 # So creating sources file in cmd based on the srcs of this target. 1012 # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files 1013 # whose path startswith(..) 1014 commands = ["current_dir=`basename \\\`pwd\\\``;", 1015 "for f in $(in);", 1016 "do", 1017 "echo \\\"../$$current_dir/$$f\\\" >> $(genDir)/java.sources;", 1018 "done;", 1019 cmd] 1020 1021 return NEWLINE.join(commands) 1022 1023class JavaJniRegistrationGeneratorSanitizer(JniRegistrationGeneratorSanitizer): 1024 def get_name(self): 1025 name = super().get_name() + "__java" 1026 if self.is_test_target: 1027 name += gn_utils.TESTING_SUFFIX 1028 return name 1029 1030 def _sanitize_outputs(self): 1031 self.target.outputs = [out for out in self.target.outputs if 1032 out.endswith(".srcjar")] 1033 super()._sanitize_outputs() 1034 1035class VersionSanitizer(BaseActionSanitizer): 1036 def _sanitize_args(self): 1037 self._set_value_arg('-o', '$(out)') 1038 # args for the version.py contain file path without leading --arg key. So apply sanitize 1039 # function for all the args. 1040 self._update_all_args(self._sanitize_filepath_with_location_tag) 1041 self._set_value_arg('-e', "'%s'" % self._get_value_arg('-e')) 1042 super()._sanitize_args() 1043 1044 def get_tool_files(self): 1045 tool_files = super().get_tool_files() 1046 # android_chrome_version.py is not specified in anywhere but version.py imports this file 1047 tool_files.add('build/util/android_chrome_version.py') 1048 return tool_files 1049 1050class JavaCppEnumSanitizer(BaseActionSanitizer): 1051 def _sanitize_args(self): 1052 self._update_all_args(self._sanitize_filepath_with_location_tag) 1053 self._set_value_arg('--srcjar', '$(out)') 1054 super()._sanitize_args() 1055 1056class MakeDafsaSanitizer(BaseActionSanitizer): 1057 def is_header_generated(self): 1058 # This script generates .cc files but they are #included by other sources 1059 # (e.g. registry_controlled_domain.cc) 1060 return True 1061 1062class JavaCppFeatureSanitizer(BaseActionSanitizer): 1063 def _sanitize_args(self): 1064 self._update_all_args(self._sanitize_filepath_with_location_tag) 1065 self._set_value_arg('--srcjar', '$(out)') 1066 super()._sanitize_args() 1067 1068class JavaCppStringSanitizer(BaseActionSanitizer): 1069 def _sanitize_args(self): 1070 self._update_all_args(self._sanitize_filepath_with_location_tag) 1071 self._set_value_arg('--srcjar', '$(out)') 1072 super()._sanitize_args() 1073 1074class WriteNativeLibrariesJavaSanitizer(BaseActionSanitizer): 1075 def _sanitize_args(self): 1076 self._set_value_arg('--output', '$(out)') 1077 super()._sanitize_args() 1078 1079 1080class ProtocJavaSanitizer(BaseActionSanitizer): 1081 def __init__(self, target, arch, gn): 1082 super().__init__(target, arch) 1083 self._protoc = get_protoc_module_name(gn) 1084 1085 def _sanitize_proto_path(self, arg): 1086 arg = self._sanitize_filepath(arg) 1087 return tree_path + '/' + arg 1088 1089 def _sanitize_args(self): 1090 super()._sanitize_args() 1091 self._delete_value_arg('--depfile') 1092 self._set_value_arg('--protoc', '$(location %s)' % self._protoc) 1093 self._update_value_arg('--proto-path', self._sanitize_proto_path) 1094 self._set_value_arg('--srcjar', '$(out)') 1095 self._update_arg_at(-1, self._sanitize_filepath_with_location_tag) 1096 1097 def get_tools(self): 1098 tools = super().get_tools() 1099 tools.add(self._protoc) 1100 return tools 1101 1102 1103def get_action_sanitizer(gn, target, type, arch, is_test_target): 1104 if target.script == "//build/write_buildflag_header.py": 1105 return WriteBuildFlagHeaderSanitizer(target, arch) 1106 elif target.script == "//base/write_build_date_header.py": 1107 return WriteBuildDateHeaderSanitizer(target, arch) 1108 elif target.script == '//base/android/jni_generator/jni_generator.py': 1109 return JniGeneratorSanitizer(target, arch, is_test_target) 1110 elif target.script == '//base/android/jni_generator/jni_registration_generator.py': 1111 if type == 'java_genrule': 1112 return JavaJniRegistrationGeneratorSanitizer(target, arch, is_test_target) 1113 else: 1114 return JniRegistrationGeneratorSanitizer(target, arch, is_test_target) 1115 elif target.script == "//build/util/version.py": 1116 return VersionSanitizer(target, arch) 1117 elif target.script == "//build/android/gyp/java_cpp_enum.py": 1118 return JavaCppEnumSanitizer(target, arch) 1119 elif target.script == "//net/tools/dafsa/make_dafsa.py": 1120 return MakeDafsaSanitizer(target, arch) 1121 elif target.script == '//build/android/gyp/java_cpp_features.py': 1122 return JavaCppFeatureSanitizer(target, arch) 1123 elif target.script == '//build/android/gyp/java_cpp_strings.py': 1124 return JavaCppStringSanitizer(target, arch) 1125 elif target.script == '//build/android/gyp/write_native_libraries_java.py': 1126 return WriteNativeLibrariesJavaSanitizer(target, arch) 1127 elif target.script == '//build/gn_run_binary.py': 1128 return GnRunBinarySanitizer(target, arch) 1129 elif target.script == '//build/protoc_java.py': 1130 return ProtocJavaSanitizer(target, arch, gn) 1131 else: 1132 raise Exception('Unsupported action %s' % target.script) 1133 1134def create_action_foreach_modules(blueprint, gn, target, is_test_target): 1135 """ The following assumes that rebase_path exists in the args. 1136 The args of an action_foreach contains hints about which output files are generated 1137 by which source files. 1138 This is copied directly from the args 1139 "gen/net/base/registry_controlled_domains/{{source_name_part}}-reversed-inc.cc" 1140 So each source file will generate an output whose name is the {source_name-reversed-inc.cc} 1141 """ 1142 new_args = [] 1143 for i, src in enumerate(sorted(target.sources)): 1144 # don't add script arg for the first source -- create_action_module 1145 # already does this. 1146 if i != 0: 1147 new_args.append('&&') 1148 new_args.append('python3 $(location %s)' % 1149 gn_utils.label_to_path(target.script)) 1150 for arg in target.args: 1151 if '{{source}}' in arg: 1152 new_args.append('$(location %s)' % (gn_utils.label_to_path(src))) 1153 elif '{{source_name_part}}' in arg: 1154 source_name_part = src.split("/")[-1] # Get the file name only 1155 source_name_part = source_name_part.split(".")[0] # Remove the extension (Ex: .cc) 1156 file_name = arg.replace('{{source_name_part}}', source_name_part).split("/")[-1] 1157 # file_name represent the output file name. But we need the whole path 1158 # This can be found from target.outputs. 1159 for out in target.outputs: 1160 if out.endswith(file_name): 1161 new_args.append('$(location %s)' % out) 1162 1163 for file in (target.sources | target.inputs): 1164 if file.endswith(file_name): 1165 new_args.append('$(location %s)' % gn_utils.label_to_path(file)) 1166 else: 1167 new_args.append(arg) 1168 1169 target.args = new_args 1170 return create_action_module(blueprint, gn, target, 'cc_genrule', is_test_target) 1171 1172def create_action_module_internal(gn, target, type, is_test_target, arch=None): 1173 sanitizer = get_action_sanitizer(gn, target, type, arch, is_test_target) 1174 sanitizer.sanitize() 1175 1176 module = Module(type, sanitizer.get_name(), target.name) 1177 module.cmd = sanitizer.get_cmd() 1178 module.out = sanitizer.get_outputs() 1179 if sanitizer.is_header_generated(): 1180 module.genrule_headers.add(module.name) 1181 module.srcs = sanitizer.get_srcs() 1182 module.tool_files = sanitizer.get_tool_files() 1183 module.tools = sanitizer.get_tools() 1184 1185 return module 1186 1187def get_cmd_condition(arch): 1188 ''' 1189 :param arch: archtecture name e.g. android_x86_64, android_arm64 1190 :return: condition that can be used in cc_genrule cmd to switch the behavior based on arch 1191 ''' 1192 if arch == "android_x86_64": 1193 return "( $$CC_ARCH == 'x86_64' && $$CC_OS == 'android' )" 1194 elif arch == "android_x86": 1195 return "( $$CC_ARCH == 'x86' && $$CC_OS == 'android' )" 1196 elif arch == "android_arm": 1197 return "( $$CC_ARCH == 'arm' && $$CC_OS == 'android' )" 1198 elif arch == "android_arm64": 1199 return "( $$CC_ARCH == 'arm64' && $$CC_OS == 'android' )" 1200 elif arch == "host": 1201 return "$$CC_OS != 'android'" 1202 else: 1203 raise Exception(f'Unknown architecture type {arch}') 1204 1205def merge_cmd(modules, genrule_type): 1206 ''' 1207 :param modules: dictionary whose key is arch name and value is module 1208 :param genrule_type: cc_genrule or java_genrule 1209 :return: merged command or common command if all the archs have the same command. 1210 ''' 1211 commands = list({module.cmd for module in modules.values()}) 1212 if len(commands) == 1: 1213 # If all the archs have the same command, return the command 1214 return commands[0] 1215 1216 if genrule_type != 'cc_genrule': 1217 raise Exception(f'{genrule_type} can not have different cmd between archs') 1218 1219 merged_cmd = [] 1220 for arch, module in modules.items(): 1221 merged_cmd.append(f'if [[ {get_cmd_condition(arch)} ]];') 1222 merged_cmd.append('then') 1223 merged_cmd.append(module.cmd + ';') 1224 merged_cmd.append('fi;') 1225 return NEWLINE.join(merged_cmd) 1226 1227def merge_modules(modules, genrule_type): 1228 ''' 1229 :param modules: dictionary whose key is arch name and value is module 1230 :param genrule_type: cc_genrule or java_genrule 1231 :return: merged module of input modules 1232 ''' 1233 merged_module = list(modules.values())[0] 1234 1235 # Following attributes must be the same between archs 1236 for key in ('out', 'genrule_headers', 'srcs', 'tool_files'): 1237 if any([getattr(merged_module, key) != getattr(module, key) for module in modules.values()]): 1238 raise Exception(f'{merged_module.name} has different values for {key} between archs') 1239 1240 merged_module.cmd = merge_cmd(modules, genrule_type) 1241 return merged_module 1242 1243def create_action_module(blueprint, gn, target, genrule_type, is_test_target): 1244 ''' 1245 Create module for action target and add to the blueprint. If target has arch specific attributes 1246 this function merge them and create a single module. 1247 :param blueprint: 1248 :param target: target which is converted to the module. 1249 :param genrule_type: cc_genrule or java_genrule 1250 :return: created module 1251 ''' 1252 # TODO: Handle this target correctly, this target generates java_genrule but this target has 1253 # different value for cpu-family arg between archs 1254 if target.name in ['//build/android:native_libraries_gen', 1255 '//build/android:native_libraries_gen__testing']: 1256 module = create_action_module_internal(gn, target, genrule_type, 1257 is_test_target, 1258 target.arch['android_arm']) 1259 blueprint.add_module(module) 1260 return module 1261 1262 modules = {arch_name: create_action_module_internal(gn, target, genrule_type, 1263 is_test_target, arch) 1264 for arch_name, arch in target.get_archs().items()} 1265 module = merge_modules(modules, genrule_type) 1266 blueprint.add_module(module) 1267 return module 1268 1269 1270def _get_cflags(cflags, defines): 1271 cflags = {flag for flag in cflags if flag in cflag_allowlist} 1272 # Consider proper allowlist or denylist if needed 1273 cflags |= set("-D%s" % define.replace("\"", "\\\"") for define in defines) 1274 return cflags 1275 1276def _set_linker_script(module, libs): 1277 for lib in libs: 1278 if lib.endswith(".lds"): 1279 module.ldflags.add(get_linker_script_ldflag(gn_utils.label_to_path(lib))) 1280 1281def set_module_flags(module, module_type, cflags, defines, ldflags, libs): 1282 module.cflags.update(_get_cflags(cflags, defines)) 1283 module.ldflags.update({flag for flag in ldflags 1284 if flag in ldflag_allowlist or flag.startswith("-Wl,-wrap,")}) 1285 _set_linker_script(module, libs) 1286 # TODO: implement proper cflag parsing. 1287 for flag in cflags: 1288 if '-std=' in flag: 1289 module.cpp_std = flag[len('-std='):] 1290 if '-fexceptions' in flag: 1291 module.cppflags.add('-fexceptions') 1292 1293def set_module_include_dirs(module, cflags, include_dirs): 1294 for flag in cflags: 1295 if '-isystem' in flag: 1296 module.local_include_dirs.add(flag[len('-isystem../../'):]) 1297 1298 # Adding local_include_dirs is necessary due to source_sets / filegroups 1299 # which do not properly propagate include directories. 1300 # Filter any directory inside //out as a) this directory does not exist for 1301 # aosp / soong builds and b) the include directory should already be 1302 # configured via library dependency. 1303 module.local_include_dirs.update([gn_utils.label_to_path(d) 1304 for d in include_dirs if not d.startswith('//out')]) 1305 # Remove prohibited include directories 1306 module.local_include_dirs = [d for d in module.local_include_dirs 1307 if d not in local_include_dirs_denylist] 1308 1309 1310def create_modules_from_target(blueprint, gn, gn_target_name, is_test_target): 1311 """Generate module(s) for a given GN target. 1312 1313 Given a GN target name, generate one or more corresponding modules into a 1314 blueprint. The only case when this generates >1 module is proto libraries. 1315 1316 Args: 1317 blueprint: Blueprint instance which is being generated. 1318 gn: gn_utils.GnParser object. 1319 gn_target_name: GN target for module generation. 1320 """ 1321 bp_module_name = label_to_module_name(gn_target_name) 1322 if bp_module_name in blueprint.modules: 1323 return blueprint.modules[bp_module_name] 1324 target = gn.get_target(gn_target_name) 1325 log.info('create modules for %s (%s)', target.name, target.type) 1326 1327 if target.type == 'executable': 1328 if target.testonly: 1329 module_type = 'cc_test' 1330 else: 1331 # Can be used for both host and device targets. 1332 module_type = 'cc_binary' 1333 module = Module(module_type, bp_module_name, gn_target_name) 1334 elif target.type in ['static_library', 'source_set']: 1335 module = Module('cc_library_static', bp_module_name, gn_target_name) 1336 elif target.type == 'shared_library': 1337 module = Module('cc_library_shared', bp_module_name, gn_target_name) 1338 elif target.type == 'group': 1339 # "group" targets are resolved recursively by gn_utils.get_target(). 1340 # There's nothing we need to do at this level for them. 1341 return None 1342 elif target.type == 'proto_library': 1343 module = create_proto_modules(blueprint, gn, target) 1344 if module is None: 1345 return None 1346 elif target.type == 'action': 1347 module = create_action_module(blueprint, gn, target, 'cc_genrule', is_test_target) 1348 elif target.type == 'action_foreach': 1349 module = create_action_foreach_modules(blueprint, gn, target, is_test_target) 1350 elif target.type == 'copy': 1351 # TODO: careful now! copy targets are not supported yet, but this will stop 1352 # traversing the dependency tree. For //base:base, this is not a big 1353 # problem as libicu contains the only copy target which happens to be a 1354 # leaf node. 1355 return None 1356 elif target.type == 'java_group': 1357 # Java targets are handled outside of create_modules_from_target. 1358 return None 1359 else: 1360 raise Exception('Unknown target %s (%s)' % (target.name, target.type)) 1361 1362 blueprint.add_module(module) 1363 module.srcs.update(gn_utils.label_to_path(src) 1364 for src in target.sources if is_supported_source_file(src)) 1365 1366 # Add arch-specific properties 1367 for arch_name, arch in target.get_archs().items(): 1368 module.target[arch_name].srcs.update(gn_utils.label_to_path(src) 1369 for src in arch.sources if is_supported_source_file(src)) 1370 1371 module.rtti = target.rtti 1372 1373 if target.type in gn_utils.LINKER_UNIT_TYPES: 1374 set_module_flags(module, module.type, target.cflags, target.defines, target.ldflags, target.libs) 1375 set_module_include_dirs(module, target.cflags, target.include_dirs) 1376 # TODO: set_module_xxx is confusing, apply similar function to module and target in better way. 1377 for arch_name, arch in target.get_archs().items(): 1378 # TODO(aymanm): Make libs arch-specific. 1379 set_module_flags(module.target[arch_name], module.type, 1380 arch.cflags, arch.defines, arch.ldflags, []) 1381 # -Xclang -target-feature -Xclang +mte are used to enable MTE (Memory Tagging Extensions). 1382 # Flags which does not start with '-' could not be in the cflags so enabling MTE by 1383 # -march and -mcpu Feature Modifiers. MTE is only available on arm64. This is needed for 1384 # building //base/allocator/partition_allocator:partition_alloc for arm64. 1385 if '+mte' in arch.cflags and arch_name == 'android_arm64': 1386 module.target[arch_name].cflags.add('-march=armv8-a+memtag') 1387 set_module_include_dirs(module.target[arch_name], arch.cflags, arch.include_dirs) 1388 1389 module.host_supported = target.host_supported() 1390 module.device_supported = target.device_supported() 1391 module.gn_type = target.type 1392 1393 if module.is_genrule(): 1394 module.apex_available.add(tethering_apex) 1395 1396 if module.is_compiled(): 1397 # Don't try to inject library/source dependencies into genrules or 1398 # filegroups because they are not compiled in the traditional sense. 1399 module.defaults = [defaults_module] 1400 for lib in target.libs: 1401 # Generally library names should be mangled as 'libXXX', unless they 1402 # are HAL libraries (e.g., android.hardware.health@2.0) or AIDL c++ / NDK 1403 # libraries (e.g. "android.hardware.power.stats-V1-cpp") 1404 android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \ 1405 else 'lib' + lib 1406 if lib in shared_library_allowlist: 1407 module.add_android_shared_lib(android_lib) 1408 1409 # If the module is a static library, export all the generated headers. 1410 if module.type == 'cc_library_static': 1411 module.export_generated_headers = module.generated_headers 1412 1413 if module.name == 'cronet_aml_components_cronet_android_cronet': 1414 if target.output_name is None: 1415 raise Exception('Failed to get output_name for libcronet name') 1416 # .so file name needs to match with CronetLibraryLoader.java (e.g. libcronet.109.0.5386.0.so) 1417 # So setting the output name based on the output_name from the desc.json 1418 module.stem = 'lib' + target.output_name 1419 1420 if module.is_test(): 1421 # Tests output should be a shared library in the format of 'lib[module_name]' 1422 module.stem = 'lib' + target.get_target_name()[ 1423 :target.get_target_name().find(gn_utils.TESTING_SUFFIX)] 1424 1425 # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)). 1426 all_deps = [(dep_name, 'common') for dep_name in target.transitive_proto_deps] 1427 for arch_name, arch in target.arch.items(): 1428 all_deps += [(dep_name, arch_name) for dep_name in arch.deps] 1429 1430 # Sort deps before iteration to make result deterministic. 1431 for (dep_name, arch_name) in sorted(all_deps): 1432 module_target = module.target[arch_name] if arch_name != 'common' else module 1433 # |builtin_deps| override GN deps with Android-specific ones. See the 1434 # config in the top of this file. 1435 if dep_name in builtin_deps: 1436 builtin_deps[dep_name](module, arch_name) 1437 continue 1438 1439 dep_module = create_modules_from_target(blueprint, gn, dep_name, is_test_target) 1440 1441 if dep_module is None: 1442 continue 1443 # TODO: Proper dependency check for genrule. 1444 # Currently, only propagating genrule dependencies. 1445 # Also, currently, all the dependencies are propagated upwards. 1446 # in gn, public_deps should be propagated but deps should not. 1447 # Not sure this information is available in the desc.json. 1448 # Following rule works for adding android_runtime_jni_headers to base:base. 1449 # If this doesn't work for other target, hardcoding for specific target 1450 # might be better. 1451 if module.is_genrule() and dep_module.is_genrule(): 1452 module_target.genrule_headers.add(dep_module.name) 1453 module_target.genrule_headers.update(dep_module.genrule_headers) 1454 1455 # For filegroups, and genrule, recurse but don't apply the 1456 # deps. 1457 if not module.is_compiled() or module.is_genrule(): 1458 continue 1459 1460 # Drop compiled modules that doesn't provide any benefit. This is mostly 1461 # applicable to source_sets when converted to cc_static_library, sometimes 1462 # the source set only has header files which are dropped so the module becomes empty. 1463 # is_compiled is there to prevent dropping of genrules. 1464 if dep_module.is_compiled() and not dep_module.has_input_files(): 1465 continue 1466 1467 if dep_module.type == 'cc_library_shared': 1468 module_target.shared_libs.add(dep_module.name) 1469 elif dep_module.type == 'cc_library_static': 1470 if module.type in ['cc_library_shared', 'cc_binary'] and dep_module.gn_type == 'source_set': 1471 module_target.whole_static_libs.add(dep_module.name) 1472 else: 1473 module_target.static_libs.add(dep_module.name) 1474 elif dep_module.type == 'cc_genrule': 1475 module_target.generated_headers.update(dep_module.genrule_headers) 1476 module_target.srcs.update(dep_module.genrule_srcs) 1477 module_target.shared_libs.update(dep_module.genrule_shared_libs) 1478 module_target.header_libs.update(dep_module.genrule_header_libs) 1479 else: 1480 raise Exception('Unsupported arch-specific dependency %s of target %s with type %s' % 1481 (dep_module.name, target.name, dep_module.type)) 1482 return module 1483 1484def create_java_jni_preprocessor(blueprint): 1485 bp_module_name = module_prefix + 'java_jni_annotation_preprocessor' 1486 module = Module('java_plugin', bp_module_name, '//base/android/jni_generator:jni_processor') 1487 module.srcs.update( 1488 [ 1489 "base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java", 1490 # Avoids a circular dependency with base:base_java. This is okay because 1491 # no target should ever expect to package an annotation processor. 1492 "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java", 1493 "build/android/java/src/org/chromium/build/annotations/MainDex.java", 1494 "base/android/java/src/org/chromium/base/JniStaticTestMocker.java", 1495 "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java", 1496 "base/android/java/src/org/chromium/base/annotations/NativeMethods.java", 1497 "base/android/java/src/org/chromium/base/JniException.java", 1498 ":cronet_aml_build_android_build_config_gen", 1499 ]) 1500 module.static_libs.update({ 1501 "javapoet", 1502 "guava", 1503 "auto_service_annotations", 1504 }) 1505 module.processor_class = "org.chromium.jni_generator.JniProcessor" 1506 blueprint.add_module(module) 1507 return module 1508 1509def get_java_sources(gn, predicate): 1510 java_sources = set() 1511 for target_name, sources in gn.java_sources.items(): 1512 if predicate(target_name): 1513 java_sources.update(sources) 1514 return java_sources 1515 1516def get_java_actions(gn, predicate): 1517 java_actions = set() 1518 for target_name, actions in gn.java_actions.items(): 1519 if predicate(target_name): 1520 java_actions.update(actions) 1521 return java_actions 1522 1523def get_non_api_java_sources(gn): 1524 return get_java_sources(gn, lambda name: name != java_api_target_name) 1525 1526def get_non_api_java_actions(gn): 1527 return get_java_actions(gn, lambda name: name != java_api_target_name) 1528 1529def get_api_java_sources(gn): 1530 return get_java_sources(gn, lambda name: name == java_api_target_name) 1531 1532def get_api_java_actions(gn): 1533 return get_java_actions(gn, lambda name: name == java_api_target_name) 1534 1535def create_java_module(blueprint, gn, is_test_target): 1536 bp_module_name = module_prefix + 'java' 1537 if is_test_target: 1538 bp_module_name += gn_utils.TESTING_SUFFIX 1539 module = Module('java_library', bp_module_name, '//gn:java') 1540 module.srcs.update([gn_utils.label_to_path(source) for source in get_non_api_java_sources(gn)]) 1541 module.libs = { 1542 "androidx.annotation_annotation", 1543 "androidx.annotation_annotation-experimental-nodeps", 1544 "error_prone_annotations", 1545 "framework-connectivity-t.stubs.module_lib", 1546 "framework-connectivity.stubs.module_lib", 1547 "framework-mediaprovider.stubs.module_lib", 1548 "framework-tethering.stubs.module_lib", 1549 "framework-wifi.stubs.module_lib", 1550 "jsr305", 1551 } 1552 module.static_libs = { 1553 "libprotobuf-java-lite", 1554 "modules-utils-build_system", 1555 } 1556 module.aidl["include_dirs"] = {"frameworks/base/core/java/"} 1557 module.aidl["local_include_dirs"] = gn.aidl_local_include_dirs 1558 module.sdk_version = "module_current" 1559 module.min_sdk_version = 30 1560 module.apex_available.add(tethering_apex) 1561 # TODO: support for this flag is removed upstream in crrev/c/4062652. 1562 # Consider reverting this change upstream, or worst-case downstream. As an 1563 # alternative hack, we could rename the generated file to not conflict. This 1564 # would be less likely to conflict with upstream changes if the revert is not 1565 # accepted. 1566 module.javacflags.add("-Aorg.chromium.chrome.skipGenJni") 1567 if not is_test_target: 1568 module.javacflags.add("-Apackage_prefix=android.net.connectivity") 1569 for dep in get_non_api_java_actions(gn): 1570 target = gn.get_target(dep) 1571 if target.script == '//build/android/gyp/gcc_preprocess.py': 1572 module.srcs.add(':' + create_gcc_preprocess_modules(blueprint, target).name) 1573 else: 1574 module.srcs.add(':' + create_action_module(blueprint, gn, target, 1575 'java_genrule', 1576 is_test_target).name) 1577 preprocessor_module = create_java_jni_preprocessor(blueprint) 1578 module.plugins.add(preprocessor_module.name) 1579 module.visibility.append(connectivity_visibility) 1580 blueprint.add_module(module) 1581 return module 1582 1583def create_java_api_module(blueprint, gn): 1584 source_module = Module('filegroup', module_prefix + 'api_sources', java_api_target_name) 1585 # TODO add the API helpers separately after the main API is checked in and thoroughly reviewed 1586 source_module.srcs.update([gn_utils.label_to_path(source) 1587 for source in get_api_java_sources(gn) 1588 if "apihelpers" not in source]) 1589 source_module.comment += "\n// TODO(danstahr): add the API helpers separately after the main" \ 1590 " API is checked in and thoroughly reviewed" 1591 source_module.srcs.update([ 1592 ':' + create_action_module(blueprint, gn, gn.get_target(dep), 'java_genrule', False).name 1593 for dep in get_api_java_actions(gn)]) 1594 blueprint.add_module(source_module) 1595 source_module.visibility.append(connectivity_visibility) 1596 return source_module 1597 1598def update_jni_registration_module(module, gn): 1599 # TODO: java_sources might not contain all the required java files 1600 module.srcs.update([gn_utils.label_to_path(source) 1601 for source in get_non_api_java_sources(gn) 1602 if source.endswith('.java')]) 1603 1604 1605def turn_off_allocator_shim_for_musl(module): 1606 allocation_shim = "base/allocator/partition_allocator/shim/allocator_shim.cc" 1607 allocator_shim_files = { 1608 allocation_shim, 1609 "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc", 1610 } 1611 module.srcs -= allocator_shim_files 1612 for arch in module.target.values(): 1613 arch.srcs -= allocator_shim_files 1614 module.target['android'].srcs.add(allocation_shim) 1615 if gn_utils.TESTING_SUFFIX in module.name: 1616 # allocator_shim_default_dispatch_to_glibc is only added to the __testing version of base 1617 # since base_base__testing is compiled for host. When compiling for host. Soong compiles 1618 # using glibc or musl(experimental). We currently only support compiling for glibc. 1619 module.target['glibc'].srcs.update(allocator_shim_files) 1620 else: 1621 # allocator_shim_default_dispatch_to_glibc does not exist in the prod version of base 1622 # `base_base` since this only compiles for android and bionic is used. Bionic is the equivalent 1623 # of glibc but for android. 1624 module.target['glibc'].srcs.add(allocation_shim) 1625 1626def create_blueprint_for_targets(gn, targets, test_targets): 1627 """Generate a blueprint for a list of GN targets.""" 1628 blueprint = Blueprint() 1629 1630 # Default settings used by all modules. 1631 defaults = Module('cc_defaults', defaults_module, '//gn:default_deps') 1632 defaults.cflags = [ 1633 '-DGOOGLE_PROTOBUF_NO_RTTI', 1634 '-DBORINGSSL_SHARED_LIBRARY', 1635 '-Wno-error=return-type', 1636 '-Wno-non-virtual-dtor', 1637 '-Wno-macro-redefined', 1638 '-Wno-missing-field-initializers', 1639 '-Wno-sign-compare', 1640 '-Wno-sign-promo', 1641 '-Wno-unused-parameter', 1642 '-Wno-null-pointer-subtraction', # Needed to libevent 1643 '-Wno-ambiguous-reversed-operator', # needed for icui18n 1644 '-Wno-unreachable-code-loop-increment', # needed for icui18n 1645 '-fPIC', 1646 '-Wno-c++11-narrowing', 1647 ] 1648 defaults.c_std = 'gnu11' 1649 # Chromium builds do not add a dependency for headers found inside the 1650 # sysroot, so they are added globally via defaults. 1651 defaults.target['android'].header_libs = [ 1652 'jni_headers', 1653 ] 1654 defaults.target['android'].shared_libs = [ 1655 'libmediandk' 1656 ] 1657 defaults.target['host'].cflags = [ 1658 # -DANDROID is added by default but target.defines contain -DANDROID if 1659 # it's required. So adding -UANDROID to cancel default -DANDROID if it's 1660 # not specified. 1661 # Note: -DANDROID is not consistently applied across the chromium code 1662 # base, so it is removed unconditionally for host targets. 1663 '-UANDROID', 1664 ] 1665 defaults.stl = 'none' 1666 defaults.cpp_std = 'c++17' 1667 defaults.min_sdk_version = 29 1668 defaults.apex_available.add(tethering_apex) 1669 blueprint.add_module(defaults) 1670 1671 for target in targets: 1672 module = create_modules_from_target(blueprint, gn, target, is_test_target=False) 1673 if module: 1674 module.visibility.append(connectivity_visibility) 1675 1676 for test_target in test_targets: 1677 module = create_modules_from_target(blueprint, gn, test_target + gn_utils.TESTING_SUFFIX, is_test_target=True) 1678 if module: 1679 module.visibility.append(connectivity_visibility) 1680 1681 create_java_api_module(blueprint, gn) 1682 java_module = create_java_module(blueprint, gn, is_test_target=False) 1683 java_module.libs.add(CRONET_API_MODULE_NAME) 1684 java_module_testing = create_java_module(blueprint, gn, is_test_target=True) 1685 java_module_testing.libs.add(CRONET_API_MODULE_NAME) 1686 for module in blueprint.modules.values(): 1687 if 'cronet_jni_registration' in module.name: 1688 update_jni_registration_module(module, gn) 1689 if module.name in ['cronet_aml_base_base', 'cronet_aml_base_base' + gn_utils.TESTING_SUFFIX]: 1690 turn_off_allocator_shim_for_musl(module) 1691 1692 # Merge in additional hardcoded arguments. 1693 for module in blueprint.modules.values(): 1694 for key, add_val in additional_args.get(module.name, []): 1695 curr = getattr(module, key) 1696 if add_val and isinstance(add_val, set) and isinstance(curr, set): 1697 curr.update(add_val) 1698 elif isinstance(add_val, str) and (not curr or isinstance(curr, str)): 1699 setattr(module, key, add_val) 1700 elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)): 1701 setattr(module, key, add_val) 1702 elif isinstance(add_val, dict) and isinstance(curr, dict): 1703 curr.update(add_val) 1704 elif isinstance(add_val, dict) and isinstance(curr, Module.Target): 1705 curr.__dict__.update(add_val) 1706 else: 1707 raise Exception('Unimplemented type %r of additional_args: %r' % (type(add_val), key)) 1708 1709 return blueprint 1710 1711def create_package_module(blueprint): 1712 package = Module("package", "", "PACKAGE") 1713 package.comment = "The actual license can be found in Android.extras.bp" 1714 package.default_applicable_licenses.add(CRONET_LICENSE_NAME) 1715 package.default_visibility.append(package_default_visibility) 1716 blueprint.add_module(package) 1717 1718def main(): 1719 parser = argparse.ArgumentParser( 1720 description='Generate Android.bp from a GN description.') 1721 parser.add_argument( 1722 '--desc', 1723 help='GN description (e.g., gn desc out --format=json --all-toolchains "//*".' + 1724 'You can specify multiple --desc options for different target_cpu', 1725 required=True, 1726 action='append' 1727 ) 1728 parser.add_argument( 1729 '--extras', 1730 help='Extra targets to include at the end of the Blueprint file', 1731 default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'), 1732 ) 1733 parser.add_argument( 1734 '--output', 1735 help='Blueprint file to create', 1736 default=os.path.join(gn_utils.repo_root(), 'Android.bp'), 1737 ) 1738 parser.add_argument( 1739 '-v', 1740 '--verbose', 1741 help='Print debug logs.', 1742 action='store_true', 1743 ) 1744 parser.add_argument( 1745 'targets', 1746 nargs=argparse.REMAINDER, 1747 help='Targets to include in the blueprint (e.g., "//:perfetto_tests")' 1748 ) 1749 args = parser.parse_args() 1750 1751 if args.verbose: 1752 log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=log.DEBUG) 1753 1754 targets = args.targets or DEFAULT_TARGETS 1755 gn = gn_utils.GnParser(builtin_deps) 1756 for desc_file in args.desc: 1757 with open(desc_file) as f: 1758 desc = json.load(f) 1759 for target in targets: 1760 gn.parse_gn_desc(desc, target) 1761 for test_target in DEFAULT_TESTS: 1762 gn.parse_gn_desc(desc, test_target, is_test_target=True) 1763 blueprint = create_blueprint_for_targets(gn, targets, DEFAULT_TESTS) 1764 project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 1765 tool_name = os.path.relpath(os.path.abspath(__file__), project_root) 1766 1767 create_package_module(blueprint) 1768 output = [ 1769 """// Copyright (C) 2022 The Android Open Source Project 1770// 1771// Licensed under the Apache License, Version 2.0 (the "License"); 1772// you may not use this file except in compliance with the License. 1773// You may obtain a copy of the License at 1774// 1775// http://www.apache.org/licenses/LICENSE-2.0 1776// 1777// Unless required by applicable law or agreed to in writing, software 1778// distributed under the License is distributed on an "AS IS" BASIS, 1779// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1780// See the License for the specific language governing permissions and 1781// limitations under the License. 1782// 1783// This file is automatically generated by %s. Do not edit. 1784 1785build = ["Android.extras.bp"] 1786""" % (tool_name) 1787 ] 1788 blueprint.to_string(output) 1789 if os.path.exists(args.extras): 1790 with open(args.extras, 'r') as r: 1791 for line in r: 1792 output.append(line.rstrip("\n\r")) 1793 1794 out_files = [] 1795 1796 # Generate the Android.bp file. 1797 out_files.append(args.output + '.swp') 1798 with open(out_files[-1], 'w') as f: 1799 f.write('\n'.join(output)) 1800 # Text files should have a trailing EOL. 1801 f.write('\n') 1802 1803 return 0 1804 1805 1806if __name__ == '__main__': 1807 sys.exit(main()) 1808