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