• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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