diff options
| author | William Boman <william@redwill.se> | 2023-04-16 21:02:03 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-16 21:02:03 +0200 |
| commit | 27b7a132d10bceb17d12145d4c34746e0f08198f (patch) | |
| tree | 355de9d053ab49c7f04f1d5bfc144068f1d133ac /lua | |
| parent | fix(checkhealth): use non-deprecated versions if possible (#1219) (diff) | |
| download | mason-27b7a132d10bceb17d12145d4c34746e0f08198f.tar mason-27b7a132d10bceb17d12145d4c34746e0f08198f.tar.gz mason-27b7a132d10bceb17d12145d4c34746e0f08198f.tar.bz2 mason-27b7a132d10bceb17d12145d4c34746e0f08198f.tar.lz mason-27b7a132d10bceb17d12145d4c34746e0f08198f.tar.xz mason-27b7a132d10bceb17d12145d4c34746e0f08198f.tar.zst mason-27b7a132d10bceb17d12145d4c34746e0f08198f.zip | |
refactor(health): split up checks (#1221)
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/mason-core/managers/github/client.lua | 2 | ||||
| -rw-r--r-- | lua/mason/health.lua | 458 |
2 files changed, 205 insertions, 255 deletions
diff --git a/lua/mason-core/managers/github/client.lua b/lua/mason-core/managers/github/client.lua index 06130e36..e8301f72 100644 --- a/lua/mason-core/managers/github/client.lua +++ b/lua/mason-core/managers/github/client.lua @@ -76,7 +76,7 @@ end ---@alias GitHubRateLimitResponse {resources: { core: GitHubRateLimit }} ---@async ---@return Result @of GitHubRateLimitResponse +---@return Result # Result<GitHubRateLimitResponse> function M.fetch_rate_limit() return gh_api_call "rate_limit" end diff --git a/lua/mason/health.lua b/lua/mason/health.lua index 93998443..2ffe81aa 100644 --- a/lua/mason/health.lua +++ b/lua/mason/health.lua @@ -1,176 +1,180 @@ local health = vim.health or require "health" +local Result = require "mason-core.result" local _ = require "mason-core.functional" local a = require "mason-core.async" local async_uv = require "mason-core.async.uv" +local control = require "mason-core.async.control" local github_client = require "mason-core.managers.github.client" local platform = require "mason-core.platform" local registry_sources = require "mason-registry.sources" local spawn = require "mason-core.spawn" +local Semaphore = control.Semaphore + local M = {} --- "report_" prefix has been deprecated, use the recommended replacements if they exist. -local start = health.start or health.report_start -local ok = health.ok or health.report_ok -local warn = health.warn or health.report_warn -local error = health.error or health.report_error +local report_start = _.scheduler_wrap(health.start or health.report_start) +local report_ok = _.scheduler_wrap(health.ok or health.report_ok) +local report_warn = _.scheduler_wrap(health.warn or health.report_warn) +local report_error = _.scheduler_wrap(health.error or health.report_error) + +local sem = Semaphore.new(5) + +---@async +---@param opts {cmd:string, args:string[], name: string, use_stderr: boolean?, version_check: (fun(version: string): string?), relaxed: boolean?} +local function check(opts) + local get_first_non_empty_line = _.compose(_.head, _.filter(_.complement(_.matches "^%s*$")), _.split "\n") + + local permit = sem:acquire() + + Result.try(function(try) + local result = try(spawn[opts.cmd] { + opts.args, + on_spawn = function(_, stdio) + local stdin = stdio[1] + -- some processes (`sh` for example) will endlessly read from stdin, so we close it immediately + if not stdin:is_closing() then + stdin:close() + end + end, + }) ----@alias HealthCheckResult ----| '"success"' ----| '"version-mismatch"' ----| '"parse-error"' ----| '"not-available"' + ---@type string? + local version = get_first_non_empty_line(opts.use_stderr and result.stderr or result.stdout) ----@class HealthCheck ----@field public result HealthCheckResult ----@field public version string? ----@field public relaxed boolean? ----@field public reason string? ----@field public name string -local HealthCheck = {} -HealthCheck.__index = HealthCheck + if opts.version_check then + local ok, version_mismatch = pcall(opts.version_check, version) + if ok and version_mismatch then + local report = opts.relaxed and report_warn or report_error + report(("%s: unsupported version `%s`"):format(opts.name, version), { version_mismatch }) + return + elseif not ok then + local report = opts.relaxed and report_warn or report_error + report(("%s: failed to parse version"):format(opts.name), { ("Error: %s"):format(version_mismatch) }) + return + end + end -function HealthCheck.new(props) - local self = setmetatable(props, HealthCheck) - return self + report_ok(("%s: `%s`"):format(opts.name, version or "Ok")) + end):on_failure(function(err) + local report = opts.relaxed and report_warn or report_error + report(("%s: not available"):format(opts.name), { tostring(err) }) + end) + permit:forget() end -function HealthCheck:get_version() - if self.result == "success" and not self.version or self.version == "" then - -- Some checks (bourne shell for instance) don't produce any output, so we default to just "Ok" - return "Ok" +local function check_registries() + report_start "mason.nvim [Registries]" + for source in registry_sources.iter { include_uninstalled = true } do + if source:is_installed() then + report_ok(("Registry `%s` is installed."):format(source:get_display_name())) + else + report_error( + ("Registry `%s` is not installed."):format(source:get_display_name()), + { "Run :MasonUpdate to install." } + ) + end end - return self.version end -function HealthCheck:get_health_report_level() - if health.start then - return ({ - ["success"] = "ok", - ["parse-error"] = "warn", - ["version-mismatch"] = self.relaxed and "warn" or "error", - ["not-available"] = self.relaxed and "warn" or "error", - })[self.result] - else - return ({ - ["success"] = "report_ok", - ["parse-error"] = "report_warn", - ["version-mismatch"] = self.relaxed and "report_warn" or "report_error", - ["not-available"] = self.relaxed and "report_warn" or "report_error", - })[self.result] - end +---@async +local function check_github() + report_start "mason.nvim [GitHub]" + github_client + .fetch_rate_limit() + :on_success( + ---@param rate_limit GitHubRateLimitResponse + function(rate_limit) + a.scheduler() + local remaining = rate_limit.resources.core.remaining + local used = rate_limit.resources.core.used + local limit = rate_limit.resources.core.limit + local reset = rate_limit.resources.core.reset + local diagnostics = ("Used: %d. Remaining: %d. Limit: %d. Reset: %s."):format( + used, + remaining, + limit, + vim.fn.strftime("%c", reset) + ) + if remaining <= 0 then + report_error(("GitHub API rate limit exceeded. %s"):format(diagnostics)) + else + local NON_AUTH_LIMIT = 60 + if limit > NON_AUTH_LIMIT then + report_ok(("GitHub API rate limit. %s"):format(diagnostics)) + else + report_ok( + ("GitHub API rate limit. %s\nInstall and authenticate via gh-cli to increase rate limit."):format( + diagnostics + ) + ) + end + end + end + ) + :on_failure(function() + report_warn "Failed to check GitHub API rate limit status." + end) end -function HealthCheck:__tostring() - if self.result == "success" then - return ("**%s**: `%s`"):format(self.name, self:get_version()) - elseif self.result == "version-mismatch" then - return ("**%s**: unsupported version `%s`. %s"):format(self.name, self:get_version(), self.reason) - elseif self.result == "parse-error" then - return ("**%s**: failed to parse version"):format(self.name) - elseif self.result == "not-available" then - return ("**%s**: not available"):format(self.name) +local function check_neovim() + if vim.fn.has "nvim-0.7.0" == 1 then + report_ok "neovim version >= 0.7.0" + else + report_error("neovim version < 0.7.0", { "Upgrade Neovim." }) end end ----@param callback fun(result: HealthCheck) -local function mk_healthcheck(callback) - ---@param opts {cmd:string, args:string[], name: string, use_stderr: boolean?, version_check: fun(version: string): string?, relaxed: boolean?} - return function(opts) - local parse_version = _.compose( - _.head, - _.filter(_.complement(_.matches "^%s*$")), - _.split "\n", - _.if_else(_.always(opts.use_stderr), _.prop "stderr", _.prop "stdout") - ) +---@async +local function check_core_utils() + report_start "mason.nvim [Core utils]" - ---@async - return function() - local healthcheck_result = spawn - [opts.cmd]({ - opts.args, - on_spawn = a.scope(function(_, stdio) - local stdin = stdio[1] - -- some processes (`sh` for example) will endlessly read from stdin, so we close it immediately - async_uv.shutdown(stdin) - async_uv.close(stdin) - end), - }) - :map(parse_version) - :map(function(version) - if opts.version_check then - local ok, version_check = pcall(opts.version_check, version) - if ok and version_check then - return HealthCheck.new { - result = "version-mismatch", - reason = version_check, - version = version, - name = opts.name, - relaxed = opts.relaxed, - } - elseif not ok then - return HealthCheck.new { - result = "parse-error", - version = "N/A", - name = opts.name, - relaxed = opts.relaxed, - } - end - end + check { name = "unzip", cmd = "unzip", args = { "-v" }, relaxed = true } - return HealthCheck.new { - result = "success", - version = version, - name = opts.name, - relaxed = opts.relaxed, - } - end) - :get_or_else(HealthCheck.new { - result = "not-available", - version = nil, - name = opts.name, - relaxed = opts.relaxed, - }) + -- wget is used interchangeably with curl, but with lower priority, so we mark wget as relaxed + check { cmd = "wget", args = { "--version" }, name = "wget", relaxed = true } + check { cmd = "curl", args = { "--version" }, name = "curl" } + check { + cmd = "gzip", + args = { "--version" }, + name = "gzip", + use_stderr = platform.is.mac, -- Apple gzip prints version string to stderr + relaxed = platform.is.win, + } + check { cmd = "tar", args = { "--version" }, name = "tar" } + check { + cmd = "pwsh", + args = { + "-NoProfile", + "-Command", + [[$PSVersionTable.PSVersion, $PSVersionTable.OS, $PSVersionTable.Platform -join " "]], + }, + name = "pwsh", + relaxed = not platform.is.win, + } - callback(healthcheck_result) - end + if platform.is.unix then + check { cmd = "bash", args = { "--version" }, name = "bash" } + check { cmd = "sh", name = "sh" } end -end -function M.check() - start "mason.nvim report" - if vim.fn.has "nvim-0.7.0" == 1 then - ok "neovim version >= 0.7.0" - else - error "neovim version < 0.7.0" + if platform.is.win then + check { cmd = "7z", args = { "--help" }, name = "7z", relaxed = true } end +end - local completed = 0 - - local check = mk_healthcheck(vim.schedule_wrap( - ---@param healthcheck HealthCheck - function(healthcheck) - completed = completed + 1 - health[healthcheck:get_health_report_level()](tostring(healthcheck)) - end - )) - - for source in registry_sources.iter { include_uninstalled = true } do - if source:is_installed() then - ok(("Registry `%s` is installed."):format(source:get_display_name())) - else - error(("Registry `%s` is not installed. Run :MasonUpdate to install."):format(source:get_display_name())) - end +local function check_thunk(opts) + return function() + check(opts) end +end - local checks = { - check { - name = "unzip", - cmd = "unzip", - args = { "-v" }, - relaxed = true, - }, - check { +---@async +local function check_languages() + report_start "mason.nvim [Languages]" + + a.wait_all { + check_thunk { cmd = "go", args = { "version" }, name = "Go", @@ -184,7 +188,7 @@ function M.check() end end, }, - check { + check_thunk { cmd = "cargo", args = { "--version" }, name = "cargo", @@ -196,7 +200,7 @@ function M.check() end end, }, - check { + check_thunk { cmd = "luarocks", args = { "--version" }, name = "luarocks", @@ -209,14 +213,15 @@ function M.check() end end, }, - check { cmd = "ruby", args = { "--version" }, name = "Ruby", relaxed = true }, - check { cmd = "gem", args = { "--version" }, name = "RubyGem", relaxed = true }, - check { cmd = "composer", args = { "--version" }, name = "Composer", relaxed = true }, - check { cmd = "php", args = { "--version" }, name = "PHP", relaxed = true }, - check { + check_thunk { cmd = "ruby", args = { "--version" }, name = "Ruby", relaxed = true }, + check_thunk { cmd = "gem", args = { "--version" }, name = "RubyGem", relaxed = true }, + check_thunk { cmd = "composer", args = { "--version" }, name = "Composer", relaxed = true }, + check_thunk { cmd = "php", args = { "--version" }, name = "PHP", relaxed = true }, + check_thunk { cmd = "npm", args = { "--version" }, name = "npm", + relaxed = true, version_check = function(version) -- Parses output such as "8.1.2" into major, minor, patch components local _, _, major = version:find "(%d+)%.(%d+)%.(%d+)" @@ -226,10 +231,11 @@ function M.check() end end, }, - check { + check_thunk { cmd = "node", args = { "--version" }, name = "node", + relaxed = true, version_check = function(version) -- Parses output such as "v16.3.1" into major, minor, patch local _, _, major = version:find "v(%d+)%.(%d+)%.(%d+)" @@ -238,116 +244,60 @@ function M.check() end end, }, - check { cmd = "python3", args = { "--version" }, name = "python3", relaxed = true }, - check { cmd = "python3", args = { "-m", "pip", "--version" }, name = "pip3", relaxed = true }, - check { cmd = "javac", args = { "-version" }, name = "javac", relaxed = true }, - check { cmd = "java", args = { "-version" }, name = "java", use_stderr = true, relaxed = true }, - check { cmd = "julia", args = { "--version" }, name = "julia", relaxed = true }, - check { cmd = "wget", args = { "--version" }, name = "wget" }, - -- wget is used interchangeably with curl, but with higher priority, so we mark curl as relaxed - check { cmd = "curl", args = { "--version" }, name = "curl", relaxed = true }, - check { - cmd = "gzip", - args = { "--version" }, - name = "gzip", - use_stderr = platform.is.mac, -- Apple gzip prints version string to stderr - relaxed = platform.is.win, - }, - check { cmd = "tar", args = { "--version" }, name = "tar" }, - check { - cmd = "pwsh", - args = { - "-NoProfile", - "-Command", - [[$PSVersionTable.PSVersion, $PSVersionTable.OS, $PSVersionTable.Platform -join " "]], - }, - name = "pwsh", - relaxed = not platform.is.win, - }, + check_thunk { cmd = "javac", args = { "-version" }, name = "javac", relaxed = true }, + check_thunk { cmd = "java", args = { "-version" }, name = "java", use_stderr = true, relaxed = true }, + check_thunk { cmd = "julia", args = { "--version" }, name = "julia", relaxed = true }, + function() + if platform.is.win then + check { cmd = "python", args = { "--version" }, name = "python", relaxed = true } + check { cmd = "python", args = { "-m", "pip", "--version" }, name = "pip", relaxed = true } + else + check { cmd = "python3", args = { "--version" }, name = "python3", relaxed = true } + check { cmd = "python3", args = { "-m", "pip", "--version" }, name = "pip3", relaxed = true } + end + end, + function() + a.scheduler() + if vim.env.JAVA_HOME then + check { + cmd = ("%s/bin/java"):format(vim.env.JAVA_HOME), + args = { "-version" }, + name = "JAVA_HOME", + use_stderr = true, + relaxed = true, + } + end + end, + function() + a.scheduler() + if vim.g.python3_host_prog then + check { + cmd = vim.fn.expand(vim.g.python3_host_prog), + args = { "--version" }, + name = "python3_host_prog", + relaxed = true, + } + check { + cmd = vim.fn.expand(vim.g.python3_host_prog), + args = { "-m", "pip", "--version" }, + name = "python3_host_prog pip", + relaxed = true, + } + end + end, } +end - if platform.is.unix then - table.insert(checks, check { cmd = "bash", args = { "--version" }, name = "bash" }) - table.insert(checks, check { cmd = "sh", name = "sh" }) - end - - if platform.is.win then - table.insert(checks, check { cmd = "python", args = { "--version" }, name = "python", relaxed = true }) - table.insert( - checks, - check { cmd = "python", args = { "-m", "pip", "--version" }, name = "pip", relaxed = true } - ) - table.insert(checks, check { cmd = "7z", args = { "--help" }, name = "7z", relaxed = true }) - end - - if vim.g.python3_host_prog then - table.insert( - checks, - check { - cmd = vim.fn.expand(vim.g.python3_host_prog), - args = { "--version" }, - name = "python3_host_prog", - relaxed = true, - } - ) - table.insert( - checks, - check { - cmd = vim.fn.expand(vim.g.python3_host_prog), - args = { "-m", "pip", "--version" }, - name = "python3_host_prog pip", - relaxed = true, - } - ) - end - - if vim.env.JAVA_HOME then - table.insert( - checks, - check { - cmd = ("%s/bin/java"):format(vim.env.JAVA_HOME), - args = { "-version" }, - name = "JAVA_HOME", - use_stderr = true, - relaxed = true, - } - ) - end - - table.insert(checks, function() - github_client - .fetch_rate_limit() - :map( - ---@param rate_limit GitHubRateLimitResponse - function(rate_limit) - a.scheduler() - local remaining = rate_limit.resources.core.remaining - local used = rate_limit.resources.core.used - local limit = rate_limit.resources.core.limit - local reset = rate_limit.resources.core.reset - local diagnostics = ("Used: %d. Remaining: %d. Limit: %d. Reset: %s."):format( - used, - remaining, - limit, - vim.fn.strftime("%c", reset) - ) - if remaining <= 0 then - error(("GitHub API rate limit exceeded. %s"):format(diagnostics)) - else - ok(("GitHub API rate limit. %s"):format(diagnostics)) - end - end - ) - :on_failure(function() - a.scheduler() - warn "Failed to check GitHub API rate limit status." - end) - end) +function M.check() + report_start "mason.nvim" a.run_blocking(function() - for _, c in ipairs(checks) do - c() - end + check_neovim() + check_registries() + check_core_utils() + check_languages() + check_github() + a.wait(vim.schedule) end) end |
