cache taps

There are plenty of IO operations inside Tap object, and it will be more
when implementing formula alias reverse look up(e.g. list all of alias
names for a formula). So let's cache them.

Some benchmark:

$ time brew info $(brew ruby -e 'puts Formula.tap_names') > /dev/null
Before: 6.40s user 2.42s system 96% cpu 9.134 total
After: 4.75s user 0.77s system 97% cpu 5.637 total

Closes Homebrew/homebrew#44377.

Signed-off-by: Xu Cheng <xucheng@me.com>
This commit is contained in:
Xu Cheng 2015-09-27 16:52:14 +08:00
parent 6240e896b2
commit 3b520cf195
12 changed files with 40 additions and 22 deletions

View File

@ -69,7 +69,7 @@ module Homebrew
def github_info(f) def github_info(f)
if f.tap? if f.tap?
user, repo = f.tap.split("/", 2) user, repo = f.tap.split("/", 2)
tap = Tap.new user, repo.gsub(/^homebrew-/, "") tap = Tap.fetch user, repo.gsub(/^homebrew-/, "")
if remote = tap.remote if remote = tap.remote
path = f.path.relative_path_from(tap.path) path = f.path.relative_path_from(tap.path)
github_remote_path(remote, path) github_remote_path(remote, path)

View File

@ -46,7 +46,7 @@ module Homebrew
if ARGV.named.empty? if ARGV.named.empty?
formulae = Formula.files formulae = Formula.files
else else
tap = Tap.new(*tap_args) tap = Tap.fetch(*tap_args)
raise TapUnavailableError, tap.name unless tap.installed? raise TapUnavailableError, tap.name unless tap.installed?
formulae = tap.formula_files formulae = tap.formula_files
end end

View File

@ -6,7 +6,7 @@ module Homebrew
taps = Tap taps = Tap
else else
taps = ARGV.named.map do |name| taps = ARGV.named.map do |name|
Tap.new(*tap_args(name)) Tap.fetch(*tap_args(name))
end end
end end

View File

@ -3,7 +3,7 @@ require "cmd/tap"
module Homebrew module Homebrew
def tap_pin def tap_pin
ARGV.named.each do |name| ARGV.named.each do |name|
tap = Tap.new(*tap_args(name)) tap = Tap.fetch(*tap_args(name))
tap.pin tap.pin
ohai "Pinned #{tap.name}" ohai "Pinned #{tap.name}"
end end

View File

@ -3,7 +3,7 @@ require "cmd/tap"
module Homebrew module Homebrew
def tap_unpin def tap_unpin
ARGV.named.each do |name| ARGV.named.each do |name|
tap = Tap.new(*tap_args(name)) tap = Tap.fetch(*tap_args(name))
tap.unpin tap.unpin
ohai "Unpinned #{tap.name}" ohai "Unpinned #{tap.name}"
end end

View File

@ -23,7 +23,7 @@ module Homebrew
# ensure git is installed # ensure git is installed
Utils.ensure_git_installed! Utils.ensure_git_installed!
tap = Tap.new user, repo tap = Tap.fetch user, repo
return false if tap.installed? return false if tap.installed?
ohai "Tapping #{tap}" ohai "Tapping #{tap}"
remote = clone_target || "https://github.com/#{tap.user}/homebrew-#{tap.repo}" remote = clone_target || "https://github.com/#{tap.user}/homebrew-#{tap.repo}"

View File

