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.
This commit is contained in:
Max Howell 2011-08-23 23:30:52 +01:00
parent 3a0cf31ed0
commit 19e387d92e
7 changed files with 297 additions and 223 deletions

View File

@ -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 }

View File

@ -5,7 +5,7 @@ Example usage:
brew search [foo] brew search [foo]
brew list [FORMULA...] brew list [FORMULA...]
brew update brew update
brew outdated brew upgrade [FORMULA...]
brew [info | home] [FORMULA...] brew [info | home] [FORMULA...]
Troubleshooting: Troubleshooting:

View File

@ -57,22 +57,26 @@ module Homebrew extend self
end end
end end
def install_formulae formulae def perform_preinstall_checks
formulae = [formulae].flatten.compact
return if formulae.empty?
check_ppc check_ppc
check_writable_install_location check_writable_install_location
check_cc check_cc
check_macports check_macports
end
formulae.each do |f| def install_formulae formulae
begin formulae = [formulae].flatten.compact
installer = FormulaInstaller.new f unless formulae.empty?
installer.ignore_deps = ARGV.include? '--ignore-dependencies' perform_preinstall_checks
installer.go formulae.each do |f|
rescue FormulaAlreadyInstalledError => e begin
opoo e.message fi = FormulaInstaller.new(f)
fi.install
fi.caveats
fi.finish
rescue FormulaAlreadyInstalledError => e
opoo e.message
end
end end
end end
end end

View File

@ -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

View File

