xref: /aosp_15_r20/external/angle/build/android/gyp/compile_java.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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 optparse
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# Add a check here to cause the suggested fix to be applied while compiling.
34# Use this when trying to enable more checks.
35ERRORPRONE_CHECKS_TO_APPLY = []
36
37# Checks to disable in tests.
38TESTONLY_ERRORPRONE_WARNINGS_TO_DISABLE = [
39    # Too much effort to enable.
40    'UnusedVariable',
41    # These are allowed in tests.
42    'NoStreams',
43]
44
45# Full list of checks: https://errorprone.info/bugpatterns
46ERRORPRONE_WARNINGS_TO_DISABLE = [
47    'InlineMeInliner',
48    'InlineMeSuggester',
49    # High priority to enable:
50    'HidingField',
51    'AlreadyChecked',
52    'DirectInvocationOnMock',
53    'MockNotUsedInProduction',
54    # High priority to enable in non-tests:
55    'JdkObsolete',
56    'ReturnValueIgnored',
57    'StaticAssignmentInConstructor',
58    # These are all for Javadoc, which we don't really care about.
59    # vvv
60    'InvalidBlockTag',
61    'InvalidParam',
62    'InvalidLink',
63    'InvalidInlineTag',
64    'MalformedInlineTag',
65    'MissingSummary',
66    'UnescapedEntity',
67    'UnrecognisedJavadocTag',
68    # ^^^
69    'MutablePublicArray',
70    'NonCanonicalType',
71    'DoNotClaimAnnotations',
72    'JavaUtilDate',
73    'IdentityHashMapUsage',
74    'StaticMockMember',
75    # Triggers in tests where this is useful to do.
76    'StaticAssignmentOfThrowable',
77    # TODO(crbug.com/41384349): Follow steps in bug.
78    'CatchAndPrintStackTrace',
79    # TODO(crbug.com/41364806): Follow steps in bug.
80    'TypeParameterUnusedInFormals',
81    # Android platform default is always UTF-8.
82    # https://developer.android.com/reference/java/nio/charset/Charset.html#defaultCharset()
83    'DefaultCharset',
84    # There are lots of times when we just want to post a task.
85    'FutureReturnValueIgnored',
86    # Just false positives in our code.
87    'ThreadJoinLoop',
88    # Low priority corner cases with String.split.
89    # Linking Guava and using Splitter was rejected
90    # in the https://chromium-review.googlesource.com/c/chromium/src/+/871630.
91    'StringSplitter',
92    # Preferred to use another method since it propagates exceptions better.
93    'ClassNewInstance',
94    # Results in false positives.
95    'ThreadLocalUsage',
96    # Low priority.
97    'EqualsHashCode',
98    # Not necessary for tests.
99    'OverrideThrowableToString',
100    # Not that useful.
101    'UnsafeReflectiveConstructionCast',
102    # Not that useful.
103    'MixedMutabilityReturnType',
104    # Nice to have.
105    'EqualsGetClass',
106    # A lot of false-positives from CharSequence.equals().
107    'UndefinedEquals',
108    # Dagger generated code triggers this.
109    'SameNameButDifferent',
110    # Does not apply to Android because it assumes no desugaring.
111    'UnnecessaryLambda',
112    # Nice to have.
113    'EmptyCatch',
114    # Nice to have.
115    'BadImport',
116    # Nice to have.
117    'UseCorrectAssertInTests',
118    # Must be off since we are now passing in annotation processor generated
119    # code as a source jar (deduplicating work with turbine).
120    'RefersToDaggerCodegen',
121    # We already have presubmit checks for this. We don't want it to fail
122    # local compiles.
123    'RemoveUnusedImports',
124    # Only has false positives (would not want to enable this).
125    'UnicodeEscape',
126    # A lot of existing violations. e.g. Should return List and not ArrayList
127    'NonApiType',
128    # Nice to have.
129    'StringCharset',
130    # Nice to have.
131    'StringCaseLocaleUsage',
132    # Low priority.
133    'RedundantControlFlow',
134]
135
136# Full list of checks: https://errorprone.info/bugpatterns
137# Only those marked as "experimental" need to be listed here in order to be
138# enabled.
139ERRORPRONE_WARNINGS_TO_ENABLE = [
140    'BinderIdentityRestoredDangerously',
141    'EmptyIf',
142    'EqualsBrokenForNull',
143    'InvalidThrows',
144    'LongLiteralLowerCaseSuffix',
145    'MultiVariableDeclaration',
146    'RedundantOverride',
147    'StaticQualifiedUsingExpression',
148    'TimeUnitMismatch',
149    'UnnecessaryStaticImport',
150    'UseBinds',
151    'WildcardImport',
152    'NoStreams',
153]
154
155
156def ProcessJavacOutput(output, target_name):
157  # These warnings cannot be suppressed even for third party code. Deprecation
158  # warnings especially do not help since we must support older android version.
159  deprecated_re = re.compile(r'Note: .* uses? or overrides? a deprecated API')
160  unchecked_re = re.compile(
161      r'(Note: .* uses? unchecked or unsafe operations.)$')
162  recompile_re = re.compile(r'(Note: Recompile with -Xlint:.* for details.)$')
163
164  def ApplyFilters(line):
165    return not (deprecated_re.match(line) or unchecked_re.match(line)
166                or recompile_re.match(line))
167
168  output = build_utils.FilterReflectiveAccessJavaWarnings(output)
169
170  # Warning currently cannot be silenced via javac flag.
171  if 'Unsafe is internal proprietary API' in output:
172    # Example:
173    # HiddenApiBypass.java:69: warning: Unsafe is internal proprietary API and
174    # may be removed in a future release
175    # import sun.misc.Unsafe;
176    #                 ^
177    output = re.sub(r'.*?Unsafe is internal proprietary API[\s\S]*?\^\n', '',
178                    output)
179    output = re.sub(r'\d+ warnings\n', '', output)
180
181  lines = (l for l in output.split('\n') if ApplyFilters(l))
182
183  output_processor = javac_output_processor.JavacOutputProcessor(target_name)
184  lines = output_processor.Process(lines)
185
186  return '\n'.join(lines)
187
188
189def CreateJarFile(jar_path,
190                  classes_dir,
191                  services_map=None,
192                  additional_jar_files=None,
193                  extra_classes_jar=None):
194  """Zips files from compilation into a single jar."""
195  logging.info('Start creating jar file: %s', jar_path)
196  with action_helpers.atomic_output(jar_path) as f:
197    with zipfile.ZipFile(f.name, 'w') as z:
198      zip_helpers.zip_directory(z, classes_dir)
199      if services_map:
200        for service_class, impl_classes in sorted(services_map.items()):
201          zip_path = 'META-INF/services/' + service_class
202          data = ''.join(f'{x}\n' for x in sorted(impl_classes))
203          zip_helpers.add_to_zip_hermetic(z, zip_path, data=data)
204
205      if additional_jar_files:
206        for src_path, zip_path in additional_jar_files:
207          zip_helpers.add_to_zip_hermetic(z, zip_path, src_path=src_path)
208      if extra_classes_jar:
209        path_transform = lambda p: p if p.endswith('.class') else None
210        zip_helpers.merge_zips(z, [extra_classes_jar],
211                               path_transform=path_transform)
212  logging.info('Completed jar file: %s', jar_path)
213
214
215# Java lines end in semicolon, whereas Kotlin lines do not.
216_PACKAGE_RE = re.compile(r'^package\s+(.*?)(;|\s*$)', flags=re.MULTILINE)
217
218_SERVICE_IMPL_RE = re.compile(
219    r'^([\t ]*)@ServiceImpl\(\s*(.+?)\.class\)(.*?)\sclass\s+(\w+)',
220    flags=re.MULTILINE | re.DOTALL)
221
222# Finds all top-level classes (by looking for those that are not indented).
223_TOP_LEVEL_CLASSES_RE = re.compile(
224    # Start of line, or after /* package */
225    r'^(?:/\*.*\*/\s*)?'
226    # Annotations
227    r'(?:@\w+(?:\(.*\))\s+)*'
228    r'(?:(?:public|protected|private)\s+)?'
229    r'(?:(?:static|abstract|final|sealed)\s+)*'
230    r'(?:class|@?interface|enum|record)\s+'
231    r'(\w+?)\b[^"]*?$',
232    flags=re.MULTILINE)
233
234
235def ParseJavaSource(data, services_map, path=None):
236  """This should support both Java and Kotlin files."""
237  package_name = ''
238  if m := _PACKAGE_RE.search(data):
239    package_name = m.group(1)
240
241  class_names = _TOP_LEVEL_CLASSES_RE.findall(data)
242
243  # Very rare, so worth an upfront check.
244  if '@ServiceImpl' in data:
245    for indent, service_class, modifiers, impl_class in (
246        _SERVICE_IMPL_RE.findall(data)):
247      if 'public' not in modifiers:
248        raise Exception(f'@ServiceImpl can be used only on public classes '
249                        f'(when parsing {path})')
250      # Assume indent means nested class that is one level deep.
251      if indent:
252        impl_class = f'{class_names[0]}${impl_class}'
253      else:
254        assert class_names[0] == impl_class
255
256      # Parse imports to resolve the class.
257      dot_idx = service_class.find('.')
258      # Handle @ServiceImpl(OuterClass.InnerClass.class)
259      outer_class = service_class if dot_idx == -1 else service_class[:dot_idx]
260
261      if m := re.search(r'^import\s+([\w\.]*\.' + outer_class + r')[;\s]',
262                        data,
263                        flags=re.MULTILINE):
264        service_class = m.group(1) + service_class[len(outer_class):]
265      else:
266        service_class = f'{package_name}.{service_class}'
267
268      # Convert OuterClass.InnerClass -> OuterClass$InnerClass.
269      for m in list(re.finditer(r'\.[A-Z]', service_class))[1:]:
270        idx = m.start()
271        service_class = service_class[:idx] + '$' + service_class[idx + 1:]
272
273      services_map[service_class].append(f'{package_name}.{impl_class}')
274
275  return package_name, class_names
276
277
278class _MetadataParser:
279
280  def __init__(self, chromium_code, excluded_globs):
281    self._chromium_code = chromium_code
282    self._excluded_globs = excluded_globs
283    # Map of .java path -> .srcjar/nested/path.java.
284    self._srcjar_files = {}
285    # Map of @ServiceImpl class -> impl class
286    self.services_map = collections.defaultdict(list)
287
288  def AddSrcJarSources(self, srcjar_path, extracted_paths, parent_dir):
289    for path in extracted_paths:
290      # We want the path inside the srcjar so the viewer can have a tree
291      # structure.
292      self._srcjar_files[path] = '{}/{}'.format(
293          srcjar_path, os.path.relpath(path, parent_dir))
294
295  def _CheckPathMatchesClassName(self, source_file, package_name, class_name):
296    if source_file.endswith('.java'):
297      parts = package_name.split('.') + [class_name + '.java']
298    else:
299      parts = package_name.split('.') + [class_name + '.kt']
300    expected_suffix = os.path.sep.join(parts)
301    if not source_file.endswith(expected_suffix):
302      raise Exception(('Source package+class name do not match its path.\n'
303                       'Actual path: %s\nExpected path: %s') %
304                      (source_file, expected_suffix))
305
306  def _ProcessInfo(self, java_file, package_name, class_names, source):
307    for class_name in class_names:
308      yield '{}.{}'.format(package_name, class_name)
309      # Skip aidl srcjars since they don't indent code correctly.
310      if '_aidl.srcjar' in source:
311        continue
312      assert not self._chromium_code or len(class_names) == 1, (
313          'Chromium java files must only have one class: {} found: {}'.format(
314              source, class_names))
315      if self._chromium_code:
316        # This check is not necessary but nice to check this somewhere.
317        self._CheckPathMatchesClassName(java_file, package_name, class_names[0])
318
319  def _ShouldIncludeInJarInfo(self, fully_qualified_name):
320    name_as_class_glob = fully_qualified_name.replace('.', '/') + '.class'
321    return not build_utils.MatchesGlob(name_as_class_glob, self._excluded_globs)
322
323  def ParseAndWriteInfoFile(self, output_path, java_files, kt_files=None):
324    """Writes a .jar.info file.
325
326    Maps fully qualified names for classes to either the java file that they
327    are defined in or the path of the srcjar that they came from.
328    """
329    logging.info('Collecting info file entries')
330    entries = {}
331    for path in itertools.chain(java_files, kt_files or []):
332      data = pathlib.Path(path).read_text()
333      package_name, class_names = ParseJavaSource(data,
334                                                  self.services_map,
335                                                  path=path)
336      source = self._srcjar_files.get(path, path)
337      for fully_qualified_name in self._ProcessInfo(path, package_name,
338                                                    class_names, source):
339        if self._ShouldIncludeInJarInfo(fully_qualified_name):
340          entries[fully_qualified_name] = path
341
342    logging.info('Writing info file: %s', output_path)
343    with action_helpers.atomic_output(output_path, mode='wb') as f:
344      jar_info_utils.WriteJarInfoFile(f, entries, self._srcjar_files)
345    logging.info('Completed info file: %s', output_path)
346
347
348def _OnStaleMd5(changes, options, javac_cmd, javac_args, java_files, kt_files):
349  logging.info('Starting _OnStaleMd5')
350
351  # Use the build server for errorprone runs.
352  if (options.enable_errorprone and not options.skip_build_server
353      and server_utils.MaybeRunCommand(
354          name=options.target_name,
355          argv=sys.argv,
356          stamp_file=options.jar_path,
357          force=options.use_build_server,
358          experimental=options.experimental_build_server)):
359    logging.info('Using build server')
360    return
361
362  if options.enable_kythe_annotations:
363    # Kythe requires those env variables to be set and compile_java.py does the
364    # same
365    if not os.environ.get('KYTHE_ROOT_DIRECTORY') or \
366        not os.environ.get('KYTHE_OUTPUT_DIRECTORY'):
367      raise Exception('--enable-kythe-annotations requires '
368                      'KYTHE_ROOT_DIRECTORY and KYTHE_OUTPUT_DIRECTORY '
369                      'environment variables to be set.')
370    javac_extractor_cmd = build_utils.JavaCmd() + [
371        '--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
372        '--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
373        '--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
374        '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
375        '--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
376        '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
377        '--add-exports=jdk.internal.opt/jdk.internal.opt=ALL-UNNAMED',
378        '-jar',
379        _JAVAC_EXTRACTOR,
380    ]
381    try:
382      # _RunCompiler()'s partial javac implementation does not support
383      # generating outputs in $KYTHE_OUTPUT_DIRECTORY.
384      _RunCompiler(changes,
385                   options,
386                   javac_extractor_cmd + javac_args,
387                   java_files,
388                   options.jar_path + '.javac_extractor',
389                   enable_partial_javac=False)
390    except build_utils.CalledProcessError as e:
391      # Having no index for particular target is better than failing entire
392      # codesearch. Log and error and move on.
393      logging.error('Could not generate kzip: %s', e)
394
395  intermediates_out_dir = None
396  jar_info_path = None
397  if not options.enable_errorprone:
398    # Delete any stale files in the generated directory. The purpose of
399    # options.generated_dir is for codesearch and Android Studio.
400    shutil.rmtree(options.generated_dir, True)
401    intermediates_out_dir = options.generated_dir
402
403    # Write .info file only for the main javac invocation (no need to do it
404    # when running Error Prone.
405    jar_info_path = options.jar_path + '.info'
406
407  # Compiles with Error Prone take twice as long to run as pure javac. Thus GN
408  # rules run both in parallel, with Error Prone only used for checks.
409  try:
410    _RunCompiler(changes,
411                 options,
412                 javac_cmd + javac_args,
413                 java_files,
414                 options.jar_path,
415                 kt_files=kt_files,
416                 jar_info_path=jar_info_path,
417                 intermediates_out_dir=intermediates_out_dir,
418                 enable_partial_javac=True)
419  except build_utils.CalledProcessError as e:
420    # Do not output stacktrace as it takes up space on gerrit UI, forcing
421    # you to click though to find the actual compilation error. It's never
422    # interesting to see the Python stacktrace for a Java compilation error.
423    sys.stderr.write(e.output)
424    sys.exit(1)
425
426  logging.info('Completed all steps in _OnStaleMd5')
427
428
429def _RunCompiler(changes,
430                 options,
431                 javac_cmd,
432                 java_files,
433                 jar_path,
434                 kt_files=None,
435                 jar_info_path=None,
436                 intermediates_out_dir=None,
437                 enable_partial_javac=False):
438  """Runs java compiler.
439
440  Args:
441    changes: md5_check.Changes object.
442    options: Object with command line flags.
443    javac_cmd: Command to execute.
444    java_files: List of java files passed from command line.
445    jar_path: Path of output jar file.
446    kt_files: List of Kotlin files passed from command line if any.
447    jar_info_path: Path of the .info file to generate.
448        If None, .info file will not be generated.
449    intermediates_out_dir: Directory for saving intermediate outputs.
450        If None a temporary directory is used.
451    enable_partial_javac: Enables compiling only Java files which have changed
452        in the special case that no method signatures have changed. This is
453        useful for large GN targets.
454        Not supported if compiling generates outputs other than |jar_path| and
455        |jar_info_path|.
456  """
457  logging.info('Starting _RunCompiler')
458
459  java_files = java_files.copy()
460  java_srcjars = options.java_srcjars
461  parse_java_files = jar_info_path is not None
462
463  # Use jar_path's directory to ensure paths are relative (needed for rbe).
464  temp_dir = jar_path + '.staging'
465  build_utils.DeleteDirectory(temp_dir)
466  os.makedirs(temp_dir)
467  metadata_parser = _MetadataParser(options.chromium_code,
468                                    options.jar_info_exclude_globs)
469  try:
470    classes_dir = os.path.join(temp_dir, 'classes')
471
472    if java_files:
473      os.makedirs(classes_dir)
474
475      if enable_partial_javac and changes:
476        all_changed_paths_are_java = all(
477            p.endswith(".java") for p in changes.IterChangedPaths())
478        if (all_changed_paths_are_java and not changes.HasStringChanges()
479            and os.path.exists(jar_path)
480            and (jar_info_path is None or os.path.exists(jar_info_path))):
481          # Log message is used by tests to determine whether partial javac
482          # optimization was used.
483          logging.info('Using partial javac optimization for %s compile' %
484                       (jar_path))
485
486          # Header jar corresponding to |java_files| did not change.
487          # As a build speed optimization (crbug.com/1170778), re-compile only
488          # java files which have changed. Re-use old jar .info file.
489          java_files = list(changes.IterChangedPaths())
490
491          # Disable srcjar extraction, since we know the srcjar didn't show as
492          # changed (only .java files).
493          java_srcjars = None
494
495          # @ServiceImpl has class retention, so will alter header jars when
496          # modified (and hence not reach this block).
497          # Likewise, nothing in .info files can change if header jar did not
498          # change.
499          parse_java_files = False
500
501          # Extracts .class as well as META-INF/services.
502          build_utils.ExtractAll(jar_path, classes_dir)
503
504    if intermediates_out_dir is None:
505      intermediates_out_dir = temp_dir
506
507    input_srcjars_dir = os.path.join(intermediates_out_dir, 'input_srcjars')
508
509    if java_srcjars:
510      logging.info('Extracting srcjars to %s', input_srcjars_dir)
511      build_utils.MakeDirectory(input_srcjars_dir)
512      for srcjar in options.java_srcjars:
513        extracted_files = build_utils.ExtractAll(
514            srcjar, no_clobber=True, path=input_srcjars_dir, pattern='*.java')
515        java_files.extend(extracted_files)
516        if parse_java_files:
517          metadata_parser.AddSrcJarSources(srcjar, extracted_files,
518                                           input_srcjars_dir)
519      logging.info('Done extracting srcjars')
520
521    if java_files:
522      # Don't include the output directory in the initial set of args since it
523      # being in a temp dir makes it unstable (breaks md5 stamping).
524      cmd = list(javac_cmd)
525      cmd += ['-d', classes_dir]
526
527      if options.classpath:
528        cmd += ['-classpath', ':'.join(options.classpath)]
529
530      # Pass source paths as response files to avoid extremely long command
531      # lines that are tedius to debug.
532      java_files_rsp_path = os.path.join(temp_dir, 'files_list.txt')
533      with open(java_files_rsp_path, 'w') as f:
534        f.write(' '.join(java_files))
535      cmd += ['@' + java_files_rsp_path]
536
537      process_javac_output_partial = functools.partial(
538          ProcessJavacOutput, target_name=options.target_name)
539
540      logging.debug('Build command %s', cmd)
541      start = time.time()
542      before_join_callback = None
543      if parse_java_files:
544        before_join_callback = lambda: metadata_parser.ParseAndWriteInfoFile(
545            jar_info_path, java_files, kt_files)
546
547      if options.print_javac_command_line:
548        print(shlex.join(cmd))
549        return
550
551      build_utils.CheckOutput(cmd,
552                              print_stdout=options.chromium_code,
553                              stdout_filter=process_javac_output_partial,
554                              stderr_filter=process_javac_output_partial,
555                              fail_on_output=options.warnings_as_errors,
556                              before_join_callback=before_join_callback)
557      end = time.time() - start
558      logging.info('Java compilation took %ss', end)
559    elif parse_java_files:
560      if options.print_javac_command_line:
561        raise Exception('need java files for --print-javac-command-line.')
562      metadata_parser.ParseAndWriteInfoFile(jar_info_path, java_files, kt_files)
563
564    CreateJarFile(jar_path, classes_dir, metadata_parser.services_map,
565                  options.additional_jar_files, options.kotlin_jar_path)
566
567    # Remove input srcjars that confuse Android Studio:
568    # https://crbug.com/353326240
569    for root, _, files in os.walk(intermediates_out_dir):
570      for subpath in files:
571        p = os.path.join(root, subpath)
572        # JNI Zero placeholders
573        if '_jni_java/' in p and not p.endswith('Jni.java'):
574          os.unlink(p)
575
576    logging.info('Completed all steps in _RunCompiler')
577  finally:
578    # preserve temp_dir for rsp fie when --print-javac-command-line
579    if not options.print_javac_command_line:
580      shutil.rmtree(temp_dir)
581
582
583def _ParseOptions(argv):
584  parser = optparse.OptionParser()
585  action_helpers.add_depfile_arg(parser)
586
587  parser.add_option('--target-name', help='Fully qualified GN target name.')
588  parser.add_option('--skip-build-server',
589                    action='store_true',
590                    help='Avoid using the build server.')
591  parser.add_option('--use-build-server',
592                    action='store_true',
593                    help='Always use the build server.')
594  parser.add_option('--experimental-build-server',
595                    action='store_true',
596                    help='Use experimental build server features.')
597  parser.add_option('--java-srcjars',
598                    action='append',
599                    default=[],
600                    help='List of srcjars to include in compilation.')
601  parser.add_option(
602      '--generated-dir',
603      help='Subdirectory within target_gen_dir to place extracted srcjars and '
604      'annotation processor output for codesearch to find.')
605  parser.add_option('--classpath', action='append', help='Classpath to use.')
606  parser.add_option(
607      '--processorpath',
608      action='append',
609      help='GN list of jars that comprise the classpath used for Annotation '
610      'Processors.')
611  parser.add_option(
612      '--processor-arg',
613      dest='processor_args',
614      action='append',
615      help='key=value arguments for the annotation processors.')
616  parser.add_option(
617      '--additional-jar-file',
618      dest='additional_jar_files',
619      action='append',
620      help='Additional files to package into jar. By default, only Java .class '
621      'files are packaged into the jar. Files should be specified in '
622      'format <filename>:<path to be placed in jar>.')
623  parser.add_option(
624      '--jar-info-exclude-globs',
625      help='GN list of exclude globs to filter from generated .info files.')
626  parser.add_option(
627      '--chromium-code',
628      type='int',
629      help='Whether code being compiled should be built with stricter '
630      'warnings for chromium code.')
631  parser.add_option(
632      '--errorprone-path', help='Use the Errorprone compiler at this path.')
633  parser.add_option(
634      '--enable-errorprone',
635      action='store_true',
636      help='Enable errorprone checks')
637  parser.add_option('--enable-nullaway',
638                    action='store_true',
639                    help='Enable NullAway (requires --enable-errorprone)')
640  parser.add_option('--testonly',
641                    action='store_true',
642                    help='Disable some Error Prone checks')
643  parser.add_option('--warnings-as-errors',
644                    action='store_true',
645                    help='Treat all warnings as errors.')
646  parser.add_option('--jar-path', help='Jar output path.')
647  parser.add_option(
648      '--javac-arg',
649      action='append',
650      default=[],
651      help='Additional arguments to pass to javac.')
652  parser.add_option('--print-javac-command-line',
653                    action='store_true',
654                    help='Just show javac command line (for ide_query).')
655  parser.add_option(
656      '--enable-kythe-annotations',
657      action='store_true',
658      help='Enable generation of Kythe kzip, used for codesearch. Ensure '
659      'proper environment variables are set before using this flag.')
660  parser.add_option(
661      '--header-jar',
662      help='This is the header jar for the current target that contains '
663      'META-INF/services/* files to be included in the output jar.')
664  parser.add_option(
665      '--kotlin-jar-path',
666      help='Kotlin jar to be merged into the output jar. This contains the '
667      ".class files from this target's .kt files.")
668
669  options, args = parser.parse_args(argv)
670  build_utils.CheckOptions(options, parser, required=('jar_path', ))
671
672  options.classpath = action_helpers.parse_gn_list(options.classpath)
673  options.processorpath = action_helpers.parse_gn_list(options.processorpath)
674  options.java_srcjars = action_helpers.parse_gn_list(options.java_srcjars)
675  options.jar_info_exclude_globs = action_helpers.parse_gn_list(
676      options.jar_info_exclude_globs)
677
678  additional_jar_files = []
679  for arg in options.additional_jar_files or []:
680    filepath, jar_filepath = arg.split(':')
681    additional_jar_files.append((filepath, jar_filepath))
682  options.additional_jar_files = additional_jar_files
683
684  files = []
685  for arg in args:
686    # Interpret a path prefixed with @ as a file containing a list of sources.
687    if arg.startswith('@'):
688      files.extend(build_utils.ReadSourcesList(arg[1:]))
689    else:
690      files.append(arg)
691
692  # The target's .sources file contains both Java and Kotlin files. We use
693  # compile_kt.py to compile the Kotlin files to .class and header jars. Javac
694  # is run only on .java files.
695  java_files = [f for f in files if f.endswith('.java')]
696  # Kotlin files are needed to populate the info file and attribute size in
697  # supersize back to the appropriate Kotlin file.
698  kt_files = [f for f in files if f.endswith('.kt')]
699
700  return options, java_files, kt_files
701
702
703def main(argv):
704  build_utils.InitLogging('JAVAC_DEBUG')
705  argv = build_utils.ExpandFileArgs(argv)
706  options, java_files, kt_files = _ParseOptions(argv)
707
708  javac_cmd = [build_utils.JAVAC_PATH]
709
710  javac_args = [
711      '-g',
712      # Jacoco does not currently support a higher value.
713      '--release',
714      '17',
715      # Chromium only allows UTF8 source files.  Being explicit avoids
716      # javac pulling a default encoding from the user's environment.
717      '-encoding',
718      'UTF-8',
719      # Prevent compiler from compiling .java files not listed as inputs.
720      # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
721      '-sourcepath',
722      ':',
723      # protobuf-generated files fail this check (javadoc has @deprecated,
724      # but method missing @Deprecated annotation).
725      '-Xlint:-dep-ann',
726      # Do not warn about finalize() methods. Android still intends to support
727      # them.
728      '-Xlint:-removal',
729      # https://crbug.com/1441023
730      '-J-XX:+PerfDisableSharedMem',
731  ]
732
733  if options.enable_errorprone:
734    # All errorprone args are passed space-separated in a single arg.
735    errorprone_flags = ['-Xplugin:ErrorProne']
736
737    if options.enable_nullaway:
738      # See: https://github.com/uber/NullAway/wiki/Configuration
739      # Treat these packages as @NullMarked by default.
740      # These apply to both .jars in classpath as well as code being compiled.
741      errorprone_flags += [
742          '-XepOpt:NullAway:AnnotatedPackages=android,java,org.chromium'
743      ]
744      # Detect "assert foo != null" as a null check.
745      errorprone_flags += ['-XepOpt:NullAway:AssertsEnabled=true']
746      # Do not ignore @Nullable in non-annotated packages.
747      errorprone_flags += [
748          '-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true'
749      ]
750      # Treat @RecentlyNullable the same as @Nullable.
751      errorprone_flags += ['-XepOpt:Nullaway:AcknowledgeAndroidRecent=true']
752      # Enable experimental checking of @Nullable generics.
753      # https://github.com/uber/NullAway/wiki/JSpecify-Support
754      errorprone_flags += ['-XepOpt:NullAway:JSpecifyMode=true']
755      # Treat these the same as constructors.
756      init_methods = [
757          'android.app.Application.onCreate',
758          'android.app.Activity.onCreate',
759          'android.app.Service.onCreate',
760          'android.app.backup.BackupAgent.onCreate',
761          'android.content.ContentProvider.attachInfo',
762          'android.content.ContentProvider.onCreate',
763          'android.content.ContentWrapper.attachBaseContext',
764      ]
765      errorprone_flags += [
766          '-XepOpt:NullAway:KnownInitializers=' + ','.join(init_methods)
767      ]
768
769    # Make everything a warning so that when treat_warnings_as_errors is false,
770    # they do not fail the build.
771    errorprone_flags += ['-XepAllErrorsAsWarnings']
772    # Don't check generated files (those tagged with @Generated).
773    errorprone_flags += ['-XepDisableWarningsInGeneratedCode']
774    errorprone_flags.extend('-Xep:{}:OFF'.format(x)
775                            for x in ERRORPRONE_WARNINGS_TO_DISABLE)
776    errorprone_flags.extend('-Xep:{}:WARN'.format(x)
777                            for x in ERRORPRONE_WARNINGS_TO_ENABLE)
778    if options.testonly:
779      errorprone_flags.extend('-Xep:{}:OFF'.format(x)
780                              for x in TESTONLY_ERRORPRONE_WARNINGS_TO_DISABLE)
781
782    if ERRORPRONE_CHECKS_TO_APPLY:
783      to_apply = list(ERRORPRONE_CHECKS_TO_APPLY)
784      if options.testonly:
785        to_apply = [
786            x for x in to_apply
787            if x not in TESTONLY_ERRORPRONE_WARNINGS_TO_DISABLE
788        ]
789      errorprone_flags += [
790          '-XepPatchLocation:IN_PLACE', '-XepPatchChecks:,' + ','.join(to_apply)
791      ]
792
793    # These are required to use JDK 16, and are taken directly from
794    # https://errorprone.info/docs/installation
795    javac_args += [
796        '-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
797        '-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
798        '-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED',
799        '-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED',
800        '-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
801        '-J--add-exports=jdk.compiler/com.sun.tools.javac.processing='
802        'ALL-UNNAMED',
803        '-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
804        '-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
805        '-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
806        '-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED',
807    ]
808
809    javac_args += ['-XDcompilePolicy=simple', ' '.join(errorprone_flags)]
810
811    javac_args += ['-XDshould-stop.ifError=FLOW']
812    # This flag quits errorprone after checks and before code generation, since
813    # we do not need errorprone outputs, this speeds up errorprone by 4 seconds
814    # for chrome_java.
815    if not ERRORPRONE_CHECKS_TO_APPLY:
816      javac_args += ['-XDshould-stop.ifNoError=FLOW']
817
818  # This effectively disables all annotation processors, even including
819  # annotation processors in service provider configuration files named
820  # META-INF/. See the following link for reference:
821  #     https://docs.oracle.com/en/java/javase/11/tools/javac.html
822  javac_args.extend(['-proc:none'])
823
824  if options.processorpath:
825    javac_args.extend(['-processorpath', ':'.join(options.processorpath)])
826  if options.processor_args:
827    for arg in options.processor_args:
828      javac_args.extend(['-A%s' % arg])
829
830  javac_args.extend(options.javac_arg)
831
832  if options.print_javac_command_line:
833    if options.java_srcjars:
834      raise Exception(
835          '--print-javac-command-line does not work with --java-srcjars')
836    _OnStaleMd5(None, options, javac_cmd, javac_args, java_files, kt_files)
837    return 0
838
839  classpath_inputs = options.classpath + options.processorpath
840
841  depfile_deps = classpath_inputs
842  # Files that are already inputs in GN should go in input_paths.
843  input_paths = ([build_utils.JAVAC_PATH] + depfile_deps +
844                 options.java_srcjars + java_files + kt_files)
845  if options.header_jar:
846    input_paths.append(options.header_jar)
847  input_paths += [x[0] for x in options.additional_jar_files]
848
849  output_paths = [options.jar_path]
850  if not options.enable_errorprone:
851    output_paths += [options.jar_path + '.info']
852
853  input_strings = (javac_cmd + javac_args + options.classpath + java_files +
854                   kt_files +
855                   [options.warnings_as_errors, options.jar_info_exclude_globs])
856
857  do_it = lambda changes: _OnStaleMd5(changes, options, javac_cmd, javac_args,
858                                      java_files, kt_files)
859  # Incremental build optimization doesn't work for ErrorProne. Skip md5 check.
860  if options.enable_errorprone:
861    do_it(None)
862    action_helpers.write_depfile(options.depfile, output_paths[0], depfile_deps)
863  else:
864    # Use md5_check for |pass_changes| feature.
865    md5_check.CallAndWriteDepfileIfStale(do_it,
866                                         options,
867                                         depfile_deps=depfile_deps,
868                                         input_paths=input_paths,
869                                         input_strings=input_strings,
870                                         output_paths=output_paths,
871                                         pass_changes=True)
872  return 0
873
874
875if __name__ == '__main__':
876  sys.exit(main(sys.argv[1:]))
877