chromium/build/android/gyp/compile_kt.py

#!/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') 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(
      '--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 += [
      '-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.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:]))