Skip to content
Snippets Groups Projects
formula_installer.rb 18.6 KiB
Newer Older
require 'cxxstdlib'
require 'exceptions'
Max Howell's avatar
Max Howell committed
require 'formula'
Max Howell's avatar
Max Howell committed
require 'keg'
require 'tab'
require 'bottles'
require 'caveats'
Adam Vandenberg's avatar
Adam Vandenberg committed
require 'cleaner'
require 'formula_cellar_checks'
require 'install_renamed'
require 'hooks/bottles'
Max Howell's avatar
Max Howell committed

class FormulaInstaller
  include FormulaCellarChecks

  def self.mode_attr_accessor(*names)
    attr_accessor(*names)
    names.each do |name|
      predicate = "#{name}?"
      define_method(predicate) { !!send(name) }
      private(predicate)
    end
  attr_reader :formula
  attr_accessor :options
  mode_attr_accessor :show_summary_heading, :show_header
  mode_attr_accessor :build_from_source, :build_bottle, :force_bottle
Jack Nagel's avatar
Jack Nagel committed
  mode_attr_accessor :ignore_deps, :only_deps, :interactive, :git
  mode_attr_accessor :verbose, :debug, :quieter
  def initialize(formula)
    @formula = formula
    @show_header = false
    @ignore_deps = false
    @only_deps = false
    @build_from_source = false
    @build_bottle = false
    @force_bottle = false
    @interactive = false
Jack Nagel's avatar
Jack Nagel committed
    @git = false
    @verbose = false
    @quieter = false
Jack Nagel's avatar
Jack Nagel committed
    @debug = false
    @poured_bottle = false
    @pour_failed   = false
  def pour_bottle? install_bottle_options={:warn=>false}
    return true if Homebrew::Hooks::Bottles.formula_has_bottle?(formula)

    bottle = formula.bottle
    return true  if force_bottle? && bottle
    return false if build_from_source? || build_bottle? || interactive?
    return false unless options.empty?

    return true if formula.local_bottle_path
    return false unless bottle && formula.pour_bottle?
    unless bottle.compatible_cellar?
      if install_bottle_options[:warn]
        opoo "Building source; cellar of #{formula.name}'s bottle is #{bottle.cellar}"
  def install_bottle_for?(dep, build)
    return pour_bottle? if dep == formula
    return false if build_from_source?
    return false unless dep.bottle && dep.pour_bottle?
    return false unless build.used_options.empty?
    return false unless dep.bottle.compatible_cellar?
    return true
  def prelude
    verify_deps_exist unless ignore_deps?
    lock
    check_install_sanity
  end

      formula.recursive_dependencies.map(&:to_formula)
    rescue TapFormulaUnavailableError => e
      if Homebrew.install_tap(e.user, e.repo)
        retry
      else
        raise
      end
    end
  rescue FormulaUnavailableError => e
    e.dependent = formula.name
  def check_install_sanity
    raise FormulaInstallationAlreadyAttemptedError, formula if @@attempted.include?(formula)
      unlinked_deps = formula.recursive_dependencies.map(&:to_formula).select do |dep|
        dep.installed? and not dep.keg_only? and not dep.linked_keg.directory?
      raise CannotInstallFormulaError,
        "You must `brew link #{unlinked_deps*' '}' before #{formula.name} can be installed" unless unlinked_deps.empty?
  def build_bottle_preinstall
    @etc_var_glob ||= "#{HOMEBREW_PREFIX}/{etc,var}/**/*"
    @etc_var_preinstall = Dir[@etc_var_glob]
  end

  def build_bottle_postinstall
    @etc_var_postinstall = Dir[@etc_var_glob]
    (@etc_var_postinstall - @etc_var_preinstall).each do |file|
      Pathname.new(file).cp_path_sub(HOMEBREW_PREFIX, formula.bottle_prefix)
