Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2017 The Chromium Authors. All rights reserved. | 2 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Tool for finding the cause of APK bloat. | 6 """Tool for finding the cause of APK bloat. |
| 7 | 7 |
| 8 Run diagnose_apk_bloat.py -h for detailed usage help. | 8 Run diagnose_apk_bloat.py -h for detailed usage help. |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import argparse | 11 import argparse |
| 12 import logging | 12 import collections |
| 13 import itertools | |
| 14 import json | |
| 13 import multiprocessing | 15 import multiprocessing |
| 14 import os | 16 import os |
| 15 import shutil | 17 import shutil |
| 16 import subprocess | 18 import subprocess |
| 17 import sys | 19 import sys |
| 18 | 20 |
| 19 import helpers | 21 _SRC_ROOT = os.path.abspath( |
| 22 os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) | |
| 23 _DEFAULT_OUT_DIR = os.path.join(_SRC_ROOT, 'out', 'diagnose-apk-bloat') | |
| 24 _DEFAULT_TARGET = 'monochrome_public_apk' | |
| 25 _DEFAULT_ARCHIVE_DIR = os.path.join(_SRC_ROOT, 'binary-size-bloat') | |
| 20 | 26 |
| 21 _DEFAULT_OUT_DIR = os.path.join(helpers.SRC_ROOT, 'out', 'diagnose-apk-bloat') | 27 # Global variable for storing the initial branch before the script was launched |
| 22 _DEFAULT_TARGET = 'monochrome_public_apk' | 28 # so that it doesn't need to be passed everywhere in case we fail and exit. |
| 23 _DEFAULT_ARCHIVE_DIR = os.path.join(helpers.SRC_ROOT, 'binary-size-bloat') | 29 _initial_branch = None |
| 30 | |
| 31 | |
| 32 class BaseDiff(object): | |
| 33 """Base class capturing binary size diffs.""" | |
| 34 def __init__(self, name): | |
| 35 self.name = name | |
| 36 self.banner = '\n' + '*' * 30 + name + '*' * 30 | |
| 37 self.RunDiff() | |
| 38 | |
| 39 def PrintResults(self, output_file): | |
|
agrieve
2017/04/12 02:02:05
nit: Maybe just rename this to "AppendResults", an
| |
| 40 with open(output_file, 'a') as logfile: | |
| 41 _PrintAndWriteToFile(logfile, self.banner) | |
| 42 _PrintAndWriteToFile(logfile, 'Summary:') | |
| 43 _PrintAndWriteToFile(logfile, self.Summary()) | |
| 44 _PrintAndWriteToFile(logfile, '\nDetails:') | |
| 45 for l in self.DetailedResults(): | |
| 46 _PrintAndWriteToFile(logfile, l) | |
| 47 | |
| 48 def Summary(self): | |
| 49 """A short description that summarizes the source of binary size bloat.""" | |
| 50 raise NotImplementedError() | |
| 51 | |
| 52 def DetailedResults(self): | |
| 53 """An iterable description of the cause of binary size bloat.""" | |
| 54 raise NotImplementedError() | |
| 55 | |
| 56 def ProduceDiff(self): | |
| 57 """Prepare a binary size diff with ready to print results.""" | |
| 58 raise NotImplementedError() | |
| 59 | |
| 60 def RunDiff(self): | |
| 61 _Print('Creating {}', self.name) | |
| 62 self.ProduceDiff() | |
| 63 | |
| 64 | |
| 65 _ResourceSizesDiffResult = collections.namedtuple( | |
| 66 'ResourceSizesDiffResult', ['section', 'value', 'units']) | |
| 67 | |
| 68 | |
| 69 class ResourceSizesDiff(BaseDiff): | |
| 70 _RESOURCE_SIZES_PATH = os.path.join( | |
| 71 _SRC_ROOT, 'build', 'android', 'resource_sizes.py') | |
| 72 | |
| 73 def __init__(self, archive_dirs, apk_name, slow_options=False): | |
| 74 self._archive_dirs = archive_dirs | |
| 75 self._apk_name = apk_name | |
| 76 self._slow_options = slow_options | |
| 77 self._diff = None # Set by |ProduceDiff()| | |
| 78 super(ResourceSizesDiff, self).__init__('Resource Sizes Diff') | |
| 79 | |
| 80 def DetailedResults(self): | |
| 81 for section, value, units in self._diff: | |
| 82 yield '{:>+10,} {} {}'.format(value, units, section) | |
| 83 | |
| 84 def Summary(self): | |
| 85 for s in self._diff: | |
| 86 if 'normalized' in s.section: | |
| 87 return 'Normalized APK size: {:+,} {}'.format(s.value, s.units) | |
| 88 return '' | |
| 89 | |
| 90 def ProduceDiff(self): | |
| 91 chartjsons = self._RunResourceSizes() | |
| 92 diff = [] | |
| 93 with_patch = chartjsons[0]['charts'] | |
| 94 without_patch = chartjsons[1]['charts'] | |
| 95 for section, section_dict in with_patch.iteritems(): | |
| 96 for subsection, v in section_dict.iteritems(): | |
| 97 # Ignore entries when resource_sizes.py chartjson format has changed. | |
| 98 if (section not in without_patch or | |
| 99 subsection not in without_patch[section] or | |
| 100 v['units'] != without_patch[section][subsection]['units']): | |
| 101 _Print('Found differing dict structures for resource_sizes.py, ' | |
| 102 'skipping {} {}', section, subsection) | |
| 103 else: | |
| 104 diff.append( | |
| 105 _ResourceSizesDiffResult( | |
| 106 '%s %s' % (section, subsection), | |
| 107 v['value'] - without_patch[section][subsection]['value'], | |
| 108 v['units'])) | |
| 109 self._diff = sorted(diff, key=lambda x: abs(x.value), reverse=True) | |
| 110 | |
| 111 def _RunResourceSizes(self): | |
| 112 chartjsons = [] | |
| 113 for archive_dir in self._archive_dirs: | |
| 114 apk_path = os.path.join(archive_dir, self._apk_name) | |
| 115 chartjson_file = os.path.join(archive_dir, 'results-chart.json') | |
| 116 cmd = [self._RESOURCE_SIZES_PATH, '--output-dir', archive_dir, | |
| 117 '--no-output-dir', | |
| 118 '--chartjson', apk_path] | |
| 119 if self._slow_options: | |
| 120 cmd += ['--estimate-patch-size'] | |
| 121 else: | |
| 122 cmd += ['--no-static-initializer-check'] | |
| 123 _RunCmd(cmd) | |
| 124 with open(chartjson_file) as f: | |
| 125 chartjsons.append(json.load(f)) | |
| 126 return chartjsons | |
| 24 | 127 |
| 25 | 128 |
| 26 class _BuildHelper(object): | 129 class _BuildHelper(object): |
| 27 """Helper class for generating and building targets.""" | 130 """Helper class for generating and building targets.""" |
| 28 | 131 |
| 29 def __init__(self, args): | 132 def __init__(self, args): |
| 30 self.enable_chrome_android_internal = args.enable_chrome_android_internal | 133 self.enable_chrome_android_internal = args.enable_chrome_android_internal |
| 31 self.max_jobs = args.max_jobs | 134 self.max_jobs = args.max_jobs |
| 32 self.max_load_average = args.max_load_average | 135 self.max_load_average = args.max_load_average |
| 33 self.output_directory = args.output_directory | 136 self.output_directory = args.output_directory |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 55 return ['gn', 'gen', self.output_directory, '--args=%s' % gn_args] | 158 return ['gn', 'gen', self.output_directory, '--args=%s' % gn_args] |
| 56 | 159 |
| 57 def _GenNinjaCmd(self): | 160 def _GenNinjaCmd(self): |
| 58 cmd = ['ninja', '-C', self.output_directory] | 161 cmd = ['ninja', '-C', self.output_directory] |
| 59 cmd += ['-j', self.max_jobs] if self.max_jobs else [] | 162 cmd += ['-j', self.max_jobs] if self.max_jobs else [] |
| 60 cmd += ['-l', self.max_load_average] if self.max_load_average else [] | 163 cmd += ['-l', self.max_load_average] if self.max_load_average else [] |
| 61 cmd += [self.target] | 164 cmd += [self.target] |
| 62 return cmd | 165 return cmd |
| 63 | 166 |
| 64 def Build(self): | 167 def Build(self): |
| 65 logging.info('Building %s. This may take a while (run with -vv for ' | 168 _Print('Building: {}.', self.target) |
| 66 'detailed ninja output).', self.target) | |
| 67 _RunCmd(self._GenGnCmd()) | 169 _RunCmd(self._GenGnCmd()) |
| 68 _RunCmd(self._GenNinjaCmd(), print_stdout=True) | 170 _RunCmd(self._GenNinjaCmd(), print_stdout=True) |
| 69 | 171 |
| 70 | 172 |
| 71 def _GetLinkerMapPath(target_os, target): | 173 def _GetMainLibPath(target_os, target): |
| 72 # TODO(estevenson): Get this from GN instead of hardcoding. | 174 # TODO(estevenson): Get this from GN instead of hardcoding. |
| 73 if target_os == 'linux': | 175 if target_os == 'linux': |
| 74 return 'chrome.map.gz' | 176 return 'chrome' |
| 75 elif 'monochrome' in target: | 177 elif 'monochrome' in target: |
| 76 return 'lib.unstripped/libmonochrome.so.map.gz' | 178 return 'lib.unstripped/libmonochrome.so' |
| 77 else: | 179 else: |
| 78 return 'lib.unstripped/libchrome.so.map.gz' | 180 return 'lib.unstripped/libchrome.so' |
| 79 | 181 |
| 80 | 182 |
| 81 def _ApkPathFromTarget(target): | 183 def _ApkNameFromTarget(target): |
| 82 # Only works on apk targets that follow: my_great_apk naming convention. | 184 # Only works on apk targets that follow: my_great_apk naming convention. |
| 83 apk_name = ''.join(s.title() for s in target.split('_')[:-1]) + '.apk' | 185 apk_name = ''.join(s.title() for s in target.split('_')[:-1]) + '.apk' |
| 84 return os.path.join('apks', apk_name) | 186 return apk_name.replace('Webview', 'WebView') |
| 85 | 187 |
| 86 | 188 |
| 87 def _RunCmd(cmd, print_stdout=False): | 189 def _RunCmd(cmd, print_stdout=False): |
| 88 """Convenience function for running commands. | 190 """Convenience function for running commands. |
| 89 | 191 |
| 90 Args: | 192 Args: |
| 91 cmd: the command to run. | 193 cmd: the command to run. |
| 92 print_stdout: if this is True, then the stdout of the process will be | 194 print_stdout: if this is True, then the stdout of the process will be |
| 93 printed (to stdout if log level is DEBUG otherwise to /dev/null). | 195 printed, otherwise stdout will be returned. |
| 94 If false, stdout will be returned. | |
| 95 | 196 |
| 96 Returns: | 197 Returns: |
| 97 Command stdout if |print_stdout| is False otherwise ''. | 198 Command stdout if |print_stdout| is False otherwise ''. |
| 98 """ | 199 """ |
| 99 cmd_str = ' '.join(c for c in cmd) | 200 cmd_str = ' '.join(c for c in cmd) |
| 100 logging.debug('Running: %s', cmd_str) | 201 _Print('Running: {}', cmd_str) |
| 101 if not print_stdout: | 202 if print_stdout: |
| 102 proc_stdout = subprocess.PIPE | |
| 103 elif logging.getLogger().isEnabledFor(logging.DEBUG): | |
| 104 proc_stdout = sys.stdout | 203 proc_stdout = sys.stdout |
| 105 else: | 204 else: |
| 106 proc_stdout = open(os.devnull, 'wb') | 205 proc_stdout = subprocess.PIPE |
| 107 | 206 |
| 108 proc = subprocess.Popen(cmd, stdout=proc_stdout, stderr=subprocess.PIPE) | 207 proc = subprocess.Popen(cmd, stdout=proc_stdout, stderr=subprocess.PIPE) |
| 109 stdout, stderr = proc.communicate() | 208 stdout, stderr = proc.communicate() |
| 110 | 209 |
| 111 if proc.returncode != 0: | 210 if proc.returncode != 0: |
| 112 logging.error('Command failed: %s\nstderr:\n%s' % (cmd_str, stderr)) | 211 _Die('command failed: {}\nstderr:\n{}', cmd_str, stderr) |
| 113 sys.exit(1) | |
| 114 | 212 |
| 115 return stdout.strip() if stdout else '' | 213 return stdout.strip() if stdout else '' |
| 116 | 214 |
| 117 | 215 |
| 118 def _GitCmd(args): | 216 def _GitCmd(args): |
| 119 return _RunCmd(['git', '-C', helpers.SRC_ROOT] + args) | 217 return _RunCmd(['git', '-C', _SRC_ROOT] + args) |
| 120 | 218 |
| 121 | 219 |
| 122 def _GclientSyncCmd(rev): | 220 def _GclientSyncCmd(rev): |
| 123 cwd = os.getcwd() | 221 cwd = os.getcwd() |
| 124 os.chdir(helpers.SRC_ROOT) | 222 os.chdir(_SRC_ROOT) |
| 125 logging.info('gclient sync to %s', rev) | |
| 126 _RunCmd(['gclient', 'sync', '-r', 'src@' + rev], print_stdout=True) | 223 _RunCmd(['gclient', 'sync', '-r', 'src@' + rev], print_stdout=True) |
| 127 os.chdir(cwd) | 224 os.chdir(cwd) |
| 128 | 225 |
| 129 | 226 |
| 130 def _ArchiveBuildResult(archive_dir, build_helper): | 227 def _ArchiveBuildResult(archive_dir, build_helper): |
| 131 """Save resulting APK and mapping file.""" | 228 """Save build artifacts necessary for diffing.""" |
| 132 def ArchiveFile(file_path): | 229 _Print('Saving build results to: {}', archive_dir) |
| 133 file_path = os.path.join(build_helper.output_directory, file_path) | 230 if not os.path.exists(archive_dir): |
| 134 if os.path.exists(file_path): | 231 os.makedirs(archive_dir) |
| 135 if not os.path.exists(archive_dir): | |
| 136 os.makedirs(archive_dir) | |
| 137 shutil.copy(file_path, archive_dir) | |
| 138 else: | |
| 139 logging.error('Expected file: %s not found.' % file_path) | |
| 140 sys.exit(1) | |
| 141 | 232 |
| 142 logging.info('Saving build results to: %s', archive_dir) | 233 def ArchiveFile(filename): |
| 143 ArchiveFile(_GetLinkerMapPath(build_helper.target_os, build_helper.target)) | 234 if not os.path.exists(filename): |
| 235 _Die('missing expected file: {}', filename) | |
| 236 shutil.copy(filename, archive_dir) | |
| 237 | |
| 238 lib_path = os.path.join( | |
| 239 build_helper.output_directory, | |
| 240 _GetMainLibPath(build_helper.target_os, build_helper.target)) | |
| 241 ArchiveFile(lib_path) | |
| 242 | |
| 243 size_path = os.path.join( | |
| 244 archive_dir, os.path.splitext(os.path.basename(lib_path))[0] + '.size') | |
| 245 supersize_path = os.path.join(_SRC_ROOT, 'tools/binary_size/supersize') | |
| 246 _RunCmd([supersize_path, 'archive', size_path, '--output-directory', | |
| 247 build_helper.output_directory, '--elf-file', lib_path]) | |
| 248 | |
| 144 if build_helper.target_os == 'android': | 249 if build_helper.target_os == 'android': |
| 145 ArchiveFile(_ApkPathFromTarget(build_helper.target)) | 250 apk_path = os.path.join(build_helper.output_directory, 'apks', |
| 251 _ApkNameFromTarget(build_helper.target)) | |
| 252 ArchiveFile(apk_path) | |
| 146 | 253 |
| 147 | 254 |
| 148 def _SyncAndBuild(rev_with_patch, rev_without_patch, archive_dir, build_helper): | 255 def _SyncAndBuild(revs, archive_dirs, build_helper): |
| 149 rev_with_patch = _GitCmd(['rev-parse', rev_with_patch]) | |
| 150 rev_without_patch = _GitCmd([ | |
| 151 'rev-parse', rev_without_patch or rev_with_patch + '^']) | |
| 152 | |
| 153 # Move to a detached state since gclient sync doesn't work with local commits | 256 # Move to a detached state since gclient sync doesn't work with local commits |
| 154 # on a branch. | 257 # on a branch. |
| 155 _GitCmd(['checkout', '--detach']) | 258 _GitCmd(['checkout', '--detach']) |
| 259 for rev, archive_dir in itertools.izip(revs, archive_dirs): | |
| 260 _GclientSyncCmd(rev) | |
| 261 build_helper.Build() | |
| 262 _ArchiveBuildResult(archive_dir, build_helper) | |
| 156 | 263 |
| 157 _GclientSyncCmd(rev_with_patch) | |
| 158 build_helper.Build() | |
| 159 _ArchiveBuildResult( | |
| 160 os.path.join(archive_dir, 'with_patch_%s' % rev_with_patch), build_helper) | |
| 161 | 264 |
| 162 _GclientSyncCmd(rev_without_patch) | 265 def _NormalizeRev(rev): |
| 163 build_helper.Build() | 266 """Use actual revs instead of HEAD, HEAD^, etc.""" |
| 164 _ArchiveBuildResult( | 267 return _GitCmd(['rev-parse', rev]) |
| 165 os.path.join(archive_dir, 'without_patch_%s' % rev_without_patch), | |
| 166 build_helper) | |
| 167 | 268 |
| 168 | 269 |
| 169 def _EnsureDirectoryClean(): | 270 def _EnsureDirectoryClean(): |
| 170 logging.info('Checking source directory') | 271 _Print('Checking source directory') |
| 171 stdout = _GitCmd(['status', '--porcelain']) | 272 stdout = _GitCmd(['status', '--porcelain']) |
| 172 # Ignore untracked files. | 273 # Ignore untracked files. |
| 173 if stdout and stdout[:2] != '??': | 274 if stdout and stdout[:2] != '??': |
| 174 logging.error('Failure: please ensure working directory is clean.') | 275 _Die('please ensure working directory is clean.') |
| 175 sys.exit(1) | 276 |
| 277 | |
| 278 def _SetInitialBranch(): | |
| 279 global _initial_branch | |
| 280 _initial_branch = _GitCmd(['rev-parse', '--abbrev-ref', 'HEAD']) | |
| 281 | |
| 282 | |
| 283 def _RestoreInitialBranch(): | |
| 284 if _initial_branch: | |
| 285 _GitCmd(['checkout', _initial_branch]) | |
| 286 | |
| 287 | |
| 288 def _Die(s, *args, **kwargs): | |
| 289 _Print('Failure: ' + s, *args, **kwargs) | |
| 290 _RestoreInitialBranch() | |
| 291 sys.exit(1) | |
| 292 | |
| 293 | |
| 294 def _Print(s, *args, **kwargs): | |
| 295 print s.format(*args, **kwargs) | |
| 296 | |
| 297 | |
| 298 def _PrintAndWriteToFile(logfile, s): | |
| 299 """Print |s| to |logfile| and stdout.""" | |
| 300 _Print(s) | |
| 301 logfile.write('%s\n' % s) | |
| 176 | 302 |
| 177 | 303 |
| 178 def main(): | 304 def main(): |
| 179 parser = argparse.ArgumentParser( | 305 parser = argparse.ArgumentParser( |
| 180 description='Find the cause of APK size bloat.', | 306 description='Find the cause of APK size bloat.', |
| 181 formatter_class=argparse.ArgumentDefaultsHelpFormatter) | 307 formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| 182 parser.add_argument('--archive-dir', | 308 parser.add_argument('--archive-dir', |
| 183 default=_DEFAULT_ARCHIVE_DIR, | 309 default=_DEFAULT_ARCHIVE_DIR, |
| 184 help='Where results are stored.') | 310 help='Where results are stored.') |
| 185 parser.add_argument('--rev-with-patch', | 311 parser.add_argument('--rev-with-patch', |
| 186 default='HEAD', | 312 default='HEAD', |
| 187 help='Commit with patch.') | 313 help='Commit with patch.') |
| 188 parser.add_argument('--rev-without-patch', | 314 parser.add_argument('--rev-without-patch', |
| 189 help='Older patch to diff against. If not supplied, ' | 315 help='Older patch to diff against. If not supplied, ' |
| 190 'the previous commit to rev_with_patch will be used.') | 316 'the previous commit to rev_with_patch will be used.') |
| 317 parser.add_argument('--include-slow-options', | |
| 318 action='store_true', | |
| 319 help='Run some extra steps that take longer to complete. ' | |
| 320 'This includes apk-patch-size estimation and ' | |
| 321 'static-initializer counting') | |
| 191 | 322 |
| 192 build_group = parser.add_argument_group('ninja', 'Args to use with ninja/gn') | 323 build_group = parser.add_argument_group('ninja', 'Args to use with ninja/gn') |
| 193 build_group.add_argument('-j', | 324 build_group.add_argument('-j', |
| 194 dest='max_jobs', | 325 dest='max_jobs', |
| 195 help='Run N jobs in parallel.') | 326 help='Run N jobs in parallel.') |
| 196 build_group.add_argument('-l', | 327 build_group.add_argument('-l', |
| 197 dest='max_load_average', | 328 dest='max_load_average', |
| 198 help='Do not start new jobs if the load average is ' | 329 help='Do not start new jobs if the load average is ' |
| 199 'greater than N.') | 330 'greater than N.') |
| 200 build_group.add_argument('--no-goma', | 331 build_group.add_argument('--no-goma', |
| 201 action='store_false', | 332 action='store_false', |
| 202 dest='use_goma', | 333 dest='use_goma', |
| 203 default=True, | 334 default=True, |
| 204 help='Use goma when building with ninja.') | 335 help='Use goma when building with ninja.') |
| 205 build_group.add_argument('--target-os', | 336 build_group.add_argument('--target-os', |
| 206 default='android', | 337 default='android', |
| 207 choices=['android', 'linux'], | 338 choices=['android', 'linux'], |
| 208 help='target_os gn arg.') | 339 help='target_os gn arg.') |
| 209 build_group.add_argument('--output-directory', | 340 build_group.add_argument('--output-directory', |
| 210 default=_DEFAULT_OUT_DIR, | 341 default=_DEFAULT_OUT_DIR, |
| 211 help='ninja output directory.') | 342 help='ninja output directory.') |
| 212 build_group.add_argument('--enable_chrome_android_internal', | 343 build_group.add_argument('--enable_chrome_android_internal', |
| 213 action='store_true', | 344 action='store_true', |
| 214 help='Allow downstream targets to be built.') | 345 help='Allow downstream targets to be built.') |
| 215 build_group.add_argument('--target', | 346 build_group.add_argument('--target', |
| 216 default=_DEFAULT_TARGET, | 347 default=_DEFAULT_TARGET, |
| 217 help='GN APK target to build.') | 348 help='GN APK target to build.') |
| 218 args = helpers.AddCommonOptionsAndParseArgs(parser, sys.argv, pypy_warn=False) | 349 args = parser.parse_args() |
| 219 | 350 |
| 220 _EnsureDirectoryClean() | 351 _EnsureDirectoryClean() |
| 352 _SetInitialBranch() | |
| 353 revs = [args.rev_with_patch, | |
| 354 args.rev_without_patch or args.rev_with_patch + '^'] | |
| 355 revs = [_NormalizeRev(r) for r in revs] | |
| 221 build_helper = _BuildHelper(args) | 356 build_helper = _BuildHelper(args) |
| 222 _SyncAndBuild(args.rev_with_patch, args.rev_without_patch, args.archive_dir, | 357 archive_dirs = [os.path.join(args.archive_dir, '%d-%s' % (len(revs) - i, rev)) |
| 223 build_helper) | 358 for i, rev in enumerate(revs)] |
| 359 _SyncAndBuild(revs, archive_dirs, build_helper) | |
| 360 _RestoreInitialBranch() | |
| 224 | 361 |
| 362 output_file = os.path.join(args.archive_dir, | |
| 363 'diff_result_{}_{}.txt'.format(*revs)) | |
| 364 if os.path.exists(output_file): | |
| 365 os.remove(output_file) | |
| 366 diffs = [] | |
| 367 if build_helper.target_os == 'android': | |
| 368 diffs += [ | |
| 369 ResourceSizesDiff(archive_dirs, _ApkNameFromTarget(args.target), | |
| 370 slow_options=args.include_slow_options) | |
| 371 ] | |
| 372 for d in diffs: | |
| 373 d.PrintResults(output_file) | |
| 225 | 374 |
| 226 if __name__ == '__main__': | 375 if __name__ == '__main__': |
| 227 sys.exit(main()) | 376 sys.exit(main()) |
| 228 | 377 |
| OLD | NEW |