lib/gdk/tool_versions_updater.rb (166 lines of code) (raw):
# frozen_string_literal: true
module GDK
class ToolVersionsUpdater
COMBINED_TOOL_VERSIONS_FILE = '.combined-tool-versions'
RUBY_PATCHES = {
'3.2.4' => 'https://gitlab.com/gitlab-org/gitlab-build-images/-/raw/d95e4efae87d5e3696f22d12a6c4e377a22f3c95/patches/ruby/3.2/thread-memory-allocations.patch',
'3.3.7' => 'https://gitlab.com/gitlab-org/gitlab-build-images/-/raw/e1be2ad5ff2a0bf0b27f86ef75b73824790b4b26/patches/ruby/3.3/thread-memory-allocations.patch',
'3.4.2' => 'https://gitlab.com/gitlab-org/gitlab-build-images/-/raw/d077c90c540ac99ae75c396b91dcfcb136281059/patches/ruby/3.4/thread-memory-allocations.patch'
}.freeze
def self.enabled_services
# Cache the results of enabled services,
@enabled_services ||= (GDK::Services.enabled + GDK::Services.legacy.select(&:enabled?)).map(&:name)
# but return a copy each time to avoid side-effects.
@enabled_services.dup
end
def run
return skip_message unless should_update?
tool_versions = collect_tool_versions
configure_env(tool_versions)
write_combined_file(tool_versions)
install_tools(tool_versions)
ensure
cleanup
end
private
def should_update?
GDK.config.mise.enabled? || !GDK.config.asdf.opt_out?
end
def skip_message
GDK::Output.info('Skipping tool versions update since neither mise nor asdf is enabled')
end
def collect_tool_versions
git_fetch_version_files
# Get all service names in the enabled list and include the required ones that are missing.
service_names = self.class.enabled_services
service_names.push('gitlab', 'gitlab-shell')
services = []
service_names.each do |name|
config_key = name.tr('-', '_')
repo_url = GDK.config.repositories[config_key]
services << [name, repo_url, config_key] if repo_url
end
GDK::Output.info("Found #{services.size} services with repositories")
threads = services.map do |name, repo_url, config_key|
Thread.new do
config = GDK.config[config_key]
version = get_version(config)
Thread.current[:tools] = fetch_service_tool_versions(name, repo_url, version)
end
end
threads << Thread.new do
Thread.current[:tools] = root_tool_versions
end
threads
.flat_map { |thread| thread.join[:tools] }
.select { |x| x }
.group_by(&:first)
.transform_values { |x| x.flat_map(&:last).uniq }
end
def root_tool_versions
path = GDK.root.join('.tool-versions')
parse_tool_versions(File.read(path))
end
def get_version(config)
return 'main' unless config
return config.__version if config.respond_to?(:__version) && config.__version
return config.default_branch if config.respond_to?(:default_branch) && config.default_branch
'main'
end
def git_fetch_version_files
branch = GDK.config.gitlab.default_branch
GDK::Shellout.new("git fetch origin #{branch}", chdir: GDK.config.gitlab.dir).execute
GDK::Shellout.new("git checkout origin/#{branch} -- '*_VERSION'", chdir: GDK.config.gitlab.dir).execute
end
def http_get(url)
uri = URI.parse(url)
response = Net::HTTP.get_response(uri)
return nil unless response.is_a?(Net::HTTPSuccess)
response.body
end
def parse_tool_versions(content)
content.each_line.flat_map do |line|
line = line.split('#', 2).first.strip
next if line.empty?
tool, *version_numbers = line.split
version_numbers.map { |version| [tool, version] }
end
end
def fetch_service_tool_versions(name, repo_url, version_or_branch)
path = repo_url.sub('.git', '')
url = "#{path}/-/raw/#{version_or_branch}/.tool-versions"
response = http_get(url)
if response.nil?
GDK::Output.debug("Failed to fetch .tool-versions for '#{name}' from #{repo_url}")
return nil
end
parse_tool_versions(response)
end
def write_combined_file(tool_versions)
combined_content = tool_versions.filter_map do |tool, versions|
"#{tool} #{versions.join(' ')}"
end.join("\n").concat("\n")
File.write(COMBINED_TOOL_VERSIONS_FILE, combined_content)
GDK::Output.debug("Combined tool versions content:\n#{combined_content}")
end
def configure_env(tool_versions)
rust_version = tool_versions['rust']&.first
if GDK.config.mise.enabled?
ENV['MISE_OVERRIDE_TOOL_VERSIONS_FILENAMES'] = COMBINED_TOOL_VERSIONS_FILE
ENV['MISE_RUST_VERSION'] = rust_version if rust_version
else
ENV['ASDF_DEFAULT_TOOL_VERSIONS_FILENAME'] = COMBINED_TOOL_VERSIONS_FILE
ENV['ASDF_RUST_VERSION'] = rust_version if rust_version
end
ENV['RUST_WITHOUT'] = 'rust-docs' if rust_version
end
def install_tools(tool_versions)
install_rust(tool_versions['rust'])
threads = []
threads << Thread.new { install_ruby(tool_versions['ruby']) }
threads << Thread.new { install_node(tool_versions['nodejs']) }
threads.each(&:join)
install_remaining_tools
GDK::Output.success('Successfully updated tool versions!')
rescue StandardError => e
GDK::Output.error("Failed to update tool versions: #{e.message}")
end
def install_rust(versions)
return if versions.nil? || versions.empty?
version = versions.first
run_install('rust', version)
end
def install_ruby(versions)
return if versions.nil? || versions.empty?
versions.each do |version|
ENV['MISC_RUBY_APPLY_PATCHES'] = RUBY_PATCHES[version] if RUBY_PATCHES[version]
run_install('ruby', version)
end
GDK::Shellout.new('asdf reshim ruby').execute unless GDK.config.asdf.opt_out?
end
def install_node(versions)
return if versions.nil? || versions.empty?
versions.each do |version|
run_install('nodejs', version)
end
end
def install_remaining_tools
run_install
end
def run_install(tool = nil, version = nil)
base_command = tool_version_manager == 'mise' ? 'mise install -y' : 'asdf install'
cmd = tool && version ? "#{base_command} #{tool} #{version}" : base_command
GDK::Shellout.new(cmd, chdir: GDK.root).execute
end
def tool_version_manager
GDK.config.mise.enabled? ? 'mise' : 'asdf'
end
def cleanup
FileUtils.rm_f(COMBINED_TOOL_VERSIONS_FILE)
if GDK.config.mise.enabled?
ENV.delete('MISE_OVERRIDE_TOOL_VERSIONS_FILENAMES')
ENV.delete('MISE_RUST_VERSION')
else
ENV.delete('ASDF_DEFAULT_TOOL_VERSIONS_FILENAME')
ENV.delete('ASDF_RUST_VERSION')
end
ENV.delete('RUST_WITHOUT')
end
end
end