Max Howell's avatar
Max Howell committed
  def install
    # not in initialize so upgrade can unlink the active keg before calling this
    # function but after instantiating this class so that it can avoid having to
    # relink the active keg if possible (because it is slow).
    if formula.linked_keg.directory?
      # some other version is already installed *and* linked
      raise CannotInstallFormulaError, <<-EOS.undent
        #{formula.name}-#{formula.linked_keg.resolved_path.basename} already installed
        To install this version, first `brew unlink #{formula.name}'
    compute_and_install_dependencies unless ignore_deps?
    if build_bottle? && (arch = ARGV.bottle_arch) && !Hardware::CPU.optimization_flags.include?(arch)
      raise "Unrecognized architecture for --bottle-arch: #{arch}"
    end

    formula.deprecated_flags.each do |deprecated_option|
      old_flag = deprecated_option.old_flag
      new_flag = deprecated_option.current_flag
      opoo "#{formula.name}: #{old_flag} was deprecated; using #{new_flag} instead!"
    oh1 "Installing #{Tty.green}#{formula.name}#{Tty.reset}" if show_header?
Max Howell's avatar
Max Howell committed

    @@attempted << formula
    if pour_bottle?(:warn => true)
      begin
      rescue => e
        raise if ARGV.homebrew_developer?
        @pour_failed = true
        onoe e.message
        opoo "Bottle installation failed: building from source."
      else
        @poured_bottle = true
    build_bottle_preinstall if build_bottle?
    unless @poured_bottle
      compute_and_install_dependencies if @pour_failed and not ignore_deps?
Max Howell's avatar
Max Howell committed
      build
      clean
Max Howell's avatar
Max Howell committed

    build_bottle_postinstall if build_bottle?
    opoo "Nothing was installed to #{formula.prefix}" unless formula.installed?
  def check_conflicts
    return if ARGV.force?

    conflicts = formula.conflicts.select do |c|
      f = Formulary.factory(c.name)
      f.linked_keg.exist? && f.opt_prefix.exist?
    raise FormulaConflictError.new(formula, conflicts) unless conflicts.empty?
  def compute_and_install_dependencies
    req_map, req_deps = expand_requirements

    check_requirements(req_map)

    deps = expand_dependencies(req_deps + formula.deps)
    if deps.empty? and only_deps?
      puts "All dependencies for #{formula.name} are satisfied."
    else
      install_dependencies(deps)
    end
  def check_requirements(req_map)
    fatals = []

    req_map.each_pair do |dependent, reqs|
      reqs.each do |req|
        puts "#{dependent}: #{req.message}"
        fatals << req if req.fatal?
    raise UnsatisfiedRequirements.new(fatals) unless fatals.empty?
  def install_requirement_default_formula?(req, dependent, build)
    return false unless req.default_formula?
    return true unless req.satisfied?
    install_bottle_for?(dependent, build) || build_bottle?
  def expand_requirements
    unsatisfied_reqs = Hash.new { |h, k| h[k] = [] }
    deps = []
    formulae = [formula]
      f.recursive_requirements do |dependent, req|
        build = effective_build_options_for(dependent)

        if (req.optional? || req.recommended?) && build.without?(req)
          Requirement.prune
        elsif req.build? && install_bottle_for?(dependent, build)
          Requirement.prune
        elsif install_requirement_default_formula?(req, dependent, build)
          dep = req.to_dependency
          deps.unshift(dep)
          formulae.unshift(dep.to_formula)
          Requirement.prune
        else
          unsatisfied_reqs[dependent] << req
  def expand_dependencies(deps)
    expanded_deps = Dependency.expand(formula, deps) do |dependent, dep|
      options = inherited_options[dep.name] = inherited_options_for(dep)
      build = effective_build_options_for(
        dependent,
        inherited_options.fetch(dependent.name, [])
      )

      if (dep.optional? || dep.recommended?) && build.without?(dep)
        Dependency.prune
      elsif dep.build? && install_bottle_for?(dependent, build)
        Dependency.prune
      elsif dep.satisfied?(options)
        Dependency.skip
    expanded_deps.map { |dep| [dep, inherited_options[dep.name]] }
  def effective_build_options_for(dependent, inherited_options=[])
    args  = dependent.build.used_options
    args |= dependent == formula ? options : inherited_options
    args |= Tab.for_formula(dependent).used_options
    BuildOptions.new(args, dependent.options)
  def inherited_options_for(dep)
    inherited_options = Options.new
    u = Option.new("universal")
    if (options.include?(u) || formula.require_universal_deps?) && !dep.build? && dep.to_formula.option_defined?(u)
      inherited_options << u
    inherited_options
  def install_dependencies(deps)
    if deps.length > 1
      oh1 "Installing dependencies for #{formula.name}: #{Tty.green}#{deps.map(&:first)*", "}#{Tty.reset}"
    deps.each { |dep, options| install_dependency(dep, options) }
    @show_header = true unless deps.empty?
  class DependencyInstaller < FormulaInstaller
    def initialize(*)
      super
      @ignore_deps = true
    end

    def sanitized_ARGV_options
      args = super
      args.delete "--ignore-dependencies"
      args
    end
  end

  def install_dependency(dep, inherited_options)
    df = dep.to_formula
      linked_keg = Keg.new(df.linked_keg.resolved_path)
      linked_keg.unlink
    end

    if df.installed?
      installed_keg = Keg.new(df.prefix)
      tmp_keg = Pathname.new("#{installed_keg}.tmp")
      installed_keg.rename(tmp_keg)
    end
    fi = DependencyInstaller.new(df)
    fi.options           |= dep.options
    fi.options           |= inherited_options
    fi.build_from_source  = build_from_source?
    fi.verbose            = verbose? && !quieter?
    fi.debug              = debug?
    oh1 "Installing #{formula.name} dependency: #{Tty.green}#{dep.name}#{Tty.reset}"
    fi.install
    fi.caveats
    fi.finish
    ignore_interrupts do
      tmp_keg.rename(installed_keg) if tmp_keg && !installed_keg.directory?
      linked_keg.link if linked_keg
    end
    ignore_interrupts { tmp_keg.rmtree if tmp_keg && tmp_keg.directory? }
Max Howell's avatar
Max Howell committed
  def caveats
    audit_installed if ARGV.homebrew_developer? and not formula.keg_only?
    c = Caveats.new(formula)
Jack Nagel's avatar
Jack Nagel committed

    unless c.empty?
      @show_summary_heading = true
      ohai 'Caveats', c.caveats
    end
Max Howell's avatar
Max Howell committed
  end

  def finish
    ohai 'Finishing up' if verbose?
Max Howell's avatar
Max Howell committed

    install_plist

    keg = Keg.new(formula.prefix)
    link(keg)
    fix_install_names(keg) if OS.mac?
Max Howell's avatar
Max Howell committed

    if build_bottle? && formula.post_install_defined?
      ohai "Not running post_install as we're building a bottle"
      puts "You can run it manually using `brew postinstall #{formula.name}`"
    else
      post_install
    end
    ohai "Summary" if verbose? or show_summary_heading?
    unlock if hold_locks?
