diff --git a/src/libcmd/installable-flake.cc b/src/libcmd/installable-flake.cc index 615f70945..5e7e42e63 100644 --- a/src/libcmd/installable-flake.cc +++ b/src/libcmd/installable-flake.cc @@ -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 InstallableFlake::getActualAttrPaths() { std::vector 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 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> diff --git a/src/libcmd/installable-flake.hh b/src/libcmd/installable-flake.hh index 314918c14..d114870fc 100644 --- a/src/libcmd/installable-flake.hh +++ b/src/libcmd/installable-flake.hh @@ -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. diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 7b5d8096a..ee31009e6 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -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); + } } }; diff --git a/src/nix/search.cc b/src/nix/search.cc index 97ef1375e..4f0c9a75f 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -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()) { + 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());