load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cpp_toolchain")

def cc_library_static(
        name,
        srcs = [],
        deps = [],
        hdrs = [],
        copts = [],
        includes = [],
        native_bridge_supported = False,  # TODO: not supported yet.
        whole_archive_deps = [],
        **kwargs):
    "Bazel macro to correspond with the cc_library_static Soong module."
    mainlib_name = "%s_mainlib" % name

    # Silently drop these attributes for now:
    # - native_bridge_supported
    native.cc_library(
        name = mainlib_name,
        srcs = srcs,
        hdrs = hdrs,
        # TODO(b/187533117): Handle whole_archive_deps differently from regular static deps.
        deps = deps + whole_archive_deps,
        copts = copts,
        includes = includes,
        **kwargs
    )

    # Safeguard target to handle the no-srcs no-deps case.
    # With no-srcs no-deps, this returns a stub. Otherwise, it's a passthrough no-op.
    _empty_library_safeguard(
        name = name,
        deps = [mainlib_name],
    )

# Returns a cloned copy of the given CcInfo object, except that all linker inputs
# with owner `old_owner_label` are recreated and owned by the current target.
#
# This is useful in the "macro with proxy rule" pattern, as some rules upstream
# may expect they are depending directly on a target which generates linker inputs,
# as opposed to a proxy target which is a level of indirection to such a target.
def _claim_ownership(ctx, old_owner_label, ccinfo):
    linker_inputs = []
    # This is not ideal, as it flattens a depset.
    for old_linker_input in ccinfo.linking_context.linker_inputs.to_list():
        if old_linker_input.owner == old_owner_label:
            new_linker_input = cc_common.create_linker_input(
                owner = ctx.label,
                libraries = depset(direct = old_linker_input.libraries))
            linker_inputs.append(new_linker_input)
        else:
            linker_inputs.append(old_linker_input)

    linking_context = cc_common.create_linking_context(linker_inputs = depset(direct = linker_inputs))
    return CcInfo(compilation_context = ccinfo.compilation_context, linking_context = linking_context)

def _empty_library_safeguard_impl(ctx):
    if len(ctx.attr.deps) != 1:
        fail("the deps attribute should always contain exactly one label")

    main_target = ctx.attr.deps[0]
    if len(ctx.files.deps) > 0:
        # This safeguard is a no-op, as a library was generated by the main target.
        new_cc_info = _claim_ownership(ctx, main_target.label, main_target[CcInfo])
        return [new_cc_info, main_target[DefaultInfo]]

    # The main library is empty; link a stub and propagate it to match Soong behavior.
    cc_toolchain = find_cpp_toolchain(ctx)
    CPP_LINK_STATIC_LIBRARY_ACTION_NAME = "c++-link-static-library"
    feature_configuration = cc_common.configure_features(
        ctx = ctx,
        cc_toolchain = cc_toolchain,
        requested_features = ctx.features,
        unsupported_features = ctx.disabled_features + ["linker_flags"],
    )

    output_file = ctx.actions.declare_file(ctx.label.name + ".a")
    linker_input = cc_common.create_linker_input(
        owner = ctx.label,
        libraries = depset(direct = [
            cc_common.create_library_to_link(
                actions = ctx.actions,
                feature_configuration = feature_configuration,
                cc_toolchain = cc_toolchain,
                static_library = output_file,
            ),
        ]),
    )
    compilation_context = cc_common.create_compilation_context()
    linking_context = cc_common.create_linking_context(linker_inputs = depset(direct = [linker_input]))

    archiver_path = cc_common.get_tool_for_action(
        feature_configuration = feature_configuration,
        action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
    )
    archiver_variables = cc_common.create_link_variables(
        feature_configuration = feature_configuration,
        cc_toolchain = cc_toolchain,
        output_file = output_file.path,
        is_using_linker = False,
    )
    command_line = cc_common.get_memory_inefficient_command_line(
        feature_configuration = feature_configuration,
        action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
        variables = archiver_variables,
    )
    args = ctx.actions.args()
    args.add_all(command_line)

    ctx.actions.run(
        executable = archiver_path,
        arguments = [args],
        inputs = depset(
            transitive = [
                cc_toolchain.all_files,
            ],
        ),
        outputs = [output_file],
    )

    cc_info = cc_common.merge_cc_infos(cc_infos = [
        main_target[CcInfo],
        CcInfo(compilation_context = compilation_context, linking_context = linking_context),
    ])
    return [
        DefaultInfo(files = depset([output_file])),
        cc_info,
    ]

# A rule which depends on a single cc_library target. If the cc_library target
# has no outputs (indicating that it has no srcs or deps), then this safeguard
# rule creates a single stub .a file using llvm-ar. This mimics Soong's behavior
# in this regard. Otherwise, this safeguard is a simple passthrough for the providers
# of the cc_library.
_empty_library_safeguard = rule(
    implementation = _empty_library_safeguard_impl,
    attrs = {
        # This should really be a label attribute since it always contains a
        # single dependency, but cc_shared_library requires that C++ rules
        # depend on each other through the "deps" attribute.
        "deps": attr.label_list(providers = [CcInfo]),
        "_cc_toolchain": attr.label(
            default = Label("@local_config_cc//:toolchain"),
            providers = [cc_common.CcToolchainInfo],
        ),
    },
    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
    fragments = ["cpp"],
)