Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 21 additions & 237 deletions lua/null-ls/builtins/formatting/nix_flake_fmt.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,217 +31,6 @@ local tmpname = function()
return mktemp()
end

--- Asynchronously build and return the formatter for the flake located at {root},
--- If {root} is not a flake, or does not have a formatter, or we cannot build the formatter, return `nil`.
--- This legacy codepath is quite complicated, and unnecessary now that `nix` has core support for
--- returning the fromatter command.
--- TODO: remove after the `nix formatter` subcommand has been released for a while.
--- The command was introduced in https://github.com/NixOS/nix/commit/d155bb901241441149c701b9efc92f5785c2e1c3
---
--- @param root string
--- @return string|nil
local legacy_find_nix_fmt = function(root)
local get_current_system = function()
local status, stdout_lines, stderr_lines = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command",
"config",
"show",
"system",
},
})

if status ~= 0 then
local stderr = table.concat(stderr_lines, "\n")
vim.defer_fn(function()
log:warn(string.format("unable to discover builtins.currentSystem from nix. stderr: %s", stderr))
end, 0)
return
end

local nix_current_system = stdout_lines[1]
return nix_current_system
end

local get_flake_ref = function(_root)
local status, stdout_lines, stderr_lines = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command flakes",
"flake",
"metadata",
"--json",
_root,
},
})

if status ~= 0 then
local stderr = table.concat(stderr_lines, "\n")
vim.defer_fn(function()
log:warn(string.format("unable to get flake ref for '%s'. stderr: %s", root, stderr))
end, 0)
return
end

local stdout = table.concat(stdout_lines, "\n")
local metadata = vim.json.decode(stdout)
local flake_ref = metadata.resolvedUrl
if flake_ref == nil then
vim.defer_fn(function()
log:warn(
string.format("flake metadata does not have a 'resolvedUrl'. metadata: %s", vim.inspect(metadata))
)
end, 0)
return
end

return flake_ref
end

local evaluate_flake_formatter = function(_root)
local nix_current_system = get_current_system()
if nix_current_system == nil then
return
end
local flake_ref = get_flake_ref(_root)
if flake_ref == nil then
return
end
local eval_nix_formatter = [[
let
system = "]] .. nix_current_system .. [[";
flake = builtins.getFlake "]] .. flake_ref .. [[";
# Various functions vendored from nixpkgs lib (to avoid adding a
# dependency on nixpkgs).
lib = rec {
getOutput = output: pkg:
if ! pkg ? outputSpecified || ! pkg.outputSpecified
then pkg.${output} or pkg.out or pkg
else pkg;
getBin = getOutput "bin";
# Simplified by removing various type assertions.
getExe' = x: y: "${getBin x}/bin/${y}";
# getExe is simplified to assume meta.mainProgram is specified.
getExe = x: getExe' x x.meta.mainProgram;
};
result =
if flake ? formatter then
if flake.formatter ? ${system} then
let
formatter = flake.formatter.${system};
drv = formatter.drvPath;
bin = lib.getExe formatter;
in
{ inherit drv bin; }
else
{ error = "this flake does not define a formatter for system: ${system}"; }
else
{ error = "this flake does not define any formatters"; };
in
builtins.toJSON result
]]

local status, stdout_lines, stderr_lines = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command flakes",
"eval",
"--raw",
-- We need `--impure` to be able to call `builtins.getFlake`
-- on an unlocked flake ref.
"--impure",
"--expr",
eval_nix_formatter,
},
})

if status ~= 0 then
local stderr = table.concat(stderr_lines, "\n")
vim.defer_fn(function()
log:warn(string.format("unable to discover 'nix fmt' command. stderr: %s", stderr))
end, 0)
return
end

local stdout = table.concat(stdout_lines, "\n")
local result = vim.json.decode(stdout)

if result.error ~= nil then
vim.defer_fn(function()
log:warn(result.error)
end, 0)
return
end

local drv_path = result.drv
local nix_fmt_path = result.bin
return drv_path, nix_fmt_path
end

local build_derivation = function(options)
if type(options.drv) ~= "string" then
error("missing drv")
elseif type(options.out_link) ~= "string" then
error("missing out_link")
end

local drv_path = options.drv
local out_link = options.out_link

local status, _, stderr_lines = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command",
"build",
"--out-link",
out_link,
drv_path .. "^out",
},
})

if status ~= 0 then
local stderr = table.concat(stderr_lines, "\n")
vim.defer_fn(function()
log:warn(string.format("unable to build 'nix fmt' entrypoint. stderr: %s", stderr))
end, 0)
return false
end

return true
end

local drv_path, nix_fmt_path = evaluate_flake_formatter(root)
if drv_path == nil then
return nil
end

-- Build the derivation. This ensures that `nix_fmt_path` exists.
if not build_derivation({ drv = drv_path, out_link = tmpname() }) then
return nil
end

return nix_fmt_path
end

local nix_has_formatter_subcommand = function()
local status, _, _ = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command flakes",
"formatter",
"--help",
},
})

return status == 0
end

--- Asynchronously computes the command that `nix fmt` would run, or nil if
--- we're not in a flake with a formatter, or if we fail to discover the
--- formatter somehow. When finished, it invokes the `done` callback with a
Expand Down Expand Up @@ -289,35 +78,30 @@ local find_nix_fmt = function(opts, done)
end

local nix_fmt_path ---@type string|nil
local is_legacy = not nix_has_formatter_subcommand()
if is_legacy then
nix_fmt_path = legacy_find_nix_fmt(opts.root)
else
local status, stdout_lines, stderr_lines = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command",
"formatter",
"build",
"--out-link",
tmpname(),
},
cwd = opts.root,
})

if status ~= 0 then
local stderr = table.concat(stderr_lines, "\n")
vim.defer_fn(function()
log:warn(string.format("unable to build 'nix fmt' entrypoint. stderr: %s", stderr))
end, 0)
return false
end
local status, stdout_lines, stderr_lines = run_job({
command = "nix",
args = {
"--extra-experimental-features",
"nix-command",
"formatter",
"build",
"--out-link",
tmpname(),
},
cwd = opts.root,
})

local stdout = table.concat(stdout_lines, "\n")
nix_fmt_path = stdout
local stdout = table.concat(stdout_lines, "\n")
if status ~= 0 then
local stderr = table.concat(stderr_lines, "\n")
vim.defer_fn(function()
log:warn(string.format("command 'nix formatter build' failed. stdout: %s stderr: %s", stdout, stderr))
end, 0)
_done(nil)
end

nix_fmt_path = stdout

return _done(nix_fmt_path)
end)
end
Expand Down