gel 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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
|
data/lib/gel/pinboard.rb
ADDED
@@ -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
|