Merge remote-tracking branch 'upstream/master' into fix-url-format

This commit is contained in:
John Ericson 2022-04-20 16:53:16 +00:00
commit 3c220442ff
26 changed files with 387 additions and 151 deletions

View file

@ -1 +1 @@
2.8.0 2.9.0

View file

@ -72,6 +72,7 @@
- [CLI guideline](contributing/cli-guideline.md) - [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.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.7 (2022-03-07)](release-notes/rl-2.7.md)
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.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) - [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md)

View file

@ -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).

View file

@ -1,42 +1 @@
# Release X.Y (202?-??-??) # 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.

View file

@ -15,7 +15,7 @@ function _complete_nix {
else else
COMPREPLY+=("$completion") COMPREPLY+=("$completion")
fi fi
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null) done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}" 2>/dev/null)
__ltrim_colon_completions "$cur" __ltrim_colon_completions "$cur"
} }

View file

@ -135,7 +135,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
std::optional<FlakeRef> getFlakeRefForCompletion() override std::optional<FlakeRef> getFlakeRefForCompletion() override
{ {
return parseFlakeRef(_installable, absPath(".")); return parseFlakeRefWithFragment(_installable, absPath(".")).first;
} }
private: private:

View file

@ -7,6 +7,7 @@
#include "registry.hh" #include "registry.hh"
#include "flake/flakeref.hh" #include "flake/flakeref.hh"
#include "store-api.hh" #include "store-api.hh"
#include "command.hh"
namespace nix { namespace nix {
@ -59,6 +60,9 @@ MixEvalArgs::MixEvalArgs()
fetchers::Attrs extraAttrs; fetchers::Attrs extraAttrs;
if (to.subdir != "") extraAttrs["dir"] = to.subdir; if (to.subdir != "") extraAttrs["dir"] = to.subdir;
fetchers::overrideRegistry(from.input, to.input, extraAttrs); fetchers::overrideRegistry(from.input, to.input, extraAttrs);
}},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(openStore(), prefix);
}} }}
}); });

View file

