-
Notifications
You must be signed in to change notification settings - Fork 3
nix build, nix profile: Report failing/succeeding installables #281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This denotes the result of a build that didn't succeed or fail, but was cancelled because some other goal failed and --keep-going was not enabled.
WalkthroughThis PR replaces pair-based build results with a variant-wrapped Changes
Sequence Diagram(s)sequenceDiagram
%% Styling: subtle green for success, red for failure
participant Caller as Caller
participant Build2 as build2()
participant ThrowErr as throwBuildErrors()
participant Extract as getSuccess()
participant Final as FinalResults
Caller->>Build2: invoke build2(...)
Build2-->>Caller: return [InstallableWithBuildResult{Success|Failure}, ...]
Caller->>ThrowErr: throwBuildErrors(buildResults, store)
alt any Failure present
ThrowErr->>Caller: throws aggregated/first relevant error (may log cancelled)
else all Success
ThrowErr-->>Caller: returns normally
end
Caller->>Extract: for each InstallableWithBuildResult call getSuccess()
alt variant is Success
Extract-->>Caller: returns BuiltPathWithResult
else variant is Failure
Extract->>Caller: rethrows stored failure
end
Caller-->>Final: vector<BuiltPathWithResult>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/libcmd/include/nix/cmd/installables.hh (1)
99-113: Clean wrapper type for build results per installable.The
InstallableWithBuildResultstruct provides a well-structured way to associate build outcomes with their installables. The type aliases improve readability.One minor observation: the comment on line 105 states that
Failure"must be aBuildResult::Failure", but the type alias is justBuildResult(which contains a variant). Consider whether the type could be tightened toBuildResult::Failuredirectly, or if the fullBuildResultis needed for additional metadata (like timing info). The current approach works but relies on the caller to uphold the invariant.src/libcmd/installables.cc (1)
594-602: Consider usingstd::getfor clarity.Since the variant can only hold
SuccessorFailure, usingstd::get<Success>(result)would be clearer and provide a runtime check, avoiding the nullable pointer dereference pattern.const BuiltPathWithResult & InstallableWithBuildResult::getSuccess() const { if (auto * failure = std::get_if<Failure>(&result)) { auto failure2 = failure->tryGetFailure(); assert(failure2); failure2->rethrow(); - } else - return *std::get_if<Success>(&result); + } + return std::get<Success>(result); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/libcmd/include/nix/cmd/installables.hh(2 hunks)src/libcmd/installables.cc(6 hunks)src/libstore/build-result.cc(4 hunks)src/libstore/include/nix/store/build-result.hh(3 hunks)src/libstore/include/nix/store/build/goal.hh(1 hunks)src/nix/profile.cc(4 hunks)tests/functional/build.sh(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/nix/profile.cc (2)
src/libcmd/include/nix/cmd/installables.hh (2)
buildResults(186-186)store(198-198)src/libcmd/installables.cc (4)
build2(652-763)build2(652-653)throwBuildErrors(604-639)throwBuildErrors(604-604)
src/libstore/build-result.cc (1)
src/libstore/include/nix/store/build-result.hh (4)
status(34-34)status(45-49)status(45-45)status(83-83)
src/libstore/include/nix/store/build-result.hh (1)
src/libstore/build-result.cc (4)
statusToString(16-30)statusToString(16-16)statusToString(32-64)statusToString(32-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build_x86_64-linux / build
- GitHub Check: build_aarch64-darwin / build
🔇 Additional comments (18)
src/libstore/include/nix/store/build/goal.hh (1)
112-112: LGTM! Sensible default for build result.Initializing
buildResulttoCancelledensures that goals that never complete (or are interrupted) have a well-defined failed state. This aligns with the new per-installable reporting where cancelled builds show ❓.src/libstore/include/nix/store/build-result.hh (4)
34-35: LGTM! Clean static helper declaration.The
statusToStringhelper provides a well-typed way to convert status codes to strings, improving API clarity over a free function.
83-84: LGTM! Symmetric withSuccess::statusToString.Provides consistent API for status-to-string conversion across both result types.
106-106: Good fallback for empty error messages.Using
statusToString(status)whenerrorMsgis empty ensuresrethrow()always provides a meaningful error message rather than throwing an empty one.
80-80: Based on my verification, I've found important evidence about the review comment:Key Findings:
HashMismatch (value 15) in
src/libstore/include/nix/store/build-result.hhhas an explicit 3-line comment explaining:
- "The protocols do not yet know about this one"
- "change it back to
OutputRejectedbefore serialization"Serialization Fallback in
src/libstore/build/derivation-building-goal.cc(lines 843-850):
- When
BuildResult::Failure::HashMismatchis encountered, it's unconditionally converted toOutputRejected- The comment states: "the protocols don't know about
HashMismatchyet"Cancelled (value 16) has:
- NO explanatory comment like HashMismatch
- NO corresponding conversion/fallback in the switch statement
- NOT handled in the serialization code
Pattern Observed:
- The switch statement at lines 843-857 handles specific statuses that need adjustment
- Cancelled is conspicuously absent from this handling
- The default case says "Other statuses need no adjusting"
The review comment's concern appears valid and substantiated. The question of whether
Cancelledneeds similar protocol compatibility handling toHashMismatchcannot be definitively answered without understanding: (1) whetherCancelledis sent to older protocol versions, and (2) whether those older versions recognize it. However, the evidence suggests this warrants investigation or clarification.
Verify protocol compatibility for
Cancelledstatus.
Cancelled = 16lacks the protocol compatibility documentation and serialization fallback thatHashMismatch = 15explicitly requires.HashMismatch(lines 76-78) is documented as needing conversion toOutputRejectedbefore serialization because older protocols don't recognize it.Cancelledhas no such comment or fallback in the switch statement atsrc/libstore/build/derivation-building-goal.cc:843-857. IfCancelledcan be sent to older protocol versions, it should receive equivalent treatment.tests/functional/build.sh (2)
170-174: LGTM! Correct error count for keep-going with 3 failing FODs.With
--keep-going, all three FODs (x1, x2, x3) are attempted and fail independently, so expecting 3 errors is correct. The individual hash mismatch assertions properly validate each failure is reported.
164-166: Based on my analysis of the code:The test expectation of 3 cancellations is correct.
The
fod-failing.nixfile defines 4 derivations:
- x1, x2, x3: Fixed-output derivations (FODs) with identical invalid hashes
- x4: A regular derivation that depends on x2 and x3
When
nix build -f fod-failing.nixis run without explicit targets (line 161), all top-level attributes are attempted:
- One FOD (typically x1) fails first with a hash mismatch error → generates 1 error message
- The remaining three derivations (x2, x3, x4) are cancelled as a result → 3 cancellations
This aligns with the modern error reporting behavior where:
- Each installable build attempt generates one error entry
- Dependent builds are cancelled and counted separately
- The test correctly validates this by checking for exactly 1 error and 3 cancellations
The test structure is sound and the assertion is accurate.
src/libstore/build-result.cc (3)
16-30: LGTM! Clean implementation with exhaustive switch.The
statusToStringimplementation correctly covers allSuccess::Statusenum values withunreachable()as a defensive default.
32-64: LGTM! Exhaustive handling of all failure statuses including newCancelled.All
Failure::Statusenum values are handled, with the newCancelledcase properly returning"Cancelled".
71-73: LGTM! JSON serialization updated to use the new member functions.Using the nested
statusToStringhelpers is cleaner than free functions and provides better API discoverability.src/libcmd/include/nix/cmd/installables.hh (2)
179-184: LGTM! Improved return type forbuild2.Returning
std::vector<InstallableWithBuildResult>instead ofstd::vector<std::pair<ref<Installable>, BuiltPathWithResult>>is a cleaner API that can now represent both successes and failures per installable.
186-187: LGTM! NewthrowBuildErrorsdeclaration.This provides a clean separation between collecting build results and reporting errors. Taking a non-const reference suggests it may consume or modify the results, which should be documented in the implementation.
src/nix/profile.cc (3)
315-335: LGTM!The function correctly adapts to the new
InstallableWithBuildResulttype. The call tob.getSuccess()is safe here becausethrowBuildErrorsis always invoked before this function at both call sites, ensuring only successful results remain when this code executes.
367-370: LGTM!The two-phase flow (build → throw errors → extract paths) is clean and makes error handling explicit. This ensures users see proper success/failure reporting before the profile is updated.
772-775: LGTM!Consistent with the
CmdProfileAddchanges, applying the same two-phase build result handling pattern.src/libcmd/installables.cc (3)
24-24: LGTM!Required include for the
Exitexception used inthrowBuildErrors.
641-650: LGTM!Clean wrapper that maintains the simpler
build()API while delegating tobuild2()internally. The error throwing before extraction ensuresgetSuccess()is safe.
652-763: LGTM!The refactored
build2()cleanly separates success and failure handling using the newInstallableWithBuildResultwrapper. Failures are detected early and stored directly (lines 718-723), while successes are properly wrapped with path information.
If any installable fails to build, it will print something like
✅ flake:nixpkgs#hello
❌ git+file:///home/eelco/Dev/patchelf#packages.x86_64-linux.default
error: Cannot build '/nix/store/nh2dx9cqcy9lw4d4rvd0dbsflwdsbzdy-patchelf-0.18.0.drv'.
Reason: builder failed with exit code 2.
...
❓ git+file:///home/eelco/Dev/patchelf#hydraJobs.coverage (cancelled)
It doesn't print anything if all installables succeed.
aef7257 to
ee75a66
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/libcmd/installables.cc (2)
604-639: Well-structured error reporting with clear status differentiation.The method properly reports successes first, then failures, and distinguishes between actual failures (❌) and cancelled builds (❓). The triple iteration through
buildResultsis clear and maintainable, though it could be consolidated into a single pass if performance becomes a concern.Note: The method only prints per-installable status when at least one failure exists, which appears intentional to avoid noise when all builds succeed.
If you'd like to optimize the multiple passes through
buildResults, consider collecting the messages in one pass:void Installable::throwBuildErrors(std::vector<InstallableWithBuildResult> & buildResults, const Store & store) { + std::vector<std::string> successMsgs, failureMsgs; + bool hasFailures = false; + for (auto & buildResult : buildResults) { - if (std::get_if<InstallableWithBuildResult::Failure>(&buildResult.result)) { - // Report success first. - for (auto & buildResult : buildResults) { - if (std::get_if<InstallableWithBuildResult::Success>(&buildResult.result)) - notice("✅ " ANSI_BOLD "%s" ANSI_NORMAL, buildResult.installable->what()); - } - - // Then failures. - for (auto & buildResult : buildResults) { - if (auto failure = std::get_if<InstallableWithBuildResult::Failure>(&buildResult.result)) { - auto failure2 = failure->tryGetFailure(); - assert(failure2); - if (failure2->status == BuildResult::Failure::Cancelled - // FIXME: remove MiscFailure eventually - || failure2->status == BuildResult::Failure::MiscFailure) - notice( - "❓ " ANSI_BOLD "%s" ANSI_NORMAL ANSI_FAINT " (cancelled)", - buildResult.installable->what()); - else { - printError("❌ " ANSI_RED "%s" ANSI_NORMAL, buildResult.installable->what()); - try { - failure2->rethrow(); - } catch (Error & e) { - logError(e.info()); - } - } - } - } - - throw Exit(1); + if (std::get_if<InstallableWithBuildResult::Success>(&buildResult.result)) { + successMsgs.push_back(buildResult.installable->what()); + } else if (auto failure = std::get_if<InstallableWithBuildResult::Failure>(&buildResult.result)) { + hasFailures = true; + // Store failure details... } } + + if (hasFailures) { + // Print all collected messages + // ... + throw Exit(1); + } }
652-763: Solid implementation of variant-based build result flow.The method correctly constructs
InstallableWithBuildResultentries for all build paths, properly handling both success and failure cases across different realization modes. The distinction between modes that include actual build results (Outputs) versus those that just resolve paths (Nothing/Derivation) is clear.There's some code duplication in constructing Success results for
DerivedPath::BuiltvsDerivedPath::Opaquecases across different modes (lines 686-697 vs 700-703, and lines 728-742 vs 744-748). Consider extracting a helper function to reduce duplication:auto makeSuccessResult = [&](const BuiltPath & path, const Aux & aux, std::optional<BuiltPathWithResult> buildResult = std::nullopt) { return InstallableWithBuildResult{ .installable = aux.installable, .result = InstallableWithBuildResult::Success{ .path = path, .info = aux.info, .result = buildResult } }; };This would make the code more maintainable if the Success structure changes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/libcmd/include/nix/cmd/installables.hh(2 hunks)src/libcmd/installables.cc(6 hunks)src/nix/profile.cc(4 hunks)tests/functional/build.sh(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/nix/profile.cc
- src/libcmd/include/nix/cmd/installables.hh
- tests/functional/build.sh
🧰 Additional context used
🧬 Code graph analysis (1)
src/libcmd/installables.cc (1)
src/libcmd/include/nix/cmd/installables.hh (7)
buildResults(186-186)evalStore(172-177)evalStore(179-184)evalStore(188-189)evalStore(191-192)evalStore(194-195)evalStore(200-201)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build_aarch64-darwin / build
- GitHub Check: build_x86_64-linux / build
🔇 Additional comments (3)
src/libcmd/installables.cc (3)
24-24: LGTM - Required for new error handling.The new include is necessary for the
Exitexception thrown inthrowBuildErrors.
594-602: LGTM - Proper variant handling and error propagation.The method correctly extracts the success result or rethrows the stored failure. The assert ensures the failure state is valid before rethrowing.
641-650: LGTM - Clean delegation and error handling.The method properly delegates to
build2(), centralizes error handling viathrowBuildErrors(), and converts results by extracting successful paths. The pattern ensures that callers either get all successful results or an exception.
| .level = lvlError, | ||
| .msg = failedResult->second->errorMsg, | ||
| }); | ||
| // Then failures. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to instead make the order be:
- Success
- Cancelled
- Failure
(instead of Success -> interleaved Cancelled/Failure)
so that failure messages require much less scrolling up to see which thing failed? With a contrived example, I build a lot of trivial derivations, made only the first one fail, and that gave me something like:
❓ path:/tmp/tmp.ozLDnPJna5#trivial-138 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-139 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-140 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-141 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-142 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-143 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-144 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-145 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-146 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-147 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-148 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-149 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-150 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-151 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-152 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-153 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-154 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-155 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-156 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-157 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-158 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-159 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-160 (cancelled)
with the error message much higher up:
❌ path:/tmp/tmp.ozLDnPJna5#trivial-1
error: Cannot build '/nix/store/1sqqk6fp8c9gyf89l1i96g3fgdpmf5r4-trivial-1-5.drv'.
Reason: builder failed with exit code 1.
Output paths:
/nix/store/0vi2yf4wkw756ijl6fq1m8wdmcqq68r3-trivial-1-5
Last 6 log lines:
> Running phase: patchPhase
> Running phase: updateAutotoolsGnuConfigScriptsPhase
> Running phase: configurePhase
> no configure script, doing nothing
> Running phase: buildPhase
> Running phase: installPhase
For full logs, run:
nix log /nix/store/1sqqk6fp8c9gyf89l1i96g3fgdpmf5r4-trivial-1-5.drv
❓ path:/tmp/tmp.ozLDnPJna5#trivial-2 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-3 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-4 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-5 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-6 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-7 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-8 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-9 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-10 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-11 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-12 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-13 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-14 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-15 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-57 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-58 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-59 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-60 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-61 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-62 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-63 (cancelled)
❓ path:/tmp/tmp.ozLDnPJna5#trivial-64 (cancelled)
| # --keep-going and FOD | ||
| out="$(nix build -f fod-failing.nix -L 2>&1)" && status=0 || status=$? | ||
| test "$status" = 1 | ||
| # one "hash mismatch" error, one "build of ... failed" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: comment is wrong now -- one hash mismatch error, 3 cancelled errors
Motivation
If any installable fails to build in
nix buildornix profile install/upgrade, Nix will print something likeIt doesn't print anything if all installables succeed.
Context
Add 👍 to pull requests you find important.
The Nix maintainer team uses a GitHub project board to schedule and track reviews.
Summary by CodeRabbit
Bug Fixes
Chores
Tests
✏️ Tip: You can customize this high-level summary in your review settings.