Home | History | Annotate | Download | only in findunused
      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 (at] 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 
    236