Home | History | Annotate | Download | only in util
      1 # Copyright 2015 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import os
      6 import re
      7 from util import build_utils
      8 
      9 
     10 class _ProguardOutputFilter(object):
     11   """ProGuard outputs boring stuff to stdout (proguard version, jar path, etc)
     12   as well as interesting stuff (notes, warnings, etc). If stdout is entirely
     13   boring, this class suppresses the output.
     14   """
     15 
     16   IGNORE_RE = re.compile(
     17       r'(?:Pro.*version|Note:|Reading|Preparing|.*:.*(?:MANIFEST\.MF|\.empty))')
     18 
     19   def __init__(self):
     20     self._last_line_ignored = False
     21 
     22   def __call__(self, output):
     23     ret = []
     24     for line in output.splitlines(True):
     25       if not line.startswith(' '):
     26         self._last_line_ignored = bool(self.IGNORE_RE.match(line))
     27       elif 'You should check if you need to specify' in line:
     28         self._last_line_ignored = True
     29 
     30       if not self._last_line_ignored:
     31         ret.append(line)
     32     return ''.join(ret)
     33 
     34 
     35 class ProguardCmdBuilder(object):
     36   def __init__(self, proguard_jar):
     37     assert os.path.exists(proguard_jar)
     38     self._proguard_jar_path = proguard_jar
     39     self._tested_apk_info_path = None
     40     self._tested_apk_info = None
     41     self._mapping = None
     42     self._libraries = None
     43     self._injars = None
     44     self._configs = None
     45     self._outjar = None
     46     self._cmd = None
     47     self._verbose = False
     48 
     49   def outjar(self, path):
     50     assert self._cmd is None
     51     assert self._outjar is None
     52     self._outjar = path
     53 
     54   def tested_apk_info(self, tested_apk_info_path):
     55     assert self._cmd is None
     56     assert self._tested_apk_info is None
     57     self._tested_apk_info_path = tested_apk_info_path
     58 
     59   def mapping(self, path):
     60     assert self._cmd is None
     61     assert self._mapping is None
     62     assert os.path.exists(path), path
     63     self._mapping = path
     64 
     65   def libraryjars(self, paths):
     66     assert self._cmd is None
     67     assert self._libraries is None
     68     for p in paths:
     69       assert os.path.exists(p), p
     70     self._libraries = paths
     71 
     72   def injars(self, paths):
     73     assert self._cmd is None
     74     assert self._injars is None
     75     for p in paths:
     76       assert os.path.exists(p), p
     77     self._injars = paths
     78 
     79   def configs(self, paths):
     80     assert self._cmd is None
     81     assert self._configs is None
     82     for p in paths:
     83       assert os.path.exists(p), p
     84     self._configs = paths
     85 
     86   def verbose(self, verbose):
     87     assert self._cmd is None
     88     self._verbose = verbose
     89 
     90   def build(self):
     91     if self._cmd:
     92       return self._cmd
     93     assert self._injars is not None
     94     assert self._outjar is not None
     95     assert self._configs is not None
     96     cmd = [
     97       'java', '-jar', self._proguard_jar_path,
     98       '-forceprocessing',
     99     ]
    100     if self._tested_apk_info_path:
    101       assert len(self._configs) == 1
    102       tested_apk_info = build_utils.ReadJson(self._tested_apk_info_path)
    103       self._configs += tested_apk_info['configs']
    104       self._injars = [
    105           p for p in self._injars if not p in tested_apk_info['inputs']]
    106       if not self._libraries:
    107         self._libraries = []
    108       self._libraries += tested_apk_info['inputs']
    109       self._mapping = tested_apk_info['mapping']
    110       cmd += [
    111         '-dontobfuscate',
    112         '-dontoptimize',
    113         '-dontshrink',
    114         '-dontskipnonpubliclibraryclassmembers',
    115       ]
    116 
    117     if self._mapping:
    118       cmd += [
    119         '-applymapping', self._mapping,
    120       ]
    121 
    122     if self._libraries:
    123       cmd += [
    124         '-libraryjars', ':'.join(self._libraries),
    125       ]
    126 
    127     cmd += [
    128       '-injars', ':'.join(self._injars)
    129     ]
    130 
    131     for config_file in self._configs:
    132       cmd += ['-include', config_file]
    133 
    134     # The output jar must be specified after inputs.
    135     cmd += [
    136       '-outjars', self._outjar,
    137       '-dump', self._outjar + '.dump',
    138       '-printseeds', self._outjar + '.seeds',
    139       '-printusage', self._outjar + '.usage',
    140       '-printmapping', self._outjar + '.mapping',
    141     ]
    142 
    143     if self._verbose:
    144       cmd.append('-verbose')
    145 
    146     self._cmd = cmd
    147     return self._cmd
    148 
    149   def GetInputs(self):
    150     self.build()
    151     inputs = [self._proguard_jar_path] + self._configs + self._injars
    152     if self._mapping:
    153       inputs.append(self._mapping)
    154     if self._libraries:
    155       inputs += self._libraries
    156     if self._tested_apk_info_path:
    157       inputs += [self._tested_apk_info_path]
    158     return inputs
    159 
    160 
    161   def CheckOutput(self):
    162     self.build()
    163     # Proguard will skip writing these files if they would be empty. Create
    164     # empty versions of them all now so that they are updated as the build
    165     # expects.
    166     open(self._outjar + '.dump', 'w').close()
    167     open(self._outjar + '.seeds', 'w').close()
    168     open(self._outjar + '.usage', 'w').close()
    169     open(self._outjar + '.mapping', 'w').close()
    170     # Warning: and Error: are sent to stderr, but messages and Note: are sent
    171     # to stdout.
    172     stdout_filter = None
    173     stderr_filter = None
    174     if not self._verbose:
    175       stdout_filter = _ProguardOutputFilter()
    176       stderr_filter = _ProguardOutputFilter()
    177     build_utils.CheckOutput(self._cmd, print_stdout=True,
    178                             print_stderr=True,
    179                             stdout_filter=stdout_filter,
    180                             stderr_filter=stderr_filter)
    181 
    182     this_info = {
    183       'inputs': self._injars,
    184       'configs': self._configs,
    185       'mapping': self._outjar + '.mapping',
    186     }
    187 
    188     build_utils.WriteJson(this_info, self._outjar + '.info')
    189 
    190