#!/usr/bin/env python3
#
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import logging
import os
import shutil
import sys
import time

import compile_java

from util import build_utils
import action_helpers  # build_utils adds //build to sys.path.


def _RunCompiler(
    args, kotlinc_cmd, source_files, jar_path, intermediates_out_dir=None
):
    """Runs the Kotlin compiler."""
    logging.info('Starting _RunCompiler')

    source_files = source_files.copy()
    kt_files = [f for f in source_files if f.endswith('.kt')]
    assert len(kt_files) > 0, 'At least one .kt file must be passed in.'

    java_srcjars = args.java_srcjars

    # Use jar_path's directory to ensure paths are relative (needed for rbe).
    temp_dir = jar_path + '.staging'
    build_utils.DeleteDirectory(temp_dir)
    os.makedirs(temp_dir)
    try:
        classes_dir = os.path.join(temp_dir, 'classes')
        os.makedirs(classes_dir)

        input_srcjars_dir = os.path.join(
            intermediates_out_dir or temp_dir, 'input_srcjars'
        )

        if java_srcjars:
            logging.info('Extracting srcjars to %s', input_srcjars_dir)
            build_utils.MakeDirectory(input_srcjars_dir)
            for srcjar in args.java_srcjars:
                source_files += build_utils.ExtractAll(
                    srcjar,
                    no_clobber=True,
                    path=input_srcjars_dir,
                    pattern='*.java',
                )
            logging.info('Done extracting srcjars')

        # Don't include the output directory in the initial set of args since it
        # being in a temp dir makes it unstable (breaks md5 stamping).
        cmd = list(kotlinc_cmd)
        cmd += ['-d', classes_dir]

        if args.classpath:
            cmd += ['-classpath', ':'.join(args.classpath)]

        # This a kotlinc plugin to generate header files for .kt files, similar to
        # turbine for .java files.
        jvm_abi_path = os.path.join(
            build_utils.KOTLIN_HOME, 'lib', 'jvm-abi-gen.jar'
        )
        cmd += [
            f'-Xplugin={jvm_abi_path}',
            '-P',
            'plugin:org.jetbrains.kotlin.jvm.abi:outputDir='
            + args.interface_jar_path,
        ]

        # Pass source paths as response files to avoid extremely long command
        # lines that are tedius to debug.
        source_files_rsp_path = os.path.join(temp_dir, 'files_list.txt')
        with open(source_files_rsp_path, 'w', encoding='utf-8') as f:
            f.write(' '.join(source_files))
        cmd += ['@' + source_files_rsp_path]

        # Explicitly set JAVA_HOME since some bots do not have this already set.
        env = os.environ.copy()
        env['JAVA_HOME'] = build_utils.JAVA_HOME

        logging.debug('Build command %s', cmd)
        start = time.time()
        build_utils.CheckOutput(
            cmd,
            env=env,
            print_stdout=args.chromium_code,
            fail_on_output=args.warnings_as_errors,
        )
        logging.info('Kotlin compilation took %ss', time.time() - start)

        compile_java.CreateJarFile(jar_path, classes_dir)

        logging.info('Completed all steps in _RunCompiler')
    finally:
        shutil.rmtree(temp_dir)


def _ParseOptions(argv):
    parser = argparse.ArgumentParser()
    action_helpers.add_depfile_arg(parser)

    parser.add_argument(
        '--java-srcjars',
        action='append',
        default=[],
        help='List of srcjars to include in compilation.',
    )
    parser.add_argument(
        '--generated-dir',
        help='Subdirectory within target_gen_dir to place extracted srcjars and '
        'annotation processor output for codesearch to find.',
    )
    parser.add_argument(
        '--classpath', action='append', help='Classpath to use.'
    )
    parser.add_argument(
        '--compiler-plugin-jar', help='Path to compiler plugin JAR.'
    )
    parser.add_argument(
        '--chromium-code',
        action='store_true',
        help='Whether code being compiled should be built with stricter '
        'warnings for chromium code.',
    )
    parser.add_argument(
        '--warnings-as-errors',
        action='store_true',
        help='Treat all warnings as errors.',
    )
    parser.add_argument('--jar-path', help='Jar output path.', required=True)
    parser.add_argument(
        '--interface-jar-path', help='Interface jar output path.', required=True
    )

    args, extra_args = parser.parse_known_args(argv)

    args.classpath = action_helpers.parse_gn_list(args.classpath)
    args.java_srcjars = action_helpers.parse_gn_list(args.java_srcjars)

    source_files = []
    for arg in extra_args:
        # Interpret a path prefixed with @ as a file containing a list of sources.
        if arg.startswith('@'):
            source_files.extend(build_utils.ReadSourcesList(arg[1:]))
        else:
            assert not arg.startswith('--'), f'Undefined option {arg}'
            source_files.append(arg)

    return args, source_files


def main(argv):
    build_utils.InitLogging('KOTLINC_DEBUG')
    argv = build_utils.ExpandFileArgs(argv)
    args, source_files = _ParseOptions(argv)

    kotlinc_cmd = [build_utils.KOTLINC_PATH]

    kotlinc_cmd += [
        "-jvm-target",
        "11",
        '-no-jdk',  # Avoid depending on the bundled JDK.
        # Avoid depending on the bundled Kotlin stdlib. This may have a version
        # skew with the one in //third_party/android_deps (which is the one we
        # prefer to use).
        '-no-stdlib',
        # Avoid depending on the bundled Kotlin reflect libs.
        '-no-reflect',
        # We typically set a default of 1G for java commands, see
        # build_utils.JavaCmd. This may help prevent OOMs.
        '-J-Xmx1G',
    ]
    if args.compiler_plugin_jar:
        kotlinc_cmd += [
            f'-Xplugin={args.compiler_plugin_jar}',
        ]

    if args.generated_dir:
        # Delete any stale files in the generated directory. The purpose of
        # args.generated_dir is for codesearch.
        shutil.rmtree(args.generated_dir, True)

    _RunCompiler(
        args,
        kotlinc_cmd,
        source_files,
        args.jar_path,
        intermediates_out_dir=args.generated_dir,
    )

    if args.depfile:
        # GN already knows of the source files, so avoid listing individual files
        # in the depfile.
        action_helpers.write_depfile(
            args.depfile, args.jar_path, args.classpath
        )


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