Implement caching for dependency expansion
This commit is contained in:
		
							parent
							
								
									3c3bf1c74d
								
							
						
					
					
						commit
						e49a338896
					
				
							
								
								
									
										9
									
								
								Library/Homebrew/dependencies.rbi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Library/Homebrew/dependencies.rbi
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					# typed: strict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Dependencies < SimpleDelegator
 | 
				
			||||||
 | 
					  include Kernel
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Requirements < SimpleDelegator
 | 
				
			||||||
 | 
					  include Kernel
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@ -35,15 +35,11 @@ module DependenciesHelpers
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def recursive_includes(klass, root_dependent, includes, ignores)
 | 
					  def recursive_includes(klass, root_dependent, includes, ignores)
 | 
				
			||||||
    type = if klass == Dependency
 | 
					    raise ArgumentError, "Invalid class argument: #{klass}" if klass != Dependency && klass != Requirement
 | 
				
			||||||
      :dependencies
 | 
					 | 
				
			||||||
    elsif klass == Requirement
 | 
					 | 
				
			||||||
      :requirements
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      raise ArgumentError, "Invalid class argument: #{klass}"
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    root_dependent.send("recursive_#{type}") do |dependent, dep|
 | 
					    cache_key = "recursive_includes_#{includes}_#{ignores}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    klass.expand(root_dependent, cache_key: cache_key) do |dependent, dep|
 | 
				
			||||||
      if dep.recommended?
 | 
					      if dep.recommended?
 | 
				
			||||||
        klass.prune if ignores.include?("recommended?") || dependent.build.without?(dep)
 | 
					        klass.prune if ignores.include?("recommended?") || dependent.build.without?(dep)
 | 
				
			||||||
      elsif dep.optional?
 | 
					      elsif dep.optional?
 | 
				
			||||||
@ -57,7 +53,7 @@ module DependenciesHelpers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      # If a tap isn't installed, we can't find the dependencies of one of
 | 
					      # If a tap isn't installed, we can't find the dependencies of one of
 | 
				
			||||||
      # its formulae, and an exception will be thrown if we try.
 | 
					      # its formulae, and an exception will be thrown if we try.
 | 
				
			||||||
      if type == :dependencies &&
 | 
					      if klass == Dependency &&
 | 
				
			||||||
         dep.is_a?(TapDependency) &&
 | 
					         dep.is_a?(TapDependency) &&
 | 
				
			||||||
         !dep.tap.installed?
 | 
					         !dep.tap.installed?
 | 
				
			||||||
        Dependency.keep_but_prune_recursive_deps
 | 
					        Dependency.keep_but_prune_recursive_deps
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ class Dependency
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  extend Forwardable
 | 
					  extend Forwardable
 | 
				
			||||||
  include Dependable
 | 
					  include Dependable
 | 
				
			||||||
 | 
					  extend Cachable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attr_reader :name, :tags, :env_proc, :option_names
 | 
					  attr_reader :name, :tags, :env_proc, :option_names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,12 +88,17 @@ class Dependency
 | 
				
			|||||||
    # `[dependent, dep]` pairs to allow callers to apply arbitrary filters to
 | 
					    # `[dependent, dep]` pairs to allow callers to apply arbitrary filters to
 | 
				
			||||||
    # the list.
 | 
					    # the list.
 | 
				
			||||||
    # The default filter, which is applied when a block is not given, omits
 | 
					    # The default filter, which is applied when a block is not given, omits
 | 
				
			||||||
    # optionals and recommendeds based on what the dependent has asked for.
 | 
					    # optionals and recommendeds based on what the dependent has asked for
 | 
				
			||||||
    def expand(dependent, deps = dependent.deps, &block)
 | 
					    def expand(dependent, deps = dependent.deps, cache_key: nil, &block)
 | 
				
			||||||
      # Keep track dependencies to avoid infinite cyclic dependency recursion.
 | 
					      # Keep track dependencies to avoid infinite cyclic dependency recursion.
 | 
				
			||||||
      @expand_stack ||= []
 | 
					      @expand_stack ||= []
 | 
				
			||||||
      @expand_stack.push dependent.name
 | 
					      @expand_stack.push dependent.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if cache_key.present?
 | 
				
			||||||
 | 
					        cache[cache_key] ||= {}
 | 
				
			||||||
 | 
					        return cache[cache_key][dependent.full_name].dup if cache[cache_key][dependent.full_name]
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expanded_deps = []
 | 
					      expanded_deps = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      deps.each do |dep|
 | 
					      deps.each do |dep|
 | 
				
			||||||
