From 4b5610fcac7ebedb4a860fe1e2b85b6b83fa3c97 Mon Sep 17 00:00:00 2001 From: Max Fierke Date: Mon, 12 Feb 2024 18:19:34 -0600 Subject: [PATCH] Implement partial support for multiple runtime managers --- .../bootstrappers/default_bootstrapper.cr | 6 +-- src/mstrap/configuration.cr | 22 +++++++-- src/mstrap/defs/runtime_config_def.cr | 15 ++++++ src/mstrap/defs/runtimes_config_def.cr | 5 +- src/mstrap/errors.cr | 6 +++ src/mstrap/runtime_manager.cr | 47 ++++++++++++++++--- src/mstrap/runtime_managers/asdf.cr | 4 ++ src/mstrap/runtime_managers/mise.cr | 4 ++ src/mstrap/step.cr | 14 ++++-- src/mstrap/steps/debug_step.cr | 10 +++- src/mstrap/steps/dependencies_step.cr | 2 +- src/mstrap/steps/init_step.cr | 2 +- src/mstrap/steps/runtimes_step.cr | 4 +- src/mstrap/steps/shell_step.cr | 2 +- src/mstrap/templates/Brewfile.cr | 4 +- src/mstrap/templates/Brewfile.ecr | 2 +- src/mstrap/templates/env.sh.ecr | 2 + src/mstrap/templates/env_sh.cr | 4 +- 18 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 src/mstrap/defs/runtime_config_def.cr diff --git a/src/mstrap/bootstrappers/default_bootstrapper.cr b/src/mstrap/bootstrappers/default_bootstrapper.cr index 62e0e80..099e2fc 100644 --- a/src/mstrap/bootstrappers/default_bootstrapper.cr +++ b/src/mstrap/bootstrappers/default_bootstrapper.cr @@ -9,7 +9,7 @@ module MStrap def bootstrap(project : Project) : Bool logd "Bootstrapping '#{project.name}' with runtime defaults." - runtime_impls(project).each do |runtime| + runtime_impls(project).each_value do |runtime| Dir.cd(project.path) do if runtime.matches? logd "Detected #{runtime.language_name}. Installing #{runtime.language_name}, project #{runtime.language_name} packages, and other relevant dependencies" @@ -23,9 +23,9 @@ module MStrap def runtime_impls(project) if project.runtimes.empty? - config.runtime_manager.runtimes + config.runtimes else - config.runtime_manager.runtimes.select do |runtime| + config.runtimes.select do |_, runtime| project.runtimes.includes?(runtime.language_name) end end diff --git a/src/mstrap/configuration.cr b/src/mstrap/configuration.cr index b5e276b..d7ce9ac 100644 --- a/src/mstrap/configuration.cr +++ b/src/mstrap/configuration.cr @@ -3,11 +3,13 @@ module MStrap include DSL @config_def : Defs::ConfigDef + @default_runtime_manager : RuntimeManager @loaded_profile_configs : Array(Defs::ProfileConfigDef) @loaded_profiles : Array(Defs::ProfileDef) @known_profile_configs : Array(Defs::ProfileConfigDef) @resolved_profile : Defs::ProfileDef - @runtime_manager : RuntimeManager + @runtime_managers : Array(RuntimeManager) + @runtimes : Hash(String, Runtime) @user : User DEFAULT_PROFILE_CONFIG_DEF = Defs::DefaultProfileConfigDef.new @@ -28,8 +30,18 @@ module MStrap # profiles with the default profiles. getter :resolved_profile - # Returns the runtime manager specified by the configuration - getter :runtime_manager + # Returns the default runtime manager specified by the configuration + getter :default_runtime_manager + + # Returns the runtime managers specified by the configuration + getter :runtime_managers + + # Returns the language runtimes with their resolved runtime manager + # + # Raises UnsupportedLanguageRuntimeManagerError if the configuration of a + # language runtime to a runtime manager is invalid + # Raises InvalidRuntimeManagerError if an invalid runtime manager is provided + getter :runtimes # Returns the mstrap user getter :user @@ -40,11 +52,13 @@ module MStrap ) @config_def = config @config_path = config_path + @default_runtime_manager = RuntimeManager.for(config.runtimes.default_manager) @loaded_profile_configs = [] of Defs::ProfileConfigDef @loaded_profiles = [] of Defs::ProfileDef @known_profile_configs = config.profiles + [DEFAULT_PROFILE_CONFIG_DEF] @resolved_profile = Defs::ProfileDef.new - @runtime_manager = RuntimeManager.for(config.runtimes.default_manager) + @runtime_managers = RuntimeManager.resolve_managers(config) + @runtimes = RuntimeManager.resolve_runtimes(config) @user = User.new(user: config.user) end diff --git a/src/mstrap/defs/runtime_config_def.cr b/src/mstrap/defs/runtime_config_def.cr new file mode 100644 index 0000000..c390302 --- /dev/null +++ b/src/mstrap/defs/runtime_config_def.cr @@ -0,0 +1,15 @@ +module MStrap + module Defs + class RuntimeConfigDef + include HCL::Serializable + + @[HCL::Label] + property name : String + + @[HCL::Attribute] + property manager : String? = nil + + def_equals_and_hash @name, @manager + end + end +end diff --git a/src/mstrap/defs/runtimes_config_def.cr b/src/mstrap/defs/runtimes_config_def.cr index 0df0c6e..6be0a2b 100644 --- a/src/mstrap/defs/runtimes_config_def.cr +++ b/src/mstrap/defs/runtimes_config_def.cr @@ -6,7 +6,10 @@ module MStrap @[HCL::Attribute] property default_manager = "asdf" - def_equals_and_hash @default_manager + @[HCL::Block(key: "runtime")] + property runtimes = [] of ::MStrap::Defs::RuntimeConfigDef + + def_equals_and_hash @default_manager, @runtimes def initialize end diff --git a/src/mstrap/errors.cr b/src/mstrap/errors.cr index 97c463a..e20493c 100644 --- a/src/mstrap/errors.cr +++ b/src/mstrap/errors.cr @@ -38,6 +38,12 @@ module MStrap end end + class UnsupportedLanguageRuntimeManagerError < MStrapError + def initialize(manager_name, language_name) + super("#{manager_name} does not support the language provided: #{language_name}") + end + end + # Exception class to indicate a failure involving language runtime setup class RuntimeSetupError < MStrapError def initialize(language_name, message) diff --git a/src/mstrap/runtime_manager.cr b/src/mstrap/runtime_manager.cr index 2a3dba4..5f1a9ee 100644 --- a/src/mstrap/runtime_manager.cr +++ b/src/mstrap/runtime_manager.cr @@ -2,6 +2,8 @@ module MStrap abstract class RuntimeManager include DSL + @runtimes : Array(Runtime)? + def name : String {{ @type.name.stringify.split("::").last.downcase }} end @@ -28,6 +30,7 @@ module MStrap abstract def set_version(language_name : String, version : String?) : Bool abstract def set_global_version(language_name : String, version : String) : Bool abstract def shell_activation(shell_name : String) : String + abstract def supported_languages : Array(String) macro finished # :nodoc: @@ -40,12 +43,44 @@ module MStrap end # :nodoc: - def runtimes - @runtimes ||= [ - {% for subclass in Runtime.subclasses %} - {{ subclass.name }}.new(self), - {% end %} - ] + def self.resolve_managers(config_def : Defs::ConfigDef) : Array(RuntimeManager) + default_runtime_manager = self.for(config_def.runtimes.default_manager) + managers = [default_runtime_manager] + + config_def.runtimes.runtimes.map(&.manager).uniq!.each do |manager_name| + next if !manager_name + managers << RuntimeManager.for(manager_name) + end + + managers + end + + # :nodoc: + def self.resolve_runtimes(config_def : Defs::ConfigDef) : Hash(String, Runtime) + impls = Hash(String, Runtime).new + default_manager = {{ @type }}.all[config_def.runtimes.default_manager] + + {% for subclass, index in Runtime.subclasses %} + {% language_name = subclass.name.stringify.split("::").last.downcase %} + + %runtime_def{index} = config_def.runtimes.runtimes.find { |r| r.name == {{ language_name }} } + + if %runtime_def{index} && (runtime_manager_name = %runtime_def{index}.manager) + runtime_manager = self.for(runtime_manager_name) + + if !runtime_manager.supported_languages.includes?({{ language_name }}) + raise UnsupportedLanguageRuntimeManagerError.new(runtime_manager.name, {{ language_name }}) + end + + impls[{{language_name}}] = {{ subclass.name }}.new(runtime_manager) + elsif default_manager.supported_languages.includes?({{ language_name }}) + impls[{{language_name}}] = {{ subclass.name }}.new(default_manager) + else + raise UnsupportedLanguageRuntimeManagerError.new(default_manager.name, {{ language_name }}) + end + {% end %} + + impls end end end diff --git a/src/mstrap/runtime_managers/asdf.cr b/src/mstrap/runtime_managers/asdf.cr index 1999e9d..5099807 100644 --- a/src/mstrap/runtime_managers/asdf.cr +++ b/src/mstrap/runtime_managers/asdf.cr @@ -92,6 +92,10 @@ module MStrap SHELL end + def supported_languages : Array(String) + %w(crystal go node php python ruby rust) + end + private def version_env_var(language_name) : String if asdf_plugin_name = plugin_name(language_name) "ASDF_#{asdf_plugin_name.upcase}_VERSION" diff --git a/src/mstrap/runtime_managers/mise.cr b/src/mstrap/runtime_managers/mise.cr index e979303..954c690 100644 --- a/src/mstrap/runtime_managers/mise.cr +++ b/src/mstrap/runtime_managers/mise.cr @@ -77,6 +77,10 @@ module MStrap fi SHELL end + + def supported_languages : Array(String) + %w(crystal go node php python ruby rust) + end end end end diff --git a/src/mstrap/step.cr b/src/mstrap/step.cr index 753c15c..f091f9e 100644 --- a/src/mstrap/step.cr +++ b/src/mstrap/step.cr @@ -6,7 +6,8 @@ module MStrap @docker : Docker? = nil # BUG?: Why aren't these inferred correctly? @profile : Defs::ProfileDef - @runtime_manager : RuntimeManager + @runtime_managers : Array(RuntimeManager) + @runtimes : Hash(String, Runtime) @user : User # Extra arguments passed to the step not processed by the main CLI @@ -21,8 +22,11 @@ module MStrap # Resolved profile for mstrap getter :profile - # Language runtime manager for mstrap - getter :runtime_manager + # Language runtime managers in use + getter :runtime_managers + + # Language runtimes + getter :runtimes # User configured for mstrap getter :user @@ -33,8 +37,10 @@ module MStrap @args = args @config = config @options = cli_options + @profile = config.resolved_profile - @runtime_manager = config.runtime_manager + @runtime_managers = config.runtime_managers + @runtimes = config.runtimes @user = config.user end diff --git a/src/mstrap/steps/debug_step.cr b/src/mstrap/steps/debug_step.cr index 91fc95f..7a56dcd 100644 --- a/src/mstrap/steps/debug_step.cr +++ b/src/mstrap/steps/debug_step.cr @@ -20,7 +20,15 @@ module MStrap puts "Loaded Config:" puts " #{options.config_path}" puts "Default runtime manager:" - puts " #{runtime_manager.name}" + puts " #{config.default_runtime_manager.name}" + puts "Resolved runtime managers:" + config.runtime_managers.each do |runtime_manager| + puts " #{runtime_manager.name}" + end + puts "Resolved runtimes:" + config.runtimes.each do |runtime_name, runtime| + puts " #{runtime_name} (via #{runtime.runtime_manager.name})" + end puts "Known Profiles:" config.known_profile_configs.each do |profile| puts " #{profile.name}" diff --git a/src/mstrap/steps/dependencies_step.cr b/src/mstrap/steps/dependencies_step.cr index 7993345..47def93 100644 --- a/src/mstrap/steps/dependencies_step.cr +++ b/src/mstrap/steps/dependencies_step.cr @@ -16,7 +16,7 @@ module MStrap end def bootstrap - install_mise if runtime_manager.name == "mise" + install_mise if config.default_runtime_manager.name == "mise" set_strap_env! strap_sh load_profile! diff --git a/src/mstrap/steps/init_step.cr b/src/mstrap/steps/init_step.cr index afa697f..c5310a2 100644 --- a/src/mstrap/steps/init_step.cr +++ b/src/mstrap/steps/init_step.cr @@ -40,7 +40,7 @@ module MStrap if !File.exists?(Paths::BREWFILE) || force? logw "No Brewfile found or update requested with --force" log "--> Copying default Brewfile to #{Paths::BREWFILE}: " - brewfile_contents = Templates::Brewfile.new(runtime_manager).to_s + brewfile_contents = Templates::Brewfile.new(config.default_runtime_manager).to_s File.write(Paths::BREWFILE, brewfile_contents) success "OK" end diff --git a/src/mstrap/steps/runtimes_step.cr b/src/mstrap/steps/runtimes_step.cr index a8f53c4..b147d3c 100644 --- a/src/mstrap/steps/runtimes_step.cr +++ b/src/mstrap/steps/runtimes_step.cr @@ -10,7 +10,7 @@ module MStrap end def bootstrap - runtime_manager.runtimes.each do |runtime| + runtimes.each_value do |runtime| if runtime.has_runtime_plugin? && runtime.has_versions? logn "==> Setting global #{runtime.language_name} settings" set_default_to_latest(runtime) @@ -49,7 +49,7 @@ module MStrap return unless latest_version log "--> Setting default #{runtime.language_name} version to #{latest_version}: " - unless runtime_manager.set_global_version(runtime.language_name, latest_version) + unless runtime.runtime_manager.set_global_version(runtime.language_name, latest_version) logc "Could not set global #{runtime.language_name} version to #{latest_version}" end success "OK" diff --git a/src/mstrap/steps/shell_step.cr b/src/mstrap/steps/shell_step.cr index 5d4da89..76940f8 100644 --- a/src/mstrap/steps/shell_step.cr +++ b/src/mstrap/steps/shell_step.cr @@ -47,7 +47,7 @@ module MStrap def bootstrap Dir.mkdir_p(MStrap::Paths::RC_DIR) - contents = Templates::EnvSh.new(shell_name, runtime_manager).to_s + contents = Templates::EnvSh.new(shell_name, runtime_managers).to_s File.write(env_sh_path, contents, perm: 0o600) exit_if_shell_changed! diff --git a/src/mstrap/templates/Brewfile.cr b/src/mstrap/templates/Brewfile.cr index 4e21e67..e67f245 100644 --- a/src/mstrap/templates/Brewfile.cr +++ b/src/mstrap/templates/Brewfile.cr @@ -4,9 +4,9 @@ module MStrap class Brewfile ECR.def_to_s "#{__DIR__}/Brewfile.ecr" - getter :runtime_manager + getter :default_runtime_manager - def initialize(@runtime_manager : RuntimeManager) + def initialize(@default_runtime_manager : RuntimeManager) end end end diff --git a/src/mstrap/templates/Brewfile.ecr b/src/mstrap/templates/Brewfile.ecr index bd52e8d..d6d8037 100644 --- a/src/mstrap/templates/Brewfile.ecr +++ b/src/mstrap/templates/Brewfile.ecr @@ -9,7 +9,7 @@ brew 'pkg-config' brew 'zlib' # Language runtime managers -<% if runtime_manager.name == "asdf" %> +<% if default_runtime_manager.name == "asdf" %> brew 'asdf' <% end %> diff --git a/src/mstrap/templates/env.sh.ecr b/src/mstrap/templates/env.sh.ecr index 2235c06..d890396 100644 --- a/src/mstrap/templates/env.sh.ecr +++ b/src/mstrap/templates/env.sh.ecr @@ -10,4 +10,6 @@ export MSTRAP_RC_DIR="<%= MStrap::Paths::RC_DIR %>" test -d <%= MStrap::Paths::HOMEBREW_PREFIX %> && eval $(<%= MStrap::Paths::HOMEBREW_PREFIX %>/bin/brew shellenv) <% end -%> +<%- runtime_managers.each do |runtime_manager| %> <%= runtime_manager.shell_activation(shell_name) %> +<% end -%> diff --git a/src/mstrap/templates/env_sh.cr b/src/mstrap/templates/env_sh.cr index 158b16e..f74fcf6 100644 --- a/src/mstrap/templates/env_sh.cr +++ b/src/mstrap/templates/env_sh.cr @@ -5,9 +5,9 @@ module MStrap ECR.def_to_s "#{__DIR__}/env.sh.ecr" getter :shell_name - getter :runtime_manager + getter :runtime_managers - def initialize(@shell_name : String, @runtime_manager : RuntimeManager) + def initialize(@shell_name : String, @runtime_managers : Array(RuntimeManager)) end def needs_homebrew_shellenv?