aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2022-04-20 16:53:16 +0000
committerJohn Ericson <John.Ericson@Obsidian.Systems>2022-04-20 16:53:16 +0000
commit3c220442ffb9c951f42185479d34a1abf8596e9b (patch)
tree0c09315dec88c7e1f9a4aad0e744692d0f72dbe7
parent75b62e52600a44b42693944b50638bf580a2c86e (diff)
parentee57f91413c9d01f1027eccbe01f7706c94919ac (diff)
Merge remote-tracking branch 'upstream/master' into fix-url-format
-rw-r--r--.version2
-rw-r--r--doc/manual/src/SUMMARY.md.in1
-rw-r--r--doc/manual/src/release-notes/rl-2.8.md53
-rw-r--r--doc/manual/src/release-notes/rl-next.md41
-rw-r--r--misc/bash/completion.sh2
-rw-r--r--src/libcmd/command.hh2
-rw-r--r--src/libcmd/common-eval-args.cc (renamed from src/libexpr/common-eval-args.cc)4
-rw-r--r--src/libcmd/common-eval-args.hh (renamed from src/libexpr/common-eval-args.hh)0
-rw-r--r--src/libcmd/installables.cc146
-rw-r--r--src/libcmd/installables.hh12
-rw-r--r--src/libexpr/eval-cache.cc4
-rw-r--r--src/libexpr/eval-cache.hh4
-rw-r--r--src/libstore/filetransfer.hh2
-rw-r--r--src/libutil/args.cc36
-rw-r--r--src/libutil/util.cc20
-rw-r--r--src/libutil/util.hh4
-rw-r--r--src/nix/app.cc14
-rw-r--r--src/nix/flake.cc15
-rw-r--r--src/nix/flake.md4
-rw-r--r--src/nix/fmt.cc53
-rw-r--r--src/nix/fmt.md53
-rw-r--r--src/nix/search.cc4
-rw-r--r--tests/flakes-run.sh29
-rw-r--r--tests/fmt.sh30
-rwxr-xr-xtests/fmt.simple.sh1
-rw-r--r--tests/local.mk2
26 files changed, 387 insertions, 151 deletions
diff --git a/.version b/.version
index 6533b6687..f3ac133c5 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-2.8.0 \ No newline at end of file
+2.9.0 \ No newline at end of file
diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in
index f0f9457d2..860222337 100644
--- a/doc/manual/src/SUMMARY.md.in
+++ b/doc/manual/src/SUMMARY.md.in
@@ -72,6 +72,7 @@
- [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
+ - [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md)
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)
- [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md)
diff --git a/doc/manual/src/release-notes/rl-2.8.md b/doc/manual/src/release-notes/rl-2.8.md
new file mode 100644
index 000000000..9778e8c3a
--- /dev/null
+++ b/doc/manual/src/release-notes/rl-2.8.md
@@ -0,0 +1,53 @@
+# Release 2.8 (2022-04-19)
+
+* New experimental command: `nix fmt`, which applies a formatter
+ defined by the `formatter.<system>` flake output to the Nix
+ expressions in a flake.
+
+* Various Nix commands can now read expressions from standard input
+ using `--file -`.
+
+* New experimental builtin function `builtins.fetchClosure` that
+ copies a closure from a binary cache at evaluation time and rewrites
+ it to content-addressed form (if it isn't already). Like
+ `builtins.storePath`, this allows importing pre-built store paths;
+ the difference is that it doesn't require the user to configure
+ binary caches and trusted public keys.
+
+ This function is only available if you enable the experimental
+ feature `fetch-closure`.
+
+* New experimental feature: *impure derivations*. These are
+ derivations that can produce a different result every time they're
+ built. Here is an example:
+
+ ```nix
+ stdenv.mkDerivation {
+ name = "impure";
+ __impure = true; # marks this derivation as impure
+ buildCommand = "date > $out";
+ }
+ ```
+
+ Running `nix build` twice on this expression will build the
+ derivation twice, producing two different content-addressed store
+ paths. Like fixed-output derivations, impure derivations have access
+ to the network. Only fixed-output derivations and impure derivations
+ can depend on an impure derivation.
+
+* `nix store make-content-addressable` has been renamed to `nix store
+ make-content-addressed`.
+
+* The `nixosModule` flake output attribute has been renamed consistent
+ with the `.default` renames in Nix 2.7.
+
+ * `nixosModule` → `nixosModules.default`
+
+ As before, the old output will continue to work, but `nix flake check` will
+ issue a warning about it.
+
+* `nix run` is now stricter in what it accepts: members of the `apps`
+ flake output are now required to be apps (as defined in [the
+ manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html#apps)),
+ and members of `packages` or `legacyPackages` must be derivations
+ (not apps).
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
index 8c8c0fd41..c869b5e2f 100644
--- a/doc/manual/src/release-notes/rl-next.md
+++ b/doc/manual/src/release-notes/rl-next.md
@@ -1,42 +1 @@
# Release X.Y (202?-??-??)
-
-* Various nix commands can now read expressions from stdin with `--file -`.
-
-* `nix store make-content-addressable` has been renamed to `nix store
- make-content-addressed`.
-
-* New experimental builtin function `builtins.fetchClosure` that
- copies a closure from a binary cache at evaluation time and rewrites
- it to content-addressed form (if it isn't already). Like
- `builtins.storePath`, this allows importing pre-built store paths;
- the difference is that it doesn't require the user to configure
- binary caches and trusted public keys.
-
- This function is only available if you enable the experimental
- feature `fetch-closure`.
-
-* New experimental feature: *impure derivations*. These are
- derivations that can produce a different result every time they're
- built. Here is an example:
-
- ```nix
- stdenv.mkDerivation {
- name = "impure";
- __impure = true; # marks this derivation as impure
- buildCommand = "date > $out";
- }
- ```
-
- Running `nix build` twice on this expression will build the
- derivation twice, producing two different content-addressed store
- paths. Like fixed-output derivations, impure derivations have access
- to the network. Only fixed-output derivations and impure derivations
- can depend on an impure derivation.
-
-* The `nixosModule` flake output attribute has been renamed consistent
- with the `.default` renames in nix 2.7.
-
- * `nixosModule` → `nixosModules.default`
-
- As before, the old output will continue to work, but `nix flake check` will
- issue a warning about it.
diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh
index 045053dee..9af695f5a 100644
--- a/misc/bash/completion.sh
+++ b/misc/bash/completion.sh
@@ -15,7 +15,7 @@ function _complete_nix {
else
COMPREPLY+=("$completion")
fi
- done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null)
+ done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}" 2>/dev/null)
__ltrim_colon_completions "$cur"
}
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index 15bc13c6e..84bbb5292 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -135,7 +135,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
std::optional<FlakeRef> getFlakeRefForCompletion() override
{
- return parseFlakeRef(_installable, absPath("."));
+ return parseFlakeRefWithFragment(_installable, absPath(".")).first;
}
private:
diff --git a/src/libexpr/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index e50ff244c..5b6e82388 100644
--- a/src/libexpr/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -7,6 +7,7 @@
#include "registry.hh"
#include "flake/flakeref.hh"
#include "store-api.hh"
+#include "command.hh"
namespace nix {
@@ -59,6 +60,9 @@ MixEvalArgs::MixEvalArgs()
fetchers::Attrs extraAttrs;
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
+ }},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(openStore(), prefix);
}}
});
diff --git a/src/libexpr/common-eval-args.hh b/src/libcmd/common-eval-args.hh
index 03fa226aa..03fa226aa 100644
--- a/src/libexpr/common-eval-args.hh
+++ b/src/libcmd/common-eval-args.hh
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 4e7262432..74c8a6df5 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -1,5 +1,6 @@
#include "globals.hh"
#include "installables.hh"
+#include "util.hh"
#include "command.hh"
#include "attr-path.hh"
#include "common-eval-args.hh"
@@ -100,6 +101,14 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.inputOverrides.insert_or_assign(
flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath("."), true));
+ }},
+ .completer = {[&](size_t n, std::string_view prefix) {
+ if (n == 0) {
+ if (auto flakeRef = getFlakeRefForCompletion())
+ completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
+ } else if (n == 1) {
+ completeFlakeRef(getEvalState()->store, prefix);
+ }
}}
});
@@ -194,6 +203,8 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
void SourceExprCommand::completeInstallable(std::string_view prefix)
{
if (file) {
+ completionType = ctAttrs;
+
evalSettings.pureEval = false;
auto state = getEvalState();
Expr *e = state->parseExprFromFile(
@@ -222,13 +233,14 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
Value v2;
state->autoCallFunction(*autoArgs, v1, v2);
- completionType = ctAttrs;
-
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) {
std::string name = i.name;
if (name.find(searchWord) == 0) {
- completions->add(i.name);
+ if (prefix_ == "")
+ completions->add(name);
+ else
+ completions->add(prefix_ + "." + name);
}
}
}
@@ -256,10 +268,11 @@ void completeFlakeRefWithFragment(
if (hash == std::string::npos) {
completeFlakeRef(evalState->store, prefix);
} else {
+ completionType = ctAttrs;
+
auto fragment = prefix.substr(hash + 1);
auto flakeRefS = std::string(prefix.substr(0, hash));
- // FIXME: do tilde expansion.
- auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
+ auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
auto evalCache = openEvalCache(*evalState,
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
@@ -271,8 +284,6 @@ void completeFlakeRefWithFragment(
flake. */
attrPathPrefixes.push_back("");
- completionType = ctAttrs;
-
for (auto & attrPathPrefixS : attrPathPrefixes) {
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
auto attrPathS = attrPathPrefixS + std::string(fragment);
@@ -346,16 +357,16 @@ DerivedPath Installable::toDerivedPath()
return std::move(buildables[0]);
}
-std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+std::vector<ref<eval_cache::AttrCursor>>
Installable::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
[&]() { return toValue(state).first; });
- return {{evalCache->getRoot(), ""}};
+ return {evalCache->getRoot()};
}
-std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
+ref<eval_cache::AttrCursor>
Installable::getCursor(EvalState & state)
{
auto cursors = getCursors(state);
@@ -578,43 +589,21 @@ InstallableFlake::InstallableFlake(
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{
- auto lockedFlake = getLockedFlake();
-
- auto cache = openEvalCache(*state, lockedFlake);
- auto root = cache->getRoot();
-
- Suggestions suggestions;
-
- for (auto & attrPath : getActualAttrPaths()) {
- debug("trying flake output attribute '%s'", attrPath);
+ auto attr = getCursor(*state);
- auto attrOrSuggestions = root->findAlongAttrPath(
- parseAttrPath(*state, attrPath),
- true
- );
-
- if (!attrOrSuggestions) {
- suggestions += attrOrSuggestions.getSuggestions();
- continue;
- }
-
- auto attr = *attrOrSuggestions;
+ auto attrPath = attr->getAttrPathStr();
- if (!attr->isDerivation())
- throw Error("flake output attribute '%s' is not a derivation", attrPath);
+ if (!attr->isDerivation())
+ throw Error("flake output attribute '%s' is not a derivation", attrPath);
- auto drvPath = attr->forceDerivation();
+ auto drvPath = attr->forceDerivation();
- auto drvInfo = DerivationInfo {
- std::move(drvPath),
- attr->getAttr(state->sOutputName)->getString()
- };
-
- return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
- }
+ auto drvInfo = DerivationInfo {
+ std::move(drvPath),
+ attr->getAttr(state->sOutputName)->getString()
+ };
- throw Error(suggestions, "flake '%s' does not provide attribute %s",
- flakeRef, showAttrPaths(getActualAttrPaths()));
+ return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
}
std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
@@ -626,33 +615,10 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
{
- auto lockedFlake = getLockedFlake();
-
- auto vOutputs = getFlakeOutputs(state, *lockedFlake);
-
- auto emptyArgs = state.allocBindings(0);
-
- Suggestions suggestions;
-
- for (auto & attrPath : getActualAttrPaths()) {
- try {
- auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
- state.forceValue(*v, pos);
- return {v, pos};
- } catch (AttrPathNotFound & e) {
- suggestions += e.info().suggestions;
- }
- }
-
- throw Error(
- suggestions,
- "flake '%s' does not provide attribute %s",
- flakeRef,
- showAttrPaths(getActualAttrPaths())
- );
+ return {&getCursor(state)->forceValue(), noPos};
}
-std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+std::vector<ref<eval_cache::AttrCursor>>
InstallableFlake::getCursors(EvalState & state)
{
auto evalCache = openEvalCache(state,
@@ -660,21 +626,55 @@ InstallableFlake::getCursors(EvalState & state)
auto root = evalCache->getRoot();
- std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res;
+ std::vector<ref<eval_cache::AttrCursor>> res;
for (auto & attrPath : getActualAttrPaths()) {
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
- if (attr) res.push_back({*attr, attrPath});
+ if (attr) res.push_back(ref(*attr));
}
return res;
}
+ref<eval_cache::AttrCursor> InstallableFlake::getCursor(EvalState & state)
+{
+ auto lockedFlake = getLockedFlake();
+
+ auto cache = openEvalCache(state, lockedFlake);
+ auto root = cache->getRoot();
+
+ Suggestions suggestions;
+
+ auto attrPaths = getActualAttrPaths();
+
+ for (auto & attrPath : attrPaths) {
+ debug("trying flake output attribute '%s'", attrPath);
+
+ auto attrOrSuggestions = root->findAlongAttrPath(
+ parseAttrPath(state, attrPath),
+ true
+ );
+
+ if (!attrOrSuggestions) {
+ suggestions += attrOrSuggestions.getSuggestions();
+ continue;
+ }
+
+ return *attrOrSuggestions;
+ }
+
+ throw Error(
+ suggestions,
+ "flake '%s' does not provide attribute %s",
+ flakeRef,
+ showAttrPaths(attrPaths));
+}
+
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{
- flake::LockFlags lockFlagsApplyConfig = lockFlags;
- lockFlagsApplyConfig.applyNixConfig = true;
if (!_lockedFlake) {
+ flake::LockFlags lockFlagsApplyConfig = lockFlags;
+ lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
}
return _lockedFlake;
@@ -980,10 +980,10 @@ std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
{
if (_installables.empty()) {
if (useDefaultInstallables())
- return parseFlakeRef(".", absPath("."));
+ return parseFlakeRefWithFragment(".", absPath(".")).first;
return {};
}
- return parseFlakeRef(_installables.front(), absPath("."));
+ return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
}
InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index f4bf0d406..b847f8939 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -80,10 +80,10 @@ struct Installable
return {};
}
- virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+ virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state);
- std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
+ virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state);
virtual FlakeRef nixpkgsFlakeRef() const
@@ -180,9 +180,15 @@ struct InstallableFlake : InstallableValue
std::pair<Value *, Pos> toValue(EvalState & state) override;
- std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+ /* Get a cursor to every attrpath in getActualAttrPaths() that
+ exists. */
+ std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state) override;
+ /* Get a cursor to the first attrpath in getActualAttrPaths() that
+ exists, or throw an exception with suggestions if none exists. */
+ ref<eval_cache::AttrCursor> getCursor(EvalState & state) override;
+
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
FlakeRef nixpkgsFlakeRef() const override;
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 54fa9b741..7d3fd01a4 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -306,9 +306,9 @@ Value * EvalCache::getRootValue()
return *value;
}
-std::shared_ptr<AttrCursor> EvalCache::getRoot()
+ref<AttrCursor> EvalCache::getRoot()
{
- return std::make_shared<AttrCursor>(ref(shared_from_this()), std::nullopt);
+ return make_ref<AttrCursor>(ref(shared_from_this()), std::nullopt);
}
AttrCursor::AttrCursor(
diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh
index c9a9bf471..b0709ebc2 100644
--- a/src/libexpr/eval-cache.hh
+++ b/src/libexpr/eval-cache.hh
@@ -33,7 +33,7 @@ public:
EvalState & state,
RootLoader rootLoader);
- std::shared_ptr<AttrCursor> getRoot();
+ ref<AttrCursor> getRoot();
};
enum AttrType {
@@ -104,6 +104,8 @@ public:
ref<AttrCursor> getAttr(std::string_view name);
+ /* Get an attribute along a chain of attrsets. Note that this does
+ not auto-call functors or functions. */
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
std::string getString();
diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh
index 1ad96bc10..40e7cf52c 100644
--- a/src/libstore/filetransfer.hh
+++ b/src/libstore/filetransfer.hh
@@ -31,7 +31,7 @@ struct FileTransferSettings : Config
R"(
The timeout (in seconds) for establishing connections in the
binary cache substituter. It corresponds to `curl`’s
- `--connect-timeout` option.
+ `--connect-timeout` option. A value of 0 means no limit.
)"};
Setting<unsigned long> stalledDownloadTimeout{
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 69aa0d094..4b8c55686 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -127,11 +127,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (flag.handler.arity == ArityAny) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
}
- if (flag.completer)
- if (auto prefix = needsCompletion(*pos)) {
- anyCompleted = true;
+ if (auto prefix = needsCompletion(*pos)) {
+ anyCompleted = true;
+ if (flag.completer)
flag.completer(n, *prefix);
- }
+ }
args.push_back(*pos++);
}
if (!anyCompleted)
@@ -146,6 +146,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
&& hasPrefix(name, std::string(*prefix, 2)))
completions->add("--" + name, flag->description);
}
+ return false;
}
auto i = longFlags.find(std::string(*pos, 2));
if (i == longFlags.end()) return false;
@@ -187,10 +188,12 @@ bool Args::processArgs(const Strings & args, bool finish)
{
std::vector<std::string> ss;
for (const auto &[n, s] : enumerate(args)) {
- ss.push_back(s);
- if (exp.completer)
- if (auto prefix = needsCompletion(s))
+ if (auto prefix = needsCompletion(s)) {
+ ss.push_back(*prefix);
+ if (exp.completer)
exp.completer(n, *prefix);
+ } else
+ ss.push_back(s);
}
exp.handler.fun(ss);
expectedArgs.pop_front();
@@ -279,21 +282,22 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
{
completionType = ctFilenames;
glob_t globbuf;
- int flags = GLOB_NOESCAPE | GLOB_TILDE;
+ int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR
if (onlyDirs)
flags |= GLOB_ONLYDIR;
#endif
- if (glob((std::string(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) {
+ // using expandTilde here instead of GLOB_TILDE(_CHECK) so that ~<Tab> expands to /home/user/
+ if (glob((expandTilde(prefix) + "*").c_str(), flags, nullptr, &globbuf) == 0) {
for (size_t i = 0; i < globbuf.gl_pathc; ++i) {
if (onlyDirs) {
- auto st = lstat(globbuf.gl_pathv[i]);
+ auto st = stat(globbuf.gl_pathv[i]);
if (!S_ISDIR(st.st_mode)) continue;
}
completions->add(globbuf.gl_pathv[i]);
}
- globfree(&globbuf);
}
+ globfree(&globbuf);
}
void completePath(size_t, std::string_view prefix)
@@ -322,11 +326,6 @@ MultiCommand::MultiCommand(const Commands & commands_)
.optional = true,
.handler = {[=](std::string s) {
assert(!command);
- if (auto prefix = needsCompletion(s)) {
- for (auto & [name, command] : commands)
- if (hasPrefix(name, *prefix))
- completions->add(name);
- }
auto i = commands.find(s);
if (i == commands.end()) {
std::set<std::string> commandNames;
@@ -337,6 +336,11 @@ MultiCommand::MultiCommand(const Commands & commands_)
}
command = {s, i->second()};
command->second->parent = this;
+ }},
+ .completer = {[&](size_t, std::string_view prefix) {
+ for (auto & [name, command] : commands)
+ if (hasPrefix(name, prefix))
+ completions->add(name);
}}
});
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index d4379a0cf..b49c1e466 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -198,6 +198,17 @@ std::string_view baseNameOf(std::string_view path)
}
+std::string expandTilde(std::string_view path)
+{
+ // TODO: expand ~user ?
+ auto tilde = path.substr(0, 2);
+ if (tilde == "~/" || tilde == "~")
+ return getHome() + std::string(path.substr(1));
+ else
+ return std::string(path);
+}
+
+
bool isInDir(std::string_view path, std::string_view dir)
{
return path.substr(0, 1) == "/"
@@ -213,6 +224,15 @@ bool isDirOrInDir(std::string_view path, std::string_view dir)
}
+struct stat stat(const Path & path)
+{
+ struct stat st;
+ if (stat(path.c_str(), &st))
+ throw SysError("getting status of '%1%'", path);
+ return st;
+}
+
+
struct stat lstat(const Path & path)
{
struct stat st;
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 20591952d..a1d0e0e6b 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -68,6 +68,9 @@ Path dirOf(const PathView path);
following the final `/' (trailing slashes are removed). */
std::string_view baseNameOf(std::string_view path);
+/* Perform tilde expansion on a path. */
+std::string expandTilde(std::string_view path);
+
/* Check whether 'path' is a descendant of 'dir'. Both paths must be
canonicalized. */
bool isInDir(std::string_view path, std::string_view dir);
@@ -77,6 +80,7 @@ bool isInDir(std::string_view path, std::string_view dir);
bool isDirOrInDir(std::string_view path, std::string_view dir);
/* Get status of `path'. */
+struct stat stat(const Path & path);
struct stat lstat(const Path & path);
/* Return true iff the given path exists. */
diff --git a/src/nix/app.cc b/src/nix/app.cc
index 803d028f0..df7303e15 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -35,7 +35,7 @@ struct InstallableDerivedPath : Installable
/**
* Return the rewrites that are needed to resolve a string whose context is
- * included in `dependencies`
+ * included in `dependencies`.
*/
StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies)
{
@@ -51,7 +51,7 @@ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies)
}
/**
- * Resolve the given string assuming the given context
+ * Resolve the given string assuming the given context.
*/
std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies)
{
@@ -61,14 +61,18 @@ std::string resolveString(Store & store, const std::string & toResolve, const Bu
UnresolvedApp Installable::toApp(EvalState & state)
{
- auto [cursor, attrPath] = getCursor(state);
+ auto cursor = getCursor(state);
+ auto attrPath = cursor->getAttrPath();
auto type = cursor->getAttr("type")->getString();
+ std::string expected = !attrPath.empty() && attrPath[0] == "apps" ? "app" : "derivation";
+ if (type != expected)
+ throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected);
+
if (type == "app") {
auto [program, context] = cursor->getAttr("program")->getStringWithContext();
-
std::vector<StorePathWithOutputs> context2;
for (auto & [path, name] : context)
context2.push_back({path, {name}});
@@ -101,7 +105,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
}
else
- throw Error("attribute '%s' has unsupported type '%s'", attrPath, type);
+ throw Error("attribute '%s' has unsupported type '%s'", cursor->getAttrPathStr(), type);
}
// FIXME: move to libcmd
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index a876bb3af..04b23ed0f 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -528,6 +528,16 @@ struct CmdFlakeCheck : FlakeCommand
}
}
+ else if (name == "formatter") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs) {
+ checkSystemName(attr.name, *attr.pos);
+ checkApp(
+ fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+ }
+
else if (name == "packages" || name == "devShells") {
state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) {
@@ -705,7 +715,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
defaultTemplateAttrPathsPrefixes,
lockFlags);
- auto [cursor, attrPath] = installable.getCursor(*evalState);
+ auto cursor = installable.getCursor(*evalState);
auto templateDirAttr = cursor->getAttr("path");
auto templateDir = templateDirAttr->getString();
@@ -1011,6 +1021,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|| (attrPath.size() == 1 && (
attrPath[0] == "defaultPackage"
|| attrPath[0] == "devShell"
+ || attrPath[0] == "formatter"
|| attrPath[0] == "nixosConfigurations"
|| attrPath[0] == "nixosModules"
|| attrPath[0] == "defaultApp"
@@ -1027,7 +1038,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
}
else if (
- (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell"))
+ (attrPath.size() == 2 && (attrPath[0] == "defaultPackage" || attrPath[0] == "devShell" || attrPath[0] == "formatter"))
|| (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells"))
)
{
diff --git a/src/nix/flake.md b/src/nix/flake.md
index d59915eeb..7d179a6c4 100644
--- a/src/nix/flake.md
+++ b/src/nix/flake.md
@@ -177,8 +177,8 @@ Currently the `type` attribute can be one of the following:
attribute `url`.
In URL form, the schema must be `http://`, `https://` or `file://`
- URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`,
- `.tar.bz2` or `.tar.zst`.
+ URLs and the extension must be `.zip`, `.tar`, `.tgz`, `.tar.gz`,
+ `.tar.xz`, `.tar.bz2` or `.tar.zst`.
* `github`: A more efficient way to fetch repositories from
GitHub. The following attributes are required:
diff --git a/src/nix/fmt.cc b/src/nix/fmt.cc
new file mode 100644
index 000000000..e5d44bd38
--- /dev/null
+++ b/src/nix/fmt.cc
@@ -0,0 +1,53 @@
+#include "command.hh"
+#include "run.hh"
+
+using namespace nix;
+
+struct CmdFmt : SourceExprCommand {
+ std::vector<std::string> args;
+
+ CmdFmt() { expectArgs({.label = "args", .handler = {&args}}); }
+
+ std::string description() override {
+ return "reformat your code in the standard style";
+ }
+
+ std::string doc() override {
+ return
+ #include "fmt.md"
+ ;
+ }
+
+ Category category() override { return catSecondary; }
+
+ Strings getDefaultFlakeAttrPaths() override {
+ return Strings{"formatter." + settings.thisSystem.get()};
+ }
+
+ Strings getDefaultFlakeAttrPathPrefixes() override { return Strings{}; }
+
+ void run(ref<Store> store) {
+ auto evalState = getEvalState();
+ auto evalStore = getEvalStore();
+
+ auto installable = parseInstallable(store, ".");
+ auto app = installable->toApp(*evalState).resolve(evalStore, store);
+
+ Strings programArgs{app.program};
+
+ // Propagate arguments from the CLI
+ if (args.empty()) {
+ // Format the current flake out of the box
+ programArgs.push_back(".");
+ } else {
+ // User wants more power, let them decide which paths to include/exclude
+ for (auto &i : args) {
+ programArgs.push_back(i);
+ }
+ }
+
+ runProgramInStore(store, app.program, programArgs);
+ };
+};
+
+static auto r2 = registerCommand<CmdFmt>("fmt");
diff --git a/src/nix/fmt.md b/src/nix/fmt.md
new file mode 100644
index 000000000..1c78bb36f
--- /dev/null
+++ b/src/nix/fmt.md
@@ -0,0 +1,53 @@
+R""(
+
+# Examples
+
+With [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt):
+
+```nix
+# flake.nix
+{
+ outputs = { nixpkgs, self }: {
+ formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixpkgs-fmt;
+ };
+}
+```
+
+- Format the current flake: `$ nix fmt`
+
+- Format a specific folder or file: `$ nix fmt ./folder ./file.nix`
+
+With [nixfmt](https://github.com/serokell/nixfmt):
+
+```nix
+# flake.nix
+{
+ outputs = { nixpkgs, self }: {
+ formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
+ };
+}
+```
+
+- Format specific files: `$ nix fmt ./file1.nix ./file2.nix`
+
+With [Alejandra](https://github.com/kamadorueda/alejandra):
+
+```nix
+# flake.nix
+{
+ outputs = { nixpkgs, self }: {
+ formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra;
+ };
+}
+```
+
+- Format the current flake: `$ nix fmt`
+
+- Format a specific folder or file: `$ nix fmt ./folder ./file.nix`
+
+# Description
+
+`nix fmt` will rewrite all Nix files (\*.nix) to a canonical format
+using the formatter specified in your flake.
+
+)""
diff --git a/src/nix/search.cc b/src/nix/search.cc
index e9307342c..e96a85ea2 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -165,8 +165,8 @@ struct CmdSearch : InstallableCommand, MixJSON
}
};
- for (auto & [cursor, prefix] : installable->getCursors(*state))
- visit(*cursor, parseAttrPath(*state, prefix), true);
+ for (auto & cursor : installable->getCursors(*state))
+ visit(*cursor, cursor->getAttrPath(), true);
if (!json && !results)
throw Error("no results for the given search term(s)!");
diff --git a/tests/flakes-run.sh b/tests/flakes-run.sh
new file mode 100644
index 000000000..88fc3e628
--- /dev/null
+++ b/tests/flakes-run.sh
@@ -0,0 +1,29 @@
+source common.sh
+
+clearStore
+rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
+cp ./shell-hello.nix ./config.nix $TEST_HOME
+cd $TEST_HOME
+
+cat <<EOF > flake.nix
+{
+ outputs = {self}: {
+ packages.$system.pkgAsPkg = (import ./shell-hello.nix).hello;
+ packages.$system.appAsApp = self.packages.$system.appAsApp;
+
+ apps.$system.pkgAsApp = self.packages.$system.pkgAsPkg;
+ apps.$system.appAsApp = {
+ type = "app";
+ program = "\${(import ./shell-hello.nix).hello}/bin/hello";
+ };
+ };
+}
+EOF
+nix run --no-write-lock-file .#appAsApp
+nix run --no-write-lock-file .#pkgAsPkg
+
+! nix run --no-write-lock-file .#pkgAsApp || fail "'nix run' shouldn’t accept an 'app' defined under 'packages'"
+! nix run --no-write-lock-file .#appAsPkg || fail "elements of 'apps' should be of type 'app'"
+
+clearStore
+
diff --git a/tests/fmt.sh b/tests/fmt.sh
new file mode 100644
index 000000000..bc05118ff
--- /dev/null
+++ b/tests/fmt.sh
@@ -0,0 +1,30 @@
+source common.sh
+
+set -o pipefail
+
+clearStore
+rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
+
+cp ./simple.nix ./simple.builder.sh ./fmt.simple.sh ./config.nix $TEST_HOME
+
+cd $TEST_HOME
+
+nix fmt --help | grep "Format"
+
+cat << EOF > flake.nix
+{
+ outputs = _: {
+ formatter.$system =
+ with import ./config.nix;
+ mkDerivation {
+ name = "formatter";
+ buildCommand = "mkdir -p \$out/bin; cp \${./fmt.simple.sh} \$out/bin/formatter";
+ };
+ };
+}
+EOF
+nix fmt ./file ./folder | grep 'Formatting: ./file ./folder'
+nix flake check
+nix flake show | grep -P "package 'formatter'"
+
+clearStore
diff --git a/tests/fmt.simple.sh b/tests/fmt.simple.sh
new file mode 100755
index 000000000..4c8c67ebb
--- /dev/null
+++ b/tests/fmt.simple.sh
@@ -0,0 +1 @@
+echo Formatting: "${@}"
diff --git a/tests/local.mk b/tests/local.mk
index 668b34500..cb869f32e 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -1,5 +1,6 @@
nix_tests = \
flakes.sh \
+ flakes-run.sh \
ca/gc.sh \
gc.sh \
remote-store.sh \
@@ -79,6 +80,7 @@ nix_tests = \
post-hook.sh \
function-trace.sh \
flake-local-settings.sh \
+ fmt.sh \
eval-store.sh \
why-depends.sh \
import-derivation.sh \