diff options
| author | Max Howell | 2009-08-10 16:48:30 +0100 |
|---|---|---|
| committer | Max Howell | 2009-08-10 18:11:17 +0100 |
| commit | 760c083c0c0c9934e4118b4669c8c8dfd0a3587d (patch) | |
| tree | 0ed76c2d20225ff1fe7e07490bc17a8932d60bab /Library | |
| parent | 5a396fd8b48835e826fe3193bd88e2274be60206 (diff) | |
| download | brew-760c083c0c0c9934e4118b4669c8c8dfd0a3587d.tar.bz2 | |
Refactor0.4
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.
Diffstat (limited to 'Library')
| -rw-r--r-- | Library/Contributions/brew_bash_completion.sh | 2 | ||||
| -rw-r--r-- | Library/Homebrew/brew.h.rb | 238 | ||||
| -rw-r--r-- | Library/Homebrew/env.rb | 25 | ||||
| -rw-r--r-- | Library/Homebrew/formula.rb | 315 | ||||
| -rw-r--r-- | Library/Homebrew/keg.rb | 163 | ||||
| -rw-r--r-- | Library/Homebrew/pathname+yeast.rb | 54 | ||||
| -rwxr-xr-x | Library/Homebrew/unittest.rb | 60 | ||||
| -rw-r--r-- | Library/Homebrew/utils.rb | 21 |
8 files changed, 566 insertions, 312 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 |
