aboutsummaryrefslogtreecommitdiffstats
path: root/lib/bundler/definition.rb
blob: cdcf7ce3ce61c28a01d93bee9211a33ace8717d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
require "digest/sha1"

# TODO: In the 0.10 release, there shouldn't be a locked subclass of Definition
module Bundler
  class Definition
    attr_reader :dependencies, :sources, :locked_specs

    def self.build(gemfile, lockfile)
      gemfile = Pathname.new(gemfile).expand_path

      unless gemfile.file?
        raise GemfileNotFound, "#{gemfile} not found"
      end

      # TODO: move this back into DSL
      builder = Dsl.new
      builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1)
      builder.to_definition(lockfile)
    end

    def initialize(lockfile, dependencies, sources)
      @dependencies, @sources = dependencies, sources

      if lockfile && File.exists?(lockfile)
        locked = LockfileParser.new(File.read(lockfile))
        @locked_deps  = locked.dependencies
        @locked_specs = SpecSet.new(locked.specs)
        @sources = locked.sources
      else
        @locked_deps  = []
        @locked_specs = SpecSet.new([])
      end
    end

    def resolve_remotely!
      @specs = resolve_remote_specs
    end

    def specs
      @specs ||= resolve(:local_specs, index)
    end

    def index
      @index ||= Index.build do |idx|
        sources.each do |s|
          idx.use s.local_specs
        end
      end
    end

    def remote_index
      @remote_index ||= Index.build do |idx|
        sources.each { |source| idx.use source.specs }
      end
    end

    def no_sources?
      sources.length == 1 && sources.first.remotes.empty?
    end

    # TODO: OMG LOL
    def resolver_dependencies
      @resolver_dependencies ||= begin
        deps = locked_specs_as_deps
        dependencies.each do |dep|
          deps << dep unless deps.any? { |d| d.name == dep.name }
        end
        deps
      end
    end

    def groups
      dependencies.map { |d| d.groups }.flatten.uniq
    end

  private

    # We have the dependencies from Gemfile.lock and the dependencies from the
    # Gemfile. Here, we are finding a list of all dependencies that were
    # originally present in the Gemfile that still satisfy the requirements
    # of the dependencies in the Gemfile.lock
    #
    # This allows us to add on the *new* requirements in the Gemfile and make
    # sure that the changes result in a conservative update to the Gemfile.lock.
    def locked_specs_as_deps
      deps = @dependencies & @locked_deps

      @dependencies.each do |dep|
        next if deps.include?(dep)
        deps << dep if @locked_specs.any? { |s| s.satisfies?(dep) }
      end

      meta_deps = @locked_specs.for(deps).map do |s|
        dep = Gem::Dependency.new(s.name, s.version)
        @locked_deps.each do |d|
          dep.source = d.source if d.name == dep.name
        end
        dep
      end
    end

    def resolve(type, idx)
      source_requirements = {}
      resolver_dependencies.each do |dep|
        next unless dep.source
        source_requirements[dep.name] = dep.source.send(type)
      end

      # Run a resolve against the locally available gems
      Resolver.resolve(resolver_dependencies, idx, source_requirements)
    end

    def resolve_remote_specs
      # An ambiguous dependency is any dependency that does not have
      # a requirement on an explicit version. If there are any, then
      # we must do a remote resolve.
      if resolver_dependencies.any? { |d| ambiguous?(d) }
        return resolve(:specs, remote_index)
      end

      # Simple logic for now. Can improve later.
      if specs.length == resolver_dependencies.length
        return specs
      else
        return resolve(:specs, remote_index)
      end
    rescue GemNotFound, PathError => e
      resolve(:specs, remote_index)
    end

    def ambiguous?(dep)
      dep.requirement.requirements.any? { |op,_| op != '=' }
    end
  end
end