PageRenderTime 44ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/build/android/gyp/javac.py

https://gitlab.com/imxiangpeng/gn-standalone
Python | 393 lines | 326 code | 24 blank | 43 comment | 35 complexity | e7d470728efac061f731c9e76372e05e MD5 | raw file
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2013 The Chromium Authors. All rights reserved.
  4. # Use of this source code is governed by a BSD-style license that can be
  5. # found in the LICENSE file.
  6. import optparse
  7. import os
  8. import shutil
  9. import re
  10. import sys
  11. import textwrap
  12. from util import build_utils
  13. from util import md5_check
  14. import jar
  15. sys.path.append(build_utils.COLORAMA_ROOT)
  16. import colorama
  17. def ColorJavacOutput(output):
  18. fileline_prefix = r'(?P<fileline>(?P<file>[-.\w/\\]+.java):(?P<line>[0-9]+):)'
  19. warning_re = re.compile(
  20. fileline_prefix + r'(?P<full_message> warning: (?P<message>.*))$')
  21. error_re = re.compile(
  22. fileline_prefix + r'(?P<full_message> (?P<message>.*))$')
  23. marker_re = re.compile(r'\s*(?P<marker>\^)\s*$')
  24. warning_color = ['full_message', colorama.Fore.YELLOW + colorama.Style.DIM]
  25. error_color = ['full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT]
  26. marker_color = ['marker', colorama.Fore.BLUE + colorama.Style.BRIGHT]
  27. def Colorize(line, regex, color):
  28. match = regex.match(line)
  29. start = match.start(color[0])
  30. end = match.end(color[0])
  31. return (line[:start]
  32. + color[1] + line[start:end]
  33. + colorama.Fore.RESET + colorama.Style.RESET_ALL
  34. + line[end:])
  35. def ApplyColor(line):
  36. if warning_re.match(line):
  37. line = Colorize(line, warning_re, warning_color)
  38. elif error_re.match(line):
  39. line = Colorize(line, error_re, error_color)
  40. elif marker_re.match(line):
  41. line = Colorize(line, marker_re, marker_color)
  42. return line
  43. return '\n'.join(map(ApplyColor, output.split('\n')))
  44. ERRORPRONE_OPTIONS = [
  45. # These crash on lots of targets.
  46. '-Xep:ParameterPackage:OFF',
  47. '-Xep:OverridesGuiceInjectableMethod:OFF',
  48. '-Xep:OverridesJavaxInjectableMethod:OFF',
  49. ]
  50. def _FilterJavaFiles(paths, filters):
  51. return [f for f in paths
  52. if not filters or build_utils.MatchesGlob(f, filters)]
  53. _MAX_MANIFEST_LINE_LEN = 72
  54. def _ExtractClassFiles(jar_path, dest_dir, java_files):
  55. """Extracts all .class files not corresponding to |java_files|."""
  56. # Two challenges exist here:
  57. # 1. |java_files| have prefixes that are not represented in the the jar paths.
  58. # 2. A single .java file results in multiple .class files when it contains
  59. # nested classes.
  60. # Here's an example:
  61. # source path: ../../base/android/java/src/org/chromium/Foo.java
  62. # jar paths: org/chromium/Foo.class, org/chromium/Foo$Inner.class
  63. # To extract only .class files not related to the given .java files, we strip
  64. # off ".class" and "$*.class" and use a substring match against java_files.
  65. def extract_predicate(path):
  66. if not path.endswith('.class'):
  67. return False
  68. path_without_suffix = re.sub(r'(?:\$|\.)[^/]+class$', '', path)
  69. partial_java_path = path_without_suffix + '.java'
  70. return not any(p.endswith(partial_java_path) for p in java_files)
  71. build_utils.ExtractAll(jar_path, path=dest_dir, predicate=extract_predicate)
  72. for path in build_utils.FindInDirectory(dest_dir, '*.class'):
  73. shutil.copystat(jar_path, path)
  74. def _ConvertToJMakeArgs(javac_cmd, pdb_path):
  75. new_args = ['bin/jmake', '-pdb', pdb_path]
  76. if javac_cmd[0] != 'javac':
  77. new_args.extend(('-jcexec', new_args[0]))
  78. if md5_check.PRINT_EXPLANATIONS:
  79. new_args.append('-Xtiming')
  80. do_not_prefix = ('-classpath', '-bootclasspath')
  81. skip_next = False
  82. for arg in javac_cmd[1:]:
  83. if not skip_next and arg not in do_not_prefix:
  84. arg = '-C' + arg
  85. new_args.append(arg)
  86. skip_next = arg in do_not_prefix
  87. return new_args
  88. def _FixTempPathsInIncrementalMetadata(pdb_path, temp_dir):
  89. # The .pdb records absolute paths. Fix up paths within /tmp (srcjars).
  90. if os.path.exists(pdb_path):
  91. # Although its a binary file, search/replace still seems to work fine.
  92. with open(pdb_path) as fileobj:
  93. pdb_data = fileobj.read()
  94. with open(pdb_path, 'w') as fileobj:
  95. fileobj.write(re.sub(r'/tmp/[^/]*', temp_dir, pdb_data))
  96. def _OnStaleMd5(changes, options, javac_cmd, java_files, classpath_inputs):
  97. with build_utils.TempDir() as temp_dir:
  98. srcjars = options.java_srcjars
  99. # The .excluded.jar contains .class files excluded from the main jar.
  100. # It is used for incremental compiles.
  101. excluded_jar_path = options.jar_path.replace('.jar', '.excluded.jar')
  102. classes_dir = os.path.join(temp_dir, 'classes')
  103. os.makedirs(classes_dir)
  104. changed_paths = None
  105. # jmake can handle deleted files, but it's a rare case and it would
  106. # complicate this script's logic.
  107. if options.incremental and changes.AddedOrModifiedOnly():
  108. changed_paths = set(changes.IterChangedPaths())
  109. # Do a full compile if classpath has changed.
  110. # jmake doesn't seem to do this on its own... Might be that ijars mess up
  111. # its change-detection logic.
  112. if any(p in changed_paths for p in classpath_inputs):
  113. changed_paths = None
  114. if options.incremental:
  115. # jmake is a compiler wrapper that figures out the minimal set of .java
  116. # files that need to be rebuilt given a set of .java files that have
  117. # changed.
  118. # jmake determines what files are stale based on timestamps between .java
  119. # and .class files. Since we use .jars, .srcjars, and md5 checks,
  120. # timestamp info isn't accurate for this purpose. Rather than use jmake's
  121. # programatic interface (like we eventually should), we ensure that all
  122. # .class files are newer than their .java files, and convey to jmake which
  123. # sources are stale by having their .class files be missing entirely
  124. # (by not extracting them).
  125. pdb_path = options.jar_path + '.pdb'
  126. javac_cmd = _ConvertToJMakeArgs(javac_cmd, pdb_path)
  127. if srcjars:
  128. _FixTempPathsInIncrementalMetadata(pdb_path, temp_dir)
  129. if srcjars:
  130. java_dir = os.path.join(temp_dir, 'java')
  131. os.makedirs(java_dir)
  132. for srcjar in options.java_srcjars:
  133. if changed_paths:
  134. changed_paths.update(os.path.join(java_dir, f)
  135. for f in changes.IterChangedSubpaths(srcjar))
  136. build_utils.ExtractAll(srcjar, path=java_dir, pattern='*.java')
  137. jar_srcs = build_utils.FindInDirectory(java_dir, '*.java')
  138. jar_srcs = _FilterJavaFiles(jar_srcs, options.javac_includes)
  139. java_files.extend(jar_srcs)
  140. if changed_paths:
  141. # Set the mtime of all sources to 0 since we use the absense of .class
  142. # files to tell jmake which files are stale.
  143. for path in jar_srcs:
  144. os.utime(path, (0, 0))
  145. if java_files:
  146. if changed_paths:
  147. changed_java_files = [p for p in java_files if p in changed_paths]
  148. if os.path.exists(options.jar_path):
  149. _ExtractClassFiles(options.jar_path, classes_dir, changed_java_files)
  150. if os.path.exists(excluded_jar_path):
  151. _ExtractClassFiles(excluded_jar_path, classes_dir, changed_java_files)
  152. # Add the extracted files to the classpath. This is required because
  153. # when compiling only a subset of files, classes that haven't changed
  154. # need to be findable.
  155. classpath_idx = javac_cmd.index('-classpath')
  156. javac_cmd[classpath_idx + 1] += ':' + classes_dir
  157. # Can happen when a target goes from having no sources, to having sources.
  158. # It's created by the call to build_utils.Touch() below.
  159. if options.incremental:
  160. if os.path.exists(pdb_path) and not os.path.getsize(pdb_path):
  161. os.unlink(pdb_path)
  162. # Don't include the output directory in the initial set of args since it
  163. # being in a temp dir makes it unstable (breaks md5 stamping).
  164. cmd = javac_cmd + ['-d', classes_dir] + java_files
  165. # JMake prints out some diagnostic logs that we want to ignore.
  166. # This assumes that all compiler output goes through stderr.
  167. stdout_filter = lambda s: ''
  168. if md5_check.PRINT_EXPLANATIONS:
  169. stdout_filter = None
  170. attempt_build = lambda: build_utils.CheckOutput(
  171. cmd,
  172. print_stdout=options.chromium_code,
  173. stdout_filter=stdout_filter,
  174. stderr_filter=ColorJavacOutput)
  175. try:
  176. attempt_build()
  177. except build_utils.CalledProcessError as e:
  178. # Work-around for a bug in jmake (http://crbug.com/551449).
  179. if 'project database corrupted' not in e.output:
  180. raise
  181. print ('Applying work-around for jmake project database corrupted '
  182. '(http://crbug.com/551449).')
  183. os.unlink(pdb_path)
  184. attempt_build()
  185. elif options.incremental:
  186. # Make sure output exists.
  187. build_utils.Touch(pdb_path)
  188. glob = options.jar_excluded_classes
  189. inclusion_predicate = lambda f: not build_utils.MatchesGlob(f, glob)
  190. exclusion_predicate = lambda f: not inclusion_predicate(f)
  191. jar.JarDirectory(classes_dir,
  192. options.jar_path,
  193. predicate=inclusion_predicate)
  194. jar.JarDirectory(classes_dir,
  195. excluded_jar_path,
  196. predicate=exclusion_predicate)
  197. def _ParseOptions(argv):
  198. parser = optparse.OptionParser()
  199. build_utils.AddDepfileOption(parser)
  200. parser.add_option(
  201. '--src-gendirs',
  202. help='Directories containing generated java files.')
  203. parser.add_option(
  204. '--java-srcjars',
  205. action='append',
  206. default=[],
  207. help='List of srcjars to include in compilation.')
  208. parser.add_option(
  209. '--bootclasspath',
  210. action='append',
  211. default=[],
  212. help='Boot classpath for javac. If this is specified multiple times, '
  213. 'they will all be appended to construct the classpath.')
  214. parser.add_option(
  215. '--classpath',
  216. action='append',
  217. help='Classpath for javac. If this is specified multiple times, they '
  218. 'will all be appended to construct the classpath.')
  219. parser.add_option(
  220. '--incremental',
  221. action='store_true',
  222. help='Whether to re-use .class files rather than recompiling them '
  223. '(when possible).')
  224. parser.add_option(
  225. '--javac-includes',
  226. default='',
  227. help='A list of file patterns. If provided, only java files that match'
  228. 'one of the patterns will be compiled.')
  229. parser.add_option(
  230. '--jar-excluded-classes',
  231. default='',
  232. help='List of .class file patterns to exclude from the jar.')
  233. parser.add_option(
  234. '--chromium-code',
  235. type='int',
  236. help='Whether code being compiled should be built with stricter '
  237. 'warnings for chromium code.')
  238. parser.add_option(
  239. '--use-errorprone-path',
  240. help='Use the Errorprone compiler at this path.')
  241. parser.add_option('--jar-path', help='Jar output path.')
  242. parser.add_option('--stamp', help='Path to touch on success.')
  243. options, args = parser.parse_args(argv)
  244. build_utils.CheckOptions(options, parser, required=('jar_path',))
  245. bootclasspath = []
  246. for arg in options.bootclasspath:
  247. bootclasspath += build_utils.ParseGypList(arg)
  248. options.bootclasspath = bootclasspath
  249. classpath = []
  250. for arg in options.classpath:
  251. classpath += build_utils.ParseGypList(arg)
  252. options.classpath = classpath
  253. java_srcjars = []
  254. for arg in options.java_srcjars:
  255. java_srcjars += build_utils.ParseGypList(arg)
  256. options.java_srcjars = java_srcjars
  257. if options.src_gendirs:
  258. options.src_gendirs = build_utils.ParseGypList(options.src_gendirs)
  259. options.javac_includes = build_utils.ParseGypList(options.javac_includes)
  260. options.jar_excluded_classes = (
  261. build_utils.ParseGypList(options.jar_excluded_classes))
  262. return options, args
  263. def main(argv):
  264. colorama.init()
  265. argv = build_utils.ExpandFileArgs(argv)
  266. options, java_files = _ParseOptions(argv)
  267. if options.src_gendirs:
  268. java_files += build_utils.FindInDirectories(options.src_gendirs, '*.java')
  269. java_files = _FilterJavaFiles(java_files, options.javac_includes)
  270. javac_cmd = ['javac']
  271. if options.use_errorprone_path:
  272. javac_cmd = [options.use_errorprone_path] + ERRORPRONE_OPTIONS
  273. javac_cmd.extend((
  274. '-g',
  275. # Chromium only allows UTF8 source files. Being explicit avoids
  276. # javac pulling a default encoding from the user's environment.
  277. '-encoding', 'UTF-8',
  278. '-classpath', ':'.join(options.classpath),
  279. # Prevent compiler from compiling .java files not listed as inputs.
  280. # See: http://blog.ltgt.net/most-build-tools-misuse-javac/
  281. '-sourcepath', ''
  282. ))
  283. if options.bootclasspath:
  284. javac_cmd.extend([
  285. '-bootclasspath', ':'.join(options.bootclasspath),
  286. '-source', '1.7',
  287. '-target', '1.7',
  288. ])
  289. if options.chromium_code:
  290. javac_cmd.extend(['-Xlint:unchecked', '-Xlint:deprecation'])
  291. else:
  292. # XDignore.symbol.file makes javac compile against rt.jar instead of
  293. # ct.sym. This means that using a java internal package/class will not
  294. # trigger a compile warning or error.
  295. javac_cmd.extend(['-XDignore.symbol.file'])
  296. classpath_inputs = options.bootclasspath
  297. if options.classpath:
  298. if options.classpath[0].endswith('.interface.jar'):
  299. classpath_inputs.extend(options.classpath)
  300. else:
  301. # TODO(agrieve): Remove this .TOC heuristic once GYP is no more.
  302. for path in options.classpath:
  303. if os.path.exists(path + '.TOC'):
  304. classpath_inputs.append(path + '.TOC')
  305. else:
  306. classpath_inputs.append(path)
  307. # Compute the list of paths that when changed, we need to rebuild.
  308. input_paths = classpath_inputs + options.java_srcjars + java_files
  309. output_paths = [
  310. options.jar_path,
  311. options.jar_path.replace('.jar', '.excluded.jar'),
  312. ]
  313. if options.incremental:
  314. output_paths.append(options.jar_path + '.pdb')
  315. # An escape hatch to be able to check if incremental compiles are causing
  316. # problems.
  317. force = int(os.environ.get('DISABLE_INCREMENTAL_JAVAC', 0))
  318. # List python deps in input_strings rather than input_paths since the contents
  319. # of them does not change what gets written to the depsfile.
  320. build_utils.CallAndWriteDepfileIfStale(
  321. lambda changes: _OnStaleMd5(changes, options, javac_cmd, java_files,
  322. classpath_inputs),
  323. options,
  324. input_paths=input_paths,
  325. input_strings=javac_cmd,
  326. output_paths=output_paths,
  327. force=force,
  328. pass_changes=True)
  329. if __name__ == '__main__':
  330. sys.exit(main(sys.argv[1:]))