gel 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/exe/gel +13 -0
- data/lib/gel.rb +22 -0
- data/lib/gel/catalog.rb +153 -0
- data/lib/gel/catalog/common.rb +82 -0
- data/lib/gel/catalog/compact_index.rb +152 -0
- data/lib/gel/catalog/dependency_index.rb +125 -0
- data/lib/gel/catalog/legacy_index.rb +157 -0
- data/lib/gel/catalog/marshal_hacks.rb +16 -0
- data/lib/gel/command.rb +86 -0
- data/lib/gel/command/config.rb +11 -0
- data/lib/gel/command/env.rb +7 -0
- data/lib/gel/command/exec.rb +66 -0
- data/lib/gel/command/help.rb +7 -0
- data/lib/gel/command/install.rb +7 -0
- data/lib/gel/command/install_gem.rb +16 -0
- data/lib/gel/command/lock.rb +34 -0
- data/lib/gel/command/ruby.rb +10 -0
- data/lib/gel/command/shell_setup.rb +25 -0
- data/lib/gel/command/stub.rb +12 -0
- data/lib/gel/command/update.rb +11 -0
- data/lib/gel/compatibility.rb +4 -0
- data/lib/gel/compatibility/bundler.rb +54 -0
- data/lib/gel/compatibility/bundler/cli.rb +6 -0
- data/lib/gel/compatibility/bundler/friendly_errors.rb +3 -0
- data/lib/gel/compatibility/bundler/setup.rb +4 -0
- data/lib/gel/compatibility/rubygems.rb +192 -0
- data/lib/gel/compatibility/rubygems/command.rb +4 -0
- data/lib/gel/compatibility/rubygems/dependency_installer.rb +0 -0
- data/lib/gel/compatibility/rubygems/gem_runner.rb +6 -0
- data/lib/gel/config.rb +80 -0
- data/lib/gel/db.rb +294 -0
- data/lib/gel/direct_gem.rb +29 -0
- data/lib/gel/environment.rb +592 -0
- data/lib/gel/error.rb +104 -0
- data/lib/gel/gemfile_parser.rb +144 -0
- data/lib/gel/gemspec_parser.rb +95 -0
- data/lib/gel/git_catalog.rb +38 -0
- data/lib/gel/git_depot.rb +119 -0
- data/lib/gel/httpool.rb +148 -0
- data/lib/gel/installer.rb +251 -0
- data/lib/gel/lock_loader.rb +164 -0
- data/lib/gel/lock_parser.rb +64 -0
- data/lib/gel/locked_store.rb +126 -0
- data/lib/gel/multi_store.rb +96 -0
- data/lib/gel/package.rb +156 -0
- data/lib/gel/package/inspector.rb +23 -0
- data/lib/gel/package/installer.rb +267 -0
- data/lib/gel/path_catalog.rb +44 -0
- data/lib/gel/pinboard.rb +140 -0
- data/lib/gel/pub_grub/preference_strategy.rb +82 -0
- data/lib/gel/pub_grub/source.rb +153 -0
- data/lib/gel/runtime.rb +27 -0
- data/lib/gel/store.rb +205 -0
- data/lib/gel/store_catalog.rb +31 -0
- data/lib/gel/store_gem.rb +80 -0
- data/lib/gel/stub_set.rb +51 -0
- data/lib/gel/support/gem_platform.rb +225 -0
- data/lib/gel/support/gem_requirement.rb +264 -0
- data/lib/gel/support/gem_version.rb +398 -0
- data/lib/gel/support/tar.rb +13 -0
- data/lib/gel/support/tar/tar_header.rb +229 -0
- data/lib/gel/support/tar/tar_reader.rb +123 -0
- data/lib/gel/support/tar/tar_reader/entry.rb +154 -0
- data/lib/gel/support/tar/tar_writer.rb +339 -0
- data/lib/gel/tail_file.rb +205 -0
- data/lib/gel/version.rb +5 -0
- data/lib/gel/work_pool.rb +143 -0
- data/man/man1/gel-exec.1 +16 -0
- data/man/man1/gel-install.1 +16 -0
- data/man/man1/gel.1 +30 -0
- metadata +131 -0
data/lib/gel/db.rb
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "sdbm"
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
require "pstore"
|
8
|
+
require "pathname"
|
9
|
+
|
10
|
+
require "monitor"
|
11
|
+
|
12
|
+
class Gel::DB
|
13
|
+
def self.new(root, name)
|
14
|
+
return super unless self == Gel::DB
|
15
|
+
|
16
|
+
if defined? ::SDBM
|
17
|
+
SDBM.new(root, name)
|
18
|
+
else
|
19
|
+
PStore.new(root, name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(root, name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def writing
|
27
|
+
end
|
28
|
+
|
29
|
+
def reading
|
30
|
+
end
|
31
|
+
|
32
|
+
def each_key
|
33
|
+
end
|
34
|
+
|
35
|
+
def key?(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def []=(key, value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Gel::DB::AutoTransaction
|
46
|
+
def initialize(root, name)
|
47
|
+
@root = root
|
48
|
+
@name = name
|
49
|
+
|
50
|
+
super
|
51
|
+
|
52
|
+
@transaction = nil
|
53
|
+
@monitor = Monitor.new
|
54
|
+
end
|
55
|
+
|
56
|
+
if Monitor.method_defined?(:mon_owned?) # Ruby 2.4+
|
57
|
+
def owned?
|
58
|
+
@monitor.mon_owned?
|
59
|
+
end
|
60
|
+
else
|
61
|
+
def owned?
|
62
|
+
@monitor.instance_variable_get(:@mon_owner) == Thread.current
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def write?
|
67
|
+
owned? && @transaction == :write
|
68
|
+
end
|
69
|
+
|
70
|
+
def read?
|
71
|
+
owned? && @transaction
|
72
|
+
end
|
73
|
+
|
74
|
+
def nested?
|
75
|
+
owned? && @transaction
|
76
|
+
end
|
77
|
+
|
78
|
+
def writing
|
79
|
+
raise if nested?
|
80
|
+
|
81
|
+
@monitor.synchronize do
|
82
|
+
begin
|
83
|
+
@transaction = :write
|
84
|
+
super
|
85
|
+
ensure
|
86
|
+
@transaction = nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def reading
|
92
|
+
raise if nested?
|
93
|
+
|
94
|
+
@monitor.synchronize do
|
95
|
+
begin
|
96
|
+
@transaction = :read
|
97
|
+
super
|
98
|
+
ensure
|
99
|
+
@transaction = nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def each_key
|
105
|
+
if read?
|
106
|
+
super
|
107
|
+
else
|
108
|
+
reading { super }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def key?(key)
|
113
|
+
if read?
|
114
|
+
super
|
115
|
+
else
|
116
|
+
reading { super }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def [](key)
|
121
|
+
if read?
|
122
|
+
super
|
123
|
+
else
|
124
|
+
reading { super }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def []=(key, value)
|
129
|
+
if write?
|
130
|
+
super
|
131
|
+
else
|
132
|
+
writing { super }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def marshal_dump
|
139
|
+
[@root, @name]
|
140
|
+
end
|
141
|
+
|
142
|
+
def marshal_load((root, name))
|
143
|
+
initialize(root, name)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Gel::DB::SDBM < Gel::DB
|
148
|
+
prepend Gel::DB::AutoTransaction
|
149
|
+
SDBM_MAX_STORE_SIZE = 1000 - 1 # arbitrary on PBLKSIZ-N
|
150
|
+
SAFE_DELIMITER = '---'
|
151
|
+
|
152
|
+
def initialize(root, name)
|
153
|
+
@sdbm = ::SDBM.new("#{root}/#{name}")
|
154
|
+
end
|
155
|
+
|
156
|
+
def writing
|
157
|
+
yield
|
158
|
+
end
|
159
|
+
|
160
|
+
def reading
|
161
|
+
yield
|
162
|
+
end
|
163
|
+
|
164
|
+
def each_key(&block)
|
165
|
+
@sdbm.each_key(&block)
|
166
|
+
end
|
167
|
+
|
168
|
+
def key?(key)
|
169
|
+
!!@sdbm[key.to_s]
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Retrieve the value from SDBM and handle for when we split
|
174
|
+
# over multiple stores. It is safe to assume that the value
|
175
|
+
# stored will be a marshaled value or a integer implying the
|
176
|
+
# amount of extra stores to retrieve the data string form. A
|
177
|
+
# marshaled store would have special starting delimiter that
|
178
|
+
# is not a decimal. If a number is not found at start of string
|
179
|
+
# then simply load it as a string and you get a value that
|
180
|
+
# is then marshaled.
|
181
|
+
def [](key)
|
182
|
+
value = @sdbm[key.to_s]
|
183
|
+
return nil unless value
|
184
|
+
|
185
|
+
if value =~ /\A~(\d+)\z/
|
186
|
+
value = $1.to_i.times.map do |idx|
|
187
|
+
@sdbm["#{key}#{SAFE_DELIMITER}#{idx}"]
|
188
|
+
end.join
|
189
|
+
end
|
190
|
+
|
191
|
+
return Marshal.load(value)
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
##
|
196
|
+
# SDBM has an arbitrary limit on the size of a string it stores,
|
197
|
+
# so we simply split any string over multiple stores for the edge
|
198
|
+
# case when it reaches this. It's optimised to take advantage of the common
|
199
|
+
# case where this is not needed.
|
200
|
+
# When the edge case is hit, the first value in the storage will be the amount
|
201
|
+
# of extra values stored to hold the split string. This amount is determined by string
|
202
|
+
# size split by the arbitrary limit imposed by SDBM
|
203
|
+
def []=(key, value)
|
204
|
+
return unless value && key
|
205
|
+
|
206
|
+
dump = Marshal.dump(value)
|
207
|
+
count = dump.length / SDBM_MAX_STORE_SIZE
|
208
|
+
|
209
|
+
if count > 0
|
210
|
+
count += 1
|
211
|
+
@sdbm["#{key.to_s}"] = "~#{count}"
|
212
|
+
count.times.map do |idx|
|
213
|
+
@sdbm["#{key.to_s}#{SAFE_DELIMITER}#{idx}"] = dump.slice!(0, SDBM_MAX_STORE_SIZE)
|
214
|
+
end
|
215
|
+
else
|
216
|
+
@sdbm[key.to_s] = dump
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class Gel::DB::PStore < Gel::DB
|
222
|
+
prepend Gel::DB::AutoTransaction
|
223
|
+
|
224
|
+
def initialize(root, name)
|
225
|
+
@pstore = ::PStore.new("#{root}/#{name}.pstore", true)
|
226
|
+
end
|
227
|
+
|
228
|
+
def writing(&block)
|
229
|
+
@pstore.transaction(false, &block)
|
230
|
+
end
|
231
|
+
|
232
|
+
def reading(&block)
|
233
|
+
@pstore.transaction(true, &block)
|
234
|
+
end
|
235
|
+
|
236
|
+
def each_key(&block)
|
237
|
+
@pstore.roots.each(&block)
|
238
|
+
end
|
239
|
+
|
240
|
+
def key?(key)
|
241
|
+
@pstore.key?(key.to_s)
|
242
|
+
end
|
243
|
+
|
244
|
+
def [](key)
|
245
|
+
@pstore[key.to_s]
|
246
|
+
end
|
247
|
+
|
248
|
+
def []=(key, value)
|
249
|
+
@pstore[key.to_s] = value
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class Gel::DB::File < Gel::DB
|
254
|
+
prepend Gel::DB::AutoTransaction
|
255
|
+
|
256
|
+
def initialize(root, name)
|
257
|
+
@path = Pathname.new("#{root}/#{name}")
|
258
|
+
end
|
259
|
+
|
260
|
+
def writing
|
261
|
+
@path.mkdir unless @path.exist?
|
262
|
+
yield
|
263
|
+
end
|
264
|
+
|
265
|
+
def reading
|
266
|
+
yield
|
267
|
+
end
|
268
|
+
|
269
|
+
def each_key
|
270
|
+
@path.each_child(false) do |child|
|
271
|
+
yield child.to_s
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def key?(key)
|
276
|
+
@path.join(key).exist?
|
277
|
+
end
|
278
|
+
|
279
|
+
def [](key)
|
280
|
+
child = @path.join(key)
|
281
|
+
if child.exist?
|
282
|
+
Marshal.load(child.binread)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def []=(key, value)
|
287
|
+
child = @path.join(key)
|
288
|
+
if value
|
289
|
+
child.binwrite Marshal.dump(value)
|
290
|
+
elsif child.exist?
|
291
|
+
child.unlink
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Gel::DirectGem < Gel::StoreGem
|
4
|
+
def load_gemspec(filename)
|
5
|
+
Gel::GemspecParser.parse(File.read(filename), filename, isolate: false)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(root, name, version = nil)
|
9
|
+
root = File.expand_path(root)
|
10
|
+
if File.exist?("#{root}/#{name}.gemspec")
|
11
|
+
gemspec = load_gemspec("#{root}/#{name}.gemspec")
|
12
|
+
elsif File.exist?("#{root}/#{name}/#{name}.gemspec")
|
13
|
+
root = "#{root}/#{name}"
|
14
|
+
gemspec = load_gemspec("#{root}/#{name}.gemspec")
|
15
|
+
else
|
16
|
+
super(root, name, version, [], bindir: "bin", executables: [], require_paths: ["lib"], dependencies: [])
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
info = {
|
21
|
+
bindir: gemspec.bindir || "bin",
|
22
|
+
executables: gemspec.executables,
|
23
|
+
require_paths: gemspec.require_paths || [gemspec.require_path].compact,
|
24
|
+
dependencies: gemspec.runtime_dependencies,
|
25
|
+
}
|
26
|
+
|
27
|
+
super(root, name, version || gemspec.version, gemspec.extensions, info)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,592 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rbconfig"
|
4
|
+
|
5
|
+
class Gel::Environment
|
6
|
+
IGNORE_LIST = %w(bundler gel rubygems-update)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :store
|
10
|
+
attr_accessor :gemfile
|
11
|
+
attr_reader :architectures
|
12
|
+
end
|
13
|
+
self.gemfile = nil
|
14
|
+
@active_lockfile = false
|
15
|
+
@architectures = ["ruby"].freeze
|
16
|
+
|
17
|
+
GEMFILE_PLATFORMS = begin
|
18
|
+
v = RbConfig::CONFIG["ruby_version"].split(".")[0..1].inject(:+)
|
19
|
+
["ruby", "ruby_#{v}", "mri", "mri_#{v}"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.platform?(platform)
|
23
|
+
platform.nil? || architectures.include?(platform)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.config
|
27
|
+
@config ||= Gel::Config.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.store_set
|
31
|
+
list = []
|
32
|
+
architectures.each do |arch|
|
33
|
+
list << Gel::MultiStore.subkey(arch, true)
|
34
|
+
list << Gel::MultiStore.subkey(arch, false)
|
35
|
+
end
|
36
|
+
list
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.activated_gems
|
40
|
+
@activated ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.open(store)
|
44
|
+
@store = store
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.original_rubylib
|
48
|
+
lib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
|
49
|
+
lib.delete File.expand_path("compatibility", __dir__)
|
50
|
+
#lib.delete File.expand_path("..", __dir__)
|
51
|
+
return nil if lib.empty?
|
52
|
+
lib.join(File::PATH_SEPARATOR)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.modified_rubylib
|
56
|
+
lib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
|
57
|
+
dir = File.expand_path("compatibility", __dir__)
|
58
|
+
lib.unshift dir unless lib.include?(dir)
|
59
|
+
#dir = File.expand_path("..", __dir__)
|
60
|
+
#lib.unshift dir unless lib.include?(dir)
|
61
|
+
lib.join(File::PATH_SEPARATOR)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.search_upwards(name, dir = Dir.pwd)
|
65
|
+
until (file = File.join(dir, name)) && File.exist?(file)
|
66
|
+
next_dir = File.dirname(dir)
|
67
|
+
return nil if next_dir == dir
|
68
|
+
dir = next_dir
|
69
|
+
end
|
70
|
+
file
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.find_gemfile(path = nil, error: true)
|
74
|
+
if path && @gemfile && @gemfile.filename != File.expand_path(path)
|
75
|
+
raise "Cannot activate #{path.inspect}; already activated #{@gemfile.filename.inspect}"
|
76
|
+
end
|
77
|
+
return @gemfile.filename if @gemfile
|
78
|
+
|
79
|
+
path ||= ENV["GEL_GEMFILE"]
|
80
|
+
path ||= search_upwards("Gemfile")
|
81
|
+
path ||= "Gemfile"
|
82
|
+
|
83
|
+
if File.exist?(path)
|
84
|
+
path
|
85
|
+
elsif error
|
86
|
+
raise "No Gemfile found in #{path.inspect}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.load_gemfile(path = nil, error: true)
|
91
|
+
return @gemfile if @gemfile
|
92
|
+
|
93
|
+
path = find_gemfile(path, error: error)
|
94
|
+
return if path.nil?
|
95
|
+
|
96
|
+
content = File.read(path)
|
97
|
+
@gemfile = Gel::GemfileParser.parse(content, path, 1)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.lockfile_name(gemfile = self.gemfile&.filename)
|
101
|
+
ENV["GEL_LOCKFILE"] ||
|
102
|
+
(gemfile && File.exist?(gemfile + ".lock") && gemfile + ".lock") ||
|
103
|
+
search_upwards("Gemfile.lock") ||
|
104
|
+
"Gemfile.lock"
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.with_root_store
|
108
|
+
app_store = Gel::Environment.store
|
109
|
+
|
110
|
+
base_store = app_store
|
111
|
+
base_store = base_store.inner if base_store.is_a?(Gel::LockedStore)
|
112
|
+
|
113
|
+
# Work around the fact Gel::Environment is a singleton: we really
|
114
|
+
# want to treat the environment we're running in separately from
|
115
|
+
# the application's environment we're working on. But for now, we
|
116
|
+
# can just cheat and swap them.
|
117
|
+
@store = base_store
|
118
|
+
|
119
|
+
yield base_store
|
120
|
+
ensure
|
121
|
+
@store = app_store
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.auto_install_pub_grub!
|
125
|
+
with_root_store do |base_store|
|
126
|
+
base_store.monitor.synchronize do
|
127
|
+
if base_store.each("pub_grub").none?
|
128
|
+
require_relative "work_pool"
|
129
|
+
|
130
|
+
Gel::WorkPool.new(2) do |work_pool|
|
131
|
+
catalog = Gel::Catalog.new("https://rubygems.org", work_pool: work_pool)
|
132
|
+
|
133
|
+
install_gem([catalog], "pub_grub", [">= 0.5.0"])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.lock(store: store(), output: nil, gemfile: Gel::Environment.load_gemfile, lockfile: Gel::Environment.lockfile_name, catalog_options: {}, preference_strategy: nil)
|
141
|
+
output = nil if $DEBUG
|
142
|
+
|
143
|
+
if lockfile && File.exist?(lockfile)
|
144
|
+
loader = Gel::LockLoader.new(lockfile, gemfile)
|
145
|
+
end
|
146
|
+
|
147
|
+
require_relative "catalog"
|
148
|
+
all_sources = (gemfile.sources | gemfile.gems.flat_map { |_, _, o| o[:source] }).compact
|
149
|
+
local_source = all_sources.delete(:local)
|
150
|
+
server_gems = gemfile.gems.select { |_, _, o| !o[:path] && !o[:git] }.map(&:first)
|
151
|
+
catalog_pool = Gel::WorkPool.new(8, name: "gel-catalog")
|
152
|
+
server_catalogs = all_sources.map { |s| Gel::Catalog.new(s, initial_gems: server_gems, work_pool: catalog_pool, **catalog_options) }
|
153
|
+
|
154
|
+
require_relative "store_catalog"
|
155
|
+
local_catalogs = local_source ? [Gel::StoreCatalog.new(store)] : []
|
156
|
+
|
157
|
+
git_sources = gemfile.gems.map { |_, _, o|
|
158
|
+
if o[:git]
|
159
|
+
if o[:branch]
|
160
|
+
[o[:git], :branch, o[:branch]]
|
161
|
+
elsif o[:tag]
|
162
|
+
[o[:git], :tag, o[:tag]]
|
163
|
+
else
|
164
|
+
[o[:git], :ref, o[:ref]]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
}.compact.uniq
|
168
|
+
|
169
|
+
path_sources = gemfile.gems.map { |_, _, o| o[:path] }.compact
|
170
|
+
|
171
|
+
require_relative "git_depot"
|
172
|
+
git_depot = Gel::GitDepot.new(store)
|
173
|
+
|
174
|
+
require_relative "path_catalog"
|
175
|
+
require_relative "git_catalog"
|
176
|
+
|
177
|
+
catalogs =
|
178
|
+
path_sources.map { |path| Gel::PathCatalog.new(path) } +
|
179
|
+
git_sources.map { |remote, ref_type, ref| Gel::GitCatalog.new(git_depot, remote, ref_type, ref) } +
|
180
|
+
[nil] +
|
181
|
+
local_catalogs +
|
182
|
+
server_catalogs
|
183
|
+
|
184
|
+
Gel::WorkPool.new(8, name: "gel-catalog-prep") do |pool|
|
185
|
+
if output
|
186
|
+
output.print "Fetching sources..."
|
187
|
+
else
|
188
|
+
Gel::Httpool::Logger.info "Fetching sources..."
|
189
|
+
end
|
190
|
+
|
191
|
+
catalogs.each do |catalog|
|
192
|
+
next if catalog.nil?
|
193
|
+
|
194
|
+
pool.queue("catalog") do
|
195
|
+
catalog.prepare
|
196
|
+
output.print "." if output
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
auto_install_pub_grub!
|
202
|
+
with_root_store do
|
203
|
+
gem "pub_grub"
|
204
|
+
end
|
205
|
+
require_relative "pub_grub/source"
|
206
|
+
|
207
|
+
strategy = loader && preference_strategy && preference_strategy.call(loader)
|
208
|
+
source = Gel::PubGrub::Source.new(gemfile, catalogs, ["ruby"], strategy)
|
209
|
+
solver = PubGrub::VersionSolver.new(source: source)
|
210
|
+
solver.define_singleton_method(:next_package_to_try) do
|
211
|
+
self.solution.unsatisfied.min_by do |term|
|
212
|
+
package = term.package
|
213
|
+
versions = self.source.versions_for(package, term.constraint.range)
|
214
|
+
|
215
|
+
if strategy
|
216
|
+
strategy.package_priority(package, versions) + @package_depth[package]
|
217
|
+
else
|
218
|
+
@package_depth[package]
|
219
|
+
end * 1000 + versions.count
|
220
|
+
end.package
|
221
|
+
end
|
222
|
+
|
223
|
+
if output
|
224
|
+
output.print "\nResolving dependencies..."
|
225
|
+
t = Time.now
|
226
|
+
until solver.solved?
|
227
|
+
solver.work
|
228
|
+
if Time.now > t + 0.1
|
229
|
+
output.print "."
|
230
|
+
t = Time.now
|
231
|
+
end
|
232
|
+
end
|
233
|
+
output.puts
|
234
|
+
else
|
235
|
+
PubGrub.logger.info "Resolving dependencies..."
|
236
|
+
solver.work until solver.solved?
|
237
|
+
end
|
238
|
+
|
239
|
+
solution = solver.result
|
240
|
+
solution.delete(source.root)
|
241
|
+
|
242
|
+
catalog_pool.stop
|
243
|
+
|
244
|
+
lock_content = []
|
245
|
+
|
246
|
+
output_specs_for = lambda do |results|
|
247
|
+
lock_content << " specs:"
|
248
|
+
results.each do |(package, version)|
|
249
|
+
next if package.name == "bundler" || package.name == "ruby" || package.name =~ /^~/
|
250
|
+
|
251
|
+
lock_content << " #{package} (#{version})"
|
252
|
+
|
253
|
+
deps = source.dependencies_for(package, version)
|
254
|
+
next unless deps && deps.first
|
255
|
+
|
256
|
+
dep_lines = deps.map do |(dep_name, dep_requirements)|
|
257
|
+
next dep_name if dep_requirements == [">= 0"] || dep_requirements == []
|
258
|
+
|
259
|
+
req = Gel::Support::GemRequirement.new(dep_requirements)
|
260
|
+
req_strings = req.requirements.sort_by { |(_op, ver)| ver }.map { |(op, ver)| "#{op} #{ver}" }
|
261
|
+
|
262
|
+
"#{dep_name} (#{req_strings.join(", ")})"
|
263
|
+
end
|
264
|
+
|
265
|
+
dep_lines.sort.each do |line|
|
266
|
+
lock_content << " #{line}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
grouped_graph = solution.sort_by { |package,_| package.name }.group_by { |(package, version)|
|
272
|
+
spec = source.spec_for_version(package, version)
|
273
|
+
catalog = spec.catalog
|
274
|
+
catalog.is_a?(Gel::Catalog) || catalog.is_a?(Gel::StoreCatalog) ? nil : catalog
|
275
|
+
}
|
276
|
+
server_gems = grouped_graph.delete(nil)
|
277
|
+
|
278
|
+
grouped_graph.keys.sort_by do |catalog|
|
279
|
+
case catalog
|
280
|
+
when Gel::GitCatalog
|
281
|
+
[1, catalog.remote, catalog.revision]
|
282
|
+
when Gel::PathCatalog
|
283
|
+
[2, catalog.path]
|
284
|
+
end
|
285
|
+
end.each do |catalog|
|
286
|
+
case catalog
|
287
|
+
when Gel::GitCatalog
|
288
|
+
lock_content << "GIT"
|
289
|
+
lock_content << " remote: #{catalog.remote}"
|
290
|
+
lock_content << " revision: #{catalog.revision}"
|
291
|
+
lock_content << " #{catalog.ref_type}: #{catalog.ref}" if catalog.ref
|
292
|
+
when Gel::PathCatalog
|
293
|
+
lock_content << "PATH"
|
294
|
+
lock_content << " remote: #{catalog.path}"
|
295
|
+
end
|
296
|
+
|
297
|
+
output_specs_for.call(grouped_graph[catalog])
|
298
|
+
lock_content << ""
|
299
|
+
end
|
300
|
+
|
301
|
+
if server_gems
|
302
|
+
lock_content << "GEM"
|
303
|
+
server_catalogs.each do |catalog|
|
304
|
+
lock_content << " remote: #{catalog}"
|
305
|
+
end
|
306
|
+
output_specs_for.call(server_gems)
|
307
|
+
lock_content << ""
|
308
|
+
end
|
309
|
+
|
310
|
+
lock_content << "PLATFORMS"
|
311
|
+
lock_content << " ruby"
|
312
|
+
lock_content << ""
|
313
|
+
|
314
|
+
lock_content << "DEPENDENCIES"
|
315
|
+
|
316
|
+
bang_deps = gemfile.gems.select { |_, _, options|
|
317
|
+
options[:path] || options[:git] || options[:source]
|
318
|
+
}.map { |name, _, _| name }
|
319
|
+
|
320
|
+
root_deps = source.root_dependencies
|
321
|
+
root_deps.sort_by { |name,_| name }.each do |name, constraints|
|
322
|
+
next if name =~ /^~/
|
323
|
+
|
324
|
+
bang = "!" if bang_deps.include?(name)
|
325
|
+
if constraints == []
|
326
|
+
lock_content << " #{name}#{bang}"
|
327
|
+
else
|
328
|
+
r = Gel::Support::GemRequirement.new(constraints)
|
329
|
+
req_strings = r.requirements.sort_by { |(_op, ver)| ver }.map { |(op, ver)| "#{op} #{ver}" }
|
330
|
+
|
331
|
+
lock_content << " #{name} (#{req_strings.join(", ")})#{bang}"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
lock_content << ""
|
335
|
+
|
336
|
+
unless gemfile.ruby.empty?
|
337
|
+
lock_content << "RUBY VERSION"
|
338
|
+
lock_content << " #{RUBY_DESCRIPTION.split.first(2).join(" ")}"
|
339
|
+
lock_content << ""
|
340
|
+
end
|
341
|
+
|
342
|
+
if loader&.bundler_version
|
343
|
+
lock_content << "BUNDLED WITH"
|
344
|
+
lock_content << " #{loader.bundler_version}"
|
345
|
+
lock_content << ""
|
346
|
+
end
|
347
|
+
|
348
|
+
lock_body = lock_content.join("\n")
|
349
|
+
|
350
|
+
if lockfile
|
351
|
+
output.puts "Writing lockfile to #{File.expand_path(lockfile)}" if output
|
352
|
+
File.write(lockfile, lock_body)
|
353
|
+
end
|
354
|
+
lock_body
|
355
|
+
end
|
356
|
+
|
357
|
+
def self.install_gem(catalogs, gem_name, requirements = nil, output: nil)
|
358
|
+
base_store = Gel::Environment.store
|
359
|
+
base_store = base_store.inner if base_store.is_a?(Gel::LockedStore)
|
360
|
+
|
361
|
+
req = Gel::Support::GemRequirement.new(requirements)
|
362
|
+
#base_store.each(gem_name) do |g|
|
363
|
+
# return false if g.satisfies?(req)
|
364
|
+
#end
|
365
|
+
|
366
|
+
require_relative "installer"
|
367
|
+
installer = Gel::Installer.new(base_store)
|
368
|
+
|
369
|
+
found_any = false
|
370
|
+
catalogs.each do |catalog|
|
371
|
+
# TODO: Hand this over to resolution so we pick up dependencies
|
372
|
+
# too
|
373
|
+
|
374
|
+
info = catalog.gem_info(gem_name)
|
375
|
+
next if info.nil?
|
376
|
+
|
377
|
+
found_any = true
|
378
|
+
version = info.keys.
|
379
|
+
map { |v| Gel::Support::GemVersion.new(v.split("-", 2).first) }.
|
380
|
+
sort_by { |v| [v.prerelease? ? 0 : 1, v] }.
|
381
|
+
reverse.find { |v| req.satisfied_by?(v) }
|
382
|
+
next if version.nil?
|
383
|
+
|
384
|
+
return false if base_store.gem?(gem_name, version.to_s)
|
385
|
+
|
386
|
+
installer.install_gem([catalog], gem_name, version.to_s)
|
387
|
+
|
388
|
+
installer.wait(output)
|
389
|
+
|
390
|
+
return true
|
391
|
+
end
|
392
|
+
|
393
|
+
if found_any
|
394
|
+
raise "no version of gem #{gem_name.inspect} satifies #{requirements.inspect}"
|
395
|
+
else
|
396
|
+
raise "unknown gem #{gem_name.inspect}"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.activate(install: false, output: nil, error: true)
|
401
|
+
loaded = Gel::Environment.load_gemfile
|
402
|
+
return if loaded.nil?
|
403
|
+
return if @active_lockfile
|
404
|
+
|
405
|
+
lockfile = Gel::Environment.lockfile_name
|
406
|
+
unless File.exist?(lockfile)
|
407
|
+
lock(output: $stderr, lockfile: lockfile)
|
408
|
+
end
|
409
|
+
|
410
|
+
@active_lockfile = true
|
411
|
+
loader = Gel::LockLoader.new(lockfile, gemfile)
|
412
|
+
|
413
|
+
base_store = Gel::Environment.store
|
414
|
+
base_store = base_store.inner if base_store.is_a?(Gel::LockedStore)
|
415
|
+
|
416
|
+
loader.activate(Gel::Environment, base_store, install: install, output: output)
|
417
|
+
end
|
418
|
+
|
419
|
+
def self.activate_for_executable(exes, install: false, output: nil)
|
420
|
+
loader = nil
|
421
|
+
if Gel::Environment.load_gemfile(error: false)
|
422
|
+
lockfile = Gel::Environment.lockfile_name
|
423
|
+
unless File.exist?(lockfile)
|
424
|
+
lock(output: $stderr, lockfile: lockfile)
|
425
|
+
end
|
426
|
+
|
427
|
+
loader = Gel::LockLoader.new(lockfile, gemfile)
|
428
|
+
|
429
|
+
base_store = Gel::Environment.store
|
430
|
+
base_store = base_store.inner if base_store.is_a?(Gel::LockedStore)
|
431
|
+
|
432
|
+
locked_store = loader.activate(nil, base_store, install: install, output: output)
|
433
|
+
|
434
|
+
exes.each do |exe|
|
435
|
+
if locked_store.each.any? { |g| g.executables.include?(exe) }
|
436
|
+
activate(install: install, output: output)
|
437
|
+
return :lock
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
locked_gems = loader ? loader.gem_names : []
|
443
|
+
|
444
|
+
@gemfile = nil
|
445
|
+
exes.each do |exe|
|
446
|
+
candidates = @store.each.select do |g|
|
447
|
+
!locked_gems.include?(g.name) && g.executables.include?(exe)
|
448
|
+
end.group_by(&:name)
|
449
|
+
|
450
|
+
case candidates.size
|
451
|
+
when 0
|
452
|
+
nil
|
453
|
+
when 1
|
454
|
+
gem(candidates.keys.first)
|
455
|
+
return :gem
|
456
|
+
else
|
457
|
+
# Multiple gems can supply this executable; do we have any
|
458
|
+
# useful way of deciding which one should win? One obvious
|
459
|
+
# tie-breaker: if a gem's name matches the executable, it wins.
|
460
|
+
|
461
|
+
if candidates.keys.include?(exe)
|
462
|
+
gem(exe)
|
463
|
+
else
|
464
|
+
gem(candidates.keys.first)
|
465
|
+
end
|
466
|
+
|
467
|
+
return :gem
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
nil
|
472
|
+
end
|
473
|
+
|
474
|
+
def self.find_executable(exe, gem_name = nil, gem_version = nil)
|
475
|
+
@store.each(gem_name) do |g|
|
476
|
+
next if gem_version && g.version != gem_version
|
477
|
+
return File.join(g.root, g.bindir, exe) if g.executables.include?(exe)
|
478
|
+
end
|
479
|
+
nil
|
480
|
+
end
|
481
|
+
|
482
|
+
def self.filtered_gems(gems = self.gemfile.gems)
|
483
|
+
platforms = GEMFILE_PLATFORMS.map(&:to_s)
|
484
|
+
gems = gems.reject { |g| g[2][:platforms] && (Array(g[2][:platforms]).map(&:to_s) & platforms).empty? }
|
485
|
+
gems
|
486
|
+
end
|
487
|
+
|
488
|
+
def self.require_groups(*groups)
|
489
|
+
gems = filtered_gems
|
490
|
+
groups = [:default] if groups.empty?
|
491
|
+
groups = groups.map(&:to_s)
|
492
|
+
gems = gems.reject { |g| ((g[2][:group] || [:default]).map(&:to_s) & groups).empty? }
|
493
|
+
@gemfile.autorequire(self, gems)
|
494
|
+
end
|
495
|
+
|
496
|
+
def self.find_gem(name, *requirements, &condition)
|
497
|
+
requirements = Gel::Support::GemRequirement.new(requirements)
|
498
|
+
|
499
|
+
@store.each(name).find do |g|
|
500
|
+
g.satisfies?(requirements) && (!condition || condition.call(g))
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def self.gem(name, *requirements, why: nil)
|
505
|
+
return if IGNORE_LIST.include?(name)
|
506
|
+
|
507
|
+
requirements = Gel::Support::GemRequirement.new(requirements)
|
508
|
+
|
509
|
+
if existing = activated_gems[name]
|
510
|
+
if existing.satisfies?(requirements)
|
511
|
+
return
|
512
|
+
else
|
513
|
+
why = " (#{why.join("; ")})" if why && why.first
|
514
|
+
raise LoadError, "already loaded gem #{name} #{existing.version}, which is incompatible with: #{requirements}#{why}"
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
gem = @store.each(name).find do |g|
|
519
|
+
g.satisfies?(requirements)
|
520
|
+
end
|
521
|
+
|
522
|
+
if gem
|
523
|
+
activate_gem gem, why: why
|
524
|
+
else
|
525
|
+
why = " (#{why.join("; ")})" if why && why.first
|
526
|
+
raise LoadError, "unable to satisfy requirements for gem #{name}: #{requirements}#{why}"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def self.gems_from_lock(name_version_pairs)
|
531
|
+
gems = @store.gems(name_version_pairs)
|
532
|
+
|
533
|
+
dirs = []
|
534
|
+
gems.each do |name, g|
|
535
|
+
dirs += g.require_paths
|
536
|
+
end
|
537
|
+
|
538
|
+
activated_gems.update gems
|
539
|
+
$:.concat dirs
|
540
|
+
end
|
541
|
+
|
542
|
+
def self.activate_gem(gem, why: nil)
|
543
|
+
return if activated_gems[gem.name] && activated_gems[gem.name].version == gem.version
|
544
|
+
raise LoadError, "already activated #{gem.name} #{activated_gems[gem.name].version}" if activated_gems[gem.name]
|
545
|
+
|
546
|
+
gem.dependencies.each do |dep, reqs|
|
547
|
+
self.gem(dep, *reqs.map { |(qual, ver)| "#{qual} #{ver}" }, why: ["required by #{gem.name} #{gem.version}", *why])
|
548
|
+
end
|
549
|
+
|
550
|
+
lib_dirs = gem.require_paths
|
551
|
+
@store.prepare gem.name => gem.version
|
552
|
+
|
553
|
+
activated_gems[gem.name] = gem
|
554
|
+
$:.concat lib_dirs
|
555
|
+
end
|
556
|
+
|
557
|
+
def self.gem_has_file?(gem_name, path)
|
558
|
+
@store.gems_for_lib(path) do |gem, subdir|
|
559
|
+
if gem.name == gem_name && gem == activated_gems[gem_name]
|
560
|
+
return gem.path(path, subdir)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
false
|
565
|
+
end
|
566
|
+
|
567
|
+
def self.scoped_require(gem_name, path)
|
568
|
+
if full_path = gem_has_file?(gem_name, path)
|
569
|
+
require full_path
|
570
|
+
else
|
571
|
+
raise LoadError, "No file #{path.inspect} found in gem #{gem_name.inspect}"
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
def self.resolve_gem_path(path)
|
576
|
+
if @store && !path.start_with?("/")
|
577
|
+
results = []
|
578
|
+
@store.gems_for_lib(path) do |gem, subdir|
|
579
|
+
results << [gem, subdir]
|
580
|
+
break if activated_gems[gem.name] == gem
|
581
|
+
end
|
582
|
+
result = results.find { |g, _| activated_gems[g.name] == g } || results.first
|
583
|
+
|
584
|
+
if result
|
585
|
+
activate_gem result[0], why: ["provides #{path.inspect}"]
|
586
|
+
return result[0].path(path, result[1])
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
path
|
591
|
+
end
|
592
|
+
end
|