Class: Gel::Installer

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin
Defined in:
lib/gel/installer.rb

Constant Summary collapse

DOWNLOAD_CONCURRENCY =
6
COMPILE_CONCURRENCY =
4

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(store) ⇒ Installer

Returns a new instance of Installer.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/gel/installer.rb', line 19

def initialize(store)
  super()

  @trace = nil

  @messages = Queue.new

  @store = store
  @dependencies = Hash.new { |h, k| h[k] = [] }
  @weights = Hash.new(1)
  @pending = Hash.new(0)

  @download_pool = Gel::WorkPool.new(DOWNLOAD_CONCURRENCY, monitor: self, name: "gel-download", collect_errors: true)
  @compile_pool = Gel::WorkPool.new(COMPILE_CONCURRENCY, monitor: self, name: "gel-compile", collect_errors: true)

  @download_pool.queue_order = -> ((_, name)) { -@weights[name] }
  @compile_pool.queue_order = -> ((_, name)) { -@weights[name] }

  @git_depot = Gel::GitDepot.new(store)

  @compile_waiting = []
end

Instance Attribute Details

#storeObject (readonly)

Returns the value of attribute store.



17
18
19
# File 'lib/gel/installer.rb', line 17

def store
  @store
end

Instance Method Details

#download_gem(catalogs, name, version) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/gel/installer.rb', line 90

def download_gem(catalogs, name, version)
  catalogs.each do |catalog|
    if fpath = catalog.cached_gem(name, version)
      return fpath
    end
  end

  catalogs.each do |catalog|
    begin
      return catalog.download_gem(name, version)
    rescue Net::HTTPExceptions
    end
  end

  raise "Unable to locate #{name} #{version} in: #{catalogs.join ", "}"
end

#install_gem(catalogs, name, version) ⇒ Object



64
65
66
67
68
69
70
71
72
# File 'lib/gel/installer.rb', line 64

def install_gem(catalogs, name, version)
  synchronize do
    raise "catalogs is nil" if catalogs.nil?
    @pending[name] += 1
    @download_pool.queue(name) do
      work_download([catalogs, name, version])
    end
  end
end

#known_dependencies(deps) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/gel/installer.rb', line 42

def known_dependencies(deps)
  deps = deps.dup

  synchronize do
    @dependencies.update(deps) { |k, l, r| deps[k] = r - l; l | r }
    return if deps.values.all?(&:empty?)

    deps.each do |dependent, dependencies|
      dependencies.each do |dependency|
        add_weight dependency, @weights[dependent]
      end
    end

    # Every time we learn about a new dependency, we reorder the
    # queues to ensure the most depended-on gems are processed first.
    # This ensures we can start compiling extension gems as soon as
    # possible.
    @download_pool.reorder_queue!
    @compile_pool.reorder_queue!
  end
end

#load_git_gem(remote, revision, name) ⇒ Object



74
75
76
77
78
79
80
81
# File 'lib/gel/installer.rb', line 74

def load_git_gem(remote, revision, name)
  synchronize do
    @pending[name] += 1
    @download_pool.queue(name) do
      work_git(remote, revision, name)
    end
  end
end

#wait(output = nil) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/gel/installer.rb', line 154

def wait(output = nil)
  clear = ""
  tty = output && output.isatty

  pools = { "Downloading" => @download_pool, "Compiling" => @compile_pool }

  return if pools.values.all?(&:idle?)

  update_status = lambda do
    synchronize do
      if output
        output.write clear
        output.write @messages.pop until @messages.empty?

        if tty
          messages = pools.map { |label, pool| pool_status(label, pool, label == "Compiling" ? @compile_waiting.size : 0) }.compact
          if messages.empty?
            msgline = ""
          else
            msgline = "[" + messages.join(";   ") + "]"
          end
          clear = "\r" + " " * msgline.size + "\r"
          output.write msgline
        end
      else
        @messages.pop until @messages.empty?
      end
      pools.values.all?(&:idle?) && @compile_waiting.empty?
    end
  end

  pools.values.map do |pool|
    Thread.new do
      Thread.current.abort_on_exception = true

      pool.wait(&update_status)
      pools.values.each(&:tick!)
      pool.stop
    end
  end.each(&:join)

  errors = @download_pool.errors + @compile_pool.errors

  if errors.empty?
    if output
      output.write "Installed #{@download_pool.count} gems\n"
    end
  else
    if output
      output.write "Installed #{@download_pool.count - errors.size} of #{@download_pool.count} gems\n\nErrors encountered with #{errors.size} gems:\n\n"
      errors.each do |(_, name), exception|
        output.write "#{name}\n  #{exception}\n\n"
      end
    end

    if errors.first
      raise errors.first.last
    else
      raise "Errors encountered while installing gems"
    end
  end
end

#work_compile(g) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/gel/installer.rb', line 126

def work_compile(g)
  synchronize do
    unless compile_ready?(g.spec.name)
      @compile_waiting << g
      return
    end
  end

  g.compile
  work_install(g)
end

#work_download(catalogs, name, version) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/gel/installer.rb', line 107

def work_download((catalogs, name, version))
  fpath = download_gem(catalogs, name, version)

  installer = Gel::Package::Installer.new(store)
  g = Gel::Package.extract(fpath, installer)
  known_dependencies g.spec.name => g.spec.runtime_dependencies.keys
  if g.needs_compile?
    synchronize do
      add_weight name, 1000

      @compile_pool.queue(g.spec.name) do
        work_compile(g)
      end
    end
  else
    work_install(g)
  end
end

#work_git(remote, revision, name) ⇒ Object



83
84
85
86
87
88
# File 'lib/gel/installer.rb', line 83

def work_git(remote, revision, name)
  @git_depot.checkout(remote, revision)

  @messages << "Using #{name} (git)\n"
  @pending[name] -= 1
end

#work_install(g) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/gel/installer.rb', line 138

def work_install(g)
  @messages << "Installing #{g.spec.name} (#{g.spec.version})\n"
  g.install
  @pending[g.spec.name] -= 1

  synchronize do
    compile_recheck, @compile_waiting = @compile_waiting, []

    compile_recheck.each do |g|
      @compile_pool.queue(g.spec.name) do
        work_compile(g)
      end
    end
  end
end