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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +74 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +39 -0
  5. data/exe/gel +13 -0
  6. data/lib/gel.rb +22 -0
  7. data/lib/gel/catalog.rb +153 -0
  8. data/lib/gel/catalog/common.rb +82 -0
  9. data/lib/gel/catalog/compact_index.rb +152 -0
  10. data/lib/gel/catalog/dependency_index.rb +125 -0
  11. data/lib/gel/catalog/legacy_index.rb +157 -0
  12. data/lib/gel/catalog/marshal_hacks.rb +16 -0
  13. data/lib/gel/command.rb +86 -0
  14. data/lib/gel/command/config.rb +11 -0
  15. data/lib/gel/command/env.rb +7 -0
  16. data/lib/gel/command/exec.rb +66 -0
  17. data/lib/gel/command/help.rb +7 -0
  18. data/lib/gel/command/install.rb +7 -0
  19. data/lib/gel/command/install_gem.rb +16 -0
  20. data/lib/gel/command/lock.rb +34 -0
  21. data/lib/gel/command/ruby.rb +10 -0
  22. data/lib/gel/command/shell_setup.rb +25 -0
  23. data/lib/gel/command/stub.rb +12 -0
  24. data/lib/gel/command/update.rb +11 -0
  25. data/lib/gel/compatibility.rb +4 -0
  26. data/lib/gel/compatibility/bundler.rb +54 -0
  27. data/lib/gel/compatibility/bundler/cli.rb +6 -0
  28. data/lib/gel/compatibility/bundler/friendly_errors.rb +3 -0
  29. data/lib/gel/compatibility/bundler/setup.rb +4 -0
  30. data/lib/gel/compatibility/rubygems.rb +192 -0
  31. data/lib/gel/compatibility/rubygems/command.rb +4 -0
  32. data/lib/gel/compatibility/rubygems/dependency_installer.rb +0 -0
  33. data/lib/gel/compatibility/rubygems/gem_runner.rb +6 -0
  34. data/lib/gel/config.rb +80 -0
  35. data/lib/gel/db.rb +294 -0
  36. data/lib/gel/direct_gem.rb +29 -0
  37. data/lib/gel/environment.rb +592 -0
  38. data/lib/gel/error.rb +104 -0
  39. data/lib/gel/gemfile_parser.rb +144 -0
  40. data/lib/gel/gemspec_parser.rb +95 -0
  41. data/lib/gel/git_catalog.rb +38 -0
  42. data/lib/gel/git_depot.rb +119 -0
  43. data/lib/gel/httpool.rb +148 -0
  44. data/lib/gel/installer.rb +251 -0
  45. data/lib/gel/lock_loader.rb +164 -0
  46. data/lib/gel/lock_parser.rb +64 -0
  47. data/lib/gel/locked_store.rb +126 -0
  48. data/lib/gel/multi_store.rb +96 -0
  49. data/lib/gel/package.rb +156 -0
  50. data/lib/gel/package/inspector.rb +23 -0
  51. data/lib/gel/package/installer.rb +267 -0
  52. data/lib/gel/path_catalog.rb +44 -0
  53. data/lib/gel/pinboard.rb +140 -0
  54. data/lib/gel/pub_grub/preference_strategy.rb +82 -0
  55. data/lib/gel/pub_grub/source.rb +153 -0
  56. data/lib/gel/runtime.rb +27 -0
  57. data/lib/gel/store.rb +205 -0
  58. data/lib/gel/store_catalog.rb +31 -0
  59. data/lib/gel/store_gem.rb +80 -0
  60. data/lib/gel/stub_set.rb +51 -0
  61. data/lib/gel/support/gem_platform.rb +225 -0
  62. data/lib/gel/support/gem_requirement.rb +264 -0
  63. data/lib/gel/support/gem_version.rb +398 -0
  64. data/lib/gel/support/tar.rb +13 -0
  65. data/lib/gel/support/tar/tar_header.rb +229 -0
  66. data/lib/gel/support/tar/tar_reader.rb +123 -0
  67. data/lib/gel/support/tar/tar_reader/entry.rb +154 -0
  68. data/lib/gel/support/tar/tar_writer.rb +339 -0
  69. data/lib/gel/tail_file.rb +205 -0
  70. data/lib/gel/version.rb +5 -0
  71. data/lib/gel/work_pool.rb +143 -0
  72. data/man/man1/gel-exec.1 +16 -0
  73. data/man/man1/gel-install.1 +16 -0
  74. data/man/man1/gel.1 +30 -0
  75. metadata +131 -0
@@ -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