1818#include " nix/store/local-fs-store.hh"
1919#include " nix/store/globals.hh"
2020#include " nix/expr/parallel-eval.hh"
21+ #include " nix/util/exit.hh"
2122
2223#include < filesystem>
2324#include < nlohmann/json.hpp>
@@ -385,7 +386,9 @@ struct CmdFlakeCheck : FlakeCommand
385386 }
386387 };
387388
388- Sync<StringSet> omittedSystems;
389+ Sync<std::vector<DerivedPath>> drvPaths_;
390+ Sync<std::set<std::string>> omittedSystems;
391+ Sync<std::map<DerivedPath, std::vector<eval_cache::AttrPath>>> derivedPathToAttrPaths_;
389392
390393 // FIXME: rewrite to use EvalCache.
391394
@@ -434,8 +437,6 @@ struct CmdFlakeCheck : FlakeCommand
434437 return std::nullopt ;
435438 };
436439
437- std::vector<DerivedPath> drvPaths;
438-
439440 FutureVector futures (*state->executor );
440441
441442 auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
@@ -633,11 +634,13 @@ struct CmdFlakeCheck : FlakeCommand
633634 *attr2.value ,
634635 attr2.pos );
635636 if (drvPath && attr_name == settings.thisSystem .get ()) {
636- auto path = DerivedPath::Built{
637+ auto derivedPath = DerivedPath::Built{
637638 .drvPath = makeConstantStorePathRef (*drvPath),
638639 .outputs = OutputsSpec::All{},
639640 };
640- drvPaths.push_back (std::move (path));
641+ (*derivedPathToAttrPaths_.lock ())[derivedPath].push_back (
642+ {state->symbols .create (" checks" ), attr.name , attr2.name });
643+ drvPaths_.lock ()->push_back (std::move (derivedPath));
641644 }
642645 }
643646 }
@@ -806,7 +809,10 @@ struct CmdFlakeCheck : FlakeCommand
806809 futures.spawn (1 , checkFlake);
807810 futures.finishAll ();
808811
809- if (build && !drvPaths.empty ()) {
812+ auto drvPaths (drvPaths_.lock ());
813+ auto derivedPathToAttrPaths (derivedPathToAttrPaths_.lock ());
814+
815+ if (build && !drvPaths->empty ()) {
810816 // TODO: This filtering of substitutable paths is a temporary workaround until
811817 // https://github.com/NixOS/nix/issues/5025 (union stores) is implemented.
812818 //
@@ -819,32 +825,68 @@ struct CmdFlakeCheck : FlakeCommand
819825 // via substitution, as `nix flake check` only needs to verify buildability,
820826 // not actually produce the outputs.
821827 state->waitForAllPaths ();
822- auto missing = store->queryMissing (drvPaths);
828+ auto missing = store->queryMissing (* drvPaths);
823829
824830 std::vector<DerivedPath> toBuild;
831+ std::set<DerivedPath> toBuildSet;
825832 for (auto & path : missing.willBuild ) {
826- toBuild.emplace_back (
827- DerivedPath::Built{
828- .drvPath = makeConstantStorePathRef (path),
829- .outputs = OutputsSpec::All{},
830- });
833+ auto derivedPath = DerivedPath::Built{
834+ .drvPath = makeConstantStorePathRef (path),
835+ .outputs = OutputsSpec::All{},
836+ };
837+ toBuild.emplace_back (derivedPath);
838+ toBuildSet.insert (std::move (derivedPath));
831839 }
832840
841+ for (auto & [derivedPath, attrPaths] : *derivedPathToAttrPaths)
842+ if (!toBuildSet.contains (derivedPath))
843+ for (auto & attrPath : attrPaths)
844+ notice (
845+ " ✅ " ANSI_BOLD " %s" ANSI_NORMAL ANSI_ITALIC ANSI_FAINT " (previously built)" ANSI_NORMAL,
846+ eval_cache::toAttrPathStr (*state, attrPath));
847+
833848 // FIXME: should start building while evaluating.
834849 Activity act (*logger, lvlInfo, actUnknown, fmt (" running %d flake checks" , toBuild.size ()));
835- store->buildPaths (toBuild);
850+ auto buildResults = store->buildPathsWithResults (toBuild);
851+ assert (buildResults.size () == toBuild.size ());
852+
853+ // Report successes first.
854+ for (auto & buildResult : buildResults)
855+ if (buildResult.tryGetSuccess ())
856+ for (auto & attrPath : (*derivedPathToAttrPaths)[buildResult.path ])
857+ notice (" ✅ " ANSI_BOLD " %s" ANSI_NORMAL, eval_cache::toAttrPathStr (*state, attrPath));
858+
859+ // Then cancelled builds.
860+ for (auto & buildResult : buildResults)
861+ if (buildResult.isCancelled ())
862+ for (auto & attrPath : (*derivedPathToAttrPaths)[buildResult.path ])
863+ notice (
864+ " ❓ " ANSI_BOLD " %s" ANSI_NORMAL ANSI_FAINT " (cancelled)" ,
865+ eval_cache::toAttrPathStr (*state, attrPath));
866+
867+ // Then failures.
868+ for (auto & buildResult : buildResults)
869+ if (auto failure = buildResult.tryGetFailure (); failure && !buildResult.isCancelled ())
870+ try {
871+ hasErrors = true ;
872+ for (auto & attrPath : (*derivedPathToAttrPaths)[buildResult.path ])
873+ printError (" ❌ " ANSI_RED " %s" ANSI_NORMAL, eval_cache::toAttrPathStr (*state, attrPath));
874+ failure->rethrow ();
875+ } catch (Error & e) {
876+ logError (e.info ());
877+ }
836878 }
837879
838- if (hasErrors)
839- throw Error (" some errors were encountered during the evaluation" );
840-
841880 if (!omittedSystems.lock ()->empty ()) {
842881 // TODO: empty system is not visible; render all as nix strings?
843882 warn (
844883 " The check omitted these incompatible systems: %s\n "
845884 " Use '--all-systems' to check all." ,
846885 concatStringsSep (" , " , *omittedSystems.lock ()));
847- };
886+ }
887+
888+ if (hasErrors)
889+ throw Exit (1 );
848890 };
849891};
850892
0 commit comments