diff options
| author | Max Howell | 2011-08-23 23:30:52 +0100 |
|---|---|---|
| committer | Max Howell | 2011-08-24 22:30:43 +0100 |
| commit | 81f1fb38a9f754319a75fcd715435f6f6258bc60 (patch) | |
| tree | dea2acd1d00a32b6da4ae0040e0982484dbd7159 | |
| parent | 3fa883f1503a69a9a4fa44de2ef061bb506aab88 (diff) | |
| download | homebrew-81f1fb38a9f754319a75fcd715435f6f6258bc60.tar.bz2 | |
`brew upgrade`
Consequence: you can no longer install when something is already installed, you must upgrade it. This doesn't apply if the formula in question was unlinked. You can still --force installs though.
Rationale: the old way of installing over the top would leave symlinks to multiple versions in /usr/local if the old version had a file the newer version didn't. The new upgrade command handles everything properly.
| -rwxr-xr-x | Library/Contributions/examples/brew-upgrade.rb | 7 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/help.rb | 2 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/install.rb | 26 | ||||
| -rw-r--r-- | Library/Homebrew/cmd/upgrade.rb | 35 | ||||
| -rw-r--r-- | Library/Homebrew/formula_installer.rb | 235 | ||||
| -rwxr-xr-x | Library/Homebrew/install.rb | 207 | ||||
| -rw-r--r-- | Library/Homebrew/test/test_external_deps.rb | 4 |
7 files changed, 295 insertions, 221 deletions
diff --git a/Library/Contributions/examples/brew-upgrade.rb b/Library/Contributions/examples/brew-upgrade.rb deleted file mode 100755 index 3ffb570ac..000000000 --- a/Library/Contributions/examples/brew-upgrade.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Updates all outdated brews -# See: http://github.com/mxcl/homebrew/issues/issue/1324 - -require 'cmd/outdated' -require 'cmd/install' - -Homebrew.install_formulae Homebrew.outdated_brews.map{ |_keg, name, _version| Formula.factory name } diff --git a/Library/Homebrew/cmd/help.rb b/Library/Homebrew/cmd/help.rb index 3e7cc75b9..2d55d0427 100644 --- a/Library/Homebrew/cmd/help.rb +++ b/Library/Homebrew/cmd/help.rb @@ -5,7 +5,7 @@ Example usage: brew search [foo] brew list [FORMULA...] brew update - brew outdated + brew upgrade [FORMULA...] brew [info | home] [FORMULA...] Troubleshooting: diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index b7f9e7cae..4b2711994 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -57,22 +57,26 @@ module Homebrew extend self end end - def install_formulae formulae - formulae = [formulae].flatten.compact - return if formulae.empty? - + def perform_preinstall_checks check_ppc check_writable_install_location check_cc check_macports + end - formulae.each do |f| - begin - installer = FormulaInstaller.new f - installer.ignore_deps = ARGV.include? '--ignore-dependencies' - installer.go - rescue FormulaAlreadyInstalledError => e - opoo e.message + def install_formulae formulae + formulae = [formulae].flatten.compact + unless formulae.empty? + perform_preinstall_checks + formulae.each do |f| + begin + fi = FormulaInstaller.new(f) + fi.install + fi.caveats + fi.finish + rescue FormulaAlreadyInstalledError => e + opoo e.message + end end end end diff --git a/Library/Homebrew/cmd/upgrade.rb b/Library/Homebrew/cmd/upgrade.rb new file mode 100644 index 000000000..f86e8a871 --- /dev/null +++ b/Library/Homebrew/cmd/upgrade.rb @@ -0,0 +1,35 @@ +require 'cmd/outdated' +require 'cmd/install' + +class Fixnum + def plural_s + if self > 1 then "s" else "" end + end +end + +module Homebrew extend self + def upgrade + Homebrew.perform_preinstall_checks + + outdated = if ARGV.named.empty? + Homebrew.outdated_brews + else + ARGV.formulae.map{ |f| [f.prefix.parent, f.name, f.version] } + end + + if outdated.count > 1 + oh1 "Upgrading #{outdated.count} outdated package#{outdated.count.plural_s}, with result:" + puts outdated.map{ |_, name, version| "#{name} #{version}" } * ", " + end + + outdated.each do |rack, name, version| + installer = FormulaInstaller.new(Formula.factory(name)) + installer.show_header = false + oh1 "Upgrading #{name}" + installer.install + Keg.new("#{rack}/#{version}").unlink + installer.caveats + installer.finish # includes link step + end + end +end diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb index 57f93fdee..5c2e61590 100644 --- a/Library/Homebrew/formula_installer.rb +++ b/Library/Homebrew/formula_installer.rb @@ -1,63 +1,96 @@ require 'exceptions' require 'formula' +require 'keg' require 'set' class FormulaInstaller + attr :f + attr :show_summary_heading, true attr :ignore_deps, true + attr :install_bottle, true + attr :show_header, true - def initialize f - @f = f + def initialize ff + @f = ff + @show_header = true + @ignore_deps = ARGV.include? '--ignore-dependencies' || ARGV.interactive? + @install_bottle = ff.pourable? #TODO better end - # raises Homebrew::InstallationErrors in the event of install failures - def go - if @f.installed? and not ARGV.force? - raise FormulaAlreadyInstalledError, @f - end + def install + raise FormulaAlreadyInstalledError, f if f.installed? and not ARGV.force? unless ignore_deps - needed_deps = @f.recursive_deps.reject {|d| d.installed?} + f.check_external_deps + + needed_deps = f.recursive_deps.reject{ |d| d.installed? } unless needed_deps.empty? - puts "Also installing dependencies: "+needed_deps*", " needed_deps.each do |dep| - FormulaInstaller.install_formula dep + fi = FormulaInstaller.new(dep) + fi.ignore_deps = true + fi.show_header = false + oh1 "Installing #{f} dependency: #{dep}" + fi.install + fi.caveats + fi.finish end - end - begin - FormulaInstaller.check_external_deps @f - rescue UnsatisfiedExternalDependencyError => e - onoe e.message - exit! 1 + + # now show header as all the deps stuff has clouded the original issue + show_header = true end end - FormulaInstaller.install_formula @f - end - def self.check_external_deps f - [:ruby, :python, :perl, :jruby].each do |type| - f.external_deps[type].each do |dep| - unless quiet_system(*external_dep_check(dep, type)) - raise UnsatisfiedExternalDependencyError.new(dep, type) - end - end if f.external_deps[type] + oh1 "Installing #{f}" if show_header + + @@attempted ||= Set.new + raise FormulaInstallationAlreadyAttemptedError, f if @@attempted.include? f + @@attempted << f + + if install_bottle + pour + else + build + clean end + + raise "Nothing was installed to #{f.prefix}" unless f.installed? end - def self.external_dep_check dep, type - case type - when :python then %W{/usr/bin/env python -c import\ #{dep}} - when :jruby then %W{/usr/bin/env jruby -rubygems -e require\ '#{dep}'} - when :ruby then %W{/usr/bin/env ruby -rubygems -e require\ '#{dep}'} - when :perl then %W{/usr/bin/env perl -e use\ #{dep}} + def caveats + if f.caveats + ohai "Caveats", f.caveats + @show_summary_heading = true end + if f.keg_only? + ohai 'Caveats', f.keg_only_text + @show_summary_heading = true + else + check_PATH + check_manpages + check_infopages + check_jars + check_m4 + end + end + + def finish + ohai 'Finishing up' if ARGV.verbose? + + link unless f.keg_only? + fix_install_names + + ohai "Summary" if ARGV.verbose? or show_summary_heading + print "#{f.prefix}: #{f.prefix.abv}" + print ", built in #{pretty_duration build_time}" if build_time + puts end - private + def build_time + @build_time ||= Time.now - @start_time unless install_bottle or ARGV.interactive? or @start_time.nil? + end - def self.install_formula f - @attempted ||= Set.new - raise FormulaInstallationAlreadyAttemptedError, f if @attempted.include? f - @attempted << f + def build + @start_time = Time.now # 1. formulae can modify ENV, so we must ensure that each # installation has a pristine ENV when it starts, forking now is @@ -93,4 +126,134 @@ class FormulaInstaller raise "Suspicious installation failure" unless $?.success? end end + + def link + Keg.new(f.prefix).link + rescue Exception => e + onoe "The linking step did not complete successfully" + puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}" + puts "You can try again using `brew link #{f.name}'" + ohai e, e.backtrace if ARGV.debug? + @show_summary_heading = true + end + + def fix_install_names + Keg.new(f.prefix).fix_install_names + rescue Exception => e + onoe "Failed to fix install names" + puts "The formula built, but you may encounter issues using it or linking other" + puts "formula against it." + ohai e, e.backtrace if ARGV.debug? + @show_summary_heading = true + end + + def clean + require 'cleaner' + Cleaner.new f if not f.pourable? + rescue Exception => e + opoo "The cleaning step did not complete successfully" + puts "Still, the installation was successful, so we will link it into your prefix" + ohai e, e.backtrace if ARGV.debug? + @show_summary_heading = true + end + + def paths + @paths ||= ENV['PATH'].split(':').map{ |p| File.expand_path p } + end + + def check_PATH + # warn the user if stuff was installed outside of their PATH + [f.bin, f.sbin].each do |bin| + if bin.directory? and bin.children.count > 0 + bin = (HOMEBREW_PREFIX/bin.basename).realpath.to_s + unless paths.include? bin + opoo "#{bin} is not in your PATH" + puts "You can amend this by altering your ~/.bashrc file" + @show_summary_heading = true + end + end + end + end + + def check_manpages + # Check for man pages that aren't in share/man + if (f.prefix+'man').exist? + opoo 'A top-level "man" folder was found.' + puts "Homebrew requires that man pages live under share." + puts 'This can often be fixed by passing "--mandir=#{man}" to configure.' + @show_summary_heading = true + end + end + + def check_infopages + # Check for info pages that aren't in share/info + if (f.prefix+'info').exist? + opoo 'A top-level "info" folder was found.' + puts "Homebrew suggests that info pages live under share." + puts 'This can often be fixed by passing "--infodir=#{info}" to configure.' + @show_summary_heading = true + end + end + + def check_jars + # Check for Jars in lib + if File.exist?(f.lib) + unless f.lib.children.select{|g| g.to_s =~ /\.jar$/}.empty? + opoo 'JARs were installed to "lib".' + puts "Installing JARs to \"lib\" can cause conflicts between packages." + puts "For Java software, it is typically better for the formula to" + puts "install to \"libexec\" and then symlink or wrap binaries into \"bin\"." + puts "See \"activemq\", \"jruby\", etc. for examples." + @show_summary_heading = true + end + end + end + + def check_m4 + # Check for m4 files + if Dir[f.share+"aclocal/*.m4"].length > 0 + opoo 'm4 macros were installed to "share/aclocal".' + puts "Homebrew does not append \"#{HOMEBREW_PREFIX}/share/aclocal\"" + puts "to \"/usr/share/aclocal/dirlist\". If an autoconf script you use" + puts "requires these m4 macros, you'll need to add this path manually." + @show_summary_heading = true + end + end +end + + +def external_dep_check dep, type + case type + when :python then %W{/usr/bin/env python -c import\ #{dep}} + when :jruby then %W{/usr/bin/env jruby -rubygems -e require\ '#{dep}'} + when :ruby then %W{/usr/bin/env ruby -rubygems -e require\ '#{dep}'} + when :perl then %W{/usr/bin/env perl -e use\ #{dep}} + end +end + + +class Formula + def keg_only_text; <<-EOS.undent + This formula is keg-only, so it was not symlinked into #{HOMEBREW_PREFIX}. + + #{self.keg_only?} + + Generally there are no consequences of this for you. + If you build your own software and it requires this formula, you'll need + to add its lib & include paths to your build variables: + + LDFLAGS -L#{lib} + CPPFLAGS -I#{include} + EOS + end + + def check_external_deps + [:ruby, :python, :perl, :jruby].each do |type| + self.external_deps[type].each do |dep| + unless quiet_system(*external_dep_check(dep, type)) + raise UnsatisfiedExternalDependencyError.new(dep, type) + end + end if self.external_deps[type] + end + end end diff --git a/Library/Homebrew/install.rb b/Library/Homebrew/install.rb index e92e80e08..9844ef401 100755 --- a/Library/Homebrew/install.rb +++ b/Library/Homebrew/install.rb @@ -1,35 +1,28 @@ #!/usr/bin/ruby -require 'global' - -def text_for_keg_only_formula f - <<-EOS -This formula is keg-only, so it was not symlinked into #{HOMEBREW_PREFIX}. -#{f.keg_only?} +# This script is called by formula_installer as a separate instance. +# Rationale: Formula can use __END__, Formula can change ENV +# Thrown exceptions are propogated back to the parent process over a pipe -Generally there are no consequences of this for you. -If you build your own software and it requires this formula, you'll need -to add its lib & include paths to your build variables: +ORIGINAL_PATHS = ENV['PATH'].split(':').map{ |p| File.expand_path p } - LDFLAGS: -L#{f.lib} - CPPFLAGS: -I#{f.include} - EOS -end +require 'global' -# I like this little at all, but see no alternative seeing as the formula -# rb file has to be the running script to allow it to use __END__ and DATA at_exit do + # the whole of everything must be run in at_exit because the formula has to + # be the run script as __END__ must work for *that* formula. + begin raise $! if $! # an exception was already thrown when parsing the formula require 'extend/ENV' - require 'fileutils' require 'hardware' require 'keg' - require 'compatibility' ENV.extend(HomebrewEnvExtension) ENV.setup_build_environment + # we must do this or tools like pkg-config won't get found by configure scripts etc. + ENV.prepend 'PATH', "#{HOMEBREW_PREFIX}/bin", ':' unless ORIGINAL_PATHS.include? "#{HOMEBREW_PREFIX}/bin" install(Formula.factory($0)) rescue Exception => e @@ -46,15 +39,7 @@ at_exit do end end -ORIGINAL_PATHS = ENV['PATH'].split(':').map{ |p| File.expand_path p } -HOMEBREW_BIN = (HOMEBREW_PREFIX+'bin').to_s - def install f - show_summary_heading = false - - # we must do this or tools like pkg-config won't get found by configure scripts etc. - ENV.prepend 'PATH', HOMEBREW_BIN, ':' unless ORIGINAL_PATHS.include? HOMEBREW_BIN - f.deps.uniq.each do |dep| dep = Formula.factory dep if dep.keg_only? @@ -65,149 +50,43 @@ def install f end end - build_time = nil - begin - 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}" - - if ARGV.flag? '--git' - system "git init" - system "git add -A" - puts "This folder is now a git repo. Make your changes and then use:" - puts " git diff | pbcopy" - puts "to copy the diff to the clipboard." - end - - interactive_shell f - nil - elsif ARGV.include? '--help' - system './configure --help' - exit $? - else - f.prefix.mkpath - beginning=Time.now - f.install if not f.pourable? - FORMULA_META_FILES.each do |filename| - next if File.directory? filename - target_file = filename - target_file = "#{filename}.txt" if File.exists? "#{filename}.txt" - # Some software symlinks these files (see help2man.rb) - target_file = Pathname.new(target_file).resolved_path - f.prefix.install target_file => filename rescue nil - (f.prefix+file).chmod 0644 rescue nil - end - build_time = Time.now-beginning if not f.pourable? + 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}" + + if ARGV.flag? '--git' + system "git init" + system "git add -A" + puts "This folder is now a git repo. Make your changes and then use:" + puts " git diff | pbcopy" + puts "to copy the diff to the clipboard." end - end - rescue Exception - if f.prefix.directory? - f.prefix.rmtree - f.prefix.parent.rmdir_if_possible - end - raise - end - if f.caveats - ohai "Caveats", f.caveats - show_summary_heading = true - end - - ohai 'Finishing up' if ARGV.verbose? - - keg = Keg.new f.prefix - - begin - require 'cleaner' - Cleaner.new f if not f.pourable? - rescue Exception => e - opoo "The cleaning step did not complete successfully" - puts "Still, the installation was successful, so we will link it into your prefix" - ohai e, e.backtrace if ARGV.debug? - show_summary_heading = true - end - - raise "Nothing was installed to #{f.prefix}" unless f.installed? - - if f.keg_only? - ohai 'Caveats', text_for_keg_only_formula(f) - show_summary_heading = true - else - # warn the user if stuff was installed outside of their PATH - [f.bin, f.sbin].each do |bin| - if bin.directory? - bin = File.expand_path bin - unless ORIGINAL_PATHS.include? HOMEBREW_BIN - opoo "#{HOMEBREW_BIN} is not in your PATH" - puts "You can amend this by altering your ~/.bashrc file" - show_summary_heading = true - end - end - end - - # Check for man pages that aren't in share/man - if (f.prefix+'man').exist? - opoo 'A top-level "man" folder was found.' - puts "Homebrew requires that man pages live under share." - puts 'This can often be fixed by passing "--mandir=#{man}" to configure.' - show_summary_heading = true - end - - # Check for info pages that aren't in share/info - if (f.prefix+'info').exist? - opoo 'A top-level "info" folder was found.' - puts "Homebrew suggests that info pages live under share." - puts 'This can often be fixed by passing "--infodir=#{info}" to configure.' - show_summary_heading = true - end - - # Check for Jars in lib - if File.exist?(f.lib) - unless f.lib.children.select{|g| g.to_s =~ /\.jar$/}.empty? - opoo 'JARs were installed to "lib".' - puts "Installing JARs to \"lib\" can cause conflicts between packages." - puts "For Java software, it is typically better for the formula to" - puts "install to \"libexec\" and then symlink or wrap binaries into \"bin\"." - puts "See \"activemq\", \"jruby\", etc. for examples." - show_summary_heading = true + interactive_shell f + nil + elsif ARGV.include? '--help' + system './configure --help' + exit $? + else + f.prefix.mkpath + f.install + FORMULA_META_FILES.each do |filename| + next if File.directory? filename + target_file = filename + target_file = "#{filename}.txt" if File.exists? "#{filename}.txt" + # Some software symlinks these files (see help2man.rb) + target_file = Pathname.new(target_file).resolved_path + f.prefix.install target_file => filename rescue nil + (f.prefix+file).chmod 0644 rescue nil end end - - # Check for m4 files - if Dir[f.share+"aclocal/*.m4"].length > 0 - opoo 'm4 macros were installed to "share/aclocal".' - puts "Homebrew does not append \"#{HOMEBREW_PREFIX}/share/aclocal\"" - puts "to \"/usr/share/aclocal/dirlist\". If an autoconf script you use" - puts "requires these m4 macros, you'll need to add this path manually." - show_summary_heading = true - end - - # link from Cellar to Prefix - begin - keg.link - rescue Exception => e - onoe "The linking step did not complete successfully" - puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}" - puts "You can try again using `brew link #{f.name}'" - ohai e, e.backtrace if ARGV.debug? - show_summary_heading = true - end end - - begin - keg.fix_install_names - rescue Exception => e - onoe "Failed to fix install names" - puts "The formula built, but you may encounter issues using it or linking other" - puts "formula against it." - ohai e, e.backtrace if ARGV.debug? - show_summary_heading = true +rescue Exception + if f.prefix.directory? + f.prefix.rmtree + f.prefix.parent.rmdir_if_possible end - - ohai "Summary" if ARGV.verbose? or show_summary_heading - print "#{f.prefix}: #{f.prefix.abv}" - print ", built in #{pretty_duration build_time}" if build_time - puts + raise end diff --git a/Library/Homebrew/test/test_external_deps.rb b/Library/Homebrew/test/test_external_deps.rb index 30b3540d9..052617712 100644 --- a/Library/Homebrew/test/test_external_deps.rb +++ b/Library/Homebrew/test/test_external_deps.rb @@ -77,13 +77,13 @@ end class ExternalDepsTests < Test::Unit::TestCase def check_deps_fail f assert_raises(UnsatisfiedExternalDependencyError) do - FormulaInstaller.check_external_deps f.new + f.new.check_external_deps end end def check_deps_pass f assert_nothing_raised do - FormulaInstaller.check_external_deps f.new + f.new.check_external_deps end end |