Max Howell's avatar
Max Howell committed
  end
  def emoji
    ENV['HOMEBREW_INSTALL_BADGE'] || "\xf0\x9f\x8d\xba"
  end

  def summary
    s = ""
    s << "#{emoji}  " if MacOS.version >= :lion and not ENV['HOMEBREW_NO_EMOJI']
    s << "#{formula.prefix}: #{formula.prefix.abv}"
    s << ", built in #{pretty_duration build_time}" if build_time
    s
  end

Max Howell's avatar
Max Howell committed
  def build_time
Jack Nagel's avatar
Jack Nagel committed
    @build_time ||= Time.now - @start_time if @start_time && !interactive?
Max Howell's avatar
Max Howell committed
  end
  def sanitized_ARGV_options
    args << "--ignore-dependencies" if ignore_deps?
      args << "--build-bottle"
      args << "--bottle-arch=#{ARGV.bottle_arch}" if ARGV.bottle_arch
    end

Jack Nagel's avatar
Jack Nagel committed
    args << "--git" if git?
    args << "--interactive" if interactive?
    args << "--verbose" if verbose?
Jack Nagel's avatar
Jack Nagel committed
    args << "--debug" if debug?
    args << "--cc=#{ARGV.cc}" if ARGV.cc
    args << "--env=#{ARGV.env}" if ARGV.env
    if formula.head?
      args << "--HEAD"
    elsif formula.devel?
      args << "--devel"
    formula.options.each do |opt|
      name  = opt.name[/\A(.+)=\z$/, 1]
      value = ARGV.value(name)
      args << "--#{name}=#{value}" if name && value
    end

    sanitized_ARGV_options + options.as_flags
