xref: /aosp_15_r20/external/cronet/android/tools/gn2bp/gen_android_bp.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 typing import List, Dict, Set
37from pathlib import Path
38
39import gn_utils
40PARENT_ROOT = os.path.abspath(
41    os.path.join(os.path.dirname(__file__), os.pardir))
42
43sys.path.insert(0, os.path.join(PARENT_ROOT, "license"))
44import license_utils
45
46ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
47
48CRONET_LICENSE_NAME = "external_cronet_license"
49
50# Default targets to translate to the blueprint file.
51DEFAULT_TARGETS = [
52    "//components/cronet/android:cronet_api_java",
53    '//components/cronet/android:cronet',
54    '//components/cronet/android:cronet_impl_native_java',
55    '//components/cronet/android:cronet_jni_registration_java',
56]
57
58DEFAULT_TESTS = [
59  '//components/cronet/android:cronet_unittests_android__library',
60  '//net:net_unittests__library',
61  '//components/cronet/android:cronet_tests',
62  '//components/cronet/android:cronet',
63  '//components/cronet/android:cronet_javatests',
64  '//components/cronet/android:cronet_jni_registration_java',
65  '//components/cronet/android:cronet_tests_jni_registration_java',
66  '//testing/android/native_test:native_test_java',
67  '//net/android:net_test_support_provider_java',
68  '//net/android:net_tests_java',
69  '//third_party/netty-tcnative:netty-tcnative-so',
70  '//third_party/netty4:netty_all_java',
71  "//build/rust/tests/test_rust_static_library:test_rust_static_library", # Added to make sure that rust still compiles
72]
73
74EXTRAS_ANDROID_BP_FILE = "Android.extras.bp"
75
76CRONET_API_MODULE_NAME = "cronet_aml_api_java"
77
78# All module names are prefixed with this string to avoid collisions.
79module_prefix = 'cronet_aml_'
80
81REMOVE_GEN_JNI_JARJAR_RULES_FILE = ":remove_gen_jni_jarjar_rules"
82# Shared libraries which are directly translated to Android system equivalents.
83shared_library_allowlist = [
84    'android',
85    'log',
86]
87
88# A dictionary that adds extra content to a specific Android.bp according to the
89# provided path.
90# The path defined must be relative to the root-repo.
91BLUEPRINTS_EXTRAS = {
92    "": ["build = [\"Android.extras.bp\"]"]
93}
94
95# A dictionary that specifies the relocation of modules from one blueprint to
96# another.
97# The default format is (relative_path_A -> relative_path_B), this means
98# that all targets which should live in relative_path_A/Android.bp will live
99# inside relative_path_B/Android.bp.
100BLUEPRINTS_MAPPING = {
101    # An Android.bp already exists inside boringssl, creating another one will
102    # lead to conflicts, add all of the boringssl generated targets to the
103    # top-level Android.bp as they are only used for tests.
104    "third_party/boringssl": "",
105    # Moving is undergoing, see crbug/40273848
106    "buildtools/third_party/libc++": "third_party/libc++",
107    # Moving is undergoing, see crbug/40273848
108    "buildtools/third_party/libc++abi": "third_party/libc++abi",
109}
110
111# Usually, README.chromium lives next to the BUILD.gn. However, some cases are
112# different, this dictionary allows setting a specific README.chromium path
113# for a specific BUILD.gn
114README_MAPPING = {
115    # Moving is undergoing, see crbug/40273848
116    "buildtools/third_party/libc++": "third_party/libc++",
117    # Moving is undergoing, see crbug/40273848
118    "buildtools/third_party/libc++abi": "third_party/libc++abi",
119}
120
121# Include directories that will be removed from all targets.
122include_dirs_denylist = [
123    'external/cronet/third_party/zlib/',
124]
125
126# Name of the module which settings such as compiler flags for all other
127# modules.
128cc_defaults_module = module_prefix + 'cc_defaults'
129
130# Name of the java default module for non-test java modules defined in Android.extras.bp
131java_framework_defaults_module = 'cronet_aml_java_framework_defaults'
132
133# Location of the project in the Android source tree.
134tree_path = 'external/cronet'
135
136# Path for the protobuf sources in the standalone build.
137buildtools_protobuf_src = '//buildtools/protobuf/src'
138
139# Location of the protobuf src dir in the Android source tree.
140android_protobuf_src = 'external/protobuf/src'
141
142# put all args on a new line for better diffs.
143NEWLINE = ' " +\n         "'
144
145# Compiler flags which are passed through to the blueprint.
146cflag_allowlist = [
147  # needed for zlib:zlib
148  "-mpclmul",
149  # needed for zlib:zlib
150  "-mssse3",
151  # needed for zlib:zlib
152  "-msse3",
153  # needed for zlib:zlib
154  "-msse4.2",
155  # flags to reduce binary size
156  "-O1",
157  "-O2",
158  "-O3",
159  "-Oz",
160  "-g1",
161  "-g2",
162  "-fdata-sections",
163  "-ffunction-sections",
164  "-fvisibility=hidden",
165  "-fvisibility-inlines-hidden",
166  "-fstack-protector",
167  "-mno-outline",
168  "-mno-outline-atomics",
169  "-fno-asynchronous-unwind-tables",
170  "-fno-unwind-tables",
171]
172
173# Linker flags which are passed through to the blueprint.
174ldflag_allowlist = [
175  # flags to reduce binary size
176  "-Wl,--as-needed",
177  "-Wl,--gc-sections",
178  "-Wl,--icf=all",
179]
180
181def get_linker_script_ldflag(script_path):
182  return f'-Wl,--script,{tree_path}/{script_path}'
183
184# Additional arguments to apply to Android.bp rules.
185additional_args = {
186    'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers': [
187      ('export_include_dirs', {
188        "net/third_party/quiche/src",
189      })
190    ],
191    'cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen__testing_headers': [
192        ('export_include_dirs', {
193        "net/third_party/quiche/src",
194        })
195     ],
196    'cronet_aml_third_party_quic_trace_quic_trace_proto_gen__testing_headers': [
197        ('export_include_dirs', {
198          "third_party/quic_trace/src",
199        })
200    ],
201    # TODO: fix upstream. Both //base:base and
202    # //base/allocator/partition_allocator:partition_alloc do not create a
203    # dependency on gtest despite using gtest_prod.h.
204    'cronet_aml_base_base': [
205        ('header_libs', {
206            'libgtest_prod_headers',
207        }),
208        ('export_header_lib_headers', {
209            'libgtest_prod_headers',
210        }),
211    ],
212    'cronet_aml_base_allocator_partition_allocator_partition_alloc': [
213        ('header_libs', {
214            'libgtest_prod_headers',
215        }),
216    ],
217    # TODO(b/309920629): Remove once upstreamed.
218    'cronet_aml_components_cronet_android_cronet_api_java': [
219        ('srcs', {
220            'components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java',
221            'components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java',
222        }),
223    ],
224    'cronet_aml_components_cronet_android_cronet_api_java__testing': [
225        ('srcs', {
226            'components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java',
227            'components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java',
228        }),
229    ],
230    'cronet_aml_components_cronet_android_cronet_javatests__testing': [
231        # Needed to @SkipPresubmit annotations
232        ('static_libs', {
233            'net-tests-utils',
234        }),
235        # This is necessary because net-tests-utils compiles against private SDK.
236        ('sdk_version', ""),
237    ],
238    'cronet_aml_components_cronet_android_cronet__testing': [
239        ('target',  ('android_riscv64', {'stem': "libmainlinecronet_riscv64"})),
240        ('comment', """TODO: remove stem for riscv64
241// This is essential as there can't be two different modules
242// with the same output. We usually got away with that because
243// the non-testing Cronet is part of the Tethering APEX and the
244// testing Cronet is not part of the Tethering APEX which made them
245// look like two different outputs from the build system perspective.
246// However, Cronet does not ship to Tethering APEX for RISCV64 which
247// raises the conflict. Once we start shipping Cronet for RISCV64,
248// this can be removed."""),
249      ],
250    'cronet_aml_third_party_netty_tcnative_netty_tcnative_so__testing': [
251        ('cflags', {
252            "-Wno-error=pointer-bool-conversion"
253        })
254    ],
255    'cronet_aml_third_party_apache_portable_runtime_apr__testing': [
256        ('cflags', {
257            "-Wno-incompatible-pointer-types-discards-qualifiers",
258        })
259    ],
260    # TODO(b/324872305): Remove when gn desc expands public_configs and update code to propagate the
261    # include_dir from the public_configs
262    # We had to add the export_include_dirs for each target because soong generates each header
263    # file in a specific directory named after the target.
264    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_chromecast_buildflags': [
265      ('export_include_dirs', {
266        "base/allocator/partition_allocator/src/",
267      })
268    ],
269    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_chromecast_buildflags__testing': [
270      ('export_include_dirs', {
271        "base/allocator/partition_allocator/src/",
272      })
273    ],
274    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_chromeos_buildflags': [
275      ('export_include_dirs', {
276        "base/allocator/partition_allocator/src/",
277      })
278    ],
279    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_chromeos_buildflags__testing': [
280      ('export_include_dirs', {
281        "base/allocator/partition_allocator/src/",
282      })
283    ],
284    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_debugging_buildflags': [
285      ('export_include_dirs', {
286        "base/allocator/partition_allocator/src/",
287      })
288    ],
289    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_debugging_buildflags__testing': [
290      ('export_include_dirs', {
291        "base/allocator/partition_allocator/src/",
292      })
293    ],
294    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_partition_alloc_buildflags': [
295      ('export_include_dirs', {
296        ".",
297        "base/allocator/partition_allocator/src/",
298      })
299    ],
300    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_partition_alloc_buildflags__testing': [
301      ('export_include_dirs', {
302        ".",
303        "base/allocator/partition_allocator/src/",
304      })
305    ],
306    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_raw_ptr_buildflags': [
307      ('export_include_dirs', {
308        "base/allocator/partition_allocator/src/",
309      })
310    ],
311    'cronet_aml_base_allocator_partition_allocator_src_partition_alloc_raw_ptr_buildflags__testing': [
312      ('export_include_dirs', {
313        "base/allocator/partition_allocator/src/",
314      })
315    ],
316    'cronet_aml_base_base_java_test_support__testing': [
317        ('errorprone', (
318            'javacflags', {
319                "-Xep:ReturnValueIgnored:WARN",
320            }
321          )
322        )
323    ]
324    # end export_include_dir.
325}
326
327_FEATURE_REGEX = "feature=\\\"(.+)\\\""
328_RUST_FLAGS_TO_REMOVE = [
329    "--target", # Added by Soong
330    "--color", # Added by Soong.
331    "--edition", # Added to the appropriate field, must be removed from flags.
332    "--sysroot", # Use AOSP's stdlib so we don't need any hacks for sysroot.
333    "-Cembed-bitcode=no", # Not compatible with Thin-LTO which is added by Soong.
334    "--cfg", # Added to the appropriate field.
335    "--extern", # Soong automatically adds that for us when we use proc_macro
336    "@", # Used by build_script outputs to have rustc load flags from a file.
337    "-Z", # Those are unstable features, completely remove those.
338]
339
340def always_disable(module, arch):
341  return None
342
343def enable_zlib(module, arch):
344  # Requires crrev/c/4109079
345  if arch == 'common':
346    module.shared_libs.add('libz')
347  else:
348    module.target[arch].shared_libs.add('libz')
349
350def enable_boringssl(module, arch):
351  # Do not add boringssl targets to cc_genrules. This happens, because protobuf targets are
352  # originally static_libraries, but later get converted to a cc_genrule.
353  if module.is_genrule(): return
354  # Lets keep statically linking BoringSSL for testing target for now. This should be fixed.
355  if module.name.endswith(gn_utils.TESTING_SUFFIX): return
356  if arch == 'common':
357    shared_libs = module.shared_libs
358  else:
359    shared_libs = module.target[arch].shared_libs
360  shared_libs.add('//external/cronet/third_party/boringssl:libcrypto')
361  shared_libs.add('//external/cronet/third_party/boringssl:libssl')
362  shared_libs.add('//external/cronet/third_party/boringssl:libpki')
363
364def add_androidx_experimental_java_deps(module, arch):
365  module.libs.add("androidx.annotation_annotation-experimental")
366
367def add_androidx_annotation_java_deps(module, arch):
368  module.libs.add("androidx.annotation_annotation")
369
370def add_protobuf_lite_runtime_java_deps(module, arch):
371  module.static_libs.add("libprotobuf-java-lite")
372
373def add_androidx_core_java_deps(module, arch):
374  module.libs.add("androidx.core_core")
375
376def add_jsr305_java_deps(module, arch):
377  module.libs.add("jsr305")
378
379def add_errorprone_annotation_java_deps(module, arch):
380  module.libs.add("error_prone_annotations")
381
382def add_androidx_collection_java_deps(module, arch):
383  module.libs.add("androidx.collection_collection")
384
385def add_junit_java_deps(module, arch):
386  module.static_libs.add("junit")
387
388def add_truth_java_deps(module, arch):
389  module.static_libs.add("truth")
390
391def add_hamcrest_java_deps(module, arch):
392  module.static_libs.add("hamcrest-library")
393  module.static_libs.add("hamcrest")
394
395def add_mockito_java_deps(module, arch):
396  module.static_libs.add("mockito")
397
398def add_guava_java_deps(module, arch):
399  module.static_libs.add("guava")
400
401def add_androidx_junit_java_deps(module, arch):
402  module.static_libs.add("androidx.test.ext.junit")
403
404def add_androidx_test_runner_java_deps(module, arch):
405  module.static_libs.add("androidx.test.runner")
406
407def add_androidx_test_rules_java_deps(module, arch):
408    module.static_libs.add("androidx.test.rules")
409
410def add_android_test_base_java_deps(module, arch):
411  module.libs.add("android.test.base")
412
413def add_accessibility_test_framework_java_deps(module, arch):
414    # BaseActivityTestRule.java depends on this but BaseActivityTestRule.java is not used in aosp.
415    pass
416
417def add_espresso_java_deps(module, arch):
418  module.static_libs.add("androidx.test.espresso.contrib")
419
420def add_android_test_mock_java_deps(module, arch):
421  module.libs.add("android.test.mock.stubs")
422
423def add_androidx_multidex_java_deps(module, arch):
424  # Androidx-multidex is disabled on unbundled branches.
425  pass
426
427def add_androidx_test_monitor_java_deps(module, arch):
428  module.libs.add("androidx.test.monitor")
429
430def add_androidx_ui_automator_java_deps(module, arch):
431  module.static_libs.add("androidx.test.uiautomator_uiautomator")
432
433def add_androidx_test_annotation_java_deps(module, arch):
434  module.static_libs.add("androidx.test.rules")
435
436def add_androidx_test_core_java_deps(module, arch):
437  module.static_libs.add("androidx.test.core")
438
439def add_androidx_activity_activity(module, arch):
440  module.static_libs.add("androidx.activity_activity")
441
442def add_androidx_fragment_fragment(module, arch):
443  module.static_libs.add("androidx.fragment_fragment")
444
445# Android equivalents for third-party libraries that the upstream project
446# depends on. This will be applied to normal and testing targets.
447_builtin_deps = {
448    '//buildtools/third_party/libunwind:libunwind':
449        always_disable,
450    # This is a binary module that generates C++ binding files, Skip this
451    # dependency completely as we construct the modules differently.
452    '//third_party/rust/cxxbridge_cmd/v1:cxxbridge':
453        always_disable,
454    '//net/data/ssl/chrome_root_store:gen_root_store_inc':
455        always_disable,
456    '//third_party/zstd:headers':
457        always_disable,
458    '//testing/buildbot/filters:base_unittests_filters':
459        always_disable,
460    '//testing/buildbot/filters:net_unittests_filters':
461        always_disable,
462    '//third_party/boringssl/src/third_party/fiat:fiat_license':
463        always_disable,
464    '//net/tools/root_store_tool:root_store_tool':
465        always_disable,
466    '//third_party/zlib:zlib':
467        enable_zlib,
468    '//third_party/androidx:androidx_annotation_annotation_java':
469        add_androidx_annotation_java_deps,
470    '//third_party/android_deps:protobuf_lite_runtime_java':
471        add_protobuf_lite_runtime_java_deps,
472    '//third_party/androidx:androidx_annotation_annotation_experimental_java':
473        add_androidx_experimental_java_deps,
474    '//third_party/androidx:androidx_core_core_java':
475        add_androidx_core_java_deps,
476    '//third_party/android_deps:com_google_code_findbugs_jsr305_java':
477        add_jsr305_java_deps,
478    '//third_party/android_deps:com_google_errorprone_error_prone_annotations_java':
479        add_errorprone_annotation_java_deps,
480    '//third_party/androidx:androidx_collection_collection_java':
481        add_androidx_collection_java_deps,
482    '//third_party/junit:junit':
483        add_junit_java_deps,
484    '//third_party/google-truth:google_truth_java':
485        add_truth_java_deps,
486    '//third_party/hamcrest:hamcrest_core_java':
487        add_hamcrest_java_deps,
488    '//third_party/mockito:mockito_java':
489        add_mockito_java_deps,
490    '//third_party/android_deps:guava_android_java':
491        add_guava_java_deps,
492    '//third_party/androidx:androidx_test_ext_junit_java':
493        add_androidx_junit_java_deps,
494    '//third_party/androidx:androidx_test_runner_java':
495        add_androidx_test_runner_java_deps,
496    '//third_party/android_sdk:android_test_base_java':
497        add_android_test_base_java_deps,
498    '//third_party/accessibility_test_framework:accessibility_test_framework_java':
499        add_accessibility_test_framework_java_deps,
500    '//third_party/accessibility_test_framework:accessibility_core_java':
501        add_accessibility_test_framework_java_deps,
502    '//third_party/android_deps:espresso_java':
503        add_espresso_java_deps,
504    '//third_party/android_sdk:android_test_mock_java':
505        add_android_test_mock_java_deps,
506    '//third_party/androidx:androidx_multidex_multidex_java':
507        add_androidx_multidex_java_deps,
508    '//third_party/androidx:androidx_test_monitor_java':
509        add_androidx_test_monitor_java_deps,
510    '//third_party/androidx:androidx_test_annotation_java':
511        add_androidx_test_annotation_java_deps,
512    '//third_party/androidx:androidx_test_core_java':
513        add_androidx_test_core_java_deps,
514    '//third_party/androidx:androidx_test_uiautomator_uiautomator_java':
515        add_androidx_ui_automator_java_deps,
516    '//third_party/hamcrest:hamcrest_java':
517        add_hamcrest_java_deps,
518    '//third_party/androidx:androidx_activity_activity_java':
519        add_androidx_activity_activity,
520    '//third_party/androidx:androidx_fragment_fragment_java':
521        add_androidx_fragment_fragment,
522    '//third_party/androidx:androidx_test_rules_java':
523        add_androidx_test_rules_java_deps,
524}
525builtin_deps = {"{}{}".format(key, suffix): value for key, value in _builtin_deps.items() for suffix in ["", gn_utils.TESTING_SUFFIX] }
526
527# Same as _builtin_deps but will only apply what is explicitly specified.
528builtin_deps.update({
529  '//third_party/boringssl:boringssl':
530    enable_boringssl,
531  '//third_party/boringssl:boringssl_asm':
532  # Due to FIPS requirements, downstream BoringSSL has a different "shape" than upstream's.
533  # We're guaranteed that if X depends on :boringssl it will also depend on :boringssl_asm.
534  # Hence, always drop :boringssl_asm and handle the translation entirely in :boringssl.
535    always_disable,
536})
537
538
539# Name of tethering apex module
540tethering_apex = "com.android.tethering"
541
542# Name of cronet api target
543java_api_target_name = "//components/cronet/android:cronet_api_java"
544
545# Visibility set for package default
546package_default_visibility = ":__subpackages__"
547
548# Visibility set for modules used from Connectivity and within external/cronet
549root_modules_visibility = {"//packages/modules/Connectivity:__subpackages__",
550                           "//external/cronet:__subpackages__"}
551
552# ----------------------------------------------------------------------------
553# End of configuration.
554# ----------------------------------------------------------------------------
555
556def write_blueprint_key_value(output, name, value, sort=True):
557  """Writes a Blueprint key-value pair to the output"""
558
559  if isinstance(value, bool):
560    if value:
561      output.append('    %s: true,' % name)
562    else:
563      output.append('    %s: false,' % name)
564    return
565  if not value:
566    return
567  if isinstance(value, set):
568    value = sorted(value)
569  if isinstance(value, list):
570    output.append('    %s: [' % name)
571    for item in sorted(value) if sort else value:
572      output.append('        "%s",' % item)
573    output.append('    ],')
574    return
575  if isinstance(value, Module.Target):
576    value.to_string(output)
577    return
578  if isinstance(value, dict):
579    kv_output = []
580    for k, v in value.items():
581      write_blueprint_key_value(kv_output, k, v)
582
583    output.append('    %s: {' % name)
584    for line in kv_output:
585      output.append('    %s' % line)
586    output.append('    },')
587    return
588  output.append('    %s: "%s",' % (name, value))
589
590
591
592class Module(object):
593  """A single module (e.g., cc_binary, cc_test) in a blueprint."""
594
595  class Target(object):
596    """A target-scoped part of a module"""
597
598    def __init__(self, name):
599      self.name = name
600      self.srcs = set()
601      self.shared_libs = set()
602      self.static_libs = set()
603      self.whole_static_libs = set()
604      self.header_libs = set()
605      self.cflags = set()
606      self.stl = None
607      self.cppflags = set()
608      self.include_dirs = set()
609      self.generated_headers = set()
610      self.export_generated_headers = set()
611      self.ldflags = set()
612      self.compile_multilib = None
613      self.stem = ""
614      self.edition = ""
615      self.features = set()
616      self.cfgs = set()
617      self.flags = list()
618      self.rustlibs = set()
619      self.proc_macros = set()
620      if name == 'host':
621        self.compile_multilib = '64'
622
623    def to_string(self, output):
624      nested_out = []
625      self._output_field(nested_out, 'srcs')
626      self._output_field(nested_out, 'shared_libs')
627      self._output_field(nested_out, 'static_libs')
628      self._output_field(nested_out, 'whole_static_libs')
629      self._output_field(nested_out, 'header_libs')
630      self._output_field(nested_out, 'cflags')
631      self._output_field(nested_out, 'stl')
632      self._output_field(nested_out, 'cppflags')
633      self._output_field(nested_out, 'include_dirs')
634      self._output_field(nested_out, 'generated_headers')
635      self._output_field(nested_out, 'export_generated_headers')
636      self._output_field(nested_out, 'ldflags')
637      self._output_field(nested_out, 'stem')
638      self._output_field(nested_out, "edition")
639      self._output_field(nested_out, 'cfgs')
640      self._output_field(nested_out, 'features')
641      self._output_field(nested_out, 'flags', False)
642      self._output_field(nested_out, 'rustlibs')
643      self._output_field(nested_out, 'proc_macros')
644
645      if nested_out:
646        # This is added here to make sure it doesn't add a `host` arch-specific module just for
647        # `compile_multilib` flag.
648        self._output_field(nested_out, 'compile_multilib')
649        output.append('    %s: {' % self.name)
650        for line in nested_out:
651          output.append('    %s' % line)
652        output.append('    },')
653
654    def _output_field(self, output, name, sort=True):
655      value = getattr(self, name)
656      return write_blueprint_key_value(output, name, value, sort)
657
658
659  def __init__(self, mod_type, name, gn_target):
660    self.type = mod_type
661    self.gn_target = gn_target
662    self.name = name
663    self.srcs = set()
664    self.comment = 'GN: ' + gn_target
665    self.shared_libs = set()
666    self.static_libs = set()
667    self.whole_static_libs = set()
668    self.tools = set()
669    self.cmd = None
670    self.host_supported = False
671    self.device_supported = True
672    self.init_rc = set()
673    self.out = set()
674    self.export_include_dirs = set()
675    self.generated_headers = set()
676    self.export_generated_headers = set()
677    self.export_static_lib_headers = set()
678    self.export_header_lib_headers = set()
679    self.defaults = set()
680    self.cflags = set()
681    self.include_dirs = set()
682    self.local_include_dirs = set()
683    self.header_libs = set()
684    self.tool_files = set()
685    # target contains a dict of Targets indexed by os_arch.
686    # example: { 'android_x86': Target('android_x86')
687    self.target = dict()
688    self.target['android'] = self.Target('android')
689    self.target['android_x86'] = self.Target('android_x86')
690    self.target['android_x86_64'] = self.Target('android_x86_64')
691    self.target['android_arm'] = self.Target('android_arm')
692    self.target['android_arm64'] = self.Target('android_arm64')
693    self.target['android_riscv64'] = self.Target('android_riscv64')
694    self.target['host'] = self.Target('host')
695    self.target['glibc'] = self.Target('glibc')
696    self.stl = None
697    self.cpp_std = None
698    self.strip = dict()
699    self.data = set()
700    self.apex_available = set()
701    self.min_sdk_version = None
702    self.proto = dict()
703    self.linker_scripts = set()
704    self.ldflags = set()
705    # The genrule_XXX below are properties that must to be propagated back
706    # on the module(s) that depend on the genrule.
707    self.genrule_headers = set()
708    self.genrule_srcs = set()
709    self.genrule_shared_libs = set()
710    self.genrule_header_libs = set()
711    self.version_script = None
712    self.test_suites = set()
713    self.test_config = None
714    self.cppflags = set()
715    self.rtti = False
716    # Name of the output. Used for setting .so file name for libcronet
717    self.libs = set()
718    self.stem = None
719    self.compile_multilib = None
720    self.aidl = dict()
721    self.plugins = set()
722    self.processor_class = None
723    self.sdk_version = None
724    self.javacflags = set()
725    self.c_std = None
726    self.default_applicable_licenses = set()
727    self.default_visibility = []
728    self.visibility = set()
729    self.gn_type = None
730    self.jarjar_rules = ""
731    self.jars = set()
732    self.build_file_path = None
733    self.include_build_directory = None
734    self.allow_rebasing = False
735    self.license_kinds = set()
736    self.license_text = set()
737    self.errorprone = dict()
738    self.crate_name = None
739    # Should be arch-dependant
740    self.crate_root = None
741    self.edition = None
742    self.rustlibs = set()
743    self.proc_macros = set()
744
745  def to_string(self, output):
746    if self.comment:
747      output.append('// %s' % self.comment)
748    output.append('%s {' % self.type)
749    self._output_field(output, 'name')
750    self._output_field(output, 'srcs')
751    self._output_field(output, 'shared_libs')
752    self._output_field(output, 'static_libs')
753    self._output_field(output, 'whole_static_libs')
754    self._output_field(output, 'tools')
755    self._output_field(output, 'cmd', sort=False)
756    if self.host_supported:
757      self._output_field(output, 'host_supported')
758    if not self.device_supported:
759      self._output_field(output, 'device_supported')
760    self._output_field(output, 'init_rc')
761    self._output_field(output, 'out')
762    self._output_field(output, 'export_include_dirs')
763    self._output_field(output, 'generated_headers')
764    self._output_field(output, 'export_generated_headers')
765    self._output_field(output, 'export_static_lib_headers')
766    self._output_field(output, 'export_header_lib_headers')
767    self._output_field(output, 'defaults')
768    self._output_field(output, 'cflags')
769    self._output_field(output, 'include_dirs')
770    self._output_field(output, 'local_include_dirs')
771    self._output_field(output, 'header_libs')
772    self._output_field(output, 'strip')
773    self._output_field(output, 'tool_files')
774    self._output_field(output, 'data')
775    self._output_field(output, 'stl')
776    self._output_field(output, 'cpp_std')
777    self._output_field(output, 'apex_available')
778    self._output_field(output, 'min_sdk_version')
779    self._output_field(output, 'version_script')
780    self._output_field(output, 'test_suites')
781    self._output_field(output, 'test_config')
782    self._output_field(output, 'proto')
783    self._output_field(output, 'linker_scripts')
784    self._output_field(output, 'ldflags')
785    self._output_field(output, 'cppflags')
786    self._output_field(output, 'libs')
787    self._output_field(output, 'stem')
788    self._output_field(output, 'compile_multilib')
789    self._output_field(output, 'aidl')
790    self._output_field(output, 'plugins')
791    self._output_field(output, 'processor_class')
792    self._output_field(output, 'sdk_version')
793    self._output_field(output, 'javacflags')
794    self._output_field(output, 'c_std')
795    self._output_field(output, 'default_applicable_licenses')
796    self._output_field(output, 'default_visibility')
797    self._output_field(output, 'visibility')
798    self._output_field(output, 'jarjar_rules')
799    self._output_field(output, 'jars')
800    self._output_field(output, 'include_build_directory')
801    self._output_field(output, 'license_text')
802    self._output_field(output, "license_kinds")
803    self._output_field(output, "errorprone")
804    self._output_field(output, 'crate_name')
805    self._output_field(output, 'crate_root')
806    self._output_field(output, 'rustlibs')
807    self._output_field(output, 'proc_macros')
808    if self.rtti:
809      self._output_field(output, 'rtti')
810
811    target_out = []
812    for arch, target in sorted(self.target.items()):
813      # _output_field calls getattr(self, arch).
814      setattr(self, arch, target)
815      self._output_field(target_out, arch)
816
817    if target_out:
818      output.append('    target: {')
819      for line in target_out:
820        output.append('    %s' % line)
821      output.append('    },')
822
823    output.append('}')
824    output.append('')
825
826  def add_android_shared_lib(self, lib):
827    if self.type.startswith('java'):
828      raise Exception('Adding Android shared lib for java_* targets is unsupported')
829    elif self.type == 'cc_binary_host':
830      raise Exception('Adding Android shared lib for host tool is unsupported')
831    elif self.host_supported:
832      self.target['android'].shared_libs.add(lib)
833    else:
834      self.shared_libs.add(lib)
835
836  def is_test(self):
837    if gn_utils.TESTING_SUFFIX in self.name:
838      name_without_prefix = self.name[:self.name.find(gn_utils.TESTING_SUFFIX)]
839      return any([name_without_prefix == label_to_module_name(target) for target in DEFAULT_TESTS])
840    return False
841
842  def _output_field(self, output, name, sort=True):
843    value = getattr(self, name)
844    return write_blueprint_key_value(output, name, value, sort)
845
846  def is_compiled(self):
847    return self.type not in ('cc_genrule', 'filegroup', 'java_genrule')
848
849  def is_genrule(self):
850    return self.type == "cc_genrule"
851
852  def has_input_files(self):
853    if self.type in ["java_library", "java_import"]:
854        return True
855    if len(self.srcs) > 0:
856      return True
857    if any([len(target.srcs) > 0 for target in self.target.values()]):
858      return True
859    # Allow cc_static_library with export_generated_headers as those are crucial for
860    # the depending modules
861    return len(self.export_generated_headers) > 0
862
863
864class Blueprint(object):
865  """In-memory representation of an Android.bp file."""
866
867  def __init__(self, buildgn_directory_path: str = ""):
868    self.modules = {}
869    # Holds the BUILD.gn path which resulted in the creation of this Android.bp.
870    self._buildgn_directory_path = buildgn_directory_path
871    self._readme_location = buildgn_directory_path
872    self._package_module = None
873    self._license_module = None
874
875  def add_module(self, module):
876    """Adds a new module to the blueprint, replacing any existing module
877        with the same name.
878
879        Args:
880            module: Module instance.
881        """
882    self.modules[module.name] = module
883
884  def set_package_module(self, module):
885    self._package_module = module
886
887  def set_license_module(self, module):
888    self._license_module = module
889
890  def get_license_module(self):
891    return self._license_module
892
893  def set_readme_location(self, readme_path: str):
894    self._readme_location = readme_path
895
896  def get_readme_location(self):
897    return self._readme_location
898
899  def get_buildgn_location(self):
900    return self._buildgn_directory_path
901
902  def to_string(self):
903    ret = []
904    if self._package_module:
905      self._package_module.to_string(ret)
906    if self._license_module:
907      self._license_module.to_string(ret)
908    for m in sorted(self.modules.values(), key=lambda m: m.name):
909      if m.type != "cc_library_static" or m.has_input_files():
910        # Don't print cc_library_static with empty srcs. These attributes are already
911        # propagated up the tree. Printing them messes the presubmits because
912        # every module is compiled while those targets are not reachable in
913        # a normal compilation path.
914        m.to_string(ret)
915    return ret
916
917
918def label_to_module_name(label):
919  """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
920  module = re.sub(r'^//:?', '', label)
921  module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
922
923  if not module.startswith(module_prefix):
924    return module_prefix + module
925  return module
926
927
928def is_supported_source_file(name):
929  """Returns True if |name| can appear in a 'srcs' list."""
930  return os.path.splitext(name)[1] in ['.c', '.cc', '.cpp', '.java', '.proto', '.S', '.aidl', '.rs']
931
932def normalize_rust_flags(rust_flags: List[str]) -> Dict[str, Set[str] | None]:
933  """
934  Normalizes the rust params where it tries to put (key, value) param
935  as a dictionary key. A key without value will have None as value.
936
937  An example of this would be:
938
939  Input: ["--cfg=feature=\"float_roundtrip\"", "--cfg=feature=\"std\"",
940          "--edition=2021", "-Cforce-unwind-tables=no", "-Dwarnings"]
941
942  Output: {
943          "--cfg": [feature=\"float_roundtrip\", feature=\"std\"],
944          "--edition": [2021],
945          "-Cforce-unwind-tables": [no],
946          "-Dwarnings": None
947          }
948  :param rust_flags: List of rust flags.
949  :return: Dictionary of rust flags where each key will point to a list of
950  values.
951  """
952  args_mapping = {}
953  previous_key = None
954  for rust_flag in rust_flags:
955    if not rust_flag.startswith("-"):
956      # This might be a key on its own, rustc supports params with no keys
957      # such as (@path).
958      if rust_flag.startswith("@"):
959        args_mapping[rust_flag] = None
960        if previous_key:
961          args_mapping[previous_key] = None
962      else:
963        # This is the value to the previous key (eg: ["--cfg", "val"])
964        if not previous_key:
965          raise ValueError(
966            f"Field {rust_flag} does not relate to any key. Rust flags found: {rust_flags}")
967        if previous_key not in args_mapping:
968          args_mapping[previous_key] = set()
969        args_mapping[previous_key].add(rust_flag)
970        previous_key = None
971    else:
972      if previous_key:
973        # We have a previous key, that means that the previous key is
974        # a no-value key.
975        args_mapping[previous_key] = None
976        previous_key = None
977      # This can be a key-only string or key=value or
978      # key=foo=value (eg:--cfg=feature=X) or key and value in different strings.
979      if "=" in rust_flag:
980        # We found an equal, this is probably a key=value string.
981        rust_flag_split = rust_flag.split("=")
982        if len(rust_flag_split) > 3:
983          raise ValueError(f"Could not normalize flag {rust_flag} as it has multiple equal signs.")
984        if rust_flag_split[0] not in args_mapping:
985          args_mapping[rust_flag_split[0]] = set()
986        args_mapping[rust_flag_split[0]].add("=".join(rust_flag_split[1:]))
987      else:
988        # Assume this is a key-only string. This will be resolved in the next
989        # iteration.
990        previous_key = rust_flag
991  if previous_key:
992    # We have a previous key without a value, this must be a key-only string.
993    args_mapping[previous_key] = None
994  return args_mapping
995
996
997def _set_rust_flags(module: Module.Target, rust_flags: List[str], arch_name: str) -> None:
998  rust_flags_dict = normalize_rust_flags(rust_flags)
999  if "--edition" in rust_flags_dict:
1000    module.edition = list(rust_flags_dict["--edition"])[0]
1001
1002  for cfg in rust_flags_dict.get("--cfg", set()):
1003    feature_regex = re.match(_FEATURE_REGEX, cfg)
1004    if feature_regex:
1005      module.features.add(feature_regex.group(1))
1006    else:
1007      module.cfgs.add(cfg.replace("\"", "\\\""))
1008
1009  pre_filter_flags = []
1010  for (key, values) in rust_flags_dict.items():
1011    if values is None:
1012      pre_filter_flags.append(key)
1013    else:
1014      pre_filter_flags.extend(f"{key}={param_val}" for param_val in values)
1015
1016  flags_to_remove = _RUST_FLAGS_TO_REMOVE
1017  # AOSP compiles everything for host under panic=unwind instead of abort.
1018  # In order to be consistent with the ecosystem, remove the -Cpanic flag.
1019  if arch_name == "host":
1020    flags_to_remove.append("-Cpanic")
1021
1022  # Remove restricted flags
1023  for pre_filter_flag in pre_filter_flags:
1024    if not any([pre_filter_flag.startswith(restricted_flag) for restricted_flag in flags_to_remove]):
1025      module.flags.append(pre_filter_flag)
1026
1027def get_protoc_module_name(gn):
1028  protoc_gn_target_name = gn.get_target('//third_party/protobuf:protoc').name
1029  return label_to_module_name(protoc_gn_target_name)
1030
1031def create_rust_cxx_module(blueprint, target):
1032  """Generate genrules for a CXX GN target
1033
1034    GN actions are used to dynamically generate files during the build. The
1035    Soong equivalent is a genrule. Currently, Chromium GN targets generates
1036    both .cc and .h files in the same target, we have to split this up to be
1037    compatible with Soong.
1038
1039    CXX bridge binary is used from AOSP instead of compiling Chromium's CXX bridge.
1040
1041    Args:
1042        blueprint: Blueprint instance which is being generated.
1043        target: gn_utils.Target object.
1044
1045    Returns:
1046        The source_genrule module.
1047  """
1048  header_genrule = Module("cc_genrule", label_to_module_name(target.name) + "_header", target.name)
1049  header_genrule.tools = {"cxxbridge"}
1050  header_genrule.cmd = "$(location cxxbridge) $(in) --header > $(out)"
1051  header_genrule.srcs = set([gn_utils.label_to_path(src) for src in target.sources])
1052  # The output of the cc_genrule is the input + ".h" suffix, this is because
1053  # the input to a CXX genrule is just one source file.
1054  header_genrule.out = set([f"{gn_utils.label_to_path(out)}.h" for out in target.sources])
1055
1056  cc_genrule = Module("cc_genrule", label_to_module_name(target.name), target.name)
1057  cc_genrule.tools = {"cxxbridge"}
1058  cc_genrule.cmd = "$(location cxxbridge) $(in) > $(out)"
1059  cc_genrule.srcs = set([gn_utils.label_to_path(src) for src in target.sources])
1060  cc_genrule.genrule_srcs = {f":{cc_genrule.name}"}
1061  # The output of the cc_genrule is the input + ".cc" suffix, this is because
1062  # the input to a CXX genrule is just one source file.
1063  cc_genrule.out = set([f"{gn_utils.label_to_path(out)}.cc" for out in target.sources])
1064
1065  cc_genrule.genrule_headers.add(header_genrule.name)
1066  blueprint.add_module(cc_genrule)
1067  blueprint.add_module(header_genrule)
1068  return cc_genrule
1069
1070def create_proto_modules(blueprint, gn, target):
1071  """Generate genrules for a proto GN target.
1072
1073    GN actions are used to dynamically generate files during the build. The
1074    Soong equivalent is a genrule. This function turns a specific kind of
1075    genrule which turns .proto files into source and header files into a pair
1076    equivalent genrules.
1077
1078    Args:
1079        blueprint: Blueprint instance which is being generated.
1080        target: gn_utils.Target object.
1081
1082    Returns:
1083        The source_genrule module.
1084    """
1085  assert (target.type == 'proto_library')
1086
1087  protoc_module_name = get_protoc_module_name(gn)
1088  tools = {protoc_module_name}
1089  cpp_out_dir = '$(genDir)/%s/' % (target.proto_in_dir)
1090  target_module_name = label_to_module_name(target.name)
1091
1092  # In GN builds the proto path is always relative to the output directory
1093  # (out/tmp.xxx).
1094  cmd = ['$(location %s)' % protoc_module_name]
1095  cmd += ['--proto_path=%s/%s' % (tree_path, target.proto_in_dir)]
1096
1097  for proto_path in target.proto_paths:
1098    cmd += [f'--proto_path={tree_path}/{proto_path}']
1099  if buildtools_protobuf_src in target.proto_paths:
1100    cmd += ['--proto_path=%s' % android_protobuf_src]
1101
1102  # We don't generate any targets for source_set proto modules because
1103  # they will be inlined into other modules if required.
1104  if target.proto_plugin == 'source_set':
1105    return None
1106
1107  # Descriptor targets only generate a single target.
1108  if target.proto_plugin == 'descriptor':
1109    out = '{}.bin'.format(target_module_name)
1110
1111    cmd += ['--descriptor_set_out=$(out)']
1112    cmd += ['$(in)']
1113
1114    descriptor_module = Module('cc_genrule', target_module_name, target.name)
1115    descriptor_module.cmd = ' '.join(cmd)
1116    descriptor_module.out = [out]
1117    descriptor_module.tools = tools
1118    blueprint.add_module(descriptor_module)
1119
1120    # Recursively extract the .proto files of all the dependencies and
1121    # add them to srcs.
1122    descriptor_module.srcs.update(
1123        gn_utils.label_to_path(src) for src in target.sources)
1124    for dep in target.proto_deps:
1125      current_target = gn.get_target(dep)
1126      descriptor_module.srcs.update(
1127          gn_utils.label_to_path(src) for src in current_target.sources)
1128
1129    return descriptor_module
1130
1131  # We create two genrules for each proto target: one for the headers and
1132  # another for the sources. This is because the module that depends on the
1133  # generated files needs to declare two different types of dependencies --
1134  # source files in 'srcs' and headers in 'generated_headers' -- and it's not
1135  # valid to generate .h files from a source dependency and vice versa.
1136  source_module_name = target_module_name
1137  source_module = Module('cc_genrule', source_module_name, target.name)
1138  blueprint.add_module(source_module)
1139  source_module.srcs.update(
1140      gn_utils.label_to_path(src) for src in target.sources)
1141
1142  header_module = Module('cc_genrule', source_module_name + '_headers',
1143                         target.name)
1144  blueprint.add_module(header_module)
1145  header_module.srcs = set(source_module.srcs)
1146
1147  header_module.export_include_dirs = {'.', 'protos'}
1148  # Since the .cc file and .h get created by a different gerule target, they
1149  # are not put in the same intermediate path, so local includes do not work
1150  # without explictily exporting the include dir.
1151  header_module.export_include_dirs.add(target.proto_in_dir)
1152
1153  # This function does not return header_module so setting apex_available attribute here.
1154  header_module.apex_available.add(tethering_apex)
1155
1156  source_module.genrule_srcs.add(':' + source_module.name)
1157  source_module.genrule_headers.add(header_module.name)
1158
1159  if target.proto_plugin == 'proto':
1160    suffixes = ['pb']
1161    source_module.genrule_shared_libs.add('libprotobuf-cpp-lite')
1162    cmd += ['--cpp_out=lite=true:' + cpp_out_dir]
1163  else:
1164    raise Exception('Unsupported proto plugin: %s' % target.proto_plugin)
1165
1166  cmd += ['$(in)']
1167  source_module.cmd = ' '.join(cmd)
1168  header_module.cmd = source_module.cmd
1169  source_module.tools = tools
1170  header_module.tools = tools
1171
1172  for sfx in suffixes:
1173    source_module.out.update('%s' %
1174                             src.replace('.proto', '.%s.cc' % sfx)
1175                             for src in source_module.srcs)
1176    header_module.out.update('%s' %
1177                             src.replace('.proto', '.%s.h' % sfx)
1178                             for src in header_module.srcs)
1179  # This has proto files that will be used for reference resolution
1180  # but not compiled into cpp files. These additional sources has no output.
1181  proto_data_sources = sorted([gn_utils.label_to_path(proto_src)
1182                               for proto_src in target.inputs if proto_src.endswith(".proto")])
1183  source_module.srcs.update(proto_data_sources)
1184  header_module.srcs.update(proto_data_sources)
1185
1186  # Allow rebasing proto genrules according to their proper path.
1187  source_module.allow_rebasing = True
1188  header_module.allow_rebasing = True
1189  header_module.build_file_path = target.build_file_path
1190  source_module.build_file_path = target.build_file_path
1191  return source_module
1192
1193
1194def create_gcc_preprocess_modules(blueprint, target):
1195  # gcc_preprocess.py internally execute host gcc which is not allowed in genrule.
1196  # So, this function create multiple modules and realize equivalent processing
1197  assert (len(target.sources) == 1)
1198  source = list(target.sources)[0]
1199  assert (Path(source).suffix == '.template')
1200  stem = Path(source).stem
1201
1202  bp_module_name = label_to_module_name(target.name)
1203
1204  # Rename .template to .cc since cc_preprocess_no_configuration does not accept .template file as
1205  # srcs
1206  rename_module = Module('genrule', bp_module_name + '_rename', target.name)
1207  rename_module.srcs.add(gn_utils.label_to_path(source))
1208  rename_module.out.add(stem + '.cc')
1209  rename_module.cmd = 'cp $(in) $(out)'
1210  blueprint.add_module(rename_module)
1211
1212  # Preprocess template file and generates java file
1213  preprocess_module = Module(
1214      'cc_preprocess_no_configuration',
1215      bp_module_name + '_preprocess',
1216      target.name
1217  )
1218  # -E: stop after preprocessing.
1219  # -P: disable line markers, i.e. '#line 309'
1220  preprocess_module.cflags.update(['-E', '-P', '-DANDROID'])
1221  preprocess_module.srcs.add(':' + rename_module.name)
1222  defines = ['-D' + target.args[i+1] for i, arg in enumerate(target.args) if arg == '--define']
1223  preprocess_module.cflags.update(defines)
1224  blueprint.add_module(preprocess_module)
1225
1226  # Generates srcjar using soong_zip
1227  module = Module('genrule', bp_module_name, target.name)
1228  module.srcs.add(':' + preprocess_module.name)
1229  module.out.add(stem + '.srcjar')
1230  module.cmd = NEWLINE.join([
1231    f'cp $(in) $(genDir)/{stem}.java &&',
1232    f'$(location soong_zip) -o $(out) -srcjar -C $(genDir) -f $(genDir)/{stem}.java'
1233  ])
1234  module.tools.add('soong_zip')
1235  blueprint.add_module(module)
1236  return module
1237
1238
1239class BaseActionSanitizer():
1240  def __init__(self, target, arch):
1241    # Just to be on the safe side, create a deep-copy.
1242    self.target = copy.deepcopy(target)
1243    if arch:
1244      # Merge arch specific attributes
1245      self.target.sources |= arch.sources
1246      self.target.inputs |= arch.inputs
1247      self.target.outputs |= arch.outputs
1248      self.target.script = self.target.script or arch.script
1249      self.target.args = self.target.args or arch.args
1250      self.target.response_file_contents = \
1251        self.target.response_file_contents or arch.response_file_contents
1252    self.target.args = self._normalize_args()
1253
1254  def get_name(self):
1255    return label_to_module_name(self.target.name)
1256
1257  def _normalize_args(self):
1258    # Convert ['--param=value'] to ['--param', 'value'] for consistency.
1259    # Escape quotations.
1260    normalized_args = []
1261    for arg in self.target.args:
1262      arg = arg.replace('"', r'\"')
1263      if arg.startswith('-'):
1264        normalized_args.extend(arg.split('='))
1265      else:
1266        normalized_args.append(arg)
1267    return normalized_args
1268
1269  # There are three types of args:
1270  # - flags (--flag)
1271  # - value args (--arg value)
1272  # - list args (--arg value1 --arg value2)
1273  # value args have exactly one arg value pair and list args have one or more arg value pairs.
1274  # Note that the set of list args contains the set of value args.
1275  # This is because list and value args are identical when the list args has only one arg value pair
1276  # Some functions provide special implementations for each type, while others
1277  # work on all of them.
1278  def _has_arg(self, arg):
1279    return arg in self.target.args
1280
1281  def _get_arg_indices(self, target_arg):
1282    return [i for i, arg in enumerate(self.target.args) if arg == target_arg]
1283
1284  # Whether an arg value pair appears once or more times
1285  def _is_list_arg(self, arg):
1286    indices = self._get_arg_indices(arg)
1287    return len(indices) > 0 and all([not self.target.args[i + 1].startswith('--') for i in indices])
1288
1289  def _update_list_arg(self, arg, func, throw_if_absent = True):
1290    if self._should_fail_silently(arg, throw_if_absent):
1291      return
1292    assert(self._is_list_arg(arg))
1293    indices = self._get_arg_indices(arg)
1294    for i in indices:
1295      self._set_arg_at(i + 1, func(self.target.args[i + 1]))
1296
1297  # Whether an arg value pair appears exactly once
1298  def _is_value_arg(self, arg):
1299    return operator.countOf(self.target.args, arg) == 1 and self._is_list_arg(arg)
1300
1301  def _get_value_arg(self, arg):
1302    assert(self._is_value_arg(arg))
1303    i = self.target.args.index(arg)
1304    return self.target.args[i + 1]
1305
1306  # used to check whether a function call should cause an error when an arg is
1307  # missing.
1308  def _should_fail_silently(self, arg, throw_if_absent):
1309    return not throw_if_absent and not self._has_arg(arg)
1310
1311  def _set_value_arg(self, arg, value, throw_if_absent = True):
1312    if self._should_fail_silently(arg, throw_if_absent):
1313      return
1314    assert(self._is_value_arg(arg))
1315    i = self.target.args.index(arg)
1316    self.target.args[i + 1] = value
1317
1318  def _update_value_arg(self, arg, func, throw_if_absent = True):
1319    if self._should_fail_silently(arg, throw_if_absent):
1320      return
1321    self._set_value_arg(arg, func(self._get_value_arg(arg)))
1322
1323  def _set_arg_at(self, position, value):
1324    self.target.args[position] = value
1325
1326  def _update_arg_at(self, position, func):
1327    self.target.args[position] = func(self.target.args[position])
1328
1329  def _delete_value_arg(self, arg, throw_if_absent = True):
1330    if self._should_fail_silently(arg, throw_if_absent):
1331      return
1332    assert(self._is_value_arg(arg))
1333    i = self.target.args.index(arg)
1334    self.target.args.pop(i)
1335    self.target.args.pop(i)
1336
1337  def _append_arg(self, arg, value):
1338    self.target.args.append(arg)
1339    self.target.args.append(value)
1340
1341  def _sanitize_filepath_with_location_tag(self, arg):
1342    if arg.startswith('../../'):
1343      arg = self._sanitize_filepath(arg)
1344      arg = self._add_location_tag(arg)
1345    return arg
1346
1347  # wrap filename in location tag.
1348  def _add_location_tag(self, filename):
1349    return '$(location %s)' % filename
1350
1351  # applies common directory transformation that *should* be universally applicable.
1352  # TODO: verify if it actually *is* universally applicable.
1353  def _sanitize_filepath(self, filepath):
1354    # Careful, order matters!
1355    # delete all leading ../
1356    filepath = re.sub('^(\.\./)+', '', filepath)
1357    filepath = re.sub('^gen/jni_headers', '$(genDir)', filepath)
1358    filepath = re.sub('^gen', '$(genDir)', filepath)
1359    return filepath
1360
1361  # Iterate through all the args and apply function
1362  def _update_all_args(self, func):
1363    self.target.args = [func(arg) for arg in self.target.args]
1364
1365  def get_pre_cmd(self):
1366    pre_cmd = []
1367    out_dirs = [out[:out.rfind("/")] for out in self.target.outputs if "/" in out]
1368    # Sort the list to make the output deterministic.
1369    for out_dir in sorted(set(out_dirs)):
1370        pre_cmd.append("mkdir -p $(genDir)/{} && ".format(out_dir))
1371    return NEWLINE.join(pre_cmd)
1372
1373  def get_base_cmd(self):
1374    arg_string = NEWLINE.join(self.target.args)
1375    cmd = '$(location %s) %s' % (
1376    gn_utils.label_to_path(self.target.script), arg_string)
1377
1378    if self.use_response_file:
1379      # Pipe response file contents into script
1380      cmd = 'echo \'%s\' |%s%s' % (self.target.response_file_contents, NEWLINE, cmd)
1381    return cmd
1382
1383  def get_cmd(self):
1384      return self.get_pre_cmd() + self.get_base_cmd()
1385
1386  def get_outputs(self):
1387    return self.target.outputs
1388
1389  def get_srcs(self):
1390    # gn treats inputs and sources for actions equally.
1391    # soong only supports source files inside srcs, non-source files are added as
1392    # tool_files dependency.
1393    files = self.target.sources.union(self.target.inputs)
1394    return {gn_utils.label_to_path(file) for file in files if is_supported_source_file(file)}
1395
1396  def get_tools(self):
1397    return set()
1398
1399  def get_tool_files(self):
1400    # gn treats inputs and sources for actions equally.
1401    # soong only supports source files inside srcs, non-source files are added as
1402    # tool_files dependency.
1403    files = self.target.sources.union(self.target.inputs)
1404    tool_files = {gn_utils.label_to_path(file)
1405                  for file in files if not is_supported_source_file(file)}
1406    tool_files.add(gn_utils.label_to_path(self.target.script))
1407    return tool_files
1408
1409  def _sanitize_args(self):
1410    # Handle passing parameters via response file by piping them into the script
1411    # and reading them from /dev/stdin.
1412
1413    self.use_response_file = gn_utils.RESPONSE_FILE in self.target.args
1414    if self.use_response_file:
1415      # Replace {{response_file_contents}} with /dev/stdin
1416      self.target.args = ['/dev/stdin' if it == gn_utils.RESPONSE_FILE else it
1417                          for it in self.target.args]
1418
1419  def _sanitize_inputs(self):
1420    pass
1421
1422  def get_deps(self):
1423    return self.target.deps
1424
1425  def sanitize(self):
1426    self._sanitize_args()
1427    self._sanitize_inputs()
1428
1429  # Whether this target generates header files
1430  def is_header_generated(self):
1431    return any(os.path.splitext(it)[1] == '.h' for it in self.target.outputs)
1432
1433class WriteBuildDateHeaderSanitizer(BaseActionSanitizer):
1434  def _sanitize_args(self):
1435    self._set_arg_at(0, '$(out)')
1436    super()._sanitize_args()
1437
1438class WriteBuildFlagHeaderSanitizer(BaseActionSanitizer):
1439  def _sanitize_args(self):
1440    self._set_value_arg('--gen-dir', '.')
1441    self._set_value_arg('--output', '$(out)')
1442    super()._sanitize_args()
1443
1444class GnRunBinarySanitizer(BaseActionSanitizer):
1445  def __init__(self, target, arch):
1446    super().__init__(target, arch)
1447    self.binary_to_target = {
1448      "clang_x64/transport_security_state_generator":
1449            "cronet_aml_net_tools_transport_security_state_generator_transport_security_state_generator__testing",
1450    }
1451    self.binary = self.binary_to_target[self.target.args[0]]
1452
1453  def _replace_gen_with_location_tag(self, arg):
1454    if arg.startswith("gen/"):
1455      return "$(location %s)" % arg.replace("gen/", "")
1456    return arg
1457
1458  def _replace_binary(self, arg):
1459    if arg in self.binary_to_target:
1460      return '$(location %s)' % self.binary
1461    return arg
1462
1463  def _remove_python_args(self):
1464    self.target.args = [arg for arg in self.target.args if "python3" not in arg]
1465
1466  def _sanitize_args(self):
1467    self._update_all_args(self._sanitize_filepath_with_location_tag)
1468    self._update_all_args(self._replace_gen_with_location_tag)
1469    self._update_all_args(self._replace_binary)
1470    self._remove_python_args()
1471    super()._sanitize_args()
1472
1473  def get_tools(self):
1474    tools = super().get_tools()
1475    tools.add(self.binary)
1476    return tools
1477
1478  def get_cmd(self):
1479    # Remove the script and use the binary right away
1480    return self.get_pre_cmd() + NEWLINE.join(self.target.args)
1481
1482class JniGeneratorSanitizer(BaseActionSanitizer):
1483  def __init__(self, target, arch, is_test_target):
1484    self.is_test_target = is_test_target
1485    super().__init__(target, arch)
1486
1487  def get_srcs(self):
1488    all_srcs = super().get_srcs()
1489    all_srcs.update({gn_utils.label_to_path(file)
1490                     for file in self.target.transitive_jni_java_sources
1491                     if is_supported_source_file(file)})
1492    return set(src for src in all_srcs if src.endswith(".java"))
1493
1494  def _add_location_tag_to_filepath(self, arg):
1495    if not arg.endswith('.class'):
1496      # --input_file supports both .class specifiers or source files as arguments.
1497      # Only source files need to be wrapped inside a $(location <label>) tag.
1498      arg = self._add_location_tag(arg)
1499    return arg
1500
1501  def _sanitize_args(self):
1502    self._set_value_arg('--jar-file', '$(location :current_android_jar)', False)
1503    if self._has_arg('--jar-file'):
1504      self._set_value_arg('--javap', '$(location :javap)')
1505    self._update_value_arg('--srcjar-path', self._sanitize_filepath, False)
1506    self._update_value_arg('--output-dir', self._sanitize_filepath)
1507    self._update_value_arg('--extra-include', self._sanitize_filepath, False)
1508    self._update_value_arg('--placeholder-srcjar-path', self._sanitize_filepath, False)
1509    self._update_list_arg('--input-file', self._sanitize_filepath)
1510    self._update_list_arg('--input-file', self._add_location_tag_to_filepath)
1511    if not self.is_test_target and not self._has_arg('--jar-file'):
1512      # Don't jarjar classes that already exists within the java SDK. The headers generated
1513      # from those genrule can simply call into the original class as it exists outside
1514      # of cronet's jar.
1515      # Only jarjar platform code
1516      self._append_arg('--package-prefix', 'android.net.connectivity')
1517    super()._sanitize_args()
1518
1519  def get_outputs(self):
1520    outputs = set()
1521    for out in super().get_outputs():
1522      # placeholder.srcjar contains empty placeholder classes used to compile generated java files
1523      # without any other deps. This is not used in aosp.
1524      if out.endswith("_placeholder.srcjar"):
1525        continue
1526      # fix target.output directory to match #include statements.
1527      outputs.add(re.sub('^jni_headers/', '', out))
1528    return outputs
1529
1530  def get_tool_files(self):
1531    tool_files = super().get_tool_files()
1532
1533    # Filter android.jar and add :current_android_jar
1534    tool_files = {file if not file.endswith('android.jar') else ':current_android_jar'
1535                  for file in tool_files }
1536    # Filter bin/javap
1537    tool_files = {file for file in tool_files if not file.endswith('bin/javap') }
1538
1539    # TODO: Remove once https://chromium-review.googlesource.com/c/chromium/src/+/5370266 has made
1540    #       its way to AOSP
1541    # Files not specified in anywhere but jni_generator.py imports this file
1542    tool_files.add('third_party/jni_zero/codegen/header_common.py')
1543    tool_files.add('third_party/jni_zero/codegen/placeholder_java_type.py')
1544
1545    return tool_files
1546
1547  def get_tools(self):
1548    tools = super().get_tools()
1549    if self._has_arg('--jar-file'):
1550      tools.add(":javap")
1551    return tools
1552
1553
1554class JavaJniGeneratorSanitizer(JniGeneratorSanitizer):
1555  def __init__(self, target, arch, is_test_target):
1556    self.is_test_target = is_test_target
1557    super().__init__(target, arch, is_test_target)
1558
1559  def get_outputs(self):
1560    # fix target.output directory to match #include statements.
1561    outputs = {re.sub('^jni_headers/', '', out) for out in super().get_outputs()}
1562    self.target.outputs = [out for out in outputs if
1563                           out.endswith(".srcjar")]
1564    return outputs
1565
1566  def get_deps(self):
1567    return {}
1568
1569  def get_name(self):
1570    name = super().get_name() + "__java"
1571    return name
1572
1573class JniRegistrationGeneratorSanitizer(BaseActionSanitizer):
1574  def __init__(self, target, arch, is_test_target):
1575    self.is_test_target = is_test_target
1576    super().__init__(target, arch)
1577
1578  def get_srcs(self):
1579    all_srcs = super().get_srcs()
1580    all_srcs.update({gn_utils.label_to_path(file)
1581                     for file in self.target.transitive_jni_java_sources
1582                     if is_supported_source_file(file)})
1583    return set(src for src in all_srcs if src.endswith(".java"))
1584
1585  def _sanitize_inputs(self):
1586    self.target.inputs = [file for file in self.target.inputs if not file.startswith('//out/')]
1587
1588  def get_outputs(self):
1589    outputs = set()
1590    for out in super().get_outputs():
1591      # placeholder.srcjar contains empty placeholder classes used to compile generated java files
1592       # without any other deps. This is not used in aosp.
1593       if out.endswith("_placeholder.srcjar"):
1594         continue
1595       # fix target.output directory to match #include statements.
1596       outputs.add(re.sub('^jni_headers/', '', out))
1597    return outputs
1598
1599  def _sanitize_args(self):
1600    self._update_value_arg('--depfile', self._sanitize_filepath)
1601    self._update_value_arg('--srcjar-path', self._sanitize_filepath)
1602    self._update_value_arg('--header-path', self._sanitize_filepath)
1603    self._update_value_arg('--placeholder-srcjar-path', self._sanitize_filepath, False)
1604    self._delete_value_arg('--depfile', False)
1605    self._set_value_arg('--java-sources-file', '$(genDir)/java.sources')
1606    if not self.is_test_target:
1607      # Only jarjar platform code
1608      self._append_arg('--package-prefix', 'android.net.connectivity')
1609    super()._sanitize_args()
1610
1611  def get_cmd(self):
1612    # jni_registration_generator.py doesn't work with python2
1613    cmd = "python3 " + super().get_base_cmd()
1614    # Path in the original sources file does not work in genrule.
1615    # So creating sources file in cmd based on the srcs of this target.
1616    # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files
1617    # whose path startswith(..)
1618    commands = ["current_dir=`basename \\\`pwd\\\``;",
1619                "for f in $(in);",
1620                "do",
1621                "echo \\\"../$$current_dir/$$f\\\" >> $(genDir)/java.sources;",
1622                "done;",
1623                cmd]
1624
1625    return self.get_pre_cmd() + NEWLINE.join(commands)
1626
1627  def get_tool_files(self):
1628      tool_files = super().get_tool_files()
1629      # TODO: Remove once https://chromium-review.googlesource.com/c/chromium/src/+/5370266 has made
1630      #       its way to AOSP
1631      # Files not specified in anywhere but jni_generator.py imports this file
1632      tool_files.add('third_party/jni_zero/codegen/header_common.py')
1633      tool_files.add('third_party/jni_zero/codegen/placeholder_java_type.py')
1634      return tool_files
1635
1636class JavaJniRegistrationGeneratorSanitizer(JniRegistrationGeneratorSanitizer):
1637  def get_name(self):
1638    name = super().get_name() + "__java"
1639    return name
1640
1641  def get_outputs(self):
1642    return [out for out in super().get_outputs() if
1643                           out.endswith(".srcjar")]
1644
1645  def get_deps(self):
1646    return {}
1647
1648class VersionSanitizer(BaseActionSanitizer):
1649  def _sanitize_args(self):
1650    self._set_value_arg('-o', '$(out)')
1651    # args for the version.py contain file path without leading --arg key. So apply sanitize
1652    # function for all the args.
1653    self._update_all_args(self._sanitize_filepath_with_location_tag)
1654    self._update_list_arg('-e', self._sanitize_eval)
1655    super()._sanitize_args()
1656
1657  def _sanitize_eval(self, eval_arg):
1658      return "'%s'" % eval_arg.replace("\'", "\\\"")
1659
1660  def get_tool_files(self):
1661    tool_files = super().get_tool_files()
1662    # android_chrome_version.py is not specified in anywhere but version.py imports this file
1663    tool_files.add('build/util/android_chrome_version.py')
1664    return tool_files
1665
1666class JavaCppEnumSanitizer(BaseActionSanitizer):
1667  def _sanitize_args(self):
1668    self._update_all_args(self._sanitize_filepath_with_location_tag)
1669    self._set_value_arg('--srcjar', '$(out)')
1670    super()._sanitize_args()
1671
1672class MakeDafsaSanitizer(BaseActionSanitizer):
1673  def is_header_generated(self):
1674    # This script generates .cc files but they are #included by other sources
1675    # (e.g. registry_controlled_domain.cc)
1676    return True
1677
1678class JavaCppFeatureSanitizer(BaseActionSanitizer):
1679  def _sanitize_args(self):
1680    self._update_all_args(self._sanitize_filepath_with_location_tag)
1681    self._set_value_arg('--srcjar', '$(out)')
1682    super()._sanitize_args()
1683
1684class JavaCppStringSanitizer(BaseActionSanitizer):
1685  def _sanitize_args(self):
1686    self._update_all_args(self._sanitize_filepath_with_location_tag)
1687    self._set_value_arg('--srcjar', '$(out)')
1688    super()._sanitize_args()
1689
1690class WriteNativeLibrariesJavaSanitizer(BaseActionSanitizer):
1691  def _sanitize_args(self):
1692    self._set_value_arg('--output', '$(out)')
1693    super()._sanitize_args()
1694
1695
1696class ProtocJavaSanitizer(BaseActionSanitizer):
1697  def __init__(self, target, arch, gn):
1698    super().__init__(target, arch)
1699    self._protoc = get_protoc_module_name(gn)
1700
1701  def _sanitize_proto_path(self, arg):
1702    arg = self._sanitize_filepath(arg)
1703    return tree_path + '/' + arg
1704
1705  def _sanitize_args(self):
1706    super()._sanitize_args()
1707    self._delete_value_arg('--depfile')
1708    self._set_value_arg('--protoc', '$(location %s)' % self._protoc)
1709    self._update_value_arg('--proto-path', self._sanitize_proto_path)
1710    self._set_value_arg('--srcjar', '$(out)')
1711    self._update_arg_at(-1, self._sanitize_filepath_with_location_tag)
1712
1713  def get_tools(self):
1714    tools = super().get_tools()
1715    tools.add(self._protoc)
1716    return tools
1717
1718
1719def get_action_sanitizer(gn, target, type, arch, is_test_target):
1720  if target.script == "//build/write_buildflag_header.py":
1721    return WriteBuildFlagHeaderSanitizer(target, arch)
1722  elif target.script == "//base/write_build_date_header.py":
1723    return WriteBuildDateHeaderSanitizer(target, arch)
1724  elif target.script == "//build/util/version.py":
1725    return VersionSanitizer(target, arch)
1726  elif target.script == "//build/android/gyp/java_cpp_enum.py":
1727    return JavaCppEnumSanitizer(target, arch)
1728  elif target.script == "//net/tools/dafsa/make_dafsa.py":
1729    return MakeDafsaSanitizer(target, arch)
1730  elif target.script == '//build/android/gyp/java_cpp_features.py':
1731    return JavaCppFeatureSanitizer(target, arch)
1732  elif target.script == '//build/android/gyp/java_cpp_strings.py':
1733    return JavaCppStringSanitizer(target, arch)
1734  elif target.script == '//build/android/gyp/write_native_libraries_java.py':
1735    return WriteNativeLibrariesJavaSanitizer(target, arch)
1736  elif target.script == '//build/gn_run_binary.py':
1737    return GnRunBinarySanitizer(target, arch)
1738  elif target.script == '//build/protoc_java.py':
1739    return ProtocJavaSanitizer(target, arch, gn)
1740  elif target.script == '//third_party/jni_zero/jni_zero.py':
1741    if target.args[0] == 'generate-final':
1742      if type == 'java_genrule':
1743        # Fill up the sources of the target for JniRegistrationGenerator
1744        # actions with all the java sources found under targets of type
1745        # `generate_jni`. Note 1: Only do this for the java part in order to
1746        # generate a complete GEN_JNI. The C++ part MUST only include java
1747        # source files that are listed explicitly in `generate_jni` targets
1748        # in the transitive dependency, this is handled inside the action
1749        # sanitizer itself (See `get_srcs`). Adding java sources that are not
1750        # listed to the C++ version of JniRegistrationGenerator will result
1751        # in undefined symbols as the C++ part generates declarations that
1752        # would have no definitions. Note 2: This is only done for the
1753        # testing targets because their JniRegistration is not complete,
1754        # Chromium generates Jni files for testing targets implicitly (See
1755        # https://source.chromium.org/chromium/chromium/src/+/main:testing
1756        # /test.gni;l=422;bpv=1;bpt=0;drc
1757        # =02820c1b362c3a00f426d7c4eab18703d89cda03) to avoid having to
1758        # replicate the same setup, just fill up the java JniRegistration
1759        # with all  java sources found under `generate_jni` targets and fill
1760        # the C++ version with the exact files.
1761        if is_test_target:
1762          target.sources.update(gn.jni_java_sources)
1763        return JavaJniRegistrationGeneratorSanitizer(target, arch, is_test_target)
1764      else:
1765        return JniRegistrationGeneratorSanitizer(target, arch, is_test_target)
1766    else:
1767      if type == 'cc_genrule':
1768        return JniGeneratorSanitizer(target, arch, is_test_target)
1769      else:
1770        return JavaJniGeneratorSanitizer(target, arch, is_test_target)
1771  else:
1772    raise Exception('Unsupported action %s from %s' % (target.script, target.name))
1773
1774def create_action_foreach_modules(blueprint, gn, target, is_test_target):
1775  """ The following assumes that rebase_path exists in the args.
1776  The args of an action_foreach contains hints about which output files are generated
1777  by which source files.
1778  This is copied directly from the args
1779  "gen/net/base/registry_controlled_domains/{{source_name_part}}-reversed-inc.cc"
1780  So each source file will generate an output whose name is the {source_name-reversed-inc.cc}
1781  """
1782  new_args = []
1783  for i, src in enumerate(sorted(target.sources)):
1784    # don't add script arg for the first source -- create_action_module
1785    # already does this.
1786    if i != 0:
1787      new_args.append('&&')
1788      new_args.append('python3 $(location %s)' %
1789                   gn_utils.label_to_path(target.script))
1790    for arg in target.args:
1791      if '{{source}}' in arg:
1792        new_args.append('$(location %s)' % (gn_utils.label_to_path(src)))
1793      elif '{{source_name_part}}' in arg:
1794        source_name_part = src.split("/")[-1] # Get the file name only
1795        source_name_part = source_name_part.split(".")[0] # Remove the extension (Ex: .cc)
1796        file_name = arg.replace('{{source_name_part}}', source_name_part).split("/")[-1]
1797        # file_name represent the output file name. But we need the whole path
1798        # This can be found from target.outputs.
1799        for out in target.outputs:
1800          if out.endswith(file_name):
1801            new_args.append('$(location %s)' % out)
1802
1803        for file in (target.sources | target.inputs):
1804          if file.endswith(file_name):
1805            new_args.append('$(location %s)' % gn_utils.label_to_path(file))
1806      else:
1807        new_args.append(arg)
1808
1809  target.args = new_args
1810  return create_action_module(blueprint, gn, target, 'cc_genrule', is_test_target)
1811
1812def create_action_module_internal(gn, target, type, is_test_target, blueprint, arch=None):
1813  if target.script == '//build/android/gyp/gcc_preprocess.py':
1814    return create_gcc_preprocess_modules(blueprint, target)
1815  sanitizer = get_action_sanitizer(gn, target, type, arch, is_test_target)
1816  sanitizer.sanitize()
1817
1818  module = Module(type, sanitizer.get_name(), target.name)
1819  module.cmd = sanitizer.get_cmd()
1820  module.out = sanitizer.get_outputs()
1821  if sanitizer.is_header_generated():
1822    module.genrule_headers.add(module.name)
1823  module.srcs = sanitizer.get_srcs()
1824  module.tool_files = sanitizer.get_tool_files()
1825  module.tools = sanitizer.get_tools()
1826  target.deps = sanitizer.get_deps()
1827
1828  return module
1829
1830def get_cmd_condition(arch):
1831  '''
1832  :param arch: archtecture name e.g. android_x86_64, android_arm64
1833  :return: condition that can be used in cc_genrule cmd to switch the behavior based on arch
1834  '''
1835  if arch == "android_x86_64":
1836    return "( $$CC_ARCH == 'x86_64' && $$CC_OS == 'android' )"
1837  elif arch == "android_x86":
1838    return "( $$CC_ARCH == 'x86' && $$CC_OS == 'android' )"
1839  elif arch == "android_arm":
1840    return "( $$CC_ARCH == 'arm' && $$CC_OS == 'android' )"
1841  elif arch == "android_arm64":
1842    return "( $$CC_ARCH == 'arm64' && $$CC_OS == 'android' )"
1843  elif arch == "android_riscv64":
1844    return "( $$CC_ARCH == 'riscv64' && $$CC_OS == 'android' )"
1845  elif arch == "host":
1846    return "$$CC_OS != 'android'"
1847  else:
1848    raise Exception(f'Unknown architecture type {arch}')
1849
1850def merge_cmd(modules, genrule_type):
1851  '''
1852  :param modules: dictionary whose key is arch name and value is module
1853  :param genrule_type: cc_genrule or java_genrule
1854  :return: merged command or common command if all the archs have the same command.
1855  '''
1856  commands = list({module.cmd for module in modules.values()})
1857  if len(commands) == 1:
1858    # If all the archs have the same command, return the command
1859    return commands[0]
1860
1861  if genrule_type != 'cc_genrule':
1862    raise Exception(f'{genrule_type} can not have different cmd between archs')
1863
1864  merged_cmd = []
1865  for arch, module in sorted(modules.items()):
1866    merged_cmd.append(f'if [[ {get_cmd_condition(arch)} ]];')
1867    merged_cmd.append('then')
1868    merged_cmd.append(module.cmd + ';')
1869    merged_cmd.append('fi;')
1870  return NEWLINE.join(merged_cmd)
1871
1872def merge_modules(modules, genrule_type):
1873  '''
1874  :param modules: dictionary whose key is arch name and value is module
1875  :param genrule_type: cc_genrule or java_genrule
1876  :return: merged module of input modules
1877  '''
1878  merged_module = list(modules.values())[0]
1879
1880  # Following attributes must be the same between archs
1881  for key in ('genrule_headers', 'srcs', 'tool_files'):
1882    if any([getattr(merged_module, key) != getattr(module, key) for module in modules.values()]):
1883      raise Exception(f'{merged_module.name} has different values for {key} between archs')
1884
1885  merged_module.cmd = merge_cmd(modules, genrule_type)
1886  return merged_module
1887
1888def create_action_module(blueprint, gn, target, genrule_type, is_test_target):
1889  '''
1890  Create module for action target and add to the blueprint. If target has arch specific attributes
1891  this function merge them and create a single module.
1892  :param blueprint:
1893  :param target: target which is converted to the module.
1894  :param genrule_type: cc_genrule or java_genrule
1895  :return: created module
1896  '''
1897  # TODO: Handle this target correctly, this target generates java_genrule but this target has
1898  # different value for cpu-family arg between archs
1899  if re.match('//build/android:native_libraries_gen(__testing)?$', target.name):
1900    module = create_action_module_internal(gn, target, genrule_type,
1901                                           is_test_target, blueprint,
1902                                           target.arch['android_arm'])
1903    blueprint.add_module(module)
1904    return module
1905
1906  modules = {arch_name: create_action_module_internal(gn, target, genrule_type,
1907                                                      is_test_target, blueprint, arch)
1908             for arch_name, arch in target.get_archs().items()}
1909  module = merge_modules(modules, genrule_type)
1910  blueprint.add_module(module)
1911  return module
1912
1913
1914def _get_cflags(cflags, defines):
1915  cflags = {flag for flag in cflags if flag in cflag_allowlist}
1916  # Consider proper allowlist or denylist if needed
1917  cflags |= set("-D%s" % define.replace("\"", "\\\"") for define in defines)
1918  return cflags
1919
1920def _set_linker_script(module, libs):
1921  for lib in libs:
1922    if lib.endswith(".lds"):
1923      module.ldflags.add(get_linker_script_ldflag(gn_utils.label_to_path(lib)))
1924
1925def set_module_flags(module, module_type, cflags, defines, ldflags, libs):
1926  module.cflags.update(_get_cflags(cflags, defines))
1927  module.ldflags.update({flag for flag in ldflags
1928          if flag in ldflag_allowlist or flag.startswith("-Wl,-wrap,")})
1929  _set_linker_script(module, libs)
1930  # TODO: implement proper cflag parsing.
1931  for flag in cflags:
1932    if '-std=' in flag:
1933      module.cpp_std = flag[len('-std='):]
1934    if '-fexceptions' in flag:
1935      module.cppflags.add('-fexceptions')
1936
1937def set_module_include_dirs(module, cflags, include_dirs):
1938  for flag in cflags:
1939    if '-isystem' in flag:
1940      module.include_dirs.add(f"external/cronet/{flag[len('-isystem../../'):]}")
1941
1942  # Adding include_dirs is necessary due to source_sets / filegroups
1943  # which do not properly propagate include directories.
1944  # Filter any directory inside //out as a) this directory does not exist for
1945  # aosp / soong builds and b) the include directory should already be
1946  # configured via library dependency.
1947  # Note: include_dirs is used instead of local_include_dirs as an Android.bp
1948  # can't access other directories outside of its current directory. This
1949  # is worked around by using include_dirs.
1950  module.include_dirs.update([f"external/cronet/{gn_utils.label_to_path(d)}"
1951                                 for d in include_dirs if not d.startswith('//out')])
1952  # Remove prohibited include directories
1953  module.include_dirs = [d for d in module.include_dirs
1954                               if d not in include_dirs_denylist]
1955
1956
1957def create_modules_from_target(blueprint, gn, gn_target_name, parent_gn_type, is_test_target):
1958  """Generate module(s) for a given GN target.
1959
1960    Given a GN target name, generate one or more corresponding modules into a
1961    blueprint. The only case when this generates >1 module is proto libraries.
1962
1963    Args:
1964        blueprint: Blueprint instance which is being generated.
1965        gn: gn_utils.GnParser object.
1966        gn_target_name: GN target for module generation.
1967        parent_gn_type: GN type of the parent node.
1968    """
1969  bp_module_name = label_to_module_name(gn_target_name)
1970  target = gn.get_target(gn_target_name)
1971
1972  # Append __java suffix to actions reachable from java_library. This is necessary
1973  # to differentiate them from cc actions.
1974  # This means that a GN action of name X will be translated to two different modules of names
1975  # X and X__java(only if X is reachable from a java target).
1976  if target.type == "action" and parent_gn_type == "java_library":
1977    bp_module_name += "__java"
1978
1979  if target.type in ["rust_library", "rust_proc_macro"]:
1980    # "lib{crate_name}" must be a prefix of the module name, this is a Soong
1981    # restriction.
1982    # https://cs.android.com/android/_/android/platform/build/soong/+/31934a55a8a1f9e4d56d68810f4a646f12ab6eb5:rust/library.go;l=724;drc=fdec8723d574daf54b956cc0f6dc879087da70a6;bpv=0;bpt=0
1983    if len(target.crate_name) > 50:
1984      # It is unreasonable to have such a crate name, this usually happens when
1985      # the name of the crate is equal to the target path, this is the default
1986      # for Chromium when a crate_name is not declared. Since we are prepending
1987      # the crate name to the name, let's cut it short.
1988      # There is a length limit on the module name because module names
1989      # are used to create files and there is a limit on file names.
1990      target.crate_name = target.crate_name[:50] + "__TRIMMED"
1991    bp_module_name = f"lib{target.crate_name}_{bp_module_name}"
1992
1993    if parent_gn_type == "static_library":
1994      # CC modules must depend on a different type of modules that are
1995      # rust_ffi_static instead of rust_library_rlib
1996      bp_module_name += "__FFI"
1997
1998  # There is a limit on the length of the module name as the output depends
1999  # on that.
2000  if len(bp_module_name) > 128:
2001    bp_module_name = bp_module_name[:128] + "__TRIMMED"
2002  if bp_module_name in blueprint.modules:
2003    return blueprint.modules[bp_module_name]
2004
2005  log.info('create modules for %s (%s)', target.name, target.type)
2006
2007  if target.type == 'executable':
2008    if target.testonly:
2009      module_type = 'cc_test'
2010    else:
2011      # Can be used for both host and device targets.
2012      module_type = 'cc_binary'
2013    module = Module(module_type, bp_module_name, gn_target_name)
2014  elif target.type == 'rust_executable':
2015    module = Module("rust_binary", bp_module_name, gn_target_name)
2016  elif target.type == "rust_library":
2017    _type = "rust_library_rlib"
2018    if parent_gn_type == "static_library":
2019      # CPP modules must depend on rust_ffi_static as this generates the
2020      # necessary static library that can be linked.
2021      _type = "rust_ffi_static"
2022    # Chromium only uses rlibs.
2023    module = Module(_type, bp_module_name, gn_target_name)
2024  elif target.type == "rust_proc_macro":
2025    module = Module("rust_proc_macro", bp_module_name, gn_target_name)
2026  elif target.type in ['static_library', 'source_set']:
2027    module = Module('cc_library_static', bp_module_name, gn_target_name)
2028  elif target.type == 'shared_library':
2029    module = Module('cc_library_shared', bp_module_name, gn_target_name)
2030  elif target.type == 'group':
2031    # "group" targets are resolved recursively by gn_utils.get_target().
2032    # There's nothing we need to do at this level for them.
2033    return None
2034  elif target.type == 'proto_library':
2035    module = create_proto_modules(blueprint, gn, target)
2036    if module is None:
2037      return None
2038  elif target.type == 'action':
2039    module = create_action_module(blueprint, gn, target, 'java_genrule' if parent_gn_type == "java_library" else 'cc_genrule', is_test_target)
2040  elif target.type == 'action_foreach':
2041    if target.script == "//third_party/rust/cxx/chromium_integration/run_cxxbridge.py":
2042      module = create_rust_cxx_module(blueprint, target)
2043    else:
2044      module = create_action_foreach_modules(blueprint, gn, target, is_test_target)
2045  elif target.type == 'copy':
2046    # TODO: careful now! copy targets are not supported yet, but this will stop
2047    # traversing the dependency tree. For //base:base, this is not a big
2048    # problem as libicu contains the only copy target which happens to be a
2049    # leaf node.
2050    return None
2051  elif target.type == 'java_library':
2052    if target.jar_path:
2053      module = Module('java_import', bp_module_name, gn_target_name)
2054      module.jars.add(target.jar_path)
2055    else:
2056      module = Module('java_library', bp_module_name, gn_target_name)
2057      # Don't remove GEN_JNI from those modules as they have the real GEN_JNI that we want to include
2058      if gn_target_name not in ['//components/cronet/android:cronet_jni_registration_java',
2059                                '//components/cronet/android:cronet_jni_registration_java__testing',
2060                                '//components/cronet/android:cronet_tests_jni_registration_java__testing']:
2061          module.jarjar_rules = REMOVE_GEN_JNI_JARJAR_RULES_FILE
2062    module.min_sdk_version = 30
2063    module.apex_available = [tethering_apex]
2064    if is_test_target:
2065        module.sdk_version = target.sdk_version
2066    else:
2067        module.defaults.add(java_framework_defaults_module)
2068  else:
2069    raise Exception('Unknown target %s (%s)' % (target.name, target.type))
2070
2071  blueprint.add_module(module)
2072  if target.type not in ['action', 'action_foreach']:
2073      # Actions should get their srcs from their corresponding ActionSanitizer as actionSanitizer
2074      # filters srcs differently according to the type of the action.
2075      module.srcs.update(gn_utils.label_to_path(src)
2076                         for src in target.sources if is_supported_source_file(src))
2077
2078  # Add arch-specific properties
2079  for arch_name, arch in target.get_archs().items():
2080    module.target[arch_name].srcs.update(gn_utils.label_to_path(src)
2081                                         for src in arch.sources if is_supported_source_file(src))
2082
2083  module.rtti = target.rtti
2084
2085  if target.type in gn_utils.LINKER_UNIT_TYPES:
2086    set_module_flags(module, module.type, target.cflags, target.defines, target.ldflags, target.libs)
2087    set_module_include_dirs(module, target.cflags, target.include_dirs)
2088    # TODO: set_module_xxx is confusing, apply similar function to module and target in better way.
2089    for arch_name, arch in target.get_archs().items():
2090      # TODO(aymanm): Make libs arch-specific.
2091      set_module_flags(module.target[arch_name], module.type,
2092                       arch.cflags, arch.defines, arch.ldflags, [])
2093      # -Xclang -target-feature -Xclang +mte are used to enable MTE (Memory Tagging Extensions).
2094      # Flags which does not start with '-' could not be in the cflags so enabling MTE by
2095      # -march and -mcpu Feature Modifiers. MTE is only available on arm64. This is needed for
2096      # building //base/allocator/partition_allocator:partition_alloc for arm64.
2097      if '+mte' in arch.cflags and arch_name == 'android_arm64':
2098        module.target[arch_name].cflags.add('-march=armv8-a+memtag')
2099      set_module_include_dirs(module.target[arch_name], arch.cflags, arch.include_dirs)
2100
2101  if not module.type == "rust_proc_macro":
2102    # rust_proc_macro modules does not support the fields of `host_supported`
2103    # or `device_supported`. In a different world, we would have classes for
2104    # each different module that specifies what it can support to avoid
2105    # those kind of conditions.
2106    #
2107    # See go/android.bp for additional information.
2108    module.host_supported = target.host_supported()
2109    module.device_supported = target.device_supported()
2110
2111  module.gn_type = target.type
2112  module.build_file_path = target.build_file_path
2113  # Chromium does not use visibility at all, in order to avoid visibility issues
2114  # in AOSP. Make every module visible to any module in external/cronet.
2115  module.visibility = {"//external/cronet:__subpackages__"}
2116
2117  if module.type.startswith("rust"):
2118    module.crate_name = target.crate_name
2119    module.crate_root = gn_utils.label_to_path(target.crate_root)
2120    module.min_sdk_version = 30
2121    module.apex_available = [tethering_apex]
2122    for arch_name, arch in target.get_archs().items():
2123      _set_rust_flags(module.target[arch_name], arch.rust_flags, arch_name)
2124
2125  if module.is_genrule():
2126    module.apex_available.add(tethering_apex)
2127
2128  if module.type == "java_library":
2129    if gn_utils.contains_aidl(target.sources):
2130      # frameworks/base/core/java includes the source files that are used to compile framework.aidl.
2131      # framework.aidl is added implicitly as a dependency to every AIDL GN action, this can be
2132      # identified by third_party/android_sdk/public/platforms/android-34/framework.aidl.
2133      module.aidl["include_dirs"] = {"frameworks/base/core/java/"}
2134      module.aidl["local_include_dirs"] = target.local_aidl_includes
2135
2136  if (module.is_compiled() and not module.type.startswith("java")
2137      and not module.type.startswith("rust")):
2138    # Don't try to inject library/source dependencies into genrules or
2139    # filegroups because they are not compiled in the traditional sense.
2140    module.defaults = [cc_defaults_module]
2141    for lib in target.libs:
2142      # Generally library names should be mangled as 'libXXX', unless they
2143      # are HAL libraries (e.g., [email protected]) or AIDL c++ / NDK
2144      # libraries (e.g. "android.hardware.power.stats-V1-cpp")
2145      android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \
2146        else 'lib' + lib
2147      if lib in shared_library_allowlist:
2148        module.add_android_shared_lib(android_lib)
2149
2150  # If the module is a static library, export all the generated headers.
2151  if module.type == 'cc_library_static':
2152    module.export_generated_headers = module.generated_headers
2153
2154  if module.name in ['cronet_aml_components_cronet_android_cronet',
2155                     'cronet_aml_components_cronet_android_cronet' + gn_utils.TESTING_SUFFIX]:
2156    if target.output_name is None:
2157      raise Exception('Failed to get output_name for libcronet name')
2158    # .so file name needs to match with CronetLibraryLoader.java (e.g. libcronet.109.0.5386.0.so)
2159    # So setting the output name based on the output_name from the desc.json
2160    module.stem = 'libmainline' + target.output_name
2161  elif module.is_test() and module.type == 'cc_library_shared':
2162    if target.output_name:
2163      # If we have an output name already declared, use it.
2164      module.stem = 'lib' + target.output_name
2165    else:
2166      # Tests output should be a shared library in the format of 'lib[module_name]'
2167      module.stem = 'lib' + target.get_target_name()[
2168                            :target.get_target_name().find(gn_utils.TESTING_SUFFIX)]
2169
2170  # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)).
2171  all_deps = [(dep_name, 'common') for dep_name in target.proto_deps]
2172  for arch_name, arch in target.arch.items():
2173    all_deps += [(dep_name, arch_name) for dep_name in arch.deps]
2174
2175  # Sort deps before iteration to make result deterministic.
2176  for (dep_name, arch_name) in sorted(all_deps):
2177    module_target = module.target[arch_name] if arch_name != 'common' else module
2178    # |builtin_deps| override GN deps with Android-specific ones. See the
2179    # config in the top of this file.
2180    if dep_name in builtin_deps:
2181      builtin_deps[dep_name](module, arch_name)
2182      continue
2183
2184    # This is like the builtin_deps with always_disable except that it matches
2185    # a string.
2186    if "_build_script" in dep_name:
2187      continue
2188
2189    dep_module = create_modules_from_target(blueprint, gn, dep_name, target.type, is_test_target)
2190
2191    if dep_module is None:
2192      continue
2193
2194    # TODO: Proper dependency check for genrule.
2195    # Currently, only propagating genrule dependencies.
2196    # Also, currently, all the dependencies are propagated upwards.
2197    # in gn, public_deps should be propagated but deps should not.
2198    # Not sure this information is available in the desc.json.
2199    # Following rule works for adding android_runtime_jni_headers to base:base.
2200    # If this doesn't work for other target, hardcoding for specific target
2201    # might be better.
2202    if module.is_genrule() and dep_module.is_genrule():
2203      if module_target.gn_type != "proto_library":
2204        # proto_library are treated differently because each proto action
2205        # is split into two different targets, a cpp target and a header target.
2206        # the cpp target is used as the entry point to the proto action, hence
2207        # it should not be propagated as a genrule header because it generates
2208        # cpp files only.
2209        module_target.genrule_headers.add(dep_module.name)
2210      module_target.genrule_headers.update(dep_module.genrule_headers)
2211
2212    # For filegroups, and genrule, recurse but don't apply the
2213    # deps.
2214    if not module.is_compiled() or module.is_genrule():
2215      continue
2216
2217    # Drop compiled modules that doesn't provide any benefit. This is mostly
2218    # applicable to source_sets when converted to cc_static_library, sometimes
2219    # the source set only has header files which are dropped so the module becomes empty.
2220    # is_compiled is there to prevent dropping of genrules.
2221    if dep_module.is_compiled() and not dep_module.has_input_files():
2222      continue
2223
2224    if dep_module.type == 'cc_library_shared':
2225      module_target.shared_libs.add(dep_module.name)
2226    elif dep_module.type == 'cc_library_static':
2227      if module.type in ['cc_library_shared', 'cc_binary']:
2228        module_target.whole_static_libs.add(dep_module.name)
2229      elif module.type == 'cc_library_static':
2230        module_target.generated_headers.update(dep_module.generated_headers)
2231        module_target.shared_libs.update(dep_module.shared_libs)
2232        module_target.header_libs.update(dep_module.header_libs)
2233      elif module.type in ["rust_library_rlib", "rust_binary"]:
2234        module_target.static_libs.add(dep_module.name)
2235      else:
2236        raise Exception(f"Trying to add an unknown type {dep_module.type} to a type of {module.type}")
2237    elif dep_module.type == "rust_library_rlib":
2238        module_target.rustlibs.add(dep_module.name)
2239    elif dep_module.type == "rust_ffi_static":
2240        assert module.type == "cc_library_static", "Only static CC libraries can depend on rust_ffi_static"
2241        # CPP libraries must not depend on rust_library_rlib, they must depend
2242        # on rust_ffi_rlib as per aosp/3094614 and go/android-made-to-order-rust-staticlibs.
2243        module_target.static_libs.add(dep_module.name)
2244    elif dep_module.type == "rust_proc_macro":
2245      module_target.proc_macros.add(dep_module.name)
2246    elif dep_module.type == 'cc_genrule':
2247      module_target.generated_headers.update(dep_module.genrule_headers)
2248      module_target.srcs.update(dep_module.genrule_srcs)
2249      module_target.shared_libs.update(dep_module.genrule_shared_libs)
2250      module_target.header_libs.update(dep_module.genrule_header_libs)
2251    elif dep_module.type in ['java_library', 'java_import']:
2252      # A module depending on a module with system_current sdk version should also compile against
2253      # the system sdk. This is because a module's SDK API surface should be >= its deps SDK API surface.
2254      # And system_current has a larger API surface than current or module_current.
2255      if dep_module.sdk_version == 'system_current':
2256        module.sdk_version = 'system_current'
2257      if module.type not in ["cc_library_static"]:
2258        # This is needed to go around the case where `url` component depends
2259        # on `url_java`.
2260        # TODO(aymanm): Remove the if condition once crrev/4902547 has been imported downstream
2261        module_target.static_libs.add(dep_module.name)
2262    elif dep_module.type in ['genrule', 'java_genrule']:
2263      module_target.srcs.add(":" + dep_module.name)
2264    else:
2265      raise Exception('Unsupported arch-specific dependency %s of target %s with type %s' %
2266                      (dep_module.name, target.name, dep_module.type))
2267  return module
2268
2269def turn_off_allocator_shim_for_musl(module):
2270  allocation_shim = "base/allocator/partition_allocator/shim/allocator_shim.cc"
2271  allocator_shim_files = {
2272    allocation_shim,
2273    "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc",
2274  }
2275  module.srcs -= allocator_shim_files
2276  for arch in module.target.values():
2277    arch.srcs -= allocator_shim_files
2278  module.target['android'].srcs.add(allocation_shim)
2279  if gn_utils.TESTING_SUFFIX in module.name:
2280    # allocator_shim_default_dispatch_to_glibc is only added to the __testing version of base
2281    # since base_base__testing is compiled for host. When compiling for host. Soong compiles
2282    # using glibc or musl(experimental). We currently only support compiling for glibc.
2283    module.target['glibc'].srcs.update(allocator_shim_files)
2284  else:
2285    # allocator_shim_default_dispatch_to_glibc does not exist in the prod version of base
2286    # `base_base` since this only compiles for android and bionic is used. Bionic is the equivalent
2287    # of glibc but for android.
2288    module.target['glibc'].srcs.add(allocation_shim)
2289
2290def create_cc_defaults_module():
2291    defaults = Module('cc_defaults', cc_defaults_module, '//gn:default_deps')
2292    defaults.cflags = [
2293        '-DGOOGLE_PROTOBUF_NO_RTTI',
2294        '-DBORINGSSL_SHARED_LIBRARY',
2295        '-Wno-error=return-type',
2296        '-Wno-non-virtual-dtor',
2297        '-Wno-macro-redefined',
2298        '-Wno-missing-field-initializers',
2299        '-Wno-sign-compare',
2300        '-Wno-sign-promo',
2301        '-Wno-unused-parameter',
2302        '-Wno-null-pointer-subtraction', # Needed to libevent
2303        '-Wno-ambiguous-reversed-operator', # needed for icui18n
2304        '-Wno-unreachable-code-loop-increment', # needed for icui18n
2305        '-fPIC',
2306        '-Wno-c++11-narrowing',
2307        # Needed for address_space_randomization.h on riscv
2308        # Can be removed after 125.0.6375.0 is imported
2309        '-Wno-invalid-constexpr',
2310        # b/330508686 disable coverage profiling for files or function in this list.
2311        '-fprofile-list=external/cronet/exclude_coverage.list',
2312    ]
2313    defaults.build_file_path = ""
2314    defaults.include_build_directory = False
2315    defaults.c_std = 'gnu11'
2316    # Chromium builds do not add a dependency for headers found inside the
2317    # sysroot, so they are added globally via defaults.
2318    defaults.target['android'].header_libs = [
2319        'jni_headers',
2320    ]
2321    defaults.target['android'].shared_libs = [
2322        'libmediandk'
2323    ]
2324    defaults.target['host'].cflags = [
2325        # -DANDROID is added by default but target.defines contain -DANDROID if
2326        # it's required.  So adding -UANDROID to cancel default -DANDROID if it's
2327        # not specified.
2328        # Note: -DANDROID is not consistently applied across the chromium code
2329        # base, so it is removed unconditionally for host targets.
2330        '-UANDROID',
2331    ]
2332    defaults.stl = 'none'
2333    defaults.cpp_std = 'c++17'
2334    defaults.min_sdk_version = 29
2335    defaults.apex_available.add(tethering_apex)
2336    return defaults
2337
2338def create_blueprint_for_targets(gn, targets, test_targets):
2339  """Generate a blueprint for a list of GN targets."""
2340  blueprint = Blueprint()
2341
2342  # Default settings used by all modules.
2343  blueprint.add_module(create_cc_defaults_module())
2344
2345  for target in targets:
2346    module = create_modules_from_target(blueprint, gn, target, parent_gn_type=None, is_test_target=False)
2347    if module:
2348      module.visibility.update(root_modules_visibility)
2349
2350  for test_target in test_targets:
2351    module = create_modules_from_target(blueprint, gn, test_target + gn_utils.TESTING_SUFFIX, parent_gn_type=None, is_test_target=True)
2352    if module:
2353      module.visibility.update(root_modules_visibility)
2354
2355  # Merge in additional hardcoded arguments.
2356  for module in blueprint.modules.values():
2357    for key, add_val in additional_args.get(module.name, []):
2358      curr = getattr(module, key)
2359      if add_val and isinstance(add_val, set) and isinstance(curr, set):
2360        curr.update(add_val)
2361      elif isinstance(add_val, str) and (not curr or isinstance(curr, str)):
2362        setattr(module, key, add_val)
2363      elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)):
2364        setattr(module, key, add_val)
2365      elif isinstance(add_val, dict) and isinstance(curr, dict):
2366        curr.update(add_val)
2367      elif isinstance(add_val[1], dict) and isinstance(curr[add_val[0]], Module.Target):
2368        curr[add_val[0]].__dict__.update(add_val[1])
2369      elif isinstance(curr, dict):
2370        curr[add_val[0]] = add_val[1]
2371      else:
2372        raise Exception('Unimplemented type %r of additional_args: %r' % (type(add_val), key))
2373
2374  return blueprint
2375
2376def _rebase_file(filepath: str, blueprint_path: str) -> str | None:
2377  """
2378  Rebases a single file, this method delegates to _rebase_files
2379
2380  :param filepath: a single string representing filepath.
2381  :param blueprint_path: Path for which the srcs will be rebased relative to.
2382  :returns The rebased filepaths or None.
2383  """
2384  rebased_file = _rebase_files([filepath], blueprint_path)
2385  if rebased_file:
2386    return list(rebased_file)[0]
2387  return None
2388
2389def _rebase_files(filepaths, parent_prefix):
2390  """
2391  Rebase a list of filepaths according to the provided path. This assumes
2392  that the |filepaths| are subdirectories of the |parent|.
2393  If the assumption is violated then None is returned.
2394
2395  Note: filepath can be references to other modules (eg: ":module"), those
2396  are added as-is without any translation.
2397
2398  :param filepaths: Collection of strings representing filepaths.
2399  :param parent_prefix: Path for which the srcs will be rebased relative to.
2400  :returns The rebased filepaths or None.
2401  """
2402  if not parent_prefix:
2403    return filepaths
2404
2405  rebased_srcs = set()
2406  for src in filepaths:
2407    if src.startswith(":"):
2408      # This is a reference to another Android.bp module, add as-is.
2409      rebased_srcs.add(src)
2410      continue
2411
2412    if not src.startswith(parent_prefix):
2413      # This module depends on a source file that is not in its subpackage.
2414      return None
2415    # Remove the BUILD file path to make it relative.
2416    rebased_srcs.add(src[len(parent_prefix) + 1:])
2417  return rebased_srcs
2418
2419
2420# TODO: Move to Module's class.
2421def _rebase_module(module: Module, blueprint_path: str) -> Module | None:
2422  """
2423  Rebases the module specified on top of the blueprint_path if possible.
2424  If the rebase operation has failed, None is returned to indicate that the
2425  module should stay as a top-level module.
2426
2427  Currently, there is no support for rebasing genrules and libraries that
2428  breaks the package boundaries.
2429
2430  :returns A new module based on the provided one but rebased or None.
2431  """
2432
2433  module_copy = copy.deepcopy(module)
2434  # TODO: Find a better way to rebase attribute and verify if all rebase operations
2435  # have succeeded or not.
2436  if module_copy.crate_root:
2437    module_copy.crate_root = _rebase_file(module_copy.crate_root,
2438                                           blueprint_path)
2439    if module_copy.crate_root is None:
2440      return None
2441
2442  if module_copy.srcs:
2443    module_copy.srcs = _rebase_files(module_copy.srcs, blueprint_path)
2444    if module_copy.srcs is None:
2445      return None
2446
2447  if module_copy.jars:
2448    module_copy.jars = _rebase_files(module_copy.jars, blueprint_path)
2449    if module_copy.jars is None:
2450      return None
2451
2452  for (arch_name, arch) in module_copy.target.items():
2453    module_copy.target[arch_name].srcs = (
2454        _rebase_files(module_copy.target[arch_name].srcs, blueprint_path))
2455    if module_copy.target[arch_name].srcs is None:
2456      return None
2457
2458  return module_copy
2459
2460def _path_to_name(path: str) -> str:
2461  return "external_cronet_%s_license" % (path.replace("/", "_").lower())
2462
2463def _maybe_create_license_module(path: str) -> Module | None:
2464  """
2465  Creates a module license if a README.chromium exists in the path provided
2466  otherwise just returns None.
2467
2468  :param path: Path to check for README.chromium
2469  :return: Module or None.
2470  """
2471  readme_chromium_file = Path(os.path.join(path, "README.chromium"))
2472  if (not readme_chromium_file.exists() or
2473      license_utils.is_ignored_readme_chromium(str(readme_chromium_file))):
2474    return None
2475
2476  license_module = Module("license", _path_to_name(path), "License-Artificial")
2477  license_module.visibility = {":__subpackages__"}
2478  # Assume that a LICENSE file always exist as we run the
2479  # create_android_metadata_license.py script each time we run GN2BP.
2480  license_module.license_text = {"LICENSE"}
2481  metadata = license_utils.parse_chromium_readme_file(
2482      str(readme_chromium_file))
2483  for license in metadata.get_licenses():
2484      license_module.license_kinds.add(license_utils.get_license_bp_name(license))
2485  return license_module
2486
2487
2488def _get_longest_matching_blueprint(current_blueprint_path: str,
2489    all_blueprints: Dict[str, Blueprint]) -> Blueprint | None:
2490  longest_path_matching = None
2491  for (blueprint_path, search_blueprint) in all_blueprints.items():
2492    if (search_blueprint.get_license_module()
2493        and current_blueprint_path.startswith(blueprint_path)
2494        and (longest_path_matching is None or len(blueprint_path) > len(
2495            longest_path_matching))):
2496      longest_path_matching = blueprint_path
2497
2498  if longest_path_matching:
2499    return all_blueprints[longest_path_matching]
2500  return None
2501
2502def finalize_package_modules(blueprints: Dict[str, Blueprint]):
2503  """
2504  Adds a package module to every blueprint passed in |blueprints|. A package
2505  module is just a reference to a license module, the approach here is that
2506  the package module will point to the nearest ancestor's license module, the
2507  nearest ancestor could be in the same Android.bp.
2508
2509  :param blueprints: Dictionary of (path, blueprint) to be populated with
2510  """
2511
2512  for (current_path, blueprint) in blueprints.items():
2513    if current_path == "":
2514      # Don't add a package module for the top-level Android.bp, this is handled
2515      # manually in Android.extras.bp.
2516      continue
2517
2518    package_module = Module("package", None, "Package-Artificial")
2519    if blueprint.get_license_module():
2520      package_module.default_applicable_licenses.add(
2521          blueprint.get_license_module().name)
2522    else:  # Search for closest ancestor with a license module
2523      ancestor_blueprint = _get_longest_matching_blueprint(current_path,
2524                                                           blueprints)
2525      if ancestor_blueprint:
2526        # We found an ancestor, make a reference to its license module
2527        package_module.default_applicable_licenses.add(
2528          ancestor_blueprint.get_license_module().name)
2529      else:
2530        # No ancestor with a license found, this is most likely a non-third
2531        # license, just point at Chromium's license in Android.extras.bp.
2532        package_module.default_applicable_licenses.add(
2533          "external_cronet_license")
2534
2535    blueprint.set_package_module(package_module)
2536
2537
2538def create_license_modules(blueprints: Dict[str, Blueprint]) -> Dict[
2539  str, Module]:
2540  """
2541  Creates license module (if possible) for each blueprint passed, a license
2542  module will be created if a README.chromium exists in the same directory as
2543  the BUILD.gn which created that blueprint.
2544
2545  Note: A blueprint can be in a different directory than where the BUILD.gn is
2546  declared, this is the case in rust crates.
2547
2548  :param blueprints: List of paths for all possible blueprints.
2549  :return: Dictionary of (path, license_module).
2550  """
2551  license_modules = {}
2552  for blueprint_path, blueprint in blueprints.items():
2553    if not blueprint.get_readme_location():
2554      # Don't generate a license for the top-level Android.bp as this is handled
2555      # manually in Android.extras.bp
2556      continue
2557
2558    license_module = _maybe_create_license_module(blueprint.get_readme_location())
2559    if license_module:
2560      license_modules[blueprint_path] = license_module
2561  return license_modules
2562
2563
2564def _get_rust_crate_root_directory_from_crate_root(crate_root: str) -> str:
2565  if crate_root and crate_root.startswith(
2566      "third_party/rust/chromium_crates_io/vendor"):
2567    # Return the first 5 directories (a/b/c/d/e)
2568    crate_root_dir = crate_root.split("/")[:5]
2569    return "/".join(crate_root_dir)
2570  return None
2571
2572
2573def _locate_android_bp_destination(module: Module) -> str:
2574  """Returns the appropriate location of the generated Android.bp for the
2575  specified module. Sometimes it is favourable to relocate the Android.bp to
2576  a different location other than next to BUILD.gn (eg: rust's BUILD.gn are
2577  defined in a different directory than the source code).
2578
2579  :returns the appropriate location for the blueprint
2580  """
2581  crate_root_dir = _get_rust_crate_root_directory_from_crate_root(
2582      module.crate_root)
2583  if module.build_file_path in BLUEPRINTS_MAPPING:
2584    return BLUEPRINTS_MAPPING[module.build_file_path]
2585  elif crate_root_dir:
2586    return crate_root_dir
2587  else:
2588    return module.build_file_path
2589
2590def _break_down_blueprint(top_level_blueprint: Blueprint):
2591  """
2592  This breaks down the top-level blueprint into smaller blueprints in
2593  different directory. The goal here is to break down the huge Android.bp
2594  into smaller ones for compliance with SBOM. At the moment, not all targets
2595  can be easily rebased to a different directory as GN does not respect
2596  package boundaries.
2597
2598  :returns A dictionary of path -> Blueprint, the path is relative to repository
2599  root.
2600  """
2601  blueprints = {"": Blueprint()}
2602  for (module_name, module) in top_level_blueprint.modules.items():
2603    if module.type in ["package", "genrule", "cc_genrule", "java_genrule",
2604                       "cc_preprocess_no_configuration"] and not module.allow_rebasing:
2605      # Exclude the genrules from the rebasing as there is no support for them.
2606      # cc_preprocess_no_configuration is created only for the sake of genrules as an intermediate
2607      # target.
2608      blueprints[""].add_module(module)
2609      continue
2610
2611    android_bp_path = _locate_android_bp_destination(module)
2612    if android_bp_path is None:
2613      # Raise an exception if the module does not specify a BUILD file path.
2614      raise Exception(f"Found module {module_name} without a build file path.")
2615
2616    rebased_module = _rebase_module(module, android_bp_path)
2617    if rebased_module:
2618      if not android_bp_path in blueprints.keys():
2619        blueprints[android_bp_path] = Blueprint(module.build_file_path)
2620      blueprints[android_bp_path].add_module(rebased_module)
2621    else:
2622      # Append to the top-level blueprint.
2623      blueprints[""].add_module(module)
2624
2625  for blueprint in blueprints.values():
2626    if blueprint.get_buildgn_location() in README_MAPPING:
2627      blueprint.set_readme_location(README_MAPPING[
2628        blueprint.get_buildgn_location()])
2629  return blueprints
2630
2631def main():
2632  parser = argparse.ArgumentParser(
2633      description='Generate Android.bp from a GN description.')
2634  parser.add_argument(
2635      '--desc',
2636      help='GN description (e.g., gn desc out --format=json --all-toolchains "//*".' +
2637           'You can specify multiple --desc options for different target_cpu',
2638      required=True,
2639      action='append'
2640  )
2641  parser.add_argument(
2642      '--repo_root',
2643      required=True,
2644      help='Path to the root of the repistory'
2645  )
2646  parser.add_argument(
2647      '--build_script_output',
2648      help='JSON-formatted file containing output of build scripts broken down' +
2649        'by architecture.',
2650      required=True
2651  )
2652  parser.add_argument(
2653      '--extras',
2654      help='Extra targets to include at the end of the Blueprint file',
2655      default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'),
2656  )
2657  parser.add_argument(
2658      '--output',
2659      help='Blueprint file to create',
2660      default=os.path.join(gn_utils.repo_root(), 'Android.bp'),
2661  )
2662  parser.add_argument(
2663      '-v',
2664      '--verbose',
2665      help='Print debug logs.',
2666      action='store_true',
2667  )
2668  parser.add_argument(
2669      'targets',
2670      nargs=argparse.REMAINDER,
2671      help='Targets to include in the blueprint (e.g., "//:perfetto_tests")'
2672  )
2673  args = parser.parse_args()
2674
2675  if args.verbose:
2676    log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=log.DEBUG)
2677
2678  targets = args.targets or DEFAULT_TARGETS
2679  build_scripts_output = None
2680  with open(args.build_script_output) as f:
2681    build_scripts_output = json.load(f)
2682  gn = gn_utils.GnParser(builtin_deps, build_scripts_output)
2683  for desc_file in args.desc:
2684    with open(desc_file) as f:
2685      desc = json.load(f)
2686    for target in targets:
2687      gn.parse_gn_desc(desc, target)
2688    for test_target in DEFAULT_TESTS:
2689      gn.parse_gn_desc(desc, test_target, is_test_target=True)
2690  top_level_blueprint = create_blueprint_for_targets(gn, targets, DEFAULT_TESTS)
2691  project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
2692  tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
2693
2694  final_blueprints = _break_down_blueprint(top_level_blueprint)
2695  license_modules = create_license_modules(final_blueprints)
2696  for (path, module) in license_modules.items():
2697    final_blueprints[path].set_license_module(module)
2698
2699  finalize_package_modules(final_blueprints)
2700
2701
2702  header = """// Copyright (C) 2022 The Android Open Source Project
2703//
2704// Licensed under the Apache License, Version 2.0 (the "License");
2705// you may not use this file except in compliance with the License.
2706// You may obtain a copy of the License at
2707//
2708//      http://www.apache.org/licenses/LICENSE-2.0
2709//
2710// Unless required by applicable law or agreed to in writing, software
2711// distributed under the License is distributed on an "AS IS" BASIS,
2712// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2713// See the License for the specific language governing permissions and
2714// limitations under the License.
2715//
2716// This file is automatically generated by %s. Do not edit.
2717""" % (tool_name)
2718
2719  for (path, blueprint) in final_blueprints.items():
2720    android_bp_file = Path(os.path.join(args.repo_root, path, "Android.bp"))
2721    android_bp_file.write_text("\n".join(
2722        [header] + BLUEPRINTS_EXTRAS.get(path, []) + blueprint.to_string()))
2723
2724  return 0
2725
2726
2727if __name__ == '__main__':
2728  sys.exit(main())
2729