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/>.
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
def initialize cmd, args=[]
super "#{cmd} #{args*' '}"
@ -40,7 +97,9 @@ class AbstractFormula
@homepage=self.class.homepage unless @homepage
@md5=self.class.md5 unless @md5
@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
# if the dir is there, but it's empty we consider it not installed
@ -60,7 +119,7 @@ class AbstractFormula
Formula.path name
end
attr_reader :url, :version, :url, :homepage, :name
attr_reader :url, :version, :homepage, :name
def bin; prefix+'bin' end
def sbin; prefix+'sbin' end
@ -71,6 +130,15 @@ class AbstractFormula
def info; prefix+'share'+'info' 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
def caveats; nil end
# 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
def brew
ohai "Downloading #{@url}"
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)
stage do
begin
patch
yield self
@ -144,8 +200,10 @@ protected
end
private
# creates a temporary directory then yields, when the block returns it
# recursively deletes the temporary directory
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
begin
wd=Dir.pwd
@ -157,20 +215,6 @@ private
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
require 'digest'
type='MD5'
@ -183,7 +227,18 @@ private
else
opoo "Cannot verify package integrity"
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
@ -216,7 +271,7 @@ private
end
class <<self
attr_reader :url, :version, :md5, :url, :homepage, :sha1
attr_reader :url, :version, :homepage, :md5, :sha1
end
end
@ -254,30 +309,6 @@ class Formula <AbstractFormula
end
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
raise 'You cannot override Formula.brew' if method == 'brew'
end
@ -289,11 +320,8 @@ class ScriptFileFormula <AbstractFormula
super
@name=name
end
def uncompress path
path.dirname
end
def install
bin.install File.basename(@url)
bin.install Dir['*']
end
end

View File

@ -24,9 +24,8 @@ class Pathname
end
def rename newname
raise unless file?
dst=dirname+newname
dst.unlink if dst.exist?
dst.unlink if dst.exist? and file?
mv dst
end

View File

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

View File

@ -49,3 +49,17 @@ def interactive_shell
Process.wait pid
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