From fb5aefd4a053d718637ea8690c6eeba2d6e56008 Mon Sep 17 00:00:00 2001 From: Max Howell Date: Fri, 24 Jul 2009 15:10:01 +0100 Subject: [PATCH] Refactor--object orientate where sensible --- Library/Homebrew/brewkit.rb | 371 +--------------------------------- Library/Homebrew/env.rb | 95 ++++++++- Library/Homebrew/formula.rb | 263 ++++++++++++++++++++++++ Library/Homebrew/keg.rb | 153 ++++++++++++++ Library/Homebrew/unittest.rb | 2 +- README | 33 +++- bin/brew | 374 ++++++++++++++--------------------- 7 files changed, 680 insertions(+), 611 deletions(-) create mode 100644 Library/Homebrew/formula.rb create mode 100644 Library/Homebrew/keg.rb diff --git a/Library/Homebrew/brewkit.rb b/Library/Homebrew/brewkit.rb index a32f015d5c..f5d819f1b0 100644 --- a/Library/Homebrew/brewkit.rb +++ b/Library/Homebrew/brewkit.rb @@ -15,9 +15,8 @@ # You should have received a copy of the GNU General Public License # along with Homebrew. If not, see . -require 'pathname' require 'osx/cocoa' # to get number of cores -require 'env' +require 'formula' # optimise all the way to eleven, references: # http://en.gentoo-wiki.com/wiki/Safe_Cflags/Intel @@ -38,343 +37,7 @@ unless $root.to_s == '/usr/local' end -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 - -def cache - cache=File.expand_path "~/Library/Caches/Homebrew" - FileUtils.mkpath cache - return cache -end - -class BuildError 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 &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 \ No newline at end of file diff --git a/Library/Homebrew/env.rb b/Library/Homebrew/env.rb index 951d9f9dc5..dbba19e8a7 100644 --- a/Library/Homebrew/env.rb +++ b/Library/Homebrew/env.rb @@ -21,4 +21,97 @@ $root=Pathname.new(__FILE__).dirname.parent.parent.realpath $formula=$root+'Library'+'Formula' $cellar=$root+'Cellar' -HOMEBREW_VERSION='0.2' \ No newline at end of file +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 diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb new file mode 100644 index 0000000000..443ab1de14 --- /dev/null +++ b/Library/Homebrew/formula.rb @@ -0,0 +1,263 @@ +# Copyright 2009 Max Howell +# +# 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 . + +require 'env' + +class BuildError 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 +# +# 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 . + +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 \ No newline at end of file diff --git a/Library/Homebrew/unittest.rb b/Library/Homebrew/unittest.rb index 84f0d3f818..7b15b29497 100755 --- a/Library/Homebrew/unittest.rb +++ b/Library/Homebrew/unittest.rb @@ -2,7 +2,7 @@ $:.unshift File.dirname(__FILE__) require 'test/unit' -require 'brewkit' +require 'formula' require 'stringio' class TestFormula 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 +def abv keg=nil + path=keg ? keg.path : $cellar + if path.directory? + `find #{path} -type f | wc -l`.strip+' files, '+`du -hd0 #{path} | cut -d"\t" -f1`.strip + else + nil end end -# symlinks a directory recursively into our FHS tree -def lnd keg, start - start=keg+start - return unless start.directory? +def install formula + require 'keg' - start.find do |from| - next if from == start + raise "#{formula.name} already installed!\n\t#{formula.prefix}" if formula.installed? + + beginning = Time.now - prune=false - relative_path=from.relative_path_from keg - to=$root+relative_path - - if from.directory? - # no need to put .app bundles in the path, the user can just use - # spotlight, or the open command and actual mac apps use an equivalent - Find.prune if from.extname.to_s == '.app' - - cmd=yield from.relative_path_from(start) - - if :skip == cmd - Find.prune - elsif :mkpath == cmd - to.mkpath - $n+=1 + 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 - symlink_relative_to from, to - Find.prune + 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 COPYRIGHT AUTHORS].each do |file| + formula.prefix.install file if File.file? file end - elsif from.file? - symlink_relative_to from, to 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} 1 + end puts "Created #{n} links" 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' - require 'brewkit' - url=ARGV.shift - version=extract_version File.basename(url, Pathname.new(url).extname) + when 'prune' + puts "Pruned #{prune} symbolic links" - /(.*?)[-_.]?#{version}/.match File.basename(url) - raise "Couldn't parse name from #{url}" if $1.nil? or $1.empty? - - path=$formula+($1.downcase+'.rb') - raise "#{path} already exists!" if File.exist? path - - f=File.new path, 'w' - f.puts "require 'brewkit'" - f.puts - f.puts "class #{__class $1} /dev/null" and $? == 0 - exec "mate #{path}" + when 'mk', 'make' + paths=ARGV.collect {|arg| mk arg} + if paths.empty? + raise "Invalid URL" + elsif Kernel.system "which mate > /dev/null" and $? == 0 + paths=paths.collect {|path| path.to_s.gsub " ", "\\ "} + exec "mate #{paths.join ' '}" else - puts path + puts paths.join("\n") end - when 'info','abv' + when 'info', 'abv' if ARGV.empty? puts abv + elsif ARGV[0][0..6] == 'http://' + puts Pathname.new(ARGV.shift).version else - if ARGV[0][0..6] == 'http://' - require 'brewkit' - path=Pathname.new ARGV[0] - basename=File.basename path, path.extname - v=extract_version basename - puts v - else - o=__obj shift_formulae_from_ARGV[0] - puts "#{o.name} #{o.version}" - puts o.homepage - if abv=abv(o.name) - ohai "Installation" - puts abv - end - if o.caveats - ohai 'Caveats' - puts o.caveats - end + #TODO show outdated status and that + keg=extract_kegs[0] + frm=Formula.create keg.name + puts "#{keg.name} #{keg.version}" + puts frm.homepage + if keg.installed? + puts "#{abv keg} (installed to #{keg.path})" + end + if frm.caveats + ohai 'Caveats' + puts frm.caveats end end - + else puts usage end