aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Library/Contributions/brew_bash_completion.sh2
-rw-r--r--Library/Homebrew/brew.h.rb238
-rw-r--r--Library/Homebrew/env.rb25
-rw-r--r--Library/Homebrew/formula.rb315
-rw-r--r--Library/Homebrew/keg.rb163
-rw-r--r--Library/Homebrew/pathname+yeast.rb54
-rwxr-xr-xLibrary/Homebrew/unittest.rb60
-rw-r--r--Library/Homebrew/utils.rb21
-rw-r--r--README5
-rwxr-xr-xbin/brew349
10 files changed, 659 insertions, 573 deletions
diff --git a/Library/Contributions/brew_bash_completion.sh b/Library/Contributions/brew_bash_completion.sh
index dd18faa03..f33204d36 100644
--- a/Library/Contributions/brew_bash_completion.sh
+++ b/Library/Contributions/brew_bash_completion.sh
@@ -35,7 +35,7 @@ _brew_to_completion()
;;
# 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/`
COMPREPLY=( $(compgen -W "${cellar_contents}" -- ${cur}) )
return 0
diff --git a/Library/Homebrew/brew.h.rb b/Library/Homebrew/brew.h.rb
new file mode 100644
index 000000000..3dee3ec2d
--- /dev/null
+++ b/Library/Homebrew/brew.h.rb
@@ -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
diff --git a/Library/Homebrew/env.rb b/Library/Homebrew/env.rb
deleted file mode 100644
index 3710aa793..000000000
--- a/Library/Homebrew/env.rb
+++ /dev/null
@@ -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'
diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index 4f02e2077..f01798eba 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -15,30 +15,31 @@
# You should have received a copy of the GNU General Public License
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
-require 'utils'
-class BuildError <RuntimeError
- def initialize cmd
- super "Build failed during: #{cmd}"
+class ExecutionError <RuntimeError
+ def initialize cmd, args=[]
+ super "#{cmd} #{args*' '}"
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'
+class BuildError <ExecutionError; end
-private
- class <<self
- attr_reader :url, :version, :md5, :url, :homepage, :sha1
+class FormulaUnavailableError <RuntimeError
+ def initialize name
+ super "No available formula for #{name}"
end
+end
-public
- attr_reader :url, :version, :url, :homepage, :name
-
- # reimplement if your package has dependencies
- def deps
+# 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.
+class AbstractFormula
+ def initialize noop=nil
+ @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 is nil" if @url.nil?
end
# if the dir is there, but it's empty we consider it not installed
@@ -48,61 +49,115 @@ public
return false
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
- raise "@name.nil!" if @name.nil?
- raise "@version.nil?" if @version.nil?
+ raise "Invalid @name" if @name.nil? or @name.empty?
+ raise "Invalid @version" if @version.nil? or @version.empty?
HOMEBREW_CELLAR+@name+@version
end
- def bin; prefix+'bin' end
- def doc; prefix+'share'+'doc'+name end
- def lib; prefix+'lib' end
- def man; prefix+'share'+'man' end
+ def path
+ Formula.path name
+ 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 info; prefix+'share'+'info' end
def include; prefix+'include' end
- def caveats
- nil
+ # tell the user about any caveats regarding this package
+ 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
-
+
+protected
# 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
+ def system cmd, *args
+ full="#{cmd} #{args*' '}".strip
+ ohai full
+ if ARGV.verbose?
+ safe_system cmd, *args
else
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?
out+=f.gets
end
end
- puts out unless $? == 0
+ unless $? == 0
+ puts out
+ raise
+ 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
- # we don't have a std_autotools variant because autotools is a lot less
- # consistent and the standard parameters are more memorable
- # really Homebrew should determine what works inside brew() then
- # we could add --disable-dependency-tracking when it will work
- def std_cmake_parameters
- # The None part makes cmake use the environment's CFLAGS etc. settings
- "-DCMAKE_INSTALL_PREFIX='#{prefix}' -DCMAKE_BUILD_TYPE=None"
+ # 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'
@@ -110,7 +165,7 @@ public
supplied=eval "@#{type.downcase}"
hash=eval("Digest::#{type}").hexdigest(fn.read)
- if supplied
+ if supplied and not supplied.empty?
raise "#{type} mismatch: #{hash}" unless supplied.upcase == hash.upcase
else
opoo "Cannot verify package integrity"
@@ -119,91 +174,69 @@ public
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
+ 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
-protected
- # returns the directory where the archive was uncompressed
- # in this Abstract case we assume there is no archive
- def uncompress path
- path.dirname
+# 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
-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
+ def self.class name
+ #remove invalid characters and camelcase
+ name.capitalize.gsub(/[-_\s]([a-zA-Z0-9])/) { $1.upcase }
+ 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
+ def self.factory name
+ require self.path(name)
+ return eval(self.class(name)).new(name)
+ rescue LoadError
+ raise FormulaUnavailableError.new(name)
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
+ def self.path name
+ HOMEBREW_PREFIX+'Library'+'Formula'+"#{name.downcase}.rb"
+ end
+
+ # we don't have a std_autotools variant because autotools is a lot less
+ # consistent and the standard parameters are more memorable
+ # really Homebrew should determine what works inside brew() then
+ # we could add --disable-dependency-tracking when it will work
+ def std_cmake_parameters
+ # The None part makes cmake use the environment's CFLAGS etc. settings
+ "-DCMAKE_INSTALL_PREFIX='#{prefix}' -DCMAKE_BUILD_TYPE=None"
end
private
- def uncompress(path)
- if path.extname == '.zip'
- `unzip -qq "#{path}"`
+ 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
- `tar xf "#{path}"`
+ %w[tar xf]
end
+ end
- raise "Compression tool failed" if $? != 0
+ def uncompress path
+ safe_system *uncompress_args<<path
entries=Dir['*']
- if entries.nil? or entries.length == 0
- raise "Empty tarball!"
+ if entries.length == 0
+ raise "Empty archive"
elsif entries.length == 1
# if one dir enter it as that will be where the build is
entries.first
@@ -211,30 +244,6 @@ private
# 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
- 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
def method_added method
@@ -243,16 +252,24 @@ class Formula <UnidentifiedFormula
end
# see ack.rb for an example usage
-# you need to set @version and @name
class ScriptFileFormula <AbstractFormula
+ def initialize name=nil
+ super
+ @name=name
+ end
+ def uncompress path
+ path.dirname
+ end
def install
- bin.install name
+ bin.install File.basename(@url)
end
end
+# see flac.rb for example usage
class GithubGistFormula <ScriptFileFormula
- def initialize
- super File.basename(self.class.url)
+ def initialize name=nil
+ super
+ @name=name
@version=File.basename(File.dirname(url))[0,6]
end
end
diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb
index d2db862af..69e11983d 100644
--- a/Library/Homebrew/keg.rb
+++ b/Library/Homebrew/keg.rb
@@ -14,146 +14,61 @@
#
# You should have received a copy of the GNU General Public License
# along with Homebrew. If not, see <http://www.gnu.org/licenses/>.
+#
+class Keg <Pathname
+ def initialize path
+ super path
+ raise "#{to_s} is not a valid keg" unless parent.parent == HOMEBREW_CELLAR
+ raise "#{to_s} is not a directory" unless directory?
+ end
-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
- 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
+ def uninstall
+ chmod_R 0777 # ensure we have permission to delete
+ rmtree
+ parent.rmdir_if_possible
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/
- 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}
+ def link
+ $n=0
+ $d=0
- # remove empty directories TODO Rubyize!
- `perl -MFile::Find -e"finddepth(sub{rmdir},'#{path}')"`
- end
+ mkpaths=(1..9).collect {|x| "man/man#{x}"} <<'man'<<'doc'<<'locale'<<'info'<<'aclocal'
- def rm
- # don't rmtree shit if we aren't positive about our location!
- raise "Bad stuff!" unless path.parent.parent == HOMEBREW_CELLAR
+ # 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
+ 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}
- if path.directory?
- FileUtils.chmod_R 0777, path # ensure we have permission to delete
- path.rmtree # HOMEBREW_CELLAR/foo/1.2.0
- path.parent.rmdir if path.parent.children.length == 0 # HOMEBREW_CELLAR/foo
- end
+ return $n+$d
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?
-
- root=Pathname.new HOMEBREW_PREFIX
- start.find do |from|
- next if from == start
+ # symlinks the contents of self+foo recursively into /usr/local/foo
+ def link_dir foo
+ root=self+foo
- prune=false
+ root.find do |src|
+ next if src == root
- relative_path=from.relative_path_from path
- to=root+relative_path
+ dst=HOMEBREW_PREFIX+src.relative_path_from(self)
+ dst.extend ObserverPathnameExtension
- if from.file?
- __symlink_relative_to from, to
- elsif from.directory?
+ if src.file?
+ dst.make_relative_symlink src
+ elsif src.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'
+ Find.prune if src.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
+ case yield src.relative_path_from(root)
+ when :skip then Find.prune
+ when :mkpath then dst.mkpath
+ else dst.make_relative_symlink src; 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 \ No newline at end of file
+end
diff --git a/Library/Homebrew/pathname+yeast.rb b/Library/Homebrew/pathname+yeast.rb
index 67a21cfc2..43c07c5ab 100644
--- a/Library/Homebrew/pathname+yeast.rb
+++ b/Library/Homebrew/pathname+yeast.rb
@@ -68,6 +68,24 @@ class Pathname
return File.basename(to_s, extname)
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
# eg. boost_1_39_0
/((\d+_)+\d+)$/.match stem
@@ -100,3 +118,39 @@ class Pathname
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
diff --git a/Library/Homebrew/unittest.rb b/Library/Homebrew/unittest.rb
index 878b05ab9..1bee7b3a6 100755
--- a/Library/Homebrew/unittest.rb
+++ b/Library/Homebrew/unittest.rb
@@ -1,9 +1,8 @@
#!/usr/bin/ruby
$:.unshift File.dirname(__FILE__)
+require 'pathname+yeast'
require 'formula'
require 'keg'
-require 'pathname+yeast'
-require 'stringio'
require 'utils'
# 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?
at_exit { HOMEBREW_PREFIX.parent.rmtree }
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
@@ -26,12 +26,16 @@ class MockFormula <Formula
end
end
+class MostlyAbstractFormula <AbstractFormula
+ @url=''
+end
+
class TestBall <Formula
def initialize
@url="file:///#{Pathname.new(__FILE__).parent.realpath}/testball-0.1.tbz"
super "testball"
end
-
+
def install
prefix.install "bin"
prefix.install "libexec"
@@ -51,16 +55,28 @@ class TestBallOverrideBrew <Formula
super "foo"
end
def brew
- puts "We can't override brew"
+ # We can't override brew
end
end
+class TestScriptFileFormula <ScriptFileFormula
+ @url="file:///#{Pathname.new(__FILE__).realpath}"
+ @version="1"
+
+ def initialize
+ super
+ @name='test-script-formula'
+ end
+end
def nostdout
- tmp=$stdout
+ require 'stringio'
+ tmpo=$stdout
+ tmpe=$stderr
$stdout=StringIO.new
yield
- $stdout=tmp
+ensure
+ $stdout=tmpo
end
@@ -162,9 +178,10 @@ class BeerTasting <Test::Unit::TestCase
def test_install
f=TestBall.new
+ assert_equal Formula.path(f.name), f.path
assert !f.installed?
- nostdout do
+ nostdout do
f.brew do
f.install
end
@@ -180,17 +197,29 @@ class BeerTasting <Test::Unit::TestCase
assert !(f.prefix+'main.c').exist?
assert f.installed?
- keg=Keg.new f
- keg.ln
+ keg=Keg.new f.prefix
+ keg.link
assert_equal 2, HOMEBREW_PREFIX.children.length
assert (HOMEBREW_PREFIX+'bin').directory?
assert_equal 3, (HOMEBREW_PREFIX+'bin').children.length
- keg.rm
- assert !keg.path.exist?
+ keg.uninstall
+ assert !keg.exist?
assert !f.installed?
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
assert_nothing_raised { nostdout { TestBallValidMd5.new.brew {} } }
end
@@ -213,10 +242,17 @@ class BeerTasting <Test::Unit::TestCase
path.dirname.mkpath
`echo "require 'brewkit'; class #{classname} <Formula; @url=''; end" > #{path}`
- assert_not_nil Formula.create(FOOBAR)
+ assert_not_nil Formula.factory(FOOBAR)
end
def test_cant_override_brew
assert_raises(RuntimeError) { TestBallOverrideBrew.new }
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
diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb
index 112c6b6b6..bb64c3851 100644
--- a/Library/Homebrew/utils.rb
+++ b/Library/Homebrew/utils.rb
@@ -22,5 +22,24 @@ end
# shows a warning in delicious pink
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
diff --git a/README b/README
index a79b4f17f..e981eda09 100644
--- a/README
+++ b/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
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
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.
diff --git a/bin/brew b/bin/brew
index 6af4460ac..d4162986a 100755
--- a/bin/brew
+++ b/bin/brew
@@ -1,306 +1,135 @@
#!/usr/bin/ruby
-$:.unshift __FILE__+'/../../Library/Homebrew'
-require 'env'
-require 'find'
-
-PRISTINE_ARGV=ARGV.dup
+$:.unshift ENV['RUBYLIB']=File.expand_path(__FILE__+'/../../Library/Homebrew')
+
+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)"
-# often causes Ruby to throw exception ffs
-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
+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']
-######################################################################## 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 '--prefix' then puts prefix
+ when '--prefix' then puts HOMEBREW_PREFIX
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 'macports' then exec "open 'http://www.macports.org/ports.php?by=name&substr=#{ARGV.shift}'"
when 'home', 'homepage'
- require 'formula'
- homepages=extract_named_args.collect {|name| Formula.create(name).homepage}
- exec "open #{homepages.join' '}"
+ if ARGV.named.empty?
+ exec "open", HOMEBREW_WWW
+ else
+ exec "open", *ARGV.formulae.collect {|f| f.homepage}
+ end
when 'ls', 'list'
- dirs=extract_kegs.collect {|keg| keg.path}
- exec "find #{dirs.join' '} -not -type d -print"
+ exec "find", *ARGV.kegs.concat(%w[-not -type d -print])
when 'edit'
if ARGV.empty?
- d=HOMEBREW_PREFIX
- exec "mate #{Dir["#{d}/Library/*"].join' '} #{d}/bin/brew #{d}/README"
+ exec "mate", *Dir["#{HOMEBREW_PREFIX}/Library/*"]<<
+ "#{HOMEBREW_PREFIX}/bin/brew"<<
+ "#{HOMEBREW_PREFIX}/README"
else
- require 'formula'
- paths=extract_named_args.collect {|name| Formula.path(name).to_s.gsub ' ', '\\ '}
- exec "mate #{paths.join ' '}"
+ exec "mate", *ARGV.formulae.collect {|f| f.path}
end
when 'install'
- require 'formula'
- extract_named_args.each do |name|
- f=Formula.create(name)
- raise "#{f.name} already installed!\n==> #{f.prefix}" if f.installed? unless ARGV.include? '--force'
- install f
+ require 'keg'
+ ARGV.formulae.each do |f|
+ raise "#{f.name} is already installed" if f.installed? unless ARGV.force?
+ start_time=Time.now
+ 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
when 'ln', 'link'
- n=0
- (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"
+ ARGV.kegs.each {|keg| puts "#{keg.link} links created for #{keg}"}
+
+ when 'unlink'
+ ARGV.kegs.each {|keg| puts "#{keg.unlink} links removed for #{keg}"}
- when 'rm', 'uninstall'
- extract_kegs.each do |keg|
- puts "Removing #{keg.name}..."
- keg.rm
+ when 'rm', 'uninstall', 'remove'
+ ARGV.kegs.each do |keg|
+ puts "Uninstalling #{keg}..."
+ keg.uninstall
end
- print "Pruning #{prefix}/..."
- puts " #{prune} symbolic links pruned"
+ prune
+
+ when 'up', 'update'
+ puts "Reserved command"
when 'prune'
- puts "Pruned #{prune} symbolic links"
+ prune
when 'mk', 'make'
- mode = "make"
- if ARGV.length > 0
- 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 ' '}"
+ if ARGV.include? '--macports'
+ exec "open", "http://www.macports.org/ports.php?by=name&substr=#{ARGV.next}"
else
- puts paths.join("\n")
+ exec "mate", *ARGV.named.collect {|name| make name}
end
when 'info', 'abv'
- if ARGV.empty?
- puts `ls #{HOMEBREW_CELLAR} | wc -l`.strip+" kegs, "+abv
+ if ARGV.named.empty?
+ puts `ls #{HOMEBREW_CELLAR} | wc -l`.strip+" kegs, "+HOMEBREW_CELLAR.abv
elsif ARGV[0][0..6] == 'http://'
puts Pathname.new(ARGV.shift).version
else
- require 'formula'
- #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
+ ARGV.named.each {|name| info name}
end
else
- puts usage
+ puts ARGV.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
+rescue SystemExit
+ ohai "Kernel.exit" if ARGV.verbose?
+rescue Interrupt => e
+ puts # seemingly a newline is typical
+ exit 130
+rescue SystemCallError, RuntimeError => e
+ if ARGV.verbose? or ARGV.debug?
+ onoe e.inspect
+ puts e.backtrace
else
- raise
+ onoe e
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