herve 0.1.1
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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +30 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +36 -0
- data/Rakefile +12 -0
- data/exe/herve +11 -0
- data/lib/herve/cache/entry.rb +50 -0
- data/lib/herve/cache/shard.rb +17 -0
- data/lib/herve/cache/timestamp.rb +26 -0
- data/lib/herve/cache.rb +48 -0
- data/lib/herve/cli/gem_command.rb +46 -0
- data/lib/herve/cli/ruby_commands/find.rb +19 -0
- data/lib/herve/cli/ruby_commands/install.rb +76 -0
- data/lib/herve/cli/ruby_commands/list.rb +207 -0
- data/lib/herve/cli/ruby_commands/pin.rb +36 -0
- data/lib/herve/cli/ruby_commands/run.rb +21 -0
- data/lib/herve/cli/ruby_commands/uninstall.rb +33 -0
- data/lib/herve/cli/ruby_commands.rb +49 -0
- data/lib/herve/cli/shell_commands/completion.rb +89 -0
- data/lib/herve/cli/shell_commands/env.rb +34 -0
- data/lib/herve/cli/shell_commands/init.rb +53 -0
- data/lib/herve/cli/shell_commands.rb +27 -0
- data/lib/herve/cli.rb +67 -0
- data/lib/herve/config.rb +207 -0
- data/lib/herve/release.rb +5 -0
- data/lib/herve/ruby/engine.rb +38 -0
- data/lib/herve/ruby/request.rb +121 -0
- data/lib/herve/ruby.rb +188 -0
- data/lib/herve/version.rb +135 -0
- data/lib/herve.rb +26 -0
- metadata +187 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Herve
|
6
|
+
module CLI
|
7
|
+
module RubyCommands
|
8
|
+
class Uninstall < BaseCommand
|
9
|
+
desc "uninstall given ruby version"
|
10
|
+
argument :version, desc: "version to uninstall", required: true
|
11
|
+
option :install_dir, desc: "installation directory", default: DEFAULT_RUBIES
|
12
|
+
|
13
|
+
def call(version:, **options)
|
14
|
+
install_dir = Herve.expand_path(options[:install_dir])
|
15
|
+
raise ConfigError, "install_dir does not exist" unless File.directory?(install_dir)
|
16
|
+
|
17
|
+
config.ruby_dirs = [install_dir] unless install_dir.nil?
|
18
|
+
ruby = RubyCommands.find_ruby(config, version)
|
19
|
+
raise RubyError, "no matching ruby" if ruby.nil?
|
20
|
+
|
21
|
+
uninstall_ruby(ruby)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def uninstall_ruby(ruby)
|
27
|
+
FileUtils.remove_dir(ruby.path)
|
28
|
+
logger.info { "Ruby #{Rainbow(ruby.version).cyan} uninstalled" }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herve
|
4
|
+
module CLI
|
5
|
+
module RubyCommands
|
6
|
+
def self.find_ruby(config, request)
|
7
|
+
req = request.nil? ? config.ruby_request : Herve::Ruby::Request.parse(request)
|
8
|
+
config.matching_ruby(req)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.current_platform_string
|
12
|
+
platform = ENV["HERVE_TEST_PLATFORM"] || RUBY_PLATFORM
|
13
|
+
case platform
|
14
|
+
when "x86_64-linux"
|
15
|
+
"x86_64_linux"
|
16
|
+
when "aarch64-darwin"
|
17
|
+
"arm64_sonoma"
|
18
|
+
when "aarch64-linux"
|
19
|
+
"arm64_linux"
|
20
|
+
else
|
21
|
+
raise Error, "herve does not support #{platform}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require_relative "ruby_commands/find"
|
27
|
+
require_relative "ruby_commands/install"
|
28
|
+
require_relative "ruby_commands/list"
|
29
|
+
require_relative "ruby_commands/pin"
|
30
|
+
require_relative "ruby_commands/run"
|
31
|
+
require_relative "ruby_commands/uninstall"
|
32
|
+
|
33
|
+
CLI.register "ruby" do |cli|
|
34
|
+
cli.register "find", RubyCommands::Find
|
35
|
+
cli.register "install", RubyCommands::Install
|
36
|
+
cli.register "list", RubyCommands::List
|
37
|
+
cli.register "pin", RubyCommands::Pin
|
38
|
+
cli.register "run", RubyCommands::Run
|
39
|
+
cli.register "uninstall", RubyCommands::Uninstall
|
40
|
+
end
|
41
|
+
|
42
|
+
%w[find install list pin run uninstall].each do |cmd|
|
43
|
+
before("ruby #{cmd}") do |args|
|
44
|
+
setup_config(args)
|
45
|
+
setup_logger(args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "completely"
|
4
|
+
|
5
|
+
module Herve
|
6
|
+
module CLI
|
7
|
+
module ShellCommands
|
8
|
+
class Completion < BaseCommand
|
9
|
+
desc "generate completion for your shell"
|
10
|
+
argument :shell, desc: "your shell", required: true, values: SUPPORTED_SHELLS
|
11
|
+
|
12
|
+
def call(shell:, **_args)
|
13
|
+
raise Error, "unsupported shell '#{shell}'" unless SUPPORTED_SHELLS.include?(shell)
|
14
|
+
|
15
|
+
if shell == "zsh"
|
16
|
+
puts "autoload -Uz +X compinit && compinit"
|
17
|
+
puts "autoload -Uz +X bashcompinit && bashcompinit"
|
18
|
+
end
|
19
|
+
|
20
|
+
puts Completely::Completions.new(generate_completion_data).script
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def generate_completion_data
|
26
|
+
data = {}
|
27
|
+
update_data_with_commands(data, cli_root_node)
|
28
|
+
program_name = Dry::CLI::ProgramName.call
|
29
|
+
data[program_name] = data.keys.reject { |k| k.include?(" ") } << "help"
|
30
|
+
data
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_data_with_commands(data, node, prefix: nil)
|
34
|
+
node.children.each do |name, subnode|
|
35
|
+
key = prefix.nil? ? name : "#{prefix} #{name}"
|
36
|
+
if subnode.command
|
37
|
+
arguments, options = command(subnode.command)
|
38
|
+
update_data_with_arguments(data, key, arguments)
|
39
|
+
update_data_with_options(data, key, options)
|
40
|
+
elsif subnode.children
|
41
|
+
data[key] = subnode.children.keys
|
42
|
+
update_data_with_commands(data, subnode, prefix: key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def cli_root_node
|
48
|
+
CLI.get({}).instance_variable_get(:@node)
|
49
|
+
end
|
50
|
+
|
51
|
+
def command(command)
|
52
|
+
args = command.arguments.map { |arg| argument_values(arg) }
|
53
|
+
options = command.options.to_h { |opt| ["--#{opt.name.to_s.gsub("_", "-")}", option_values(opt)] }
|
54
|
+
options["--help"] = []
|
55
|
+
[args, options]
|
56
|
+
end
|
57
|
+
|
58
|
+
def argument_values(argument)
|
59
|
+
return ["<directory>"] if argument.name.to_s.end_with?("dir")
|
60
|
+
|
61
|
+
argument.values
|
62
|
+
end
|
63
|
+
|
64
|
+
def option_values(option)
|
65
|
+
return ["<directory>"] if option.name.to_s.end_with?("dir")
|
66
|
+
return option.values if option.values
|
67
|
+
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_data_with_arguments(data, name, arguments)
|
72
|
+
values = arguments.shift || []
|
73
|
+
data[name] = values.dup
|
74
|
+
values.each { |v| update_data_with_arguments(data, "#{name}*#{v}", arguments) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_data_with_options(data, name, options)
|
78
|
+
data[name] ||= []
|
79
|
+
data[name].concat(options.keys)
|
80
|
+
options.each do |opt, values|
|
81
|
+
next if values.empty?
|
82
|
+
|
83
|
+
data["#{name}*#{opt}"] = values
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herve
|
4
|
+
module CLI
|
5
|
+
module ShellCommands
|
6
|
+
class Env < BaseCommand
|
7
|
+
desc "generate environment to configure your shell"
|
8
|
+
argument :shell, desc: "your shell", required: true, values: SUPPORTED_SHELLS
|
9
|
+
|
10
|
+
def call(shell:, **_args)
|
11
|
+
ruby = config.project_ruby
|
12
|
+
env = Config.env_for(ruby)
|
13
|
+
unset = env.select { |_k, v| v.nil? }.keys
|
14
|
+
set = env.compact
|
15
|
+
case shell
|
16
|
+
when "bash", "zsh"
|
17
|
+
env_bash(unset, set)
|
18
|
+
else
|
19
|
+
raise Error, "unsupported shell '#{shell}'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def env_bash(unset, set)
|
26
|
+
puts "unset #{unset.join(" ")}" unless unset.empty?
|
27
|
+
set.each do |var, val|
|
28
|
+
puts "export #{var}=#{val}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herve
|
4
|
+
module CLI
|
5
|
+
module ShellCommands
|
6
|
+
class Init < BaseCommand
|
7
|
+
desc "generate environment to configure your shell"
|
8
|
+
argument :shell, desc: "your shell", required: true, values: SUPPORTED_SHELLS
|
9
|
+
|
10
|
+
def call(shell:, **)
|
11
|
+
case shell
|
12
|
+
when "bash"
|
13
|
+
init_bash
|
14
|
+
when "zsh"
|
15
|
+
init_zsh
|
16
|
+
else
|
17
|
+
raise Error, "unsupported shell '#{shell}'"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def init_bash
|
24
|
+
puts <<~ENDOFTEXT
|
25
|
+
_herve_autoload_hook() {
|
26
|
+
eval "$(#{config.current_exe} shell env bash)"
|
27
|
+
}
|
28
|
+
_herve_autoload_hook
|
29
|
+
chpwd_hook() {
|
30
|
+
if [[ "$PWD" != "$_OLDPWD" ]]; then
|
31
|
+
_herve_autoload_hook
|
32
|
+
_OLDPWD="$PWD"
|
33
|
+
fi
|
34
|
+
}
|
35
|
+
_OLDPWD="$PWD"
|
36
|
+
PROMPT_COMMAND="_chpwd_hook${PROMPT_COMMAND:*; $PROMPT_COMMAND}"
|
37
|
+
ENDOFTEXT
|
38
|
+
end
|
39
|
+
|
40
|
+
def init_zsh
|
41
|
+
puts <<~ENDOFTEXT
|
42
|
+
autoload -U add-zsh-hook
|
43
|
+
_herve_autoload_hook() {
|
44
|
+
eval "$(#{config.current_exe} shell env zsh)"
|
45
|
+
}
|
46
|
+
add-zsh-hook chpwd _herve_autoload_hook
|
47
|
+
_herve_autoload_hook
|
48
|
+
ENDOFTEXT
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herve
|
4
|
+
module CLI
|
5
|
+
module ShellCommands
|
6
|
+
# Supported shells
|
7
|
+
# @return [Array<String>]
|
8
|
+
SUPPORTED_SHELLS = %w[bash zsh].freeze
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative "shell_commands/completion"
|
12
|
+
require_relative "shell_commands/env"
|
13
|
+
require_relative "shell_commands/init"
|
14
|
+
|
15
|
+
CLI.register "shell" do |cli|
|
16
|
+
cli.register "completion", ShellCommands::Completion
|
17
|
+
cli.register "env", ShellCommands::Env
|
18
|
+
cli.register "init", ShellCommands::Init
|
19
|
+
end
|
20
|
+
|
21
|
+
%w[env init].each do |cmd|
|
22
|
+
before("shell #{cmd}") do |args|
|
23
|
+
setup_config(args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/herve/cli.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "rainbow"
|
5
|
+
require "dry/cli"
|
6
|
+
|
7
|
+
module Herve
|
8
|
+
module CLI
|
9
|
+
extend Dry::CLI::Registry
|
10
|
+
|
11
|
+
class BaseCommand < Dry::CLI::Command
|
12
|
+
# @return [Config]
|
13
|
+
attr_reader :config
|
14
|
+
# @return [Logger]
|
15
|
+
attr_reader :logger
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def inherited(klass)
|
19
|
+
super
|
20
|
+
|
21
|
+
klass.option :ruby_dir, desc: "ruby directories to use"
|
22
|
+
klass.option :project_dir, desc: "project directory to use"
|
23
|
+
klass.option :verbose, desc: "Add verbosity", type: :flag, aliases: %w[-v]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def setup_config(options)
|
30
|
+
# Unfortunately, Rainbow checks STDOUT and STDERR
|
31
|
+
Rainbow.enabled = $stdout.tty? && $stderr.tty?
|
32
|
+
|
33
|
+
config = Config.from_env_and_options(ENV, options)
|
34
|
+
|
35
|
+
ruby_dir = options[:ruby_dir]
|
36
|
+
ruby_dirs = if ruby_dir.nil?
|
37
|
+
config.default_ruby_dirs
|
38
|
+
else
|
39
|
+
ruby_dir.split(":").map { |rd| config.root_dir.join(rd) }
|
40
|
+
end
|
41
|
+
config.ruby_dirs = ruby_dirs
|
42
|
+
@config = config
|
43
|
+
end
|
44
|
+
|
45
|
+
def setup_logger(options)
|
46
|
+
logger = Logger.new($stdout)
|
47
|
+
logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO
|
48
|
+
logger.formatter = proc do |severity, _datetime, _progname, msg|
|
49
|
+
colored_msg = case severity
|
50
|
+
when "FATAL", "ERROR"
|
51
|
+
Rainbow(msg).red
|
52
|
+
when "DEBUG"
|
53
|
+
Rainbow(msg).darkslategray
|
54
|
+
else
|
55
|
+
msg
|
56
|
+
end
|
57
|
+
"#{colored_msg}\n"
|
58
|
+
end
|
59
|
+
@logger = logger
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require_relative "cli/gem_command"
|
64
|
+
require_relative "cli/ruby_commands"
|
65
|
+
require_relative "cli/shell_commands"
|
66
|
+
end
|
67
|
+
end
|
data/lib/herve/config.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "xdg"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module Herve
|
8
|
+
# @private environment variables handle by herve
|
9
|
+
ENV_VARS = (%w[RUBY_ROOT RUBY_ENGINE RUBY_VERSION RUBYLIB RUBYOPT] +
|
10
|
+
ENV.keys.select { |v| v.start_with?("GEM_") || v.start_with?("BUNDLE") }).freeze
|
11
|
+
# Base URL to fetch releases
|
12
|
+
RELEASES_URL = "https://api.github.com"
|
13
|
+
|
14
|
+
class ConfigError < Error; end
|
15
|
+
|
16
|
+
class Config
|
17
|
+
DEFAULT_ROOT_DIR = "/"
|
18
|
+
DEFAULT_SEARCH_RUBIES = %w[$HOME/.rubies /opt/rubies /usr/local/rubies].freeze
|
19
|
+
|
20
|
+
attr_reader :root_dir, :project_dir, :current_exe, :gemfile, :cache, :release_base_url
|
21
|
+
attr_accessor :ruby_dirs
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def from_env_and_options(env, options)
|
25
|
+
root_dir = Pathname.new(env["RV_ROOT_DIR"] || DEFAULT_ROOT_DIR)
|
26
|
+
project_dir = if options.key?(:project_dir) && !options[:project_dir].nil?
|
27
|
+
Pathname.new(options[:project_dir])
|
28
|
+
else
|
29
|
+
find_project_dir(Pathname.pwd, root_dir)
|
30
|
+
end
|
31
|
+
bundle_gemfile = env.fetch("BUNDLE_GEMFILE", nil)
|
32
|
+
gemfile = bundle_gemfile ? Pathname.new(bundle_gemfile) : nil
|
33
|
+
env.fetch("HERVE_RELEASES_URL", RELEASES_URL)
|
34
|
+
new(root_dir, project_dir, gemfile)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Ruby,nil] ruby
|
38
|
+
# @return [Array(Array<String>, Hash(String => String)>)]
|
39
|
+
def env_for(ruby)
|
40
|
+
env = ENV_VARS.to_h { |var| [var, nil] }
|
41
|
+
paths = split_path(ENV["PATH"])
|
42
|
+
|
43
|
+
old_ruby_paths = %w[RUBY_ROOT GEM_ROOT GEM_HOME].map { |v| ENV[v] }
|
44
|
+
.compact
|
45
|
+
.map { |p| File.join(p, "bin") }
|
46
|
+
old_gem_paths = split_path(ENV["GEM_PATH"])
|
47
|
+
|
48
|
+
# Remove old Ruby and Gem paths from PATH
|
49
|
+
paths.delete_if { |p| old_ruby_paths.include?(p) || old_gem_paths.include?(p) }
|
50
|
+
set_env_for(ruby, env, paths) unless ruby.nil?
|
51
|
+
env["PATH"] = paths.join(":")
|
52
|
+
env
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def find_project_dir(cwd, root_dir)
|
58
|
+
project_dir = cwd
|
59
|
+
loop do
|
60
|
+
ruby_version = project_dir.join(".ruby-version")
|
61
|
+
return project_dir if ruby_version.exist?
|
62
|
+
return nil if project_dir == root_dir
|
63
|
+
|
64
|
+
parent = project_dir.parent
|
65
|
+
return nil if parent == project_dir
|
66
|
+
|
67
|
+
project_dir = parent
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def split_path(path)
|
72
|
+
(path || "").split(":")
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_env_for(ruby, env, paths)
|
76
|
+
gem_paths = []
|
77
|
+
paths.insert(0, ruby.bin_path.to_s)
|
78
|
+
env["RUBY_ROOT"] = ruby.path.to_s
|
79
|
+
ruby_request = ruby.version
|
80
|
+
env["RUBY_ENGINE"] = ruby_request.engine.name
|
81
|
+
env["RUBY_VERSION"] = ruby_request.version_number
|
82
|
+
if ruby.gem_home
|
83
|
+
gem_home = ruby.gem_home
|
84
|
+
gem_home_bin = gem_home.join("bin").to_s
|
85
|
+
paths.insert(0, gem_home_bin)
|
86
|
+
gem_paths.insert(0, gem_home_bin)
|
87
|
+
env["GEM_HOME"] = gem_home.to_s
|
88
|
+
end
|
89
|
+
if ruby.gem_root
|
90
|
+
gem_root = ruby.gem_root
|
91
|
+
gem_root_bin = gem_root.join("bin").to_s
|
92
|
+
paths.insert(0, gem_root_bin)
|
93
|
+
gem_paths.insert(0, gem_root_bin)
|
94
|
+
env["GEM_ROOT"] = gem_root.to_s
|
95
|
+
end
|
96
|
+
env["GEM_PATH"] = gem_paths.join(":")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(root_dir, project_dir, gemfile, release_base_url = RELEASES_URL)
|
101
|
+
@root_dir = root_dir
|
102
|
+
@project_dir = project_dir
|
103
|
+
@current_exe = Pathname.new($PROGRAM_NAME).realpath
|
104
|
+
@gemfile = gemfile
|
105
|
+
@release_base_url = release_base_url
|
106
|
+
@cache = Cache.new(Pathname.new(XDG::Cache.new.home).join("herve"))
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_ruby_dirs
|
110
|
+
DEFAULT_SEARCH_RUBIES.map do |dir|
|
111
|
+
path = Herve.expand_path(dir)
|
112
|
+
joinable_path = path.relative_path_from("/")
|
113
|
+
joined_path = root_dir.join(joinable_path)
|
114
|
+
if joined_path.split.last.to_s == ".rubies"
|
115
|
+
# Ensure ~/.rubies is in list, even if it does not exist yet
|
116
|
+
joined_path
|
117
|
+
else
|
118
|
+
begin
|
119
|
+
joined_path.realpath
|
120
|
+
rescue SystemCallError
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end.compact
|
125
|
+
end
|
126
|
+
|
127
|
+
def rubies
|
128
|
+
discover_rubies
|
129
|
+
end
|
130
|
+
|
131
|
+
def project_ruby
|
132
|
+
matching_ruby(ruby_request)
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Ruby::Request,nil]
|
136
|
+
def ruby_request
|
137
|
+
return nil if project_dir.nil?
|
138
|
+
|
139
|
+
begin
|
140
|
+
file = project_dir.join(".ruby-version")
|
141
|
+
Ruby::Request.parse(File.read(file))
|
142
|
+
rescue Error
|
143
|
+
nil
|
144
|
+
rescue SystemCallError => e
|
145
|
+
raise Error, e.message
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def matching_ruby(request)
|
150
|
+
return nil if request.nil?
|
151
|
+
|
152
|
+
rubies.reverse.find { |ruby| request.satisfied_by(ruby) }
|
153
|
+
end
|
154
|
+
|
155
|
+
def ruby_version
|
156
|
+
file = project_dir.join(".ruby-version")
|
157
|
+
raise ConfigError, "no .ruby-version file in #{project_dir}" unless file.exist?
|
158
|
+
|
159
|
+
file.read
|
160
|
+
end
|
161
|
+
|
162
|
+
def ruby_version=(version)
|
163
|
+
dir = project_dir || Dir.cwd
|
164
|
+
dir.join(".ruby-version").write("#{version}\n")
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def discover_rubies
|
170
|
+
paths = []
|
171
|
+
ruby_dirs.select(&:exist?).map do |dir|
|
172
|
+
dir.children.select do |child|
|
173
|
+
paths += child.children.select(&:directory?) if child.directory? && child.readable?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
paths.map do |path|
|
178
|
+
cached_ruby(path) || Ruby.from_directory(path)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def cached_ruby(path)
|
183
|
+
key = cache_key_from_path(path)
|
184
|
+
return nil if key.nil?
|
185
|
+
|
186
|
+
entry = cache.entry(:ruby, "interpreters", key)
|
187
|
+
begin
|
188
|
+
content = File.read(entry.path)
|
189
|
+
cached_ruby = JSON.parse(content)
|
190
|
+
rescue SystemCallError, JSON::ParserError
|
191
|
+
return nil
|
192
|
+
end
|
193
|
+
return cached_ruby if cached_ruby.valid?
|
194
|
+
|
195
|
+
entry.path.delete
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def cache_key_from_path(path)
|
200
|
+
bin = path.join("bin").join("ruby")
|
201
|
+
return nil unless bin.exist?
|
202
|
+
|
203
|
+
timestamp = Cache::Timestamp.path(bin)
|
204
|
+
Cache.digest("#{timestamp}#{bin}")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Herve
|
4
|
+
class Ruby
|
5
|
+
class Engine
|
6
|
+
# Known engines, in priority order
|
7
|
+
KINDS = %i[ruby jruby truffleruby mruby].freeze
|
8
|
+
|
9
|
+
attr_reader :kind
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
@kind = KINDS.find { |k| k.to_s == name } || :unknown
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
@name.dup
|
18
|
+
end
|
19
|
+
alias to_s name
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
priority(kind) <=> priority(other.kind)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
kind == other.kind
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def priority(kind)
|
32
|
+
return 100 if kind == :unknown
|
33
|
+
|
34
|
+
KINDS.index(kind)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|