• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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|Printing|ProgramClass:|Searching|'
18      r'jar \[|\d+ class path entries checked')
19
20  def __init__(self):
21    self._last_line_ignored = False
22    self._ignore_next_line = False
23
24  def __call__(self, output):
25    ret = []
26    for line in output.splitlines(True):
27      if self._ignore_next_line:
28        self._ignore_next_line = False
29        continue
30
31      if '***BINARY RUN STATS***' in line:
32        self._last_line_ignored = True
33        self._ignore_next_line = True
34      elif not line.startswith(' '):
35        self._last_line_ignored = bool(self.IGNORE_RE.match(line))
36      elif 'You should check if you need to specify' in line:
37        self._last_line_ignored = True
38
39      if not self._last_line_ignored:
40        ret.append(line)
41    return ''.join(ret)
42
43
44class ProguardCmdBuilder(object):
45  def __init__(self, proguard_jar):
46    assert os.path.exists(proguard_jar)
47    self._proguard_jar_path = proguard_jar
48    self._mapping = None
49    self._libraries = None
50    self._injars = None
51    self._configs = None
52    self._config_exclusions = None
53    self._outjar = None
54    self._verbose = False
55    self._disabled_optimizations = []
56
57  def outjar(self, path):
58    assert self._outjar is None
59    self._outjar = path
60
61  def mapping(self, path):
62    assert self._mapping is None
63    assert os.path.exists(path), path
64    self._mapping = path
65
66  def libraryjars(self, paths):
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._injars is None
74    for p in paths:
75      assert os.path.exists(p), p
76    self._injars = paths
77
78  def configs(self, paths):
79    assert self._configs is None
80    self._configs = paths
81    for p in self._configs:
82      assert os.path.exists(p), p
83
84  def config_exclusions(self, paths):
85    assert self._config_exclusions is None
86    self._config_exclusions = paths
87
88  def verbose(self, verbose):
89    self._verbose = verbose
90
91  def disable_optimizations(self, optimizations):
92    self._disabled_optimizations += optimizations
93
94  def build(self):
95    assert self._injars is not None
96    assert self._outjar is not None
97    assert self._configs is not None
98    cmd = [
99      'java', '-jar', self._proguard_jar_path,
100      '-forceprocessing',
101    ]
102
103    if self._mapping:
104      cmd += ['-applymapping', self._mapping]
105
106    if self._libraries:
107      cmd += ['-libraryjars', ':'.join(self._libraries)]
108
109    for optimization in self._disabled_optimizations:
110      cmd += [ '-optimizations', '!' + optimization ]
111
112    # Filter to just .class files to avoid warnings about multiple inputs having
113    # the same files in META_INF/.
114    cmd += [
115        '-injars',
116        ':'.join('{}(**.class)'.format(x) for x in self._injars)
117    ]
118
119    for config_file in self.GetConfigs():
120      cmd += ['-include', config_file]
121
122    # The output jar must be specified after inputs.
123    cmd += [
124      '-outjars', self._outjar,
125      '-printseeds', self._outjar + '.seeds',
126      '-printusage', self._outjar + '.usage',
127      '-printmapping', self._outjar + '.mapping',
128    ]
129
130    if self._verbose:
131      cmd.append('-verbose')
132
133    return cmd
134
135  def GetDepfileDeps(self):
136    # The list of inputs that the GN target does not directly know about.
137    inputs = self._configs + self._injars
138    if self._libraries:
139      inputs += self._libraries
140    return inputs
141
142  def GetConfigs(self):
143    ret = list(self._configs)
144    for path in self._config_exclusions:
145      ret.remove(path)
146    return ret
147
148  def GetInputs(self):
149    inputs = self.GetDepfileDeps()
150    inputs += [self._proguard_jar_path]
151    if self._mapping:
152      inputs.append(self._mapping)
153    return inputs
154
155  def GetOutputs(self):
156    return [
157        self._outjar,
158        self._outjar + '.flags',
159        self._outjar + '.mapping',
160        self._outjar + '.seeds',
161        self._outjar + '.usage',
162    ]
163
164  def _WriteFlagsFile(self, cmd, out):
165    # Quite useful for auditing proguard flags.
166    for config in sorted(self._configs):
167      out.write('#' * 80 + '\n')
168      out.write(config + '\n')
169      out.write('#' * 80 + '\n')
170      with open(config) as config_file:
171        contents = config_file.read().rstrip()
172      # Remove numbers from generated rule comments to make file more
173      # diff'able.
174      contents = re.sub(r' #generated:\d+', '', contents)
175      out.write(contents)
176      out.write('\n\n')
177    out.write('#' * 80 + '\n')
178    out.write('Command-line\n')
179    out.write('#' * 80 + '\n')
180    out.write(' '.join(cmd) + '\n')
181
182  def CheckOutput(self):
183    cmd = self.build()
184
185    # There are a couple scenarios (.mapping files and switching from no
186    # proguard -> proguard) where GN's copy() target is used on output
187    # paths. These create hardlinks, so we explicitly unlink here to avoid
188    # updating files with multiple links.
189    for path in self.GetOutputs():
190      if os.path.exists(path):
191        os.unlink(path)
192
193    with open(self._outjar + '.flags', 'w') as out:
194      self._WriteFlagsFile(cmd, out)
195
196    # Warning: and Error: are sent to stderr, but messages and Note: are sent
197    # to stdout.
198    stdout_filter = None
199    stderr_filter = None
200    if not self._verbose:
201      stdout_filter = ProguardOutputFilter()
202      stderr_filter = ProguardOutputFilter()
203    build_utils.CheckOutput(cmd, print_stdout=True,
204                            print_stderr=True,
205                            stdout_filter=stdout_filter,
206                            stderr_filter=stderr_filter)
207
208    # Proguard will skip writing -printseeds / -printusage / -printmapping if
209    # the files would be empty, but ninja needs all outputs to exist.
210    open(self._outjar + '.seeds', 'a').close()
211    open(self._outjar + '.usage', 'a').close()
212    open(self._outjar + '.mapping', 'a').close()
213