<commit_before>require "cxxstdlib"
require "exceptions"
require "formula"
require "keg"
require "tab"
require "utils/bottles"
require "caveats"
require "cleaner"
require "formula_cellar_checks"
require "install_renamed"
require "cmd/postinstall"
require "hooks/bottles"
require "debrew"
require "sandbox"
require "emoji"
require "development_tools"

class FormulaInstaller
  include FormulaCellarChecks
  extend Predicable

  def self.mode_attr_accessor(*names)
    attr_accessor(*names)
    private(*names)
    names.each do |name|
      predicate = "#{name}?"
      define_method(predicate) do
        send(name) ? true : false
      end
      private(predicate)
    end
  end

  attr_reader :formula
  attr_accessor :options, :build_bottle, :invalid_option_names
  attr_accessor :installed_as_dependency, :installed_on_request
  mode_attr_accessor :show_summary_heading, :show_header
  mode_attr_accessor :build_from_source, :force_bottle
  mode_attr_accessor :ignore_deps, :only_deps, :interactive, :git
  mode_attr_accessor :verbose, :debug, :quieter, :link_keg

  def initialize(formula)
    @formula = formula
    @link_keg = !formula.keg_only?
    @show_header = false
    @ignore_deps = false
    @only_deps = false
    @build_from_source = ARGV.build_from_source? || ARGV.build_all_from_source?
    @build_bottle = false
    @force_bottle = ARGV.force_bottle?
    @interactive = false
    @git = false
    @verbose = ARGV.verbose?
    @quieter = ARGV.quieter?
    @debug = ARGV.debug?
    @installed_as_dependency = false
    @installed_on_request = true
    @options = Options.new
    @invalid_option_names = []
    @requirement_messages = []
    @poured_bottle = false
    @pour_failed   = false
  end

  def self.attempted
    @attempted ||= Set.new
  end

  # When no build tools are available and build flags are passed through ARGV,
  # it's necessary to interrupt the user before any sort of installation
  # can proceed. Only invoked when the user has no developer tools.
  def self.prevent_build_flags
    build_flags = ARGV.collect_build_flags

    raise BuildFlagsError, build_flags unless build_flags.empty?
  end

  def build_bottle?
    return false unless @build_bottle
    !formula.bottle_disabled?
  end

  def pour_bottle?(install_bottle_options = { warn: false })
    return true if Homebrew::Hooks::Bottles.formula_has_bottle?(formula)

    return false if @pour_failed

    return false if !formula.bottled? && !formula.local_bottle_path
    return true  if force_bottle?
    return false if build_from_source? || build_bottle? || interactive?
    return false if ARGV.cc
    return false unless options.empty?
    return false if formula.bottle_disabled?
    unless formula.pour_bottle?
      if install_bottle_options[:warn] && formula.pour_bottle_check_unsatisfied_reason
        opoo <<~EOS
          Building #{formula.full_name} from source:
            #{formula.pour_bottle_check_unsatisfied_reason}
        EOS
      end
      return false
    end

    bottle = formula.bottle_specification
    unless bottle.compatible_cellar?
      if install_bottle_options[:warn]
        opoo <<~EOS
          Building #{formula.full_name} from source:
            The bottle needs a #{bottle.cellar} Cellar (yours is #{HOMEBREW_CELLAR}).
        EOS
      end
      return false
    end

    true
  end

  def install_bottle_for?(dep, build)
    return pour_bottle? if dep == formula
    return false if ARGV.build_formula_from_source?(dep)
    return false unless dep.bottle && dep.pour_bottle?
    return false unless build.used_options.empty?
    return false unless dep.bottle.compatible_cellar?
    true
  end

  def prelude
    Tab.clear_cache
    verify_deps_exist unless ignore_deps?
    lock
    check_install_sanity
  end

  def verify_deps_exist
    begin
      compute_dependencies
    rescue TapFormulaUnavailableError => e
      raise if e.tap.installed?

      e.tap.install
      retry
    end
  rescue FormulaUnavailableError => e
    e.dependent = formula.full_name
    raise
  end

  def check_install_sanity
    raise FormulaInstallationAlreadyAttemptedError, formula if self.class.attempted.include?(formula)

    return if ignore_deps?

    recursive_deps = formula.recursive_dependencies
    recursive_formulae = recursive_deps.map(&:to_formula)
    recursive_runtime_deps = formula.recursive_dependencies.reject(&:build?)
    recursive_runtime_formulae = recursive_runtime_deps.map(&:to_formula)

    recursive_dependencies = []
    recursive_formulae.each do |dep|
      dep_recursive_dependencies = dep.recursive_dependencies.map(&:to_s)
      if dep_recursive_dependencies.include?(formula.name)
        recursive_dependencies << "#{formula.full_name} depends on #{dep.full_name}"
        recursive_dependencies << "#{dep.full_name} depends on #{formula.full_name}"
      end
    end

    unless recursive_dependencies.empty?
      raise CannotInstallFormulaError, <<~EOS
        #{formula.full_name} contains a recursive dependency on itself:
          #{recursive_dependencies.join("\n  ")}
      EOS
    end

    if recursive_formulae.flat_map(&:recursive_dependencies).map(&:to_s).include?(formula.name)
      raise CannotInstallFormulaError, <<~EOS
        #{formula.full_name} contains a recursive dependency on itself!
      EOS
    end

    version_hash = {}
    version_conflicts = Set.new
    recursive_runtime_formulae.each do |f|
      name = f.name
      unversioned_name, = name.split("@")
      version_hash[unversioned_name] ||= Set.new
      version_hash[unversioned_name] << name
      next if version_hash[unversioned_name].length < 2
      version_conflicts += version_hash[unversioned_name]
    end
    unless version_conflicts.empty?
      raise CannotInstallFormulaError, <<~EOS
        #{formula.full_name} contains conflicting version recursive dependencies:
          #{version_conflicts.to_a.join ", "}
        View these with `brew deps --tree #{formula.full_name}`.
      EOS
    end

    pinned_unsatisfied_deps = recursive_deps.select do |dep|
      dep.to_formula.pinned? && !dep.satisfied?(inherited_options_for(dep))<commit_msg>formula_installer: allow version mismatched python deps<commit_after>require "cxxstdlib"
