blob: 515b266dab1f06163f675c9efa66c8fa5655a6e2 [file] [log] [blame]
The Android Open Source Project52d4c302009-03-03 19:29:09 -08001#!/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
11require 'find'
12
13debug = 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
35def 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
47end
48
49class UnusedResources
50 attr_accessor :appDir, :unusedLayoutIds, :unusedStringIds
51end
52
53class 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
72end
73
74
75def 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
87end
88
89@@globalJavaIdUses = Hash.new
90
91def 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 }
103end
104
105def 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
166end
167
168def 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
175end
176
177def 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 }
193end
194
195def 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>"
203end
204
205def 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>"
216end
217
218result = Array.new
219
220recordJavaUses("java/android/**/*.java")
221
222if debug
223 result << findUnusedResources("apps/Browser")
224else
225 findApps(".").each { |appDir|
226 result << findUnusedResources(appDir)
227 }
228end
229
230if ARGV[0] == "-html"
231 displayHtml result
232else
233 displayText result
234end
235