gel 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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