Skip to content

Commit 6fa0376

Browse files
committed
C API: Propagate nix_store_realise build errors
1 parent 12293a8 commit 6fa0376

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

src/libstore-c/nix_api_store.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ nix_err nix_store_realise(
173173
const auto nixStore = store->ptr;
174174
auto results = nixStore->buildPathsWithResults(paths, nix::bmNormal, nixStore);
175175

176+
assert(results.size() == 1);
177+
178+
// Check if any builds failed
179+
for (auto & result : results) {
180+
if (!result.success())
181+
result.rethrow();
182+
}
183+
176184
if (callback) {
177185
for (const auto & result : results) {
178186
for (const auto & [outputName, realisation] : result.builtOutputs) {

src/libstore-c/nix_api_store.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ nix_err nix_store_real_path(
186186
* @param[in] path Path to build
187187
* @param[in] userdata data to pass to every callback invocation
188188
* @param[in] callback called for every realised output
189+
* @return NIX_OK if the build succeeded, or an error code if the build/scheduling/outputs/copying/etc failed.
190+
* On error, the callback is never invoked and error information is stored in context.
189191
*/
190192
nix_err nix_store_realise(
191193
nix_c_context * context,

src/libstore-tests/nix_api_store.cc

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,141 @@ TEST_F(nix_api_store_test_base, build_from_json)
329329
nix_store_free(store);
330330
}
331331

332+
TEST_F(nix_api_store_test_base, nix_store_realise_invalid_system)
333+
{
334+
// Test that nix_store_realise properly reports errors when the system is invalid
335+
nix::experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations");
336+
nix::settings.substituters = {};
337+
338+
auto * store = open_local_store();
339+
340+
std::filesystem::path unitTestData{getenv("_NIX_TEST_UNIT_DATA")};
341+
std::ifstream t{unitTestData / "derivation/ca/self-contained.json"};
342+
std::stringstream buffer;
343+
buffer << t.rdbuf();
344+
345+
// Use an invalid system that cannot be built
346+
std::string jsonStr = nix::replaceStrings(buffer.str(), "x86_64-linux", "bogus65-bogusos");
347+
348+
auto * drv = nix_derivation_from_json(ctx, store, jsonStr.c_str());
349+
assert_ctx_ok();
350+
ASSERT_NE(drv, nullptr);
351+
352+
auto * drvPath = nix_add_derivation(ctx, store, drv);
353+
assert_ctx_ok();
354+
ASSERT_NE(drvPath, nullptr);
355+
356+
int callbackCount = 0;
357+
auto cb = LambdaAdapter{.fun = [&](const char * outname, const StorePath * outPath) { callbackCount++; }};
358+
359+
auto ret = nix_store_realise(
360+
ctx, store, drvPath, static_cast<void *>(&cb), decltype(cb)::call_void<const char *, const StorePath *>);
361+
362+
// Should fail with an error
363+
ASSERT_NE(ret, NIX_OK);
364+
ASSERT_EQ(callbackCount, 0) << "Callback should not be invoked when build fails";
365+
366+
// Check that error message is set
367+
std::string errMsg = nix_err_msg(nullptr, ctx, nullptr);
368+
ASSERT_FALSE(errMsg.empty()) << "Error message should be set";
369+
ASSERT_NE(errMsg.find("system"), std::string::npos) << "Error should mention system";
370+
371+
// Clean up
372+
nix_store_path_free(drvPath);
373+
nix_derivation_free(drv);
374+
nix_store_free(store);
375+
}
376+
377+
TEST_F(nix_api_store_test_base, nix_store_realise_builder_fails)
378+
{
379+
// Test that nix_store_realise properly reports errors when the builder fails
380+
nix::experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations");
381+
nix::settings.substituters = {};
382+
383+
auto * store = open_local_store();
384+
385+
std::filesystem::path unitTestData{getenv("_NIX_TEST_UNIT_DATA")};
386+
std::ifstream t{unitTestData / "derivation/ca/self-contained.json"};
387+
std::stringstream buffer;
388+
buffer << t.rdbuf();
389+
390+
// Replace with current system and make builder command fail
391+
std::string jsonStr = nix::replaceStrings(buffer.str(), "x86_64-linux", nix::settings.thisSystem.get());
392+
jsonStr = nix::replaceStrings(jsonStr, "echo $name foo > $out", "exit 1");
393+
394+
auto * drv = nix_derivation_from_json(ctx, store, jsonStr.c_str());
395+
assert_ctx_ok();
396+
ASSERT_NE(drv, nullptr);
397+
398+
auto * drvPath = nix_add_derivation(ctx, store, drv);
399+
assert_ctx_ok();
400+
ASSERT_NE(drvPath, nullptr);
401+
402+
int callbackCount = 0;
403+
auto cb = LambdaAdapter{.fun = [&](const char * outname, const StorePath * outPath) { callbackCount++; }};
404+
405+
auto ret = nix_store_realise(
406+
ctx, store, drvPath, static_cast<void *>(&cb), decltype(cb)::call_void<const char *, const StorePath *>);
407+
408+
// Should fail with an error
409+
ASSERT_NE(ret, NIX_OK);
410+
ASSERT_EQ(callbackCount, 0) << "Callback should not be invoked when build fails";
411+
412+
// Check that error message is set
413+
std::string errMsg = nix_err_msg(nullptr, ctx, nullptr);
414+
ASSERT_FALSE(errMsg.empty()) << "Error message should be set";
415+
416+
// Clean up
417+
nix_store_path_free(drvPath);
418+
nix_derivation_free(drv);
419+
nix_store_free(store);
420+
}
421+
422+
TEST_F(nix_api_store_test_base, nix_store_realise_builder_no_output)
423+
{
424+
// Test that nix_store_realise properly reports errors when builder succeeds but produces no output
425+
nix::experimentalFeatureSettings.set("extra-experimental-features", "ca-derivations");
426+
nix::settings.substituters = {};
427+
428+
auto * store = open_local_store();
429+
430+
std::filesystem::path unitTestData{getenv("_NIX_TEST_UNIT_DATA")};
431+
std::ifstream t{unitTestData / "derivation/ca/self-contained.json"};
432+
std::stringstream buffer;
433+
buffer << t.rdbuf();
434+
435+
// Replace with current system and make builder succeed but not produce output
436+
std::string jsonStr = nix::replaceStrings(buffer.str(), "x86_64-linux", nix::settings.thisSystem.get());
437+
jsonStr = nix::replaceStrings(jsonStr, "echo $name foo > $out", "true");
438+
439+
auto * drv = nix_derivation_from_json(ctx, store, jsonStr.c_str());
440+
assert_ctx_ok();
441+
ASSERT_NE(drv, nullptr);
442+
443+
auto * drvPath = nix_add_derivation(ctx, store, drv);
444+
assert_ctx_ok();
445+
ASSERT_NE(drvPath, nullptr);
446+
447+
int callbackCount = 0;
448+
auto cb = LambdaAdapter{.fun = [&](const char * outname, const StorePath * outPath) { callbackCount++; }};
449+
450+
auto ret = nix_store_realise(
451+
ctx, store, drvPath, static_cast<void *>(&cb), decltype(cb)::call_void<const char *, const StorePath *>);
452+
453+
// Should fail with an error
454+
ASSERT_NE(ret, NIX_OK);
455+
ASSERT_EQ(callbackCount, 0) << "Callback should not be invoked when build produces no output";
456+
457+
// Check that error message is set
458+
std::string errMsg = nix_err_msg(nullptr, ctx, nullptr);
459+
ASSERT_FALSE(errMsg.empty()) << "Error message should be set";
460+
461+
// Clean up
462+
nix_store_path_free(drvPath);
463+
nix_derivation_free(drv);
464+
nix_store_free(store);
465+
}
466+
332467
TEST_F(NixApiStoreTestWithRealisedPath, nix_store_get_fs_closure_with_outputs)
333468
{
334469
// Test closure computation with include_outputs on a derivation path

0 commit comments

Comments
 (0)