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,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "cgi"
5
+ require "zlib"
6
+
7
+ require_relative "../pinboard"
8
+
9
+ require_relative "common"
10
+ require_relative "marshal_hacks"
11
+
12
+ class Gel::Catalog::DependencyIndex
13
+ include Gel::Catalog::Common
14
+ CACHE_TYPE = "quick"
15
+
16
+ LIST_MAX = 40
17
+
18
+ def initialize(catalog, *args)
19
+ super(*args)
20
+
21
+ @catalog = catalog
22
+
23
+ @active_gems = Set.new
24
+ @pending_gems = Set.new
25
+ end
26
+
27
+ def prepare(gems)
28
+ @monitor.synchronize do
29
+ @pending_gems.merge(gems)
30
+ end
31
+ force_refresh_including(gems.first)
32
+ @monitor.synchronize do
33
+ @refresh_cond.wait_until { gems.all? { |g| _info(g) } }
34
+ end
35
+ end
36
+
37
+ def force_refresh_including(gem_name)
38
+ gems_to_refresh = []
39
+
40
+ @monitor.synchronize do
41
+ return if _info(gem_name) || @active_gems.include?(gem_name)
42
+
43
+ gems_to_refresh << gem_name
44
+ @pending_gems.delete gem_name
45
+
46
+ while gems_to_refresh.size < LIST_MAX && @pending_gems.size > 0
47
+ a_gem = @pending_gems.first
48
+ @pending_gems.delete a_gem
49
+ gems_to_refresh << a_gem
50
+ end
51
+
52
+ @active_gems.merge gems_to_refresh
53
+ end
54
+
55
+ refresh_some_gems gems_to_refresh
56
+ end
57
+
58
+ def refresh_some_gems(gems)
59
+ gem_list = gems.map { |g| CGI.escape(g) }.sort.join(",")
60
+ @work_pool.queue(gem_list) do
61
+ response =
62
+ begin
63
+ @catalog.send(:http_get, "api/v1/dependencies?gems=#{gem_list}")
64
+ rescue Exception => ex
65
+ @monitor.synchronize do
66
+ @error = ex
67
+ @refresh_cond.broadcast
68
+ end
69
+ next
70
+ end
71
+
72
+ new_info = {}
73
+ gems.each { |g| new_info[g] = {} }
74
+
75
+ new_dependencies = Set.new
76
+
77
+ hashes = Marshal.load(response.body)
78
+ hashes.each do |hash|
79
+ v = hash[:number].to_s
80
+ v += "-#{hash[:platform]}" unless hash[:platform] == "ruby"
81
+
82
+ (new_info[hash[:name]] ||= {})[v] = {
83
+ dependencies: hash[:dependencies].map { |name, versions| [name, versions.split(/,\s*/)] },
84
+ ruby: lambda do
85
+ # The disadvantage of trying to avoid this per-version
86
+ # request is that when we do discover we need it, we need it
87
+ # immediately. :/
88
+ pinboard.file(uri("quick", "Marshal.4.8", "#{hash[:name]}-#{v}.gemspec.rz"), token: false, tail: false) do |f|
89
+ data = Zlib::Inflate.inflate(f.read)
90
+ # TODO: Extract the data we need without a full unmarshal
91
+ Marshal.load(data).required_ruby_version
92
+ end
93
+ end,
94
+ }
95
+ end
96
+
97
+ hashes.group_by { |h| h[:name] }.each do |_, group|
98
+ versions = group.group_by { |h| h[:number] }
99
+ latest = versions.keys.max_by { |v| Gel::Support::GemVersion.new(v) }
100
+ new_dependencies.merge versions[latest].flat_map { |h| h[:dependencies].map(&:first) }.uniq
101
+ end
102
+
103
+ @monitor.synchronize do
104
+ @gem_info.update new_info
105
+ @active_gems.subtract new_info.keys
106
+
107
+ @refresh_cond.broadcast
108
+
109
+ new_dependencies.subtract @active_gems
110
+ new_dependencies.subtract @gem_info.keys
111
+ @pending_gems.merge new_dependencies
112
+ end
113
+ end
114
+
115
+ @work_pool.start
116
+ end
117
+
118
+ def refresh_gem(gem_name, immediate = false)
119
+ @monitor.synchronize do
120
+ @pending_gems << gem_name unless _info(gem_name) || @active_gems.include?(gem_name)
121
+ end
122
+
123
+ force_refresh_including gem_name if immediate
124
+ end
125
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "zlib"
5
+
6
+ require_relative "../pinboard"
7
+
8
+ require_relative "common"
9
+ require_relative "marshal_hacks"
10
+
11
+ class Gel::Catalog::LegacyIndex
12
+ include Gel::Catalog::Common
13
+ CACHE_TYPE = "quick"
14
+
15
+ def initialize(*)
16
+ super
17
+
18
+ @needs_update = true
19
+ @updating = false
20
+ @active_gems = Set.new
21
+ @pending_gems = Set.new
22
+
23
+ @gem_versions = {}
24
+ end
25
+
26
+ def prepare(gems)
27
+ @monitor.synchronize do
28
+ @pending_gems.merge(gems)
29
+ end
30
+ update
31
+ @monitor.synchronize do
32
+ @refresh_cond.wait_until { gems.all? { |g| _info(g) } }
33
+ end
34
+ end
35
+
36
+ def update
37
+ @monitor.synchronize do
38
+ return false unless @needs_update
39
+ @needs_update = false
40
+ @updating = true
41
+ end
42
+
43
+ specs = false
44
+ prerelease_specs = false
45
+
46
+ versions = {}
47
+
48
+ error = lambda do |ex|
49
+ @monitor.synchronize do
50
+ @error = ex
51
+ @updating = false
52
+ @refresh_cond.broadcast
53
+ end
54
+ end
55
+
56
+ spec_file_handler = lambda do |for_prerelease|
57
+ lambda do |f|
58
+ data = Zlib::GzipReader.new(f).read
59
+ data = Marshal.load(data)
60
+
61
+ data.each do |name, version, platform|
62
+ v = version.to_s
63
+ v += "-#{platform}" unless platform == "ruby"
64
+ (versions[name] ||= {})[v] = nil
65
+ end
66
+
67
+ done = false
68
+ @monitor.synchronize do
69
+ if for_prerelease
70
+ prerelease_specs = true
71
+ else
72
+ specs = true
73
+ end
74
+
75
+ if specs && prerelease_specs
76
+ done = true
77
+ @gem_info.update versions
78
+ @updating = false
79
+ @refresh_cond.broadcast
80
+ end
81
+ end
82
+
83
+ if done
84
+ (@active_gems | @pending_gems).each do |name|
85
+ refresh_gem(name)
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ pinboard.async_file(uri("specs.4.8.gz"), tail: false, error: error, &spec_file_handler.(false))
92
+ pinboard.async_file(uri("prerelease_specs.4.8.gz"), tail: false, error: error, &spec_file_handler.(true))
93
+
94
+ true
95
+ end
96
+
97
+ def refresh_gem(gem_name, immediate = true)
98
+ update
99
+
100
+ versions = nil
101
+ @monitor.synchronize do
102
+ if @updating
103
+ @pending_gems << gem_name
104
+ return
105
+ end
106
+
107
+ unless info = @gem_info[gem_name]
108
+ @gem_info[gem_name] = {}
109
+ @refresh_cond.broadcast
110
+ return
111
+ end
112
+
113
+ versions = info.keys.select { |v| info[v].nil? }
114
+ versions.each do |v|
115
+ info[v] = :pending
116
+ end
117
+ end
118
+
119
+ loaded_data = {}
120
+ versions.each do |v|
121
+ loaded_data[v] = nil
122
+ end
123
+
124
+ versions.each do |v|
125
+ error = lambda do |ex|
126
+ @gem_info[gem_name][v] = ex
127
+ @refresh_cond.broadcast
128
+ end
129
+
130
+ pinboard.async_file(uri("quick", "Marshal.4.8", "#{gem_name}-#{v}.gemspec.rz"), token: false, tail: false, error: error) do |f|
131
+ data = Zlib::Inflate.inflate(f.read)
132
+ # TODO: Extract the data we need without a full unmarshal
133
+ spec = Marshal.load(data)
134
+
135
+ @monitor.synchronize do
136
+ loaded_data[v] = { dependencies: spec.dependencies, ruby: spec.required_ruby_version }
137
+ if loaded_data.values.all?
138
+ @gem_info[gem_name].update loaded_data
139
+ @refresh_cond.broadcast
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ def _info(gem_name)
149
+ if i = super
150
+ if i.values.all? { |v| v.is_a?(Hash) }
151
+ i
152
+ elsif e = i.values.grep(Exception).first
153
+ raise e
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gem::Specification
4
+ class Unmarshalled
5
+ attr_accessor :required_ruby_version
6
+ attr_accessor :dependencies
7
+ end
8
+
9
+ def self._load(str)
10
+ array = Marshal.load(str)
11
+ o = Unmarshalled.new
12
+ o.required_ruby_version = array[6].as_list
13
+ o.dependencies = array[9].map { |d| [d.name, d.requirement.as_list] if d.type == :runtime }.compact
14
+ o
15
+ end
16
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "compatibility"
4
+ require_relative "error"
5
+
6
+ class Gel::Command
7
+ def self.run(command_line)
8
+ command_line = command_line.dup
9
+ if command_name = extract_word(command_line)
10
+ const = command_name.downcase.sub(/^./, &:upcase).gsub(/[-_]./) { |s| s[1].upcase }
11
+ if Gel::Command.const_defined?(const, false)
12
+ command = Gel::Command.const_get(const, false).new
13
+ command.run(command_line)
14
+ elsif Gel::Environment.activate_for_executable(["gel-#{command_name}", command_name])
15
+ command_name = "gel-#{command_name}" if Gel::Environment.find_executable("gel-#{command_name}")
16
+ command = Gel::Command::Exec.new
17
+ command.run([command_name, *command_line])
18
+ else
19
+ raise "Unknown command #{command_name.inspect}"
20
+ end
21
+ else
22
+ puts <<~EOF
23
+ Gel doesn't have a default command; please run `gel install`
24
+ EOF
25
+ end
26
+ rescue Exception => ex
27
+ raise if $DEBUG || (command && command.reraise)
28
+ handle_error(ex)
29
+ end
30
+
31
+ def self.handle_error(ex)
32
+ case ex
33
+ when Gel::ReportableError
34
+ $stderr.puts "ERROR: #{ex.message}"
35
+ if more = ex.details
36
+ $stderr.puts more
37
+ end
38
+
39
+ exit ex.exit_code
40
+ when Interrupt
41
+ # Re-signal so our parent knows why we died
42
+ Signal.trap(ex.signo, "SYSTEM_DEFAULT")
43
+ Process.kill(ex.signo, Process.pid)
44
+
45
+ # Shouldn't be reached
46
+ raise ex
47
+ when SystemExit, SignalException
48
+ raise ex
49
+ when StandardError, ScriptError, NoMemoryError, SystemStackError
50
+ # We want basically everything here: we definitely care about
51
+ # StandardError and ScriptError... but we also assume that whatever
52
+ # caused NoMemoryError or SystemStackError was way down the call
53
+ # stack, so we've now unwound enough to safely handle even those.
54
+
55
+ $stderr.print "\n\n===== Gel Internal Error =====\n\n"
56
+
57
+ # We'll improve this later, but for now after the header we'll leave
58
+ # ruby to write the message & backtrace:
59
+ raise ex
60
+ else
61
+ raise ex
62
+ end
63
+ end
64
+
65
+ def self.extract_word(arguments)
66
+ if idx = arguments.index { |w| w =~ /^[^-]/ }
67
+ arguments.delete_at(idx)
68
+ end
69
+ end
70
+
71
+ # If set to true, an error raised from #run will pass straight up to
72
+ # ruby instead of being treated as an internal Gel error
73
+ attr_accessor :reraise
74
+ end
75
+
76
+ require_relative "command/help"
77
+ require_relative "command/install"
78
+ require_relative "command/install_gem"
79
+ require_relative "command/env"
80
+ require_relative "command/exec"
81
+ require_relative "command/lock"
82
+ require_relative "command/update"
83
+ require_relative "command/ruby"
84
+ require_relative "command/stub"
85
+ require_relative "command/config"
86
+ require_relative "command/shell_setup"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gel::Command::Config < Gel::Command
4
+ def run(command_line)
5
+ if command_line.size == 1
6
+ puts Gel::Environment.config[command_line.first]
7
+ else
8
+ Gel::Environment.config[command_line.shift] = command_line.join(" ")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gel::Command::Env < Gel::Command
4
+ def run(command_line)
5
+ raise "TODO"
6
+ end
7
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Gel::Command::Exec < Gel::Command
4
+ def run(command_line, from_stub: false)
5
+ original_command = command_line.shift
6
+ expanded_command, command_source = expand_executable(original_command)
7
+
8
+ if from_stub && [:original, :path].include?(command_source)
9
+ raise Gel::Error::BrokenStubError.new(name: original_command)
10
+ end
11
+
12
+ gemfile = Gel::Environment.find_gemfile(error: false)
13
+
14
+ if gemfile && command_source != :gem
15
+ ENV["GEL_GEMFILE"] = File.expand_path(gemfile)
16
+ ENV["GEL_LOCKFILE"] = File.expand_path(Gel::Environment.lockfile_name(gemfile))
17
+ end
18
+
19
+ ENV["RUBYLIB"] = Gel::Environment.modified_rubylib
20
+
21
+ if execute_inline?(expanded_command)
22
+ if command_source == :path || command_source == :original
23
+ if ENV["GEL_LOCKFILE"]
24
+ Gel::Environment.activate(output: $stderr)
25
+ end
26
+ end
27
+
28
+ $0 = original_command
29
+ ARGV.replace(command_line)
30
+
31
+ # Any error after this point should bypass Gel's error
32
+ # handling
33
+ self.reraise = true
34
+ Kernel.load(expanded_command)
35
+ else
36
+ Kernel.exec([original_command, expanded_command], *command_line)
37
+ end
38
+ end
39
+
40
+ def expand_executable(original_command)
41
+ if original_command.include?(File::SEPARATOR) || (File::ALT_SEPARATOR && original_command.include?(File::ALT_SEPARATOR))
42
+ return [File.expand_path(original_command), :path]
43
+ end
44
+
45
+ if source = Gel::Environment.activate_for_executable([original_command])
46
+ if found = Gel::Environment.find_executable(original_command)
47
+ return [found, source]
48
+ end
49
+ end
50
+
51
+ path_attempts = ENV["PATH"].split(File::PATH_SEPARATOR).map { |e| File.join(e, original_command) }
52
+ if found = path_attempts.find { |path| File.executable?(path) }
53
+ return [File.expand_path(found), :path]
54
+ end
55
+
56
+ [original_command, :original]
57
+ end
58
+
59
+ def execute_inline?(expanded_command)
60
+ if File.exist?(expanded_command) && File.executable?(expanded_command)
61
+ File.open(expanded_command, "rb") do |f|
62
+ f.read(2) == "#!" && f.gets.chomp =~ /\bruby\b/
63
+ end
64
+ end
65
+ end
66
+ end