1#!/usr/bin/env python3 2# encoding: utf-8 3# Copyright 2021 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7import argparse 8import os 9import pathlib 10import sys 11 12from util import build_utils 13from util import resource_utils 14import action_helpers # build_utils adds //build to sys.path. 15 16 17def _FilterUnusedResources(r_text_in, r_text_out, unused_resources_config): 18 removed_resources = set() 19 with open(unused_resources_config, encoding='utf-8') as output_config: 20 for line in output_config: 21 # example line: attr/line_height#remove 22 resource = line.split('#')[0] 23 resource_type, resource_name = resource.split('/') 24 removed_resources.add((resource_type, resource_name)) 25 kept_lines = [] 26 with open(r_text_in, encoding='utf-8') as infile: 27 for line in infile: 28 # example line: int attr line_height 0x7f0014ee 29 resource_type, resource_name = line.split(' ')[1:3] 30 if (resource_type, resource_name) not in removed_resources: 31 kept_lines.append(line) 32 33 with open(r_text_out, 'w', encoding='utf-8') as out_file: 34 out_file.writelines(kept_lines) 35 36 37def _WritePaths(dest_path, lines): 38 pathlib.Path(dest_path).write_text('\n'.join(lines) + '\n') 39 40 41def main(args): 42 parser = argparse.ArgumentParser() 43 44 action_helpers.add_depfile_arg(parser) 45 parser.add_argument('--script', 46 required=True, 47 help='Path to the unused resources detector script.') 48 parser.add_argument( 49 '--dependencies-res-zips', 50 required=True, 51 action='append', 52 help='Resources zip archives to investigate for unused resources.') 53 parser.add_argument('--dexes', 54 action='append', 55 required=True, 56 help='Path to dex file, or zip with dex files.') 57 parser.add_argument( 58 '--proguard-mapping', 59 help='Path to proguard mapping file for the optimized dex.') 60 parser.add_argument('--r-text-in', required=True, help='Path to input R.txt') 61 parser.add_argument( 62 '--r-text-out', 63 help='Path to output R.txt with unused resources removed.') 64 parser.add_argument('--android-manifests', 65 action='append', 66 required=True, 67 help='Path to AndroidManifest') 68 parser.add_argument('--output-config', 69 required=True, 70 help='Path to output the aapt2 config to.') 71 args = build_utils.ExpandFileArgs(args) 72 options = parser.parse_args(args) 73 options.dependencies_res_zips = (action_helpers.parse_gn_list( 74 options.dependencies_res_zips)) 75 76 # in case of no resources, short circuit early. 77 if not options.dependencies_res_zips: 78 build_utils.Touch(options.output_config) 79 return 80 81 with build_utils.TempDir() as temp_dir: 82 dep_subdirs = [] 83 for dependency_res_zip in options.dependencies_res_zips: 84 dep_subdirs += resource_utils.ExtractDeps([dependency_res_zip], temp_dir) 85 86 # Use files for paths to avoid command line getting too long. 87 # https://crbug.com/362019371 88 manifests_file = os.path.join(temp_dir, 'manifests-inputs.txt') 89 resources_file = os.path.join(temp_dir, 'resources-inputs.txt') 90 dexes_file = os.path.join(temp_dir, 'dexes-inputs.txt') 91 92 _WritePaths(manifests_file, options.android_manifests) 93 _WritePaths(resources_file, dep_subdirs) 94 _WritePaths(dexes_file, options.dexes) 95 96 cmd = [ 97 options.script, 98 '--rtxts', 99 options.r_text_in, 100 '--manifests', 101 manifests_file, 102 '--resourceDirs', 103 resources_file, 104 '--dexes', 105 dexes_file, 106 '--outputConfig', 107 options.output_config, 108 ] 109 if options.proguard_mapping: 110 cmd += [ 111 '--mapping', 112 options.proguard_mapping, 113 ] 114 build_utils.CheckOutput(cmd) 115 116 if options.r_text_out: 117 _FilterUnusedResources(options.r_text_in, options.r_text_out, 118 options.output_config) 119 120 if options.depfile: 121 depfile_deps = (options.dependencies_res_zips + options.android_manifests + 122 options.dexes) + [options.r_text_in] 123 if options.proguard_mapping: 124 depfile_deps.append(options.proguard_mapping) 125 action_helpers.write_depfile(options.depfile, options.output_config, 126 depfile_deps) 127 128 129if __name__ == '__main__': 130 main(sys.argv[1:]) 131