• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7import fnmatch
8import optparse
9import os
10import shutil
11import re
12import sys
13import textwrap
14
15from util import build_utils
16from util import md5_check
17
18import jar
19
20sys.path.append(build_utils.COLORAMA_ROOT)
21import colorama
22
23
24def ColorJavacOutput(output):
25  fileline_prefix = r'(?P<fileline>(?P<file>[-.\w/\\]+.java):(?P<line>[0-9]+):)'
26  warning_re = re.compile(
27      fileline_prefix + r'(?P<full_message> warning: (?P<message>.*))$')
28  error_re = re.compile(
29      fileline_prefix + r'(?P<full_message> (?P<message>.*))$')
30  marker_re = re.compile(r'\s*(?P<marker>\^)\s*$')
31
32  warning_color = ['full_message', colorama.Fore.YELLOW + colorama.Style.DIM]
33  error_color = ['full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT]
34  marker_color = ['marker',  colorama.Fore.BLUE + colorama.Style.BRIGHT]
35
36  def Colorize(line, regex, color):
37    match = regex.match(line)
38    start = match.start(color[0])
39    end = match.end(color[0])
40    return (line[:start]
41            + color[1] + line[start:end]
42            + colorama.Fore.RESET + colorama.Style.RESET_ALL
43            + line[end:])
44
45  def ApplyColor(line):
46    if warning_re.match(line):
47      line = Colorize(line, warning_re, warning_color)
48    elif error_re.match(line):
49      line = Colorize(line, error_re, error_color)
50    elif marker_re.match(line):
51      line = Colorize(line, marker_re, marker_color)
52    return line
53
54  return '\n'.join(map(ApplyColor, output.split('\n')))
55
56
57def DoJavac(
58    classpath, classes_dir, chromium_code, java_files):
59  """Runs javac.
60
61  Builds |java_files| with the provided |classpath| and puts the generated
62  .class files into |classes_dir|. If |chromium_code| is true, extra lint
63  checking will be enabled.
64  """
65
66  jar_inputs = []
67  for path in classpath:
68    if os.path.exists(path + '.TOC'):
69      jar_inputs.append(path + '.TOC')
70    else:
71      jar_inputs.append(path)
72
73  javac_args = [
74      '-g',
75      '-source', '1.7',
76      '-target', '1.7',
77      '-classpath', ':'.join(classpath),
78      '-d', classes_dir]
79  if chromium_code:
80    javac_args.extend(['-Xlint:unchecked', '-Xlint:deprecation'])
81  else:
82    # XDignore.symbol.file makes javac compile against rt.jar instead of
83    # ct.sym. This means that using a java internal package/class will not
84    # trigger a compile warning or error.
85    javac_args.extend(['-XDignore.symbol.file'])
86
87  javac_cmd = ['javac'] + javac_args + java_files
88
89  def Compile():
90    build_utils.CheckOutput(
91        javac_cmd,
92        print_stdout=chromium_code,
93        stderr_filter=ColorJavacOutput)
94
95  record_path = os.path.join(classes_dir, 'javac.md5.stamp')
96  md5_check.CallAndRecordIfStale(
97      Compile,
98      record_path=record_path,
99      input_paths=java_files + jar_inputs,
100      input_strings=javac_cmd)
101
102
103_MAX_MANIFEST_LINE_LEN = 72
104
105
106def CreateManifest(manifest_path, classpath, main_class=None):
107  """Creates a manifest file with the given parameters.
108
109  This generates a manifest file that compiles with the spec found at
110  http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#JAR_Manifest
111
112  Args:
113    manifest_path: The path to the manifest file that should be created.
114    classpath: The JAR files that should be listed on the manifest file's
115      classpath.
116    main_class: If present, the class containing the main() function.
117
118  """
119  output = ['Manifest-Version: 1.0']
120  if main_class:
121    output.append('Main-Class: %s' % main_class)
122  if classpath:
123    sanitized_paths = []
124    for path in classpath:
125      sanitized_paths.append(os.path.basename(path.strip('"')))
126    output.append('Class-Path: %s' % ' '.join(sanitized_paths))
127  output.append('Created-By: ')
128  output.append('')
129
130  wrapper = textwrap.TextWrapper(break_long_words=True,
131                                 drop_whitespace=False,
132                                 subsequent_indent=' ',
133                                 width=_MAX_MANIFEST_LINE_LEN - 2)
134  output = '\r\n'.join(w for l in output for w in wrapper.wrap(l))
135
136  with open(manifest_path, 'w') as f:
137    f.write(output)
138
139
140def main(argv):
141  colorama.init()
142
143  argv = build_utils.ExpandFileArgs(argv)
144
145  parser = optparse.OptionParser()
146  build_utils.AddDepfileOption(parser)
147
148  parser.add_option(
149      '--src-gendirs',
150      help='Directories containing generated java files.')
151  parser.add_option(
152      '--java-srcjars',
153      action='append',
154      default=[],
155      help='List of srcjars to include in compilation.')
156  parser.add_option(
157      '--classpath',
158      action='append',
159      help='Classpath for javac. If this is specified multiple times, they '
160      'will all be appended to construct the classpath.')
161  parser.add_option(
162      '--javac-includes',
163      help='A list of file patterns. If provided, only java files that match'
164      'one of the patterns will be compiled.')
165  parser.add_option(
166      '--jar-excluded-classes',
167      default='',
168      help='List of .class file patterns to exclude from the jar.')
169
170  parser.add_option(
171      '--chromium-code',
172      type='int',
173      help='Whether code being compiled should be built with stricter '
174      'warnings for chromium code.')
175
176  parser.add_option(
177      '--classes-dir',
178      help='Directory for compiled .class files.')
179  parser.add_option('--jar-path', help='Jar output path.')
180  parser.add_option(
181      '--main-class',
182      help='The class containing the main method.')
183
184  parser.add_option('--stamp', help='Path to touch on success.')
185
186  options, args = parser.parse_args(argv)
187
188  if options.main_class and not options.jar_path:
189    parser.error('--main-class requires --jar-path')
190
191  classpath = []
192  for arg in options.classpath:
193    classpath += build_utils.ParseGypList(arg)
194
195  java_srcjars = []
196  for arg in options.java_srcjars:
197    java_srcjars += build_utils.ParseGypList(arg)
198
199  java_files = args
200  if options.src_gendirs:
201    src_gendirs = build_utils.ParseGypList(options.src_gendirs)
202    java_files += build_utils.FindInDirectories(src_gendirs, '*.java')
203
204  input_files = classpath + java_srcjars + java_files
205  with build_utils.TempDir() as temp_dir:
206    classes_dir = os.path.join(temp_dir, 'classes')
207    os.makedirs(classes_dir)
208    if java_srcjars:
209      java_dir = os.path.join(temp_dir, 'java')
210      os.makedirs(java_dir)
211      for srcjar in java_srcjars:
212        build_utils.ExtractAll(srcjar, path=java_dir, pattern='*.java')
213      java_files += build_utils.FindInDirectory(java_dir, '*.java')
214
215    if options.javac_includes:
216      javac_includes = build_utils.ParseGypList(options.javac_includes)
217      filtered_java_files = []
218      for f in java_files:
219        for include in javac_includes:
220          if fnmatch.fnmatch(f, include):
221            filtered_java_files.append(f)
222            break
223      java_files = filtered_java_files
224
225    DoJavac(
226        classpath,
227        classes_dir,
228        options.chromium_code,
229        java_files)
230
231    if options.jar_path:
232      if options.main_class:
233        manifest_file = os.path.join(temp_dir, 'manifest')
234        CreateManifest(manifest_file, classpath,
235                       options.main_class)
236      else:
237        manifest_file = None
238      jar.JarDirectory(classes_dir,
239                       build_utils.ParseGypList(options.jar_excluded_classes),
240                       options.jar_path,
241                       manifest_file=manifest_file)
242
243    if options.classes_dir:
244      # Delete the old classes directory. This ensures that all .class files in
245      # the output are actually from the input .java files. For example, if a
246      # .java file is deleted or an inner class is removed, the classes
247      # directory should not contain the corresponding old .class file after
248      # running this action.
249      build_utils.DeleteDirectory(options.classes_dir)
250      shutil.copytree(classes_dir, options.classes_dir)
251
252  if options.depfile:
253    build_utils.WriteDepfile(
254        options.depfile,
255        input_files + build_utils.GetPythonDependencies())
256
257  if options.stamp:
258    build_utils.Touch(options.stamp)
259
260
261if __name__ == '__main__':
262  sys.exit(main(sys.argv[1:]))
263
264
265