class AbstractDownloadStrategy
def initialize url, name, version, specs
@url=url
case specs when Hash
@spec = specs.keys.first # only use first spec
@ref = specs.values.first
end
end
def expand_safe_system_args args
args.each_with_index do |arg, ii|
if arg.is_a? Hash
unless ARGV.verbose?
args[ii] = arg[:quiet_flag]
else
args.delete_at ii
end
return args
end
end
# 2 as default because commands are eg. svn up, git pull
args.insert(2, '-q') unless ARGV.verbose?
return args
end
def quiet_safe_system *args
safe_system(*expand_safe_system_args(args))
end
end
class CurlDownloadStrategy < AbstractDownloadStrategy
attr_reader :tarball_path
def initialize url, name, version, specs
super
@unique_token="#{name}-#{version}" unless name.to_s.empty? or name == '__UNKNOWN__'
if @unique_token
@tarball_path=HOMEBREW_CACHE+(@unique_token+ext)
else
@tarball_path=HOMEBREW_CACHE+File.basename(@url)
end
end
def cached_location
@tarball_path
end
# Private method, can be overridden if needed.
def _fetch
curl @url, '-o', @tarball_path
end
def fetch
ohai "Downloading #{@url}"
unless @tarball_path.exist?
begin
_fetch
rescue Exception
ignore_interrupts { @tarball_path.unlink if @tarball_path.exist? }
raise
end
else
puts "File already downloaded in #{File.dirname(@tarball_path)}"
end
return @tarball_path # thus performs checksum verification
end
def stage
if @tarball_path.extname == '.jar'
magic_bytes = nil
elsif @tarball_path.extname == '.pkg'
# Use more than 4 characters to not clash with magicbytes
magic_bytes = "____pkg"
else
# get the first four bytes
File.open(@tarball_path) { |f| magic_bytes = f.read(4) }
end
# magic numbers stolen from /usr/share/file/magic/
case magic_bytes
when /^PK\003\004/ # .zip archive
quiet_safe_system '/usr/bin/unzip', {:quiet_flag => '-qq'}, @tarball_path
chdir
when /^\037\213/, /^BZh/, /^\037\235/ # gzip/bz2/compress compressed
# TODO check if it's really a tar archive
safe_system '/usr/bin/tar', 'xf', @tarball_path
chdir
when '____pkg'
safe_system '/usr/sbin/pkgutil', '--expand', @tarball_path, File.basename(@url)
chdir
when 'Rar!'
quiet_safe_system 'unrar', 'x', {:quiet_flag => '-inul'}, @tarball_path
else
# we are assuming it is not an archive, use original filename
# this behaviour is due to ScriptFileFormula expectations
# So I guess we should cp, but we mv, for this historic reason
# HOWEVER if this breaks some expectation you had we *will* change the
# behaviour, just open an issue at github
# We also do this for jar files, as they are in fact zip files, but
# we don't want to unzip them
FileUtils.mv @tarball_path, 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[https?://(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
# Detect and download from Apache Mirror
class CurlApacheMirrorDownloadStrategy < CurlDownloadStrategy
def _fetch
# Fetch mirror list site
require 'open-uri'
mirror_list = open(@url).read()
# Parse out suggested mirror
# Yep, this is ghetto, grep the first element content
mirror_url = mirror_list[/([^<]+)/, 1]
raise "Couldn't determine mirror. Try again later." if mirror_url.nil?
ohai "Best Mirror #{mirror_url}"
# Start download from that mirror
curl mirror_url, '-o', @tarball_path
end
end
# Download via an HTTP POST.
# Query parameters on the URL are converted into POST parameters
class CurlPostDownloadStrategy < CurlDownloadStrategy
def _fetch
base_url,data = @url.split('?')
curl base_url, '-d', data, '-o', @tarball_path
end
end
# Use this strategy to download but not unzip a file.
# Useful for installing jars.
class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy
def stage
FileUtils.cp @tarball_path, File.basename(@url)
end
end
# Normal strategy tries to untar as well
class GzipOnlyDownloadStrategy < CurlDownloadStrategy
def stage
FileUtils.mv @tarball_path, File.basename(@url)
safe_system '/usr/bin/gunzip', '-f', File.basename(@url)
end
end
# This Download Strategy is provided for use with sites that
# only provide HTTPS and also have a broken cert.
# Try not to need this, as we probably won't accept the formula.
class CurlUnsafeDownloadStrategy < CurlDownloadStrategy
def _fetch
curl @url, '--insecure', '-o', @tarball_path
end
end
# This strategy extracts our binary packages.
class CurlBottleDownloadStrategy true
end
end
end
private
def split_url(in_url)
parts=in_url.sub(%r[^cvs://], '').split(/:/)
mod=parts.pop
url=parts.join(':')
[ mod, url ]
end
end
class MercurialDownloadStrategy < AbstractDownloadStrategy
def initialize url, name, version, specs
super
@unique_token="#{name}--hg" unless name.to_s.empty? or name == '__UNKNOWN__'
@clone=HOMEBREW_CACHE+@unique_token
end
def cached_location; @clone; end
def fetch
raise "You must `easy_install mercurial'" unless system "/usr/bin/which hg"
ohai "Cloning #{@url}"
unless @clone.exist?
url=@url.sub(%r[^hg://], '')
safe_system 'hg', 'clone', url, @clone
else
puts "Updating #{@clone}"
Dir.chdir(@clone) do
safe_system 'hg', 'pull'
safe_system 'hg', 'update'
end
end
end
def stage
dst=Dir.getwd
Dir.chdir @clone do
if @spec and @ref
ohai "Checking out #{@spec} #{@ref}"
Dir.chdir @clone do
safe_system 'hg', 'archive', '-y', '-r', @ref, '-t', 'files', dst
end
else
safe_system 'hg', 'archive', '-y', '-t', 'files', dst
end
end
end
end
class BazaarDownloadStrategy < AbstractDownloadStrategy
def initialize url, name, version, specs
super
@unique_token="#{name}--bzr" unless name.to_s.empty? or name == '__UNKNOWN__'
@clone=HOMEBREW_CACHE+@unique_token
end
def cached_location; @clone; end
def fetch
raise "You must install bazaar first" \
unless system "/usr/bin/which bzr"
ohai "Cloning #{@url}"
unless @clone.exist?
url=@url.sub(%r[^bzr://], '')
# 'lightweight' means history-less
safe_system 'bzr', 'checkout', '--lightweight', url, @clone
else
puts "Updating #{@clone}"
Dir.chdir(@clone) { safe_system 'bzr', 'update' }
end
end
def stage
dst=Dir.getwd
Dir.chdir @clone do
if @spec and @ref
ohai "Checking out #{@spec} #{@ref}"
Dir.chdir @clone do
safe_system 'bzr', 'export', '-r', @ref, dst
end
else
safe_system 'bzr', 'export', dst
end
end
end
end
class FossilDownloadStrategy < AbstractDownloadStrategy
def initialize url, name, version, specs
super
@unique_token="#{name}--fossil" unless name.to_s.empty? or name == '__UNKNOWN__'
@clone=HOMEBREW_CACHE+@unique_token
end
def cached_location; @clone; end
def fetch
raise "You must install fossil first" \
unless system "/usr/bin/which fossil"
ohai "Cloning #{@url}"
unless @clone.exist?
url=@url.sub(%r[^fossil://], '')
safe_system 'fossil', 'clone', url, @clone
else
puts "Updating #{@clone}"
safe_system 'fossil', 'pull', '-R', @clone
end
end
def stage
# TODO: The 'open' and 'checkout' commands are very noisy and have no '-q' option.
safe_system 'fossil', 'open', @clone
if @spec and @ref
ohai "Checking out #{@spec} #{@ref}"
safe_system 'fossil', 'checkout', @ref
end
end
end
def detect_download_strategy url
case url
# We use a special URL pattern for cvs
when %r[^cvs://] then CVSDownloadStrategy
# Standard URLs
when %r[^bzr://] then BazaarDownloadStrategy
when %r[^git://] then GitDownloadStrategy
when %r[^hg://] then MercurialDownloadStrategy
when %r[^svn://] then SubversionDownloadStrategy
when %r[^svn\+http://] then SubversionDownloadStrategy
when %r[^fossil://] then FossilDownloadStrategy
# Some well-known source hosts
when %r[^https?://github\.com/.+\.git$] then GitDownloadStrategy
when %r[^https?://(.+?\.)?googlecode\.com/hg] then MercurialDownloadStrategy
when %r[^https?://(.+?\.)?googlecode\.com/svn] then SubversionDownloadStrategy
when %r[^https?://(.+?\.)?sourceforge\.net/svnroot/] then SubversionDownloadStrategy
when %r[^http://svn.apache.org/repos/] then SubversionDownloadStrategy
when %r[^http://www.apache.org/dyn/closer.cgi] then CurlApacheMirrorDownloadStrategy
# Otherwise just try to download
else CurlDownloadStrategy
end
end