@ -39,20 +39,20 @@ module Homebrew
def resolve_test_tap def resolve_test_tap
tap = ARGV.value("tap") tap = ARGV.value("tap")
return Tap.new(*tap_args(tap)) if tap return Tap.fetch(*tap_args(tap)) if tap
if ENV["UPSTREAM_BOT_PARAMS"] if ENV["UPSTREAM_BOT_PARAMS"]
bot_argv = ENV["UPSTREAM_BOT_PARAMS"].split " " bot_argv = ENV["UPSTREAM_BOT_PARAMS"].split " "
bot_argv.extend HomebrewArgvExtension bot_argv.extend HomebrewArgvExtension
tap = bot_argv.value("tap") tap = bot_argv.value("tap")
return Tap.new(*tap_args(tap)) if tap return Tap.fetch(*tap_args(tap)) if tap
end end
if git_url = ENV["UPSTREAM_GIT_URL"] || ENV["GIT_URL"] if git_url = ENV["UPSTREAM_GIT_URL"] || ENV["GIT_URL"]
# Also can get tap from Jenkins GIT_URL. # Also can get tap from Jenkins GIT_URL.
url_path = git_url.sub(%r{^https?://github\.com/}, "").chomp("/") url_path = git_url.sub(%r{^https?://github\.com/}, "").chomp("/")
HOMEBREW_TAP_ARGS_REGEX =~ url_path HOMEBREW_TAP_ARGS_REGEX =~ url_path
return Tap.new($1, $3) if $1 && $3 && $3 != "homebrew" return Tap.fetch($1, $3) if $1 && $3 && $3 != "homebrew"
end end
# return nil means we are testing core repo. # return nil means we are testing core repo.

View File

@ -6,7 +6,7 @@ module Homebrew
raise "Usage is `brew untap <tap-name>`" if ARGV.empty? raise "Usage is `brew untap <tap-name>`" if ARGV.empty?
ARGV.named.each do |tapname| ARGV.named.each do |tapname|
tap = Tap.new(*tap_args(tapname)) tap = Tap.fetch(*tap_args(tapname))
raise TapUnavailableError, tap.name unless tap.installed? raise TapUnavailableError, tap.name unless tap.installed?
puts "Untapping #{tap}... (#{tap.path.abv})" puts "Untapping #{tap}... (#{tap.path.abv})"

View File

@ -63,6 +63,8 @@ module Homebrew
end end
end end
Tap.clear_cache
# automatically tap any migrated formulae's new tap # automatically tap any migrated formulae's new tap
report.select_formula(:D).each do |f| report.select_formula(:D).each do |f|
next unless (dir = HOMEBREW_CELLAR/f).exist? next unless (dir = HOMEBREW_CELLAR/f).exist?
@ -372,7 +374,7 @@ class Report
user = $1 user = $1
repo = $2.sub("homebrew-", "") repo = $2.sub("homebrew-", "")
oldname = path.basename(".rb").to_s oldname = path.basename(".rb").to_s
next unless newname = Tap.new(user, repo).formula_renames[oldname] next unless newname = Tap.fetch(user, repo).formula_renames[oldname]
else else
oldname = path.basename(".rb").to_s oldname = path.basename(".rb").to_s
next unless newname = FORMULA_RENAMES[oldname] next unless newname = FORMULA_RENAMES[oldname]

View File

@ -273,7 +273,7 @@ class Formula
end end
elsif tap? elsif tap?
user, repo = tap.split("/") user, repo = tap.split("/")
formula_renames = Tap.new(user, repo.sub("homebrew-", "")).formula_renames formula_renames = Tap.fetch(user, repo.sub("homebrew-", "")).formula_renames
if formula_renames.value?(name) if formula_renames.value?(name)
formula_renames.to_a.rassoc(name).first formula_renames.to_a.rassoc(name).first
end end

View File

@ -145,7 +145,7 @@ class Formulary
def initialize(tapped_name) def initialize(tapped_name)
user, repo, name = tapped_name.split("/", 3).map(&:downcase) user, repo, name = tapped_name.split("/", 3).map(&:downcase)
@tap = Tap.new user, repo.sub(/^homebrew-/, "") @tap = Tap.fetch user, repo.sub(/^homebrew-/, "")
name = @tap.formula_renames.fetch(name, name) name = @tap.formula_renames.fetch(name, name)
path = @tap.formula_files.detect { |file| file.basename(".rb").to_s == name } path = @tap.formula_files.detect { |file| file.basename(".rb").to_s == name }

View File

@ -9,6 +9,17 @@ require "utils/json"
class Tap class Tap
TAP_DIRECTORY = HOMEBREW_LIBRARY/"Taps" TAP_DIRECTORY = HOMEBREW_LIBRARY/"Taps"
CACHE = {}
def self.clear_cache
CACHE.clear
end
def self.fetch(user, repo)
cache_key = "#{user}/#{repo}".downcase
CACHE.fetch(cache_key) { |key| CACHE[key] = Tap.new(user, repo) }
end
extend Enumerable extend Enumerable
# The user name of this {Tap}. Usually, it's the Github username of # The user name of this {Tap}. Usually, it's the Github username of
@ -86,31 +97,33 @@ class Tap
# an array of all {Formula} files of this {Tap}. # an array of all {Formula} files of this {Tap}.
def formula_files def formula_files
dir = [@path/"Formula", @path/"HomebrewFormula", @path].detect(&:directory?) @formula_files ||= if dir = [@path/"Formula", @path/"HomebrewFormula", @path].detect(&:directory?)
return [] unless dir dir.children.select { |p| p.extname == ".rb" }
dir.children.select { |p| p.extname == ".rb" } else
[]
end
end end
# an array of all {Formula} names of this {Tap}. # an array of all {Formula} names of this {Tap}.
def formula_names def formula_names
formula_files.map { |f| "#{name}/#{f.basename(".rb")}" } @formula_names ||= formula_files.map { |f| "#{name}/#{f.basename(".rb")}" }
end end
# an array of all alias files of this {Tap}. # an array of all alias files of this {Tap}.
# @private # @private
def alias_files def alias_files
Pathname.glob("#{path}/Aliases/*").select(&:file?) @alias_files ||= Pathname.glob("#{path}/Aliases/*").select(&:file?)
end end
# an array of all aliases of this {Tap}. # an array of all aliases of this {Tap}.
# @private # @private
def aliases def aliases
alias_files.map { |f| "#{name}/#{f.basename}" } @aliases ||= alias_files.map { |f| "#{name}/#{f.basename}" }
end end
# an array of all commands files of this {Tap}. # an array of all commands files of this {Tap}.
def command_files def command_files
Pathname.glob("#{path}/cmd/brew-*").select(&:executable?) @command_files ||= Pathname.glob("#{path}/cmd/brew-*").select(&:executable?)
end end
def pinned_symlink_path def pinned_symlink_path
@ -118,13 +131,15 @@ class Tap
end end
def pinned? def pinned?
@pinned ||= pinned_symlink_path.directory? return @pinned if instance_variable_defined?(:@pinned)
@pinned = pinned_symlink_path.directory?
end end
def pin def pin
raise TapUnavailableError, name unless installed? raise TapUnavailableError, name unless installed?
raise TapPinStatusError.new(name, true) if pinned? raise TapPinStatusError.new(name, true) if pinned?
pinned_symlink_path.make_relative_symlink(@path) pinned_symlink_path.make_relative_symlink(@path)
@pinned = true
end end
def unpin def unpin
@ -132,6 +147,7 @@ class Tap
raise TapPinStatusError.new(name, false) unless pinned? raise TapPinStatusError.new(name, false) unless pinned?
pinned_symlink_path.delete pinned_symlink_path.delete
pinned_symlink_path.dirname.rmdir_if_possible pinned_symlink_path.dirname.rmdir_if_possible
@pinned = false
end end
def to_hash def to_hash
@ -170,7 +186,7 @@ class Tap
TAP_DIRECTORY.subdirs.each do |user| TAP_DIRECTORY.subdirs.each do |user|
user.subdirs.each do |repo| user.subdirs.each do |repo|
yield new(user.basename.to_s, repo.basename.to_s.sub("homebrew-", "")) yield fetch(user.basename.to_s, repo.basename.to_s.sub("homebrew-", ""))
end end
end end
end end