Max Howell's avatar
Max Howell committed
  def build
    FileUtils.rm Dir["#{HOMEBREW_LOGS}/#{formula.name}/*"]
Max Howell's avatar
Max Howell committed
    @start_time = Time.now
Max Howell's avatar
Max Howell committed

    # 1. formulae can modify ENV, so we must ensure that each
    #    installation has a pristine ENV when it starts, forking now is
Max Howell's avatar
Max Howell committed
    #    the easiest way to do this
    read, write = IO.pipe
    # I'm guessing this is not a good way to do this, but I'm no UNIX guru
    ENV['HOMEBREW_ERROR_PIPE'] = write.to_i.to_s

    args = %W[
      nice #{RUBY_PATH}
      -W0
      -I #{HOMEBREW_LIBRARY_PATH}
      #{HOMEBREW_LIBRARY_PATH}/build.rb
      #{formula.path}
    ].concat(build_argv)

    # Ruby 2.0+ sets close-on-exec on all file descriptors except for
    # 0, 1, and 2 by default, so we have to specify that we want the pipe
    # to remain open in the child process.
    args << { write => write } if RUBY_VERSION >= "2.0"

    pid = fork do
      rescue Exception => e
        Marshal.dump(e, write)
Max Howell's avatar
Max Howell committed
        write.close
Max Howell's avatar
Max Howell committed
      end
    end
    ignore_interrupts(:quietly) do # the child will receive the interrupt and marshal it back
      raise Marshal.load(data) unless data.nil? or data.empty?
Max Howell's avatar
Max Howell committed
      raise Interrupt if $?.exitstatus == 130
      raise "Suspicious installation failure" unless $?.success?
    raise "Empty installation" if Dir["#{formula.prefix}/*"].empty?
Jack Nagel's avatar
Jack Nagel committed
  rescue Exception
    ignore_interrupts do
      # any exceptions must leave us with nothing installed
      formula.prefix.rmtree if formula.prefix.directory?
      formula.rack.rmdir_if_possible
Max Howell's avatar
Max Howell committed
  end
Max Howell's avatar
Max Howell committed

    if formula.keg_only?
      begin
        keg.optlink
      rescue Keg::LinkError => e
        onoe "Failed to create #{formula.opt_prefix}"
        puts "Things that depend on #{formula.name} will probably not build."
        Homebrew.failed = true
      opoo "This keg was marked linked already, continuing anyway"
      keg.remove_linked_keg_record
Max Howell's avatar
Max Howell committed
    begin
      keg.link
    rescue Keg::ConflictError => e
Max Howell's avatar
Max Howell committed
      onoe "The `brew link` step did not complete successfully"
      puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
      puts
      puts "Possible conflicting files are:"
      mode = OpenStruct.new(:dry_run => true, :overwrite => true)
      keg.link(mode)
Max Howell's avatar
Max Howell committed
      @show_summary_heading = true
      Homebrew.failed = true
    rescue Keg::LinkError => e
      onoe "The `brew link` step did not complete successfully"
      puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
      puts e
      puts
      puts "You can try again using:"
      puts "  brew link #{formula.name}"
      @show_summary_heading = true
      Homebrew.failed = true
    rescue Exception => e
      onoe "An unexpected error occurred during the `brew link` step"
      puts "The formula built, but is not symlinked into #{HOMEBREW_PREFIX}"
      puts e
      puts e.backtrace if debug?
      @show_summary_heading = true
      ignore_interrupts { keg.unlink }
      Homebrew.failed = true
Max Howell's avatar
Max Howell committed
    end
