Module: Bundler::Overrule::DependencyRewriter

Defined in:
lib/bundler/overrule/dependency_rewriter.rb

Overview

The heart of the gem: given a list of Gem::Dependency objects and a set of rules, return the rewritten list plus the edges that changed.

Deliberately pure — no Bundler objects, no global state, no I/O — so the behavior contract can be unit-tested without running Bundler at all. Both the resolution seam and the installation seam call this same function, which is what keeps the lockfile and the installed gems from ever disagreeing about what a dependency list contains.

Class Method Summary collapse

Class Method Details

.apply_rule(dependency, rule, owner) ⇒ Array(Gem::Dependency, Edge)

Decide what one dependency becomes under one rule.

Returns:

  • (Array(Gem::Dependency, Edge))

    the replacement (nil to drop the dependency entirely) and the edge to record (nil if nothing changed)



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/bundler/overrule/dependency_rewriter.rb', line 57

def apply_rule(dependency, rule, owner)
  # Development dependencies never reach the resolver, but spec objects
  # still report them. Rewriting or dropping one would be a lie about
  # what the gem declares, for no benefit.
  return [dependency, nil] if rule.nil? || development?(dependency)

  case rule.type
  when :ban
    [nil, Edge.new(rule, owner, requirement_string(dependency), nil)]
  when :force
    return [dependency, nil] if dependency.requirement == rule.requirement

    [replace(dependency, rule.requirement),
     Edge.new(rule, owner, requirement_string(dependency), rule.requirement.to_s)]
  else
    [dependency, nil]
  end
end

.development?(dependency) ⇒ Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/bundler/overrule/dependency_rewriter.rb', line 76

def development?(dependency)
  dependency.respond_to?(:type) && dependency.type == :development
end

.replace(dependency, requirement) ⇒ Object

Swap in a new requirement while keeping everything else about the dependency intact.

We copy-and-mutate rather than construct a fresh object on purpose. Bundler::Dependency derives #groups, #source, #git, #path, #github, #branch and #ref lazily from an options hash it keeps privately, and the shape of that hash has changed across Bundler versions. Rebuilding from public readers silently drops whichever of those we forgot — a force on a gem declared with git: would quietly lose its source. Duplicating preserves the exact class and every option, forever.



94
95
96
97
98
# File 'lib/bundler/overrule/dependency_rewriter.rb', line 94

def replace(dependency, requirement)
  replacement = dependency.dup
  replacement.instance_variable_set(:@requirement, requirement)
  replacement
end

.requirement_string(dependency) ⇒ Object



80
81
82
# File 'lib/bundler/overrule/dependency_rewriter.rb', line 80

def requirement_string(dependency)
  dependency.requirement.to_s
end

.rewrite(dependencies, rules_by_name, owner: nil) ⇒ Array(Array<Gem::Dependency>, Array<Edge>)

Parameters:

  • dependencies (Array<Gem::Dependency>)
  • rules_by_name (Hash{String => Rule})
  • owner (String, nil) (defaults to: nil)

    gem name that declared these dependencies

Returns:

  • (Array(Array<Gem::Dependency>, Array<Edge>))


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/bundler/overrule/dependency_rewriter.rb', line 36

def rewrite(dependencies, rules_by_name, owner: nil)
  return [dependencies, []] if rules_by_name.empty? || dependencies.nil? || dependencies.empty?

  edges = []
  rewritten = []

  dependencies.each do |dependency|
    rule = rules_by_name[dependency.name]
    replacement, edge = apply_rule(dependency, rule, owner)

    rewritten << replacement unless replacement.nil?
    edges << edge if edge
  end

  [rewritten, edges]
end