diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc index 4ce597952..06b139f91 100644 --- a/src/libflake-c/nix_api_flake.cc +++ b/src/libflake-c/nix_api_flake.cc @@ -97,7 +97,16 @@ nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_fla { nix_clear_err(context); try { - auto lockSettings = nix::make_ref(); + auto lockSettings = nix::make_ref(nix::flake::LockFlags{ + .recreateLockFile = false, + .updateLockFile = true, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .writeLockFile = true, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .failOnUnlocked = false, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .useRegistries = false, + .allowUnlocked = false, // == `nix_flake_lock_flags_set_mode_write_as_needed` + .commitLockFile = false, + + }); return new nix_flake_lock_flags{lockSettings}; } NIXC_CATCH_ERRS_NULL @@ -108,16 +117,68 @@ void nix_flake_lock_flags_free(nix_flake_lock_flags * flags) delete flags; } +nix_err nix_flake_lock_flags_set_mode_virtual(nix_c_context * context, nix_flake_lock_flags * flags) +{ + nix_clear_err(context); + try { + flags->lockFlags->updateLockFile = true; + flags->lockFlags->writeLockFile = false; + flags->lockFlags->failOnUnlocked = false; + flags->lockFlags->allowUnlocked = true; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, nix_flake_lock_flags * flags) +{ + nix_clear_err(context); + try { + flags->lockFlags->updateLockFile = true; + flags->lockFlags->writeLockFile = true; + flags->lockFlags->failOnUnlocked = false; + flags->lockFlags->allowUnlocked = true; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_lock_flags_set_mode_check(nix_c_context * context, nix_flake_lock_flags * flags) +{ + nix_clear_err(context); + try { + flags->lockFlags->updateLockFile = false; + flags->lockFlags->writeLockFile = false; + flags->lockFlags->failOnUnlocked = true; + flags->lockFlags->allowUnlocked = false; + } + NIXC_CATCH_ERRS +} + +nix_err nix_flake_lock_flags_add_input_override( + nix_c_context * context, nix_flake_lock_flags * flags, const char * inputPath, nix_flake_reference * flakeRef) +{ + nix_clear_err(context); + try { + auto path = nix::flake::parseInputAttrPath(inputPath); + flags->lockFlags->inputOverrides.emplace(path, *flakeRef->flakeRef); + if (flags->lockFlags->writeLockFile) { + return nix_flake_lock_flags_set_mode_virtual(context, flags); + } + } + NIXC_CATCH_ERRS +} + nix_locked_flake * nix_flake_lock( nix_c_context * context, - nix_flake_settings * settings, + nix_fetchers_settings * fetchSettings, + nix_flake_settings * flakeSettings, EvalState * eval_state, nix_flake_lock_flags * flags, nix_flake_reference * flakeReference) { + nix_clear_err(context); try { auto lockedFlake = nix::make_ref(nix::flake::lockFlake( - *settings->settings, eval_state->state, *flakeReference->flakeRef, *flags->lockFlags)); + *flakeSettings->settings, eval_state->state, *flakeReference->flakeRef, *flags->lockFlags)); return new nix_locked_flake{lockedFlake}; } NIXC_CATCH_ERRS_NULL diff --git a/src/libflake-c/nix_api_flake.h b/src/libflake-c/nix_api_flake.h index c82fe29c3..f5b9dc542 100644 --- a/src/libflake-c/nix_api_flake.h +++ b/src/libflake-c/nix_api_flake.h @@ -126,6 +126,49 @@ nix_flake_lock_flags * nix_flake_lock_flags_new(nix_c_context * context, nix_fla */ void nix_flake_lock_flags_free(nix_flake_lock_flags * settings); +/** + * @brief Put the lock flags in a mode that checks whether the lock is up to date. + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @return NIX_OK on success, NIX_ERR on failure + * + * This causes `nix_flake_lock` to fail if the lock needs to be updated. + */ +nix_err nix_flake_lock_flags_set_mode_check(nix_c_context * context, nix_flake_lock_flags * flags); + +/** + * @brief Put the lock flags in a mode that updates the lock file in memory, if needed. + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] update Whether to allow updates + * + * This will cause `nix_flake_lock` to update the lock file in memory, if needed. + */ +nix_err nix_flake_lock_flags_set_mode_virtual(nix_c_context * context, nix_flake_lock_flags * flags); + +/** + * @brief Put the lock flags in a mode that updates the lock file on disk, if needed. + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] update Whether to allow updates + * + * This will cause `nix_flake_lock` to update the lock file on disk, if needed. + */ +nix_err nix_flake_lock_flags_set_mode_write_as_needed(nix_c_context * context, nix_flake_lock_flags * flags); + +/** + * @brief Add input overrides to the lock flags + * @param[out] context Optional, stores error information + * @param[in] flags The flags to modify + * @param[in] inputPath The input path to override + * @param[in] flakeRef The flake reference to use as the override + * + * This switches the `flags` to `nix_flake_lock_flags_set_mode_virtual` if not in mode + * `nix_flake_lock_flags_set_mode_check`. + */ +nix_err nix_flake_lock_flags_add_input_override( + nix_c_context * context, nix_flake_lock_flags * flags, const char * inputPath, nix_flake_reference * flakeRef); + /** * @brief Lock a flake, if not already locked. * @param[out] context Optional, stores error information @@ -135,6 +178,7 @@ void nix_flake_lock_flags_free(nix_flake_lock_flags * settings); */ nix_locked_flake * nix_flake_lock( nix_c_context * context, + nix_fetchers_settings * fetchSettings, nix_flake_settings * settings, EvalState * eval_state, nix_flake_lock_flags * flags, diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc index eb1f7c486..f7e0cb719 100644 --- a/src/libflake-tests/nix_api_flake.cc +++ b/src/libflake-tests/nix_api_flake.cc @@ -71,15 +71,8 @@ TEST_F(nix_api_store_test, nix_api_flake_reference_not_absolute_no_basedir_fail) std::string fragment; nix_flake_reference * flakeReference = nullptr; auto r = nix_flake_reference_and_fragment_from_string( - ctx, - fetchSettings, - settings, - parseFlags, - str.data(), - str.size(), - &flakeReference, - OBSERVE_STRING(fragment)); - + ctx, fetchSettings, settings, parseFlags, str.data(), str.size(), &flakeReference, OBSERVE_STRING(fragment)); + ASSERT_NE(NIX_OK, r); ASSERT_EQ(nullptr, flakeReference); @@ -126,11 +119,7 @@ TEST_F(nix_api_store_test, nix_api_load_flake) assert_ctx_ok(); ASSERT_NE(nullptr, parseFlags); - auto r0 = nix_flake_reference_parse_flags_set_base_directory( - ctx, - parseFlags, - tmpDir.c_str(), - tmpDir.size()); + auto r0 = nix_flake_reference_parse_flags_set_base_directory(ctx, parseFlags, tmpDir.c_str(), tmpDir.size()); assert_ctx_ok(); ASSERT_EQ(NIX_OK, r0); @@ -138,14 +127,7 @@ TEST_F(nix_api_store_test, nix_api_load_flake) const std::string ref = ".#legacyPackages.aarch127-unknown...orion"; nix_flake_reference * flakeReference = nullptr; auto r = nix_flake_reference_and_fragment_from_string( - ctx, - fetchSettings, - settings, - parseFlags, - ref.data(), - ref.size(), - &flakeReference, - OBSERVE_STRING(fragment)); + ctx, fetchSettings, settings, parseFlags, ref.data(), ref.size(), &flakeReference, OBSERVE_STRING(fragment)); assert_ctx_ok(); ASSERT_EQ(NIX_OK, r); ASSERT_NE(nullptr, flakeReference); @@ -157,7 +139,7 @@ TEST_F(nix_api_store_test, nix_api_load_flake) assert_ctx_ok(); ASSERT_NE(nullptr, lockFlags); - auto lockedFlake = nix_flake_lock(ctx, settings, state, lockFlags, flakeReference); + auto lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); assert_ctx_ok(); ASSERT_NE(nullptr, lockedFlake); @@ -183,4 +165,219 @@ TEST_F(nix_api_store_test, nix_api_load_flake) nix_flake_settings_free(settings); } +TEST_F(nix_api_store_test, nix_api_load_flake_with_flags) +{ + nix_libstore_init(ctx); + assert_ctx_ok(); + nix_libexpr_init(ctx); + assert_ctx_ok(); + + auto tmpDir = nix::createTempDir(); + nix::AutoDelete delTmpDir(tmpDir, true); + + nix::createDirs(tmpDir + "/b"); + nix::writeFile(tmpDir + "/b/flake.nix", R"( + { + outputs = { ... }: { + hello = "BOB"; + }; + } + )"); + + nix::createDirs(tmpDir + "/a"); + nix::writeFile(tmpDir + "/a/flake.nix", R"( + { + inputs.b.url = ")" + tmpDir + R"(/b"; + outputs = { b, ... }: { + hello = b.hello; + }; + } + )"); + + nix::createDirs(tmpDir + "/c"); + nix::writeFile(tmpDir + "/c/flake.nix", R"( + { + outputs = { ... }: { + hello = "Claire"; + }; + } + )"); + + nix_libstore_init(ctx); + assert_ctx_ok(); + + auto fetchSettings = nix_fetchers_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, fetchSettings); + + auto settings = nix_flake_settings_new(ctx); + assert_ctx_ok(); + ASSERT_NE(nullptr, settings); + + nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store); + ASSERT_NE(nullptr, builder); + assert_ctx_ok(); + + auto state = nix_eval_state_build(ctx, builder); + assert_ctx_ok(); + ASSERT_NE(nullptr, state); + + nix_eval_state_builder_free(builder); + + auto parseFlags = nix_flake_reference_parse_flags_new(ctx, settings); + assert_ctx_ok(); + ASSERT_NE(nullptr, parseFlags); + + auto r0 = nix_flake_reference_parse_flags_set_base_directory(ctx, parseFlags, tmpDir.c_str(), tmpDir.size()); + assert_ctx_ok(); + ASSERT_EQ(NIX_OK, r0); + + std::string fragment; + const std::string ref = "./a"; + nix_flake_reference * flakeReference = nullptr; + auto r = nix_flake_reference_and_fragment_from_string( + ctx, fetchSettings, settings, parseFlags, ref.data(), ref.size(), &flakeReference, OBSERVE_STRING(fragment)); + assert_ctx_ok(); + ASSERT_EQ(NIX_OK, r); + ASSERT_NE(nullptr, flakeReference); + ASSERT_EQ(fragment, ""); + + // Step 1: Do not update, fails + + auto lockFlags = nix_flake_lock_flags_new(ctx, settings); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockFlags); + + nix_flake_lock_flags_set_mode_check(ctx, lockFlags); + assert_ctx_ok(); + + // Step 2: Update but do not write, succeeds + + auto lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_err(); + ASSERT_EQ(nullptr, lockedFlake); + + nix_flake_lock_flags_set_mode_virtual(ctx, lockFlags); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + auto value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + auto helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + std::string helloStr; + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("BOB", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + + // Step 3: Lock was not written, so Step 1 would fail again + + nix_flake_lock_flags_set_mode_check(ctx, lockFlags); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_err(); + ASSERT_EQ(nullptr, lockedFlake); + + // Step 4: Update and write, succeeds + + nix_flake_lock_flags_set_mode_write_as_needed(ctx, lockFlags); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + helloStr.clear(); + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("BOB", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + + // Step 5: Lock was written, so Step 1 would succeed + + nix_flake_lock_flags_set_mode_check(ctx, lockFlags); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + helloStr.clear(); + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("BOB", helloStr); + + nix_value_decref(ctx, value); + nix_locked_flake_free(lockedFlake); + + // Step 6: Lock with override, do not write + + nix_flake_lock_flags_set_mode_write_as_needed(ctx, lockFlags); + assert_ctx_ok(); + + nix_flake_reference * overrideFlakeReference = nullptr; + nix_flake_reference_and_fragment_from_string( + ctx, fetchSettings, settings, parseFlags, "./c", 3, &overrideFlakeReference, OBSERVE_STRING(fragment)); + assert_ctx_ok(); + ASSERT_NE(nullptr, overrideFlakeReference); + + nix_flake_lock_flags_add_input_override(ctx, lockFlags, "b", overrideFlakeReference); + assert_ctx_ok(); + + lockedFlake = nix_flake_lock(ctx, fetchSettings, settings, state, lockFlags, flakeReference); + assert_ctx_ok(); + ASSERT_NE(nullptr, lockedFlake); + + // Get the output attrs + value = nix_locked_flake_get_output_attrs(ctx, settings, state, lockedFlake); + assert_ctx_ok(); + ASSERT_NE(nullptr, value); + + helloAttr = nix_get_attr_byname(ctx, value, state, "hello"); + assert_ctx_ok(); + ASSERT_NE(nullptr, helloAttr); + + helloStr.clear(); + nix_get_string(ctx, helloAttr, OBSERVE_STRING(helloStr)); + assert_ctx_ok(); + ASSERT_EQ("Claire", helloStr); + + nix_flake_reference_parse_flags_free(parseFlags); + nix_flake_lock_flags_free(lockFlags); + nix_flake_reference_free(flakeReference); + nix_state_free(state); + nix_flake_settings_free(settings); +} + } // namespace nixC