Factor out downloading from Formula

This patch adds a ArchiveDownloadStrategy that handles downloading
tarbarlls and decompressing them into the staging area ready for brewing.

Refactored safe_system and curl into utils.rb

Signed-off-by: Max Howell <max@methylblue.com>

Modifications to Adam's original patch:

I reverted objectification of checksum verification because I couldn't think
of any other download validation methods that might be useful to us in the
future, so allowing such flexibility had no advantages. If we ever need this
to be OO we can add it. But for now less complexity is preferable.

I removed the @svnurl class member. Instead download_strategy is autodetected
by examining the url. The user can override the download_strategy in case this
fails. Thus we already can easily add support for clones of git repositories.
This commit is contained in:
Adam Vandenberg 2009-08-11 12:20:55 -07:00 committed by Max Howell
parent 65e1419ea9
commit 0eaf4bbcd9
4 changed files with 119 additions and 74 deletions

View File

@ -16,6 +16,63 @@
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>. # along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
class AbstractDownloadStrategy
def initialize url, name, version
@url=url
@unique_token="#{name}-#{version}"
end
end
class HttpDownloadStrategy <AbstractDownloadStrategy
def fetch
ohai "Downloading #{@url}"
@dl=HOMEBREW_CACHE+(@unique_token+ext)
unless @dl.exist?
curl @url, '-o', @dl
else
puts "File already downloaded and cached"
end
return @dl # thus performs checksum verification
end
def stage
case `file -b #{@dl}`
when /^Zip archive data/
safe_system 'unzip', '-qq', @dl
chdir
when /^(gzip|bzip2) compressed data/
# TODO do file -z now to see if it is in fact a tar
safe_system 'tar', 'xf', @dl
chdir
else
# we are assuming it is not an archive, use original filename
# this behaviour is due to ScriptFileFormula expectations
@dl.mv File.basename(@url)
end
end
private
def chdir
entries=Dir['*']
case entries.length
when 0 then raise "Empty archive"
when 1 then Dir.chdir entries.first rescue nil
end
end
def ext
# GitHub uses odd URLs for zip files, so check for those
rx=%r[http://(www\.)?github\.com/.*/(zip|tar)ball/]
if rx.match @url
if $2 == 'zip'
'.zip'
else
'.tgz'
end
else
Pathname.new(@url).extname
end
end
end
class ExecutionError <RuntimeError class ExecutionError <RuntimeError
def initialize cmd, args=[] def initialize cmd, args=[]
super "#{cmd} #{args*' '}" super "#{cmd} #{args*' '}"
@ -40,7 +97,9 @@ class AbstractFormula
@homepage=self.class.homepage unless @homepage @homepage=self.class.homepage unless @homepage
@md5=self.class.md5 unless @md5 @md5=self.class.md5 unless @md5
@sha1=self.class.sha1 unless @sha1 @sha1=self.class.sha1 unless @sha1
raise "@url is nil" if @url.nil? raise if @url.nil?
raise if @name =~ /\s/
raise if @version =~ /\s/
end end
# if the dir is there, but it's empty we consider it not installed # if the dir is there, but it's empty we consider it not installed
@ -60,7 +119,7 @@ class AbstractFormula
Formula.path name Formula.path name
end end
attr_reader :url, :version, :url, :homepage, :name attr_reader :url, :version, :homepage, :name
def bin; prefix+'bin' end def bin; prefix+'bin' end
def sbin; prefix+'sbin' end def sbin; prefix+'sbin' end
@ -71,6 +130,15 @@ class AbstractFormula
def info; prefix+'share'+'info' end def info; prefix+'share'+'info' end
def include; prefix+'include' end def include; prefix+'include' end
# reimplement if we don't autodetect the download strategy you require
def download_strategy
case url
when %r[^svn://] then SubversionDownloadStrategy
when %r[^git://] then GitDownloadStrategy
when %r[^http://(.+?\.)?googlecode\.com/svn] then SubversionDownloadStrategy
else HttpDownloadStrategy
end
end
# tell the user about any caveats regarding this package # tell the user about any caveats regarding this package
def caveats; nil end def caveats; nil end
# patches are automatically applied after extracting the tarball # patches are automatically applied after extracting the tarball
@ -89,19 +157,7 @@ class AbstractFormula
# yields self with current working directory set to the uncompressed tarball # yields self with current working directory set to the uncompressed tarball
def brew def brew
ohai "Downloading #{@url}" stage do
tgz=HOMEBREW_CACHE+File.basename(@url)
unless tgz.exist?
HOMEBREW_CACHE.mkpath
curl @url, '-o', tgz
else
puts "File already downloaded and cached"
end
verify_download_integrity tgz
mktemp do
Dir.chdir uncompress(tgz)
begin begin
patch patch
yield self yield self
@ -144,8 +200,10 @@ protected
end end
private private
# creates a temporary directory then yields, when the block returns it
# recursively deletes the temporary directory
def mktemp def mktemp
tmp=Pathname.new `mktemp -dt #{File.basename @url}`.strip tmp=Pathname.new `mktemp -dt #{name}-#{version}`.strip
raise if not tmp.directory? or $? != 0 raise if not tmp.directory? or $? != 0
begin begin
wd=Dir.pwd wd=Dir.pwd
@ -157,20 +215,6 @@ private
end end
end end
# Kernel.system but with exceptions
def safe_system cmd, *args
puts "#{cmd} #{args*' '}" if ARGV.verbose?
execd=Kernel.system cmd, *args
# somehow Ruby doesn't handle the CTRL-C from another process -- WTF!?
raise Interrupt, cmd if $?.termsig == 2
raise ExecutionError.new(cmd, args) unless execd and $? == 0
end
def curl url, *args
safe_system 'curl', '-f#LA', HOMEBREW_USER_AGENT, url, *args
end
def verify_download_integrity fn def verify_download_integrity fn
require 'digest' require 'digest'
type='MD5' type='MD5'
@ -183,7 +227,18 @@ private
else else
opoo "Cannot verify package integrity" opoo "Cannot verify package integrity"
puts "The formula did not provide a download checksum" puts "The formula did not provide a download checksum"
puts "For your reference the #{type} is: #{hash}" puts "For your reference the #{type} is: #{hash}"
end
end
def stage
ds=download_strategy.new url, name, version
HOMEBREW_CACHE.mkpath
dl=ds.fetch
verify_download_integrity dl if dl.kind_of? Pathname
mktemp do
ds.stage
yield
end end
end end
@ -216,7 +271,7 @@ private
end end
class <<self class <<self
attr_reader :url, :version, :md5, :url, :homepage, :sha1 attr_reader :url, :version, :homepage, :md5, :sha1
end end
end end
@ -254,30 +309,6 @@ class Formula <AbstractFormula
end end
private private
def uncompress_args
rx=%r[http://(www.)?github.com/.*/(zip|tar)ball/]
if rx.match @url and $2 == '.zip' or Pathname.new(@url).extname == '.zip'
%w[unzip -qq]
else
%w[tar xf]
end
end
def uncompress path
safe_system *uncompress_args<<path
entries=Dir['*']
if entries.length == 0
raise "Empty archive"
elsif entries.length == 1
# if one dir enter it as that will be where the build is
entries.first
else
# if there's more than one dir, then this is the build directory already
Dir.pwd
end
end
def method_added method def method_added method
raise 'You cannot override Formula.brew' if method == 'brew' raise 'You cannot override Formula.brew' if method == 'brew'
end end
@ -289,11 +320,8 @@ class ScriptFileFormula <AbstractFormula
super super
@name=name @name=name
end end
def uncompress path
path.dirname
end
def install def install
bin.install File.basename(@url) bin.install Dir['*']
end end
end end

View File

@ -22,11 +22,10 @@ class Pathname
def mv dst def mv dst
FileUtils.mv to_s, dst FileUtils.mv to_s, dst
end end
def rename newname def rename newname
raise unless file?
dst=dirname+newname dst=dirname+newname
dst.unlink if dst.exist? dst.unlink if dst.exist? and file?
mv dst mv dst
end end

View File

@ -43,9 +43,9 @@ end
class TestZip <Formula class TestZip <Formula
def initialize def initialize
path=HOMEBREW_CACHE.parent+'test-0.1.zip' zip=HOMEBREW_CACHE.parent+'test-0.1.zip'
Kernel.system 'zip', '-0', path, __FILE__ Kernel.system 'zip', '-0', zip, __FILE__
@url="file://#{path}" @url="file://#{zip}"
super 'testzip' super 'testzip'
end end
end end
@ -63,7 +63,6 @@ class TestBallOverrideBrew <Formula
super "foo" super "foo"
end end
def brew def brew
# We can't override brew
end end
end end
@ -78,13 +77,18 @@ class TestScriptFileFormula <ScriptFileFormula
end end
def nostdout def nostdout
require 'stringio' if ARGV.include? '-V'
tmpo=$stdout yield
tmpe=$stderr end
$stdout=StringIO.new begin
yield require 'stringio'
ensure tmpo=$stdout
$stdout=tmpo tmpe=$stderr
$stdout=StringIO.new
yield
ensure
$stdout=tmpo
end
end end

View File

@ -49,3 +49,17 @@ def interactive_shell
Process.wait pid Process.wait pid
end end
end end
# Kernel.system but with exceptions
def safe_system cmd, *args
puts "#{cmd} #{args*' '}" if ARGV.verbose?
execd=Kernel.system cmd, *args
# somehow Ruby doesn't handle the CTRL-C from another process -- WTF!?
raise Interrupt, cmd if $?.termsig == 2
raise ExecutionError.new(cmd, args) unless execd and $? == 0
end
def curl url, *args
safe_system 'curl', '-f#LA', HOMEBREW_USER_AGENT, url, *args
end