WIP: fix flake file not found for untracked files in git

Still WIP, because the installables code is disasteriously duplicated.
So far this implements for:

- nix build
- nix search
- nix flake show

Change-Id: I3196720c2a458b7dab8d969ae1b04b3a7d29869b
This commit is contained in:
Qyriad 2024-04-28 05:43:47 -06:00
parent 3b43fabe97
commit 22759fa6b2
4 changed files with 102 additions and 4 deletions

View file

@ -1,6 +1,7 @@
#include "globals.hh"
#include "installable-flake.hh"
#include "installable-derived-path.hh"
#include "logging.hh"
#include "outputs-spec.hh"
#include "util.hh"
#include "command.hh"
@ -25,6 +26,61 @@
namespace nix {
[[nodiscard]] FileError handleGitTrackError(
FileError && fileError,
FlakeRef const & flakeRef,
flake::LockedFlake const & lockedFlake
)
{
// TODO(Qyriad): do the same thing for other supported VCSes
auto const isGit = flakeRef.input.getType() == "git";
bool const isNotFound = fileError.errNo == ENOENT;
bool const unhelpfulSource =
!flakeRef.input.getSourcePath() || !lockedFlake.flake.sourceInfo;
if (!isGit || !isNotFound || unhelpfulSource || fileError.referencedPaths.size() != 1) {
// If this flake isn't a Git flake or doesn't have a source path,
// or if this error is something other than ENOENT, *or* if ENOENT
// is about more than one path (or if we have no path information at all),
// then we don't have any helpful hints to add. Rethrow.
throw;
}
// Otherwise, get the path of the missing file as relative to the flake source tree,
// instead of the Nix store.
// The full path that got ENOENT'd.
auto const missingStorePath = fileError.referencedPaths.front();
// Directory of the original flake.nix (outside the Nix store).
auto const flakeSourceDir = *flakeRef.input.getSourcePath();
// Nix store directory containing the fetched flake.
// to_string() returns the path without the /nix/store part.
auto const flakeStoreDir = lockedFlake.flake.sourceInfo->storePath.to_string();
if (missingStorePath.find(flakeStoreDir) == missingStorePath.npos) {
// The mising path isn't in the flake source tree. We have no helpful
// information to offer.
throw;
}
// Strip the root path in the store (e.g. c8kjkvxfq9pfwd4832ishyl2mpw4l58y-source).
auto const amountToStrip = missingStorePath.find(flakeStoreDir) + flakeStoreDir.size();
auto const origSrcPath = flakeSourceDir + missingStorePath.substr(amountToStrip);
if (!pathExists(origSrcPath)) {
throw;
}
ErrorInfo existingInfo(std::move(fileError.info()));
existingInfo.msg = HintFmt(
"%1%\nnote: '%2%' found in original source; did you run git add?",
Uncolored(existingInfo.msg),
origSrcPath
);
auto withTip = FileError(fileError.errNo, std::move(fileError.referencedPaths), "");
withTip.err = existingInfo;
return withTip;
}
std::vector<std::string> InstallableFlake::getActualAttrPaths()
{
std::vector<std::string> res;
@ -94,6 +150,15 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
auto attrPath = attr->getAttrPathStr();
try {
// Evaluate here, instead of as a side effect of isDerivation(),
// for clarity of error handling.
attr->forceValue();
} catch (FileError & e) {
e.addTrace({}, "in source tree referenced by flake %s", flakeRef);
throw handleGitTrackError(std::move(e), flakeRef, *_lockedFlake);
}
if (!attr->isDerivation()) {
// FIXME: use eval cache?
@ -162,7 +227,12 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {&getCursor(state)->forceValue(), noPos};
try {
return {&getCursor(state)->forceValue(), noPos};
} catch (FileError & e) {
e.addTrace({}, "in source tree referenced by flake %s", flakeRef);
throw handleGitTrackError(std::move(e), flakeRef, *_lockedFlake);
}
}
std::vector<ref<eval_cache::AttrCursor>>

View file

@ -5,6 +5,12 @@
namespace nix {
[[nodiscard]] FileError handleGitTrackError(
FileError && fileError,
FlakeRef const & flakeRef,
flake::LockedFlake const & lockedFlake
);
/**
* Extra info about a \ref DerivedPath "derived path" that ultimately
* come from a Flake.

View file

@ -1357,9 +1357,14 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
auto cache = openEvalCache(*state, flake);
auto j = visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
if (json)
logger->cout("%s", j.dump());
try {
auto j = visit(*cache->getRoot(), {}, fmt(ANSI_BOLD "%s" ANSI_NORMAL, flake->flake.lockedRef), "");
if (json) {
logger->cout("%s", j.dump());
}
} catch (FileError & e) {
throw handleGitTrackError(std::move(e), flake->flake.originalRef, *flake);
}
}
};

View file

@ -3,6 +3,7 @@
#include "eval.hh"
#include "eval-inline.hh"
#include "eval-settings.hh"
#include "installable-flake.hh"
#include "names.hh"
#include "get-drvs.hh"
#include "common-args.hh"
@ -108,6 +109,22 @@ struct CmdSearch : InstallableValueCommand, MixJSON
}
};
// If this is a flake, let's try to be helpful and add a tip if eval
// ends up tripping over an untracked file in git.
if (auto instFlake = installable.dynamic_pointer_cast<InstallableFlake>()) {
try {
// nb: isDerivation() below also does this as a side effect.
cursor.forceValue();
} catch (FileError & e) {
e.addTrace({}, "in source tree referenced by flake %s", instFlake->flakeRef);
throw handleGitTrackError(
std::move(e),
instFlake->flakeRef,
*instFlake->_lockedFlake
);
}
}
if (cursor.isDerivation()) {
DrvName name(cursor.getAttr(state->sName)->getString());