#!/usr/bin/env python3
import argparse
import json
import subprocess
import sys
import os

root_libs = [
    '//absl/algorithm:algorithm',
    '//absl/cleanup:cleanup',
    '//absl/container:btree',
    '//absl/container:flat_hash_map',
    '//absl/container:flat_hash_set',
    '//absl/container:node_hash_map',
    '//absl/container:node_hash_set',
    '//absl/debugging:failure_signal_handler',
    '//absl/flags:flag',
    '//absl/flags:parse',
    '//absl/functional:bind_front',
    '//absl/log:absl_check',
    '//absl/log:absl_log',
    '//absl/log:check',
    '//absl/log:die_if_null',
    '//absl/log:initialize',
    '//absl/log:log',
    '//absl/log:scoped_mock_log',
    '//absl/random:bit_gen_ref',
    '//absl/random:random',
    '//absl/status:statusor',
    '//absl/strings:strings',
    '//absl/synchronization:synchronization',
]
ignored_deps = set([
    '@googletest//:gtest'
])

# Convert names like //foo/bar:baz to foo_bar_baz.
# If the last segment of the path and the target names are the same,
# the target name will be dropped, as per bazel convention.
# eg: //foo/bar:bar -> foo_bar
def bazel_name_to_bp_name(name):
    parts = name[name.rfind('/')+1:].split(':')
    if len(parts) == 2 and parts[0] == parts[1]:
        name = name.removesuffix(':'+parts[1])
    name = name[2:].replace('/', '_').replace(':', '_')
    return name