@ -1,5 +1,6 @@
#include "globals.hh" #include "globals.hh"
#include "installables.hh" #include "installables.hh"
#include "util.hh"
#include "command.hh" #include "command.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "common-eval-args.hh" #include "common-eval-args.hh"
@ -100,6 +101,14 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.inputOverrides.insert_or_assign( lockFlags.inputOverrides.insert_or_assign(
flake::parseInputPath(inputPath), flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath("."), true)); 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) void SourceExprCommand::completeInstallable(std::string_view prefix)
{ {
if (file) { if (file) {
completionType = ctAttrs;
evalSettings.pureEval = false; evalSettings.pureEval = false;
auto state = getEvalState(); auto state = getEvalState();
Expr *e = state->parseExprFromFile( Expr *e = state->parseExprFromFile(
@ -222,13 +233,14 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
Value v2; Value v2;
state->autoCallFunction(*autoArgs, v1, v2); state->autoCallFunction(*autoArgs, v1, v2);
completionType = ctAttrs;
if (v2.type() == nAttrs) { if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) { for (auto & i : *v2.attrs) {
std::string name = i.name; std::string name = i.name;
if (name.find(searchWord) == 0) { 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) { if (hash == std::string::npos) {
completeFlakeRef(evalState->store, prefix); completeFlakeRef(evalState->store, prefix);
} else { } else {
completionType = ctAttrs;
auto fragment = prefix.substr(hash + 1); auto fragment = prefix.substr(hash + 1);
auto flakeRefS = std::string(prefix.substr(0, hash)); auto flakeRefS = std::string(prefix.substr(0, hash));
// FIXME: do tilde expansion. auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
auto evalCache = openEvalCache(*evalState, auto evalCache = openEvalCache(*evalState,
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags))); std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
@ -271,8 +284,6 @@ void completeFlakeRefWithFragment(
flake. */ flake. */
attrPathPrefixes.push_back(""); attrPathPrefixes.push_back("");
completionType = ctAttrs;
for (auto & attrPathPrefixS : attrPathPrefixes) { for (auto & attrPathPrefixS : attrPathPrefixes) {
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
auto attrPathS = attrPathPrefixS + std::string(fragment); auto attrPathS = attrPathPrefixS + std::string(fragment);
@ -346,16 +357,16 @@ DerivedPath Installable::toDerivedPath()
return std::move(buildables[0]); 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) Installable::getCursors(EvalState & state)
{ {
auto evalCache = auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state, std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
[&]() { return toValue(state).first; }); [&]() { 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) Installable::getCursor(EvalState & state)
{ {
auto cursors = getCursors(state); auto cursors = getCursors(state);
@ -578,43 +589,21 @@ InstallableFlake::InstallableFlake(
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation() std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{ {
auto lockedFlake = getLockedFlake(); auto attr = getCursor(*state);
auto cache = openEvalCache(*state, lockedFlake); auto attrPath = attr->getAttrPathStr();
auto root = cache->getRoot();
Suggestions suggestions; if (!attr->isDerivation())
throw Error("flake output attribute '%s' is not a derivation", attrPath);
for (auto & attrPath : getActualAttrPaths()) { auto drvPath = attr->forceDerivation();
debug("trying flake output attribute '%s'", attrPath);
auto attrOrSuggestions = root->findAlongAttrPath( auto drvInfo = DerivationInfo {
parseAttrPath(*state, attrPath), std::move(drvPath),
true attr->getAttr(state->sOutputName)->getString()
); };
if (!attrOrSuggestions) { return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
suggestions += attrOrSuggestions.getSuggestions();
continue;
}
auto attr = *attrOrSuggestions;
if (!attr->isDerivation())
throw Error("flake output attribute '%s' is not a derivation", attrPath);
auto drvPath = attr->forceDerivation();
auto drvInfo = DerivationInfo {
std::move(drvPath),
attr->getAttr(state->sOutputName)->getString()
};
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
}
throw Error(suggestions, "flake '%s' does not provide attribute %s",
flakeRef, showAttrPaths(getActualAttrPaths()));
} }
std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations() std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
@ -626,33 +615,10 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
{ {
auto lockedFlake = getLockedFlake(); return {&getCursor(state)->forceValue(), noPos};
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())
);
} }
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> std::vector<ref<eval_cache::AttrCursor>>
InstallableFlake::getCursors(EvalState & state) InstallableFlake::getCursors(EvalState & state)
{ {
auto evalCache = openEvalCache(state, auto evalCache = openEvalCache(state,
@ -660,21 +626,55 @@ InstallableFlake::getCursors(EvalState & state)
auto root = evalCache->getRoot(); 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()) { for (auto & attrPath : getActualAttrPaths()) {
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
if (attr) res.push_back({*attr, attrPath}); if (attr) res.push_back(ref(*attr));
} }
return res; 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 std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{ {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
lockFlagsApplyConfig.applyNixConfig = true;
if (!_lockedFlake) { if (!_lockedFlake) {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig)); _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
} }
return _lockedFlake; return _lockedFlake;
@ -980,10 +980,10 @@ std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
{ {
if (_installables.empty()) { if (_installables.empty()) {
if (useDefaultInstallables()) if (useDefaultInstallables())
return parseFlakeRef(".", absPath(".")); return parseFlakeRefWithFragment(".", absPath(".")).first;
return {}; return {};
} }
return parseFlakeRef(_installables.front(), absPath(".")); return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
} }
InstallableCommand::InstallableCommand(bool supportReadOnlyMode) InstallableCommand::InstallableCommand(bool supportReadOnlyMode)

View file

@ -80,10 +80,10 @@ struct Installable
return {}; return {};
} }
virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state); getCursors(EvalState & state);
std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string> virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state); getCursor(EvalState & state);
virtual FlakeRef nixpkgsFlakeRef() const virtual FlakeRef nixpkgsFlakeRef() const
@ -180,9 +180,15 @@ struct InstallableFlake : InstallableValue
std::pair<Value *, Pos> toValue(EvalState & state) override; 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; 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; std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
FlakeRef nixpkgsFlakeRef() const override; FlakeRef nixpkgsFlakeRef() const override;

View file

@ -306,9 +306,9 @@ Value * EvalCache::getRootValue()
return *value; 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( AttrCursor::AttrCursor(

View file

@ -33,7 +33,7 @@ public:
EvalState & state, EvalState & state,
RootLoader rootLoader); RootLoader rootLoader);
std::shared_ptr<AttrCursor> getRoot(); ref<AttrCursor> getRoot();
}; };
enum AttrType { enum AttrType {
@ -104,6 +104,8 @@ public:
ref<AttrCursor> getAttr(std::string_view name); 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); OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
std::string getString(); std::string getString();

View file

@ -31,7 +31,7 @@ struct FileTransferSettings : Config
R"( R"(
The timeout (in seconds) for establishing connections in the The timeout (in seconds) for establishing connections in the
binary cache substituter. It corresponds to `curl`s 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{ Setting<unsigned long> stalledDownloadTimeout{

View file

@ -127,11 +127,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (flag.handler.arity == ArityAny) break; if (flag.handler.arity == ArityAny) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
} }
if (flag.completer) if (auto prefix = needsCompletion(*pos)) {
if (auto prefix = needsCompletion(*pos)) { anyCompleted = true;
anyCompleted = true; if (flag.completer)
flag.completer(n, *prefix); flag.completer(n, *prefix);
} }
args.push_back(*pos++); args.push_back(*pos++);
} }
if (!anyCompleted) if (!anyCompleted)
@ -146,6 +146,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
&& hasPrefix(name, std::string(*prefix, 2))) && hasPrefix(name, std::string(*prefix, 2)))
completions->add("--" + name, flag->description); completions->add("--" + name, flag->description);
} }
return false;
} }
auto i = longFlags.find(std::string(*pos, 2)); auto i = longFlags.find(std::string(*pos, 2));
if (i == longFlags.end()) return false; if (i == longFlags.end()) return false;
@ -187,10 +188,12 @@ bool Args::processArgs(const Strings & args, bool finish)
{ {
std::vector<std::string> ss; std::vector<std::string> ss;
for (const auto &[n, s] : enumerate(args)) { for (const auto &[n, s] : enumerate(args)) {
ss.push_back(s); if (auto prefix = needsCompletion(s)) {
if (exp.completer) ss.push_back(*prefix);
if (auto prefix = needsCompletion(s)) if (exp.completer)
exp.completer(n, *prefix); exp.completer(n, *prefix);
} else
ss.push_back(s);
} }
exp.handler.fun(ss); exp.handler.fun(ss);
expectedArgs.pop_front(); expectedArgs.pop_front();
@ -279,21 +282,22 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
{ {
completionType = ctFilenames; completionType = ctFilenames;
glob_t globbuf; glob_t globbuf;
int flags = GLOB_NOESCAPE | GLOB_TILDE; int flags = GLOB_NOESCAPE;
#ifdef GLOB_ONLYDIR #ifdef GLOB_ONLYDIR
if (onlyDirs) if (onlyDirs)
flags |= GLOB_ONLYDIR; flags |= GLOB_ONLYDIR;
#endif #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) { for (size_t i = 0; i < globbuf.gl_pathc; ++i) {
if (onlyDirs) { if (onlyDirs) {
auto st = lstat(globbuf.gl_pathv[i]); auto st = stat(globbuf.gl_pathv[i]);
if (!S_ISDIR(st.st_mode)) continue; if (!S_ISDIR(st.st_mode)) continue;
} }
completions->add(globbuf.gl_pathv[i]); completions->add(globbuf.gl_pathv[i]);
} }
globfree(&globbuf);
} }
globfree(&globbuf);
} }
void completePath(size_t, std::string_view prefix) void completePath(size_t, std::string_view prefix)
@ -322,11 +326,6 @@ MultiCommand::MultiCommand(const Commands & commands_)
.optional = true, .optional = true,
.handler = {[=](std::string s) { .handler = {[=](std::string s) {
assert(!command); assert(!command);
if (auto prefix = needsCompletion(s)) {
for (auto & [name, command] : commands)
if (hasPrefix(name, *prefix))
completions->add(name);
}
auto i = commands.find(s); auto i = commands.find(s);
if (i == commands.end()) { if (i == commands.end()) {
std::set<std::string> commandNames; std::set<std::string> commandNames;
@ -337,6 +336,11 @@ MultiCommand::MultiCommand(const Commands & commands_)
} }
command = {s, i->second()}; command = {s, i->second()};
command->second->parent = this; command->second->parent = this;
}},
.completer = {[&](size_t, std::string_view prefix) {
for (auto & [name, command] : commands)
if (hasPrefix(name, prefix))
completions->add(name);
}} }}
}); });

