Skip to content

Commit 0847316

Browse files
committed
Add nix formatter build command
`nix formatter build` is sort of like `nix build`: it builds, links, and prints a path to the formatter program: $ nix formatter build /nix/store/cb9w44vkhk2x4adfxwgdkkf5gjmm856j-treefmt/bin/treefmt Note that unlike `nix build`, this prints the full path to the program, not just the store path (in the example above that would be `/nix/store/cb9w44vkhk2x4adfxwgdkkf5gjmm856j-treefmt`). Motivation ---------- I maintain a vim plugin that automatically runs `nix fmt` on files on save. Since `nix fmt` can be quite slow due to nix evaluation, I choose to cache the `nix fmt `entrypoint. This was very awkward to do, see the implementation for details: https://github.com/nvimtools/none-ls.nvim/blob/786460723170bda9e9f95c55a382d21436575297/lua/null-ls/builtins/formatting/nix_flake_fmt.lua#L83-L110. I recently discovered that my implementation was buggy (it didn't handle flakes that expose a `formatter` package, such as nixpkgs), so I had to rework the implementation: nvimtools/none-ls.nvim#272. With the new `nix formatter build` command, I can delete all this akward code, and it will be easier for other folks to build performant editor integrations for `nix fmt`.
1 parent 7706cff commit 0847316

File tree

3 files changed

+126
-4
lines changed

3 files changed

+126
-4
lines changed

src/nix/formatter-build.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
R""(
2+
3+
# Description
4+
5+
`nix formatter build` builds the formatter specified in the flake.
6+
7+
Similar to [`nix build`](@docroot@/command-ref/new-cli/nix3-build),
8+
unless `--no-link` is specified, after a successful
9+
build, it creates a symlink to the store path of the formatter. This symlink is
10+
named `./result` by default; this can be overridden using the
11+
`--out-link` option.
12+
13+
# Examples
14+
15+
* Build the formatter:
16+
17+
```console
18+
# nix formatter build
19+
/nix/store/cb9w44vkhk2x4adfxwgdkkf5gjmm856j-treefmt/bin/treefmt
20+
```
21+
)""

src/nix/formatter.cc

+78-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include "nix/cmd/command.hh"
22
#include "nix/cmd/installable-value.hh"
3+
#include "nix/cmd/installable-derived-path.hh"
4+
#include "nix/main/common-args.hh"
35
#include "nix/expr/eval.hh"
46
#include "run.hh"
7+
#include "nix/store/local-fs-store.hh"
58

69
using namespace nix;
710

@@ -25,7 +28,7 @@ struct CmdFormatter : NixMultiCommand
2528

2629
static auto rCmdFormatter = registerCommand<CmdFormatter>("formatter");
2730