@ -104,18 +110,20 @@ class Dependency
 | 
				
			|||||||
        when :skip
 | 
					        when :skip
 | 
				
			||||||
          next if @expand_stack.include? dep.name
 | 
					          next if @expand_stack.include? dep.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expanded_deps.concat(expand(dep.to_formula, &block))
 | 
					          expanded_deps.concat(expand(dep.to_formula, cache_key: cache_key, &block))
 | 
				
			||||||
        when :keep_but_prune_recursive_deps
 | 
					        when :keep_but_prune_recursive_deps
 | 
				
			||||||
          expanded_deps << dep
 | 
					          expanded_deps << dep
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
          next if @expand_stack.include? dep.name
 | 
					          next if @expand_stack.include? dep.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          expanded_deps.concat(expand(dep.to_formula, &block))
 | 
					          expanded_deps.concat(expand(dep.to_formula, cache_key: cache_key, &block))
 | 
				
			||||||
          expanded_deps << dep
 | 
					          expanded_deps << dep
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      merge_repeats(expanded_deps)
 | 
					      expanded_deps = merge_repeats(expanded_deps)
 | 
				
			||||||
 | 
					      cache[cache_key][dependent.full_name] = expanded_deps.dup if cache_key.present?
 | 
				
			||||||
 | 
					      expanded_deps
 | 
				
			||||||
    ensure
 | 
					    ensure
 | 
				
			||||||
      @expand_stack.pop
 | 
					      @expand_stack.pop
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
				
			|||||||
@ -310,6 +310,8 @@ module Homebrew
 | 
				
			|||||||
        Formula.clear_cache
 | 
					        Formula.clear_cache
 | 
				
			||||||
        Keg.clear_cache
 | 
					        Keg.clear_cache
 | 
				
			||||||
        Tab.clear_cache
 | 
					        Tab.clear_cache
 | 
				
			||||||
 | 
					        Dependency.clear_cache
 | 
				
			||||||
 | 
					        Requirement.clear_cache
 | 
				
			||||||
        tab = Tab.for_keg(keg)
 | 
					        tab = Tab.for_keg(keg)
 | 
				
			||||||
        original_tab = tab.dup
 | 
					        original_tab = tab.dup
 | 
				
			||||||
        tab.poured_from_bottle = false
 | 
					        tab.poured_from_bottle = false
 | 
				
			||||||
 | 
				
			|||||||
@ -165,7 +165,7 @@ class Formula
 | 
				
			|||||||
  # during the installation of a {Formula}. This is annoying but the result of
 | 
					  # during the installation of a {Formula}. This is annoying but the result of
 | 
				
			||||||
  # state that we're trying to eliminate.
 | 
					  # state that we're trying to eliminate.
 | 
				
			||||||
  # @return [BuildOptions]
 | 
					  # @return [BuildOptions]
 | 
				
			||||||
  attr_accessor :build
 | 
					  attr_reader :build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Whether this formula should be considered outdated
 | 
					  # Whether this formula should be considered outdated
 | 
				
			||||||
  # if the target of the alias it was installed with has since changed.
 | 
					  # if the target of the alias it was installed with has since changed.
 | 
				
			||||||
