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,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