28-
struct CmdFormatterRun : SourceExprCommand
31+
struct CmdFormatterRun : SourceExprCommand, MixJSON
2932
{
3033
std::vector<std::string> args;
3134

@@ -87,6 +90,80 @@ struct CmdFormatterRun : SourceExprCommand
8790

8891
static auto rFormatterRun = registerCommand2<CmdFormatterRun>({"formatter", "run"});
8992

93+
struct CmdFormatterBuild : SourceExprCommand
94+
{
95+
Path outLink = "result";
96+
97+
CmdFormatterBuild()
98+
{
99+
addFlag({
100+
.longName = "out-link",
101+
.shortName = 'o',
102+
.description = "Use *path* as prefix for the symlink to the build result. It defaults to `result`.",
103+
.labels = {"path"},
104+
.handler = {&outLink},
105+
.completer = completePath,
106+
});
107+
108+
addFlag({
109+
.longName = "no-link",
110+
.description = "Do not create symlink to the build results.",
111+
.handler = {&outLink, Path("")},
112+
});
113+
}
114+
115+
std::string description() override
116+
{
117+
return "build the current flake's formatter";
118+
}
119+
120+
std::string doc() override
121+
{
122+
return
123+
#include "formatter-build.md"
124+
;
125+
}
126+
127+
Category category() override
128+
{
129+
return catSecondary;
130+
}
131+
132+
Strings getDefaultFlakeAttrPaths() override
133+
{
134+
return Strings{"formatter." + settings.thisSystem.get()};
135+
}
136+
137+
Strings getDefaultFlakeAttrPathPrefixes() override
138+
{
139+
return Strings{};
140+
}
141+
142+
void run(ref<Store> store) override
143+
{
144+
auto evalState = getEvalState();
145+
auto evalStore = getEvalStore();
146+
147+
auto installable_ = parseInstallable(store, ".");
148+
auto & installable = InstallableValue::require(*installable_);
149+
auto app = installable.toApp(*evalState).resolve(evalStore, store);
150+
151+
auto unresolvedApp = installable.toApp(*evalState);
152+
Installables installableContext;
153+
for (auto & ctxElt : unresolvedApp.unresolved.context)
154+
installableContext.push_back(make_ref<InstallableDerivedPath>(store, DerivedPath{ctxElt}));
155+
auto buildables = Installable::build(evalStore, store, Realise::Outputs, installableContext);
156+
157+
if (outLink != "")
158+
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
159+
createOutLinks(outLink, toBuiltPaths(buildables), *store2);
160+
161+
logger->cout("%s", app.program);
162+
};
163+
};
164+
165+
static auto rFormatterBuild = registerCommand2<CmdFormatterBuild>({"formatter", "build"});
166+
90167
struct CmdFmt : CmdFormatterRun
91168
{
92169
void run(ref<Store> store) override

tests/functional/formatter.sh

+27-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ TODO_NixOS # Provide a `shell` variable. Try not to `export` it, perhaps.
77
clearStoreIfPossible
88
rm -rf "$TEST_HOME"/.cache "$TEST_HOME"/.config "$TEST_HOME"/.local
99

10-
cp ./simple.nix ./simple.builder.sh ./fmt.simple.sh "${config_nix}" "$TEST_HOME"
10+
cp ./simple.nix ./simple.builder.sh ./formatter.simple.sh "${config_nix}" "$TEST_HOME"
1111

1212
cd "$TEST_HOME"
1313

14-
nix fmt --help | grep "forward"
14+
nix formatter --help | grep "build or run the formatter"
15+
nix fmt --help | grep "reformat your code"
16+
nix fmt run --help | grep "reformat your code"
17+
nix fmt build --help | grep "build"
1518

1619
cat << EOF > flake.nix
1720
{
@@ -23,16 +26,37 @@ cat << EOF > flake.nix
2326
buildCommand = ''
2427
mkdir -p \$out/bin
2528
echo "#! ${shell}" > \$out/bin/formatter
26-
cat \${./fmt.simple.sh} >> \$out/bin/formatter
29+
cat \${./formatter.simple.sh} >> \$out/bin/formatter
2730
chmod +x \$out/bin/formatter
2831
'';
2932
};
3033
};
3134
}
3235
EOF
36+
3337
# No arguments check
3438
[[ "$(nix fmt)" = "Formatting(0):" ]]
39+
[[ "$(nix formatter run)" = "Formatting(0):" ]]
40+
3541
# Argument forwarding check
3642
nix fmt ./file ./folder | grep 'Formatting(2): ./file ./folder'
43+
nix formatter run ./file ./folder | grep 'Formatting(2): ./file ./folder'
44+
45+
# Build checks
46+
## Defaults to a ./result.
47+
nix formatter build | grep ".\+/bin/formatter"
48+
[[ -L ./result ]]
49+
rm result
50+
51+
## Can prevent the symlink.
52+
nix formatter build --no-link
53+
[[ ! -e ./result ]]
54+
55+
## Can change the symlink name.
56+
nix formatter build --out-link my-result | grep ".\+/bin/formatter"
57+
[[ -L ./my-result ]]
58+
rm ./my-result
59+
60+
# Flake outputs check.
3761
nix flake check
3862
nix flake show | grep -P "package 'formatter'"

0 commit comments

Comments
 (0)