brew/bin/brew
2009-06-26 13:03:49 +01:00

347 lines
9.1 KiB
Ruby
Executable File

#!/usr/bin/ruby
require 'find'
require 'pathname'
$:.unshift Pathname.new(__FILE__).dirname.parent.realpath+'Library'+'Homebrew'
require 'env'
# often causes Ruby to throw exception ffs
Dir.chdir '/' unless File.exist? Dir.getwd
def prune
n=0
dirs=Array.new
$root.find do |path|
if path.directory?
name=path.relative_path_from($root).to_s
if name == '.git' or name == 'Cellar' or name == 'Library/Homebrew' or name == 'Library/Formula'
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
# entries lists '.' and '..' so 2 is minimum basically
dirs.sort.reverse_each do |d|
if d.children.length == 0
d.rmdir
n+=1
end
end
return n
end
def formulize name
name=Pathname.new name
return name if name.directory? and name.parent.realpath == $cellar
return File.basename(name, '.rb') if name.file? and name.extname == '.rb' and name.parent.realpath == $formula
name=name.to_s
raise "#{name} is an invalid name for a formula" if name.include? '/'
return name if ($formula+(name+'.rb')).file?
return name if ($cellar+name).directory?
raise "No formula or keg for #{name} found"
end
def shift_formulae_from_ARGV
fae=Array.new
i=0
while name=ARGV[i]
unless name[0,1] == '-'
fae<<formulize(ARGV.shift).to_s
else
i+=1
end
end
raise "You must specify a formula" if fae.empty?
return fae
end
def __class name
#remove invalid characters and camelcase
name.capitalize.gsub(/[-_\s]([a-zA-Z0-9])/) { $1.upcase }
end
def __rb name
$formula+(name+'.rb')
end
def __obj name
require "#{__rb name}"
return eval(__class(name)).new(name)
end
def rm keg
#TODO if multiple versions don't rm all unless --force
path=$cellar+keg
path.rmtree
puts "#{path} removed (#{prune} files)"
end
def ln name
keg=$cellar+name
keg=keg.realpath
if keg.parent.parent == $root
# we are one dir too high
kids=keg.children
raise "#{keg} is empty :(" if kids.length == 0
raise "There are multiple versions of #{keg.basename} installed please specify one" if kids.length > 1
keg=keg.children.first
raise "#{keg} is not a directory" unless keg.directory?
elsif keg.parent.parent.parent != $root
raise '#{keg} is not a keg'
end
# yeah indeed, you have to force anything you need in the main tree into
# these directories :P
# NOTE that not everything needs to be in the main tree
# TODO consider using hardlinks
$n=0
lnd(keg, 'etc') {:mkdir}
lnd(keg, 'include') {:link}
lnd(keg, 'bin') {:link}
lnd(keg, 'lib') {|path| :mkpath if ['pkgconfig','php'].include? path.to_s}
lnd(keg, 'share') do |path|
mkpaths=(1..9).collect {|x| "man/man#{x}"} <<'man'<<'doc'<<'locale'<<'info'<<'aclocal'
:mkpath if mkpaths.include? path.to_s
end
return $n
end
def symlink_relative_to from, to
tod=to.dirname
tod.mkpath
Dir.chdir(tod) do
#TODO use ruby function so we get exceptions
`ln -sf "#{from.relative_path_from tod}"`
$n+=1
end
end
# symlinks a directory recursively into our FHS tree
def lnd keg, start
start=keg+start
return unless start.directory?
start.find do |from|
next if from == start
prune=false
relative_path=from.relative_path_from keg
to=$root+relative_path
if from.directory?
# 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
Find.prune if from.extname.to_s == '.app'
cmd=yield from.relative_path_from(start)
if :skip == cmd
Find.prune
elsif :mkpath == cmd
to.mkpath
$n+=1
else
symlink_relative_to from, to
Find.prune
end
elsif from.file?
symlink_relative_to from, to
end
end
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
def abv keg=''
keg=$cellar+keg
return nil if not File.directory? keg
`find #{keg} -type f | wc -l`.strip+' files, '+`du -hd0 #{keg} | cut -d"\t" -f1`.strip
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
case ARGV.shift
when 'prune'
puts "Pruned #{prune} files"
when '--prefix'
# we use the cwd because __FILE__ can be relative and expand_path
# resolves the symlink for the working directory if fed a relative path
# NOTE we don't use Dir.pwd because it resolves the symlink :(
cwd=Pathname.new `pwd`.strip
puts File.expand_path(cwd+__FILE__+'../../')
when '--cache'
puts File.expand_path('~/Library/Application Support/Homebrew')
when '-h', '--help', '--usage', '-?'
puts usage
when '-v', '--version'
puts HOMEBREW_VERSION
when 'list'
fae=shift_formulae_from_ARGV.collect do |name|
keg=$cellar+name
keg.directory? ? keg : nil
end
raise 'No such keg' if fae.first.nil? and fae.length == 1
puts `find #{fae.join' '} -type f -print`
when 'macports'
exec "open 'http://www.macports.org/ports.php?by=name&substr=#{ARGV.shift}'"
when 'edit'
if ARGV.empty?
exec "mate #{$formula} #{$root}/Library/Homebrew #{$root}/bin/brew #{$root}/README"
else
exec "mate #{$formula}/#{ARGV.shift}.rb"
end
when 'install'
shift_formulae_from_ARGV.each do |name|
beginning = Time.now
o=__obj(name)
begin
raise "#{o.prefix} already exists!" if o.prefix.exist?
o.prefix.mkpath
o.brew do
if ARGV.include? '--interactive'
ohai "Entering interactive mode, type `exit' to return to this shell"
exec "bash" # exec() because system() didn't work :(
elsif ARGV.include? '--help'
ohai './configure --help'
puts `./configure --help`
exit
else
o.install
['README','ChangeLog','COPYING','COPYRIGHT','AUTHORS'].each do |file|
FileUtils.cp file, o.prefix if File.file? file
end
#this is common, and we don't want it
versioned_docs=o.doc.parent+"#{o.name}-#{o.version}"
versioned_docs.rename o.doc if versioned_docs.exist?
end
end
ohai 'Finishing up'
o.clean
ln name
if o.caveats
ohai "Caveats"
puts o.caveats
ohai "Summary"
end
puts "#{o.prefix}: "+abv(name)+", built in #{pretty_duration Time.now-beginning}"
rescue Exception
FileUtils.rm_rf o.prefix
raise
end
end
when 'ln'
n=0
shift_formulae_from_ARGV.each {|name| n+=ln name}
puts "Created #{n} links"
when 'rm', 'uninstall'
shift_formulae_from_ARGV.each {|name| rm name}
when 'mk'
require 'brewkit'
url=ARGV.shift
version=extract_version File.basename(url, Pathname.new(url).extname)
/(.*?)[-_.]?#{version}/.match File.basename(url)
raise "Couldn't parse name from #{url}" if $1.nil?
path=$formula+($1.downcase+'.rb')
raise "#{path} already exists!" if File.exist? path
f=File.new path, 'w'
f.puts "require 'brewkit'"
f.puts
f.puts "class #{__class $1} <Formula"
f.puts " @url='#{url}'"
f.puts " @md5=''"
f.puts " @homepage=''"
f.puts
f.puts " def install"
f.puts " system \"./configure --disable-debug --prefix='\#{prefix}'\""
f.puts " system \"make install\""
f.puts " end"
f.print "end"
f.close
if Kernel.system "which mate > /dev/null" and $? == 0
exec "mate #{path}"
else
puts path
end
when 'info','abv'
if ARGV.empty?
puts abv
else
if ARGV[0][0..6] == 'http://'
require 'brewkit'
path=Pathname.new ARGV[0]
basename=File.basename path, path.extname
v=extract_version basename
puts v
else
o=__obj shift_formulae_from_ARGV[0]
puts "#{o.name} #{o.version}"
puts o.homepage
if abv=abv(o.name)
ohai "Installation"
puts abv
end
if o.caveats
ohai 'Caveats'
puts o.caveats
end
end
end
else
puts usage
end
rescue StandardError, Interrupt => e
if ARGV.include? '--verbose' or ENV['HOMEBREW_DEBUG']
raise
elsif e.kind_of? Interrupt
puts # seeimgly a newline is typical
exit 130
elsif e.kind_of? StandardError and not e.kind_of? NameError
puts "\033[1;31mError\033[0;0m: #{e}"
exit 1
else
raise
end
end