aboutsummaryrefslogtreecommitdiffstats
path: root/lua
diff options
context:
space:
mode:
authorWilliam Boman <william@redwill.se>2023-04-16 21:02:03 +0200
committerGitHub <noreply@github.com>2023-04-16 21:02:03 +0200
commit27b7a132d10bceb17d12145d4c34746e0f08198f (patch)
tree355de9d053ab49c7f04f1d5bfc144068f1d133ac /lua
parentfix(checkhealth): use non-deprecated versions if possible (#1219) (diff)
downloadmason-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.lua2
-rw-r--r--lua/mason/health.lua458
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