Skip to content

Commit

Permalink
Implement a rustup RuntimeManager
Browse files Browse the repository at this point in the history
  • Loading branch information
maxfierke committed Feb 13, 2024
1 parent 4b5610f commit 82df346
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 5 deletions.
119 changes: 116 additions & 3 deletions spec/mstrap/configuration_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,12 @@ Spectator.describe MStrap::Configuration do
end
end

describe "#runtime_manager" do
describe "#default_runtime_manager" do
context "for v1.0 configs" do
subject { MStrap::Configuration.new(config_def_v1_0) }

it "defaults to asdf" do
expect(subject.runtime_manager).to be_a(MStrap::RuntimeManagers::ASDF)
expect(subject.default_runtime_manager).to be_a(MStrap::RuntimeManagers::ASDF)
end
end

Expand Down Expand Up @@ -350,7 +350,120 @@ Spectator.describe MStrap::Configuration do
subject { MStrap::Configuration.new(config_def_v1_1) }

it "can be set through runtimes.default_manager" do
expect(subject.runtime_manager).to be_a(MStrap::RuntimeManagers::Mise)
expect(subject.default_runtime_manager).to be_a(MStrap::RuntimeManagers::Mise)
end
end
end

describe "#runtime_managers" do
context "for v1.0 configs" do
subject { MStrap::Configuration.new(config_def_v1_0) }

it "defaults to [asdf]" do
expect(subject.runtime_managers).to eq([MStrap::RuntimeManager.for("asdf")])
end
end

context "for v1.1 configs" do
let(config_def_v1_1) do
MStrap::Defs::ConfigDef.from_hcl(<<-HCL)
version = "1.1"
runtimes {
default_manager = "mise"
runtime "rust" {
manager = "rustup"
}
}
user {
name = "Reginald Testington"
email = "[email protected]"
}
profile "personal" {
url = "ssh://[email protected]/reggiemctest/mstrap-personal.git"
}
HCL
end

subject { MStrap::Configuration.new(config_def_v1_1) }

it "it derived through runtimes.default_manager and runtimes.runtime[*].manager" do
expect(subject.runtime_managers).to eq([
MStrap::RuntimeManager.for("mise"),
MStrap::RuntimeManager.for("rustup"),
])
end
end
end

describe "#runtimes" do
context "for v1.0 configs" do
subject { MStrap::Configuration.new(config_def_v1_0) }

it "defaults all to ASDF-provided runtimes" do
%w(crystal go node php python ruby rust).each do |language_name|
expect(subject.runtimes[language_name].runtime_manager).to be_a(MStrap::RuntimeManagers::ASDF)
end
end

it "returns the expected language runtimes" do
expect(subject.runtimes["crystal"]).to be_a(MStrap::Runtimes::Crystal)
expect(subject.runtimes["go"]).to be_a(MStrap::Runtimes::Go)
expect(subject.runtimes["node"]).to be_a(MStrap::Runtimes::Node)
expect(subject.runtimes["php"]).to be_a(MStrap::Runtimes::Php)
expect(subject.runtimes["python"]).to be_a(MStrap::Runtimes::Python)
expect(subject.runtimes["ruby"]).to be_a(MStrap::Runtimes::Ruby)
expect(subject.runtimes["rust"]).to be_a(MStrap::Runtimes::Rust)
end
end

context "for v1.1 configs" do
let(config_def_v1_1) do
MStrap::Defs::ConfigDef.from_hcl(<<-HCL)
version = "1.1"
runtimes {
default_manager = "mise"
runtime "rust" {
manager = "rustup"
}
}
user {
name = "Reginald Testington"
email = "[email protected]"
}
profile "personal" {
url = "ssh://[email protected]/reggiemctest/mstrap-personal.git"
}
HCL
end

subject { MStrap::Configuration.new(config_def_v1_1) }

it "allows overriding language manager for specific languages" do
%w(crystal go node php python ruby).each do |language_name|
expect(subject.runtimes[language_name].runtime_manager).to be_a(MStrap::RuntimeManagers::Mise)
end

expect(subject.runtimes["rust"].runtime_manager).to be_a(MStrap::RuntimeManagers::Rustup)
end

it "returns the expected language runtimes" do
expect(subject.runtimes["crystal"]).to be_a(MStrap::Runtimes::Crystal)
expect(subject.runtimes["go"]).to be_a(MStrap::Runtimes::Go)
expect(subject.runtimes["node"]).to be_a(MStrap::Runtimes::Node)
expect(subject.runtimes["php"]).to be_a(MStrap::Runtimes::Php)
expect(subject.runtimes["python"]).to be_a(MStrap::Runtimes::Python)
expect(subject.runtimes["ruby"]).to be_a(MStrap::Runtimes::Ruby)
expect(subject.runtimes["rust"]).to be_a(MStrap::Runtimes::Rust)
end
end
end
Expand Down
83 changes: 83 additions & 0 deletions src/mstrap/runtime_managers/rustup.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module MStrap
module RuntimeManagers
class Rustup < RuntimeManager
def current_version(language_name : String) : String?
current_toolchain = `rustup show active-toolchain`.chomp
extract_rust_version_from_toolchain(current_toolchain)
end

