tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

compile_java.py (27089B)


      1 #!/usr/bin/env python3
      2 #
      3 # Copyright 2013 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 import collections
      8 import functools
      9 import itertools
     10 import logging
     11 import argparse
     12 import os
     13 import pathlib
     14 import re
     15 import shlex
     16 import shutil
     17 import sys
     18 import time
     19 import zipfile
     20 
     21 import javac_output_processor
     22 from util import build_utils
     23 from util import md5_check
     24 from util import jar_info_utils
     25 from util import server_utils
     26 import action_helpers  # build_utils adds //build to sys.path.
     27 import zip_helpers
     28 
     29 _JAVAC_EXTRACTOR = os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party',
     30                                'android_prebuilts', 'build_tools', 'common',
     31                                'framework', 'javac_extractor.jar')
     32 
     33 
     34 def ProcessJavacOutput(output, target_name):
     35  # These warnings cannot be suppressed even for third party code. Deprecation
     36  # warnings especially do not help since we must support older android version.
     37  deprecated_re = re.compile(r'Note: .* uses? or overrides? a deprecated API')
     38  unchecked_re = re.compile(
     39      r'(Note: .* uses? unchecked or unsafe operations.)$')
     40  recompile_re = re.compile(r'(Note: Recompile with -Xlint:.* for details.)$')
     41 
     42  def ApplyFilters(line):
     43    return not (deprecated_re.match(line) or unchecked_re.match(line)
     44                or recompile_re.match(line))
     45 
     46  output = build_utils.FilterReflectiveAccessJavaWarnings(output)
     47 
     48  # Warning currently cannot be silenced via javac flag.
     49  if 'Unsafe is internal proprietary API' in output:
     50    # Example:
     51    # HiddenApiBypass.java:69: warning: Unsafe is internal proprietary API and
     52    # may be removed in a future release
     53    # import sun.misc.Unsafe;
     54    #                 ^
     55    output = re.sub(r'.*?Unsafe is internal proprietary API[\s\S]*?\^\n', '',
     56                    output)
     57    output = re.sub(r'\d+ warnings\n', '', output)
     58 
     59  lines = (l for l in output.split('\n') if ApplyFilters(l))
     60 
     61  output_processor = javac_output_processor.JavacOutputProcessor(target_name)
     62  lines = output_processor.Process(lines)
     63 
     64  return '\n'.join(lines)
     65 
     66 
     67 def CreateJarFile(jar_path,
     68                  classes_dir,
     69                  services_map=None,
     70                  additional_jar_files=None,
     71                  extra_classes_jar=None):
     72  """Zips files from compilation into a single jar."""
     73  logging.info('Start creating jar file: %s', jar_path)
     74  with action_helpers.atomic_output(jar_path) as f:
     75    with zipfile.ZipFile(f.name, 'w') as z:
     76      zip_helpers.zip_directory(z, classes_dir)
     77      if services_map:
     78        for service_class, impl_classes in sorted(services_map.items()):
     79          zip_path = 'META-INF/services/' + service_class
     80          data = ''.join(f'{x}\n' for x in sorted(impl_classes))
     81          zip_helpers.add_to_zip_hermetic(z, zip_path, data=data)
     82 
     83      if additional_jar_files:
     84        for src_path, zip_path in additional_jar_files:
     85          zip_helpers.add_to_zip_hermetic(z, zip_path, src_path=src_path)
     86      if extra_classes_jar:
     87        path_transform = lambda p: p if p.endswith('.class') else None
     88        zip_helpers.merge_zips(z, [extra_classes_jar],
     89                               path_transform=path_transform)
     90  logging.info('Completed jar file: %s', jar_path)
     91 
     92 
     93 # Java lines end in semicolon, whereas Kotlin lines do not.
     94 _PACKAGE_RE = re.compile(r'^package\s+(.*?)(;|\s*$)', flags=re.MULTILINE)
     95 
     96 _SERVICE_IMPL_RE = re.compile(
     97    r'^([\t ]*)@ServiceImpl\(\s*(.+?)\.class\)(.*?)\sclass\s+(\w+)',
     98    flags=re.MULTILINE | re.DOTALL)
     99 
    100 # Finds all top-level classes (by looking for those that are not indented).
    101 _TOP_LEVEL_CLASSES_RE = re.compile(
    102    # Start of line, or after /* package */
    103    r'^(?:/\*.*\*/\s*)?'
    104    # Annotations
    105    r'(?:@\w+(?:\(.*\))\s+)*'
    106    r'(?:(?:public|protected|private)\s+)?'
    107    r'(?:(?:static|abstract|final|sealed)\s+)*'
    108    r'(?:class|@?interface|enum|record)\s+'
    109    r'(\w+?)\b[^"]*?$',
    110    flags=re.MULTILINE)
    111 
    112 
    113 def ParseJavaSource(data, services_map, path=None):
    114  """This should support both Java and Kotlin files."""
    115  package_name = ''
    116  if m := _PACKAGE_RE.search(data):
    117    package_name = m.group(1)
    118 
    119  class_names = _TOP_LEVEL_CLASSES_RE.findall(data)
    120 
    121  # Very rare, so worth an upfront check.
    122  if '@ServiceImpl' in data:
    123    for indent, service_class, modifiers, impl_class in (
    124        _SERVICE_IMPL_RE.findall(data)):
    125      if 'public' not in modifiers:
    126        raise Exception(f'@ServiceImpl can be used only on public classes '
    127                        f'(when parsing {path})')
    128      # Assume indent means nested class that is one level deep.
    129      if indent:
    130        impl_class = f'{class_names[0]}${impl_class}'
    131      else:
    132        assert class_names[0] == impl_class
    133 
    134      # Parse imports to resolve the class.
    135      dot_idx = service_class.find('.')
    136      # Handle @ServiceImpl(OuterClass.InnerClass.class)
    137      outer_class = service_class if dot_idx == -1 else service_class[:dot_idx]
    138 
    139      if m := re.search(r'^import\s+([\w\.]*\.' + outer_class + r')[;\s]',
    140                        data,
    141                        flags=re.MULTILINE):
    142        service_class = m.group(1) + service_class[len(outer_class):]
    143      else:
    144        service_class = f'{package_name}.{service_class}'
    145 
    146      # Convert OuterClass.InnerClass -> OuterClass$InnerClass.
    147      for m in list(re.finditer(r'\.[A-Z]', service_class))[1:]:
    148        idx = m.start()
    149        service_class = service_class[:idx] + '$' + service_class[idx + 1:]
    150 
    151      services_map[service_class].append(f'{package_name}.{impl_class}')
    152 
    153  return package_name, class_names
    154 
    155 
    156 class _MetadataParser:
    157 
    158  def __init__(self, chromium_code, exclude_globs, include_globs):
    159    self._chromium_code = chromium_code
    160    self._exclude_globs = exclude_globs
    161    self._include_globs = include_globs
    162    # Map of .java path -> .srcjar/nested/path.java.
    163    self._srcjar_files = {}
    164    # Map of @ServiceImpl class -> impl class
    165    self.services_map = collections.defaultdict(list)
    166 
    167  def AddSrcJarSources(self, srcjar_path, extracted_paths, parent_dir):
    168    for path in extracted_paths:
    169      # We want the path inside the srcjar so the viewer can have a tree
    170      # structure.
    171      self._srcjar_files[path] = '{}/{}'.format(
    172          srcjar_path, os.path.relpath(path, parent_dir))
    173 
    174  def _CheckPathMatchesClassName(self, source_file, package_name, class_name):
    175    if source_file.endswith('.java'):
    176      parts = package_name.split('.') + [class_name + '.java']
    177    else:
    178      parts = package_name.split('.') + [class_name + '.kt']
    179    expected_suffix = os.path.sep.join(parts)
    180    if not source_file.endswith(expected_suffix):
    181      raise Exception(('Source package+class name do not match its path.\n'
    182                       'Actual path: %s\nExpected path: %s') %
    183                      (source_file, expected_suffix))
    184 
    185  def _ProcessInfo(self, java_file, package_name, class_names, source):
    186    for class_name in class_names:
    187      yield '{}.{}'.format(package_name, class_name)
    188      # Skip aidl srcjars since they don't indent code correctly.
    189      if '_aidl.srcjar' in source:
    190        continue
    191      assert not self._chromium_code or len(class_names) == 1, (
    192          'Chromium java files must only have one class: {} found: {}'.format(
    193              source, class_names))
    194      if self._chromium_code:
    195        # This check is not necessary but nice to check this somewhere.
    196        self._CheckPathMatchesClassName(java_file, package_name, class_names[0])
    197 
    198  def _ShouldIncludeInJarInfo(self, fully_qualified_name):
    199    name_as_class_glob = fully_qualified_name.replace('.', '/') + '.class'
    200    if self._include_globs and not build_utils.MatchesGlob(
    201        name_as_class_glob, self._include_globs):
    202      return False
    203    return not build_utils.MatchesGlob(name_as_class_glob, self._exclude_globs)
    204 
    205  def ParseAndWriteInfoFile(self, output_path, java_files, kt_files=None):
    206    """Writes a .jar.info file.
    207 
    208    Maps fully qualified names for classes to either the java file that they
    209    are defined in or the path of the srcjar that they came from.
    210    """
    211    logging.info('Collecting info file entries')
    212    entries = {}
    213    for path in itertools.chain(java_files, kt_files or []):
    214      data = pathlib.Path(path).read_text()
    215      package_name, class_names = ParseJavaSource(data,
    216                                                  self.services_map,
    217                                                  path=path)
    218      source = self._srcjar_files.get(path, path)
    219      for fully_qualified_name in self._ProcessInfo(path, package_name,
    220                                                    class_names, source):
    221        if self._ShouldIncludeInJarInfo(fully_qualified_name):
    222          entries[fully_qualified_name] = path
    223 
    224    logging.info('Writing info file: %s', output_path)
    225    with action_helpers.atomic_output(output_path, mode='wb') as f:
    226      jar_info_utils.WriteJarInfoFile(f, entries, self._srcjar_files)
    227    logging.info('Completed info file: %s', output_path)
    228 
    229 
    230 def _OnStaleMd5(changes,
    231                options,
    232                javac_cmd,
    233                javac_args,
    234                java_files,
    235                kt_files,
    236                use_errorprone=False):
    237  logging.info('Starting _OnStaleMd5')
    238 
    239  if options.enable_kythe_annotations:
    240    # Kythe requires those env variables to be set and compile_java.py does the
    241    # same
    242    if not os.environ.get('KYTHE_ROOT_DIRECTORY') or \
    243        not os.environ.get('KYTHE_OUTPUT_DIRECTORY'):
    244      raise Exception('--enable-kythe-annotations requires '
    245                      'KYTHE_ROOT_DIRECTORY and KYTHE_OUTPUT_DIRECTORY '
    246                      'environment variables to be set.')
    247    javac_extractor_cmd = build_utils.JavaCmd() + [
    248        '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
    249        '--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
    250        '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
    251        '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
    252        '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
    253        '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
    254        '--add-exports=jdk.internal.opt/jdk.internal.opt=ALL-UNNAMED',
    255        '-jar',
    256        _JAVAC_EXTRACTOR,
    257    ]
    258    try:
    259      # _RunCompiler()'s partial javac implementation does not support
    260      # generating outputs in $KYTHE_OUTPUT_DIRECTORY.
    261      _RunCompiler(changes,
    262                   options,
    263                   javac_extractor_cmd + javac_args,
    264                   java_files,
    265                   options.jar_path + '.javac_extractor',
    266                   enable_partial_javac=False)
    267    except build_utils.CalledProcessError as e:
    268      # Having no index for particular target is better than failing entire
    269      # codesearch. Log and error and move on.
    270      logging.error('Could not generate kzip: %s', e)
    271 
    272  intermediates_out_dir = None
    273  jar_info_path = None
    274  if not use_errorprone:
    275    # Delete any stale files in the generated directory. The purpose of
    276    # options.generated_dir is for codesearch and Android Studio.
    277    shutil.rmtree(options.generated_dir, True)
    278    intermediates_out_dir = options.generated_dir
    279 
    280    # Write .info file only for the main javac invocation (no need to do it
    281    # when running Error Prone.
    282    jar_info_path = options.jar_path + '.info'
    283 
    284  # Compiles with Error Prone take twice as long to run as pure javac. Thus GN
    285  # rules run both in parallel, with Error Prone only used for checks.
    286  try:
    287    _RunCompiler(changes,
    288                 options,
    289                 javac_cmd + javac_args,
    290                 java_files,
    291                 options.jar_path,
    292                 use_errorprone=use_errorprone,
    293                 kt_files=kt_files,
    294                 jar_info_path=jar_info_path,
    295                 intermediates_out_dir=intermediates_out_dir,
    296                 enable_partial_javac=True)
    297  except build_utils.CalledProcessError as e:
    298    # Do not output stacktrace as it takes up space on gerrit UI, forcing
    299    # you to click though to find the actual compilation error. It's never
    300    # interesting to see the Python stacktrace for a Java compilation error.
    301    sys.stderr.write(e.output)
    302    sys.exit(1)
    303 
    304  logging.info('Completed all steps in _OnStaleMd5')
    305 
    306 
    307 def _RunCompiler(changes,
    308                 options,
    309                 javac_cmd,
    310                 java_files,
    311                 jar_path,
    312                 use_errorprone=False,
    313                 kt_files=None,
    314                 jar_info_path=None,
    315                 intermediates_out_dir=None,
    316                 enable_partial_javac=False):
    317  """Runs java compiler.
    318 
    319  Args:
    320    changes: md5_check.Changes object.
    321    options: Object with command line flags.
    322    javac_cmd: Command to execute.
    323    java_files: List of java files passed from command line.
    324    jar_path: Path of output jar file.
    325    kt_files: List of Kotlin files passed from command line if any.
    326    jar_info_path: Path of the .info file to generate.
    327        If None, .info file will not be generated.
    328    intermediates_out_dir: Directory for saving intermediate outputs.
    329        If None a temporary directory is used.
    330    enable_partial_javac: Enables compiling only Java files which have changed
    331        in the special case that no method signatures have changed. This is
    332        useful for large GN targets.
    333        Not supported if compiling generates outputs other than |jar_path| and
    334        |jar_info_path|.
    335  """
    336  logging.info('Starting _RunCompiler')
    337 
    338  java_files = java_files.copy()
    339  java_srcjars = options.java_srcjars
    340  parse_java_files = jar_info_path is not None
    341 
    342  # Use jar_path's directory to ensure paths are relative (needed for rbe).
    343  temp_dir = jar_path + '.staging'
    344  build_utils.DeleteDirectory(temp_dir)
    345  os.makedirs(temp_dir)
    346  metadata_parser = _MetadataParser(options.chromium_code,
    347                                    options.jar_info_exclude_globs,
    348                                    options.jar_info_include_globs)
    349  try:
    350    classes_dir = os.path.join(temp_dir, 'classes')
    351 
    352    if java_files:
    353      os.makedirs(classes_dir)
    354 
    355      if enable_partial_javac and changes:
    356        all_changed_paths_are_java = all(
    357            p.endswith(".java") for p in changes.IterChangedPaths())
    358        if (all_changed_paths_are_java and not changes.HasStringChanges()
    359            and os.path.exists(jar_path)
    360            and (jar_info_path is None or os.path.exists(jar_info_path))):
    361          # Log message is used by tests to determine whether partial javac
    362          # optimization was used.
    363          logging.info('Using partial javac optimization for %s compile' %
    364                       (jar_path))
    365 
    366          # Header jar corresponding to |java_files| did not change.
    367          # As a build speed optimization (crbug.com/1170778), re-compile only
    368          # java files which have changed. Re-use old jar .info file.
    369          java_files = list(changes.IterChangedPaths())
    370 
    371          # Disable srcjar extraction, since we know the srcjar didn't show as
    372          # changed (only .java files).
    373          java_srcjars = None
    374 
    375          # @ServiceImpl has class retention, so will alter header jars when
    376          # modified (and hence not reach this block).
    377          # Likewise, nothing in .info files can change if header jar did not
    378          # change.
    379          parse_java_files = False
    380 
    381          # Extracts .class as well as META-INF/services.
    382          build_utils.ExtractAll(jar_path, classes_dir)
    383 
    384    if intermediates_out_dir is None:
    385      intermediates_out_dir = temp_dir
    386 
    387    input_srcjars_dir = os.path.join(intermediates_out_dir, 'input_srcjars')
    388 
    389    if java_srcjars:
    390      logging.info('Extracting srcjars to %s', input_srcjars_dir)
    391      build_utils.MakeDirectory(input_srcjars_dir)
    392      for srcjar in options.java_srcjars:
    393        extracted_files = build_utils.ExtractAll(
    394            srcjar, no_clobber=True, path=input_srcjars_dir, pattern='*.java')
    395        java_files.extend(extracted_files)
    396        if parse_java_files:
    397          metadata_parser.AddSrcJarSources(srcjar, extracted_files,
    398                                           input_srcjars_dir)
    399      logging.info('Done extracting srcjars')
    400 
    401    if java_files:
    402      # Don't include the output directory in the initial set of args since it
    403      # being in a temp dir makes it unstable (breaks md5 stamping).
    404      cmd = list(javac_cmd)
    405      cmd += ['-d', classes_dir]
    406 
    407      if options.classpath:
    408        cmd += ['-classpath', ':'.join(options.classpath)]
    409 
    410      # Pass source paths as response files to avoid extremely long command
    411      # lines that are tedius to debug.
    412      java_files_rsp_path = os.path.join(temp_dir, 'files_list.txt')
    413      with open(java_files_rsp_path, 'w') as f:
    414        f.write(' '.join(java_files))
    415      cmd += ['@' + java_files_rsp_path]
    416 
    417      process_javac_output_partial = functools.partial(
    418          ProcessJavacOutput, target_name=options.target_name)
    419 
    420      logging.debug('Build command %s', cmd)
    421      start = time.time()
    422      before_join_callback = None
    423      if parse_java_files:
    424        before_join_callback = lambda: metadata_parser.ParseAndWriteInfoFile(
    425            jar_info_path, java_files, kt_files)
    426 
    427      if options.print_javac_command_line:
    428        print(shlex.join(cmd))
    429        return
    430 
    431      build_utils.CheckOutput(cmd,
    432                              print_stdout=options.chromium_code,
    433                              stdout_filter=process_javac_output_partial,
    434                              stderr_filter=process_javac_output_partial,
    435                              fail_on_output=options.warnings_as_errors,
    436                              before_join_callback=before_join_callback)
    437      end = time.time() - start
    438      logging.info('Java compilation took %ss', end)
    439    elif parse_java_files:
    440      if options.print_javac_command_line:
    441        raise Exception('need java files for --print-javac-command-line.')
    442      metadata_parser.ParseAndWriteInfoFile(jar_info_path, java_files, kt_files)
    443 
    444    if use_errorprone:
    445      # There is no jar file when running errorprone and jar_path is actually
    446      # just the stamp file for that target.
    447      server_utils.MaybeTouch(jar_path)
    448    else:
    449      CreateJarFile(jar_path, classes_dir, metadata_parser.services_map,
    450                    options.additional_jar_files, options.kotlin_jar_path)
    451 
    452 
    453    # Remove input srcjars that confuse Android Studio:
    454    # https://crbug.com/353326240
    455    for root, _, files in os.walk(intermediates_out_dir):
    456      for subpath in files:
    457        p = os.path.join(root, subpath)
    458        # JNI Zero placeholders
    459        if '_jni_java/' in p and not p.endswith('Jni.java'):
    460          os.unlink(p)
    461 
    462    logging.info('Completed all steps in _RunCompiler')
    463  finally:
    464    # preserve temp_dir for rsp fie when --print-javac-command-line
    465    if not options.print_javac_command_line:
    466      shutil.rmtree(temp_dir)
    467 
    468 
    469 def _ParseOptions(argv):
    470  parser = argparse.ArgumentParser()
    471  action_helpers.add_depfile_arg(parser)
    472 
    473  parser.add_argument('--target-name', help='Fully qualified GN target name.')
    474  parser.add_argument('--java-srcjars',
    475                      action='append',
    476                      default=[],
    477                      help='List of srcjars to include in compilation.')
    478  parser.add_argument(
    479      '--generated-dir',
    480      help='Subdirectory within target_gen_dir to place extracted srcjars and '
    481      'annotation processor output for codesearch to find.')
    482  parser.add_argument('--classpath', action='append', help='Classpath to use.')
    483  parser.add_argument(
    484      '--processorpath',
    485      action='append',
    486      help='GN list of jars that comprise the classpath used for Annotation '
    487      'Processors.')
    488  parser.add_argument('--processor-arg',
    489                      dest='processor_args',
    490                      action='append',
    491                      help='key=value arguments for the annotation processors.')
    492  parser.add_argument(
    493      '--additional-jar-file',
    494      dest='additional_jar_files',
    495      action='append',
    496      help='Additional files to package into jar. By default, only Java .class '
    497      'files are packaged into the jar. Files should be specified in '
    498      'format <filename>:<path to be placed in jar>.')
    499  parser.add_argument(
    500      '--jar-info-exclude-globs',
    501      help='GN list of exclude globs to filter from generated .info files.')
    502  parser.add_argument(
    503      '--jar-info-include-globs',
    504      help='GN list of inlclude globs to filter from generated .info files.')
    505  parser.add_argument(
    506      '--chromium-code',
    507      action='store_true',
    508      help='Whether code being compiled should be built with stricter '
    509      'warnings for chromium code.')
    510  parser.add_argument('--warnings-as-errors',
    511                      action='store_true',
    512                      help='Treat all warnings as errors.')
    513  parser.add_argument('--jar-path', required=True, help='Jar output path.')
    514  parser.add_argument('--javac-arg',
    515                      action='append',
    516                      default=[],
    517                      help='Additional arguments to pass to javac.')
    518  parser.add_argument('--print-javac-command-line',
    519                      action='store_true',
    520                      help='Just show javac command line (for ide_query).')
    521  parser.add_argument(
    522      '--enable-kythe-annotations',
    523      action='store_true',
    524      help='Enable generation of Kythe kzip, used for codesearch. Ensure '
    525      'proper environment variables are set before using this flag.')
    526  parser.add_argument(
    527      '--header-jar',
    528      help='This is the header jar for the current target that contains '
    529      'META-INF/services/* files to be included in the output jar.')
    530  parser.add_argument(
    531      '--kotlin-jar-path',
    532      help='Kotlin jar to be merged into the output jar. This contains the '
    533      ".class files from this target's .kt files.")
    534  parser.add_argument('sources', nargs='*')
    535 
    536  options = parser.parse_args(argv)
    537 
    538  options.classpath = action_helpers.parse_gn_list(options.classpath)
    539  options.processorpath = action_helpers.parse_gn_list(options.processorpath)
    540  options.java_srcjars = action_helpers.parse_gn_list(options.java_srcjars)
    541  options.jar_info_exclude_globs = action_helpers.parse_gn_list(
    542      options.jar_info_exclude_globs)
    543  options.jar_info_include_globs = action_helpers.parse_gn_list(
    544      options.jar_info_include_globs)
    545 
    546  additional_jar_files = []
    547  for arg in options.additional_jar_files or []:
    548    filepath, jar_filepath = arg.split(':')
    549    additional_jar_files.append((filepath, jar_filepath))
    550  options.additional_jar_files = additional_jar_files
    551 
    552  files = []
    553  for arg in options.sources:
    554    # Interpret a path prefixed with @ as a file containing a list of sources.
    555    if arg.startswith('@'):
    556      files.extend(build_utils.ReadSourcesList(arg[1:]))
    557    else:
    558      files.append(arg)
    559 
    560  # The target's .sources file contains both Java and Kotlin files. We use
    561  # compile_kt.py to compile the Kotlin files to .class and header jars. Javac
    562  # is run only on .java files.
    563  java_files = [f for f in files if f.endswith('.java')]
    564  # Kotlin files are needed to populate the info file and attribute size in
    565  # supersize back to the appropriate Kotlin file.
    566  kt_files = [f for f in files if f.endswith('.kt')]
    567 
    568  return options, java_files, kt_files
    569 
    570 
    571 def main(argv,
    572         extra_javac_args=None,
    573         use_errorprone=False,
    574         write_depfile_only=False):
    575  build_utils.InitLogging('JAVAC_DEBUG')
    576  argv = build_utils.ExpandFileArgs(argv)
    577  options, java_files, kt_files = _ParseOptions(argv)
    578 
    579  javac_cmd = [build_utils.JAVAC_PATH]
    580 
    581  javac_args = [
    582      '-g',
    583      # Required for Error Prone's /* paramName= */ check.
    584      '-parameters',
    585      # Jacoco does not currently support a higher value.
    586      '--release',
    587      '17',
    588      # Chromium only allows UTF8 source files.  Being explicit avoids
    589      # javac pulling a default encoding from the user's environment.
    590      '-encoding',
    591      'UTF-8',
    592      # Prevent compiler from compiling .java files not listed as inputs.
    593      # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
    594      '-sourcepath',
    595      ':',
    596      # protobuf-generated files fail this check (javadoc has @deprecated,
    597      # but method missing @Deprecated annotation).
    598      '-Xlint:-dep-ann',
    599      # Do not warn about finalize() methods. Android still intends to support
    600      # them.
    601      '-Xlint:-removal',
    602      # https://crbug.com/1441023
    603      '-J-XX:+PerfDisableSharedMem',
    604 
    605      # Disable all annotation processors (we run them via Turbine).
    606      '-proc:none',
    607  ]
    608 
    609  if extra_javac_args:
    610    javac_args.extend(extra_javac_args)
    611 
    612  if options.processorpath:
    613    javac_args.extend(['-processorpath', ':'.join(options.processorpath)])
    614  if options.processor_args:
    615    for arg in options.processor_args:
    616      javac_args.extend(['-A%s' % arg])
    617 
    618  javac_args.extend(options.javac_arg)
    619 
    620  do_it = lambda changes: _OnStaleMd5(changes,
    621                                      options,
    622                                      javac_cmd,
    623                                      javac_args,
    624                                      java_files,
    625                                      kt_files,
    626                                      use_errorprone=use_errorprone)
    627 
    628  if options.print_javac_command_line:
    629    if options.java_srcjars:
    630      raise Exception(
    631          '--print-javac-command-line does not work with --java-srcjars')
    632    do_it(None)
    633    return 0
    634 
    635  depfile_deps = options.classpath + options.processorpath
    636 
    637  output_paths = [options.jar_path]
    638  if not use_errorprone:
    639    jar_info_path = options.jar_path + '.info'
    640    output_paths.append(jar_info_path)
    641 
    642  # Incremental build optimization doesn't work for ErrorProne. Skip md5 check.
    643  if write_depfile_only:
    644    action_helpers.write_depfile(options.depfile, output_paths[0], depfile_deps)
    645  elif use_errorprone:
    646    do_it(None)
    647    action_helpers.write_depfile(options.depfile, output_paths[0], depfile_deps)
    648  else:
    649    # Files that are already inputs in GN should go in input_paths.
    650    input_paths = ([build_utils.JAVAC_PATH] + depfile_deps +
    651                   options.java_srcjars + java_files + kt_files)
    652    if options.header_jar:
    653      input_paths.append(options.header_jar)
    654    input_paths += [x[0] for x in options.additional_jar_files]
    655 
    656    input_strings = (
    657        javac_cmd + javac_args + options.classpath + java_files + kt_files + [
    658            options.warnings_as_errors, options.jar_info_exclude_globs,
    659            options.jar_info_include_globs
    660        ])
    661 
    662    # Use md5_check for |pass_changes| feature.
    663    md5_check.CallAndWriteDepfileIfStale(do_it,
    664                                         options,
    665                                         depfile_deps=depfile_deps,
    666                                         input_paths=input_paths,
    667                                         input_strings=input_strings,
    668                                         output_paths=output_paths,
    669                                         pass_changes=True)
    670  return 0
    671 
    672 
    673 if __name__ == '__main__':
    674  sys.exit(main(sys.argv[1:]))