def main():
    query = subprocess.check_output(['bazel', 'query', '--output=streamed_jsonproto', 'kind(cc_library, //...)'], text=True)
    query = query.split('\n')

    # API level 29 is required native ELF TLS support, see bionic/android-changes-for-ndk-developers.md
    # We need it because absl/strings/internal/cordz_functions.h defines a thread_local global variable.
    # If we compile with API lower than 29, it won't link correctly with code compiled with API >= 29
    bp = '''
    // This Android.bp file was autogenerated by external/absel-cpp/generate_bp.py
    // DO NOT EDIT

    package {
        default_applicable_licenses: ["libabsl_license"],
        default_visibility: ["//visibility:private"],
    }

    license {
        name: "libabsl_license",
        visibility: [":__subpackages__"],
        license_kinds: ["SPDX-license-identifier-Apache-2.0"],
        license_text: ["LICENSE"],
    }

    cc_defaults {
        name: "absl_defaults",
        host_supported: true,
        product_available: true,
        ramdisk_available: true,
        recovery_available: true,
        vendor_available: true,
        vendor_ramdisk_available: true,
        sdk_version: "29",
        min_sdk_version: "apex_inherit",
        stl: "libc++",
        apex_available: [
            "//apex_available:platform",
            "//apex_available:anyapex",
        ],
        target: {
            windows: {
                enabled: true,
                cflags: ["-Wno-unknown-pragmas"],
            },
        },
    }

    cc_defaults {
        name: "absl_notls_defaults",
        defaults_visibility: ["//external/protobuf:__subpackages__"],
        host_supported: true,
        ramdisk_available: true,
        recovery_available: true,
        vendor_ramdisk_available: true,
        apex_available: [
            "//apex_available:platform",
            "com.android.runtime",
        ],
        cflags: ["-DANDROID_DISABLE_TLS_FOR_LINKER=1"],
    }

    cc_defaults {
        name: "absl_test_defaults",
        host_supported: true,
        product_available: true,
        vendor_available: true,
        stl: "libc++",
    }

    cc_defaults {
        name: "absl_notls_test_defaults",
        host_supported: true,
        stl: "libc++",
        cflags: ["-DANDROID_DISABLE_TLS_FOR_LINKER=1"],
    }
    '''

    libs_graph = {}
    libs = {}
    alwayslink_libs = {}
    public_libs = {}

    for q in query:
        if not q.strip():
            continue
        q = json.loads(q)
        name = q['rule']['name']
        libs[name] = q
        libs_graph[name] = []
        for attr in q['rule']['attribute']:
            if attr['name'] == 'deps':
                libs_graph[name] = attr.get('stringListValue', [])
            if attr['name'] == 'alwayslink':
                if attr.get('booleanValue', False):
                    alwayslink_libs[name] = True
            if attr['name'] == 'visibility':
                if "//visibility:public" in attr['stringListValue']:
                    public_libs[name] = True

    queue = [l for l in root_libs]
    processed = set(ignored_deps)

    while queue:
        name = queue.pop()
        if name in processed:
            continue
        processed.add(name)
        lib = libs[name]

        bp_mod_name = bazel_name_to_bp_name(name)

        deps = []
        srcs = []
        hdrs = []
        testonly = False
        for attr in lib['rule']['attribute']:
            if attr['name'] == 'deps':
                deps = attr.get('stringListValue', [])
            if attr['name'] == 'srcs':
                srcs = attr.get('stringListValue', [])
            if attr['name'] == 'hdrs':
                hdrs.extend(attr.get('stringListValue', []))
            if attr['name'] == 'textual_hdrs':
                hdrs.extend(attr.get('stringListValue', []))
            if attr['name'] == 'testonly':
                testonly = attr.get('booleanValue', False)

        hdrs = list(sorted(set(hdrs)))

        srcs = [h[2:].replace(':', '/') for h in srcs]
        hdrs = [h[2:].replace(':', '/') for h in hdrs]

        # In bazel, you can put headers in the srcs field to mean "private" headers that are not
        # re-exported. Soong doesn't allow .h files in srcs, or .inc files anywhere. So make this
        # a little easier for soong by moving all headers to the actual headers property, which
        # will cause some internal headers to be exported but at least this is still better than
        # exporting the whole absl folder. The .inc files are also copied to the generated header
        # directories because sometimes headers #include the .inc files.
        hdrs.extend([h for h in srcs if h.endswith('.h') or h.endswith('.inc')])
        srcs = [s for s in srcs if not s.endswith('.h') and not s.endswith('.inc')]

        for dep in deps:
            if dep in alwayslink_libs:
                # We are actually using whole_static_libs for everything now, so in theory this
                # error isn't necessary. But we want to use regular static_libs. We only use
                # whole_static_libs because soong doesn't re-export static_lib dependencies.
                # (b/123002125#comment3)
                sys.exit(f'{name} depends on {dep}, which is alwayslink=1. alwayslink isn\'t supported by this script. (would need to use whole_static_libs instead of static_libs)')

        generated_hdrs_attr = ""
        if hdrs:
            header_files_for_bp = ['"' + h + '"' for h in hdrs]
            # We add an extra my_include_dir/ to not get duplication location errors, as both in and
            # out are exactly the same paths
            header_files_for_out = ['"my_include_dir/' + h + '"' for h in hdrs]
            bp += f'''
            genrule {{
                name: "{bp_mod_name}_hdrs",
                srcs: [
                  {',\n'.join(header_files_for_bp)}
                ],
                out: [
                  {',\n'.join(header_files_for_out)}
                ],
                export_include_dirs: ["my_include_dir"],
                cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " +
                  "cp --parents $(in) $(genDir)/temp && " +
                  // delete empty folders automatically created by soong
                  "rm -rf $(genDir)/my_include_dir/* && " +
                  "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " +
                  "rm -rf $(genDir)/temp"
            }}
            '''
            generated_hdrs_attr = f'generated_headers: ["{bp_mod_name}_hdrs"],\n'
            generated_hdrs_attr += f'export_generated_headers: ["{bp_mod_name}_hdrs"],\n'

        module_type = 'cc_library_static'
        defaults_module = 'absl_defaults'
        notls_defaults_module = 'absl_notls_defaults'
        bp_deps = [bazel_name_to_bp_name(d) for d in deps if d not in ignored_deps]
        extra_attributes = ''

        # Special handling for test-only libraries.
        if testonly:
            module_type = 'cc_test_library'
            defaults_module = 'absl_test_defaults'
            notls_defaults_module = 'absl_notls_test_defaults'
            extra_attributes = '''
            static_libs: ["libgmock", "libgtest"],
            shared: {
                enabled: false,
            },
            '''

        # Hardcode the addition of liblog to log_sink_set. log_sink_set uses a select() in bazel
        # to add the liblog dep only on android.
        if name == "//absl/log/internal:log_sink_set":
            extra_attributes = '''
            target: {
                android: {
                    shared_libs: ["liblog"],
                },
            },
            '''

        visibility_prop = ''
        visibility_prop_notls = ''
        if name in public_libs:
            visibility_prop = 'visibility: ["//visibility:public"],'
            visibility_prop_notls = 'visibility: ["//external/protobuf"],'

        bp_deps_for_bp = ['"' + d + '"' for d in bp_deps]
        src_files_for_bp = ['"' + h + '"' for h in srcs]
        bp += f'''
        {module_type} {{
            name: "{bp_mod_name}",
            defaults: ["{defaults_module}"],
            {visibility_prop}
            srcs: [
                {',\n'.join(src_files_for_bp)}
            ],
            {generated_hdrs_attr}
            whole_static_libs: [
                {',\n'.join(bp_deps_for_bp)}
            ],
            export_static_lib_headers: [
                {',\n'.join(bp_deps_for_bp)}
            ],
            {extra_attributes}
        }}
        '''

        # We need to generate separate versions of the library with TLS disabled
        # for use in the dynamic linker and its dependencies, which does not
        # support ELF TLS segments when loading itself.
        bp_notls_deps_for_bp = ['"' + d + '_notls"' for d in bp_deps]
        bp += f'''
        {module_type} {{
            name: "{bp_mod_name}_notls",
            defaults: ["{notls_defaults_module}"],
            {visibility_prop_notls}
            srcs: [
                {',\n'.join(src_files_for_bp)}
            ],
            {generated_hdrs_attr}
            whole_static_libs: [
                {',\n'.join(bp_notls_deps_for_bp)}
            ],
            export_static_lib_headers: [
                {',\n'.join(bp_notls_deps_for_bp)}
            ],
            {extra_attributes}
        }}
        '''

        queue.extend(deps)

    with open('Android.bp', 'w') as f:
        f.write(bp)
    subprocess.check_call(['bpfmt', '-w', 'Android.bp'])

if __name__ == "__main__":
    main()