@ -222,10 +222,28 @@ class Formula
 | 
				
			|||||||
    spec = send(spec_sym)
 | 
					    spec = send(spec_sym)
 | 
				
			||||||
    raise FormulaSpecificationError, "#{spec_sym} spec is not available for #{full_name}" unless spec
 | 
					    raise FormulaSpecificationError, "#{spec_sym} spec is not available for #{full_name}" unless spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    old_spec_sym = @active_spec_sym
 | 
				
			||||||
    @active_spec = spec
 | 
					    @active_spec = spec
 | 
				
			||||||
    @active_spec_sym = spec_sym
 | 
					    @active_spec_sym = spec_sym
 | 
				
			||||||
    validate_attributes!
 | 
					    validate_attributes!
 | 
				
			||||||
    @build = active_spec.build
 | 
					    @build = active_spec.build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if spec_sym == old_spec_sym
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Dependency.clear_cache
 | 
				
			||||||
 | 
					    Requirement.clear_cache
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # @private
 | 
				
			||||||
 | 
					  def build=(build_options)
 | 
				
			||||||
 | 
					    old_options = @build
 | 
				
			||||||
 | 
					    @build = build_options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if old_options.used_options == build_options.used_options &&
 | 
				
			||||||
 | 
					              old_options.unused_options == build_options.unused_options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Dependency.clear_cache
 | 
				
			||||||
 | 
					    Requirement.clear_cache
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
@ -1657,13 +1675,15 @@ class Formula
 | 
				
			|||||||
  # means if a depends on b then b will be ordered before a in this list
 | 
					  # means if a depends on b then b will be ordered before a in this list
 | 
				
			||||||
  # @private
 | 
					  # @private
 | 
				
			||||||
  def recursive_dependencies(&block)
 | 
					  def recursive_dependencies(&block)
 | 
				
			||||||
    Dependency.expand(self, &block)
 | 
					    cache_key = "Formula#recursive_dependencies" unless block
 | 
				
			||||||
 | 
					    Dependency.expand(self, cache_key: cache_key, &block)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # The full set of Requirements for this formula's dependency tree.
 | 
					  # The full set of Requirements for this formula's dependency tree.
 | 
				
			||||||
  # @private
 | 
					  # @private
 | 
				
			||||||
  def recursive_requirements(&block)
 | 
					  def recursive_requirements(&block)
 | 
				
			||||||
    Requirement.expand(self, &block)
 | 
					    cache_key = "Formula#recursive_requirements" unless block
 | 
				
			||||||
 | 
					    Requirement.expand(self, cache_key: cache_key, &block)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Returns a Keg for the opt_prefix or installed_prefix if they exist.
 | 
					  # Returns a Keg for the opt_prefix or installed_prefix if they exist.
 | 
				
			||||||
 | 
				
			|||||||
