mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Refactor
Large refactor to Formula, mostly improving reliability and error handling but also layout and readability. General improvements so testing can be more complete. Patches are automatically downloaded and applied for Formula that return a list of urls from Formula::patches. Split out the brew command logic to facilitate testing. Facility from Adam Vandenberg to allow selective cleaning of files, added because Python doesn't work when stripped.
This commit is contained in:
parent
5a396fd8b4
commit
760c083c0c
@ -35,7 +35,7 @@ _brew_to_completion()
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
# Commands that take an existing brew...
|
# Commands that take an existing brew...
|
||||||
abv|info|list|link|ls|ln|rm|uninstall)
|
abv|info|list|link|ls|ln|rm|remove|uninstall)
|
||||||
cellar_contents=`ls ${brew_base}/Cellar/`
|
cellar_contents=`ls ${brew_base}/Cellar/`
|
||||||
COMPREPLY=( $(compgen -W "${cellar_contents}" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "${cellar_contents}" -- ${cur}) )
|
||||||
return 0
|
return 0
|
||||||
|
238
Library/Homebrew/brew.h.rb
Normal file
238
Library/Homebrew/brew.h.rb
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
# Copyright 2009 Max Howell <max@methylblue.com>
|
||||||
|
#
|
||||||
|
# This file is part of Homebrew.
|
||||||
|
#
|
||||||
|
# Homebrew is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Homebrew is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
def make url
|
||||||
|
require 'formula'
|
||||||
|
|
||||||
|
path=Pathname.new url
|
||||||
|
|
||||||
|
/(.*?)[-_.]?#{path.version}/.match path.basename
|
||||||
|
raise "Couldn't parse name from #{url}" if $1.nil? or $1.empty?
|
||||||
|
|
||||||
|
path=Formula.path $1
|
||||||
|
raise "#{path} already exists" if path.exist?
|
||||||
|
|
||||||
|
template=<<-EOS
|
||||||
|
require 'brewkit'
|
||||||
|
|
||||||
|
class #{Formula.class $1} <Formula
|
||||||
|
@url='#{url}'
|
||||||
|
@homepage=''
|
||||||
|
@md5=''
|
||||||
|
|
||||||
|
cmake def deps
|
||||||
|
cmake BinaryDep.new 'cmake'
|
||||||
|
cmake end
|
||||||
|
cmake
|
||||||
|
def install
|
||||||
|
autotools system "./configure --prefix='\#{prefix}' --disable-debug --disable-dependency-tracking"
|
||||||
|
cmake system "cmake . \#{cmake_std_parameters}"
|
||||||
|
system "make install"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
EOS
|
||||||
|
|
||||||
|
mode=nil
|
||||||
|
if ARGV.include? '--cmake'
|
||||||
|
mode= :cmake
|
||||||
|
elsif ARGV.include? '--autotools'
|
||||||
|
mode= :autotools
|
||||||
|
end
|
||||||
|
|
||||||
|
f=File.new path, 'w'
|
||||||
|
template.each_line do |s|
|
||||||
|
if s.strip.empty?
|
||||||
|
f.puts
|
||||||
|
next
|
||||||
|
end
|
||||||
|
cmd=s[0..11].strip
|
||||||
|
if cmd.empty?
|
||||||
|
cmd=nil
|
||||||
|
else
|
||||||
|
cmd=cmd.to_sym
|
||||||
|
end
|
||||||
|
out=s[12..-1] || ''
|
||||||
|
|
||||||
|
if mode.nil?
|
||||||
|
# we show both but comment out cmake as it is less common
|
||||||
|
# the implication being the pacakger should remove whichever is not needed
|
||||||
|
if cmd == :cmake and not out.empty?
|
||||||
|
f.print '#'
|
||||||
|
out = out[1..-1]
|
||||||
|
end
|
||||||
|
elsif cmd != mode and not cmd.nil?
|
||||||
|
next
|
||||||
|
end
|
||||||
|
f.puts out
|
||||||
|
end
|
||||||
|
f.close
|
||||||
|
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def info name
|
||||||
|
require 'formula'
|
||||||
|
|
||||||
|
history="http://github.com/mxcl/homebrew/commits/masterbrew/Library/Formula/#{Formula.path(name).basename}"
|
||||||
|
exec 'open', history if ARGV.flag? '--github'
|
||||||
|
|
||||||
|
f=Formula.factory name
|
||||||
|
puts "#{f.name} #{f.version}"
|
||||||
|
puts f.homepage
|
||||||
|
|
||||||
|
if f.prefix.parent.directory?
|
||||||
|
kids=f.prefix.parent.children
|
||||||
|
kids.each do |keg|
|
||||||
|
print "#{keg} (#{keg.abv})"
|
||||||
|
print " *" if f.prefix == keg and kids.length > 1
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
else
|
||||||
|
puts "Not installed"
|
||||||
|
end
|
||||||
|
|
||||||
|
if f.caveats
|
||||||
|
puts
|
||||||
|
puts f.caveats
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
|
||||||
|
puts history
|
||||||
|
|
||||||
|
rescue FormulaUnavailableError
|
||||||
|
# check for DIY installation
|
||||||
|
d=HOMEBREW_PREFIX+name
|
||||||
|
if d.directory?
|
||||||
|
ohai "DIY Installation"
|
||||||
|
d.children.each {|keg| puts "#{keg} (#{keg.abv})"}
|
||||||
|
else
|
||||||
|
raise "No such formula or keg"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def clean f
|
||||||
|
Cleaner.new f
|
||||||
|
# remove empty directories TODO Rubyize!
|
||||||
|
`perl -MFile::Find -e"finddepth(sub{rmdir},'#{f.prefix}')"`
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def install f
|
||||||
|
f.brew do
|
||||||
|
if ARGV.flag? '--interactive'
|
||||||
|
ohai "Entering interactive mode"
|
||||||
|
puts "Type `exit' to return and finalize the installation"
|
||||||
|
puts "Install to this prefix: #{f.prefix}"
|
||||||
|
interactive_shell
|
||||||
|
elsif ARGV.include? '--help'
|
||||||
|
system './configure --help'
|
||||||
|
exit $?
|
||||||
|
else
|
||||||
|
f.prefix.mkpath
|
||||||
|
f.install
|
||||||
|
%w[README ChangeLog COPYING LICENSE COPYRIGHT AUTHORS].each do |file|
|
||||||
|
f.prefix.install file if File.file? file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def prune
|
||||||
|
$n=0
|
||||||
|
$d=0
|
||||||
|
|
||||||
|
dirs=Array.new
|
||||||
|
paths=%w[bin etc lib include share].collect {|d| HOMEBREW_PREFIX+d}
|
||||||
|
|
||||||
|
paths.each do |path|
|
||||||
|
path.find do |path|
|
||||||
|
path.extend ObserverPathnameExtension
|
||||||
|
if path.symlink?
|
||||||
|
path.unlink unless path.resolved_path_exists?
|
||||||
|
elsif path.directory?
|
||||||
|
dirs<<path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dirs.sort.reverse_each {|d| d.rmdir_if_possible}
|
||||||
|
|
||||||
|
if $n == 0 and $d == 0
|
||||||
|
puts "Nothing pruned" if ARGV.verbose?
|
||||||
|
else
|
||||||
|
# always showing symlinks text is deliberate
|
||||||
|
print "Pruned #{$n} symbolic links "
|
||||||
|
print "and #{$n} directories " if $d > 0
|
||||||
|
puts "from #{HOMEBREW_PREFIX}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
################################################################ class Cleaner
|
||||||
|
class Cleaner
|
||||||
|
def initialize f
|
||||||
|
@f=f
|
||||||
|
[f.bin, f.lib].each {|d| clean_dir d}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def strip path, args=''
|
||||||
|
return if @f.skip_clean? path
|
||||||
|
puts "strip #{path}" if ARGV.verbose?
|
||||||
|
path.chmod 0644 # so we can strip
|
||||||
|
unless path.stat.nlink > 1
|
||||||
|
`strip #{args} #{path}`
|
||||||
|
else
|
||||||
|
# strip unlinks the file and recreates it, thus breaking hard links!
|
||||||
|
# is this expected behaviour? patch does it too… still,mktm this fixes it
|
||||||
|
tmp=`mktemp -t #{path.basename}`.strip
|
||||||
|
`strip #{args} -o #{tmp} #{path}`
|
||||||
|
`cat #{tmp} > #{path}`
|
||||||
|
File.unlink tmp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean_file path
|
||||||
|
perms=0444
|
||||||
|
case `file -h #{path}`
|
||||||
|
when /Mach-O dynamically linked shared library/
|
||||||
|
strip path, '-SxX'
|
||||||
|
when /Mach-O [^ ]* ?executable/
|
||||||
|
strip path
|
||||||
|
perms=0544
|
||||||
|
when /script text executable/
|
||||||
|
perms=0544
|
||||||
|
end
|
||||||
|
path.chmod perms
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean_dir d
|
||||||
|
d.find do |path|
|
||||||
|
if not path.file?
|
||||||
|
next
|
||||||
|
elsif path.extname == '.la' and not @f.skip_clean? path
|
||||||
|
# *.la files are stupid
|
||||||
|
path.unlink
|
||||||
|
else
|
||||||
|
clean_file path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,25 +0,0 @@
|
|||||||
# Copyright 2009 Max Howell <max@methylblue.com>
|
|
||||||
#
|
|
||||||
# This file is part of Homebrew.
|
|
||||||
#
|
|
||||||
# Homebrew is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Homebrew is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
require 'pathname+yeast'
|
|
||||||
require 'utils'
|
|
||||||
|
|
||||||
# TODO if whoami == root then use /Library/Caches/Homebrew instead
|
|
||||||
HOMEBREW_VERSION='0.3'
|
|
||||||
HOMEBREW_CACHE=Pathname.new("~/Library/Caches/Homebrew").expand_path
|
|
||||||
HOMEBREW_PREFIX=Pathname.new(__FILE__).dirname.parent.parent.cleanpath
|
|
||||||
HOMEBREW_CELLAR=HOMEBREW_PREFIX+'Cellar'
|
|
@ -15,30 +15,31 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
|
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
require 'utils'
|
|
||||||
|
|
||||||
class BuildError <RuntimeError
|
class ExecutionError <RuntimeError
|
||||||
def initialize cmd
|
def initialize cmd, args=[]
|
||||||
super "Build failed during: #{cmd}"
|
super "#{cmd} #{args*' '}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# the base class variety of formula, you don't get a prefix, so it's not really
|
class BuildError <ExecutionError; end
|
||||||
|
|
||||||
|
class FormulaUnavailableError <RuntimeError
|
||||||
|
def initialize name
|
||||||
|
super "No available formula for #{name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# the base class variety of formula, you don't get a prefix, so it's not
|
||||||
# useful. See the derived classes for fun and games.
|
# useful. See the derived classes for fun and games.
|
||||||
class AbstractFormula
|
class AbstractFormula
|
||||||
require 'find'
|
def initialize noop=nil
|
||||||
require 'fileutils'
|
@version=self.class.version unless @version
|
||||||
|
@url=self.class.url unless @url
|
||||||
private
|
@homepage=self.class.homepage unless @homepage
|
||||||
class <<self
|
@md5=self.class.md5 unless @md5
|
||||||
attr_reader :url, :version, :md5, :url, :homepage, :sha1
|
@sha1=self.class.sha1 unless @sha1
|
||||||
end
|
raise "@url is nil" if @url.nil?
|
||||||
|
|
||||||
public
|
|
||||||
attr_reader :url, :version, :url, :homepage, :name
|
|
||||||
|
|
||||||
# reimplement if your package has dependencies
|
|
||||||
def deps
|
|
||||||
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
|
||||||
@ -48,50 +49,167 @@ public
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize name=nil
|
|
||||||
@name=name
|
|
||||||
@version=self.class.version unless @version
|
|
||||||
@url=self.class.url unless @url
|
|
||||||
@homepage=self.class.homepage unless @homepage
|
|
||||||
@md5=self.class.md5 unless @md5
|
|
||||||
@sha1=self.class.sha1 unless @sha1
|
|
||||||
raise "@url.nil?" if @url.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def prefix
|
def prefix
|
||||||
raise "@name.nil!" if @name.nil?
|
raise "Invalid @name" if @name.nil? or @name.empty?
|
||||||
raise "@version.nil?" if @version.nil?
|
raise "Invalid @version" if @version.nil? or @version.empty?
|
||||||
HOMEBREW_CELLAR+@name+@version
|
HOMEBREW_CELLAR+@name+@version
|
||||||
end
|
end
|
||||||
|
|
||||||
def bin; prefix+'bin' end
|
def path
|
||||||
def doc; prefix+'share'+'doc'+name end
|
Formula.path name
|
||||||
def lib; prefix+'lib' end
|
end
|
||||||
def man; prefix+'share'+'man' end
|
|
||||||
|
attr_reader :url, :version, :url, :homepage, :name
|
||||||
|
|
||||||
|
def bin; prefix+'bin' end
|
||||||
|
def doc; prefix+'share'+'doc'+name end
|
||||||
|
def lib; prefix+'lib' end
|
||||||
|
def man; prefix+'share'+'man' end
|
||||||
def man1; man+'man1' end
|
def man1; man+'man1' end
|
||||||
|
def info; prefix+'share'+'info' end
|
||||||
def include; prefix+'include' end
|
def include; prefix+'include' end
|
||||||
|
|
||||||
def caveats
|
# tell the user about any caveats regarding this package
|
||||||
nil
|
def caveats; nil end
|
||||||
|
# patches are automatically applied after extracting the tarball
|
||||||
|
def patches; [] end
|
||||||
|
# reimplement and specify dependencies
|
||||||
|
def deps; end
|
||||||
|
# sometimes the clean process breaks things, return true to skip anything
|
||||||
|
def skip_clean? path; false end
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
begin
|
||||||
|
patch
|
||||||
|
yield self
|
||||||
|
rescue Interrupt, RuntimeError, SystemCallError => e
|
||||||
|
raise unless ARGV.debug?
|
||||||
|
onoe e.inspect
|
||||||
|
puts e.backtrace
|
||||||
|
ohai "Rescuing build..."
|
||||||
|
puts "Type `exit' and Homebrew will attempt to finalize the installation"
|
||||||
|
puts "If nothing is installed to #{prefix}, then Homebrew will abort"
|
||||||
|
interactive_shell
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
# Pretty titles the command and buffers stdout/stderr
|
# Pretty titles the command and buffers stdout/stderr
|
||||||
# Throws if there's an error
|
# Throws if there's an error
|
||||||
def system cmd
|
def system cmd, *args
|
||||||
ohai cmd
|
full="#{cmd} #{args*' '}".strip
|
||||||
if ARGV.include? '--verbose'
|
ohai full
|
||||||
Kernel.system cmd
|
if ARGV.verbose?
|
||||||
|
safe_system cmd, *args
|
||||||
else
|
else
|
||||||
out=''
|
out=''
|
||||||
IO.popen "#{cmd} 2>&1" do |f|
|
# TODO write a ruby extension that does a good popen :P
|
||||||
|
IO.popen "#{full} 2>&1" do |f|
|
||||||
until f.eof?
|
until f.eof?
|
||||||
out+=f.gets
|
out+=f.gets
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
puts out unless $? == 0
|
unless $? == 0
|
||||||
|
puts out
|
||||||
|
raise
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
rescue
|
||||||
|
raise BuildError.new(cmd, args)
|
||||||
|
end
|
||||||
|
|
||||||
raise BuildError.new(cmd) unless $? == 0
|
private
|
||||||
|
def mktemp
|
||||||
|
tmp=Pathname.new `mktemp -dt #{File.basename @url}`.strip
|
||||||
|
raise if not tmp.directory? or $? != 0
|
||||||
|
begin
|
||||||
|
wd=Dir.pwd
|
||||||
|
Dir.chdir tmp
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
Dir.chdir wd
|
||||||
|
tmp.rmtree
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Kernel.system but with exceptions
|
||||||
|
def safe_system cmd, *args
|
||||||
|
puts "#{cmd} #{args*' '}" if ARGV.verbose?
|
||||||
|
# stderr is shown, so hopefully that will explain the problem
|
||||||
|
raise ExecutionError.new(cmd, args) unless Kernel.system cmd, *args 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'
|
||||||
|
type='SHA1' if @sha1
|
||||||
|
supplied=eval "@#{type.downcase}"
|
||||||
|
hash=eval("Digest::#{type}").hexdigest(fn.read)
|
||||||
|
|
||||||
|
if supplied and not supplied.empty?
|
||||||
|
raise "#{type} mismatch: #{hash}" unless supplied.upcase == hash.upcase
|
||||||
|
else
|
||||||
|
opoo "Cannot verify package integrity"
|
||||||
|
puts "The formula did not provide a download checksum"
|
||||||
|
puts "For your reference the #{type} is: #{hash}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def patch
|
||||||
|
unless patches.empty?
|
||||||
|
ohai "Patching"
|
||||||
|
ff=(1..patches.length).collect {|n| '%03d-homebrew.patch'%n}
|
||||||
|
curl *patches+ff.collect {|f|"-o#{f}"}
|
||||||
|
ff.each {|f| safe_system 'patch', '-p0', '-i', f}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class <<self
|
||||||
|
attr_reader :url, :version, :md5, :url, :homepage, :sha1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is the meat. See the examples.
|
||||||
|
class Formula <AbstractFormula
|
||||||
|
def initialize name=nil
|
||||||
|
super
|
||||||
|
@name=name
|
||||||
|
@version=Pathname.new(@url).version unless @version
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.class name
|
||||||
|
#remove invalid characters and camelcase
|
||||||
|
name.capitalize.gsub(/[-_\s]([a-zA-Z0-9])/) { $1.upcase }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.factory name
|
||||||
|
require self.path(name)
|
||||||
|
return eval(self.class(name)).new(name)
|
||||||
|
rescue LoadError
|
||||||
|
raise FormulaUnavailableError.new(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.path name
|
||||||
|
HOMEBREW_PREFIX+'Library'+'Formula'+"#{name.downcase}.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
# we don't have a std_autotools variant because autotools is a lot less
|
# we don't have a std_autotools variant because autotools is a lot less
|
||||||
@ -102,108 +220,23 @@ public
|
|||||||
# The None part makes cmake use the environment's CFLAGS etc. settings
|
# The None part makes cmake use the environment's CFLAGS etc. settings
|
||||||
"-DCMAKE_INSTALL_PREFIX='#{prefix}' -DCMAKE_BUILD_TYPE=None"
|
"-DCMAKE_INSTALL_PREFIX='#{prefix}' -DCMAKE_BUILD_TYPE=None"
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_download_integrity fn
|
|
||||||
require 'digest'
|
|
||||||
type='MD5'
|
|
||||||
type='SHA1' if @sha1
|
|
||||||
supplied=eval "@#{type.downcase}"
|
|
||||||
hash=eval("Digest::#{type}").hexdigest(fn.read)
|
|
||||||
|
|
||||||
if supplied
|
private
|
||||||
raise "#{type} mismatch: #{hash}" unless supplied.upcase == hash.upcase
|
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
|
else
|
||||||
opoo "Cannot verify package integrity"
|
%w[tar xf]
|
||||||
puts "The formula did not provide a download checksum"
|
|
||||||
puts "For your reference the #{type} is: #{hash}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# yields self with current working directory set to the uncompressed tarball
|
|
||||||
def brew
|
|
||||||
ohai "Downloading #{@url}"
|
|
||||||
HOMEBREW_CACHE.mkpath
|
|
||||||
Dir.chdir HOMEBREW_CACHE do
|
|
||||||
tmp=nil
|
|
||||||
tgz=Pathname.new(fetch()).realpath
|
|
||||||
begin
|
|
||||||
verify_download_integrity tgz
|
|
||||||
|
|
||||||
# we make an additional subdirectory so know exactly what we are
|
|
||||||
# recursively deleting later
|
|
||||||
# we use mktemp rather than appsupport/blah because some build scripts
|
|
||||||
# can't handle being built in a directory with spaces in it :P
|
|
||||||
tmp=`mktemp -dt #{File.basename @url}`.strip
|
|
||||||
Dir.chdir tmp do
|
|
||||||
Dir.chdir uncompress(tgz) do
|
|
||||||
yield self
|
|
||||||
end
|
|
||||||
end
|
|
||||||
rescue Interrupt, RuntimeError
|
|
||||||
if ARGV.include? '--debug'
|
|
||||||
# debug mode allows the packager to intercept a failed build and
|
|
||||||
# investigate the problems
|
|
||||||
puts "Rescued build at: #{tmp}"
|
|
||||||
exit! 1
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
FileUtils.rm_rf tmp if tmp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
# returns the directory where the archive was uncompressed
|
|
||||||
# in this Abstract case we assume there is no archive
|
|
||||||
def uncompress path
|
def uncompress path
|
||||||
path.dirname
|
safe_system *uncompress_args<<path
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def fetch
|
|
||||||
%r[http://(www.)?github.com/.*/(zip|tar)ball/].match @url
|
|
||||||
if $2
|
|
||||||
# curl doesn't do the redirect magic that we would like, so we get a
|
|
||||||
# stupidly named file, this is why wget would be beter, but oh well
|
|
||||||
tgz="#{@name}-#{@version}.#{$2=='tar' ? 'tgz' : $2}"
|
|
||||||
oarg="-o #{tgz}"
|
|
||||||
else
|
|
||||||
oarg='-O' #use the filename that curl gets
|
|
||||||
tgz=File.expand_path File.basename(@url)
|
|
||||||
end
|
|
||||||
|
|
||||||
unless File.exists? tgz
|
|
||||||
`curl -#LA "#{HOMEBREW_USER_AGENT}" #{oarg} "#{@url}"`
|
|
||||||
raise "Download failed" unless $? == 0
|
|
||||||
else
|
|
||||||
puts "File already downloaded and cached"
|
|
||||||
end
|
|
||||||
return tgz
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# somewhat useful, it'll raise if you call prefix, but it'll unpack a tar/zip
|
|
||||||
# for you, check the md5, and allow you to yield from brew
|
|
||||||
class UnidentifiedFormula <AbstractFormula
|
|
||||||
def initialize name=nil
|
|
||||||
super name
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def uncompress(path)
|
|
||||||
if path.extname == '.zip'
|
|
||||||
`unzip -qq "#{path}"`
|
|
||||||
else
|
|
||||||
`tar xf "#{path}"`
|
|
||||||
end
|
|
||||||
|
|
||||||
raise "Compression tool failed" if $? != 0
|
|
||||||
|
|
||||||
entries=Dir['*']
|
entries=Dir['*']
|
||||||
if entries.nil? or entries.length == 0
|
if entries.length == 0
|
||||||
raise "Empty tarball!"
|
raise "Empty archive"
|
||||||
elsif entries.length == 1
|
elsif entries.length == 1
|
||||||
# if one dir enter it as that will be where the build is
|
# if one dir enter it as that will be where the build is
|
||||||
entries.first
|
entries.first
|
||||||
@ -211,30 +244,6 @@ private
|
|||||||
# if there's more than one dir, then this is the build directory already
|
# if there's more than one dir, then this is the build directory already
|
||||||
Dir.pwd
|
Dir.pwd
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# this is what you will mostly use, reimplement install, prefix won't raise
|
|
||||||
class Formula <UnidentifiedFormula
|
|
||||||
def initialize name
|
|
||||||
super name
|
|
||||||
@version=Pathname.new(@url).version unless @version
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.class name
|
|
||||||
#remove invalid characters and camelcase
|
|
||||||
name.capitalize.gsub(/[-_\s]([a-zA-Z0-9])/) { $1.upcase }
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.path name
|
|
||||||
Pathname.new(HOMEBREW_PREFIX)+'Library'+'Formula'+(name.downcase+'.rb')
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create name
|
|
||||||
require Formula.path(name)
|
|
||||||
return eval(Formula.class(name)).new(name)
|
|
||||||
rescue LoadError
|
|
||||||
raise "No formula for #{name}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def method_added method
|
def method_added method
|
||||||
@ -243,16 +252,24 @@ class Formula <UnidentifiedFormula
|
|||||||
end
|
end
|
||||||
|
|
||||||
# see ack.rb for an example usage
|
# see ack.rb for an example usage
|
||||||
# you need to set @version and @name
|
|
||||||
class ScriptFileFormula <AbstractFormula
|
class ScriptFileFormula <AbstractFormula
|
||||||
|
def initialize name=nil
|
||||||
|
super
|
||||||
|
@name=name
|
||||||
|
end
|
||||||
|
def uncompress path
|
||||||
|
path.dirname
|
||||||
|
end
|
||||||
def install
|
def install
|
||||||
bin.install name
|
bin.install File.basename(@url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# see flac.rb for example usage
|
||||||
class GithubGistFormula <ScriptFileFormula
|
class GithubGistFormula <ScriptFileFormula
|
||||||
def initialize
|
def initialize name=nil
|
||||||
super File.basename(self.class.url)
|
super
|
||||||
|
@name=name
|
||||||
@version=File.basename(File.dirname(url))[0,6]
|
@version=File.basename(File.dirname(url))[0,6]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -14,146 +14,61 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
|
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
require 'formula'
|
class Keg <Pathname
|
||||||
|
def initialize path
|
||||||
class Keg
|
super path
|
||||||
attr_reader :path, :version, :name
|
raise "#{to_s} is not a valid keg" unless parent.parent == HOMEBREW_CELLAR
|
||||||
|
raise "#{to_s} is not a directory" unless directory?
|
||||||
def initialize formula
|
|
||||||
if formula.is_a? AbstractFormula
|
|
||||||
@path=formula.prefix
|
|
||||||
@name=formula.name
|
|
||||||
@version=formula.version
|
|
||||||
elsif formula.is_a? Pathname
|
|
||||||
# TODO
|
|
||||||
elsif formula.is_a? String
|
|
||||||
path=HOMEBREW_CELLAR+formula
|
|
||||||
kids=path.children
|
|
||||||
raise "Empty installation: #{path}" if kids.length < 1
|
|
||||||
raise "Multiple versions installed" if kids.length > 1
|
|
||||||
@path=kids[0]
|
|
||||||
@name=formula
|
|
||||||
@version=@path.basename
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def clean
|
def uninstall
|
||||||
# TODO unset write permission more
|
chmod_R 0777 # ensure we have permission to delete
|
||||||
%w[bin lib].each {|d| (Pathname.new(path)+d).find do |path|
|
rmtree
|
||||||
if not path.file?
|
parent.rmdir_if_possible
|
||||||
next
|
|
||||||
elsif path.extname == '.la'
|
|
||||||
# .la files are stupid
|
|
||||||
path.unlink
|
|
||||||
else
|
|
||||||
fo=`file -h #{path}`
|
|
||||||
args=nil
|
|
||||||
perms=0444
|
|
||||||
if fo =~ /Mach-O dynamically linked shared library/
|
|
||||||
args='-SxX'
|
|
||||||
elsif fo =~ /Mach-O [^ ]* ?executable/
|
|
||||||
args='' # use strip defaults
|
|
||||||
perms=0544
|
|
||||||
elsif fo =~ /script text executable/
|
|
||||||
perms=0544
|
|
||||||
end
|
|
||||||
if args
|
|
||||||
puts "Stripping: #{path}" if ARGV.include? '--verbose'
|
|
||||||
path.chmod 0644 # so we can strip
|
|
||||||
unless path.stat.nlink > 1
|
|
||||||
`strip #{args} #{path}`
|
|
||||||
else
|
|
||||||
# strip unlinks the file and recreates it, thus breaking hard links!
|
|
||||||
# is this expected behaviour? patch does it too… still,mktm this fixes it
|
|
||||||
tmp=`mktemp -t #{path.basename}`.strip
|
|
||||||
`strip -o #{tmp} #{path}`
|
|
||||||
`cat #{tmp} > #{path}`
|
|
||||||
File.unlink tmp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
path.chmod perms
|
|
||||||
end
|
|
||||||
end}
|
|
||||||
|
|
||||||
# remove empty directories TODO Rubyize!
|
|
||||||
`perl -MFile::Find -e"finddepth(sub{rmdir},'#{path}')"`
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def rm
|
def link
|
||||||
# don't rmtree shit if we aren't positive about our location!
|
$n=0
|
||||||
raise "Bad stuff!" unless path.parent.parent == HOMEBREW_CELLAR
|
$d=0
|
||||||
|
|
||||||
if path.directory?
|
mkpaths=(1..9).collect {|x| "man/man#{x}"} <<'man'<<'doc'<<'locale'<<'info'<<'aclocal'
|
||||||
FileUtils.chmod_R 0777, path # ensure we have permission to delete
|
|
||||||
path.rmtree # HOMEBREW_CELLAR/foo/1.2.0
|
# yeah indeed, you have to force anything you need in the main tree into
|
||||||
path.parent.rmdir if path.parent.children.length == 0 # HOMEBREW_CELLAR/foo
|
# these dirs REMEMBER that *NOT* everything needs to be in the main tree
|
||||||
end
|
link_dir('etc') {:mkpath}
|
||||||
|
link_dir('bin') {:link}
|
||||||
|
link_dir('lib') {|path| :mkpath if %w[pkgconfig php].include? path.to_s}
|
||||||
|
link_dir('include') {:link}
|
||||||
|
link_dir('share') {|path| :mkpath if mkpaths.include? path.to_s}
|
||||||
|
|
||||||
|
return $n+$d
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def __symlink_relative_to from, to
|
# symlinks the contents of self+foo recursively into /usr/local/foo
|
||||||
tod=to.dirname
|
def link_dir foo
|
||||||
tod.mkpath
|
root=self+foo
|
||||||
Dir.chdir(tod) do
|
|
||||||
#TODO use Ruby function so we get exceptions
|
|
||||||
#NOTE Ruby functions are fucked up!
|
|
||||||
`ln -sf "#{from.relative_path_from tod}"`
|
|
||||||
@n+=1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# symlinks a directory recursively into our FHS tree
|
root.find do |src|
|
||||||
def __ln start
|
next if src == root
|
||||||
start=path+start
|
|
||||||
return unless start.directory?
|
|
||||||
|
|
||||||
root=Pathname.new HOMEBREW_PREFIX
|
dst=HOMEBREW_PREFIX+src.relative_path_from(self)
|
||||||
start.find do |from|
|
dst.extend ObserverPathnameExtension
|
||||||
next if from == start
|
|
||||||
|
|
||||||
prune=false
|
if src.file?
|
||||||
|
dst.make_relative_symlink src
|
||||||
relative_path=from.relative_path_from path
|
elsif src.directory?
|
||||||
to=root+relative_path
|
|
||||||
|
|
||||||
if from.file?
|
|
||||||
__symlink_relative_to from, to
|
|
||||||
elsif from.directory?
|
|
||||||
# no need to put .app bundles in the path, the user can just use
|
# no need to put .app bundles in the path, the user can just use
|
||||||
# spotlight, or the open command and actual mac apps use an equivalent
|
# spotlight, or the open command and actual mac apps use an equivalent
|
||||||
Find.prune if from.extname.to_s == '.app'
|
Find.prune if src.extname.to_s == '.app'
|
||||||
|
|
||||||
branch=from.relative_path_from start
|
case yield src.relative_path_from(root)
|
||||||
|
when :skip then Find.prune
|
||||||
case yield branch when :skip
|
when :mkpath then dst.mkpath
|
||||||
Find.prune
|
else dst.make_relative_symlink src; Find.prune
|
||||||
when :mkpath
|
|
||||||
to.mkpath
|
|
||||||
@n+=1
|
|
||||||
else
|
|
||||||
__symlink_relative_to from, to
|
|
||||||
Find.prune
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
public
|
|
||||||
def ln
|
|
||||||
# yeah indeed, you have to force anything you need in the main tree into
|
|
||||||
# these dirs REMEMBER that *NOT* everything needs to be in the main tree
|
|
||||||
# TODO consider using hardlinks
|
|
||||||
@n=0
|
|
||||||
|
|
||||||
__ln('etc') {:mkpath}
|
|
||||||
__ln('bin') {:link}
|
|
||||||
__ln('lib') {|path| :mkpath if ['pkgconfig','php'].include? path.to_s}
|
|
||||||
__ln('include') {:link}
|
|
||||||
|
|
||||||
mkpaths=(1..9).collect {|x| "man/man#{x}"} <<'man'<<'doc'<<'locale'<<'info'<<'aclocal'
|
|
||||||
__ln('share') {|path| :mkpath if mkpaths.include? path.to_s}
|
|
||||||
|
|
||||||
return @n
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
@ -68,6 +68,24 @@ class Pathname
|
|||||||
return File.basename(to_s, extname)
|
return File.basename(to_s, extname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# I don't trust the children.length == 0 check particularly, not to mention
|
||||||
|
# it is slow to enumerate the whole directory just to see if it is empty,
|
||||||
|
# instead rely on good ol' libc and the filesystem
|
||||||
|
def rmdir_if_possible
|
||||||
|
rmdir
|
||||||
|
rescue SystemCallError => e
|
||||||
|
raise unless e.errno == Errno::ENOTEMPTY::Errno
|
||||||
|
end
|
||||||
|
|
||||||
|
def chmod_R perms
|
||||||
|
require 'fileutils'
|
||||||
|
FileUtils.chmod_R perms, to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def abv
|
||||||
|
`find #{to_s} -type f | wc -l`.strip+' files, '+`du -hd0 #{to_s} | cut -d"\t" -f1`.strip
|
||||||
|
end
|
||||||
|
|
||||||
def version
|
def version
|
||||||
# eg. boost_1_39_0
|
# eg. boost_1_39_0
|
||||||
/((\d+_)+\d+)$/.match stem
|
/((\d+_)+\d+)$/.match stem
|
||||||
@ -100,3 +118,39 @@ class Pathname
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# sets $n and $d so you can observe creation of stuff
|
||||||
|
module ObserverPathnameExtension
|
||||||
|
def unlink
|
||||||
|
super
|
||||||
|
puts "rm #{to_s}" if ARGV.verbose?
|
||||||
|
$n+=1
|
||||||
|
end
|
||||||
|
def rmdir
|
||||||
|
super
|
||||||
|
puts "rmdir #{to_s}" if ARGV.verbose?
|
||||||
|
$d+=1
|
||||||
|
end
|
||||||
|
def resolved_path_exists?
|
||||||
|
(dirname+readlink).exist?
|
||||||
|
end
|
||||||
|
def mkpath
|
||||||
|
super
|
||||||
|
puts "mkpath #{to_s}" if ARGV.verbose?
|
||||||
|
$d+=1
|
||||||
|
end
|
||||||
|
def make_relative_symlink src
|
||||||
|
dirname.mkpath
|
||||||
|
Dir.chdir dirname do
|
||||||
|
# TODO use Ruby function so we get exceptions
|
||||||
|
# NOTE Ruby functions may work, but I had a lot of problems
|
||||||
|
rv=system 'ln', '-sf', src.relative_path_from(dirname)
|
||||||
|
raise "Could not create symlink #{to_s}" unless rv and $? == 0
|
||||||
|
puts "ln #{to_s}" if ARGV.verbose?
|
||||||
|
$n+=1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
$n=0
|
||||||
|
$d=0
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
#!/usr/bin/ruby
|
#!/usr/bin/ruby
|
||||||
$:.unshift File.dirname(__FILE__)
|
$:.unshift File.dirname(__FILE__)
|
||||||
|
require 'pathname+yeast'
|
||||||
require 'formula'
|
require 'formula'
|
||||||
require 'keg'
|
require 'keg'
|
||||||
require 'pathname+yeast'
|
|
||||||
require 'stringio'
|
|
||||||
require 'utils'
|
require 'utils'
|
||||||
|
|
||||||
# these are defined in env.rb usually, but we don't want to break our actual
|
# these are defined in env.rb usually, but we don't want to break our actual
|
||||||
@ -17,6 +16,7 @@ HOMEBREW_CELLAR.mkpath
|
|||||||
raise "HOMEBREW_CELLAR couldn't be created!" unless HOMEBREW_CELLAR.directory?
|
raise "HOMEBREW_CELLAR couldn't be created!" unless HOMEBREW_CELLAR.directory?
|
||||||
at_exit { HOMEBREW_PREFIX.parent.rmtree }
|
at_exit { HOMEBREW_PREFIX.parent.rmtree }
|
||||||
require 'test/unit' # must be after at_exit
|
require 'test/unit' # must be after at_exit
|
||||||
|
require 'ARGV+yeast' # needs to be after test/unit to avoid conflict with OptionsParser
|
||||||
|
|
||||||
|
|
||||||
class MockFormula <Formula
|
class MockFormula <Formula
|
||||||
@ -26,12 +26,16 @@ class MockFormula <Formula
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class MostlyAbstractFormula <AbstractFormula
|
||||||
|
@url=''
|
||||||
|
end
|
||||||
|
|
||||||
class TestBall <Formula
|
class TestBall <Formula
|
||||||
def initialize
|
def initialize
|
||||||
@url="file:///#{Pathname.new(__FILE__).parent.realpath}/testball-0.1.tbz"
|
@url="file:///#{Pathname.new(__FILE__).parent.realpath}/testball-0.1.tbz"
|
||||||
super "testball"
|
super "testball"
|
||||||
end
|
end
|
||||||
|
|
||||||
def install
|
def install
|
||||||
prefix.install "bin"
|
prefix.install "bin"
|
||||||
prefix.install "libexec"
|
prefix.install "libexec"
|
||||||
@ -51,16 +55,28 @@ class TestBallOverrideBrew <Formula
|
|||||||
super "foo"
|
super "foo"
|
||||||
end
|
end
|
||||||
def brew
|
def brew
|
||||||
puts "We can't override brew"
|
# We can't override brew
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class TestScriptFileFormula <ScriptFileFormula
|
||||||
|
@url="file:///#{Pathname.new(__FILE__).realpath}"
|
||||||
|
@version="1"
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
@name='test-script-formula'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def nostdout
|
def nostdout
|
||||||
tmp=$stdout
|
require 'stringio'
|
||||||
|
tmpo=$stdout
|
||||||
|
tmpe=$stderr
|
||||||
$stdout=StringIO.new
|
$stdout=StringIO.new
|
||||||
yield
|
yield
|
||||||
$stdout=tmp
|
ensure
|
||||||
|
$stdout=tmpo
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@ -162,9 +178,10 @@ class BeerTasting <Test::Unit::TestCase
|
|||||||
def test_install
|
def test_install
|
||||||
f=TestBall.new
|
f=TestBall.new
|
||||||
|
|
||||||
|
assert_equal Formula.path(f.name), f.path
|
||||||
assert !f.installed?
|
assert !f.installed?
|
||||||
|
|
||||||
nostdout do
|
nostdout do
|
||||||
f.brew do
|
f.brew do
|
||||||
f.install
|
f.install
|
||||||
end
|
end
|
||||||
@ -180,17 +197,29 @@ class BeerTasting <Test::Unit::TestCase
|
|||||||
assert !(f.prefix+'main.c').exist?
|
assert !(f.prefix+'main.c').exist?
|
||||||
assert f.installed?
|
assert f.installed?
|
||||||
|
|
||||||
keg=Keg.new f
|
keg=Keg.new f.prefix
|
||||||
keg.ln
|
keg.link
|
||||||
assert_equal 2, HOMEBREW_PREFIX.children.length
|
assert_equal 2, HOMEBREW_PREFIX.children.length
|
||||||
assert (HOMEBREW_PREFIX+'bin').directory?
|
assert (HOMEBREW_PREFIX+'bin').directory?
|
||||||
assert_equal 3, (HOMEBREW_PREFIX+'bin').children.length
|
assert_equal 3, (HOMEBREW_PREFIX+'bin').children.length
|
||||||
|
|
||||||
keg.rm
|
keg.uninstall
|
||||||
assert !keg.path.exist?
|
assert !keg.exist?
|
||||||
assert !f.installed?
|
assert !f.installed?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_script_install
|
||||||
|
f=TestScriptFileFormula.new
|
||||||
|
|
||||||
|
nostdout do
|
||||||
|
f.brew do
|
||||||
|
f.install
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 1, f.bin.children.length
|
||||||
|
end
|
||||||
|
|
||||||
def test_md5
|
def test_md5
|
||||||
assert_nothing_raised { nostdout { TestBallValidMd5.new.brew {} } }
|
assert_nothing_raised { nostdout { TestBallValidMd5.new.brew {} } }
|
||||||
end
|
end
|
||||||
@ -213,10 +242,17 @@ class BeerTasting <Test::Unit::TestCase
|
|||||||
path.dirname.mkpath
|
path.dirname.mkpath
|
||||||
`echo "require 'brewkit'; class #{classname} <Formula; @url=''; end" > #{path}`
|
`echo "require 'brewkit'; class #{classname} <Formula; @url=''; end" > #{path}`
|
||||||
|
|
||||||
assert_not_nil Formula.create(FOOBAR)
|
assert_not_nil Formula.factory(FOOBAR)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_cant_override_brew
|
def test_cant_override_brew
|
||||||
assert_raises(RuntimeError) { TestBallOverrideBrew.new }
|
assert_raises(RuntimeError) { TestBallOverrideBrew.new }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_abstract_formula
|
||||||
|
f=MostlyAbstractFormula.new
|
||||||
|
assert_nil f.name
|
||||||
|
assert_raises(RuntimeError) { f.prefix }
|
||||||
|
nostdout { assert_raises(ExecutionError) { f.brew } }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -22,5 +22,24 @@ end
|
|||||||
|
|
||||||
# shows a warning in delicious pink
|
# shows a warning in delicious pink
|
||||||
def opoo warning
|
def opoo warning
|
||||||
puts "WARNING \033[1;35m#{warning}\033[0;0m"
|
puts "\033[1;35m==>\033[0;0;1m Warning\033[0;0m: #{warning}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def onoe error
|
||||||
|
puts "\033[1;31m==>\033[0;0;1m Error\033[0;0m: #{error}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def pretty_duration s
|
||||||
|
return "#{(s*1000).to_i} milliseconds" if s < 3
|
||||||
|
return "#{s.to_i} seconds" if s < 10*60
|
||||||
|
return "#{(s/60).to_i} minutes"
|
||||||
|
end
|
||||||
|
|
||||||
|
def interactive_shell
|
||||||
|
pid=fork
|
||||||
|
if pid.nil?
|
||||||
|
exec ENV['SHELL']
|
||||||
|
else
|
||||||
|
Process.wait pid
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
5
README
5
README
@ -53,7 +53,10 @@ Here's why you may prefer Homebrew to the alternatives:
|
|||||||
We optimise for Leopard Intel, binaries are stripped, compile flags
|
We optimise for Leopard Intel, binaries are stripped, compile flags
|
||||||
tweaked. Nobody wants crappy, slow software. Apart from MacPorts and Fink.
|
tweaked. Nobody wants crappy, slow software. Apart from MacPorts and Fink.
|
||||||
|
|
||||||
8. Integration with existing OS X technologies
|
8. Making the most of OS X
|
||||||
|
Homebrew knows how many cores you have thanks to RubyCocoa, so it makes
|
||||||
|
sure when it builds it uses all of them, (unless you don't want it to of
|
||||||
|
course).
|
||||||
Homebrew integrates with Ruby gems, CPAN and Python disttools. These tools
|
Homebrew integrates with Ruby gems, CPAN and Python disttools. These tools
|
||||||
exist already and do the job great. We don't reinvent the wheel, we just
|
exist already and do the job great. We don't reinvent the wheel, we just
|
||||||
improve it by making these tools install with more management options.
|
improve it by making these tools install with more management options.
|
||||||
|
349
bin/brew
349
bin/brew
@ -1,306 +1,135 @@
|
|||||||
#!/usr/bin/ruby
|
#!/usr/bin/ruby
|
||||||
$:.unshift __FILE__+'/../../Library/Homebrew'
|
$:.unshift ENV['RUBYLIB']=File.expand_path(__FILE__+'/../../Library/Homebrew')
|
||||||
require 'env'
|
|
||||||
require 'find'
|
|
||||||
|
|
||||||
PRISTINE_ARGV=ARGV.dup
|
require 'pathname+yeast'
|
||||||
|
require 'ARGV+yeast'
|
||||||
|
require 'utils'
|
||||||
|
require 'brew.h'
|
||||||
|
|
||||||
|
# TODO if whoami == root then use /Library/Caches/Homebrew instead
|
||||||
|
HOMEBREW_CACHE=Pathname.new("~/Library/Caches/Homebrew").expand_path
|
||||||
|
HOMEBREW_PREFIX=Pathname.new(__FILE__).dirname.parent.cleanpath
|
||||||
|
HOMEBREW_CELLAR=HOMEBREW_PREFIX+'Cellar'
|
||||||
|
HOMEBREW_VERSION='0.4'
|
||||||
|
HOMEBREW_WWW='http://bit.ly/Homebrew'
|
||||||
HOMEBREW_USER_AGENT="Homebrew #{HOMEBREW_VERSION} (Ruby #{VERSION}; Mac OS X 10.5 Leopard)"
|
HOMEBREW_USER_AGENT="Homebrew #{HOMEBREW_VERSION} (Ruby #{VERSION}; Mac OS X 10.5 Leopard)"
|
||||||
|
|
||||||
# often causes Ruby to throw exception ffs
|
if %w[/ /usr].include? HOMEBREW_PREFIX.to_s then abort <<-troba
|
||||||
|
You have placed Homebrew at the prefix: #{HOMEBREW_PREFIX}
|
||||||
|
This is not currently supported. Voice your support for this feature at:
|
||||||
|
#{HOMEBREW_WWW}
|
||||||
|
troba
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pathname often throws if CWD doesn't exist
|
||||||
Dir.chdir '/' unless File.directory? ENV['PWD']
|
Dir.chdir '/' unless File.directory? ENV['PWD']
|
||||||
|
|
||||||
######################################################################## funcs
|
|
||||||
# remove symlinks that no longer point to files
|
|
||||||
def prune
|
|
||||||
n=0
|
|
||||||
dirs=Array.new
|
|
||||||
HOMEBREW_PREFIX.find do |path|
|
|
||||||
if path.directory?
|
|
||||||
name=path.relative_path_from(HOMEBREW_PREFIX).to_s
|
|
||||||
if name == '.git' or name == 'Cellar' or name == 'Library'
|
|
||||||
Find.prune
|
|
||||||
else
|
|
||||||
dirs<<path
|
|
||||||
end
|
|
||||||
elsif path.symlink?
|
|
||||||
resolved_path=path.dirname+path.readlink
|
|
||||||
unless resolved_path.exist?
|
|
||||||
path.unlink
|
|
||||||
n+=1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
dirs.sort.reverse_each do |d|
|
|
||||||
if d.children.length == 0
|
|
||||||
d.rmdir
|
|
||||||
n+=1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return n
|
|
||||||
end
|
|
||||||
|
|
||||||
# we actually remove formulae from ARGV so that any other analysis of ARGV
|
|
||||||
# only includes relevent arguments
|
|
||||||
# TODO require will throw if no formula, so we should catch no?
|
|
||||||
def extract_named_args
|
|
||||||
args=Array.new
|
|
||||||
ARGV.delete_if do |arg|
|
|
||||||
if arg[0,1] == '-'
|
|
||||||
false
|
|
||||||
else
|
|
||||||
args<<arg
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
raise "Expecting the name of a keg or formula, eg:\n==> brew #{PRISTINE_ARGV.join ' '} wget" if args.empty?
|
|
||||||
return args
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_kegs
|
|
||||||
require 'keg'
|
|
||||||
kegs=extract_named_args.collect {|name| Keg.new name}
|
|
||||||
return kegs
|
|
||||||
end
|
|
||||||
|
|
||||||
def abv keg=nil
|
|
||||||
path=keg ? keg.path : HOMEBREW_CELLAR
|
|
||||||
if path.directory?
|
|
||||||
`find #{path} -type f | wc -l`.strip+' files, '+`du -hd0 #{path} | cut -d"\t" -f1`.strip
|
|
||||||
else
|
|
||||||
''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def install formula
|
|
||||||
require 'keg'
|
|
||||||
|
|
||||||
beginning = Time.now
|
|
||||||
|
|
||||||
formula.brew do
|
|
||||||
if ARGV.include? '--interactive'
|
|
||||||
ohai "Entering interactive mode"
|
|
||||||
puts "Type `exit' to return and finalize the installation"
|
|
||||||
puts "Install to this prefix: #{formula.prefix}"
|
|
||||||
pid=fork
|
|
||||||
if pid.nil?
|
|
||||||
exec 'bash'
|
|
||||||
else
|
|
||||||
Process.wait pid
|
|
||||||
end
|
|
||||||
elsif ARGV.include? '--help'
|
|
||||||
ohai './configure --help'
|
|
||||||
puts `./configure --help`
|
|
||||||
exit
|
|
||||||
else
|
|
||||||
formula.prefix.mkpath
|
|
||||||
formula.install
|
|
||||||
%w[README ChangeLog COPYING LICENSE COPYRIGHT AUTHORS].each do |file|
|
|
||||||
formula.prefix.install file if File.file? file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
raise "Nothing was installed to #{formula.prefix}" unless formula.installed?
|
|
||||||
|
|
||||||
ohai 'Finishing up'
|
|
||||||
keg=Keg.new formula
|
|
||||||
keg.clean
|
|
||||||
keg.ln
|
|
||||||
if formula.caveats
|
|
||||||
ohai "Caveats"
|
|
||||||
puts formula.caveats
|
|
||||||
ohai "Summary"
|
|
||||||
end
|
|
||||||
puts "#{keg.path}: "+abv(keg)+", built in #{pretty_duration Time.now-beginning}"
|
|
||||||
rescue Exception
|
|
||||||
formula.prefix.rmtree if formula.prefix.directory?
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
|
|
||||||
def mk url, mode='make'
|
|
||||||
require 'formula'
|
|
||||||
path=Pathname.new(url)
|
|
||||||
|
|
||||||
/(.*?)[-_.]?#{path.version}/.match path.basename
|
|
||||||
raise "Couldn't parse name from #{url}" if $1.nil? or $1.empty?
|
|
||||||
|
|
||||||
path=Formula.path $1
|
|
||||||
raise "#{path} already exists!" if File.exist? path
|
|
||||||
|
|
||||||
f=File.new path, 'w'
|
|
||||||
f.puts "require 'brewkit'"
|
|
||||||
f.puts
|
|
||||||
f.puts "class #{Formula.class $1} <Formula"
|
|
||||||
f.puts " @url='#{url}'"
|
|
||||||
f.puts " @homepage=''" # second because you fill in these two first
|
|
||||||
f.puts " @md5=''"
|
|
||||||
f.puts
|
|
||||||
|
|
||||||
if mode == "cmake"
|
|
||||||
f.puts " def deps"
|
|
||||||
f.puts " BinaryDep.new 'cmake'"
|
|
||||||
f.puts " end"
|
|
||||||
f.puts
|
|
||||||
end
|
|
||||||
|
|
||||||
f.puts " def install"
|
|
||||||
|
|
||||||
if mode == "make"
|
|
||||||
f.puts " system \"./configure --disable-debug --prefix='\#{prefix}'\""
|
|
||||||
f.puts " system \"make install\""
|
|
||||||
elsif mode == "cmake"
|
|
||||||
f.puts " system \"cmake -G 'Unix Makefiles' -DCMAKE_INSTALL_PREFIX=\#{prefix}\""
|
|
||||||
f.puts " system \"make\""
|
|
||||||
f.puts " system \"make install\""
|
|
||||||
end
|
|
||||||
|
|
||||||
f.puts " end"
|
|
||||||
f.print "end"
|
|
||||||
f.close
|
|
||||||
|
|
||||||
return path
|
|
||||||
end
|
|
||||||
|
|
||||||
def prefix
|
|
||||||
Pathname.new(__FILE__).dirname.parent.expand_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def usage
|
|
||||||
name=File.basename $0
|
|
||||||
<<-EOS
|
|
||||||
Usage: #{name} command [formula] ...
|
|
||||||
Usage: #{name} [--prefix] [--cache] [--version]
|
|
||||||
Usage: #{name} [--verbose]
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
install formula ... [--debug] [--interactive]
|
|
||||||
rm formula ...
|
|
||||||
list formula ...
|
|
||||||
ln formula ...
|
|
||||||
info [formula]
|
|
||||||
mk url
|
|
||||||
prune
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################## utils
|
|
||||||
def pretty_duration s
|
|
||||||
return "#{(s*1000).to_i} milliseconds" if s < 3
|
|
||||||
return "#{s.to_i} seconds" if s < 10*60
|
|
||||||
return "#{(s/60).to_i} minutes"
|
|
||||||
end
|
|
||||||
|
|
||||||
######################################################################### impl
|
|
||||||
begin
|
begin
|
||||||
case ARGV.shift
|
case ARGV.shift
|
||||||
when '--prefix' then puts prefix
|
when '--prefix' then puts HOMEBREW_PREFIX
|
||||||
when '--cache' then puts HOMEBREW_CACHE
|
when '--cache' then puts HOMEBREW_CACHE
|
||||||
when '-h', '--help', '--usage', '-?' then puts usage
|
when '-h', '--help', '--usage', '-?' then puts ARGV.usage
|
||||||
when '-v', '--version' then puts HOMEBREW_VERSION
|
when '-v', '--version' then puts HOMEBREW_VERSION
|
||||||
when 'macports' then exec "open 'http://www.macports.org/ports.php?by=name&substr=#{ARGV.shift}'"
|
|
||||||
|
|
||||||
when 'home', 'homepage'
|
when 'home', 'homepage'
|
||||||
require 'formula'
|
if ARGV.named.empty?
|
||||||
homepages=extract_named_args.collect {|name| Formula.create(name).homepage}
|
exec "open", HOMEBREW_WWW
|
||||||
exec "open #{homepages.join' '}"
|
else
|
||||||
|
exec "open", *ARGV.formulae.collect {|f| f.homepage}
|
||||||
|
end
|
||||||
|
|
||||||
when 'ls', 'list'
|
when 'ls', 'list'
|
||||||
dirs=extract_kegs.collect {|keg| keg.path}
|
exec "find", *ARGV.kegs.concat(%w[-not -type d -print])
|
||||||
exec "find #{dirs.join' '} -not -type d -print"
|
|
||||||
|
|
||||||
when 'edit'
|
when 'edit'
|
||||||
if ARGV.empty?
|
if ARGV.empty?
|
||||||
d=HOMEBREW_PREFIX
|
exec "mate", *Dir["#{HOMEBREW_PREFIX}/Library/*"]<<
|
||||||
exec "mate #{Dir["#{d}/Library/*"].join' '} #{d}/bin/brew #{d}/README"
|
"#{HOMEBREW_PREFIX}/bin/brew"<<
|
||||||
|
"#{HOMEBREW_PREFIX}/README"
|
||||||
else
|
else
|
||||||
require 'formula'
|
exec "mate", *ARGV.formulae.collect {|f| f.path}
|
||||||
paths=extract_named_args.collect {|name| Formula.path(name).to_s.gsub ' ', '\\ '}
|
|
||||||
exec "mate #{paths.join ' '}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'install'
|
when 'install'
|
||||||
require 'formula'
|
require 'keg'
|
||||||
extract_named_args.each do |name|
|
ARGV.formulae.each do |f|
|
||||||
f=Formula.create(name)
|
raise "#{f.name} is already installed" if f.installed? unless ARGV.force?
|
||||||
raise "#{f.name} already installed!\n==> #{f.prefix}" if f.installed? unless ARGV.include? '--force'
|
start_time=Time.now
|
||||||
install f
|
begin
|
||||||
|
install f
|
||||||
|
if f.caveats
|
||||||
|
ohai "Caveats"
|
||||||
|
puts f.caveats
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
ohai 'Finishing up'
|
||||||
|
clean f
|
||||||
|
raise "Nothing was installed to #{f.prefix}" unless f.installed?
|
||||||
|
Keg.new(f.prefix).link
|
||||||
|
rescue
|
||||||
|
f.prefix.rmtree if f.prefix.directory?
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
puts "#{f.prefix}: "+f.prefix.abv+", built in #{pretty_duration Time.now-start_time}"
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'ln', 'link'
|
when 'ln', 'link'
|
||||||
n=0
|
ARGV.kegs.each {|keg| puts "#{keg.link} links created for #{keg}"}
|
||||||
(kegs=extract_kegs).each do |keg|
|
|
||||||
n+=nn=keg.ln
|
|
||||||
puts "Created #{nn} links for #{keg.name}" if kegs.length > 1
|
|
||||||
end
|
|
||||||
puts "Created #{n} links"
|
|
||||||
|
|
||||||
when 'rm', 'uninstall'
|
when 'unlink'
|
||||||
extract_kegs.each do |keg|
|
ARGV.kegs.each {|keg| puts "#{keg.unlink} links removed for #{keg}"}
|
||||||
puts "Removing #{keg.name}..."
|
|
||||||
keg.rm
|
when 'rm', 'uninstall', 'remove'
|
||||||
|
ARGV.kegs.each do |keg|
|
||||||
|
puts "Uninstalling #{keg}..."
|
||||||
|
keg.uninstall
|
||||||
end
|
end
|
||||||
print "Pruning #{prefix}/..."
|
prune
|
||||||
puts " #{prune} symbolic links pruned"
|
|
||||||
|
when 'up', 'update'
|
||||||
|
puts "Reserved command"
|
||||||
|
|
||||||
when 'prune'
|
when 'prune'
|
||||||
puts "Pruned #{prune} symbolic links"
|
prune
|
||||||
|
|
||||||
when 'mk', 'make'
|
when 'mk', 'make'
|
||||||
mode = "make"
|
if ARGV.include? '--macports'
|
||||||
if ARGV.length > 0
|
exec "open", "http://www.macports.org/ports.php?by=name&substr=#{ARGV.next}"
|
||||||
if ARGV[0] == '--cmake'
|
|
||||||
ARGV.shift
|
|
||||||
mode = "cmake"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
paths=ARGV.collect {|arg| mk arg, mode}
|
|
||||||
if paths.empty?
|
|
||||||
raise "Invalid URL"
|
|
||||||
elsif Kernel.system "which mate > /dev/null" and $? == 0
|
|
||||||
paths=paths.collect {|path| path.to_s.gsub " ", "\\ "}
|
|
||||||
exec "mate #{paths.join ' '}"
|
|
||||||
else
|
else
|
||||||
puts paths.join("\n")
|
exec "mate", *ARGV.named.collect {|name| make name}
|
||||||
end
|
end
|
||||||
|
|
||||||
when 'info', 'abv'
|
when 'info', 'abv'
|
||||||
if ARGV.empty?
|
if ARGV.named.empty?
|
||||||
puts `ls #{HOMEBREW_CELLAR} | wc -l`.strip+" kegs, "+abv
|
puts `ls #{HOMEBREW_CELLAR} | wc -l`.strip+" kegs, "+HOMEBREW_CELLAR.abv
|
||||||
elsif ARGV[0][0..6] == 'http://'
|
elsif ARGV[0][0..6] == 'http://'
|
||||||
puts Pathname.new(ARGV.shift).version
|
puts Pathname.new(ARGV.shift).version
|
||||||
else
|
else
|
||||||
require 'formula'
|
ARGV.named.each {|name| info name}
|
||||||
#TODO show outdated status and that
|
|
||||||
frm=Formula.create(extract_named_args[0])
|
|
||||||
puts "#{frm.name} #{frm.version}"
|
|
||||||
puts frm.homepage
|
|
||||||
if frm.installed?
|
|
||||||
require 'keg'
|
|
||||||
keg=Keg.new frm
|
|
||||||
puts "#{abv keg} (installed to #{keg.path})"
|
|
||||||
end
|
|
||||||
if frm.caveats
|
|
||||||
ohai 'Caveats'
|
|
||||||
puts frm.caveats
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
puts usage
|
puts ARGV.usage
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue StandardError, Interrupt => e
|
rescue SystemExit
|
||||||
if ARGV.include? '--verbose' or ENV['HOMEBREW_DEBUG']
|
ohai "Kernel.exit" if ARGV.verbose?
|
||||||
raise
|
rescue Interrupt => e
|
||||||
elsif e.kind_of? Interrupt
|
puts # seemingly a newline is typical
|
||||||
puts # seeimgly a newline is typical
|
exit 130
|
||||||
exit 130
|
rescue SystemCallError, RuntimeError => e
|
||||||
elsif e.kind_of? StandardError and not e.kind_of? NameError
|
if ARGV.verbose? or ARGV.debug?
|
||||||
puts "\033[1;31mError\033[0;0m: #{e}"
|
onoe e.inspect
|
||||||
exit 1
|
puts e.backtrace
|
||||||
else
|
else
|
||||||
raise
|
onoe e
|
||||||
end
|
end
|
||||||
|
exit 1
|
||||||
|
rescue Exception => e
|
||||||
|
onoe "Homebrew has failed you :("
|
||||||
|
puts "Please report this bug at: #{HOMEBREW_WWW}"
|
||||||
|
puts "Please include this backtrace:"
|
||||||
|
ohai e.inspect
|
||||||
|
puts e.backtrace
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user