The Android Open Source Project | 52d4c30 | 2009-03-03 19:29:09 -0800 | [diff] [blame^] | 1 | #!/usr/bin/ruby |
| 2 | # |
| 3 | # Find unused resources in all the apps found recursively under the current directory |
| 4 | # Usage: |
| 5 | # find_unused_resources.rb [-html] |
| 6 | # |
| 7 | # If -html is specified, the output will be HTML, otherwise it will be plain text |
| 8 | # |
| 9 | # Author: cbeust@google.com |
| 10 | |
| 11 | require 'find' |
| 12 | |
| 13 | debug = false |
| 14 | |
| 15 | @@stringIdPattern = Regexp.new("name=\"([@_a-zA-Z0-9 ]*)\"") |
| 16 | @@layoutIdPattern = Regexp.new("android:id=\".*id/([_a-zA-Z0-9]*)\"") |
| 17 | |
| 18 | @@stringXmlPatterns = [ |
| 19 | Regexp.new("@string/([_a-zA-Z0-9]*)"), |
| 20 | Regexp.new("@array/([_a-zA-Z0-9]*)"), |
| 21 | ] |
| 22 | |
| 23 | @@javaIdPatterns = [ |
| 24 | Regexp.new("R.id.([_a-zA-Z0-9]+)"), |
| 25 | Regexp.new("R.string.([_a-zA-Z0-9]+)"), |
| 26 | Regexp.new("R.array.([_a-zA-Z0-9]+)"), |
| 27 | Regexp.new("R.color.([_a-zA-Z0-9]+)"), |
| 28 | Regexp.new("R.configVarying.([_a-zA-Z0-9]+)"), |
| 29 | Regexp.new("R.dimen.([_a-zA-Z0-9]+)"), |
| 30 | ] |
| 31 | |
| 32 | |
| 33 | @@appDir = "partner/google/apps/Gmail" |
| 34 | |
| 35 | def findResDirectories(root) |
| 36 | result = Array.new |
| 37 | Find.find(root) do |path| |
| 38 | if FileTest.directory?(path) |
| 39 | if File.basename(path) == "res" |
| 40 | result << path |
| 41 | else |
| 42 | next |
| 43 | end |
| 44 | end |
| 45 | end |
| 46 | result |
| 47 | end |
| 48 | |
| 49 | class UnusedResources |
| 50 | attr_accessor :appDir, :unusedLayoutIds, :unusedStringIds |
| 51 | end |
| 52 | |
| 53 | class FilePosition |
| 54 | attr_accessor :file, :lineNumber |
| 55 | |
| 56 | def initialize(f, ln) |
| 57 | @file = f |
| 58 | @lineNumber = ln |
| 59 | end |
| 60 | |
| 61 | def to_s |
| 62 | "#{file}:#{lineNumber}" |
| 63 | end |
| 64 | |
| 65 | def <=>(other) |
| 66 | if @file == other.file |
| 67 | @lineNumber - other.lineNumber |
| 68 | else |
| 69 | @file <=> other.file |
| 70 | end |
| 71 | end |
| 72 | end |
| 73 | |
| 74 | |
| 75 | def findAllOccurrences(re, string) |
| 76 | result = Array.new |
| 77 | |
| 78 | s = string |
| 79 | matchData = re.match(s) |
| 80 | while (matchData) |
| 81 | result << matchData[1].to_s |
| 82 | s = s[matchData.end(1) .. -1] |
| 83 | matchData = re.match(s) |
| 84 | end |
| 85 | |
| 86 | result |
| 87 | end |
| 88 | |
| 89 | @@globalJavaIdUses = Hash.new |
| 90 | |
| 91 | def recordJavaUses(glob) |
| 92 | Dir.glob(glob).each { |filename| |
| 93 | File.open(filename) { |file| |
| 94 | file.each { |line| |
| 95 | @@javaIdPatterns.each { |re| |
| 96 | findAllOccurrences(re, line).each { |id| |
| 97 | @@globalJavaIdUses[id] = FilePosition.new(filename, file.lineno) |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | end |
| 104 | |
| 105 | def findUnusedResources(dir) |
| 106 | javaIdUses = Hash.new |
| 107 | layouts = Hash.new |
| 108 | strings = Hash.new |
| 109 | xmlIdUses = Hash.new |
| 110 | |
| 111 | Dir.glob("#{dir}/res/**/*.xml").each { |filename| |
| 112 | if ! (filename =~ /attrs.xml$/) |
| 113 | File.open(filename) { |file| |
| 114 | file.each { |line| |
| 115 | findAllOccurrences(@@stringIdPattern, line).each {|id| |
| 116 | strings[id] = FilePosition.new(filename, file.lineno) |
| 117 | } |
| 118 | findAllOccurrences(@@layoutIdPattern, line).each {|id| |
| 119 | layouts[id] = FilePosition.new(filename, file.lineno) |
| 120 | } |
| 121 | @@stringXmlPatterns.each { |re| |
| 122 | findAllOccurrences(re, line).each {|id| |
| 123 | xmlIdUses[id] = FilePosition.new(filename, file.lineno) |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | end |
| 129 | } |
| 130 | |
| 131 | Dir.glob("#{dir}/AndroidManifest.xml").each { |filename| |
| 132 | File.open(filename) { |file| |
| 133 | file.each { |line| |
| 134 | @@stringXmlPatterns.each { |re| |
| 135 | findAllOccurrences(re, line).each {|id| |
| 136 | xmlIdUses[id] = FilePosition.new(filename, file.lineno) |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | recordJavaUses("#{dir}/src/**/*.java") |
| 144 | |
| 145 | @@globalJavaIdUses.each_pair { |id, file| |
| 146 | layouts.delete(id) |
| 147 | strings.delete(id) |
| 148 | } |
| 149 | |
| 150 | javaIdUses.each_pair { |id, file| |
| 151 | layouts.delete(id) |
| 152 | strings.delete(id) |
| 153 | } |
| 154 | |
| 155 | xmlIdUses.each_pair { |id, file| |
| 156 | layouts.delete(id) |
| 157 | strings.delete(id) |
| 158 | } |
| 159 | |
| 160 | result = UnusedResources.new |
| 161 | result.appDir = dir |
| 162 | result.unusedLayoutIds = layouts |
| 163 | result.unusedStringIds = strings |
| 164 | |
| 165 | result |
| 166 | end |
| 167 | |
| 168 | def findApps(dir) |
| 169 | result = Array.new |
| 170 | Dir.glob("#{dir}/**/res").each { |filename| |
| 171 | a = filename.split("/") |
| 172 | result << a.slice(0, a.size-1).join("/") |
| 173 | } |
| 174 | result |
| 175 | end |
| 176 | |
| 177 | def displayText(result) |
| 178 | result.each { |unusedResources| |
| 179 | puts "=== #{unusedResources.appDir}" |
| 180 | |
| 181 | puts "----- Unused layout ids" |
| 182 | unusedResources.unusedLayoutIds.sort { |id, file| id[1] <=> file[1] }.each {|f| |
| 183 | puts " #{f[0]} #{f[1]}" |
| 184 | } |
| 185 | |
| 186 | |
| 187 | puts "----- Unused string ids" |
| 188 | unusedResources.unusedStringIds.sort { |id, file| id[1] <=> file[1] }.each {|f| |
| 189 | puts " #{f[0]} #{f[1]}" |
| 190 | } |
| 191 | |
| 192 | } |
| 193 | end |
| 194 | |
| 195 | def displayHtmlUnused(unusedResourceIds, title) |
| 196 | |
| 197 | puts "<h3>#{title}</h3>" |
| 198 | puts "<table border='1'>" |
| 199 | unusedResourceIds.sort { |id, file| id[1] <=> file[1] }.each {|f| |
| 200 | puts "<tr><td><b>#{f[0]}</b></td> <td>#{f[1]}</td></tr>" |
| 201 | } |
| 202 | puts "</table>" |
| 203 | end |
| 204 | |
| 205 | def displayHtml(result) |
| 206 | title = "Unused resources as of #{Time.now.localtime}" |
| 207 | puts "<html><header><title>#{title}</title></header><body>" |
| 208 | |
| 209 | puts "<h1><p align=\"center\">#{title}</p></h1>" |
| 210 | result.each { |unusedResources| |
| 211 | puts "<h2>#{unusedResources.appDir}</h2>" |
| 212 | displayHtmlUnused(unusedResources.unusedLayoutIds, "Unused layout ids") |
| 213 | displayHtmlUnused(unusedResources.unusedStringIds, "Unused other ids") |
| 214 | } |
| 215 | puts "</body>" |
| 216 | end |
| 217 | |
| 218 | result = Array.new |
| 219 | |
| 220 | recordJavaUses("java/android/**/*.java") |
| 221 | |
| 222 | if debug |
| 223 | result << findUnusedResources("apps/Browser") |
| 224 | else |
| 225 | findApps(".").each { |appDir| |
| 226 | result << findUnusedResources(appDir) |
| 227 | } |
| 228 | end |
| 229 | |
| 230 | if ARGV[0] == "-html" |
| 231 | displayHtml result |
| 232 | else |
| 233 | displayText result |
| 234 | end |
| 235 | |