Max Howell's avatar
Max Howell committed
  end

  def install_plist
    return unless formula.plist
    formula.plist_path.atomic_write(formula.plist)
    formula.plist_path.chmod 0644
  rescue Exception => e
    onoe "Failed to install plist file"
    ohai e, e.backtrace if debug?
    Homebrew.failed = true
  def fix_install_names(keg)
    keg.fix_install_names(:keg_only => formula.keg_only?)
    if @poured_bottle
      keg.relocate_install_names Keg::PREFIX_PLACEHOLDER, HOMEBREW_PREFIX.to_s,
        Keg::CELLAR_PLACEHOLDER, HOMEBREW_CELLAR.to_s, :keg_only => formula.keg_only?
Max Howell's avatar
Max Howell committed
  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."
Jack Nagel's avatar
Jack Nagel committed
    ohai e, e.backtrace if debug?
    Homebrew.failed = true
Max Howell's avatar
Max Howell committed
    @show_summary_heading = true
  end

  def clean
    ohai "Cleaning" if verbose?
    Cleaner.new(formula).clean
Max Howell's avatar
Max Howell committed
  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"
Jack Nagel's avatar
Jack Nagel committed
    ohai e, e.backtrace if debug?
    Homebrew.failed = true
Max Howell's avatar
Max Howell committed
    @show_summary_heading = true
  end

  def post_install
    formula.run_post_install
  rescue Exception => e
    opoo "The post-install step did not complete successfully"
    puts "You can try again using `brew postinstall #{formula.name}`"
Jack Nagel's avatar
Jack Nagel committed
    ohai e, e.backtrace if debug?
    Homebrew.failed = true
    @show_summary_heading = true
  end

Max Howell's avatar
Max Howell committed
  def pour
    if Homebrew::Hooks::Bottles.formula_has_bottle?(formula)
      return if Homebrew::Hooks::Bottles.pour_formula_bottle(formula)
    if formula.local_bottle_path
      downloader = LocalBottleDownloadStrategy.new(formula)
      downloader = formula.bottle
      downloader.verify_download_integrity(downloader.fetch)
Max Howell's avatar
Max Howell committed
    HOMEBREW_CELLAR.cd do
      downloader.stage
    end
    Pathname.glob("#{formula.bottle_prefix}/{etc,var}/**/*") do |path|
      path.extend(InstallRenamed)
      path.cp_path_sub(formula.bottle_prefix, HOMEBREW_PREFIX)
    FileUtils.rm_rf formula.bottle_prefix

    CxxStdlib.check_compatibility(
      formula, formula.recursive_dependencies,
      Keg.new(formula.prefix), MacOS.default_compiler
    tab = Tab.for_keg(formula.prefix)
    tab.poured_from_bottle = true
    tab.write
  def audit_check_output(output)
    if output
      opoo output
      @show_summary_heading = true
    end
    audit_check_output(check_PATH(formula.bin))
    audit_check_output(check_PATH(formula.sbin))
  private

  def hold_locks?
    @hold_locks || false
  end

  def lock
    if (@@locked ||= []).empty?
      formula.recursive_dependencies.each do |dep|
        @@locked << dep.to_formula
      end unless ignore_deps?
      @@locked.unshift(formula)
      @@locked.each(&:lock)
      @hold_locks = true
    end
  end

  def unlock
    if hold_locks?
      @@locked.each(&:unlock)
      @@locked.clear
      @hold_locks = false
    end
  end
Max Howell's avatar
Max Howell committed
end


class Formula
Misty De Meo's avatar
Misty De Meo committed
    s = "This formula is keg-only, which means it was not symlinked into #{HOMEBREW_PREFIX}."
    s << "\n\n#{keg_only_reason.to_s}"
    if lib.directory? or include.directory?
      s <<
        <<-EOS.undent_________________________________________________________72
Max Howell's avatar
Max Howell committed


        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 to your
        build variables:
Max Howell's avatar
Max Howell committed

Jack Nagel's avatar
Jack Nagel committed
      s << "    LDFLAGS:  -L#{opt_lib}\n" if lib.directory?
      s << "    CPPFLAGS: -I#{opt_include}\n" if include.directory?
    end
    s << "\n"
Max Howell's avatar
Max Howell committed
  end
Max Howell's avatar
Max Howell committed
end