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,267 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+ require "rbconfig"
6
+ require "tempfile"
7
+
8
+ class Gel::Package::Installer
9
+ def initialize(store)
10
+ @store = store
11
+ end
12
+
13
+ def gem(spec)
14
+ g = GemInstaller.new(spec, @store)
15
+ begin
16
+ yield g
17
+ rescue Exception
18
+ g.abort!
19
+ raise
20
+ end
21
+ g
22
+ end
23
+
24
+ class GemInstaller
25
+ attr_reader :spec, :store, :root, :build_path
26
+
27
+ def initialize(spec, store)
28
+ @spec = spec
29
+ @root_store = store
30
+
31
+ if store.is_a?(Gel::MultiStore)
32
+ store = store[spec.architecture, spec.extensions.any?]
33
+ end
34
+ @store = store
35
+
36
+ raise "gem already installed" if store.gem?(spec.name, spec.version)
37
+
38
+ @config = Gel::Environment.config
39
+
40
+ @root = store.gem_root(spec.name, spec.version)
41
+ FileUtils.rm_rf(@root) if @root && Dir.exist?(@root)
42
+
43
+ if spec.extensions.any?
44
+ @build_path = store.extension_path(spec.name, spec.version)
45
+ FileUtils.rm_rf(@build_path) if @build_path && Dir.exist?(@build_path)
46
+ else
47
+ @build_path = nil
48
+ end
49
+
50
+ @files = {}
51
+ @installed_files = []
52
+ spec.require_paths.each { |reqp| @files[reqp] = [] }
53
+ end
54
+
55
+ def abort!
56
+ $stderr.puts "FileUtils.rm_rf(#{root.inspect})" if root
57
+ $stderr.puts "FileUtils.rm_rf(#{build_path.inspect})" if build_path
58
+ #FileUtils.rm_rf(root) if root
59
+ #FileUtils.rm_rf(build_path) if build_path
60
+ end
61
+
62
+ def needs_compile?
63
+ !!@build_path
64
+ end
65
+
66
+ def compile_ready?
67
+ true
68
+ end
69
+
70
+ def with_build_environment(ext, install_dir)
71
+ work_dir = File.expand_path(File.dirname(ext), root)
72
+
73
+ FileUtils.mkdir_p(install_dir)
74
+ short_install_dir = Pathname.new(install_dir).relative_path_from(Pathname.new(work_dir)).to_s
75
+
76
+ local_config = Tempfile.new(["config", ".rb"])
77
+ local_config.write(<<-RUBY)
78
+ require "rbconfig"
79
+
80
+ RbConfig::MAKEFILE_CONFIG["sitearchdir"] =
81
+ RbConfig::MAKEFILE_CONFIG["sitelibdir"] =
82
+ RbConfig::CONFIG["sitearchdir"] =
83
+ RbConfig::CONFIG["sitelibdir"] = #{short_install_dir.dump}.freeze
84
+ RUBY
85
+ local_config.close
86
+
87
+ File.open("#{install_dir}/build.log", "w") do |log|
88
+ yield work_dir, short_install_dir, local_config.path, log
89
+ end
90
+ ensure
91
+ local_config.unlink if local_config
92
+ end
93
+
94
+ def gemfile_and_lockfile(rake: false)
95
+ @gemfile ||=
96
+ begin
97
+ gemfile = Tempfile.new(["#{spec.name}.gemfile", ".rb"])
98
+ gemfile.puts "source :local"
99
+ spec.runtime_dependencies.each do |(name, operator_pairs)|
100
+ arguments = [name, *operator_pairs.map { |op, ver| "#{op} #{ver}" }]
101
+ gemfile.puts "gem #{arguments.map(&:inspect).join(", ")}"
102
+ end
103
+ if rake
104
+ gemfile.puts "gem 'rake'" unless spec.runtime_dependencies.any? { |name, *| name == "rake" }
105
+ end
106
+ gemfile.close
107
+
108
+ gemfile
109
+ end
110
+
111
+ @lockfile ||=
112
+ begin
113
+ lockfile = Tempfile.new(["#{spec.name}.lockfile", ".lock"])
114
+ lockfile.close
115
+
116
+ Gel::Environment.lock(store: @root_store, output: nil, gemfile: Gel::GemfileParser.parse(File.read(gemfile.path), gemfile.path, 1), lockfile: lockfile.path)
117
+
118
+ lockfile
119
+ end
120
+
121
+ [@gemfile.path, @lockfile.path]
122
+ end
123
+
124
+ def build_environment(rake: false)
125
+ gemfile, lockfile = gemfile_and_lockfile(rake: rake)
126
+
127
+ {
128
+ "RUBYLIB" => Gel::Environment.modified_rubylib,
129
+ "GEL_STORE" => File.expand_path(@root_store.root),
130
+ "GEL_GEMFILE" => gemfile,
131
+ "GEL_LOCKFILE" => lockfile,
132
+ }
133
+ end
134
+
135
+ def build_command(work_dir, log, *command, rake: false, **options)
136
+ env = build_environment(rake: rake)
137
+ env.merge!(command.shift) if command.first.is_a?(Hash)
138
+
139
+ pid = spawn(
140
+ env,
141
+ *command,
142
+ chdir: work_dir,
143
+ in: IO::NULL,
144
+ [:out, :err] => log,
145
+ **options,
146
+ )
147
+
148
+ _, status = Process.waitpid2(pid)
149
+ status
150
+ end
151
+
152
+ def compile_extconf(ext, install_dir)
153
+ with_build_environment(ext, install_dir) do |work_dir, short_install_dir, local_config_path, log|
154
+ status = build_command(
155
+ work_dir, log,
156
+ { "MAKEFLAGS" => "-j3" },
157
+ RbConfig.ruby,
158
+ "-r", local_config_path,
159
+ File.basename(ext),
160
+ *Shellwords.shellsplit(@config[:build, @spec.name] || ""),
161
+ )
162
+ raise "extconf exited with #{status.exitstatus}" unless status.success?
163
+
164
+ _status = build_command(work_dir, log, "make", "clean", "DESTDIR=")
165
+ # Ignore exit status
166
+
167
+ status = build_command(work_dir, log, "make", "-j3", "DESTDIR=")
168
+ raise "make exited with #{status.exitstatus}" unless status.success?
169
+
170
+ status = build_command(work_dir, log, "make", "install", "DESTDIR=")
171
+ raise "make install exited with #{status.exitstatus}" unless status.success?
172
+ end
173
+ end
174
+
175
+ def compile_rakefile(ext, install_dir)
176
+ with_build_environment(ext, install_dir) do |work_dir, short_install_dir, local_config_path, log|
177
+ if File.basename(ext) =~ /mkrf_conf/i
178
+ status = build_command(
179
+ work_dir, log,
180
+ RbConfig.ruby,
181
+ "-r", local_config_path,
182
+ File.basename(ext),
183
+ rake: true,
184
+ )
185
+ raise "mkrf_conf exited with #{status.exitstatus}" unless status.success?
186
+ end
187
+
188
+ status = build_command(
189
+ work_dir, log,
190
+ { "RUBYARCHDIR" => short_install_dir, "RUBYLIBDIR" => short_install_dir },
191
+ RbConfig.ruby,
192
+ "-r", File.expand_path("../command", __dir__),
193
+ "-e", "Gel::Command.run(ARGV)",
194
+ "--",
195
+ "exec",
196
+ "rake",
197
+ rake: true,
198
+ )
199
+ end
200
+ end
201
+
202
+ def compile
203
+ if spec.extensions.any?
204
+ spec.extensions.each do |ext|
205
+ case File.basename(ext)
206
+ when /extconf/i
207
+ compile_extconf ext, build_path
208
+ when /mkrf_conf/i, /rakefile/i
209
+ compile_rakefile ext, build_path
210
+ else
211
+ raise "Don't know how to build #{ext.inspect} yet"
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ def install
218
+ loadable_file_types = ["rb", RbConfig::CONFIG["DLEXT"], RbConfig::CONFIG["DLEXT2"]].compact.reject(&:empty?)
219
+ loadable_file_types_re = /\.#{Regexp.union loadable_file_types}\z/
220
+ loadable_file_types_pattern = "*.{#{loadable_file_types.join(",")}}"
221
+
222
+ store.add_gem(spec.name, spec.version, spec.bindir, spec.executables, spec.require_paths, spec.runtime_dependencies, spec.extensions.any?) do
223
+ is_first = true
224
+ spec.require_paths.each do |reqp|
225
+ location = is_first ? spec.version : [spec.version, reqp]
226
+ store.add_lib(spec.name, location, @files[reqp].map { |s| s.sub(loadable_file_types_re, "") })
227
+ is_first = false
228
+ end
229
+
230
+ if build_path
231
+ files = Dir["#{build_path}/**/#{loadable_file_types_pattern}"].map do |file|
232
+ file[build_path.size + 1..-1]
233
+ end.map do |file|
234
+ file.sub(loadable_file_types_re, "")
235
+ end
236
+
237
+ store.add_lib(spec.name, [spec.version, Gel::StoreGem::EXTENSION_SUBDIR_TOKEN], files)
238
+ end
239
+ end
240
+ end
241
+
242
+ def file(filename, io, source_mode)
243
+ target = File.expand_path(filename, root)
244
+ raise "invalid filename #{target.inspect} outside #{(root + "/").inspect}" unless target.start_with?("#{root}/")
245
+ return if @installed_files.include?(target)
246
+ @installed_files << target
247
+ spec.require_paths.each do |reqp|
248
+ prefix = "#{root}/#{reqp}/"
249
+ if target.start_with?(prefix)
250
+ @files[reqp] << target[prefix.size..-1]
251
+ end
252
+ end
253
+ raise "won't overwrite #{target}" if File.exist?(target)
254
+ FileUtils.mkdir_p(File.dirname(target))
255
+ mode = 0444
256
+ mode |= source_mode & 0200
257
+ mode |= 0111 if source_mode & 0111 != 0
258
+ if exe = spec.executables.find { |e| filename == "#{spec.bindir}/#{e}" }
259
+ mode |= 0111
260
+ @root_store.stub_set.add(File.basename(@store.root), [exe])
261
+ end
262
+ File.open(target, "wb", mode) do |f|
263
+ f.write io.read
264
+ end
265
+ end
266
+ end
267
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gemspec_parser"
4
+
5
+ class Gel::PathCatalog
6
+ attr_reader :path
7
+
8
+ DEFAULT_GLOB = '{,*,*/*}.gemspec'
9
+
10
+ def initialize(path)
11
+ @path = path
12
+ @cache = {}
13
+ @gemspecs = nil
14
+ end
15
+
16
+ def gemspecs
17
+ @gemspecs ||= Dir["#{@path}/#{DEFAULT_GLOB}"]
18
+ end
19
+
20
+ def gem_info(name)
21
+ @cache.fetch(name) { @cache[name] = _info(name) }
22
+ end
23
+
24
+ def _info(name)
25
+ gemspec = gemspecs.detect { |path| File.basename(path) == "#{name}.gemspec" }
26
+ return unless gemspec
27
+ gemspec = gemspec_from(gemspec)
28
+
29
+ info = {}
30
+ info[gemspec.version] = {
31
+ dependencies: gemspec.runtime_dependencies,
32
+ ruby: gemspec.required_ruby_version,
33
+ }
34
+
35
+ info
36
+ end
37
+
38
+ def gemspec_from(filename)
39
+ Gel::GemspecParser.parse(File.read(filename), filename)
40
+ end
41
+
42
+ def prepare
43
+ end
44
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "db"
4
+ require_relative "httpool"
5
+ require_relative "tail_file"
6
+ require_relative "work_pool"
7
+
8
+ # For each URI, this stores:
9
+ # * the current etag
10
+ # * an external freshness token
11
+ # * a stale flag
12
+ class Gel::Pinboard
13
+ UPDATE_CONCURRENCY = 8
14
+
15
+ attr_reader :root
16
+ def initialize(root, httpool: Gel::Httpool.new, work_pool: nil)
17
+ @root = root
18
+ @httpool = httpool
19
+
20
+ @db = Gel::DB.new(root, "pins")
21
+ @files = {}
22
+ @waiting = Hash.new { |h, k| h[k] = [] }
23
+
24
+ @work_pool = work_pool || Gel::WorkPool.new(UPDATE_CONCURRENCY, name: "gel-pinboard")
25
+ @monitor = Monitor.new
26
+ end
27
+
28
+ def file(uri, token: nil, tail: true)
29
+ @monitor.synchronize do
30
+ add uri, token: token
31
+
32
+ tail_file = Gel::TailFile.new(uri, self, httpool: @httpool)
33
+ tail_file.update(force_reset: !tail) if stale(uri, token)
34
+ end
35
+
36
+ if block_given?
37
+ File.open(filename(uri), "r") do |f|
38
+ yield f
39
+ end
40
+ end
41
+ end
42
+
43
+ def async_file(uri, token: nil, tail: true, only_updated: false, error: nil)
44
+ file_to_yield = nil
45
+
46
+ @monitor.synchronize do
47
+ already_done = @files.key?(uri) && @files[uri].done?
48
+
49
+ if !already_done && stale(uri, token)
50
+ add uri, token: token
51
+
52
+ already_queued = @files.key?(uri)
53
+ tail_file = @files[uri] ||= Gel::TailFile.new(uri, self, httpool: @httpool)
54
+
55
+ unless already_queued
56
+ @work_pool.queue(uri.path) do
57
+ begin
58
+ tail_file.update(force_reset: !tail)
59
+ rescue Exception => ex
60
+ if error
61
+ error.call(ex)
62
+ else
63
+ raise
64
+ end
65
+ end
66
+ end
67
+ @work_pool.start
68
+ end
69
+
70
+ @waiting[uri] << lambda do |f, changed|
71
+ yield f if changed || !only_updated
72
+ end
73
+ elsif !only_updated
74
+ file_to_yield = filename(uri)
75
+ end
76
+ end
77
+
78
+ if file_to_yield
79
+ File.open(file_to_yield, "r") do |f|
80
+ yield f
81
+ end
82
+ end
83
+ end
84
+
85
+ def add(uri, token: nil)
86
+ @db[uri.to_s] ||= {
87
+ etag: nil,
88
+ token: token,
89
+ stale: true,
90
+ }
91
+ end
92
+
93
+ def filename(uri)
94
+ File.expand_path(mangle_uri(uri), @root)
95
+ end
96
+
97
+ def etag(uri)
98
+ read(uri)[:etag]
99
+ end
100
+
101
+ def stale(uri, token)
102
+ if h = read(uri)
103
+ if token && h[:token] == token || token == false
104
+ return h[:stale]
105
+ end
106
+
107
+ @db[uri.to_s] = h.merge(token: token, stale: true)
108
+ end
109
+
110
+ true
111
+ end
112
+
113
+ def read(uri)
114
+ @db[uri.to_s]
115
+ end
116
+
117
+ def updated(uri, etag, changed = true)
118
+ blocks = nil
119
+ @monitor.synchronize do
120
+ @db[uri.to_s] = read(uri).merge(etag: etag, stale: false)
121
+
122
+ blocks = @waiting.delete(uri)
123
+ end
124
+
125
+ return unless blocks && blocks.any?
126
+
127
+ File.open(filename(uri), "r") do |f|
128
+ blocks.each do |block|
129
+ f.rewind
130
+ block.call(f, changed)
131
+ end
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def mangle_uri(uri)
138
+ "#{uri.hostname}--#{uri.path.gsub(/[^A-Za-z0-9]+/, "-")}--#{Digest(:SHA256).hexdigest(uri.to_s)[0, 12]}"
139
+ end
140
+ end