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