• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7import collections
8import functools
9import itertools
10import logging
11import argparse
12import os
13import pathlib
14import re
15import shlex
16import shutil
17import sys
18import time
19import zipfile
20
21import javac_output_processor
22from util import build_utils
23from util import md5_check
24from util import jar_info_utils
25from util import server_utils
26import action_helpers  # build_utils adds //build to sys.path.
27import 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
34def 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
67def 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
113def 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
156class _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
230def _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
307def _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
469def _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
571def 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
673if __name__ == '__main__':
674  sys.exit(main(sys.argv[1:]))
675