@ -1,63 +1,96 @@
require 'exceptions' require 'exceptions'
require 'formula' require 'formula'
require 'keg'
require 'set' require 'set'
class FormulaInstaller class FormulaInstaller
attr :f
attr :show_summary_heading, true
attr :ignore_deps, true attr :ignore_deps, true
attr :install_bottle, true
attr :show_header, true
def initialize f def initialize ff
@f = f @f = ff
@show_header = true
@ignore_deps = ARGV.include? '--ignore-dependencies' || ARGV.interactive?
@install_bottle = ff.pourable? #TODO better
end end
# raises Homebrew::InstallationErrors in the event of install failures def install
def go raise FormulaAlreadyInstalledError, f if f.installed? and not ARGV.force?
if @f.installed? and not ARGV.force?
raise FormulaAlreadyInstalledError, @f
end
unless ignore_deps 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? unless needed_deps.empty?
puts "Also installing dependencies: "+needed_deps*", "
needed_deps.each do |dep| 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
end
begin # now show header as all the deps stuff has clouded the original issue
FormulaInstaller.check_external_deps @f show_header = true
rescue UnsatisfiedExternalDependencyError => e
onoe e.message
exit! 1
end end
end end
FormulaInstaller.install_formula @f
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 end
def self.check_external_deps f def caveats
[:ruby, :python, :perl, :jruby].each do |type| if f.caveats
f.external_deps[type].each do |dep| ohai "Caveats", f.caveats
unless quiet_system(*external_dep_check(dep, type)) @show_summary_heading = true
raise UnsatisfiedExternalDependencyError.new(dep, type) end
end if f.keg_only?
end if f.external_deps[type] ohai 'Caveats', f.keg_only_text
@show_summary_heading = true
else
check_PATH
check_manpages
check_infopages
check_jars
check_m4
end end
end end
def self.external_dep_check dep, type def finish
case type ohai 'Finishing up' if ARGV.verbose?
when :python then %W{/usr/bin/env python -c import\ #{dep}}
when :jruby then %W{/usr/bin/env jruby -rubygems -e require\ '#{dep}'} link unless f.keg_only?
when :ruby then %W{/usr/bin/env ruby -rubygems -e require\ '#{dep}'} fix_install_names
when :perl then %W{/usr/bin/env perl -e use\ #{dep}}
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
end 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 def build
@attempted ||= Set.new @start_time = Time.now
raise FormulaInstallationAlreadyAttemptedError, f if @attempted.include? f
@attempted << f
# 1. formulae can modify ENV, so we must ensure that each # 1. formulae can modify ENV, so we must ensure that each
# installation has a pristine ENV when it starts, forking now is # installation has a pristine ENV when it starts, forking now is
@ -93,4 +126,134 @@ class FormulaInstaller
raise "Suspicious installation failure" unless $?.success? raise "Suspicious installation failure" unless $?.success?
end end
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 end

View File

@ -1,35 +1,28 @@
#!/usr/bin/ruby #!/usr/bin/ruby
# 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
ORIGINAL_PATHS = ENV['PATH'].split(':').map{ |p| File.expand_path p }
require 'global' 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?}
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#{f.lib}
CPPFLAGS: -I#{f.include}
EOS
end
# 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 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 begin
raise $! if $! # an exception was already thrown when parsing the formula raise $! if $! # an exception was already thrown when parsing the formula
require 'extend/ENV' require 'extend/ENV'
require 'fileutils'
require 'hardware' require 'hardware'
require 'keg' require 'keg'
require 'compatibility'
ENV.extend(HomebrewEnvExtension) ENV.extend(HomebrewEnvExtension)
ENV.setup_build_environment 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)) install(Formula.factory($0))
rescue Exception => e rescue Exception => e
@ -46,15 +39,7 @@ at_exit do
end end
end end
ORIGINAL_PATHS = ENV['PATH'].split(':').map{ |p| File.expand_path p }
HOMEBREW_BIN = (HOMEBREW_PREFIX+'bin').to_s
def install f 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| f.deps.uniq.each do |dep|
dep = Formula.factory dep dep = Formula.factory dep
if dep.keg_only? if dep.keg_only?
@ -65,149 +50,43 @@ def install f
end end
end end
build_time = nil f.brew do
begin if ARGV.flag? '--interactive'
f.brew do ohai "Entering interactive mode"
if ARGV.flag? '--interactive' puts "Type `exit' to return and finalize the installation"
ohai "Entering interactive mode" puts "Install to this prefix: #{f.prefix}"
puts "Type `exit' to return and finalize the installation"
puts "Install to this prefix: #{f.prefix}"
if ARGV.flag? '--git' if ARGV.flag? '--git'
system "git init" system "git init"
system "git add -A" system "git add -A"
puts "This folder is now a git repo. Make your changes and then use:" puts "This folder is now a git repo. Make your changes and then use:"
puts " git diff | pbcopy" puts " git diff | pbcopy"
puts "to copy the diff to the clipboard." puts "to copy the diff to the clipboard."
end end
interactive_shell f interactive_shell f
nil nil
elsif ARGV.include? '--help' elsif ARGV.include? '--help'
system './configure --help' system './configure --help'
exit $? exit $?
else else
f.prefix.mkpath f.prefix.mkpath
beginning=Time.now f.install
f.install if not f.pourable? FORMULA_META_FILES.each do |filename|
FORMULA_META_FILES.each do |filename| next if File.directory? filename
next if File.directory? filename target_file = filename
target_file = filename target_file = "#{filename}.txt" if File.exists? "#{filename}.txt"
target_file = "#{filename}.txt" if File.exists? "#{filename}.txt" # Some software symlinks these files (see help2man.rb)
# Some software symlinks these files (see help2man.rb) target_file = Pathname.new(target_file).resolved_path
target_file = Pathname.new(target_file).resolved_path f.prefix.install target_file => filename rescue nil
f.prefix.install target_file => filename rescue nil (f.prefix+file).chmod 0644 rescue nil
(f.prefix+file).chmod 0644 rescue nil
end
build_time = Time.now-beginning if not f.pourable?
end end
end end
rescue Exception
if f.prefix.directory?
f.prefix.rmtree
f.prefix.parent.rmdir_if_possible
end
raise
end end
rescue Exception
if f.caveats if f.prefix.directory?
ohai "Caveats", f.caveats f.prefix.rmtree
show_summary_heading = true f.prefix.parent.rmdir_if_possible
end end
raise
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
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
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
end end

View File

@ -77,13 +77,13 @@ end
class ExternalDepsTests < Test::Unit::TestCase class ExternalDepsTests < Test::Unit::TestCase
def check_deps_fail f def check_deps_fail f
assert_raises(UnsatisfiedExternalDependencyError) do assert_raises(UnsatisfiedExternalDependencyError) do
FormulaInstaller.check_external_deps f.new f.new.check_external_deps
end end
end end
def check_deps_pass f def check_deps_pass f
assert_nothing_raised do assert_nothing_raised do
FormulaInstaller.check_external_deps f.new f.new.check_external_deps
end end
end end