diff options
| author | Max Howell | 2010-09-11 20:22:54 +0100 | 
|---|---|---|
| committer | Adam Vandenberg | 2011-03-12 11:55:02 -0800 | 
| commit | 9afc85ad338f9e053fe36127937d308ad62593bc (patch) | |
| tree | 610d3c0ae869d9e58e283f065c914566316ddd8c | |
| parent | f57463eee6333f8dbeb42a122290677d6c755871 (diff) | |
| download | homebrew-9afc85ad338f9e053fe36127937d308ad62593bc.tar.bz2 | |
Refactor the brew command into one file per command
The code was sucking. To the extent that maintenance was hard. It's a lot
easier to work with code that is sensibly split at sensible boundaries. So
now it is more like that.
But the refactor is minimal. Because we don't want you to have more merge
hell than absolutely necessary.
If you merge you will need to pay attention to brew.h.rb (as it is deleted)
and bin/brew (as command logic is gone). It will be painful, but you will just
have to help git out by moving any changes around manually.
Note compatibility.rb. It ensures that any function renames or removals don't
break anything. We're pretty serious about backwards compatibility. And that's
because we encourage you to hack around with the innards. And we couldn't do
that if we would then just make stuff disappear behind your back.
48 files changed, 1284 insertions, 1238 deletions
| diff --git a/Library/Contributions/examples/brew-missing.rb b/Library/Contributions/examples/brew-missing.rb index 68206278c..701bb1ca2 100755 --- a/Library/Contributions/examples/brew-missing.rb +++ b/Library/Contributions/examples/brew-missing.rb @@ -1,21 +1,18 @@  require "formula" -require 'formula_installer' +require "cmd/outdated"  def main    # Names of outdated brews; they count as installed. -  outdated = outdated_brews.collect {|b| b[1]} +  outdated = Homebrew.outdated_brews.collect{ |b| b[1] } -  HOMEBREW_CELLAR.subdirs.each do |keg| -    next unless keg.subdirs -    if ((f = Formula.factory(keg.basename.to_s)).installed? rescue false) -      f_deps = FormulaInstaller.expand_deps(f).collect{|g| g.name}.uniq -      next if f_deps.empty? - -      missing_deps = f_deps.reject do |dep_name| +  HOMEBREW_CELLAR.subdirs.each do |rack| +    f = Formula.factory rack.basename.to_s rescue nil +    if f and f.installed? +      missing_deps = f.recursive_deps.map{ |g| g.name }.uniq.reject do |dep_name|          Formula.factory(dep_name).installed? or outdated.include?(dep_name)        end -      puts "#{f.name}: #{missing_deps.join(', ')}" unless missing_deps.empty? +      puts "#{f.name}: #{missing_deps * ', '}" unless missing_deps.empty?      end    end  end diff --git a/Library/Contributions/examples/brew-server b/Library/Contributions/examples/brew-server index b9919cf7a..5e7f80b58 100755 --- a/Library/Contributions/examples/brew-server +++ b/Library/Contributions/examples/brew-server @@ -82,7 +82,6 @@ get '/' do  end  get '/search' do -  require 'brew.h'    q = params['q']    results = search_brews(q) diff --git a/Library/Contributions/examples/brew-upgrade.rb b/Library/Contributions/examples/brew-upgrade.rb index 6c7831fde..3ffb570ac 100755 --- a/Library/Contributions/examples/brew-upgrade.rb +++ b/Library/Contributions/examples/brew-upgrade.rb @@ -1,11 +1,7 @@  # Updates all outdated brews  # See: http://github.com/mxcl/homebrew/issues/issue/1324 -# patch ARGV to use all of the outdated packages as the names passed in -module HomebrewArgvExtension -  def formulae -    @formulae = outdated_brews.map {|_keg, name, _version| Formula.factory name} -  end -end +require 'cmd/outdated' +require 'cmd/install' -brew_install +Homebrew.install_formulae Homebrew.outdated_brews.map{ |_keg, name, _version| Formula.factory name } diff --git a/Library/Homebrew/beer_events.rb b/Library/Homebrew/beer_events.rb deleted file mode 100644 index 18d0d0a6c..000000000 --- a/Library/Homebrew/beer_events.rb +++ /dev/null @@ -1,199 +0,0 @@ -# Vendored from Rucola: http://github.com/alloy/rucola/tree/master -# -# Copyright (c) 2007, 2008, 2009 Eloy Duran <eloy.de.enige@gmail.com> -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation -# files (the "Software"), to deal in the Software without -# restriction, including without limitation the rights to use, -# copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following -# conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -begin -  require 'osx/cocoa' -  OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework' -   -  module Rucola -    class FSEvents -      class FSEvent -        attr_reader :fsevents_object -        attr_reader :id -        attr_reader :path -        def initialize(fsevents_object, id, path) -          @fsevents_object, @id, @path = fsevents_object, id, path -        end -         -        # Returns an array of the files/dirs in the path that the event occurred in. -        # The files are sorted by the modification time, the first entry is the last modified file. -        def files -          Dir.glob("#{File.expand_path(path)}/*").sort_by {|f| File.mtime(f) }.reverse -        end -         -        # Returns the last modified file in the path that the event occurred in. -        def last_modified_file -          files.first -        end -      end -       -      class StreamError < StandardError; end -       -      attr_reader :paths -      attr_reader :stream -       -      attr_accessor :allocator -      attr_accessor :context -      attr_accessor :since -      attr_accessor :latency -      attr_accessor :flags -       -      # Initializes a new FSEvents `watchdog` object and starts watching the directories you specify for events. The -      # block is used as a handler for events, which are passed as the block's argument. This method is the easiest -      # way to start watching some directories if you don't care about the details of setting up the event stream. -      # -      #   Rucola::FSEvents.start_watching('/tmp') do |events| -      #     events.each { |event| log.debug("#{event.files.inspect} were changed.") } -      #   end -      #    -      #   Rucola::FSEvents.start_watching('/var/log/system.log', '/var/log/secure.log', :since => last_id, :latency => 5) do -      #     Growl.notify("Something was added to your log files!") -      #   end -      # -      # Note that the method also returns the FSEvents object. This enables you to control the event stream if you want to. -      # -      #   fsevents = Rucola::FSEvents.start_watching('/Volumes') do |events| -      #     events.each { |event| Growl.notify("Volume changes: #{event.files.to_sentence}") } -      #   end -      #   fsevents.stop -      def self.start_watching(*params, &block) -        fsevents = new(*params, &block) -        fsevents.create_stream -        fsevents.start -        fsevents -      end -       -      # Creates a new FSEvents `watchdog` object. You can specify a list of paths to watch and options to control the -      # behaviour of the watchdog. The block you pass serves as a callback when an event is generated on one of the -      # specified paths. -      # -      #   fsevents = FSEvents.new('/etc/passwd') { Mailer.send_mail("Someone touched the password file!") } -      #   fsevents.create_stream -      #   fsevents.start -      # -      #   fsevents = FSEvents.new('/home/upload', :since => UploadWatcher.last_event_id) do |events| -      #     events.each do |event| -      #       UploadWatcher.last_event_id = event.id -      #       event.files.each do |file| -      #         UploadWatcher.logfile.append("#{file} was changed") -      #       end -      #     end -      #   end -      # -      # *:since: The service will report events that have happened after the supplied event ID. Never use 0 because that  -      #   will cause every fsevent since the "beginning of time" to be reported. Use OSX::KFSEventStreamEventIdSinceNow -      #   if you want to receive events that have happened after this call. (Default: OSX::KFSEventStreamEventIdSinceNow). -      #   You can find the ID's passed with :since in the events passed to your block. -      # *:latency: Number of seconds to wait until an FSEvent is reported, this allows the service to bundle events. (Default: 0.0) -      # -      # Please refer to the Cocoa documentation for the rest of the options. -      def initialize(*params, &block) -        raise ArgumentError, 'No callback block was specified.' unless block_given? -         -        options = params.last.kind_of?(Hash) ? params.pop : {} -        @paths = params.flatten -         -        paths.each { |path| raise ArgumentError, "The specified path (#{path}) does not exist." unless File.exist?(path) } -         -        @allocator = options[:allocator] || OSX::KCFAllocatorDefault -        @context   = options[:context]   || nil -        @since     = options[:since]     || OSX::KFSEventStreamEventIdSinceNow -        @latency   = options[:latency]   || 0.0 -        @flags     = options[:flags]     || 0 -        @stream    = options[:stream]    || nil -         -        @user_callback = block -        @callback = Proc.new do |stream, client_callback_info, number_of_events, paths_pointer, event_flags, event_ids| -          paths_pointer.regard_as('*') -          events = [] -          number_of_events.times {|i| events << Rucola::FSEvents::FSEvent.new(self, event_ids[i], paths_pointer[i]) } -          @user_callback.call(events) -        end -      end -       -      # Create the stream. -      # Raises a Rucola::FSEvents::StreamError if the stream could not be created. -      def create_stream -        @stream = OSX.FSEventStreamCreate(@allocator, @callback, @context, @paths, @since, @latency, @flags) -        raise(StreamError, 'Unable to create FSEvents stream.') unless @stream -        OSX.FSEventStreamScheduleWithRunLoop(@stream, OSX.CFRunLoopGetCurrent, OSX::KCFRunLoopDefaultMode) -      end -       -      # Start the stream. -      # Raises a Rucola::FSEvents::StreamError if the stream could not be started. -      def start -        raise(StreamError, 'Unable to start FSEvents stream.') unless OSX.FSEventStreamStart(@stream) -      end -       -      # Stop the stream. -      # You can resume it by calling `start` again. -      def stop -        OSX.FSEventStreamStop(@stream) -      end -    end -  end -   -  # The complete BeerEvents API :) -  HOMEBREW_KEEP_DRY = %w{ /System /usr /etc /sbin /bin /Applications /Library } -   -  def watch_out_for_spill -    # Disable the RubyCocoa thread hook as apparently Laurent did not apply the -    # thread patches to the OS X system Ruby -    ENV['RUBYCOCOA_THREAD_HOOK_DISABLE'] = 'kampai' -     -    Thread.new { OSX.CFRunLoopRun() } -     -    start = Time.now -    dog = Rucola::FSEvents.start_watching(*HOMEBREW_KEEP_DRY) do |events| -      spill = events.map { |e| e.files }.flatten -      spill.reject! { |f| File.mtime(f) < start } -      spill.reject! { |path| path =~ /^#{HOMEBREW_PREFIX}/ } -      # irrelevent files that change a lot -      spill.reject! { |path| path == "/Library/Preferences/SystemConfiguration/com.apple.PowerManagement.plist-lock" } -      spill.reject! { |path| path =~ %r{^(/System)?/Library/Caches/} } -      unless spill.empty? -        opoo "Detected installation of files outside the Homebrew prefix:" -        puts *spill -      end -    end -    yield -  ensure -    dog.stop -  end -rescue LoadError => e -  onoe "RubyCocoa could not be loaded, therefore checking for spill is disabled." -  puts "When using a custom Ruby installation, you'll need to install RubyCocoa." -  puts "If this is not the case, see if the following ticket applies, or create one." -  puts "  http://github.com/mxcl/homebrew/issues#issue/37" -   -  if ARGV.verbose? -    onoe e.message -    puts e.backtrace -  end -   -  def watch_out_for_spill -    yield -  end -end
\ No newline at end of file diff --git a/Library/Homebrew/brew.h.rb b/Library/Homebrew/brew.h.rb deleted file mode 100644 index c4bfd1de1..000000000 --- a/Library/Homebrew/brew.h.rb +++ /dev/null @@ -1,600 +0,0 @@ -FORMULA_META_FILES = %w[README README.md ChangeLog COPYING LICENSE LICENCE COPYRIGHT AUTHORS] -PLEASE_REPORT_BUG = "#{Tty.white}Please follow the instructions to report this bug at: #{Tty.em}\nhttps://github.com/mxcl/homebrew/wiki/new-issue#{Tty.reset}" - -def check_for_blacklisted_formula names -  return if ARGV.force? - -  names.each do |name| -    case name -    when 'tex', 'tex-live', 'texlive' then abort <<-EOS.undent -      Installing TeX from source is weird and gross, requires a lot of patches, -      and only builds 32-bit (and thus can't use Homebrew deps on Snow Leopard.) - -      We recommend using a MacTeX distribution: -        http://www.tug.org/mactex/ -    EOS - -    when 'mercurial', 'hg' then abort <<-EOS.undent -      Mercurial can be install thusly: -        brew install pip && pip install mercurial -    EOS - -    when 'npm' then abort <<-EOS.undent -      npm can be installed thusly by following the instructions at -        http://npmjs.org/ - -      To do it in one line, use this command: -        curl http://npmjs.org/install.sh | sh -    EOS - - -    when 'setuptools' then abort <<-EOS.undent -      When working with a Homebrew-built Python, distribute is preferred -      over setuptools, and can be used as the prerequisite for pip. - -      Install distribute using: -        brew install distribute -    EOS -    end -  end -end - -def __make url, name -  require 'formula' -  require 'digest' -  require 'erb' - -  path = Formula.path(name) -  raise "#{path} already exists" if path.exist? - -  if Formula.aliases.include? name and not ARGV.force? -    realname = Formula.resolve_alias(name) -    raise <<-EOS.undent -          "#{name}" is an alias for formula "#{realname}". -          Please check that you are not creating a duplicate. -          To force creation use --force. -          EOS -  end - -  if ARGV.include? '--cmake' -    mode = :cmake -  elsif ARGV.include? '--autotools' -    mode = :autotools -  else -    mode = nil -  end - -  version = Pathname.new(url).version -  if version.nil? -    opoo "Version cannot be determined from URL." -    puts "You'll need to add an explicit 'version' to the formula." -  else -    puts "Version detected as #{version}." -  end - -  md5 = '' -  if ARGV.include? "--cache" and version != nil -    strategy = detect_download_strategy url -    if strategy == CurlDownloadStrategy -      d = strategy.new url, name, version, nil -      the_tarball = d.fetch -      md5 = the_tarball.md5 -      puts "MD5 is #{md5}" -    else -      puts "--cache requested, but we can only cache formulas that use Curl." -    end -  end - -  formula_template = <<-EOS -require 'formula' - -class #{Formula.class_s name} <Formula -  url '#{url}' -  homepage '' -  md5 '#{md5}' - -<% if mode == :cmake %> -  depends_on 'cmake' -<% elsif mode == nil %> -  # depends_on 'cmake' -<% end %> - -  def install -<% if mode == :cmake %> -    system "cmake . \#{std_cmake_parameters}" -<% elsif mode == :autotools %> -    system "./configure", "--disable-debug", "--disable-dependency-tracking", -                          "--prefix=\#{prefix}" -<% else %> -    system "./configure", "--disable-debug", "--disable-dependency-tracking", -                          "--prefix=\#{prefix}" -    # system "cmake . \#{std_cmake_parameters}" -<% end %> -    system "make install" -  end -end -  EOS - -  path.write(ERB.new(formula_template, nil, '>').result(binding)) -  return path -end - -def make url -  path = Pathname.new url - -  /(.*?)[-_.]?#{path.version}/.match path.basename - -  unless $1.to_s.empty? -    name = $1 -  else -    print "Formula name [#{path.stem}]: " -    gots = $stdin.gets.chomp -    if gots.empty? -      name = path.stem -    else -      name = gots -    end -  end - -  force_text = "If you really want to make this formula use --force." - -  case name.downcase -  when 'vim', 'screen' -    raise <<-EOS -#{name} is blacklisted for creation -Apple distributes this program with OS X. - -#{force_text} -    EOS -  when 'libarchive', 'libpcap' -    raise <<-EOS -#{name} is blacklisted for creation -Apple distributes this library with OS X, you can find it in /usr/lib. - -#{force_text} -    EOS -  when 'libxml', 'libxlst', 'freetype', 'libpng' -    raise <<-EOS -#{name} is blacklisted for creation -Apple distributes this library with OS X, you can find it in /usr/X11/lib. -However not all build scripts look here, so you may need to call ENV.x11 or -ENV.libxml2 in your formula's install function. - -#{force_text} -    EOS -  when 'rubygem' -    raise "Sorry RubyGems comes with OS X so we don't package it.\n\n#{force_text}" -  when 'wxwidgets' -    raise <<-EOS -#{name} is blacklisted for creation -An older version of wxWidgets is provided by Apple with OS X, but -a formula for wxWidgets 2.8.10 is provided: - -    brew install wxmac - -  #{force_text} -    EOS -  end unless ARGV.force? - -  __make url, name -end - -def github_info name -  formula_name = Formula.path(name).basename -  user = 'mxcl' -  branch = 'master' - -  if system "/usr/bin/which -s git" -    gh_user=`git config --global github.user 2>/dev/null`.chomp -    /^\*\s*(.*)/.match(`git --work-tree=#{HOMEBREW_REPOSITORY} branch 2>/dev/null`) -    unless $1.nil? || $1.empty? || gh_user.empty? -      branch = $1.chomp -      user = gh_user -    end -  end - -  return "http://github.com/#{user}/homebrew/commits/#{branch}/Library/Formula/#{formula_name}" -end - -def info f -  exec 'open', github_info(f.name) if ARGV.flag? '--github' - -  puts "#{f.name} #{f.version}" -  puts f.homepage - -  puts "Depends on: #{f.deps.join(', ')}" unless f.deps.empty? - -  if f.prefix.parent.directory? -    kids=f.prefix.parent.children -    kids.each do |keg| -      next if keg.basename.to_s == '.DS_Store' -      print "#{keg} (#{keg.abv})" -      print " *" if f.installed_prefix == keg and kids.length > 1 -      puts -    end -  else -    puts "Not installed" -  end - -  if f.caveats -    puts -    puts f.caveats -    puts -  end - -  history = github_info(f.name) -  puts history if 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 issues_for_formula name -  # bit basic as depends on the issue at github having the exact name of the -  # formula in it. Which for stuff like objective-caml is unlikely. So we -  # really should search for aliases too. - -  name = f.name if Formula === name - -  require 'open-uri' -  require 'yaml' - -  issues = [] - -  open("http://github.com/api/v2/yaml/issues/search/mxcl/homebrew/open/"+name) do |f| -    YAML::load(f.read)['issues'].each do |issue| -      issues << 'http://github.com/mxcl/homebrew/issues/#issue/%s' % issue['number'] -    end -  end - -  issues -rescue -  [] -end - -def cleanup name -  require 'formula' - -  f = Formula.factory name -  formula_cellar = f.prefix.parent - -  if f.installed? and formula_cellar.directory? -    kids = f.prefix.parent.children -    kids.each do |keg| -      next if f.installed_prefix == keg -      print "Uninstalling #{keg}..." -      FileUtils.rm_rf keg -      puts -    end -  else -    # If the cellar only has one version installed, don't complain -    # that we can't tell which one to keep. -    if formula_cellar.children.length > 1 -      opoo "Skipping #{name}: most recent version #{f.version} not installed" -    end -  end -end - -def clean f -  require 'cleaner' -  Cleaner.new f -  -  # Hunt for empty folders and nuke them unless they are -  # protected by f.skip_clean? -  # We want post-order traversal, so put the dirs in a stack -  # and then pop them off later. -  paths = [] -  f.prefix.find do |path| -    paths << path if path.directory? -  end - -  until paths.empty? do -    d = paths.pop -    if d.children.empty? and not f.skip_clean? d -      puts "rmdir: #{d} (empty)" if ARGV.verbose? -      d.rmdir -    end -  end -end - - -def prune -  $n=0 -  $d=0 - -  dirs=Array.new -  paths=%w[bin sbin 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 #{$d} directories " if $d > 0 -    puts  "from #{HOMEBREW_PREFIX}" -  end -end - - -def diy -  path=Pathname.getwd - -  if ARGV.include? '--set-version' -    version=ARGV.next -  else -    version=path.version -    raise "Couldn't determine version, try --set-version" if version.to_s.empty? -  end -   -  if ARGV.include? '--set-name' -    name=ARGV.next -  else -    path.basename.to_s =~ /(.*?)-?#{version}/ -    if $1.nil? or $1.empty? -      name=path.basename -    else -      name=$1 -    end -  end - -  prefix=HOMEBREW_CELLAR+name+version - -  if File.file? 'CMakeLists.txt' -    "-DCMAKE_INSTALL_PREFIX=#{prefix}" -  elsif File.file? 'Makefile.am' -    "--prefix=#{prefix}" -  else -    raise "Couldn't determine build system" -  end -end - -def macports_or_fink_installed? -  # See these issues for some history: -  # http://github.com/mxcl/homebrew/issues/#issue/13 -  # http://github.com/mxcl/homebrew/issues/#issue/41 -  # http://github.com/mxcl/homebrew/issues/#issue/48 - -  %w[port fink].each do |ponk| -    path = `/usr/bin/which -s #{ponk}` -    return ponk unless path.empty? -  end - -  # we do the above check because macports can be relocated and fink may be -  # able to be relocated in the future. This following check is because if -  # fink and macports are not in the PATH but are still installed it can -  # *still* break the build -- because some build scripts hardcode these paths: -  %w[/sw/bin/fink /opt/local/bin/port].each do |ponk| -    return ponk if File.exist? ponk -  end - -  # finally, sometimes people make their MacPorts or Fink read-only so they -  # can quickly test Homebrew out, but still in theory obey the README's  -  # advise to rename the root directory. This doesn't work, many build scripts -  # error out when they try to read from these now unreadable directories. -  %w[/sw /opt/local].each do |path| -    path = Pathname.new(path) -    return path if path.exist? and not path.readable? -  end -   -  false -end - -def versions_of(keg_name) -  `/bin/ls #{HOMEBREW_CELLAR}/#{keg_name}`.collect { |version| version.strip }.reverse -end - - -def outdated_brews -  require 'formula' - -  results = [] -  HOMEBREW_CELLAR.subdirs.each do |keg| -    # Skip kegs with no versions installed -    next unless keg.subdirs - -    # Skip HEAD formulae, consider them "evergreen" -    next if keg.subdirs.collect{|p|p.basename.to_s}.include? "HEAD" - -    name = keg.basename.to_s -    if (not (f = Formula.factory(name)).installed? rescue nil) -      results << [keg, name, f.version] -    end -  end -  return results -end - -def search_brews text -  require "formula" - -  return Formula.names if text.to_s.empty? - -  rx = if text =~ %r{^/(.*)/$} -    Regexp.new($1) -  else -    /.*#{Regexp.escape text}.*/i -  end - -  aliases = Formula.aliases -  results = (Formula.names+aliases).grep rx - -  # Filter out aliases when the full name was also found -  results.reject do |alias_name| -    if aliases.include? alias_name -      results.include? Formula.resolve_alias(alias_name) -    end -  end -end - -def brew_install -  require 'formula_installer' -  require 'hardware' - -  ############################################################ sanity checks -  case Hardware.cpu_type when :ppc, :dunno -    abort "Sorry, Homebrew does not support your computer's CPU architecture.\n"+ -          "For PPC support, see: http://github.com/sceaga/homebrew/tree/powerpc" -  end - -  raise "Cannot write to #{HOMEBREW_CELLAR}" if HOMEBREW_CELLAR.exist? and not HOMEBREW_CELLAR.writable? -  raise "Cannot write to #{HOMEBREW_PREFIX}" unless HOMEBREW_PREFIX.writable? - -  ################################################################# warnings -  begin -    if MACOS_VERSION >= 10.6 -      opoo "You should upgrade to Xcode 3.2.3" if llvm_build < RECOMMENDED_LLVM -    else -      opoo "You should upgrade to Xcode 3.1.4" if (gcc_40_build < RECOMMENDED_GCC_40) or (gcc_42_build < RECOMMENDED_GCC_42) -    end -  rescue -    # the reason we don't abort is some formula don't require Xcode -    # TODO allow formula to declare themselves as "not needing Xcode" -    opoo "Xcode is not installed! Builds may fail!" -  end - -  if macports_or_fink_installed? -    opoo "It appears you have MacPorts or Fink installed." -    puts "Software installed with MacPorts and Fink are known to cause problems." -    puts "If you experience issues try uninstalling these tools." -  end - -  ################################################################# install! -  installer = FormulaInstaller.new -  installer.install_deps = !ARGV.include?('--ignore-dependencies') - -  ARGV.formulae.each do |f| -    if not f.installed? or ARGV.force? -      installer.install f -    else -      puts "Formula already installed: #{f.prefix}" -    end -  end -end - -########################################################## class PrettyListing -class PrettyListing -  def initialize path -    Pathname.new(path).children.sort{ |a,b| a.to_s.downcase <=> b.to_s.downcase }.each do |pn| -      case pn.basename.to_s -      when 'bin', 'sbin' -        pn.find { |pnn| puts pnn unless pnn.directory? } -      when 'lib' -        print_dir pn do |pnn| -          # dylibs have multiple symlinks and we don't care about them -          (pnn.extname == '.dylib' or pnn.extname == '.pc') and not pnn.symlink? -        end -      else -        if pn.directory? -          if pn.symlink? -            puts "#{pn} -> #{pn.readlink}" -          else -            print_dir pn -          end -        elsif not (FORMULA_META_FILES.include? pn.basename.to_s or pn.basename.to_s == '.DS_Store') -          puts pn -        end -      end -    end -  end - -private -  def print_dir root -    dirs = [] -    remaining_root_files = [] -    other = '' - -    root.children.sort.each do |pn| -      if pn.directory? -        dirs << pn -      elsif block_given? and yield pn -        puts pn -        other = 'other ' -      else -        remaining_root_files << pn unless pn.basename.to_s == '.DS_Store' -      end -    end - -    dirs.each do |d| -      files = [] -      d.find { |pn| files << pn unless pn.directory? } -      print_remaining_files files, d -    end - -    print_remaining_files remaining_root_files, root, other -  end - -  def print_remaining_files files, root, other = '' -    case files.length -    when 0 -      # noop -    when 1 -      puts files -    else -      puts "#{root}/ (#{files.length} #{other}files)" -    end -  end -end - - -def gcc_42_build -  `/usr/bin/gcc-4.2 -v 2>&1` =~ /build (\d{4,})/ -  if $1 -    $1.to_i  -  elsif system "/usr/bin/which gcc" -    # Xcode 3.0 didn't come with gcc-4.2 -    # We can't change the above regex to use gcc because the version numbers -    # are different and thus, not useful. -    # FIXME I bet you 20 quid this causes a side effect — magic values tend to -    401 -  else -    nil -  end -end -alias :gcc_build :gcc_42_build # For compatibility - -def gcc_40_build -  `/usr/bin/gcc-4.0 -v 2>&1` =~ /build (\d{4,})/ -  if $1 -    $1.to_i  -  else -    nil -  end -end - -def llvm_build -  if MACOS_VERSION >= 10.6 -    xcode_path = `/usr/bin/xcode-select -print-path`.chomp -    return nil if xcode_path.empty? -    `#{xcode_path}/usr/bin/llvm-gcc -v 2>&1` =~ /LLVM build (\d{4,})/ -    $1.to_i -  end -end - -def xcode_version -  `xcodebuild -version 2>&1` =~ /Xcode (\d(\.\d)*)/ -  return $1 ? $1 : nil -end - -def _compiler_recommendation build, recommended -  message = (!build.nil? && build < recommended) ? "(#{recommended} or newer recommended)" : "" -  return build, message -end diff --git a/Library/Homebrew/cleaner.rb b/Library/Homebrew/cleaner.rb index 84335f8a3..f19efc23b 100644 --- a/Library/Homebrew/cleaner.rb +++ b/Library/Homebrew/cleaner.rb @@ -6,6 +6,21 @@ class Cleaner      unless ENV['HOMEBREW_KEEP_INFO'].nil?        f.info.rmtree if f.info.directory? and not f.skip_clean? f.info      end + +    # Hunt for empty folders and nuke them unless they are protected by +    # f.skip_clean? We want post-order traversal, so put the dirs in a stack +    # and then pop them off later. +    paths = [] +    f.prefix.find do |path| +      paths << path if path.directory? +    end + +    paths.each do |d| +      if d.children.empty? and not f.skip_clean? d +        puts "rmdir: #{d} (empty)" if ARGV.verbose? +        d.rmdir +      end +    end    end  private diff --git a/Library/Homebrew/cmd/--cache.rb b/Library/Homebrew/cmd/--cache.rb new file mode 100644 index 000000000..f0a5b22f2 --- /dev/null +++ b/Library/Homebrew/cmd/--cache.rb @@ -0,0 +1,9 @@ +module Homebrew extend self +  def __cache +    if ARGV.named.empty? +      puts HOMEBREW_CACHE +    else +      puts ARGV.formulae.map{ |f| f.cached_download } +    end +  end +end diff --git a/Library/Homebrew/cmd/--cellar.rb b/Library/Homebrew/cmd/--cellar.rb new file mode 100644 index 000000000..c7e140dc5 --- /dev/null +++ b/Library/Homebrew/cmd/--cellar.rb @@ -0,0 +1,9 @@ +module Homebrew extend self +  def __cellar +    if ARGV.named.empty? +      puts HOMEBREW_CELLAR +    else +      puts ARGV.formulae.map{ |f| HOMEBREW_CELLAR/f } +    end +  end +end diff --git a/Library/Homebrew/cmd/--config.rb b/Library/Homebrew/cmd/--config.rb new file mode 100644 index 000000000..7a70df007 --- /dev/null +++ b/Library/Homebrew/cmd/--config.rb @@ -0,0 +1,66 @@ +require 'hardware' + +module Homebrew extend self +  def __config +    puts config_s +  end + +  def llvm +    @llvm ||= MacOS.llvm_build_version +  end + +  def gcc_42 +    @gcc_42 ||= MacOS.gcc_42_build_version +  end + +  def gcc_40 +    @gcc_40 ||= MacOS.gcc_40_build_version +  end + +  def xcode_version +    `xcodebuild -version 2>&1` =~ /Xcode (\d(\.\d)*)/ +    $1 +  end + +  def llvm_recommendation +    "(#{RECOMMENDED_LLVM} or newer recommended)" if llvm and llvm < RECOMMENDED_LLVM +  end +   +  def gcc_42_recommendation +    "(#{RECOMMENDED_GCC_42} or newer recommended)" if gcc_42 and gcc_42 < RECOMMENDED_GCC_42 +  end + +  def gcc_40_recommendation +    "(#{RECOMMENDED_GCC_40} or newer recommended)" if gcc_40.nil? and gcc_40 < RECOMMENDED_GCC_40 +  end + +  def sha +    sha = `cd #{HOMEBREW_REPOSITORY} && git rev-parse --verify HEAD 2> /dev/null`.chomp +    if sha.empty? then "(none)" else sha end +  end + +  def system_ruby +    Pathname.new('/usr/bin/ruby').realpath.to_s +  end + +  def config_s; <<-EOS.undent +    HOMEBREW_VERSION: #{HOMEBREW_VERSION} +    HEAD: #{sha} +    HOMEBREW_PREFIX: #{HOMEBREW_PREFIX} +    HOMEBREW_CELLAR: #{HOMEBREW_CELLAR} +    HOMEBREW_REPOSITORY: #{HOMEBREW_REPOSITORY} +    HOMEBREW_LIBRARY_PATH: #{HOMEBREW_LIBRARY_PATH} +    Hardware: #{Hardware.cores_as_words}-core #{Hardware.bits}-bit #{Hardware.intel_family} +    OS X: #{MACOS_FULL_VERSION} +    Kernel Architecture: #{`uname -m`.chomp} +    Ruby: #{RUBY_VERSION}-#{RUBY_PATCHLEVEL} +    /usr/bin/ruby => #{system_ruby} +    Xcode: #{xcode_version} +    GCC-4.0: #{gcc_40 ? "build #{gcc_40}" : "N/A"} #{gcc_40_recommendation} +    GCC-4.2: #{gcc_42 ? "build #{gcc_42}" : "N/A"} #{gcc_42_recommendation} +    LLVM: #{llvm ? "build #{llvm}" : "N/A" } #{llvm_recommendation} +    MacPorts or Fink? #{macports_or_fink_installed?} +    X11 installed? #{x11_installed?} +    EOS +  end +end diff --git a/Library/Homebrew/cmd/--env.rb b/Library/Homebrew/cmd/--env.rb new file mode 100644 index 000000000..07623171d --- /dev/null +++ b/Library/Homebrew/cmd/--env.rb @@ -0,0 +1,32 @@ +require 'extend/ENV' +require 'hardware' + +module Homebrew extend self +  def __env +    ENV.extend(HomebrewEnvExtension) +    ENV.setup_build_environment +    dump_build_env ENV +  end + +  def dump_build_env env +    puts %["--use-llvm" was specified] if ARGV.include? '--use-llvm' + +    %w[ CC CXX LD ].each do |k| +      value = env[k] +      if value +        results = value +        if File.exists? value and File.symlink? value +          target = Pathname.new(value) +          results += " => #{target.realpath}" +        end +        puts "#{k}: #{results}" +      end +    end + +    %w[ CFLAGS CXXFLAGS CPPFLAGS LDFLAGS MACOSX_DEPLOYMENT_TARGET MAKEFLAGS PKG_CONFIG_PATH +        HOMEBREW_DEBUG HOMEBREW_VERBOSE HOMEBREW_USE_LLVM HOMEBREW_SVN ].each do |k| +      value = env[k] +      puts "#{k}: #{value}" if value +    end +  end +end diff --git a/Library/Homebrew/cmd/--prefix.rb b/Library/Homebrew/cmd/--prefix.rb new file mode 100644 index 000000000..9a15203a9 --- /dev/null +++ b/Library/Homebrew/cmd/--prefix.rb @@ -0,0 +1,9 @@ +module Homebrew extend self +  def __prefix +    if ARGV.named.empty? +      puts HOMEBREW_PREFIX +    else +      puts ARGV.formulae.map{ |f| f.prefix } +    end +  end +end diff --git a/Library/Homebrew/cmd/--repository.rb b/Library/Homebrew/cmd/--repository.rb new file mode 100644 index 000000000..f14ab9901 --- /dev/null +++ b/Library/Homebrew/cmd/--repository.rb @@ -0,0 +1,5 @@ +module Homebrew extend self +  def __repository +    puts HOMEBREW_REPOSITORY +  end +end diff --git a/Library/Homebrew/cmd/cat.rb b/Library/Homebrew/cmd/cat.rb new file mode 100644 index 000000000..a87eba61e --- /dev/null +++ b/Library/Homebrew/cmd/cat.rb @@ -0,0 +1,10 @@ +module Homebrew extend self +  def cat +    # do not "fix" this to support multiple arguments, the output would be +    # unparsable, if the user wants to cat multiple formula they can call +    # brew cat multiple times. + +    cd HOMEBREW_REPOSITORY +    exec "cat", ARGV.formulae.first.path, *ARGV.options_only +  end +end diff --git a/Library/Homebrew/cmd/cleanup.rb b/Library/Homebrew/cmd/cleanup.rb new file mode 100644 index 000000000..f5e4637ec --- /dev/null +++ b/Library/Homebrew/cmd/cleanup.rb @@ -0,0 +1,42 @@ +require 'formula' +require 'cmd/prune' + +module Homebrew extend self +  def cleanup +    if ARGV.named.empty? +      HOMEBREW_CELLAR.children.each do |rack| +        begin +          cleanup_formula rack.basename.to_s if rack.directory? +        rescue FormulaUnavailableError => e +          opoo "Formula not found for #{e.name}" +        end +      end +      # seems like a good time to do some additional cleanup +      Homebrew.prune +    else +      ARGV.formulae.each do |f| +        cleanup_formula f +      end +    end +  end + +  def cleanup_formula f +    f = Formula.factory f +    rack = f.prefix.parent + +    if f.installed? and rack.directory? +      rack.children.each do |keg| +        if f.installed_prefix != keg +          print "Uninstalling #{keg}..." +          rm_rf keg +          puts +        end +      end +    elsif rack.children.length > 1 +      # If the cellar only has one version installed, don't complain +      # that we can't tell which one to keep. +      opoo "Skipping #{name}: most recent version #{f.version} not installed" +    end +  end + +end diff --git a/Library/Homebrew/cmd/create.rb b/Library/Homebrew/cmd/create.rb new file mode 100644 index 000000000..e21c9a524 --- /dev/null +++ b/Library/Homebrew/cmd/create.rb @@ -0,0 +1,153 @@ +require 'formula' + +module Homebrew extend self +  def create +    if ARGV.include? '--macports' +      exec "open", "http://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" +    elsif ARGV.include? '--fink' +      exec "open", "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" +    elsif ARGV.named.empty? +      raise UsageError +    else +      paths = ARGV.named.map do |url| +        fc = FormulaCreator.new +        fc.url = url +        fc.mode = if ARGV.include? '--cmake' +          :cmake +        elsif ARGV.include? '--autotools' +          :autotools +        end + +        if fc.name.to_s.strip.empty? +          path = Pathname.new url +          print "Formula name [#{path.stem}]: " +          fc.name = __gets || path.stem +        end + +        unless ARGV.force? +          if msg = blacklisted?(fc.name) +            raise "#{msg}\n\nIf you really want to make this formula use --force." +          end + +          if Formula.aliases.include? fc.name +            realname = Formula.caniconical_name fc.name +            raise <<-EOS.undent +              The formula #{realname} is already aliased to #{fc.name} +              Please check that you are not creating a duplicate. +              To force creation use --force. +              EOS +          end +        end +        fc.generate +        fc.path +      end +      exec_editor *paths +    end +  end + +  def __gets +    gots = $stdin.gets.chomp +    if gots.empty? then nil else gots end +  end + +  def blacklisted? name +    case name.downcase +    when 'vim', 'screen' then <<-EOS.undent +      #{name} is blacklisted for creation +      Apple distributes this program with OS X. +      EOS +    when 'libarchive', 'libpcap' then <<-EOS.undent +      #{name} is blacklisted for creation +      Apple distributes this library with OS X, you can find it in /usr/lib. +      EOS +    when 'libxml', 'libxlst', 'freetype', 'libpng' then <<-EOS.undent +      #{name} is blacklisted for creation +      Apple distributes this library with OS X, you can find it in /usr/X11/lib. +      However not all build scripts look here, so you may need to call ENV.x11 or +      ENV.libxml2 in your formula's install function. +      EOS +    when /^rubygems?$/ +      "Sorry RubyGems comes with OS X so we don't package it." +    when 'wxwidgets' then <<-EOS.undent +      #{name} is blacklisted for creation +      An older version of wxWidgets is provided by Apple with OS X, but +      a formula for wxWidgets 2.8.10 is provided: + +      brew install wxmac +      EOS +    end +  end +end + +class FormulaCreator +  attr :url +  attr :md5 +  attr :name, true +  attr :path +  attr :mode, true + +  def url= url +    @url = url +    path = Pathname.new url +    /(.*?)[-_.]?#{path.version}/.match path.basename +    @name = $1 +    @path = Formula.path $1 +  end + +  def version +    Pathname.new(url).version +  end + +  def generate +    raise "#{path} already exists" if path.exist? +    raise VersionUndetermined if version.nil? + +    require 'digest' +    require 'erb' + +    if version.nil? +      opoo "Version cannot be determined from URL." +      puts "You'll need to add an explicit 'version' to the formula." +    else +      puts "Version detected as #{version}." +    end + +    unless ARGV.include? "--no-md5" and version +      strategy = detect_download_strategy url +      @md5 = strategy.new(url, name, version, nil).fetch.md5 if strategy == CurlDownloadStrategy +    end + +    path.write ERB.new(template, nil, '>').result(binding) +  end + +  def template; <<-EOS.undent +    require 'formula' + +    class #{Formula.class_s name} <Formula +      url '#{url}' +      homepage '' +      md5 '#{md5}' + +    <% if mode == :cmake %> +      depends_on 'cmake' +    <% elsif mode == nil %> +      # depends_on 'cmake' +    <% end %> + +      def install +    <% if mode == :cmake %> +        system "cmake . \#{std_cmake_parameters}" +    <% elsif mode == :autotools %> +        system "./configure", "--disable-debug", "--disable-dependency-tracking", +                              "--prefix=\#{prefix}" +    <% else %> +        system "./configure", "--disable-debug", "--disable-dependency-tracking", +                              "--prefix=\#{prefix}" +        # system "cmake . \#{std_cmake_parameters}" +    <% end %> +        system "make install" +      end +    end +    EOS +  end +end diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb new file mode 100644 index 000000000..2e9a723f4 --- /dev/null +++ b/Library/Homebrew/cmd/deps.rb @@ -0,0 +1,14 @@ +module Homebrew extend self +  def deps +    puts if ARGV.include?('--all') +      require 'formula' +      Formula.all.each do |f| +        "#{f.name}:#{f.deps.join(' ')}" +      end +    elsif ARGV.include?("-1") or ARGV.include?("--1") +      *ARGV.formulae.map{ |f| f.deps or [] }.flatten.uniq.sort +    else +      *ARGV.formulae.map{ |f| f.recursive_deps.map{ |f| f.name } }.flatten.uniq.sort +    end +  end +end diff --git a/Library/Homebrew/cmd/diy.rb b/Library/Homebrew/cmd/diy.rb new file mode 100644 index 000000000..174626afc --- /dev/null +++ b/Library/Homebrew/cmd/diy.rb @@ -0,0 +1,34 @@ +module Homebrew extend self +  def diy +    path = Pathname.getwd + +    version = if ARGV.include? '--set-version' +      ARGV.next +    elsif path.version.to_s.empty? +      raise "Couldn't determine version, try --set-version" +    else +      path.version +    end + +    name = if ARGV.include? '--set-name' +      ARGV.next +    else +      path.basename.to_s =~ /(.*?)-?#{version}/ +      if $1.to_s.empty? +        path.basename +      else +        $1 +      end +    end + +    prefix = HOMEBREW_CELLAR/name/version + +    if File.file? 'CMakeLists.txt' +      puts "-DCMAKE_INSTALL_PREFIX=#{prefix}" +    elsif File.file? 'Makefile.am' +      puts "--prefix=#{prefix}" +    else +      raise "Couldn't determine build system" +    end +  end +end diff --git a/Library/Homebrew/brew_doctor.rb b/Library/Homebrew/cmd/doctor.rb index b26cd4f5d..bad3a5dc0 100644 --- a/Library/Homebrew/brew_doctor.rb +++ b/Library/Homebrew/cmd/doctor.rb @@ -583,7 +583,8 @@ def check_for_other_vars    end  end -def brew_doctor +module Homebrew extend self +def doctor    read, write = IO.pipe    if fork == nil @@ -629,3 +630,4 @@ def brew_doctor      end    end  end +end diff --git a/Library/Homebrew/cmd/edit.rb b/Library/Homebrew/cmd/edit.rb new file mode 100644 index 000000000..6ab91dccf --- /dev/null +++ b/Library/Homebrew/cmd/edit.rb @@ -0,0 +1,26 @@ +require 'formula' + +module Homebrew extend self +  def edit +    if ARGV.named.empty? +      # EDITOR isn't a good fit here, we need a GUI client that actually has +      # a UI for projects, so apologies if this wasn't what you expected, +      # please improve it! :) +      exec 'mate', HOMEBREW_REPOSITORY/"bin/brew", +                   HOMEBREW_REPOSITORY/'README.md', +                   HOMEBREW_REPOSITORY/".gitignore", +              *Dir[HOMEBREW_REPOSITORY/"Library/*"] +    else +      # Don't use ARGV.formulae as that will throw if the file doesn't parse +      paths = ARGV.named.map do |name| +        HOMEBREW_REPOSITORY/"Library/Formula/#{Formula.caniconical_name name}.rb" +      end +      unless ARGV.force? +        paths.each do |path| +          raise FormulaUnavailableError, path.basename('.rb').to_s unless path.file? +        end +      end +      exec_editor *paths +    end +  end +end diff --git a/Library/Homebrew/cmd/home.rb b/Library/Homebrew/cmd/home.rb new file mode 100644 index 000000000..a91607988 --- /dev/null +++ b/Library/Homebrew/cmd/home.rb @@ -0,0 +1,9 @@ +module Homebrew extend self +  def home +    if ARGV.named.empty? +      exec "open", HOMEBREW_WWW +    else +      exec "open", *ARGV.formulae.map{ |f| f.homepage } +    end +  end +end diff --git a/Library/Homebrew/cmd/info.rb b/Library/Homebrew/cmd/info.rb new file mode 100644 index 000000000..7f9767e07 --- /dev/null +++ b/Library/Homebrew/cmd/info.rb @@ -0,0 +1,92 @@ +require 'formula' + +module Homebrew extend self +  def info +    if ARGV.named.empty? +      if ARGV.include? "--all" +        Formula.each do |f| +          info_formula f +          puts '---' +        end +      else +        puts "#{HOMEBREW_CELLAR.children.length} kegs, #{HOMEBREW_CELLAR.abv}" +      end +    elsif valid_url ARGV[0] +      path = Pathname.new(ARGV.shift) +      /(.*?)[-_.]?#{path.version}/.match path.basename +      unless $1.to_s.empty? +        name = $1 +      else +        name = path.stem +      end +      puts "#{name} #{path.version}" +    else +      ARGV.formulae.each{ |f| info_formula f } +    end +  end + +  def github_info name +    formula_name = Formula.path(name).basename +    user = 'mxcl' +    branch = 'master' + +    if system "/usr/bin/which -s git" +      gh_user=`git config --global github.user 2>/dev/null`.chomp +      /^\*\s*(.*)/.match(`git --work-tree=#{HOMEBREW_REPOSITORY} branch 2>/dev/null`) +      unless $1.nil? || $1.empty? || gh_user.empty? +        branch = $1.chomp +        user = gh_user +      end +    end + +    "http://github.com/#{user}/homebrew/commits/#{branch}/Library/Formula/#{formula_name}" +  end + +  def info_formula f +    exec 'open', github_info(f.name) if ARGV.flag? '--github' + +    puts "#{f.name} #{f.version}" +    puts f.homepage + +    puts "Depends on: #{f.deps*', '}" unless f.deps.empty? + +    rack = f.prefix.parent +    if rack.directory? +      kegs = rack.children +      kegs.each do |keg| +        next if keg.basename.to_s == '.DS_Store' +        print "#{keg} (#{keg.abv})" +        print " *" if f.installed_prefix == keg and kegs.length > 1 +        puts +      end +    else +      puts "Not installed" +    end + +    if f.caveats +      puts +      puts f.caveats +      puts +    end + +    history = github_info f.name +    puts history if 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 + +  private + +  def valid_url u +    u[0..6] == 'http://' or u[0..7] == 'https://' or u[0..5] == 'ftp://' +  end + +end diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb new file mode 100644 index 000000000..ed5fb6777 --- /dev/null +++ b/Library/Homebrew/cmd/install.rb @@ -0,0 +1,87 @@ +require 'formula_installer' +require 'hardware' + +module Homebrew extend self +  def install +    brew_install +  end +end + +def brew_install +  ############################################################ sanity checks +  case Hardware.cpu_type when :ppc, :dunno +    abort "Sorry, Homebrew does not support your computer's CPU architecture.\n"+ +          "For PPC support, see: http://github.com/sceaga/homebrew/tree/powerpc" +  end + +  raise "Cannot write to #{HOMEBREW_CELLAR}" if HOMEBREW_CELLAR.exist? and not HOMEBREW_CELLAR.writable? +  raise "Cannot write to #{HOMEBREW_PREFIX}" unless HOMEBREW_PREFIX.writable? + +  ################################################################# warnings +  begin +    if MACOS_VERSION >= 10.6 +      opoo "You should upgrade to Xcode 3.2.3" if llvm_build < RECOMMENDED_LLVM +    else +      opoo "You should upgrade to Xcode 3.1.4" if (gcc_40_build < RECOMMENDED_GCC_40) or (gcc_42_build < RECOMMENDED_GCC_42) +    end +  rescue +    # the reason we don't abort is some formula don't require Xcode +    # TODO allow formula to declare themselves as "not needing Xcode" +    opoo "Xcode is not installed! Builds may fail!" +  end + +  if macports_or_fink_installed? +    opoo "It appears you have MacPorts or Fink installed." +    puts "Software installed with MacPorts and Fink are known to cause problems." +    puts "If you experience issues try uninstalling these tools." +  end + +  ################################################################# install! +  installer = FormulaInstaller.new +  installer.install_deps = !ARGV.include?('--ignore-dependencies') + +  ARGV.formulae.each do |f| +    if not f.installed? or ARGV.force? +      installer.install f +    else +      puts "Formula already installed: #{f.prefix}" +    end +  end +end + +def check_for_blacklisted_formula names +  return if ARGV.force? + +  names.each do |name| +    case name +    when 'tex', 'tex-live', 'texlive' then abort <<-EOS.undent +      Installing TeX from source is weird and gross, requires a lot of patches, +      and only builds 32-bit (and thus can't use Homebrew deps on Snow Leopard.) + +      We recommend using a MacTeX distribution: +        http://www.tug.org/mactex/ +    EOS + +    when 'mercurial', 'hg' then abort <<-EOS.undent +      Mercurial can be install thusly: +        brew install pip && pip install mercurial +    EOS + +    when 'npm' then abort <<-EOS.undent +      npm can be installed thusly by following the instructions at +        http://npmjs.org/ + +      To do it in one line, use this command: +        curl http://npmjs.org/install.sh | sudo sh +    EOS + +    when 'setuptools' then abort <<-EOS.undent +      When working with a Homebrew-built Python, distribute is preferred +      over setuptools, and can be used as the prerequisite for pip. + +      Install distribute using: +        brew install distribute +    EOS +    end +  end +end diff --git a/Library/Homebrew/cmd/link.rb b/Library/Homebrew/cmd/link.rb new file mode 100644 index 000000000..5565309e2 --- /dev/null +++ b/Library/Homebrew/cmd/link.rb @@ -0,0 +1,8 @@ +module Homebrew extend self +  def link +    ARGV.kegs.each do |keg| +      print "Linking #{keg}... " +      puts "#{keg.link} links created" +    end +  end +end diff --git a/Library/Homebrew/cmd/list.rb b/Library/Homebrew/cmd/list.rb new file mode 100644 index 000000000..8e35530fd --- /dev/null +++ b/Library/Homebrew/cmd/list.rb @@ -0,0 +1,88 @@ +module Homebrew extend self +  def list +    if ARGV.flag? '--unbrewed' +      dirs = HOMEBREW_PREFIX.children.select{ |pn| pn.directory? }.map{ |pn| pn.basename.to_s } +      dirs -= %w[Library Cellar .git] +      cd HOMEBREW_PREFIX +      exec 'find', *dirs + %w[-type f ( ! -iname .ds_store ! -iname brew )] +    elsif ARGV.flag? '--versions' +      if ARGV.named.empty? +        HOMEBREW_CELLAR.children.select{ |pn| pn.directory? } +      else +        ARGV.named.map{ |n| HOMEBREW_CELLAR/n }.select{ |pn| pn.exist? } +      end.each do |d| +        versions = d.children.select{ |pn| pn.directory? }.map{ |pn| pn.basename.to_s } +        puts "#{d.basename} #{versions*' '}" +      end +    elsif ARGV.named.empty? +      ENV['CLICOLOR'] = nil +      exec 'ls', *ARGV.options_only << HOMEBREW_CELLAR if HOMEBREW_CELLAR.exist? +    elsif ARGV.verbose? or not $stdout.tty? +      exec "find", *ARGV.kegs + %w[-not -type d -print] +    else +      ARGV.kegs.each{ |keg| PrettyListing.new keg } +    end +  end +end + +class PrettyListing +  def initialize path +    Pathname.new(path).children.sort{ |a,b| a.to_s.downcase <=> b.to_s.downcase }.each do |pn| +      case pn.basename.to_s +      when 'bin', 'sbin' +        pn.find { |pnn| puts pnn unless pnn.directory? } +      when 'lib' +        print_dir pn do |pnn| +          # dylibs have multiple symlinks and we don't care about them +          (pnn.extname == '.dylib' or pnn.extname == '.pc') and not pnn.symlink? +        end +      else +        if pn.directory? +          if pn.symlink? +            puts "#{pn} -> #{pn.readlink}" +          else +            print_dir pn +          end +        elsif not (FORMULA_META_FILES + ['.DS_Store']).include? pn.basename.to_s +          puts pn +        end +      end +    end +  end + +  def print_dir root +    dirs = [] +    remaining_root_files = [] +    other = '' + +    root.children.sort.each do |pn| +      if pn.directory? +        dirs << pn +      elsif block_given? and yield pn +        puts pn +        other = 'other ' +      else +        remaining_root_files << pn unless pn.basename.to_s == '.DS_Store' +      end +    end + +    dirs.each do |d| +      files = [] +      d.find { |pn| files << pn unless pn.directory? } +      print_remaining_files files, d +    end + +    print_remaining_files remaining_root_files, root, other +  end + +  def print_remaining_files files, root, other = '' +    case files.length +    when 0 +      # noop +    when 1 +      puts files +    else +      puts "#{root}/ (#{files.length} #{other}files)" +    end +  end +end diff --git a/Library/Homebrew/cmd/log.rb b/Library/Homebrew/cmd/log.rb new file mode 100644 index 000000000..ceb0c8e02 --- /dev/null +++ b/Library/Homebrew/cmd/log.rb @@ -0,0 +1,10 @@ +module Homebrew extend self +  def log +    cd HOMEBREW_REPOSITORY +    if ARGV.named.empty? +      exec "git", "log", *ARGV.options_only +    else +      exec "git", "log", *ARGV.formulae.map(&:path), *ARGV.options_only +    end +  end +end diff --git a/Library/Homebrew/cmd/outdated.rb b/Library/Homebrew/cmd/outdated.rb new file mode 100644 index 000000000..37fba0f99 --- /dev/null +++ b/Library/Homebrew/cmd/outdated.rb @@ -0,0 +1,28 @@ +require 'formula' + +module Homebrew extend self +  def outdated +    outdated_brews.each do |keg, name, version| +      if $stdout.tty? and not ARGV.flag? '--quiet' +        versions = keg.cd{ Dir['*'] }.join(', ') +        puts "#{name} (#{versions} < #{version})" +      else +        puts name +      end +    end +  end + +  def outdated_brews +    HOMEBREW_CELLAR.subdirs.map do |rack| +      # Skip kegs with no versions installed +      next unless rack.subdirs + +      # Skip HEAD formulae, consider them "evergreen" +      next if rack.subdirs.map{ |keg| keg.basename.to_s }.include? "HEAD" + +      name = rack.basename.to_s +      f = Formula.factory name rescue nil +      [rack, name, f.version] if f and not f.installed? +    end.compact +  end +end
\ No newline at end of file diff --git a/Library/Homebrew/cmd/prune.rb b/Library/Homebrew/cmd/prune.rb new file mode 100644 index 000000000..ba4bbc8e5 --- /dev/null +++ b/Library/Homebrew/cmd/prune.rb @@ -0,0 +1,31 @@ +module Homebrew extend self +  # $n and $d are used by the ObserverPathnameExtension to keep track of +  # certain filesystem actions. + +  def prune +    $n = 0 +    $d = 0 +    dirs = [] + +    %w[bin sbin etc lib include share].map{ |d| HOMEBREW_PREFIX/d }.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 +      print "Pruned #{$n} symbolic links " +      print "and #{$d} directories " if $d > 0 +      puts  "from #{HOMEBREW_PREFIX}" +    end +  end +end diff --git a/Library/Homebrew/cmd/search.rb b/Library/Homebrew/cmd/search.rb new file mode 100644 index 000000000..b6a0e971d --- /dev/null +++ b/Library/Homebrew/cmd/search.rb @@ -0,0 +1,42 @@ +require "formula" + +module Homebrew extend self +  def search +    if ARGV.include? '--macports' +      exec "open", "http://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" +    elsif ARGV.include? '--fink' +      exec "open", "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" +    end + +    require 'cmd/install' # for blacklisted? function +    blacklisted? ARGV.named do |msg, _| +      abort msg +    end unless ARGV.force? + +    puts_columns search_brews(ARGV.first) +  end + +  def search_brews text +    if text.to_s.empty? +      Formula.names +    else +      rx = if text =~ %r{^/(.*)/$} +        Regexp.new($1) +      else +        /.*#{Regexp.escape text}.*/i +      end + +      aliases = Formula.aliases +      results = (Formula.names+aliases).grep rx + +      # Filter out aliases when the full name was also found +      results.reject do |alias_name| +        if aliases.include? alias_name +          resolved_name = (HOMEBREW_REPOSITORY/"Library/Aliases"/alias_name).readlink.basename('.rb').to_s +          results.include? resolved_name +        end +      end +    end +  end + +end diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb new file mode 100644 index 000000000..34de88130 --- /dev/null +++ b/Library/Homebrew/cmd/uninstall.rb @@ -0,0 +1,31 @@ +require 'keg' + +module Homebrew extend self +  def uninstall +    unless ARGV.force? +      ARGV.kegs.each do |keg| +        puts "Uninstalling #{keg}..." +        keg.unlink +        keg.uninstall +      end +    else +      ARGV.formulae.each do |f| +        rack = f.prefix.parent +        if rack.directory? +          puts "Uninstalling #{f}..." +          rack.children do |keg| +            if keg.directory? +              keg = Keg.new(keg) +              keg.unlink +              keg.rmtree +            end +          end +          rack.rmdir +        end +      end +    end +  rescue MultipleVersionsInstalledError => e +    onoe e +    puts "Use `brew remove --force #{e.name}` to remove all versions." +  end +end diff --git a/Library/Homebrew/cmd/unlink.rb b/Library/Homebrew/cmd/unlink.rb new file mode 100644 index 000000000..d4749ab76 --- /dev/null +++ b/Library/Homebrew/cmd/unlink.rb @@ -0,0 +1,8 @@ +module Homebrew extend self +  def unlink +    ARGV.kegs.each do |keg| +      print "Unlinking #{keg}... " +      puts "#{keg.unlink} links removed" +    end +  end +end diff --git a/Library/Homebrew/update.rb b/Library/Homebrew/cmd/update.rb index 9e4fb90d9..32f144c8e 100644 --- a/Library/Homebrew/update.rb +++ b/Library/Homebrew/cmd/update.rb @@ -1,3 +1,16 @@ +module Homebrew extend self +  def update +    abort "Please `brew install git' first." unless system "/usr/bin/which -s git" + +    updater = RefreshBrew.new +    if updater.update_from_masterbrew! +      updater.report +    else +      puts "Already up-to-date." +    end +  end +end +  class RefreshBrew    REPOSITORY_URL   = "http://github.com/mxcl/homebrew.git"    INIT_COMMAND     = "git init" diff --git a/Library/Homebrew/cmd/uses.rb b/Library/Homebrew/cmd/uses.rb new file mode 100644 index 000000000..455b29b31 --- /dev/null +++ b/Library/Homebrew/cmd/uses.rb @@ -0,0 +1,25 @@ +require 'formula' + +# `brew uses foo bar` now returns formula that use both foo and bar +# Rationale: If you want the union just run the command twice and +# concatenate the results. +# The intersection is harder to achieve with shell tools. + +module Homebrew extend self +  def uses +    uses = Formula.all.select do |f| +      ARGV.formulae.all? do |ff| +        # For each formula given, show which other formulas depend on it. +        # We only go one level up, ie. direct dependencies. +        f.deps.include? ff.name +      end +    end +    if ARGV.include? "--installed" +      uses = uses.select do |f| +        keg = HOMEBREW_CELLAR/f +        keg.directory? and not keg.subdirs.empty? +      end +    end +    puts uses.sort +  end +end diff --git a/Library/Homebrew/compatibility.rb b/Library/Homebrew/compatibility.rb new file mode 100644 index 000000000..52f98416a --- /dev/null +++ b/Library/Homebrew/compatibility.rb @@ -0,0 +1,47 @@ + +# maybe never used by anyone, but alas it must continue to exist +def versions_of(keg_name) +  `/bin/ls #{HOMEBREW_CELLAR}/#{keg_name}`.collect { |version| version.strip }.reverse +end + +def dump_config +  require 'cmd/--config' +  Homebrew.__config +end + +def dump_build_env env +  require 'cmd/--env' +  Homebrew.dump_build_env env +end + +def gcc_42_build +  MacOS.gcc_42_build_version +end + +alias :gcc_build :gcc_42_build + +def gcc_40_build +  MacOS.gcc_40_build_version +end + +def llvm_build +  MacOS.llvm_build_version +end + +def x11_installed? +  MacOS.x11_installed? +end + +def macports_or_fink_installed? +  MacOS.macports_or_fink_installed? +end + +def outdated_brews +  require 'cmd/outdated' +  Homebrew.outdated_brews +end + +def search_brews text +  require 'cmd/search' +  Homebrew.search_brews text +end diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb new file mode 100644 index 000000000..73cda01e5 --- /dev/null +++ b/Library/Homebrew/exceptions.rb @@ -0,0 +1,99 @@ + +class NotAKegError < RuntimeError +end + +class FormulaUnavailableError < RuntimeError +  attr :name +  def initialize name +    @name = name +    super "No available formula for #{name}" +  end +end + +module Homebrew +  class InstallationError < RuntimeError +    attr :formula +    def initialize formula +      @formula = formula +    end +    def initialize formula, message +      super message +      @formula = formula +    end +  end +end + +class FormulaAlreadyInstalledError < Homebrew::InstallationError +  def message +    "Formula already installed: #{formula}" +  end +end + +class FormulaInstallationAlreadyAttemptedError < Homebrew::InstallationError +  def message +    "Formula installation already attempted: #{formula}" +  end +end + +class UnsatisfiedExternalDependencyError < Homebrew::InstallationError +  attr :type +   +  def initialize formula, type +    @type = type +    @formula = formula +  end + +  def message +    <<-EOS.undent +      Unsatisfied dependency: #{formula} +      Homebrew does not provide #{type.to_s.capitalize} dependencies, #{tool} does: + +          #{command_line} #{formula} +      EOS +  end + +  private + +  def tool +    case type +      when :python then 'pip' +      when :ruby, :jruby then 'rubygems' +      when :perl then 'cpan' +    end +  end + +  def command_line +    case type +      when :python +        "#{brew_pip}pip install" +      when :ruby +        "gem install" +      when :perl +        "cpan -i" +      when :jruby +        "jruby -S gem install" +    end +  end + +  def brew_pip +    'brew install pip && ' unless Formula.factory('pip').installed? +  end +end + +class BuildError < Homebrew::InstallationError +  attr :exit_status +  attr :command +  attr :env + +  def initialize formula, cmd, args, es +    @command = cmd +    @env = ENV.to_hash +    @exit_status = es.exitstatus rescue 1 +    args = args.map{ |arg| arg.gsub " ", "\\ " }.join(" ") +    super formula, "Failed executing: #{command} #{args}" +  end + +  def was_running_configure? +    @command == './configure' +  end +end diff --git a/Library/Homebrew/extend/ENV.rb b/Library/Homebrew/extend/ENV.rb index 61fb3c2b3..a4e290d5f 100644 --- a/Library/Homebrew/extend/ENV.rb +++ b/Library/Homebrew/extend/ENV.rb @@ -152,7 +152,7 @@ module HomebrewEnvExtension    end    def x11 -    opoo "You do not have X11 installed, this formula may not build." if not x11_installed? +    opoo "You do not have X11 installed, this formula may not build." if not MacOS.x11_installed?      # There are some config scripts (e.g. freetype) here that should go in the path      prepend 'PATH', '/usr/X11/bin', ':' diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 0e3266351..78e438726 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1,15 +1,6 @@  require 'download_strategy'  require 'fileutils' -class FormulaUnavailableError <RuntimeError -  def initialize name -    @name = name -    super "No available formula for #{name}" -  end - -  attr_reader :name -end -  class SoftwareSpecification    attr_reader :url, :specs, :using @@ -223,6 +214,22 @@ class Formula      end    end +  def == b +    name == b.name +  end +  def eql? b +    self == b and self.class.equal? b.class +  end +  def hash +    name.hash +  end +  def <=> b +    name <=> b.name +  end +  def to_s +    name +  end +    # Standard parameters for CMake builds.    # Using Build Type "None" tells cmake to use our CFLAGS,etc. settings.    # Setting it to Release would ignore our flags. @@ -266,15 +273,25 @@ class Formula    # an array of all Formula, instantiated    def self.all -    all = [] +    map{ |f| f } +  end +  def self.map +    rv = [] +    each{ |f| rv << yield(f) } +    rv +  end +  def self.each      names.each do |n|        begin -        all << Formula.factory(n) +        yield Formula.factory(n)        rescue          # Don't let one broken formula break commands.        end      end -    return all +  end + +  def inspect +    name    end    def self.aliases @@ -320,8 +337,9 @@ class Formula          install_type = :from_path          target_file = path.to_s        else +        name = Formula.caniconical_name(name)          # For names, map to the path and then require -        require self.path(name) +        require Formula.path(name)          install_type = :from_name        end      end @@ -344,7 +362,7 @@ class Formula    end    def self.path name -    HOMEBREW_REPOSITORY+"Library/Formula/#{name.downcase}.rb" +    HOMEBREW_REPOSITORY/"Library/Formula/#{name.downcase}.rb"    end    def deps @@ -352,7 +370,20 @@ class Formula    end    def external_deps -    self.class.external_deps +    self.class.external_deps or {} +  end + +  # deps are in an installable order +  # which means if a depends on b then b will be ordered before a in this list +  def recursive_deps +    Formula.expand_deps(self).flatten.uniq +  end + +  def self.expand_deps f +    f.deps.map do |dep| +      dep = Formula.factory dep +      expand_deps(dep) << dep +    end    end  protected @@ -381,11 +412,8 @@ protected          raise        end      end -  rescue SystemCallError -    # usually because exec could not be find the command that was requested -    raise    rescue -    raise BuildError.new(cmd, args, $?) +    raise BuildError.new(self, cmd, args, $?)    end  private diff --git a/Library/Homebrew/global.rb b/Library/Homebrew/global.rb index 0555b1267..3cc1024e7 100644 --- a/Library/Homebrew/global.rb +++ b/Library/Homebrew/global.rb @@ -2,6 +2,8 @@ require 'extend/pathname'  require 'extend/ARGV'  require 'extend/string'  require 'utils' +require 'exceptions' +require 'compatibility'  ARGV.extend(HomebrewArgvExtension) @@ -40,3 +42,11 @@ HOMEBREW_USER_AGENT = "Homebrew #{HOMEBREW_VERSION} (Ruby #{RUBY_VERSION}-#{RUBY  RECOMMENDED_LLVM = 2326  RECOMMENDED_GCC_40 = (MACOS_VERSION >= 10.6) ? 5494 : 5493  RECOMMENDED_GCC_42 = (MACOS_VERSION >= 10.6) ? 5664 : 5577 + +require 'fileutils' +module Homebrew extend self +  include FileUtils +end + +FORMULA_META_FILES = %w[README README.md ChangeLog COPYING LICENSE LICENCE COPYRIGHT AUTHORS] +PLEASE_REPORT_BUG = "#{Tty.white}Please report this bug at #{Tty.em}\nhttps://github.com/mxcl/homebrew/wiki/new-issue#{Tty.reset}" diff --git a/Library/Homebrew/install.rb b/Library/Homebrew/install.rb index f3ca8d2bd..6b338ea83 100755 --- a/Library/Homebrew/install.rb +++ b/Library/Homebrew/install.rb @@ -30,7 +30,7 @@ at_exit do      require 'fileutils'      require 'hardware'      require 'keg' -    require 'brew.h.rb' +    require 'compatibility'      ENV.extend(HomebrewEnvExtension)      ENV.setup_build_environment @@ -121,7 +121,8 @@ def install f    ohai 'Finishing up' if ARGV.verbose?    begin -    clean f +    require 'cleaner' +    Cleaner.new f    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" diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index e278a5ce7..22bf40bc3 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -1,3 +1,5 @@ +require 'extend/pathname' +  class Keg <Pathname    def initialize path      super path @@ -5,8 +7,6 @@ class Keg <Pathname      raise "#{to_s} is not a directory" unless directory?    end -  class NotAKegError <RuntimeError; end -    # if path is a file in a keg then this will return the containing Keg object    def self.for path      path = path.realpath diff --git a/Library/Homebrew/test/test_ENV.rb b/Library/Homebrew/test/test_ENV.rb index 1cd716dfd..da3a9d6af 100644 --- a/Library/Homebrew/test/test_ENV.rb +++ b/Library/Homebrew/test/test_ENV.rb @@ -1,6 +1,5 @@  require 'testing_env'  require 'utils' -require 'brew.h'  require 'extend/ENV'  ENV.extend(HomebrewEnvExtension) diff --git a/Library/Homebrew/test/test_bucket.rb b/Library/Homebrew/test/test_bucket.rb index 8492b4929..086297dd2 100644 --- a/Library/Homebrew/test/test_bucket.rb +++ b/Library/Homebrew/test/test_bucket.rb @@ -5,7 +5,6 @@ ARGV.extend(HomebrewArgvExtension)  require 'test/testball'  require 'utils' -require 'brew.h'  class MockFormula <Formula    def initialize url @@ -79,19 +78,24 @@ class BeerTasting < Test::Unit::TestCase    # end    def test_brew_h +    require 'cmd/info' +    require 'cmd/prune' +    require 'cleaner' +      nostdout do        assert_nothing_raised do          f=TestBall.new -        make f.url -        info f -        clean f -        prune +        Homebrew.info_formula f +        Cleaner.new f +        Homebrew.prune          #TODO test diy function too        end      end    end    def test_brew_cleanup +    require 'cmd/cleanup' +      f1=TestBall.new      f1.instance_eval { @version = "0.1" }      f2=TestBall.new @@ -110,7 +114,7 @@ class BeerTasting < Test::Unit::TestCase      assert f3.installed?      nostdout do -      cleanup f3 +      Homebrew.cleanup_formula f3      end      assert !f1.installed? @@ -175,4 +179,16 @@ class BeerTasting < Test::Unit::TestCase      assert_equal 'foo-0.1', foo1.stem      assert_equal '0.1', foo1.version    end + +  class MockMockFormula < Struct.new(:name); end + +  def test_formula_equality +    f = MockFormula.new('http://example.com/test-0.1.tgz') +    g = MockMockFormula.new('test') + +    assert f == f +    assert f == g +    assert f.eql? f +    assert (not (f.eql? g)) +  end  end diff --git a/Library/Homebrew/test/test_external_deps.rb b/Library/Homebrew/test/test_external_deps.rb index 2b96e7678..30b3540d9 100644 --- a/Library/Homebrew/test/test_external_deps.rb +++ b/Library/Homebrew/test/test_external_deps.rb @@ -3,38 +3,12 @@ require 'testing_env'  require 'extend/ARGV' # needs to be after test/unit to avoid conflict with OptionsParser  ARGV.extend(HomebrewArgvExtension) +require 'extend/string'  require 'test/testball'  require 'formula_installer'  require 'utils' -# Custom formula installer that checks deps but does not -# run the install code. We also override the external dep -# install messages, so for instance the Python check doesnt -# look for the pip formula (which isn't avaialble in test -# mode.) -class DontActuallyInstall < FormulaInstaller -  def install_private f ; end -   -  def pyerr dep -    "Python module install message." -  end -   -  def plerr dep -    "Perl module install message." -  end -   -  def rberr dep -    "Ruby module install message." -  end - -  def jrberr dep -    "JRuby module install message." -  end -end - - -  class BadPerlBall <TestBall    depends_on "notapackage" => :perl @@ -102,14 +76,14 @@ end  class ExternalDepsTests < Test::Unit::TestCase    def check_deps_fail f -    assert_raises(RuntimeError) do -      DontActuallyInstall.new.install f.new +    assert_raises(UnsatisfiedExternalDependencyError) do +      FormulaInstaller.check_external_deps f.new      end    end    def check_deps_pass f      assert_nothing_raised do -      DontActuallyInstall.new.install f.new +      FormulaInstaller.check_external_deps f.new      end    end diff --git a/Library/Homebrew/test/test_formula.rb b/Library/Homebrew/test/test_formula.rb index aa545f6d4..fd3987168 100644 --- a/Library/Homebrew/test/test_formula.rb +++ b/Library/Homebrew/test/test_formula.rb @@ -5,7 +5,6 @@ ARGV.extend(HomebrewArgvExtension)  require 'test/testball'  require 'utils' -require 'brew.h'  class MostlyAbstractFormula <Formula diff --git a/Library/Homebrew/test/test_formula_install.rb b/Library/Homebrew/test/test_formula_install.rb index db1a2b103..059d7656b 100644 --- a/Library/Homebrew/test/test_formula_install.rb +++ b/Library/Homebrew/test/test_formula_install.rb @@ -26,7 +26,7 @@ class ConfigureTests < Test::Unit::TestCase      f=ConfigureFails.new      begin        f.brew { f.install } -    rescue ExecutionError => e +    rescue BuildError => e        assert e.was_running_configure?      end    end diff --git a/Library/Homebrew/test/test_updater.rb b/Library/Homebrew/test/test_updater.rb index 1d733560b..5659f9c67 100644 --- a/Library/Homebrew/test/test_updater.rb +++ b/Library/Homebrew/test/test_updater.rb @@ -7,7 +7,7 @@ ARGV.extend(HomebrewArgvExtension)  require 'formula'  require 'utils' -require 'update' +require 'cmd/update'  class RefreshBrewMock < RefreshBrew    def in_prefix_expect(expect, returns = '') diff --git a/Library/Homebrew/test/testing_env.rb b/Library/Homebrew/test/testing_env.rb index ffc8b3003..0933185bb 100644 --- a/Library/Homebrew/test/testing_env.rb +++ b/Library/Homebrew/test/testing_env.rb @@ -8,6 +8,7 @@ ABS__FILE__=File.expand_path(__FILE__)  $:.push(File.expand_path(__FILE__+'/../..'))  require 'extend/pathname' +require 'exceptions'  # these are defined in global.rb, but we don't want to break our actual  # homebrew tree, and we do want to test everything :) @@ -26,4 +27,9 @@ at_exit { HOMEBREW_PREFIX.parent.rmtree }  # Test fixtures and files can be found relative to this path  TEST_FOLDER = Pathname.new(ABS__FILE__).parent.realpath +require 'fileutils' +module Homebrew extend self +  include FileUtils +end +  require 'test/unit' # must be after at_exit diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index de16df994..6dedf764b 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -1,38 +1,3 @@ -class ExecutionError <RuntimeError -  attr :exit_status -  attr :command - -  def initialize cmd, args = [], es = nil -    @command = cmd -    super "Failure while executing: #{cmd} #{pretty(args)*' '}" -    @exit_status = es.exitstatus rescue 1 -  end - -  def was_running_configure? -    @command == './configure' -  end - -  private - -  def pretty args -    args.collect do |arg| -      if arg.to_s.include? ' ' -        "'#{ arg.gsub "'", "\\'" }'" -      else -        arg -      end -    end -  end -end - -class BuildError <ExecutionError -  attr :env - -  def initialize cmd, args = [], es = nil -    super -    @env = ENV.to_hash -  end -end  class Tty    class <<self @@ -112,7 +77,10 @@ end  # Kernel.system but with exceptions  def safe_system cmd, *args -  raise ExecutionError.new(cmd, args, $?) unless Homebrew.system(cmd, *args) +  unless Homebrew.system cmd, *args +    args = args.map{ |arg| arg.gsub " ", "\\ " } * " " +    raise "Failure while executing: #{cmd} #{args}" +  end  end  # prints no output @@ -263,28 +231,97 @@ def nostdout    end  end -def dump_build_env env -  puts "\"--use-llvm\" was specified" if ARGV.include? '--use-llvm' +module MacOS extend self +  def gcc_42_build_version +    `/usr/bin/gcc-4.2 -v 2>&1` =~ /build (\d{4,})/ +    if $1 +      $1.to_i +    elsif system "/usr/bin/which gcc" +      # Xcode 3.0 didn't come with gcc-4.2 +      # We can't change the above regex to use gcc because the version numbers +      # are different and thus, not useful. +      # FIXME I bet you 20 quid this causes a side effect — magic values tend to +      401 +    else +      nil +    end +  end -  %w[ CC CXX LD ].each do |k| -    value = env[k] -    if value -      results = value -      if File.exists? value and File.symlink? value -        target = Pathname.new(value) -        results += " => #{target.realpath}" -      end -      puts "#{k}: #{results}" +  def gcc_40_build_version +    `/usr/bin/gcc-4.0 -v 2>&1` =~ /build (\d{4,})/ +    if $1 +      $1.to_i +    else +      nil      end    end -  %w[ CFLAGS CXXFLAGS CPPFLAGS LDFLAGS MACOSX_DEPLOYMENT_TARGET MAKEFLAGS PKG_CONFIG_PATH -      HOMEBREW_DEBUG HOMEBREW_VERBOSE HOMEBREW_USE_LLVM HOMEBREW_SVN ].each do |k| -    value = env[k] -    puts "#{k}: #{value}" if value +  def llvm_build_version +    if MACOS_VERSION >= 10.6 +      xcode_path = `/usr/bin/xcode-select -print-path`.chomp +      return nil if xcode_path.empty? +      `#{xcode_path}/usr/bin/llvm-gcc -v 2>&1` =~ /LLVM build (\d{4,})/ +      $1.to_i +    end    end -end  def x11_installed?    Pathname.new('/usr/X11/lib/libpng.dylib').exist? -end
\ No newline at end of file +end + +  def macports_or_fink_installed? +    # See these issues for some history: +    # http://github.com/mxcl/homebrew/issues/#issue/13 +    # http://github.com/mxcl/homebrew/issues/#issue/41 +    # http://github.com/mxcl/homebrew/issues/#issue/48 + +    %w[port fink].each do |ponk| +      path = `/usr/bin/which -s #{ponk}` +      return ponk unless path.empty? +    end + +    # we do the above check because macports can be relocated and fink may be +    # able to be relocated in the future. This following check is because if +    # fink and macports are not in the PATH but are still installed it can +    # *still* break the build -- because some build scripts hardcode these paths: +    %w[/sw/bin/fink /opt/local/bin/port].each do |ponk| +      return ponk if File.exist? ponk +    end + +    # finally, sometimes people make their MacPorts or Fink read-only so they +    # can quickly test Homebrew out, but still in theory obey the README's +    # advise to rename the root directory. This doesn't work, many build scripts +    # error out when they try to read from these now unreadable directories. +    %w[/sw /opt/local].each do |path| +      path = Pathname.new(path) +      return path if path.exist? and not path.readable? +    end + +    false +  end +end + +module GitHub extend self +  def issues_for_formula name +    # bit basic as depends on the issue at github having the exact name of the +    # formula in it. Which for stuff like objective-caml is unlikely. So we +    # really should search for aliases too. + +    name = f.name if Formula === name + +    require 'open-uri' +    require 'yaml' + +    issues = [] + +    open "http://github.com/api/v2/yaml/issues/search/mxcl/homebrew/open/#{name}" do |f| +      YAML::load(f.read)['issues'].each do |issue| +        issues << 'http://github.com/mxcl/homebrew/issues/#issue/%s' % issue['number'] +      end +    end + +    issues +  rescue +    [] +  end +end @@ -38,313 +38,48 @@ if MACOS_VERSION < 10.5    abort "Homebrew requires Leopard or higher. For Tiger support, see:\nhttp://github.com/sceaga/homebrew/tree/tiger"  end -def dump_config -  require 'hardware' -  sha = `cd #{HOMEBREW_REPOSITORY} && git rev-parse --verify HEAD 2> /dev/null`.chomp -  sha = "(none)" if sha.empty? -  bits = Hardware.bits -  cores = Hardware.cores_as_words -  kernel_arch = `uname -m`.chomp -  system_ruby = Pathname.new("/usr/bin/ruby") - -  llvm,   llvm_msg   = _compiler_recommendation llvm_build,   RECOMMENDED_LLVM -  gcc_42, gcc_42_msg = _compiler_recommendation gcc_42_build, RECOMMENDED_GCC_42 -  gcc_40, gcc_40_msg = _compiler_recommendation gcc_40_build, RECOMMENDED_GCC_40 -  xcode = xcode_version || "?" - -  puts <<-EOS -HOMEBREW_VERSION: #{HOMEBREW_VERSION} -HEAD: #{sha} -HOMEBREW_PREFIX: #{HOMEBREW_PREFIX} -HOMEBREW_CELLAR: #{HOMEBREW_CELLAR} -HOMEBREW_REPOSITORY: #{HOMEBREW_REPOSITORY} -HOMEBREW_LIBRARY_PATH: #{HOMEBREW_LIBRARY_PATH} -Hardware: #{cores}-core #{bits}-bit #{Hardware.intel_family} -OS X: #{MACOS_FULL_VERSION} -Kernel Architecture: #{kernel_arch} -Ruby: #{RUBY_VERSION}-#{RUBY_PATCHLEVEL} -/usr/bin/ruby => #{system_ruby.realpath} -Xcode: #{xcode} -GCC-4.0: #{gcc_40 ? "build #{gcc_40}" : "N/A"} #{gcc_42_msg} -GCC-4.2: #{gcc_42 ? "build #{gcc_42}" : "N/A"} #{gcc_40_msg} -LLVM: #{llvm ? "build #{llvm}" : "N/A" } #{llvm_msg} -MacPorts or Fink? #{macports_or_fink_installed?} -X11 installed? #{x11_installed?} -EOS +def require? path +  require path.to_s.chomp +rescue LoadError => e +  # HACK :( because we should raise on syntax errors but +  # not if the file doesn't exist. TODO make robust! +  raise unless e.to_s.include? path  end  begin -  require 'brew.h' - -  case arg = ARGV.shift -  when '--cache' -    if ARGV.named.empty? -      puts HOMEBREW_CACHE -    else -      puts ARGV.formulae.collect {|f| f.cached_download} -    end -  when '--prefix' -    if ARGV.named.empty? -      puts HOMEBREW_PREFIX -    else -      puts ARGV.formulae.collect {|f| f.prefix} -    end -  when '--repository' -    puts HOMEBREW_REPOSITORY -  when '--cellar' -    if ARGV.named.empty? -      puts HOMEBREW_CELLAR -    else -      puts ARGV.formulae.collect {|f| HOMEBREW_CELLAR+f.name} -    end -  when '--config' -    dump_config -  when '--env' -    require 'hardware' -    require 'extend/ENV' -    ENV.extend(HomebrewEnvExtension) -    ENV.setup_build_environment -    dump_build_env ENV - -    when 'home', 'homepage' -      if ARGV.named.empty? -        exec "open", HOMEBREW_WWW -      else -        exec "open", *ARGV.formulae.collect {|f| f.homepage} -      end - -    when 'ls', 'list' -      if ARGV.flag? '--unbrewed' -        dirs = HOMEBREW_PREFIX.children.select { |pn| pn.directory? }.collect { |pn| pn.basename.to_s } -        dirs -= ['Library', 'Cellar', '.git'] -        Dir.chdir HOMEBREW_PREFIX -        exec 'find', *dirs + %w[-type f ( ! -iname .ds_store ! -iname brew )] -      elsif ARGV.flag? '--versions' -        if ARGV.named.empty? -          to_list = HOMEBREW_CELLAR.children.select { |pn| pn.directory? } -        else -          to_list = ARGV.named.collect { |n| HOMEBREW_CELLAR+n }.select { |pn| pn.exist? } -        end -        to_list.each do |d| -          versions = d.children.select { |pn| pn.directory? }.collect { |pn| pn.basename.to_s } -          puts "#{d.basename} #{versions *' '}" -        end -      elsif ARGV.named.empty? -        ENV['CLICOLOR']=nil -        exec 'ls', *ARGV.options_only<<HOMEBREW_CELLAR if HOMEBREW_CELLAR.exist? -      elsif ARGV.verbose? or not $stdout.tty? -        exec "find", *ARGV.kegs+%w[-not -type d -print] -      else -        ARGV.kegs.each { |keg| PrettyListing.new keg } -      end - -    when 'search', '-S' -      if ARGV.include? '--macports' -        exec "open", "http://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" -      elsif ARGV.include? '--fink' -        exec "open", "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" -      end - -      check_for_blacklisted_formula(ARGV.named) -      puts_columns search_brews(ARGV.first) - -    when 'edit' -      if ARGV.named.empty? -        # EDITOR isn't a good fit here, we need a GUI client that actually has -        # a UI for projects, so apologies if this wasn't what you expected, -        # please improve it! :) -        exec 'mate', *Dir["#{HOMEBREW_REPOSITORY}/Library/*"]<< -                          "#{HOMEBREW_REPOSITORY}/bin/brew"<< -                          "#{HOMEBREW_REPOSITORY}/README.md" -      else -        require 'formula' -        # Don't use ARGV.formulae as that will throw if the file doesn't parse -        paths = ARGV.named.collect do |name| -          path = Formula.path(Formula.resolve_alias(name)) -          unless File.exist? path -            raise FormulaUnavailableError, name -          else -            path -          end -        end -        exec_editor(*paths) -      end - -  when 'up', 'update' -    abort "Please `brew install git' first." unless system "/usr/bin/which -s git" - -    require 'update' -    updater = RefreshBrew.new -    unless updater.update_from_masterbrew! -      puts "Already up-to-date." -    else -      updater.report -    end - -    when 'ln', 'link' -      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', 'remove' -      if ARGV.flag? "--force" -        require 'formula' -        ARGV.formulae.each do |f| -          formula_cellar = f.prefix.parent -          next unless File.exist? formula_cellar -          puts "Uninstalling #{f.name}..." -          formula_cellar.children do |k| -            keg = Keg.new(k) -            keg.unlink -          end - -          formula_cellar.rmtree -        end -      else -        begin -          ARGV.kegs.each do |keg| -            puts "Uninstalling #{keg}..." -            keg.unlink -            keg.uninstall -          end -        rescue MultipleVersionsInstalledError => e -          onoe e -          puts "Use `brew remove --force #{e.name}` to remove all versions." -        end -      end - -    when 'prune' -      prune - -    when 'create' -      if ARGV.include? '--macports' -        exec "open", "http://www.macports.org/ports.php?by=name&substr=#{ARGV.next}" -      elsif ARGV.include? '--fink' -        exec "open", "http://pdb.finkproject.org/pdb/browse.php?summary=#{ARGV.next}" -      elsif ARGV.named.empty? -        raise UsageError -      else -        exec_editor(*ARGV.named.collect {|name| make name}) -      end - -    when 'diy', 'configure' -      puts diy - -    when 'info', 'abv' -      if ARGV.named.empty? -        if ARGV.include? "--all" -          require 'formula' -          Formula.all.each do |f| -            info f -            puts '---' -          end -        else -          puts `ls #{HOMEBREW_CELLAR} | wc -l`.strip+" kegs, "+HOMEBREW_CELLAR.abv -        end -      elsif ARGV[0][0..6] == 'http://' or ARGV[0][0..7] == 'https://' or ARGV[0][0..5] == 'ftp://' -        path = Pathname.new(ARGV.shift) -        /(.*?)[-_.]?#{path.version}/.match path.basename -        unless $1.to_s.empty? -          name = $1 -        else -          name = path.stem -        end -        puts "#{name} #{path.version}" -      else -        ARGV.formulae.each{ |f| info f } -      end - -  when 'cleanup' -    if ARGV.named.empty? -      require 'formula' -      HOMEBREW_CELLAR.children.each do |rack| -        begin -          cleanup(rack.basename.to_s) if rack.directory? -        rescue FormulaUnavailableError => e -          opoo "Formula not found for #{e.name}" -        end -      end -      prune # seems like a good time to do some additional cleanup -    else -      ARGV.named.each { |name| cleanup name} -    end - -  when 'install' -    check_for_blacklisted_formula(ARGV.named) -    brew_install - -  when 'log' -    Dir.chdir HOMEBREW_REPOSITORY -    args = ARGV.options_only -    args += ARGV.formulae.map{ |formula| formula.path } unless ARGV.named.empty? -    exec "git", "log", *args - -  # For each formula given, show which other formulas depend on it. -  # We only go one level up, ie. direct dependencies. -  when 'uses' -    uses = ARGV.formulae.map{ |f| Formula.all.select{ |ff| ff.deps.include? f.name }.map{|f| f.name} }.flatten.uniq -    if ARGV.include? "--installed" -      uses = uses.select { |f| f = HOMEBREW_CELLAR+f; f.directory? and not f.subdirs.empty? } -    end -    puts uses.sort - -  when 'deps' -    if ARGV.include?('--all') -      require 'formula' -      Formula.all.each do |f| -        puts "#{f.name}:#{f.deps.join(' ')}" -      end -    elsif ARGV.include?("-1") or ARGV.include?("--1") -      puts ARGV.formulae.map {|f| f.deps or []}.flatten.uniq.sort -    else -      require 'formula_installer' -      puts ARGV.formulae.map {|f| FormulaInstaller.expand_deps(f).map {|f| f.name} }.flatten.uniq.sort -    end - -  when 'cat' -    Dir.chdir HOMEBREW_REPOSITORY -    exec "cat", ARGV.formulae.first.path, *ARGV.options_only - -  when 'outdated' -    outdated_brews.each do |keg, name, version| -      if $stdout.tty? and not ARGV.flag? '--quiet' -        versions = keg.cd{ Dir['*'] }.join(', ') -        puts "#{name} (#{versions} < #{version})" -      else -        puts name -      end +  aliases = {'ls' => :list, +             'homepage' => :home, +             '-S' => :search, +             'up' => :update, +             'ln' => :link, +             'rm' => :uninstall, +             'remove' => :uninstall, +             'configure' => :diy, +             'abv' => :info, +             'dr' => :doctor, +             '--repo' => '--repository'} + +  cmd = ARGV.shift +  cmd = aliases[cmd] if aliases[cmd] + +  # Add example external commands to PATH before checking. +  ENV['PATH'] += ":#{HOMEBREW_REPOSITORY}/Library/Contributions/examples" + +  if system "/usr/bin/which -s brew-#{cmd}" +    %w[CACHE CELLAR LIBRARY_PATH PREFIX REPOSITORY].each do |e| +      ENV["HOMEBREW_#{e}"] = eval "HOMEBREW_#{e}"      end - -  when 'doctor', 'dr' -    require 'brew_doctor' -    brew_doctor - +    exec "brew-#{cmd}", *ARGV +  elsif require? `/usr/bin/which brew-#{cmd}.rb` +    exit 0 +  elsif require? HOMEBREW_REPOSITORY/"Library/Homebrew/cmd"/cmd +    Homebrew.send cmd.to_s.gsub('-', '_')    else -    # Add example external commands to PATH before checking. -    ENV['PATH'] += ":#{HOMEBREW_REPOSITORY}/Library/Contributions/examples" - -    # Check for an external shell command -    if system "/usr/bin/which -s brew-#{arg}" -      # Add some Homebrew vars to the ENV -      %w(CACHE CELLAR LIBRARY_PATH PREFIX REPOSITORY).each do |e| -        ENV["HOMEBREW_#{e}"] = eval("HOMEBREW_#{e}") -      end -      exec("brew-#{arg}", *ARGV) -    end - -    # Check for an external ruby command -    external_rb = `/usr/bin/which brew-#{arg}.rb`.chomp -    unless external_rb.empty? -      require external_rb -      exit 0 -    end -      # Check for git commands -    if %w(branch checkout pull push rebase reset).include? arg -      onoe "Unknown command '#{arg}' (did you mean 'git #{arg}'?)" +    if %w[branch checkout pull push rebase reset].include? cmd +      onoe "Unknown command: #{cmd} (did you mean `git #{cmd}'?)"      else -      onoe "Unknown command '#{arg}'" +      onoe "Unknown command: #{cmd}"      end    end @@ -362,24 +97,28 @@ rescue Interrupt => e    puts # seemingly a newline is typical    exit 130  rescue BuildError => e +  require 'cmd/--config' +  require 'cmd/--env' +    e.backtrace[1] =~ %r{Library/Formula/(.+)\.rb:(\d+)}    formula_name = $1    error_line = $2 +    puts "Exit status: #{e.exit_status}"    puts    puts "http://github.com/mxcl/homebrew/blob/master/Library/Formula/#{formula_name}.rb#L#{error_line}"    puts    ohai "Environment" -  dump_config +  puts Homebrew.config_s    puts    ohai "Build Flags" -  dump_build_env e.env +  Homebrew.dump_build_env e.env    onoe e    puts PLEASE_REPORT_BUG    # this feature can be slow (depends on network conditions and if github is up)    # so ideally we'd show feedback, eg. "checking for existing issues..." and    # then replace that string with the following when the github api returns -  issues = issues_for_formula(formula_name) +  issues = GitHub.issues_for_formula formula_name    puts "These existing issues may help you:", *issues unless issues.empty?    if e.was_running_configure?      puts "It looks like an autotools configure failed." | 
