Refactor--object orientate where sensible

This commit is contained in:
Max Howell 2009-07-24 15:10:01 +01:00
parent 5eb9d6519d
commit fb5aefd4a0
7 changed files with 680 additions and 611 deletions

View File

@ -15,9 +15,8 @@
# 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 'pathname'
require 'osx/cocoa' # to get number of cores require 'osx/cocoa' # to get number of cores
require 'env' require 'formula'
# optimise all the way to eleven, references: # optimise all the way to eleven, references:
# http://en.gentoo-wiki.com/wiki/Safe_Cflags/Intel # http://en.gentoo-wiki.com/wiki/Safe_Cflags/Intel
@ -38,343 +37,7 @@ unless $root.to_s == '/usr/local'
end end
def ohai title ######################################################################## utils
n=`tput cols`.strip.to_i-4
puts "\033[0;34m==>\033[0;0;1m #{title[0,n]}\033[0;0m"
end
def cache
cache=File.expand_path "~/Library/Caches/Homebrew"
FileUtils.mkpath cache
return cache
end
class BuildError <RuntimeError
def initialize cmd
super "Build failed during: #{cmd}"
end
end
# pass in the basename of the filename _without_ any file extension
def extract_version basename
# eg. boost_1_39_0
/((\d+_)+\d+)$/.match basename
return $1.gsub('_', '.') if $1
# eg. foobar-4.5.1-1
/-((\d+\.)*\d+-\d+)$/.match basename
return $1 if $1
# eg. foobar-4.5.1
/-((\d+\.)*\d+)$/.match basename
return $1 if $1
# eg. foobar-4.5.1b
/-((\d+\.)*\d+([abc]|rc\d))$/.match basename
return $1 if $1
# eg foobar-4.5.0-beta1
/-((\d+\.)*\d+-beta\d+)$/.match basename
return $1 if $1
# eg. foobar4.5.1
/((\d+\.)*\d+)$/.match basename
return $1 if $1
# eg. otp_src_R13B (this is erlang's style)
# eg. astyle_1.23_macosx.tar.gz
basename.scan /_([^_]+)/ do |match|
return match.first if /\d/.match $1
end
end
# make our code neater
class Pathname
def mv dst
FileUtils.mv to_s, dst
end
def rename dst
dst=Pathname.new dst
dst.unlink if dst.exist?
mv dst
end
def install src
if src.is_a? Array
src.each {|src| install src }
elsif File.exist? src
mkpath
if File.symlink? src
# we cp symlinks because FileUtils.mv is shit and won't mv a symlink
# if its final destination has an invalid target! FFS. Ruby is shit.
FileUtils.cp src, to_s
else
# we mv when possible as it is faster and you should only be using
# this function when installing from the temporary build directory
FileUtils.mv src, to_s
end
end
end
def cp dst
if file?
FileUtils.cp to_s, dst
else
FileUtils.cp_r to_s, dst
end
end
# for filetypes we support
def extname
/\.(zip|tar\.(gz|bz2)|tgz)$/.match to_s
return ".#{$1}" if $1
return File.extname(to_s)
end
# for filetypes we support, basename without extension
def stem
return File.basename(to_s, extname)
end
end
# the base class variety of formula, you don't get a prefix, so it's not really
# useful. See the derived classes for fun and games.
class AbstractFormula
require 'find'
require 'fileutils'
# fuck knows, ruby is weird
# TODO please fix!
def self.url
@url
end
def url
self.class.url
end
def self.md5
@md5
end
def md5
self.class.md5
end
def self.homepage
@homepage
end
def homepage
self.class.homepage
end
# end ruby is weird section
def version
@version
end
def name
@name
end
def initialize name=nil
@name=name
# fuck knows, ruby is weird
@url=url if @url.nil?
raise "@url.nil?" if @url.nil?
@md5=md5 if @md5.nil?
# end ruby is weird section
end
public
def prefix
raise "@name.nil!" if @name.nil?
raise "@version.nil?" if @version.nil?
$cellar+@name+@version
end
def bin
prefix+'bin'
end
def doc
prefix+'share'+'doc'+name
end
def man
prefix+'share'+'man'
end
def man1
prefix+'share'+'man'+'man1'
end
def lib
prefix+'lib'
end
def include
prefix+'include'
end
def caveats
nil
end
# yields self with current working directory set to the uncompressed tarball
def brew
ohai "Downloading #{@url}"
Dir.chdir cache do
tmp=tgz=nil
begin
tgz=Pathname.new(fetch()).realpath
md5=`md5 -q "#{tgz}"`.strip
raise "MD5 mismatch: #{md5}" unless @md5 and md5 == @md5.downcase
# 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
def clean
#TODO strip libexec too
[bin,lib].each {|path| path.find do |path|
if not path.file?
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/ # defaults strip everything
args='' # still do the strip
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
`perl -MFile::Find -e"finddepth(sub{rmdir},'#{prefix}')"`
end
protected
def uncompress path
path.dirname
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
agent="Homebrew #{HOMEBREW_VERSION} (Ruby #{VERSION}; Mac OS X 10.5 Leopard)"
unless File.exists? tgz
`curl -#LA "#{agent}" #{oarg} "#{@url}"`
raise "Download failed" unless $? == 0
else
puts "File already downloaded and cached"
end
return tgz
end
def method_added method
raise 'You cannot override Formula.brew' if method == 'brew'
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['*']
if entries.nil? or entries.length == 0
raise "Empty tarball!"
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
end
# this is what you will mostly use, reimplement install, prefix won't raise
class Formula <UnidentifiedFormula
def initialize name
super name
@version=extract_version Pathname.new(File.basename(@url)).stem unless @version
end
end
# see ack.rb for an example usage
class ScriptFileFormula <AbstractFormula
def install
bin.install name
end
end
class GithubGistFormula <ScriptFileFormula
def initialize
super File.basename(url)
@version=File.basename(File.dirname(url))[0,6]
end
end
def inreplace(path, before, after) def inreplace(path, before, after)
before=before.to_s.gsub('"', '\"').gsub('/', '\/') before=before.to_s.gsub('"', '\"').gsub('/', '\/')
@ -385,34 +48,6 @@ def inreplace(path, before, after)
#TODO optimise it by taking before and after as arrays #TODO optimise it by taking before and after as arrays
#Bah, just make the script writers do it themselves with a standard collect block #Bah, just make the script writers do it themselves with a standard collect block
#TODO use ed -- less to escape #TODO use ed -- less to escape
#TODO the above doesn't escape all regexp symbols!
`perl -pi -e "s/#{before}/#{after}/g" "#{path}"` `perl -pi -e "s/#{before}/#{after}/g" "#{path}"`
end end
def system cmd
ohai cmd
out=''
IO.popen("#{cmd} 2>&1") do |f|
until f.eof?
s=f.gets
if ARGV.include? '--verbose'
puts s
else
out+=s
end
end
end
unless $? == 0
puts out unless ARGV.include? '--verbose' #already did that above
raise BuildError.new(cmd)
end
end
####################################################################### script
if $0 == __FILE__
d=$cellar.parent+'bin'
d.mkpath unless d.exist?
Dir.chdir d
Pathname.new('brew').make_symlink Pathname.new('../Cellar')+'homebrew'+'brew'
end

View File

@ -21,4 +21,97 @@ $root=Pathname.new(__FILE__).dirname.parent.parent.realpath
$formula=$root+'Library'+'Formula' $formula=$root+'Library'+'Formula'
$cellar=$root+'Cellar' $cellar=$root+'Cellar'
HOMEBREW_VERSION='0.2' HOMEBREW_VERSION='0.3'
HOMEBREW_CACHE=File.expand_path "~/Library/Caches/Homebrew"
######################################################################## utils
def ohai title
n=`tput cols`.strip.to_i-4
puts "\033[0;34m==>\033[0;0;1m #{title[0,n]}\033[0;0m"
end
############################################################### class Pathname
# we enhance Pathname to make our code more legible
# of course this kind of thing is evil, but meh
class Pathname
def mv dst
FileUtils.mv to_s, dst
end
def rename dst
dst=Pathname.new dst
dst.unlink if dst.exist?
mv dst
end
def install src
if src.is_a? Array
src.each {|src| install src }
elsif File.exist? src
mkpath
if File.symlink? src
# we use the BSD mv command because FileUtils copies the target and
# not the link! I'm beginning to wish I'd used Python quite honestly!
`mv #{src} #{to_s}`
else
# we mv when possible as it is faster and you should only be using
# this function when installing from the temporary build directory
FileUtils.mv src, to_s
end
end
end
def cp dst
if file?
FileUtils.cp to_s, dst
else
FileUtils.cp_r to_s, dst
end
end
# extended to support the double extensions .tar.gz and .tar.bz2
def extname
/(\.tar\.(gz|bz2))$/.match to_s
return $1 if $1
return File.extname(to_s)
end
# for filetypes we support, basename without extension
def stem
return File.basename(to_s, extname)
end
def version
# eg. boost_1_39_0
/((\d+_)+\d+)$/.match stem
return $1.gsub('_', '.') if $1
# eg. foobar-4.5.1-1
/-((\d+\.)*\d+-\d+)$/.match stem
return $1 if $1
# eg. foobar-4.5.1
/-((\d+\.)*\d+)$/.match stem
return $1 if $1
# eg. foobar-4.5.1b
/-((\d+\.)*\d+([abc]|rc\d))$/.match stem
return $1 if $1
# eg foobar-4.5.0-beta1
/-((\d+\.)*\d+-beta\d+)$/.match stem
return $1 if $1
# eg. foobar4.5.1
/((\d+\.)*\d+)$/.match stem
return $1 if $1
# eg. otp_src_R13B (this is erlang's style)
# eg. astyle_1.23_macosx.tar.gz
stem.scan /_([^_]+)/ do |match|
return match.first if /\d/.match $1
end
end
end

263
Library/Homebrew/formula.rb Normal file
View File

@ -0,0 +1,263 @@
# 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 'env'
class BuildError <RuntimeError
def initialize cmd
super "Build failed during: #{cmd}"
end
end
# the base class variety of formula, you don't get a prefix, so it's not really
# useful. See the derived classes for fun and games.
class AbstractFormula
require 'find'
require 'fileutils'
# fuck knows, ruby is weird
# TODO please fix!
def self.url
@url
end
def url
self.class.url
end
def self.md5
@md5
end
def md5
self.class.md5
end
def self.homepage
@homepage
end
def homepage
self.class.homepage
end
# end ruby is weird section
def version
@version
end
def name
@name
end
# if the dir is there, but it's empty we consider it not installed
def installed?
return prefix.children.count > 0
rescue
return false
end
def initialize name=nil
@name=name
# fuck knows, ruby is weird
@url=url if @url.nil?
raise "@url.nil?" if @url.nil?
@md5=md5 if @md5.nil?
# end ruby is weird section
end
public
def prefix
raise "@name.nil!" if @name.nil?
raise "@version.nil?" if @version.nil?
$cellar+@name+@version
end
def bin
prefix+'bin'
end
def doc
prefix+'share'+'doc'+name
end
def man
prefix+'share'+'man'
end
def man1
man+'man1'
end
def lib
prefix+'lib'
end
def include
prefix+'include'
end
def caveats
nil
end
# Pretty titles the command and buffers stdout/stderr
# Throws if there's an error
def system cmd
ohai cmd
if ARGV.include? '--verbose'
Kernel.system cmd
else
out=''
IO.popen "#{cmd} 2>&1" do |f|
until f.eof?
out+=f.gets
end
end
puts out unless $? == 0
end
raise BuildError.new(cmd) unless $? == 0
end
# yields self with current working directory set to the uncompressed tarball
def brew
ohai "Downloading #{@url}"
FileUtils.mkpath HOMEBREW_CACHE
Dir.chdir HOMEBREW_CACHE do
tmp=tgz=nil
begin
tgz=Pathname.new(fetch()).realpath
md5=`md5 -q "#{tgz}"`.strip
raise "MD5 mismatch: #{md5}" unless @md5 and md5 == @md5.downcase
# 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
path.dirname
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
agent="Homebrew #{HOMEBREW_VERSION} (Ruby #{VERSION}; Mac OS X 10.5 Leopard)"
unless File.exists? tgz
`curl -#LA "#{agent}" #{oarg} "#{@url}"`
raise "Download failed" unless $? == 0
else
puts "File already downloaded and cached"
end
return tgz
end
def method_added method
raise 'You cannot override Formula.brew' if method == 'brew'
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['*']
if entries.nil? or entries.length == 0
raise "Empty tarball!"
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
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
$formula+(name.downcase+'.rb')
end
def self.create name
require Formula.path(name)
return eval(Formula.class(name)).new(name)
rescue
raise "No formula for #{name}"
end
end
# see ack.rb for an example usage
class ScriptFileFormula <AbstractFormula
def install
bin.install name
end
end
class GithubGistFormula <ScriptFileFormula
def initialize
super File.basename(url)
@version=File.basename(File.dirname(url))[0,6]
end
end

153
Library/Homebrew/keg.rb Normal file
View File

@ -0,0 +1,153 @@
# 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 'env'
require 'formula'
class Keg
attr_reader :path, :version, :name
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
kids=($cellar+formula).children
raise "Multiple versions installed" if kids.length > 1
@path=kids[0]
@name=formula
@version=@path.basename
end
end
def clean
# TODO unset write permission more
%w[bin lib].each {|d| (Pathname.new(path)+d).find do |path|
if not path.file?
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/ # defaults strip everything
args='' # still do the strip
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
def rm
if path.directory?
FileUtils.chmod_R 0777, path # ensure we have permission to delete
path.rmtree
end
end
private
def __symlink_relative_to from, to
tod=to.dirname
tod.mkpath
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
def __ln start
start=path+start
return unless start.directory?
start.find do |from|
next if from == start
prune=false
relative_path=from.relative_path_from path
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
# spotlight, or the open command and actual mac apps use an equivalent
Find.prune if from.extname.to_s == '.app'
branch=from.relative_path_from start
case yield branch when :skip
Find.prune
when :mkpath
to.mkpath
@n+=1
else
__symlink_relative_to from, to
Find.prune
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

View File

@ -2,7 +2,7 @@
$:.unshift File.dirname(__FILE__) $:.unshift File.dirname(__FILE__)
require 'test/unit' require 'test/unit'
require 'brewkit' require 'formula'
require 'stringio' require 'stringio'
class TestFormula <Formula class TestFormula <Formula

33
README
View File

@ -1,7 +1,7 @@
Homebrew Homebrew
======== ========
Homebrew's purpose is the same as MacPorts or Fink, ie. to let you easily Homebrew's purpose is basically the same as MacPorts or Fink, ie. to let you
install other open source software on your Mac. easily install other open source software on your Mac.
Here's why you may prefer Homebrew to the alternatives: Here's why you may prefer Homebrew to the alternatives:
@ -34,30 +34,43 @@ Here's why you may prefer Homebrew to the alternatives:
Homebrew will automatically open it for you to tweak with TextMate or Homebrew will automatically open it for you to tweak with TextMate or
$EDITOR. $EDITOR.
Or skip going via a package entirely, just install into the Cellar and use 6. DIY package installation
"brew ln" to symlink it into the main tree. MacPorts doesn't support the beta version? Need an older version? Need
custom compile flags? The Homebrew toolchain is carefully segregated so
you can just build your own stuff while still reaping the benefits of
package management.
6. Optimisation Just install to the Cellar and then call brew ln to symlink that
installation into /usr/local, eg.
./configure --prefix=/usr/local/Cellar/wget/1.10
make install
brew ln wget
This means you can also install multiple versions of the same package and
switch on demand.
7. Optimisation
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.
7. Integration with existing OS X technologies 8. Integration with existing OS X technologies
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.
8. Complimenting what OS X already has 9. Complimenting what OS X already has
Macports is an autarky. You get a duplicate copy of libz, OpenSSL, Python Macports is an autarky. You get a duplicate copy of libz, OpenSSL, Python
etc. They do this to support OS X Tiger, etc. more easily. We don't support etc. They do this to support OS X Tiger, etc. more easily. We don't support
Tiger, we duplicate nothing. Homebrew compliments OS X, it doesn't seek to Tiger, we duplicate nothing. Homebrew compliments OS X, it doesn't seek to
operate independently of it. operate independently of it.
9. Homebrew has a beer theme A. Homebrew has a beer theme
Beer goggles will help you to evangelise Homebrew more effectively. Beer goggles will help you to evangelise Homebrew more effectively.
X. Homebrew helps you get chicks B. Homebrew helps get you chicks
There's no conclusive scientific evidence as yet, but I firmly believe it's There's no conclusive scientific evidence as yet, but I firmly believe it's
just a matter of statistics and time. just a matter of time and statistics.
I know I've made it sound so awesome you can hardly wait to rip MacPorts out I know I've made it sound so awesome you can hardly wait to rip MacPorts out
and embrace the fresh hoppy taste of Homebrew, but I should point out that it and embrace the fresh hoppy taste of Homebrew, but I should point out that it

374
bin/brew
View File

@ -8,6 +8,7 @@ require 'env'
# often causes Ruby to throw exception ffs # often causes Ruby to throw exception ffs
Dir.chdir '/' unless File.directory? ENV['PWD'] Dir.chdir '/' unless File.directory? ENV['PWD']
######################################################################## funcs
def prune def prune
n=0 n=0
dirs=Array.new dirs=Array.new
@ -37,130 +38,113 @@ def prune
return n return n
end end
def formulize name # we actually remove formulae from ARGV so that any other analysis of ARGV
name=Pathname.new name # only includes relevent arguments
return name if name.directory? and name.parent.realpath == $cellar # TODO require will throw if no formula, so we should catch no?
return File.basename(name, '.rb') if name.file? and name.extname == '.rb' and name.parent.realpath == $formula def extract_named_args
args=Array.new
name=name.to_s ARGV.delete_if do |arg|
raise "#{name} is an invalid name for a formula" if name.include? '/' if arg[0,1] == '-'
false
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 else
i+=1 args<<arg
true
end end
end end
raise "You must specify a formula" if fae.empty? return args
return fae
end end
def __class name def extract_kegs
#remove invalid characters and camelcase require 'keg'
name.capitalize.gsub(/[-_\s]([a-zA-Z0-9])/) { $1.upcase } kegs=extract_named_args.collect {|name| Keg.new name}
raise "Expecting the name of a keg or formula, eg:\n\tbrew #{ARGV.join ' '} wget" if kegs.empty?
return kegs
end end
def __rb name def abv keg=nil
$formula+(name+'.rb') path=keg ? keg.path : $cellar
end if path.directory?
`find #{path} -type f | wc -l`.strip+' files, '+`du -hd0 #{path} | cut -d"\t" -f1`.strip
def __obj name else
require "#{__rb name}" nil
return eval(__class(name)).new(name)
end
def rm keg
#TODO if multiple versions don't rm all unless --force
path=$cellar+keg
`chmod -R u+rw #{path}` # we leave things read only
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
end end
# symlinks a directory recursively into our FHS tree def install formula
def lnd keg, start require 'keg'
start=keg+start
return unless start.directory?
start.find do |from| raise "#{formula.name} already installed!\n\t#{formula.prefix}" if formula.installed?
next if from == start
beginning = Time.now
prune=false formula.brew do
relative_path=from.relative_path_from keg if ARGV.include? '--interactive'
to=$root+relative_path ohai "Entering interactive mode"
puts "Type `exit' to return and finalize the installation"
if from.directory? puts "Install to this prefix: #{formula.prefix}"
# no need to put .app bundles in the path, the user can just use pid=fork
# spotlight, or the open command and actual mac apps use an equivalent if pid.nil?
Find.prune if from.extname.to_s == '.app' exec 'bash'
cmd=yield from.relative_path_from(start)
if :skip == cmd
Find.prune
elsif :mkpath == cmd
to.mkpath
$n+=1
else else
symlink_relative_to from, to Process.wait pid
Find.prune end
elsif ARGV.include? '--help'
ohai './configure --help'
puts `./configure --help`
exit
else
formula.prefix.mkpath
formula.install
%w[README ChangeLog COPYING COPYRIGHT AUTHORS].each do |file|
formula.prefix.install file if File.file? file
end end
elsif from.file?
symlink_relative_to from, to
end end
end end
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
raise
end
def mk 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 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
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
return path
end
def prefix
Pathname.new(__FILE__).dirname.parent.expand_path
end end
def usage def usage
@ -181,11 +165,6 @@ Commands:
EOS EOS
end 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 ######################################################################## utils
def pretty_duration s def pretty_duration s
@ -197,147 +176,80 @@ end
######################################################################### impl ######################################################################### impl
begin begin
case ARGV.shift case ARGV.shift
when 'prune' when '--prefix' then puts prefix
puts "Pruned #{prune} files" when '--cache' then puts Homebrew::cache
when '--prefix' when '-h', '--help', '--usage', '-?' then puts usage
# we use the cwd because __FILE__ can be relative and expand_path when '-v', '--version' then puts HOMEBREW_VERSION
# resolves the symlink for the working directory if fed a relative path when 'macports' then exec "open 'http://www.macports.org/ports.php?by=name&substr=#{ARGV.shift}'"
# NOTE we don't use Dir.pwd because it resolves the symlink :(
cwd=Pathname.new `pwd`.strip when 'ls', 'list'
puts File.expand_path(cwd+__FILE__+'../../') dirs=extract_kegs.collect {|keg| keg.path}
when '--cache' exec "find #{dirs.join' '} -not -type d -print"
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' when 'edit'
if ARGV.empty? if ARGV.empty?
exec "mate #{$formula} #{$root}/Library/Homebrew #{$root}/bin/brew #{$root}/README" exec "mate #{$formula} #{$root}/Library/Homebrew #{$root}/bin/brew #{$root}/README"
else else
exec "mate #{$formula}/#{ARGV.shift}.rb" paths=extract_kegs.collect {|keg| keg.formula_path.to_s.gsub ' ', '\\ '}
exec "mate #{paths.join ' '}"
end end
when 'install' when 'install'
shift_formulae_from_ARGV.each do |name| require 'formula'
beginning = Time.now extract_named_args.each do |name|
o=__obj(name) install Formula.create(name)
begin
raise "#{o.prefix} already exists!" if o.prefix.exist?
o.brew do
if ARGV.include? '--interactive'
ohai "Entering interactive mode, type `exit' to return to finalize installation"
puts "Install to this prefix: #{o.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
o.prefix.mkpath
o.install
%w[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 end
when 'ln' when 'ln', 'link'
n=0 n=0
shift_formulae_from_ARGV.each {|name| n+=ln name} (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" puts "Created #{n} links"
when 'rm', 'uninstall' when 'rm', 'uninstall'
shift_formulae_from_ARGV.each {|name| rm name} extract_kegs.each do |keg|
puts "Removing #{keg.name}..."
keg.rm
end
print "Pruning #{prefix}/..."
puts " #{prune} symbolic links pruned"
when 'mk' when 'prune'
require 'brewkit' puts "Pruned #{prune} symbolic links"
url=ARGV.shift
version=extract_version File.basename(url, Pathname.new(url).extname)
/(.*?)[-_.]?#{version}/.match File.basename(url) when 'mk', 'make'
raise "Couldn't parse name from #{url}" if $1.nil? or $1.empty? paths=ARGV.collect {|arg| mk arg}
if paths.empty?
path=$formula+($1.downcase+'.rb') raise "Invalid URL"
raise "#{path} already exists!" if File.exist? path elsif Kernel.system "which mate > /dev/null" and $? == 0
paths=paths.collect {|path| path.to_s.gsub " ", "\\ "}
f=File.new path, 'w' exec "mate #{paths.join ' '}"
f.puts "require 'brewkit'"
f.puts
f.puts "class #{__class $1} <Formula"
f.puts " @url='#{url}'"
f.puts " @homepage=''" # second because you fill in these two first
f.puts " @md5=''"
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 else
puts path puts paths.join("\n")
end end
when 'info','abv' when 'info', 'abv'
if ARGV.empty? if ARGV.empty?
puts abv puts abv
elsif ARGV[0][0..6] == 'http://'
puts Pathname.new(ARGV.shift).version
else else
if ARGV[0][0..6] == 'http://' #TODO show outdated status and that
require 'brewkit' keg=extract_kegs[0]
path=Pathname.new ARGV[0] frm=Formula.create keg.name
basename=File.basename path, path.extname puts "#{keg.name} #{keg.version}"
v=extract_version basename puts frm.homepage
puts v if keg.installed?
else puts "#{abv keg} (installed to #{keg.path})"
o=__obj shift_formulae_from_ARGV[0] end
puts "#{o.name} #{o.version}" if frm.caveats
puts o.homepage ohai 'Caveats'
if abv=abv(o.name) puts frm.caveats
ohai "Installation"
puts abv
end
if o.caveats
ohai 'Caveats'
puts o.caveats
end
end end
end end
else else
puts usage puts usage
end end