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