xref: /aosp_15_r20/external/cronet/build/android/gyp/compile_java.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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        '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
424        '--add-exports=jdk.internal.opt/jdk.internal.opt=ALL-UNNAMED',
425        '-jar',
426        _JAVAC_EXTRACTOR,
427    ]
428    try:
429      # _RunCompiler()'s partial javac implementation does not support
430      # generating outputs in $KYTHE_OUTPUT_DIRECTORY.
431      _RunCompiler(changes,
432                   options,
433                   javac_extractor_cmd + javac_args,
434                   java_files,
435                   options.jar_path + '.javac_extractor',
436                   enable_partial_javac=False)
437    except build_utils.CalledProcessError as e:
438      # Having no index for particular target is better than failing entire
439      # codesearch. Log and error and move on.
440      logging.error('Could not generate kzip: %s', e)
441
442  intermediates_out_dir = None
443  jar_info_path = None
444  if not options.enable_errorprone:
445    # Delete any stale files in the generated directory. The purpose of
446    # options.generated_dir is for codesearch.
447    shutil.rmtree(options.generated_dir, True)
448    intermediates_out_dir = options.generated_dir
449
450    jar_info_path = options.jar_path + '.info'
451
452  # Compiles with Error Prone take twice as long to run as pure javac. Thus GN
453  # rules run both in parallel, with Error Prone only used for checks.
454  try:
455    _RunCompiler(changes,
456                 options,
457                 javac_cmd + javac_args,
458                 java_files,
459                 options.jar_path,
460                 kt_files=kt_files,
461                 jar_info_path=jar_info_path,
462                 intermediates_out_dir=intermediates_out_dir,
463                 enable_partial_javac=True)
464  except build_utils.CalledProcessError as e:
465    # Do not output stacktrace as it takes up space on gerrit UI, forcing
466    # you to click though to find the actual compilation error. It's never
467    # interesting to see the Python stacktrace for a Java compilation error.
468    sys.stderr.write(e.output)
469    sys.exit(1)
470
471  logging.info('Completed all steps in _OnStaleMd5')
472
473
474def _RunCompiler(changes,
475                 options,
476                 javac_cmd,
477                 java_files,
478                 jar_path,
479                 kt_files=None,
480                 jar_info_path=None,
481                 intermediates_out_dir=None,
482                 enable_partial_javac=False):
483  """Runs java compiler.
484
485  Args:
486    changes: md5_check.Changes object.
487    options: Object with command line flags.
488    javac_cmd: Command to execute.
489    java_files: List of java files passed from command line.
490    jar_path: Path of output jar file.
491    kt_files: List of Kotlin files passed from command line if any.
492    jar_info_path: Path of the .info file to generate.
493        If None, .info file will not be generated.
494    intermediates_out_dir: Directory for saving intermediate outputs.
495        If None a temporary directory is used.
496    enable_partial_javac: Enables compiling only Java files which have changed
497        in the special case that no method signatures have changed. This is
498        useful for large GN targets.
499        Not supported if compiling generates outputs other than |jar_path| and
500        |jar_info_path|.
501  """
502  logging.info('Starting _RunCompiler')
503
504  java_files = java_files.copy()
505  java_srcjars = options.java_srcjars
506  save_info_file = jar_info_path is not None
507
508  # Use jar_path's directory to ensure paths are relative (needed for goma).
509  temp_dir = jar_path + '.staging'
510  build_utils.DeleteDirectory(temp_dir)
511  os.makedirs(temp_dir)
512  info_file_context = None
513  try:
514    classes_dir = os.path.join(temp_dir, 'classes')
515    service_provider_configuration = os.path.join(
516        temp_dir, 'service_provider_configuration')
517
518    if java_files:
519      os.makedirs(classes_dir)
520
521      if enable_partial_javac:
522        all_changed_paths_are_java = all(
523            p.endswith(".java") for p in changes.IterChangedPaths())
524        if (all_changed_paths_are_java and not changes.HasStringChanges()
525            and os.path.exists(jar_path)
526            and (jar_info_path is None or os.path.exists(jar_info_path))):
527          # Log message is used by tests to determine whether partial javac
528          # optimization was used.
529          logging.info('Using partial javac optimization for %s compile' %
530                       (jar_path))
531
532          # Header jar corresponding to |java_files| did not change.
533          # As a build speed optimization (crbug.com/1170778), re-compile only
534          # java files which have changed. Re-use old jar .info file.
535          java_files = list(changes.IterChangedPaths())
536          java_srcjars = None
537
538          # Reuse old .info file.
539          save_info_file = False
540
541          build_utils.ExtractAll(jar_path, classes_dir, pattern='*.class')
542
543    if save_info_file:
544      info_file_context = _InfoFileContext(options.chromium_code,
545                                           options.jar_info_exclude_globs)
546
547    if intermediates_out_dir is None:
548      intermediates_out_dir = temp_dir
549
550    input_srcjars_dir = os.path.join(intermediates_out_dir, 'input_srcjars')
551
552    if java_srcjars:
553      logging.info('Extracting srcjars to %s', input_srcjars_dir)
554      build_utils.MakeDirectory(input_srcjars_dir)
555      for srcjar in options.java_srcjars:
556        extracted_files = build_utils.ExtractAll(
557            srcjar, no_clobber=True, path=input_srcjars_dir, pattern='*.java')
558        java_files.extend(extracted_files)
559        if save_info_file:
560          info_file_context.AddSrcJarSources(srcjar, extracted_files,
561                                             input_srcjars_dir)
562      logging.info('Done extracting srcjars')
563
564    if options.header_jar:
565      logging.info('Extracting service provider configs')
566      # Extract META-INF/services/* so that it can be copied into the output
567      # .jar
568      build_utils.ExtractAll(options.header_jar,
569                             no_clobber=True,
570                             path=service_provider_configuration,
571                             pattern='META-INF/services/*')
572      logging.info('Done extracting service provider configs')
573
574    if save_info_file and java_files:
575      info_file_context.SubmitFiles(java_files)
576      info_file_context.SubmitFiles(kt_files)
577
578    if java_files:
579      # Don't include the output directory in the initial set of args since it
580      # being in a temp dir makes it unstable (breaks md5 stamping).
581      cmd = list(javac_cmd)
582      cmd += ['-d', classes_dir]
583
584      if options.classpath:
585        cmd += ['-classpath', ':'.join(options.classpath)]
586
587      # Pass source paths as response files to avoid extremely long command
588      # lines that are tedius to debug.
589      java_files_rsp_path = os.path.join(temp_dir, 'files_list.txt')
590      with open(java_files_rsp_path, 'w') as f:
591        f.write(' '.join(java_files))
592      cmd += ['@' + java_files_rsp_path]
593
594      process_javac_output_partial = functools.partial(
595          ProcessJavacOutput, target_name=options.target_name)
596
597      logging.debug('Build command %s', cmd)
598      start = time.time()
599      build_utils.CheckOutput(cmd,
600                              print_stdout=options.chromium_code,
601                              stdout_filter=process_javac_output_partial,
602                              stderr_filter=process_javac_output_partial,
603                              fail_on_output=options.warnings_as_errors)
604      end = time.time() - start
605      logging.info('Java compilation took %ss', end)
606
607    CreateJarFile(jar_path, classes_dir, service_provider_configuration,
608                  options.additional_jar_files, options.kotlin_jar_path)
609
610    if save_info_file:
611      info_file_context.Commit(jar_info_path)
612
613    logging.info('Completed all steps in _RunCompiler')
614  finally:
615    if info_file_context:
616      info_file_context.Close()
617    shutil.rmtree(temp_dir)
618
619
620def _ParseOptions(argv):
621  parser = optparse.OptionParser()
622  action_helpers.add_depfile_arg(parser)
623
624  parser.add_option('--target-name', help='Fully qualified GN target name.')
625  parser.add_option('--skip-build-server',
626                    action='store_true',
627                    help='Avoid using the build server.')
628  parser.add_option('--use-build-server',
629                    action='store_true',
630                    help='Always use the build server.')
631  parser.add_option(
632      '--java-srcjars',
633      action='append',
634      default=[],
635      help='List of srcjars to include in compilation.')
636  parser.add_option(
637      '--generated-dir',
638      help='Subdirectory within target_gen_dir to place extracted srcjars and '
639      'annotation processor output for codesearch to find.')
640  parser.add_option('--classpath', action='append', help='Classpath to use.')
641  parser.add_option(
642      '--processorpath',
643      action='append',
644      help='GN list of jars that comprise the classpath used for Annotation '
645      'Processors.')
646  parser.add_option(
647      '--processor-arg',
648      dest='processor_args',
649      action='append',
650      help='key=value arguments for the annotation processors.')
651  parser.add_option(
652      '--additional-jar-file',
653      dest='additional_jar_files',
654      action='append',
655      help='Additional files to package into jar. By default, only Java .class '
656      'files are packaged into the jar. Files should be specified in '
657      'format <filename>:<path to be placed in jar>.')
658  parser.add_option(
659      '--jar-info-exclude-globs',
660      help='GN list of exclude globs to filter from generated .info files.')
661  parser.add_option(
662      '--chromium-code',
663      type='int',
664      help='Whether code being compiled should be built with stricter '
665      'warnings for chromium code.')
666  parser.add_option(
667      '--gomacc-path', help='When set, prefix javac command with gomacc')
668  parser.add_option(
669      '--errorprone-path', help='Use the Errorprone compiler at this path.')
670  parser.add_option(
671      '--enable-errorprone',
672      action='store_true',
673      help='Enable errorprone checks')
674  parser.add_option(
675      '--warnings-as-errors',
676      action='store_true',
677      help='Treat all warnings as errors.')
678  parser.add_option('--jar-path', help='Jar output path.')
679  parser.add_option(
680      '--javac-arg',
681      action='append',
682      default=[],
683      help='Additional arguments to pass to javac.')
684  parser.add_option(
685      '--enable-kythe-annotations',
686      action='store_true',
687      help='Enable generation of Kythe kzip, used for codesearch. Ensure '
688      'proper environment variables are set before using this flag.')
689  parser.add_option(
690      '--header-jar',
691      help='This is the header jar for the current target that contains '
692      'META-INF/services/* files to be included in the output jar.')
693  parser.add_option(
694      '--kotlin-jar-path',
695      help='Kotlin jar to be merged into the output jar. This contains the '
696      ".class files from this target's .kt files.")
697
698  options, args = parser.parse_args(argv)
699  build_utils.CheckOptions(options, parser, required=('jar_path', ))
700
701  options.classpath = action_helpers.parse_gn_list(options.classpath)
702  options.processorpath = action_helpers.parse_gn_list(options.processorpath)
703  options.java_srcjars = action_helpers.parse_gn_list(options.java_srcjars)
704  options.jar_info_exclude_globs = action_helpers.parse_gn_list(
705      options.jar_info_exclude_globs)
706
707  additional_jar_files = []
708  for arg in options.additional_jar_files or []:
709    filepath, jar_filepath = arg.split(':')
710    additional_jar_files.append((filepath, jar_filepath))
711  options.additional_jar_files = additional_jar_files
712
713  files = []
714  for arg in args:
715    # Interpret a path prefixed with @ as a file containing a list of sources.
716    if arg.startswith('@'):
717      files.extend(build_utils.ReadSourcesList(arg[1:]))
718    else:
719      files.append(arg)
720
721  # The target's .sources file contains both Java and Kotlin files. We use
722  # compile_kt.py to compile the Kotlin files to .class and header jars. Javac
723  # is run only on .java files.
724  java_files = [f for f in files if f.endswith('.java')]
725  # Kotlin files are needed to populate the info file and attribute size in
726  # supersize back to the appropriate Kotlin file.
727  kt_files = [f for f in files if f.endswith('.kt')]
728
729  return options, java_files, kt_files
730
731
732def main(argv):
733  build_utils.InitLogging('JAVAC_DEBUG')
734  argv = build_utils.ExpandFileArgs(argv)
735  options, java_files, kt_files = _ParseOptions(argv)
736
737  # Only use the build server for errorprone runs.
738  if (options.enable_errorprone and not options.skip_build_server
739      and server_utils.MaybeRunCommand(name=options.target_name,
740                                       argv=sys.argv,
741                                       stamp_file=options.jar_path,
742                                       force=options.use_build_server)):
743    return
744
745  javac_cmd = []
746  if options.gomacc_path:
747    javac_cmd.append(options.gomacc_path)
748  javac_cmd.append(build_utils.JAVAC_PATH)
749
750  javac_args = [
751      '-g',
752      # Jacoco does not currently support a higher value.
753      '--release',
754      '17',
755      # Chromium only allows UTF8 source files.  Being explicit avoids
756      # javac pulling a default encoding from the user's environment.
757      '-encoding',
758      'UTF-8',
759      # Prevent compiler from compiling .java files not listed as inputs.
760      # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
761      '-sourcepath',
762      ':',
763      # protobuf-generated files fail this check (javadoc has @deprecated,
764      # but method missing @Deprecated annotation).
765      '-Xlint:-dep-ann',
766      # Do not warn about finalize() methods. Android still intends to support
767      # them.
768      '-Xlint:-removal',
769      # https://crbug.com/1441023
770      '-J-XX:+PerfDisableSharedMem',
771  ]
772
773  if options.enable_errorprone:
774    # All errorprone args are passed space-separated in a single arg.
775    errorprone_flags = ['-Xplugin:ErrorProne']
776    # Make everything a warning so that when treat_warnings_as_errors is false,
777    # they do not fail the build.
778    errorprone_flags += ['-XepAllErrorsAsWarnings']
779    # Don't check generated files.
780    errorprone_flags += ['-XepDisableWarningsInGeneratedCode']
781    errorprone_flags.extend('-Xep:{}:OFF'.format(x)
782                            for x in ERRORPRONE_WARNINGS_TO_DISABLE)
783    errorprone_flags.extend('-Xep:{}:WARN'.format(x)
784                            for x in ERRORPRONE_WARNINGS_TO_ENABLE)
785
786    if ERRORPRONE_CHECKS_TO_APPLY:
787      errorprone_flags += [
788          '-XepPatchLocation:IN_PLACE',
789          '-XepPatchChecks:,' + ','.join(ERRORPRONE_CHECKS_TO_APPLY)
790      ]
791
792    # These are required to use JDK 16, and are taken directly from
793    # https://errorprone.info/docs/installation
794    javac_args += [
795        '-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
796        '-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
797        '-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
798        '-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED',
799        '-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
800        '-J--add-exports=jdk.compiler/com.sun.tools.javac.processing='
801        'ALL-UNNAMED',
802        '-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
803        '-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
804        '-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
805        '-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
806    ]
807
808    javac_args += ['-XDcompilePolicy=simple', ' '.join(errorprone_flags)]
809
810    # This flag quits errorprone after checks and before code generation, since
811    # we do not need errorprone outputs, this speeds up errorprone by 4 seconds
812    # for chrome_java.
813    if not ERRORPRONE_CHECKS_TO_APPLY:
814      javac_args += ['-XDshould-stop.ifNoError=FLOW']
815
816  # This effectively disables all annotation processors, even including
817  # annotation processors in service provider configuration files named
818  # META-INF/. See the following link for reference:
819  #     https://docs.oracle.com/en/java/javase/11/tools/javac.html
820  javac_args.extend(['-proc:none'])
821
822  if options.processorpath:
823    javac_args.extend(['-processorpath', ':'.join(options.processorpath)])
824  if options.processor_args:
825    for arg in options.processor_args:
826      javac_args.extend(['-A%s' % arg])
827
828  javac_args.extend(options.javac_arg)
829
830  classpath_inputs = options.classpath + options.processorpath
831
832  depfile_deps = classpath_inputs
833  # Files that are already inputs in GN should go in input_paths.
834  input_paths = ([build_utils.JAVAC_PATH] + depfile_deps +
835                 options.java_srcjars + java_files + kt_files)
836  if options.header_jar:
837    input_paths.append(options.header_jar)
838  input_paths += [x[0] for x in options.additional_jar_files]
839
840  output_paths = [options.jar_path]
841  if not options.enable_errorprone:
842    output_paths += [options.jar_path + '.info']
843
844  input_strings = (javac_cmd + javac_args + options.classpath + java_files +
845                   kt_files +
846                   [options.warnings_as_errors, options.jar_info_exclude_globs])
847
848  # Use md5_check for |pass_changes| feature.
849  md5_check.CallAndWriteDepfileIfStale(lambda changes: _OnStaleMd5(
850      changes, options, javac_cmd, javac_args, java_files, kt_files),
851                                       options,
852                                       depfile_deps=depfile_deps,
853                                       input_paths=input_paths,
854                                       input_strings=input_strings,
855                                       output_paths=output_paths,
856                                       pass_changes=True)
857
858
859if __name__ == '__main__':
860  sys.exit(main(sys.argv[1:]))
861