236 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/ruby
 | 
						|
#
 | 
						|
# Find unused resources in all the apps found recursively under the current directory
 | 
						|
# Usage:
 | 
						|
#   find_unused_resources.rb [-html]
 | 
						|
#
 | 
						|
# If -html is specified, the output will be HTML, otherwise it will be plain text
 | 
						|
#
 | 
						|
# Author: cbeust@google.com
 | 
						|
 | 
						|
require 'find'
 | 
						|
 | 
						|
debug = false
 | 
						|
 | 
						|
@@stringIdPattern = Regexp.new("name=\"([@_a-zA-Z0-9 ]*)\"")
 | 
						|
@@layoutIdPattern = Regexp.new("android:id=\".*id/([_a-zA-Z0-9]*)\"")
 | 
						|
 | 
						|
@@stringXmlPatterns = [
 | 
						|
  Regexp.new("@string/([_a-zA-Z0-9]*)"),
 | 
						|
  Regexp.new("@array/([_a-zA-Z0-9]*)"),
 | 
						|
]
 | 
						|
 | 
						|
@@javaIdPatterns = [
 | 
						|
  Regexp.new("R.id.([_a-zA-Z0-9]+)"),
 | 
						|
  Regexp.new("R.string.([_a-zA-Z0-9]+)"),
 | 
						|
  Regexp.new("R.array.([_a-zA-Z0-9]+)"),
 | 
						|
  Regexp.new("R.color.([_a-zA-Z0-9]+)"),
 | 
						|
  Regexp.new("R.configVarying.([_a-zA-Z0-9]+)"),
 | 
						|
  Regexp.new("R.dimen.([_a-zA-Z0-9]+)"),
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
@@appDir = "partner/google/apps/Gmail"
 | 
						|
 | 
						|
def findResDirectories(root)
 | 
						|
  result = Array.new
 | 
						|
  Find.find(root) do |path|
 | 
						|
    if FileTest.directory?(path)
 | 
						|
      if File.basename(path) == "res"
 | 
						|
        result << path
 | 
						|
      else
 | 
						|
        next
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
  result
 | 
						|
end
 | 
						|
 | 
						|
class UnusedResources
 | 
						|
  attr_accessor :appDir, :unusedLayoutIds, :unusedStringIds
 | 
						|
end
 | 
						|
 | 
						|
class FilePosition
 | 
						|
  attr_accessor :file, :lineNumber
 | 
						|
 | 
						|
  def initialize(f, ln)
 | 
						|
    @file = f
 | 
						|
    @lineNumber = ln
 | 
						|
  end
 | 
						|
 | 
						|
  def to_s
 | 
						|
    "#{file}:#{lineNumber}"
 | 
						|
  end
 | 
						|
 | 
						|
  def <=>(other)
 | 
						|
    if @file == other.file
 | 
						|
      @lineNumber - other.lineNumber
 | 
						|
    else
 | 
						|
      @file <=> other.file
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
def findAllOccurrences(re, string)
 | 
						|
  result = Array.new
 | 
						|
 | 
						|
  s = string
 | 
						|
  matchData = re.match(s)
 | 
						|
  while (matchData)
 | 
						|
    result << matchData[1].to_s
 | 
						|
    s = s[matchData.end(1) .. -1]
 | 
						|
    matchData = re.match(s)
 | 
						|
  end
 | 
						|
 | 
						|
  result
 | 
						|
end
 | 
						|
 | 
						|
@@globalJavaIdUses = Hash.new
 | 
						|
 | 
						|
def recordJavaUses(glob)
 | 
						|
  Dir.glob(glob).each { |filename|
 | 
						|
    File.open(filename) { |file|
 | 
						|
      file.each { |line|
 | 
						|
	@@javaIdPatterns.each { |re|
 | 
						|
          findAllOccurrences(re, line).each { |id|
 | 
						|
            @@globalJavaIdUses[id] = FilePosition.new(filename, file.lineno)
 | 
						|
	  }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
end
 | 
						|
 | 
						|
def findUnusedResources(dir)
 | 
						|
  javaIdUses = Hash.new
 | 
						|
  layouts = Hash.new
 | 
						|
  strings = Hash.new
 | 
						|
  xmlIdUses = Hash.new
 | 
						|
 | 
						|
  Dir.glob("#{dir}/res/**/*.xml").each { |filename|
 | 
						|
    if ! (filename =~ /attrs.xml$/)
 | 
						|
      File.open(filename) { |file|
 | 
						|
        file.each { |line|
 | 
						|
          findAllOccurrences(@@stringIdPattern, line).each {|id|
 | 
						|
            strings[id] = FilePosition.new(filename, file.lineno)
 | 
						|
          }
 | 
						|
          findAllOccurrences(@@layoutIdPattern, line).each {|id|
 | 
						|
            layouts[id] = FilePosition.new(filename, file.lineno)
 | 
						|
          }
 | 
						|
          @@stringXmlPatterns.each { |re|
 | 
						|
            findAllOccurrences(re, line).each {|id|
 | 
						|
              xmlIdUses[id] = FilePosition.new(filename, file.lineno)
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    end
 | 
						|
  }
 | 
						|
 
 | 
						|
  Dir.glob("#{dir}/AndroidManifest.xml").each { |filename|
 | 
						|
    File.open(filename) { |file|
 | 
						|
      file.each { |line|
 | 
						|
        @@stringXmlPatterns.each { |re|
 | 
						|
          findAllOccurrences(re, line).each {|id|
 | 
						|
            xmlIdUses[id] = FilePosition.new(filename, file.lineno)
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  recordJavaUses("#{dir}/src/**/*.java")
 | 
						|
 | 
						|
  @@globalJavaIdUses.each_pair { |id, file|
 | 
						|
    layouts.delete(id)
 | 
						|
    strings.delete(id)
 | 
						|
  }
 | 
						|
 | 
						|
  javaIdUses.each_pair { |id, file|
 | 
						|
    layouts.delete(id)
 | 
						|
    strings.delete(id)
 | 
						|
  }
 | 
						|
 | 
						|
  xmlIdUses.each_pair { |id, file|
 | 
						|
    layouts.delete(id)
 | 
						|
    strings.delete(id)
 | 
						|
  }
 | 
						|
 | 
						|
  result = UnusedResources.new
 | 
						|
  result.appDir = dir
 | 
						|
  result.unusedLayoutIds = layouts
 | 
						|
  result.unusedStringIds = strings
 | 
						|
 | 
						|
  result
 | 
						|
end
 | 
						|
 | 
						|
def findApps(dir)
 | 
						|
  result = Array.new
 | 
						|
  Dir.glob("#{dir}/**/res").each { |filename|
 | 
						|
    a = filename.split("/")
 | 
						|
    result << a.slice(0, a.size-1).join("/")
 | 
						|
  }
 | 
						|
  result
 | 
						|
end
 | 
						|
 | 
						|
def displayText(result)
 | 
						|
  result.each { |unusedResources|
 | 
						|
    puts "=== #{unusedResources.appDir}"
 | 
						|
 | 
						|
    puts "----- Unused layout ids"
 | 
						|
    unusedResources.unusedLayoutIds.sort { |id, file| id[1] <=> file[1] }.each {|f|
 | 
						|
      puts "    #{f[0]} #{f[1]}"
 | 
						|
    }
 | 
						|
 | 
						|
 
 | 
						|
    puts "----- Unused string ids"
 | 
						|
    unusedResources.unusedStringIds.sort { |id, file| id[1] <=> file[1] }.each {|f|
 | 
						|
      puts "    #{f[0]} #{f[1]}"
 | 
						|
    }
 | 
						|
 
 | 
						|
  }
 | 
						|
end
 | 
						|
 | 
						|
def displayHtmlUnused(unusedResourceIds, title)
 | 
						|
 | 
						|
  puts "<h3>#{title}</h3>"
 | 
						|
  puts "<table border='1'>"
 | 
						|
  unusedResourceIds.sort { |id, file| id[1] <=> file[1] }.each {|f|
 | 
						|
    puts "<tr><td><b>#{f[0]}</b></td> <td>#{f[1]}</td></tr>"
 | 
						|
  }
 | 
						|
  puts "</table>"
 | 
						|
end
 | 
						|
 | 
						|
def displayHtml(result)
 | 
						|
  title = "Unused resources as of #{Time.now.localtime}"
 | 
						|
  puts "<html><header><title>#{title}</title></header><body>"
 | 
						|
 | 
						|
  puts "<h1><p align=\"center\">#{title}</p></h1>"
 | 
						|
  result.each { |unusedResources|
 | 
						|
    puts "<h2>#{unusedResources.appDir}</h2>"
 | 
						|
    displayHtmlUnused(unusedResources.unusedLayoutIds, "Unused layout ids")
 | 
						|
    displayHtmlUnused(unusedResources.unusedStringIds, "Unused other ids")
 | 
						|
  }
 | 
						|
  puts "</body>"
 | 
						|
end
 | 
						|
 | 
						|
result = Array.new
 | 
						|
 | 
						|
recordJavaUses("java/android/**/*.java")
 | 
						|
 | 
						|
if debug
 | 
						|
  result << findUnusedResources("apps/Browser")
 | 
						|
else 
 | 
						|
  findApps(".").each { |appDir|
 | 
						|
    result << findUnusedResources(appDir)
 | 
						|
  }
 | 
						|
end
 | 
						|
 | 
						|
if ARGV[0] == "-html"
 | 
						|
  displayHtml result
 | 
						|
else
 | 
						|
  displayText result
 | 
						|
end
 | 
						|
 |