#!/usr/bin/env python3
#
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Compiles twice: With incremental_javac_test_toggle_gn=[false, true]

The purpose of compiling the target twice is to test that builds generated by
the incremental build code path are valid.
"""

import argparse
import os
import pathlib
import subprocess
import shutil

_CHROMIUM_SRC = pathlib.Path(__file__).resolve().parents[4].resolve()
_NINJA_PATH = _CHROMIUM_SRC / 'third_party' / 'ninja' / 'ninja'

# Relative to _CHROMIUM_SRC
_GN_SRC_REL_PATH = 'buildtools/linux64/gn'

_USING_PARTIAL_JAVAC_MSG = 'Using partial javac optimization'


def _raise_command_exception(args, returncode, output):
  """Raises an exception whose message describes a command failure.

    Args:
      args: shell command-line (as passed to subprocess.Popen())
      returncode: status code.
      output: command output.
    Raises:
      a new Exception.
    """
  message = ('Command failed with status {}: {}\n'
             'Output:-----------------------------------------\n{}\n'
             '------------------------------------------------\n').format(
                 returncode, args, output)
  raise Exception(message)


def _run_command(args, check_returncode=True, cwd=None, env=None):
  """Runs shell command. Raises exception if command fails."""
  p = subprocess.Popen(args,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.STDOUT,
                       cwd=cwd,
                       env=env,
                       universal_newlines=True)
  pout, _ = p.communicate()
  if check_returncode and p.returncode != 0:
    _raise_command_exception(args, p.returncode, pout)
  return pout


def _copy_and_append_gn_args(src_args_path, dest_args_path, extra_args):
  """Copies args.gn.

    Args:
      src_args_path: args.gn file to copy.
      dest_args_path: Copy file destination.
      extra_args: Text to append to args.gn after copy.
    """
  with open(src_args_path) as f:
    initial_args_str = f.read()

  with open(dest_args_path, 'w') as f:
    f.write(initial_args_str)
    f.write('\n')

    # Write |extra_args| after |initial_args_str| so that |extra_args|
    # overwrites |initial_args_str| in the case of duplicate entries.
    f.write('\n'.join(extra_args))


def _run_gn(args, check_returncode=True):
  _run_command([_GN_SRC_REL_PATH] + args,
               check_returncode=check_returncode,
               cwd=_CHROMIUM_SRC)


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('--target-name',
                      required=True,
                      help='name of target to build with and without ' +
                      'incremental_javac_test_toggle_gn=true')
  parser.add_argument('--gn-args-path',
                      required=True,
                      help='Path to args.gn file to copy args from.')
  parser.add_argument('--out-dir',
                      required=True,
                      help='Path to output directory to use for compilation.')
  parser.add_argument('--out-jar',
                      required=True,
                      help='Path where output jar should be stored.')
  options = parser.parse_args()

  options.out_dir = pathlib.Path(options.out_dir).resolve()

  options.out_dir.mkdir(parents=True, exist_ok=True)

  # Clear the output directory so that first compile is not an incremental
  # build.
  # This will make the test fail in the scenario that:
  # - The output directory contains a previous build generated by this script.
  # - Incremental builds are broken and are a no-op.
  _run_gn(['clean', options.out_dir.relative_to(_CHROMIUM_SRC)],
          check_returncode=False)

  out_gn_args_path = options.out_dir / 'args.gn'
  extra_gn_args = [
      'treat_warnings_as_errors = true',
      # GOMA does not work with non-standard output directories.
      'use_goma = false',
  ]
  _copy_and_append_gn_args(
      options.gn_args_path, out_gn_args_path,
      extra_gn_args + ['incremental_javac_test_toggle_gn = false'])

  _run_gn([
      '--root-target=' + options.target_name, 'gen',
      options.out_dir.relative_to(_CHROMIUM_SRC)
  ])

  ninja_env = os.environ.copy()
  ninja_env['JAVAC_DEBUG'] = '1'

  # Strip leading '//'
  gn_path = options.target_name[2:]
  ninja_args = [_NINJA_PATH, '-C', options.out_dir, gn_path]
  ninja_output = _run_command(ninja_args, env=ninja_env)
  if _USING_PARTIAL_JAVAC_MSG in ninja_output:
    raise Exception('Incorrectly using partial javac for clean compile.')

  _copy_and_append_gn_args(
      options.gn_args_path, out_gn_args_path,
      extra_gn_args + ['incremental_javac_test_toggle_gn = true'])
  ninja_output = _run_command(ninja_args, env=ninja_env)
  if _USING_PARTIAL_JAVAC_MSG not in ninja_output:
    raise Exception('Not using partial javac for incremental compile.')

  expected_output_path = '{}/obj/{}.javac.jar'.format(options.out_dir,
                                                      gn_path.replace(':', '/'))
  if not os.path.exists(expected_output_path):
    raise Exception('{} not created.'.format(expected_output_path))

  shutil.copyfile(expected_output_path, options.out_jar)


if __name__ == '__main__':
  main()
