• 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 functools
8import logging
9import multiprocessing
10import optparse
11import os
12import re
13import shutil
14import sys
15import time
16import zipfile
17
18import javac_output_processor
19from util import build_utils
20from util import md5_check
21from util import jar_info_utils
22from util import server_utils
23import action_helpers  # build_utils adds //build to sys.path.
24import zip_helpers
25
26_JAVAC_EXTRACTOR = os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party',
27                                'android_prebuilts', 'build_tools', 'common',
28                                'framework', 'javac_extractor.jar')
29
30# Add a check here to cause the suggested fix to be applied while compiling.
31# Use this when trying to enable more checks.
32ERRORPRONE_CHECKS_TO_APPLY = []
33
34# Full list of checks: https://errorprone.info/bugpatterns
35ERRORPRONE_WARNINGS_TO_DISABLE = [
36    # Temporarily disabling to roll doubledown.
37    # TODO(wnwen): Re-enable this upstream.
38    'InlineMeInliner',
39    # The following are super useful, but existing issues need to be fixed first
40    # before they can start failing the build on new errors.
41    'InvalidParam',
42    'InvalidLink',
43    'InvalidInlineTag',
44    'EmptyBlockTag',
45    'PublicConstructorForAbstractClass',
46    'InvalidBlockTag',
47    'StaticAssignmentInConstructor',
48    'MutablePublicArray',
49    'UnescapedEntity',
50    'NonCanonicalType',
51    'AlmostJavadoc',
52    'ReturnValueIgnored',
53    # The following are added for errorprone update: https://crbug.com/1216032
54    'InlineMeSuggester',
55    'DoNotClaimAnnotations',
56    'JavaUtilDate',
57    'IdentityHashMapUsage',
58    'UnnecessaryMethodReference',
59    'LongFloatConversion',
60    'CharacterGetNumericValue',
61    'ErroneousThreadPoolConstructorChecker',
62    'StaticMockMember',
63    'MissingSuperCall',
64    'ToStringReturnsNull',
65    # If possible, this should be automatically fixed if turned on:
66    'MalformedInlineTag',
67    # TODO(crbug.com/834807): Follow steps in bug
68    'DoubleBraceInitialization',
69    # TODO(crbug.com/834790): Follow steps in bug.
70    'CatchAndPrintStackTrace',
71    # TODO(crbug.com/801210): Follow steps in bug.
72    'SynchronizeOnNonFinalField',
73    # TODO(crbug.com/802073): Follow steps in bug.
74    'TypeParameterUnusedInFormals',
75    # TODO(crbug.com/803484): Follow steps in bug.
76    'CatchFail',
77    # TODO(crbug.com/803485): Follow steps in bug.
78    'JUnitAmbiguousTestClass',
79    # Android platform default is always UTF-8.
80    # https://developer.android.com/reference/java/nio/charset/Charset.html#defaultCharset()
81    'DefaultCharset',
82    # Low priority since there are lots of tags that don't fit this check.
83    'UnrecognisedJavadocTag',
84    # Low priority since the alternatives still work.
85    'JdkObsolete',
86    # We don't use that many lambdas.
87    'FunctionalInterfaceClash',
88    # There are lots of times when we just want to post a task.
89    'FutureReturnValueIgnored',
90    # Nice to be explicit about operators, but not necessary.
91    'OperatorPrecedence',
92    # Just false positives in our code.
93    'ThreadJoinLoop',
94    # Low priority corner cases with String.split.
95    # Linking Guava and using Splitter was rejected
96    # in the https://chromium-review.googlesource.com/c/chromium/src/+/871630.
97    'StringSplitter',
98    # Preferred to use another method since it propagates exceptions better.
99    'ClassNewInstance',
100    # Nice to have static inner classes but not necessary.
101    'ClassCanBeStatic',
102    # Explicit is better than implicit.
103    'FloatCast',
104    # Results in false positives.
105    'ThreadLocalUsage',
106    # Also just false positives.
107    'Finally',
108    # False positives for Chromium.
109    'FragmentNotInstantiable',
110    # Low priority to fix.
111    'HidingField',
112    # Low priority.
113    'IntLongMath',
114    # Low priority.
115    'BadComparable',
116    # Low priority.
117    'EqualsHashCode',
118    # Nice to fix but low priority.
119    'TypeParameterShadowing',
120    # Good to have immutable enums, also low priority.
121    'ImmutableEnumChecker',
122    # False positives for testing.
123    'InputStreamSlowMultibyteRead',
124    # Nice to have better primitives.
125    'BoxedPrimitiveConstructor',
126    # Not necessary for tests.
127    'OverrideThrowableToString',
128    # Nice to have better type safety.
129    'CollectionToArraySafeParameter',
130    # Makes logcat debugging more difficult, and does not provide obvious
131    # benefits in the Chromium codebase.
132    'ObjectToString',
133    # Triggers on private methods that are @CalledByNative.
134    'UnusedMethod',
135    # Triggers on generated R.java files.
136    'UnusedVariable',
137    # Not that useful.
138    'UnsafeReflectiveConstructionCast',
139    # Not that useful.
140    'MixedMutabilityReturnType',
141    # Nice to have.
142    'EqualsGetClass',
143    # A lot of false-positives from CharSequence.equals().
144    'UndefinedEquals',
145    # Nice to have.
146    'ExtendingJUnitAssert',
147    # Nice to have.
148    'SystemExitOutsideMain',
149    # Nice to have.
150    'TypeParameterNaming',
151    # Nice to have.
152    'UnusedException',
153    # Nice to have.
154    'UngroupedOverloads',
155    # Nice to have.
156    'FunctionalInterfaceClash',
157    # Nice to have.
158    'InconsistentOverloads',
159    # Dagger generated code triggers this.
160    'SameNameButDifferent',
161    # Nice to have.
162    'UnnecessaryLambda',
163    # Nice to have.
164    'UnnecessaryAnonymousClass',
165    # Nice to have.
166    'LiteProtoToString',
167    # Nice to have.
168    'MissingSummary',
169    # Nice to have.
170    'ReturnFromVoid',
171    # Nice to have.
172    'EmptyCatch',
173    # Nice to have.
174    'BadImport',
175    # Nice to have.
176    'UseCorrectAssertInTests',
177    # Nice to have.
178    'InlineFormatString',
179    # Nice to have.
180    'DefaultPackage',
181    # Must be off since we are now passing in annotation processor generated
182    # code as a source jar (deduplicating work with turbine).
183    'RefersToDaggerCodegen',
184    # We already have presubmit checks for this. Not necessary to warn on
185    # every build.
186    'RemoveUnusedImports',
187    # We do not care about unnecessary parenthesis enough to check for them.
188    'UnnecessaryParentheses',
189    # The only time we trigger this is when it is better to be explicit in a
190    # list of unicode characters, e.g. FindAddress.java
191    'UnicodeEscape',
192    # Nice to have.
193    'AlreadyChecked',
194]
195
196# Full list of checks: https://errorprone.info/bugpatterns
197# Only those marked as "experimental" need to be listed here in order to be
198# enabled.
199ERRORPRONE_WARNINGS_TO_ENABLE = [
200    'BinderIdentityRestoredDangerously',
201    'EmptyIf',
202    'EqualsBrokenForNull',
203    'InvalidThrows',
204    'LongLiteralLowerCaseSuffix',
205    'MultiVariableDeclaration',
206    'RedundantOverride',
207    'StaticQualifiedUsingExpression',
208    'StringEquality',
209    'TimeUnitMismatch',
210    'UnnecessaryStaticImport',
211    'UseBinds',
212    'WildcardImport',
213]
214
215
216def ProcessJavacOutput(output, target_name):
217  # These warnings cannot be suppressed even for third party code. Deprecation
218  # warnings especially do not help since we must support older android version.
219  deprecated_re = re.compile(r'Note: .* uses? or overrides? a deprecated API')
220  unchecked_re = re.compile(
221      r'(Note: .* uses? unchecked or unsafe operations.)$')
222  recompile_re = re.compile(r'(Note: Recompile with -Xlint:.* for details.)$')
223
224  def ApplyFilters(line):
225    return not (deprecated_re.match(line) or unchecked_re.match(line)
226                or recompile_re.match(line))
227
228  output = build_utils.FilterReflectiveAccessJavaWarnings(output)
229
230  # Warning currently cannot be silenced via javac flag.
231  if 'Unsafe is internal proprietary API' in output:
232    # Example:
233    # HiddenApiBypass.java:69: warning: Unsafe is internal proprietary API and
234    # may be removed in a future release
235    # import sun.misc.Unsafe;
236    #                 ^
237    output = re.sub(r'.*?Unsafe is internal proprietary API[\s\S]*?\^\n', '',
238                    output)
239    output = re.sub(r'\d+ warnings\n', '', output)
240
241  lines = (l for l in output.split('\n') if ApplyFilters(l))
242
243  output_processor = javac_output_processor.JavacOutputProcessor(target_name)
244  lines = output_processor.Process(lines)
245
246  return '\n'.join(lines)
247
248
249def CreateJarFile(jar_path,
250                  classes_dir,
251                  service_provider_configuration_dir=None,
252                  additional_jar_files=None,
253                  extra_classes_jar=None):
254  """Zips files from compilation into a single jar."""
255  logging.info('Start creating jar file: %s', jar_path)
256  with action_helpers.atomic_output(jar_path) as f:
257    with zipfile.ZipFile(f.name, 'w') as z:
258      zip_helpers.zip_directory(z, classes_dir)
259      if service_provider_configuration_dir:
260        config_files = build_utils.FindInDirectory(
261            service_provider_configuration_dir)
262        for config_file in config_files:
263          zip_path = os.path.relpath(config_file,
264                                     service_provider_configuration_dir)
265          zip_helpers.add_to_zip_hermetic(z, zip_path, src_path=config_file)
266
267      if additional_jar_files:
268        for src_path, zip_path in additional_jar_files:
269          zip_helpers.add_to_zip_hermetic(z, zip_path, src_path=src_path)
270      if extra_classes_jar:
271        path_transform = lambda p: p if p.endswith('.class') else None
272        zip_helpers.merge_zips(z, [extra_classes_jar],
273                               path_transform=path_transform)
274  logging.info('Completed jar file: %s', jar_path)
275
276
277def _ParsePackageAndClassNames(source_file):
278  """This should support both Java and Kotlin files."""
279  package_name = ''
280  class_names = []
281  with open(source_file) as f:
282    for l in f:
283      # Strip unindented comments.
284      # Considers a leading * as a continuation of a multi-line comment (our
285      # linter doesn't enforce a space before it like there should be).
286      l = re.sub(r'^(?://.*|/?\*.*?(?:\*/\s*|$))', '', l)
287      # Stripping things between double quotes (strings), so if the word "class"
288      # shows up in a string this doesn't trigger. This isn't strictly correct
289      # (with escaped quotes) but covers a very large percentage of cases.
290      l = re.sub('(?:".*?")', '', l)
291
292      # Java lines end in semicolon, whereas Kotlin lines do not.
293      m = re.match(r'package\s+(.*?)(;|\s*$)', l)
294      if m and not package_name:
295        package_name = m.group(1)
296
297      # Not exactly a proper parser, but works for sources that Chrome uses.
298      # In order to not match nested classes, it just checks for lack of indent.
299      m = re.match(r'(?:\S.*?)?(?:class|@?interface|enum)\s+(.+?)\b', l)
300      if m:
301        class_names.append(m.group(1))
302  return package_name, class_names
303
304
305def _ProcessSourceFileForInfo(source_file):
306  package_name, class_names = _ParsePackageAndClassNames(source_file)
307  return source_file, package_name, class_names
308
309
310class _InfoFileContext:
311  """Manages the creation of the class->source file .info file."""
312
313  def __init__(self, chromium_code, excluded_globs):
314    self._chromium_code = chromium_code
315    self._excluded_globs = excluded_globs
316    # Map of .java path -> .srcjar/nested/path.java.
317    self._srcjar_files = {}
318    # List of generators from pool.imap_unordered().
319    self._results = []
320    # Lazily created multiprocessing.Pool.
321    self._pool = None
322
323  def AddSrcJarSources(self, srcjar_path, extracted_paths, parent_dir):
324    for path in extracted_paths:
325      # We want the path inside the srcjar so the viewer can have a tree
326      # structure.
327      self._srcjar_files[path] = '{}/{}'.format(
328          srcjar_path, os.path.relpath(path, parent_dir))
329
330  def SubmitFiles(self, source_files):
331    if not source_files:
332      return
333    if self._pool is None:
334      # Restrict to just one process to not slow down compiling. Compiling
335      # is always slower.
336      self._pool = multiprocessing.Pool(1)
337    logging.info('Submitting %d files for info', len(source_files))
338    self._results.append(
339        self._pool.imap_unordered(_ProcessSourceFileForInfo,
340                                  source_files,
341                                  chunksize=1000))
342
343  def _CheckPathMatchesClassName(self, source_file, package_name, class_name):
344    if source_file.endswith('.java'):
345      parts = package_name.split('.') + [class_name + '.java']
346    else:
347      parts = package_name.split('.') + [class_name + '.kt']
348    expected_suffix = os.path.sep.join(parts)
349    if not source_file.endswith(expected_suffix):
350      raise Exception(('Source package+class name do not match its path.\n'
351                       'Actual path: %s\nExpected path: %s') %
352                      (source_file, expected_suffix))
353
354  def _ProcessInfo(self, java_file, package_name, class_names, source):
355    for class_name in class_names:
356      yield '{}.{}'.format(package_name, class_name)
357      # Skip aidl srcjars since they don't indent code correctly.
358      if '_aidl.srcjar' in source:
359        continue
360      assert not self._chromium_code or len(class_names) == 1, (
361          'Chromium java files must only have one class: {}'.format(source))
362      if self._chromium_code:
363        # This check is not necessary but nice to check this somewhere.
364        self._CheckPathMatchesClassName(java_file, package_name, class_names[0])
365
366  def _ShouldIncludeInJarInfo(self, fully_qualified_name):
367    name_as_class_glob = fully_qualified_name.replace('.', '/') + '.class'
368    return not build_utils.MatchesGlob(name_as_class_glob, self._excluded_globs)
369
370  def _Collect(self):
371    if self._pool is None:
372      return {}
373    ret = {}
374    for result in self._results:
375      for java_file, package_name, class_names in result:
376        source = self._srcjar_files.get(java_file, java_file)
377        for fully_qualified_name in self._ProcessInfo(java_file, package_name,
378                                                      class_names, source):
379          if self._ShouldIncludeInJarInfo(fully_qualified_name):
380            ret[fully_qualified_name] = java_file
381    return ret
382
383  def Close(self):
384    # Work around for Python 2.x bug with multiprocessing and daemon threads:
385    # https://bugs.python.org/issue4106
386    if self._pool is not None:
387      logging.info('Joining multiprocessing.Pool')
388      self._pool.terminate()
389      self._pool.join()
390      logging.info('Done.')
391
392  def Commit(self, output_path):
393    """Writes a .jar.info file.
394
395    Maps fully qualified names for classes to either the java file that they
396    are defined in or the path of the srcjar that they came from.
397    """
398    logging.info('Collecting info file entries')
399    entries = self._Collect()
400
401    logging.info('Writing info file: %s', output_path)
402    with action_helpers.atomic_output(output_path, mode='wb') as f:
403      jar_info_utils.WriteJarInfoFile(f, entries, self._srcjar_files)
404    logging.info('Completed info file: %s', output_path)
405
406
407def _OnStaleMd5(changes, options, javac_cmd, javac_args, java_files, kt_files):
408  logging.info('Starting _OnStaleMd5')
409  if options.enable_kythe_annotations:
410    # Kythe requires those env variables to be set and compile_java.py does the
411    # same
412    if not os.environ.get('KYTHE_ROOT_DIRECTORY') or \
413        not os.environ.get('KYTHE_OUTPUT_DIRECTORY'):
414      raise Exception('--enable-kythe-annotations requires '
415                      'KYTHE_ROOT_DIRECTORY and KYTHE_OUTPUT_DIRECTORY '
416                      'environment variables to be set.')
417    javac_extractor_cmd = build_utils.JavaCmd() + [
418        '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
419        '--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
420        '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
421        '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
422        '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
423        '-jar',
424        _JAVAC_EXTRACTOR,
425    ]
426    try:
427      # _RunCompiler()'s partial javac implementation does not support
428      # generating outputs in $KYTHE_OUTPUT_DIRECTORY.
429      _RunCompiler(changes,
430                   options,
431                   javac_extractor_cmd + javac_args,
432                   java_files,
433                   options.jar_path + '.javac_extractor',
434                   enable_partial_javac=False)
435    except build_utils.CalledProcessError as e:
436      # Having no index for particular target is better than failing entire
437      # codesearch. Log and error and move on.
438      logging.error('Could not generate kzip: %s', e)
439
440  intermediates_out_dir = None
441  jar_info_path = None
442  if not options.enable_errorprone:
443    # Delete any stale files in the generated directory. The purpose of
444    # options.generated_dir is for codesearch.
445    shutil.rmtree(options.generated_dir, True)
446    intermediates_out_dir = options.generated_dir
447
448    jar_info_path = options.jar_path + '.info'
449
450  # Compiles with Error Prone take twice as long to run as pure javac. Thus GN
451  # rules run both in parallel, with Error Prone only used for checks.
452  try:
453    _RunCompiler(changes,
454                 options,
455                 javac_cmd + javac_args,
456                 java_files,
457                 options.jar_path,
458                 kt_files=kt_files,
459                 jar_info_path=jar_info_path,
460                 intermediates_out_dir=intermediates_out_dir,
461                 enable_partial_javac=True)
462  except build_utils.CalledProcessError as e:
463    # Do not output stacktrace as it takes up space on gerrit UI, forcing
464    # you to click though to find the actual compilation error. It's never
465    # interesting to see the Python stacktrace for a Java compilation error.
466    sys.stderr.write(e.output)
467    sys.exit(1)
468
469  logging.info('Completed all steps in _OnStaleMd5')
470
471
472def _RunCompiler(changes,
473                 options,
474                 javac_cmd,
475                 java_files,
476                 jar_path,
477                 kt_files=None,
478                 jar_info_path=None,
479                 intermediates_out_dir=None,
480                 enable_partial_javac=False):
481  """Runs java compiler.
482
483  Args:
484    changes: md5_check.Changes object.
485    options: Object with command line flags.
486    javac_cmd: Command to execute.
487    java_files: List of java files passed from command line.
488    jar_path: Path of output jar file.
489    kt_files: List of Kotlin files passed from command line if any.
490    jar_info_path: Path of the .info file to generate.
491        If None, .info file will not be generated.
492    intermediates_out_dir: Directory for saving intermediate outputs.
493        If None a temporary directory is used.
494    enable_partial_javac: Enables compiling only Java files which have changed
495        in the special case that no method signatures have changed. This is
496        useful for large GN targets.
497        Not supported if compiling generates outputs other than |jar_path| and
498        |jar_info_path|.
499  """
500  logging.info('Starting _RunCompiler')
501
502  java_files = java_files.copy()
503  java_srcjars = options.java_srcjars
504  save_info_file = jar_info_path is not None
505
506  # Use jar_path's directory to ensure paths are relative (needed for goma).
507  temp_dir = jar_path + '.staging'
508  build_utils.DeleteDirectory(temp_dir)
509  os.makedirs(temp_dir)
510  info_file_context = None
511  try:
512    classes_dir = os.path.join(temp_dir, 'classes')
513    service_provider_configuration = os.path.join(
514        temp_dir, 'service_provider_configuration')
515
516    if java_files:
517      os.makedirs(classes_dir)
518
519      if enable_partial_javac:
520        all_changed_paths_are_java = all(
521            p.endswith(".java") for p in changes.IterChangedPaths())
522        if (all_changed_paths_are_java and not changes.HasStringChanges()
523            and os.path.exists(jar_path)
524            and (jar_info_path is None or os.path.exists(jar_info_path))):
525          # Log message is used by tests to determine whether partial javac
526          # optimization was used.
527          logging.info('Using partial javac optimization for %s compile' %
528                       (jar_path))
529
530          # Header jar corresponding to |java_files| did not change.
531          # As a build speed optimization (crbug.com/1170778), re-compile only
532          # java files which have changed. Re-use old jar .info file.
533          java_files = list(changes.IterChangedPaths())
534          java_srcjars = None
535
536          # Reuse old .info file.
537          save_info_file = False
538
539          build_utils.ExtractAll(jar_path, classes_dir, pattern='*.class')
540
541    if save_info_file:
542      info_file_context = _InfoFileContext(options.chromium_code,
543                                           options.jar_info_exclude_globs)
544
545    if intermediates_out_dir is None:
546      intermediates_out_dir = temp_dir
547
548    input_srcjars_dir = os.path.join(intermediates_out_dir, 'input_srcjars')
549
550    if java_srcjars:
551      logging.info('Extracting srcjars to %s', input_srcjars_dir)
552      build_utils.MakeDirectory(input_srcjars_dir)
553      for srcjar in options.java_srcjars:
554        extracted_files = build_utils.ExtractAll(
555            srcjar, no_clobber=True, path=input_srcjars_dir, pattern='*.java')
556        java_files.extend(extracted_files)
557        if save_info_file:
558          info_file_context.AddSrcJarSources(srcjar, extracted_files,
559                                             input_srcjars_dir)
560      logging.info('Done extracting srcjars')
561
562    if options.header_jar:
563      logging.info('Extracting service provider configs')
564      # Extract META-INF/services/* so that it can be copied into the output
565      # .jar
566      build_utils.ExtractAll(options.header_jar,
567                             no_clobber=True,
568                             path=service_provider_configuration,
569                             pattern='META-INF/services/*')
570      logging.info('Done extracting service provider configs')
571
572    if save_info_file and java_files:
573      info_file_context.SubmitFiles(java_files)
574      info_file_context.SubmitFiles(kt_files)
575
576    if java_files:
577      # Don't include the output directory in the initial set of args since it
578      # being in a temp dir makes it unstable (breaks md5 stamping).
579      cmd = list(javac_cmd)
580      cmd += ['-d', classes_dir]
581
582      if options.classpath:
583        cmd += ['-classpath', ':'.join(options.classpath)]
584
585      # Pass source paths as response files to avoid extremely long command
586      # lines that are tedius to debug.
587      java_files_rsp_path = os.path.join(temp_dir, 'files_list.txt')
588      with open(java_files_rsp_path, 'w') as f:
589        f.write(' '.join(java_files))
590      cmd += ['@' + java_files_rsp_path]
591
592      process_javac_output_partial = functools.partial(
593          ProcessJavacOutput, target_name=options.target_name)
594
595      logging.debug('Build command %s', cmd)
596      start = time.time()
597      build_utils.CheckOutput(cmd,
598                              print_stdout=options.chromium_code,
599                              stdout_filter=process_javac_output_partial,
600                              stderr_filter=process_javac_output_partial,
601                              fail_on_output=options.warnings_as_errors)
602      end = time.time() - start
603      logging.info('Java compilation took %ss', end)
604
605    CreateJarFile(jar_path, classes_dir, service_provider_configuration,
606                  options.additional_jar_files, options.kotlin_jar_path)
607
608    if save_info_file:
609      info_file_context.Commit(jar_info_path)
610
611    logging.info('Completed all steps in _RunCompiler')
612  finally:
613    if info_file_context:
614      info_file_context.Close()
615    shutil.rmtree(temp_dir)
616
617
618def _ParseOptions(argv):
619  parser = optparse.OptionParser()
620  action_helpers.add_depfile_arg(parser)
621
622  parser.add_option('--target-name', help='Fully qualified GN target name.')
623  parser.add_option('--skip-build-server',
624                    action='store_true',
625                    help='Avoid using the build server.')
626  parser.add_option('--use-build-server',
627                    action='store_true',
628                    help='Always use the build server.')
629  parser.add_option(
630      '--java-srcjars',
631      action='append',
632      default=[],
633      help='List of srcjars to include in compilation.')
634  parser.add_option(
635      '--generated-dir',
636      help='Subdirectory within target_gen_dir to place extracted srcjars and '
637      'annotation processor output for codesearch to find.')
638  parser.add_option('--classpath', action='append', help='Classpath to use.')
639  parser.add_option(
640      '--processorpath',
641      action='append',
642      help='GN list of jars that comprise the classpath used for Annotation '
643      'Processors.')
644  parser.add_option(
645      '--processor-arg',
646      dest='processor_args',
647      action='append',
648      help='key=value arguments for the annotation processors.')
649  parser.add_option(
650      '--additional-jar-file',
651      dest='additional_jar_files',
652      action='append',
653      help='Additional files to package into jar. By default, only Java .class '
654      'files are packaged into the jar. Files should be specified in '
655      'format <filename>:<path to be placed in jar>.')
656  parser.add_option(
657      '--jar-info-exclude-globs',
658      help='GN list of exclude globs to filter from generated .info files.')
659  parser.add_option(
660      '--chromium-code',
661      type='int',
662      help='Whether code being compiled should be built with stricter '
663      'warnings for chromium code.')
664  parser.add_option(
665      '--gomacc-path', help='When set, prefix javac command with gomacc')
666  parser.add_option(
667      '--errorprone-path', help='Use the Errorprone compiler at this path.')
668  parser.add_option(
669      '--enable-errorprone',
670      action='store_true',
671      help='Enable errorprone checks')
672  parser.add_option(
673      '--warnings-as-errors',
674      action='store_true',
675      help='Treat all warnings as errors.')
676  parser.add_option('--jar-path', help='Jar output path.')
677  parser.add_option(
678      '--javac-arg',
679      action='append',
680      default=[],
681      help='Additional arguments to pass to javac.')
682  parser.add_option(
683      '--enable-kythe-annotations',
684      action='store_true',
685      help='Enable generation of Kythe kzip, used for codesearch. Ensure '
686      'proper environment variables are set before using this flag.')
687  parser.add_option(
688      '--header-jar',
689      help='This is the header jar for the current target that contains '
690      'META-INF/services/* files to be included in the output jar.')
691  parser.add_option(
692      '--kotlin-jar-path',
693      help='Kotlin jar to be merged into the output jar. This contains the '
694      ".class files from this target's .kt files.")
695
696  options, args = parser.parse_args(argv)
697  build_utils.CheckOptions(options, parser, required=('jar_path', ))
698
699  options.classpath = action_helpers.parse_gn_list(options.classpath)
700  options.processorpath = action_helpers.parse_gn_list(options.processorpath)
701  options.java_srcjars = action_helpers.parse_gn_list(options.java_srcjars)
702  options.jar_info_exclude_globs = action_helpers.parse_gn_list(
703      options.jar_info_exclude_globs)
704
705  additional_jar_files = []
706  for arg in options.additional_jar_files or []:
707    filepath, jar_filepath = arg.split(':')
708    additional_jar_files.append((filepath, jar_filepath))
709  options.additional_jar_files = additional_jar_files
710
711  files = []
712  for arg in args:
713    # Interpret a path prefixed with @ as a file containing a list of sources.
714    if arg.startswith('@'):
715      files.extend(build_utils.ReadSourcesList(arg[1:]))
716    else:
717      files.append(arg)
718
719  # The target's .sources file contains both Java and Kotlin files. We use
720  # compile_kt.py to compile the Kotlin files to .class and header jars. Javac
721  # is run only on .java files.
722  java_files = [f for f in files if f.endswith('.java')]
723  # Kotlin files are needed to populate the info file and attribute size in
724  # supersize back to the appropriate Kotlin file.
725  kt_files = [f for f in files if f.endswith('.kt')]
726
727  return options, java_files, kt_files
728
729
730def main(argv):
731  build_utils.InitLogging('JAVAC_DEBUG')
732  argv = build_utils.ExpandFileArgs(argv)
733  options, java_files, kt_files = _ParseOptions(argv)
734
735  # Only use the build server for errorprone runs.
736  if (options.enable_errorprone and not options.skip_build_server
737      and server_utils.MaybeRunCommand(name=options.target_name,
738                                       argv=sys.argv,
739                                       stamp_file=options.jar_path,
740                                       force=options.use_build_server)):
741    return
742
743  javac_cmd = []
744  if options.gomacc_path:
745    javac_cmd.append(options.gomacc_path)
746  javac_cmd.append(build_utils.JAVAC_PATH)
747
748  javac_args = [
749      '-g',
750      # Jacoco does not currently support a higher value.
751      '--release',
752      '17',
753      # Chromium only allows UTF8 source files.  Being explicit avoids
754      # javac pulling a default encoding from the user's environment.
755      '-encoding',
756      'UTF-8',
757      # Prevent compiler from compiling .java files not listed as inputs.
758      # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
759      '-sourcepath',
760      ':',
761      # protobuf-generated files fail this check (javadoc has @deprecated,
762      # but method missing @Deprecated annotation).
763      '-Xlint:-dep-ann',
764      # Do not warn about finalize() methods. Android still intends to support
765      # them.
766      '-Xlint:-removal',
767  ]
768
769  if options.enable_errorprone:
770    # All errorprone args are passed space-separated in a single arg.
771    errorprone_flags = ['-Xplugin:ErrorProne']
772    # Make everything a warning so that when treat_warnings_as_errors is false,
773    # they do not fail the build.
774    errorprone_flags += ['-XepAllErrorsAsWarnings']
775    # Don't check generated files.
776    errorprone_flags += ['-XepDisableWarningsInGeneratedCode']
777    errorprone_flags.extend('-Xep:{}:OFF'.format(x)
778                            for x in ERRORPRONE_WARNINGS_TO_DISABLE)
779    errorprone_flags.extend('-Xep:{}:WARN'.format(x)
780                            for x in ERRORPRONE_WARNINGS_TO_ENABLE)
781
782    if ERRORPRONE_CHECKS_TO_APPLY:
783      errorprone_flags += [
784          '-XepPatchLocation:IN_PLACE',
785          '-XepPatchChecks:,' + ','.join(ERRORPRONE_CHECKS_TO_APPLY)
786      ]
787
788    # These are required to use JDK 16, and are taken directly from
789    # https://errorprone.info/docs/installation
790    javac_args += [
791        '-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
792        '-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
793        '-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
794        '-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED',
795        '-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
796        '-J--add-exports=jdk.compiler/com.sun.tools.javac.processing='
797        'ALL-UNNAMED',
798        '-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
799        '-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
800        '-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
801        '-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
802    ]
803
804    javac_args += ['-XDcompilePolicy=simple', ' '.join(errorprone_flags)]
805
806    # This flag quits errorprone after checks and before code generation, since
807    # we do not need errorprone outputs, this speeds up errorprone by 4 seconds
808    # for chrome_java.
809    if not ERRORPRONE_CHECKS_TO_APPLY:
810      javac_args += ['-XDshould-stop.ifNoError=FLOW']
811
812  # This effectively disables all annotation processors, even including
813  # annotation processors in service provider configuration files named
814  # META-INF/. See the following link for reference:
815  #     https://docs.oracle.com/en/java/javase/11/tools/javac.html
816  javac_args.extend(['-proc:none'])
817
818  if options.processorpath:
819    javac_args.extend(['-processorpath', ':'.join(options.processorpath)])
820  if options.processor_args:
821    for arg in options.processor_args:
822      javac_args.extend(['-A%s' % arg])
823
824  javac_args.extend(options.javac_arg)
825
826  classpath_inputs = options.classpath + options.processorpath
827
828  depfile_deps = classpath_inputs
829  # Files that are already inputs in GN should go in input_paths.
830  input_paths = ([build_utils.JAVAC_PATH] + depfile_deps +
831                 options.java_srcjars + java_files + kt_files)
832  if options.header_jar:
833    input_paths.append(options.header_jar)
834  input_paths += [x[0] for x in options.additional_jar_files]
835
836  output_paths = [options.jar_path]
837  if not options.enable_errorprone:
838    output_paths += [options.jar_path + '.info']
839
840  input_strings = (javac_cmd + javac_args + options.classpath + java_files +
841                   kt_files +
842                   [options.warnings_as_errors, options.jar_info_exclude_globs])
843
844  # Use md5_check for |pass_changes| feature.
845  md5_check.CallAndWriteDepfileIfStale(lambda changes: _OnStaleMd5(
846      changes, options, javac_cmd, javac_args, java_files, kt_files),
847                                       options,
848                                       depfile_deps=depfile_deps,
849                                       input_paths=input_paths,
850                                       input_strings=input_strings,
851                                       output_paths=output_paths,
852                                       pass_changes=True)
853
854
855if __name__ == '__main__':
856  sys.exit(main(sys.argv[1:]))
857