#!/usr/bin/env -S python3 -u
#  Copyright (C) 2024 The Android Open Source Project
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import argparse
import os
from pathlib import Path
import shutil
import subprocess
import sys

# Formatted using: pyformat -s 4 --force_quote_type double -i scripts/gather-android-metalava-artifacts.py


def parse_command_line_args(args):
    """Define the command line options and the parse the command line arguments with them.

    :param args: the command line arguments :return: Return the result of
    parsing the command line arguments.
    """
    args_parser = argparse.ArgumentParser(
        description=(
            "Gather Android artifacts created by Metalava. This will build and"
            " then copy a set of targets to the output directory. If no custom"
            " targets are provided then a set of default ones will be provided"
            " that covers stub generation, signature to JDiff conversion and"
            " api-versions.xml file generation. The intent is that this would"
            " be run this before and after making the change to build and copy"
            " the artifacts into two separate directories that can then be"
            " compared to see what, if any, changes have happened. This does"
            " not check signature file generation as that can be easily checked"
            " by running `m checkapi`."
        ),
    )
    args_parser.add_argument(
        "directory",
        help="Output directory into which artifacts will be copied.",
    )
    args_parser.add_argument(
        "--stub-src-jar",
        action="append",
        help="Additional stub jar to gather",
    )

    return args_parser.parse_args(args)


def default_stub_files():
    """:return: A representative sample list of stub source jars generated by the Android build using Metalava"""
    return [
        f"out/target/common/docs/{x}-stubs.srcjar"
        for x in [
            "api-stubs-docs-non-updatable",
            "system-api-stubs-docs-non-updatable",
            "test-api-stubs-docs-non-updatable",
            "module-lib-api-stubs-docs-non-updatable",
        ]
    ]


def default_doc_stub_files():
    """:return: A representative sample list of doc stub source jars generated by the Android build using Metalava"""
    return [
        "out/target/common/docs/framework-doc-stubs-stubs.srcjar",
        "out/target/common/docs/framework-doc-system-stubs-stubs.srcjar",
    ]


def default_api_version_files():
    """:return: A representative sample list of `api-versions.xml` files generated by the Android build using

    Metalava.
    """
    return [
        "out/soong/lint/api_versions_public.xml",
        "out/soong/lint/api_versions_system.xml",
        "out/soong/lint/api_versions_module_lib.xml",
        "out/soong/lint/api_versions_system_server.xml",
        "out/target/common/obj/PACKAGING/api_versions_module_lib_complete_generated-api-versions.xml",
        "out/target/common/obj/PACKAGING/api_versions_system_server_complete_generated-api-versions.xml",
    ]


def default_jdiff_files():
    """:return: A representative sample list of JDiff files created by the Android build using Metalava."""
    return [
        # JDiff files generated from jar files.
        "out/target/common/obj/api.xml",
        "out/target/common/obj/system-api.xml",
        "out/target/common/obj/module-lib-api.xml",
        "out/target/common/obj/system-server-api.xml",
        "out/target/common/obj/test-api.xml",
        # JDiff files generated from txt files.
        "out/soong/.intermediates/packages/services/Car/car-lib/android.car-test-stubs-jdiff/gen/car-test-api.xml",
        "out/soong/.intermediates/packages/services/Car/car-lib/android.car-stubs-jdiff/gen/car-api.xml",
        "out/soong/.intermediates/packages/services/Car/car-lib/android.car-system-stubs-jdiff/gen/car-system-api.xml",
        ""# JDiff files generated from txt files and then compressed using gzip..
        "out/soong/.intermediates/cts/tests/signature/api/cts-android-test-base-current-api-gz/gen/android-test-base-current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-android-test-mock-current-api-gz/gen/android-test-mock-current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-android-test-runner-current-api-gz/gen/android-test-runner-current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-apache-http-legacy-current-api-gz/gen/apache-http-legacy-current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-car-system-current-api-gz/gen/car-system-current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-current-api-gz/android_common/gen/current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-system-current-api-gz/android_common/gen/system-current.api.gz",
        "out/soong/.intermediates/cts/tests/signature/api/cts-system-removed-api-gz/android_common/gen/system-removed.api.gz",

    ]


def default_dex_writer_files():
    """:return: A representative sample list of dex writer related files created by the Android build using Metalava."""
    return [
        # This is not actually a dex writer file but it contains information derived from lots of dex writer files so
        # any differences in the dex writer files will affect this file.
        "out/soong/hiddenapi/hiddenapi-flags.csv",
    ]


def default_custom_files(top):
    """Returns a representative sample list of custom files created by the Android build using a custom tool based, at

    least in part, on Metalava.
    :return: A list of custom files.
    """
    product_out = Path(os.environ.get("ANDROID_PRODUCT_OUT")).relative_to(top)
    return [
        f"{product_out}/obj/ETC/flag-api-mapping-{surface}_intermediates/flag-api-mapping-{surface}"
        for surface in [
            "PublicApi",
            "SystemApi",
            "ModuleLibApi",
            "SystemServerApi",
        ]
    ]


def construct_target_list(args, top):
    """Generate a list of targets from the supplied arguments

    :param args: the command line arguments.
    :return: a non-empty list of targets to build.
    """
    targets = []
    # If any custom options have been provided then build them.
    if args.stub_src_jar:
        targets += args.stub_src_jar
    # If no custom targets have been provided then use the default targets.
    if not targets:
        targets += default_stub_files()
        targets += default_doc_stub_files()
        targets += default_jdiff_files()
        targets += default_api_version_files()
        targets += default_dex_writer_files()
        targets += default_custom_files(top)
    return targets


def main(args):
    top = os.environ.get("ANDROID_BUILD_TOP")
    if not top:
        raise Exception("ANDROID_BUILD_TOP not specified")
    os.chdir(top)

    # Parse command line arguments.
    args = parse_command_line_args(args)

    # Make sure that the output directory does not already exist.
    output_dir = Path(args.directory)
    if output_dir.exists():
        raise Exception(f"{output_dir} exists, please delete or change")

    # Construct the list of targets to build, using defaults where required.
    targets = construct_target_list(args, top)

    # Build the targets.
    build_targets(targets)

    # Create the output directory and copy the targets into it.
    copy_targets(output_dir, targets)


def copy_targets(output_dir, targets):
    print(f"Making output directory: '{output_dir}'")
    os.mkdir(output_dir)
    print()
    print(f"Copying the following targets into '{output_dir}':")
    for t in targets:
        print(f"    {t}")
        shutil.copy(t, output_dir)
    print()


def build_targets(targets):
    print()
    print("Building the following targets:")
    for t in targets:
        print(f"    {t}")
    print()
    subprocess.run(
        ["build/soong/soong_ui.bash", "--make-mode"] + targets, check=True
    )
    print()


if __name__ == "__main__":
    main(sys.argv[1:])