View file

@ -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) bool isInDir(std::string_view path, std::string_view dir)
{ {
return path.substr(0, 1) == "/" 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 lstat(const Path & path)
{ {
struct stat st; struct stat st;

View file

@ -68,6 +68,9 @@ Path dirOf(const PathView path);
following the final `/' (trailing slashes are removed). */ following the final `/' (trailing slashes are removed). */
std::string_view baseNameOf(std::string_view path); 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 /* Check whether 'path' is a descendant of 'dir'. Both paths must be
canonicalized. */ canonicalized. */
bool isInDir(std::string_view path, std::string_view dir); 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); bool isDirOrInDir(std::string_view path, std::string_view dir);
/* Get status of `path'. */ /* Get status of `path'. */
struct stat stat(const Path & path);
struct stat lstat(const Path & path); struct stat lstat(const Path & path);
/* Return true iff the given path exists. */ /* Return true iff the given path exists. */

View file

@ -35,7 +35,7 @@ struct InstallableDerivedPath : Installable
/** /**
* Return the rewrites that are needed to resolve a string whose context is * 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) 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) 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) UnresolvedApp Installable::toApp(EvalState & state)
{ {
auto [cursor, attrPath] = getCursor(state); auto cursor = getCursor(state);
auto attrPath = cursor->getAttrPath();
auto type = cursor->getAttr("type")->getString(); 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") { if (type == "app") {
auto [program, context] = cursor->getAttr("program")->getStringWithContext(); auto [program, context] = cursor->getAttr("program")->getStringWithContext();
std::vector<StorePathWithOutputs> context2; std::vector<StorePathWithOutputs> context2;
for (auto & [path, name] : context) for (auto & [path, name] : context)
context2.push_back({path, {name}}); context2.push_back({path, {name}});
@ -101,7 +105,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
} }
else 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 // FIXME: move to libcmd

View file

@ -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") { else if (name == "packages" || name == "devShells") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos);
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
@ -705,7 +715,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
defaultTemplateAttrPathsPrefixes, defaultTemplateAttrPathsPrefixes,
lockFlags); lockFlags);
auto [cursor, attrPath] = installable.getCursor(*evalState); auto cursor = installable.getCursor(*evalState);
auto templateDirAttr = cursor->getAttr("path"); auto templateDirAttr = cursor->getAttr("path");
auto templateDir = templateDirAttr->getString(); auto templateDir = templateDirAttr->getString();
@ -1011,6 +1021,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|| (attrPath.size() == 1 && ( || (attrPath.size() == 1 && (
attrPath[0] == "defaultPackage" attrPath[0] == "defaultPackage"
|| attrPath[0] == "devShell" || attrPath[0] == "devShell"
|| attrPath[0] == "formatter"
|| attrPath[0] == "nixosConfigurations" || attrPath[0] == "nixosConfigurations"
|| attrPath[0] == "nixosModules" || attrPath[0] == "nixosModules"
|| attrPath[0] == "defaultApp" || attrPath[0] == "defaultApp"
@ -1027,7 +1038,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
} }
else if ( 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")) || (attrPath.size() == 3 && (attrPath[0] == "checks" || attrPath[0] == "packages" || attrPath[0] == "devShells"))
) )
{ {

View file

@ -177,8 +177,8 @@ Currently the `type` attribute can be one of the following:
attribute `url`. attribute `url`.
In URL form, the schema must be `http://`, `https://` or `file://` In URL form, the schema must be `http://`, `https://` or `file://`
URLs and the extension must be `.zip`, `.tar`, `.tar.gz`, `.tar.xz`, URLs and the extension must be `.zip`, `.tar`, `.tgz`, `.tar.gz`,
`.tar.bz2` or `.tar.zst`. `.tar.xz`, `.tar.bz2` or `.tar.zst`.
* `github`: A more efficient way to fetch repositories from * `github`: A more efficient way to fetch repositories from
GitHub. The following attributes are required: GitHub. The following attributes are required:

53
src/nix/fmt.cc Normal file
View file

@ -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");

53
src/nix/fmt.md Normal file
View file

@ -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.
)""

View file

@ -165,8 +165,8 @@ struct CmdSearch : InstallableCommand, MixJSON
} }
}; };
for (auto & [cursor, prefix] : installable->getCursors(*state)) for (auto & cursor : installable->getCursors(*state))
visit(*cursor, parseAttrPath(*state, prefix), true); visit(*cursor, cursor->getAttrPath(), true);
if (!json && !results) if (!json && !results)
throw Error("no results for the given search term(s)!"); throw Error("no results for the given search term(s)!");

29
tests/flakes-run.sh Normal file
View file

@ -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' shouldnt accept an 'app' defined under 'packages'"
! nix run --no-write-lock-file .#appAsPkg || fail "elements of 'apps' should be of type 'app'"
clearStore

30
tests/fmt.sh Normal file
View file

@ -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

1
tests/fmt.simple.sh Executable file
View file

@ -0,0 +1 @@
echo Formatting: "${@}"

View file

@ -1,5 +1,6 @@
nix_tests = \ nix_tests = \
flakes.sh \ flakes.sh \
flakes-run.sh \
ca/gc.sh \ ca/gc.sh \
gc.sh \ gc.sh \
remote-store.sh \ remote-store.sh \
@ -79,6 +80,7 @@ nix_tests = \
post-hook.sh \ post-hook.sh \
function-trace.sh \ function-trace.sh \
flake-local-settings.sh \ flake-local-settings.sh \
fmt.sh \
eval-store.sh \ eval-store.sh \
why-depends.sh \ why-depends.sh \
import-derivation.sh \ import-derivation.sh \