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 5import os 6import re 7from util import build_utils 8 9 10class _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 35class 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