Home | History | Annotate | Download | only in gyp
      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 
      7 import fnmatch
      8 import optparse
      9 import os
     10 import shutil
     11 import re
     12 import sys
     13 import textwrap
     14 
     15 from util import build_utils
     16 from util import md5_check
     17 
     18 import jar
     19 
     20 sys.path.append(build_utils.COLORAMA_ROOT)
     21 import colorama
     22 
     23 
     24 def 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 
     57 def 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 
    106 def 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 
    140 def 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 
    261 if __name__ == '__main__':
    262   sys.exit(main(sys.argv[1:]))
    263 
    264 
    265