require "exceptions"
require "formula"
require "keg"
require "tab"
require "utils/bottles"
require "caveats"
require "cleaner"
require "formula_cellar_checks"
require "install_renamed"
require "cmd/postinstall"
require "hooks/bottles"
require "debrew"
require "sandbox"
require "emoji"
require "development_tools"

class FormulaInstaller
  include FormulaCellarChecks
  extend Predicable

  def self.mode_attr_accessor(*names)
    attr_accessor(*names)
    private(*names)
    names.each do |name|
      predicate = "#{name}?"
      define_method(predicate) do
        send(name) ? true : false
      end
      private(predicate)
    end
  end

  attr_reader :formula
  attr_accessor :options, :build_bottle, :invalid_option_names
  attr_accessor :installed_as_dependency, :installed_on_request
  mode_attr_accessor :show_summary_heading, :show_header
  mode_attr_accessor :build_from_source, :force_bottle
  mode_attr_accessor :ignore_deps, :only_deps, :interactive, :git
  mode_attr_accessor :verbose, :debug, :quieter, :link_keg

  def initialize(formula)
    @formula = formula
    @link_keg = !formula.keg_only?
    @show_header = false
    @ignore_deps = false
    @only_deps = false
    @build_from_source = ARGV.build_from_source? || ARGV.build_all_from_source?
    @build_bottle = false
    @force_bottle = ARGV.force_bottle?
    @interactive = false
    @git = false
    @verbose = ARGV.verbose?
    @quieter = ARGV.quieter?
    @debug = ARGV.debug?
    @installed_as_dependency = false
    @installed_on_request = true
    @options = Options.new
    @invalid_option_names = []
    @requirement_messages = []
    @poured_bottle = false
    @pour_failed   = false
  end

  def self.attempted
    @attempted ||= Set.new
  end

  # When no build tools are available and build flags are passed through ARGV,
  # it's necessary to interrupt the user before any sort of installation
  # can proceed. Only invoked when the user has no developer tools.
  def self.prevent_build_flags
    build_flags = ARGV.collect_build_flags

    raise BuildFlagsError, build_flags unless build_flags.empty?
  end

  def build_bottle?
    return false unless @build_bottle
    !formula.bottle_disabled?
  end

  def pour_bottle?(install_bottle_options = { warn: false })
    return true if Homebrew::Hooks::Bottles.formula_has_bottle?(formula)

    return false if @pour_failed

    return false if !formula.bottled? && !formula.local_bottle_path
    return true  if force_bottle?
    return false if build_from_source? || build_bottle? || interactive?
    return false if ARGV.cc
    return false unless options.empty?
    return false if formula.bottle_disabled?
    unless formula.pour_bottle?
      if install_bottle_options[:warn] && formula.pour_bottle_check_unsatisfied_reason
        opoo <<~EOS
          Building #{formula.full_name} from source:
            #{formula.pour_bottle_check_unsatisfied_reason}
        EOS
      end
      return false
    end

    bottle = formula.bottle_specification
    unless bottle.compatible_cellar?
      if install_bottle_options[:warn]
        opoo <<~EOS
          Building #{formula.full_name} from source:
            The bottle needs a #{bottle.cellar} Cellar (yours is #{HOMEBREW_CELLAR}).
        EOS
      end
      return false
    end

    true
  end

  def install_bottle_for?(dep, build)
    return pour_bottle? if dep == formula
    return false if ARGV.build_formula_from_source?(dep)
    return false unless dep.bottle && dep.pour_bottle?
    return false unless build.used_options.empty?
    return false unless dep.bottle.compatible_cellar?
    true
  end

  def prelude
    Tab.clear_cache
    verify_deps_exist unless ignore_deps?
    lock
    check_install_sanity
  end

  def verify_deps_exist
    begin
      compute_dependencies
    rescue TapFormulaUnavailableError => e
      raise if e.tap.installed?

      e.tap.install
      retry
    end
  rescue FormulaUnavailableError => e
    e.dependent = formula.full_name
    raise
  end

  def check_install_sanity
    raise FormulaInstallationAlreadyAttemptedError, formula if self.class.attempted.include?(formula)

    return if ignore_deps?

    recursive_deps = formula.recursive_dependencies
    recursive_formulae = recursive_deps.map(&:to_formula)
    recursive_runtime_deps = formula.recursive_dependencies.reject(&:build?)
    recursive_runtime_formulae = recursive_runtime_deps.map(&:to_formula)

    recursive_dependencies = []
    recursive_formulae.each do |dep|
      dep_recursive_dependencies = dep.recursive_dependencies.map(&:to_s)
      if dep_recursive_dependencies.include?(formula.name)
        recursive_dependencies << "#{formula.full_name} depends on #{dep.full_name}"
        recursive_dependencies << "#{dep.full_name} depends on #{formula.full_name}"
      end
    end

    unless recursive_dependencies.empty?
      raise CannotInstallFormulaError, <<~EOS
        #{formula.full_name} contains a recursive dependency on itself:
          #{recursive_dependencies.join("\n  ")}
      EOS
    end

    if recursive_formulae.flat_map(&:recursive_dependencies).map(&:to_s).include?(formula.name)
      raise CannotInstallFormulaError, <<~EOS
        #{formula.full_name} contains a recursive dependency on itself!
      EOS
    end

    version_hash = {}
    version_conflicts = Set.new
    recursive_runtime_formulae.each do |f|
      name = f.name
      unversioned_name, = name.split("@")
      next if unversioned_name == "python"
      version_hash[unversioned_name] ||= Set.new
      version_hash[unversioned_name] << name
      next if version_hash[unversioned_name].length < 2
      version_conflicts += version_hash[unversioned_name]
    end
    unless version_conflicts.empty?
      raise CannotInstallFormulaError, <<~EOS
        #{formula.full_name} contains conflicting version recursive dependencies:
          #{version_conflicts.to_a.join ", "}
        View these with `brew deps --tree #{formula.full_name}`.
      EOS
    end

    pinned_unsatisfied_deps = recursive_deps.select do |dep|
      dep.to_formula.pinned? && !dep.satisfied?(inherited_options_for(dep))
{'fraction_non_alphanumeric': 0.08093189382255053, 'fraction_numerical': 0.00016235084016559786, 'mean_word_length': 3.5918747670518076, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3295338', 'n_tokens_mistral': 4106, 'n_tokens_neox': 3892, 'n_words': 880}