@ -572,8 +572,8 @@ class FormulaInstaller
 | 
				
			|||||||
    unsatisfied_reqs = Hash.new { |h, k| h[k] = [] }
 | 
					    unsatisfied_reqs = Hash.new { |h, k| h[k] = [] }
 | 
				
			||||||
    req_deps = []
 | 
					    req_deps = []
 | 
				
			||||||
    formulae = [formula]
 | 
					    formulae = [formula]
 | 
				
			||||||
    formula_deps_map = Dependency.expand(formula)
 | 
					    formula_deps_map = formula.recursive_dependencies
 | 
				
			||||||
                                 .index_by(&:name)
 | 
					                              .index_by(&:name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while (f = formulae.pop)
 | 
					    while (f = formulae.pop)
 | 
				
			||||||
      runtime_requirements = runtime_requirements(f)
 | 
					      runtime_requirements = runtime_requirements(f)
 | 
				
			||||||
 | 
				
			|||||||
@ -114,6 +114,12 @@ class Options
 | 
				
			|||||||
    @options.to_a * other
 | 
					    @options.to_a * other
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def ==(other)
 | 
				
			||||||
 | 
					    instance_of?(other.class) &&
 | 
				
			||||||
 | 
					      to_a == other.to_a
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  alias eql? ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def empty?
 | 
					  def empty?
 | 
				
			||||||
    @options.empty?
 | 
					    @options.empty?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ class Requirement
 | 
				
			|||||||
  extend T::Sig
 | 
					  extend T::Sig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  include Dependable
 | 
					  include Dependable
 | 
				
			||||||
 | 
					  extend Cachable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  attr_reader :tags, :name, :cask, :download
 | 
					  attr_reader :tags, :name, :cask, :download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -219,7 +220,12 @@ class Requirement
 | 
				
			|||||||
    # the list.
 | 
					    # the list.
 | 
				
			||||||
    # The default filter, which is applied when a block is not given, omits
 | 
					    # The default filter, which is applied when a block is not given, omits
 | 
				
			||||||
    # optionals and recommendeds based on what the dependent has asked for.
 | 
					    # optionals and recommendeds based on what the dependent has asked for.
 | 
				
			||||||
    def expand(dependent, &block)
 | 
					    def expand(dependent, cache_key: nil, &block)
 | 
				
			||||||
 | 
					      if cache_key.present?
 | 
				
			||||||
 | 
					        cache[cache_key] ||= {}
 | 
				
			||||||
 | 
					        return cache[cache_key][dependent.full_name].dup if cache[cache_key][dependent.full_name]
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      reqs = Requirements.new
 | 
					      reqs = Requirements.new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      formulae = dependent.recursive_dependencies.map(&:to_formula)
 | 
					      formulae = dependent.recursive_dependencies.map(&:to_formula)
 | 
				
			||||||
@ -233,6 +239,7 @@ class Requirement
 | 
				
			|||||||
        end
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      cache[cache_key][dependent.full_name] = reqs.dup if cache_key.present?
 | 
				
			||||||
      reqs
 | 
					      reqs
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -166,8 +166,7 @@ describe Formula do
 | 
				
			|||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_values_with_no_installed_alias = [
 | 
					    build_values_with_no_installed_alias = [
 | 
				
			||||||
      nil,
 | 
					      BuildOptions.new(Options.new, f.options),
 | 
				
			||||||
      BuildOptions.new({}, {}),
 | 
					 | 
				
			||||||
      Tab.new(source: { "path" => f.path.to_s }),
 | 
					      Tab.new(source: { "path" => f.path.to_s }),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    build_values_with_no_installed_alias.each do |build|
 | 
					    build_values_with_no_installed_alias.each do |build|
 | 
				
			||||||
@ -201,7 +200,10 @@ describe Formula do
 | 
				
			|||||||
      url "foo-1.0"
 | 
					      url "foo-1.0"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    build_values_with_no_installed_alias = [nil, BuildOptions.new({}, {}), Tab.new(source: { "path" => f.path })]
 | 
					    build_values_with_no_installed_alias = [
 | 
				
			||||||
 | 
					      BuildOptions.new(Options.new, f.options),
 | 
				
			||||||
 | 
					      Tab.new(source: { "path" => f.path }),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
    build_values_with_no_installed_alias.each do |build|
 | 
					    build_values_with_no_installed_alias.each do |build|
 | 
				
			||||||
      f.build = build
 | 
					      f.build = build
 | 
				
			||||||
      expect(f.installed_alias_path).to be nil
 | 
					      expect(f.installed_alias_path).to be nil
 | 
				
			||||||
@ -405,7 +407,7 @@ describe Formula do
 | 
				
			|||||||
      f = formula alias_path: alias_path do
 | 
					      f = formula alias_path: alias_path do
 | 
				
			||||||
        url "foo-1.0"
 | 
					        url "foo-1.0"
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
      f.build = BuildOptions.new({}, {})
 | 
					      f.build = BuildOptions.new(Options.new, f.options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(f.alias_path).to eq(alias_path)
 | 
					      expect(f.alias_path).to eq(alias_path)
 | 
				
			||||||
      expect(f.installed_alias_path).to be nil
 | 
					      expect(f.installed_alias_path).to be nil
 | 
				
			||||||
@ -802,7 +804,7 @@ describe Formula do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    expect(Set.new(f1.recursive_requirements)).to eq(Set[])
 | 
					    expect(Set.new(f1.recursive_requirements)).to eq(Set[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f1.build = BuildOptions.new(["--with-xcode"], f1.options)
 | 
					    f1.build = BuildOptions.new(Options.create(["--with-xcode"]), f1.options)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(Set.new(f1.recursive_requirements)).to eq(Set[xcode])
 | 
					    expect(Set.new(f1.recursive_requirements)).to eq(Set[xcode])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -185,6 +185,8 @@ RSpec.configure do |config|
 | 
				
			|||||||
    Formula.clear_cache
 | 
					    Formula.clear_cache
 | 
				
			||||||
    Keg.clear_cache
 | 
					    Keg.clear_cache
 | 
				
			||||||
    Tab.clear_cache
 | 
					    Tab.clear_cache
 | 
				
			||||||
 | 
					    Dependency.clear_cache
 | 
				
			||||||
 | 
					    Requirement.clear_cache
 | 
				
			||||||
    FormulaInstaller.clear_attempted
 | 
					    FormulaInstaller.clear_attempted
 | 
				
			||||||
    FormulaInstaller.clear_installed
 | 
					    FormulaInstaller.clear_installed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -229,6 +231,8 @@ RSpec.configure do |config|
 | 
				
			|||||||
      Formula.clear_cache
 | 
					      Formula.clear_cache
 | 
				
			||||||
      Keg.clear_cache
 | 
					      Keg.clear_cache
 | 
				
			||||||
      Tab.clear_cache
 | 
					      Tab.clear_cache
 | 
				
			||||||
 | 
					      Dependency.clear_cache
 | 
				
			||||||
 | 
					      Requirement.clear_cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      FileUtils.rm_rf [
 | 
					      FileUtils.rm_rf [
 | 
				
			||||||
        *TEST_DIRECTORIES,
 | 
					        *TEST_DIRECTORIES,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user