| #!/usr/bin/env python | 
 | # | 
 | # Copyright 2013 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | import fnmatch | 
 | import optparse | 
 | import os | 
 | import shutil | 
 | import re | 
 | import sys | 
 | import textwrap | 
 |  | 
 | from util import build_utils | 
 | from util import md5_check | 
 |  | 
 | import jar | 
 |  | 
 | sys.path.append(build_utils.COLORAMA_ROOT) | 
 | import colorama | 
 |  | 
 |  | 
 | def ColorJavacOutput(output): | 
 |   fileline_prefix = r'(?P<fileline>(?P<file>[-.\w/\\]+.java):(?P<line>[0-9]+):)' | 
 |   warning_re = re.compile( | 
 |       fileline_prefix + r'(?P<full_message> warning: (?P<message>.*))$') | 
 |   error_re = re.compile( | 
 |       fileline_prefix + r'(?P<full_message> (?P<message>.*))$') | 
 |   marker_re = re.compile(r'\s*(?P<marker>\^)\s*$') | 
 |  | 
 |   warning_color = ['full_message', colorama.Fore.YELLOW + colorama.Style.DIM] | 
 |   error_color = ['full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT] | 
 |   marker_color = ['marker',  colorama.Fore.BLUE + colorama.Style.BRIGHT] | 
 |  | 
 |   def Colorize(line, regex, color): | 
 |     match = regex.match(line) | 
 |     start = match.start(color[0]) | 
 |     end = match.end(color[0]) | 
 |     return (line[:start] | 
 |             + color[1] + line[start:end] | 
 |             + colorama.Fore.RESET + colorama.Style.RESET_ALL | 
 |             + line[end:]) | 
 |  | 
 |   def ApplyColor(line): | 
 |     if warning_re.match(line): | 
 |       line = Colorize(line, warning_re, warning_color) | 
 |     elif error_re.match(line): | 
 |       line = Colorize(line, error_re, error_color) | 
 |     elif marker_re.match(line): | 
 |       line = Colorize(line, marker_re, marker_color) | 
 |     return line | 
 |  | 
 |   return '\n'.join(map(ApplyColor, output.split('\n'))) | 
 |  | 
 |  | 
 | def DoJavac( | 
 |     classpath, classes_dir, chromium_code, java_files): | 
 |   """Runs javac. | 
 |  | 
 |   Builds |java_files| with the provided |classpath| and puts the generated | 
 |   .class files into |classes_dir|. If |chromium_code| is true, extra lint | 
 |   checking will be enabled. | 
 |   """ | 
 |  | 
 |   jar_inputs = [] | 
 |   for path in classpath: | 
 |     if os.path.exists(path + '.TOC'): | 
 |       jar_inputs.append(path + '.TOC') | 
 |     else: | 
 |       jar_inputs.append(path) | 
 |  | 
 |   javac_args = [ | 
 |       '-g', | 
 |       # Chromium only allows UTF8 source files.  Being explicit avoids | 
 |       # javac pulling a default encoding from the user's environment. | 
 |       '-encoding', 'UTF-8', | 
 |       '-source', '1.7', | 
 |       '-target', '1.7', | 
 |       '-classpath', ':'.join(classpath), | 
 |       '-d', classes_dir] | 
 |   if chromium_code: | 
 |     # TODO(aurimas): re-enable '-Xlint:deprecation' checks once they are fixed. | 
 |     javac_args.extend(['-Xlint:unchecked']) | 
 |   else: | 
 |     # XDignore.symbol.file makes javac compile against rt.jar instead of | 
 |     # ct.sym. This means that using a java internal package/class will not | 
 |     # trigger a compile warning or error. | 
 |     javac_args.extend(['-XDignore.symbol.file']) | 
 |  | 
 |   javac_cmd = ['javac'] + javac_args + java_files | 
 |  | 
 |   def Compile(): | 
 |     build_utils.CheckOutput( | 
 |         javac_cmd, | 
 |         print_stdout=chromium_code, | 
 |         stderr_filter=ColorJavacOutput) | 
 |  | 
 |   record_path = os.path.join(classes_dir, 'javac.md5.stamp') | 
 |   md5_check.CallAndRecordIfStale( | 
 |       Compile, | 
 |       record_path=record_path, | 
 |       input_paths=java_files + jar_inputs, | 
 |       input_strings=javac_cmd) | 
 |  | 
 |  | 
 | _MAX_MANIFEST_LINE_LEN = 72 | 
 |  | 
 |  | 
 | def CreateManifest(manifest_path, classpath, main_class=None, | 
 |                    manifest_entries=None): | 
 |   """Creates a manifest file with the given parameters. | 
 |  | 
 |   This generates a manifest file that compiles with the spec found at | 
 |   http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#JAR_Manifest | 
 |  | 
 |   Args: | 
 |     manifest_path: The path to the manifest file that should be created. | 
 |     classpath: The JAR files that should be listed on the manifest file's | 
 |       classpath. | 
 |     main_class: If present, the class containing the main() function. | 
 |     manifest_entries: If present, a list of (key, value) pairs to add to | 
 |       the manifest. | 
 |  | 
 |   """ | 
 |   output = ['Manifest-Version: 1.0'] | 
 |   if main_class: | 
 |     output.append('Main-Class: %s' % main_class) | 
 |   if manifest_entries: | 
 |     for k, v in manifest_entries: | 
 |       output.append('%s: %s' % (k, v)) | 
 |   if classpath: | 
 |     sanitized_paths = [] | 
 |     for path in classpath: | 
 |       sanitized_paths.append(os.path.basename(path.strip('"'))) | 
 |     output.append('Class-Path: %s' % ' '.join(sanitized_paths)) | 
 |   output.append('Created-By: ') | 
 |   output.append('') | 
 |  | 
 |   wrapper = textwrap.TextWrapper(break_long_words=True, | 
 |                                  drop_whitespace=False, | 
 |                                  subsequent_indent=' ', | 
 |                                  width=_MAX_MANIFEST_LINE_LEN - 2) | 
 |   output = '\r\n'.join(w for l in output for w in wrapper.wrap(l)) | 
 |  | 
 |   with open(manifest_path, 'w') as f: | 
 |     f.write(output) | 
 |  | 
 |  | 
 | def main(argv): | 
 |   colorama.init() | 
 |  | 
 |   argv = build_utils.ExpandFileArgs(argv) | 
 |  | 
 |   parser = optparse.OptionParser() | 
 |   build_utils.AddDepfileOption(parser) | 
 |  | 
 |   parser.add_option( | 
 |       '--src-gendirs', | 
 |       help='Directories containing generated java files.') | 
 |   parser.add_option( | 
 |       '--java-srcjars', | 
 |       action='append', | 
 |       default=[], | 
 |       help='List of srcjars to include in compilation.') | 
 |   parser.add_option( | 
 |       '--classpath', | 
 |       action='append', | 
 |       help='Classpath for javac. If this is specified multiple times, they ' | 
 |       'will all be appended to construct the classpath.') | 
 |   parser.add_option( | 
 |       '--javac-includes', | 
 |       help='A list of file patterns. If provided, only java files that match' | 
 |       'one of the patterns will be compiled.') | 
 |   parser.add_option( | 
 |       '--jar-excluded-classes', | 
 |       default='', | 
 |       help='List of .class file patterns to exclude from the jar.') | 
 |  | 
 |   parser.add_option( | 
 |       '--chromium-code', | 
 |       type='int', | 
 |       help='Whether code being compiled should be built with stricter ' | 
 |       'warnings for chromium code.') | 
 |  | 
 |   parser.add_option( | 
 |       '--classes-dir', | 
 |       help='Directory for compiled .class files.') | 
 |   parser.add_option('--jar-path', help='Jar output path.') | 
 |   parser.add_option( | 
 |       '--main-class', | 
 |       help='The class containing the main method.') | 
 |   parser.add_option( | 
 |       '--manifest-entry', | 
 |       action='append', | 
 |       help='Key:value pairs to add to the .jar manifest.') | 
 |  | 
 |   parser.add_option('--stamp', help='Path to touch on success.') | 
 |  | 
 |   options, args = parser.parse_args(argv) | 
 |  | 
 |   if options.main_class and not options.jar_path: | 
 |     parser.error('--main-class requires --jar-path') | 
 |  | 
 |   classpath = [] | 
 |   for arg in options.classpath: | 
 |     classpath += build_utils.ParseGypList(arg) | 
 |  | 
 |   java_srcjars = [] | 
 |   for arg in options.java_srcjars: | 
 |     java_srcjars += build_utils.ParseGypList(arg) | 
 |  | 
 |   java_files = args | 
 |   if options.src_gendirs: | 
 |     src_gendirs = build_utils.ParseGypList(options.src_gendirs) | 
 |     java_files += build_utils.FindInDirectories(src_gendirs, '*.java') | 
 |  | 
 |   input_files = classpath + java_srcjars + java_files | 
 |   with build_utils.TempDir() as temp_dir: | 
 |     classes_dir = os.path.join(temp_dir, 'classes') | 
 |     os.makedirs(classes_dir) | 
 |     if java_srcjars: | 
 |       java_dir = os.path.join(temp_dir, 'java') | 
 |       os.makedirs(java_dir) | 
 |       for srcjar in java_srcjars: | 
 |         build_utils.ExtractAll(srcjar, path=java_dir, pattern='*.java') | 
 |       java_files += build_utils.FindInDirectory(java_dir, '*.java') | 
 |  | 
 |     if options.javac_includes: | 
 |       javac_includes = build_utils.ParseGypList(options.javac_includes) | 
 |       filtered_java_files = [] | 
 |       for f in java_files: | 
 |         for include in javac_includes: | 
 |           if fnmatch.fnmatch(f, include): | 
 |             filtered_java_files.append(f) | 
 |             break | 
 |       java_files = filtered_java_files | 
 |  | 
 |     if len(java_files) != 0: | 
 |       DoJavac( | 
 |           classpath, | 
 |           classes_dir, | 
 |           options.chromium_code, | 
 |           java_files) | 
 |  | 
 |     if options.jar_path: | 
 |       if options.main_class or options.manifest_entry: | 
 |         if options.manifest_entry: | 
 |           entries = map(lambda e: e.split(":"), options.manifest_entry) | 
 |         else: | 
 |           entries = [] | 
 |         manifest_file = os.path.join(temp_dir, 'manifest') | 
 |         CreateManifest(manifest_file, classpath, options.main_class, entries) | 
 |       else: | 
 |         manifest_file = None | 
 |       jar.JarDirectory(classes_dir, | 
 |                        build_utils.ParseGypList(options.jar_excluded_classes), | 
 |                        options.jar_path, | 
 |                        manifest_file=manifest_file) | 
 |  | 
 |     if options.classes_dir: | 
 |       # Delete the old classes directory. This ensures that all .class files in | 
 |       # the output are actually from the input .java files. For example, if a | 
 |       # .java file is deleted or an inner class is removed, the classes | 
 |       # directory should not contain the corresponding old .class file after | 
 |       # running this action. | 
 |       build_utils.DeleteDirectory(options.classes_dir) | 
 |       shutil.copytree(classes_dir, options.classes_dir) | 
 |  | 
 |   if options.depfile: | 
 |     build_utils.WriteDepfile( | 
 |         options.depfile, | 
 |         input_files + build_utils.GetPythonDependencies()) | 
 |  | 
 |   if options.stamp: | 
 |     build_utils.Touch(options.stamp) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |   sys.exit(main(sys.argv[1:])) | 
 |  | 
 |  |