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