# Execute a command using a specific language runtime version
def runtime_exec(language_name : String, command : String, args : Array(String)? = nil, runtime_version : String? = nil)
exec_args = [] of String
exec_args << runtime_version if runtime_version

cmd_args = ["run"] + exec_args + ["--", command]
cmd_args += args if args

if command && (!args || args.empty?)
cmd "rustup #{cmd_args.join(' ')}", quiet: true
else
cmd "rustup", cmd_args, quiet: true
end
end

# Returns whether the mise plugin has been installed for a language runtime
# or not
def has_plugin?(language_name : String) : Bool
language_name == "rust"
end

def install_plugin(language_name : String) : Bool
language_name == "rust"
end

def install_version(language_name : String, version : String) : Bool
cmd("rustup toolchain install #{version}", quiet: true)
end

# Returns a list of the versions of the language runtime installed
# by mise.
def installed_versions(language_name : String) : Array(String)
rust_versions_list = `rustup toolchain list`.chomp.split("\n").map do |version|
extract_rust_version_from_toolchain(version)
end

rust_versions_list
end

# Rustup doesn't support querying this, but "stable" is _probably_ safe
def latest_version(language_name : String) : String
"stable"
end

# Rust is the only language managed by rustup, so this is always "rust"!
def plugin_name(language_name : String) : String?
"rust"
end

def set_version(language_name : String, version : String?) : Bool
cmd "rustup override set #{version}", quiet: true
end

def set_global_version(language_name, version : String) : Bool
cmd "rustup default #{version}", quiet: true
end

def shell_activation(shell_name : String) : String
<<-SHELL
# Activate rustup for Rust compiler version management
export PATH="$HOME/.cargo/bin:$PATH"
SHELL
end

def supported_languages : Array(String)
%w(rust)
end

private def extract_rust_version_from_toolchain(rustup_toolchain : String)
version, _, _ = rustup_toolchain.partition('-')
version
end
end
end
end
17 changes: 16 additions & 1 deletion src/mstrap/steps/dependencies_step.cr
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module MStrap
end

def bootstrap
install_mise if config.default_runtime_manager.name == "mise"
install_mise if runtime_managers.any? { |rm| rm.name == "mise" }
install_rustup if runtime_managers.any? { |rm| rm.name == "rustup" }
set_strap_env!
strap_sh
load_profile!
Expand Down Expand Up @@ -68,6 +69,20 @@ module MStrap
end
end

def install_rustup
rustup_installer = RustupInstaller.new

log "==> Checking for rustup: "
if rustup_installer.installed? && !options.force?
success "OK"
else
logn "Not installed".colorize(:yellow)
log "--> Installing rustup for language runtime version management: "
rustup_installer.install!
success "OK"
end
end

private def load_profile!
log "--> Reloading profile: "
config.reload!
Expand Down
2 changes: 1 addition & 1 deletion src/mstrap/supports/ca_cert_installer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module MStrap
FileUtils.mkdir_p(Paths::RC_DIR, 0o755)
Dir.cd(Paths::RC_DIR) do
unless cmd("curl -L --silent --remote-name --time-cond cacert.pem https://curl.se/ca/cacert.pem")
logc "There was an error fetching the cURL CA Cert bundle, which is needed to verify HTTPS certificates. mstrap cannot continue."
logc "There was an error fetching the cURL CA Cert bundle, which is needed to verify HTTPS certificates. mstrap cannot continue. Please ensure that `curl` is installed."
end
File.chmod(Paths::CA_CERT_BUNDLE, 0o600)
end
Expand Down
48 changes: 48 additions & 0 deletions src/mstrap/supports/rustup_installer.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module MStrap
# Manages the install of rustup for managing Rust
#
# NOTE: See `MStrap::RuntimeManagers::Rustup` for how rustup is integrated
class RustupInstaller
include DSL

# :nodoc:
RUSTUP_INIT_SH_PATH = File.join(MStrap::Paths::RC_DIR, "vendor", "rustup-init.sh")

# :nodoc:
RUSTUP_INIT_SH_URL = "https://sh.rustup.rs"

# :nodoc:
RUSTUP_BIN_DIR_PATH = File.join(ENV["HOME"], ".cargo", "bin")

def install!
fetch_installer!

install_args = [RUSTUP_INIT_SH_PATH, "--no-modify-path"]

if MStrap.debug?
install_args << "--verbose"
else
install_args << "--quiet"
install_args << "-y"
end

unless cmd "sh", install_args
logc "Failed to install rustup"
end

# "Activate" it
path = ENV["PATH"]
ENV["PATH"] = "#{RUSTUP_BIN_DIR_PATH}:#{path}"
end

def installed?
`command -v rustup` && $?.success?
end

private def fetch_installer!
HTTP::Client.get(RUSTUP_INIT_SH_URL, tls: MStrap.tls_client) do |response|
File.write(RUSTUP_INIT_SH_PATH, response.body_io.gets_to_end)
end
end
end
end

0 comments on commit 82df346

Please sign in to comment.