Skip to content

New store settings system #11139

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Ericson2314
Copy link
Member

@Ericson2314 Ericson2314 commented Jul 19, 2024

Motivation

See the linked issues for details.

The most notable user-relevant bits are:

  • This cleans up the MountedSSHStore: decomposed into its orthogonal parts

  • This brings us pretty close to being able to then implement a JSON-based config.

    • Store query parameters can be JSON
    • Stores can entirely be specified via JSON objects, but this is not yet hooked up to anything.

Also behind the scenes have these benefits:

  1. The docs are moved out of the headers, good for less rebuilding when they changes
  2. Stores are always constructed from store configs
  3. Use JSON, avoid custom serializers

Requirements

General requirements for settings:

  1. Settings have names/paths for hierarchical settings
  2. Settings have defaults when not explicitly set
  3. Settings have descriptions
  4. Have machine-readable descriptions and (some) defaults for docs
  5. Can parse settings file and fill in default to have complete settings struct with all fields initialized.

Other requirements:

  1. nix.conf can be in JSON, not a custom line oriented format
    • This notably includes not just flat settings with JSON values, but allowing the settings structure itself to be hierarchical.
    • This PR doesn't make the JSON settings file itself, but it does introduce some hierarchical store settings
  2. No complicated inheritance hierarchy for the settings infrastructure itself
    • If one looks at configuration.hh, there is a huge amount of inheritance. This new system avoids that entirely. Parsing is just a function.
  3. No global variables (library user)
    • fewer extraneous flags in CLI
    • better unit tests (no uncertain coverage due to settings state)
  4. Be able to open a store from a typed store configuration (parsing separate from actions/side effects)
    • Do not want to force parsing untyped map (StoreReference) into doing actions to open a Store in one step.
  5. UI logic (parsing both old and new settings) parsing separate from the settings themselves --- components being configured should get structs and don't have to care about how those structs were made.
    • The library / structs in headers only should be the C++ field names and types. There should be no default value, human string-name, description, etc. leaking into the header.
    • Once the old settings are reworked on top of this, tons of UI code (configuration and CLI arugments) can move out of libutil, where it does not belong, and pollutes libstore/libexpr in the library use-case.
  6. Ability to use designated initializers
    • The inheritance of the existing store config inheritance prevents using designated initializers. Pulling out the template stuff into separate data types, so it can be "plain old data" fixes this problem. Eventually it would be nice to not have as much inheritance on store configs, so we can go back to having fewer types, but that is future work.
  7. Concision and ease of editing (contributor experience)
    • <= 2 places (one header, one compilation unit with descriptions and such)

Context

Do not review by commits

  • contains commits from a different PR that are listed here because that PR was squashed
  • not much of useful separation anyway in this commit history

Part of #11106

Depends on #13154

Priorities and Process

Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@github-actions github-actions bot added new-cli Relating to the "nix" command with-tests Issues related to testing. PRs with tests have some priority store Issues and pull requests concerning the Nix store repl The Read Eval Print Loop, "nix repl" command and debugger c api Nix as a C library with a stable interface labels Jul 19, 2024
@Ericson2314
Copy link
Member Author

@L-as I think I need to UBSAN, because the store unit tests are failing for what looks like a random variable corruption.

@L-as
Copy link
Member

L-as commented Jul 19, 2024

We should just enable it by default, but it causes some weird test failures.

@edolstra
Copy link
Member

I don't really get the motivation for this change (another massive refactoring PR, which I assume will be followed by an even bigger change when this gets applied to the main settings). #10766 doesn't really give a motivation other than "we always go directly from a StoreReference to the Store data type directly" which is apparently a bad thing. #11106 mentions support for structured settings, but I don't see why that requires completely replacing the current Settings type (there's no reason why those cannot be constructed from JSON values).

This PR scatters the settings mechanism for a class like BinaryCacheStore across a new BinaryCacheStoreConfigT, BinaryCacheStoreConfig, BinaryCacheStore::Config::Descriptions::Descriptions and BinaryCacheStore::Config::BinaryCacheStoreConfig (the defaults declared in another class from the actual settings). That's not an improvement for readability!

I don't really like types like:

template<template<typename> class F>
struct BinaryCacheStoreConfigT
...
    const F<std::string> compression;

We should really keep the template metaprogramming to a minimum! Again for readability, this is not great since looking at this code, the reader has no idea what F is supposed to do.

@roberth
Copy link
Member

roberth commented Aug 28, 2024

We should really keep the template metaprogramming to a minimum! Again for readability, this is not great since looking at this code, the reader has no idea what F is supposed to do.

I believe it's quite valuable in this situation, and not all that confusing when documented properly.

For this we can make use of the language server. (not using one is cruel anyway)

Example:

diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh
index af649aaab..a638248c5 100644
--- a/src/libstore/binary-cache-store.hh
+++ b/src/libstore/binary-cache-store.hh
@@ -13,6 +13,7 @@ namespace nix {
 
 struct NarInfo;
 
+/** @param T Either 'nix::config::JustValue' or `nix::config::SettingInfo` */
 template<template<typename> class F>
 struct BinaryCacheStoreConfigT
 {
diff --git a/src/libutil/config-abstract.hh b/src/libutil/config-abstract.hh
index 2c34ff50b..e7f71e700 100644
--- a/src/libutil/config-abstract.hh
+++ b/src/libutil/config-abstract.hh
@@ -5,6 +5,9 @@
 
 namespace nix::config {
 
+/**
+ * Just a value, no metadata. explain explain
+ */
 template<typename T>
 struct JustValue
 {

With docs in these positions, readers can easily navigate to the explanations in the two relevant types that instantiate the fields.

Note also that when hovering over a config.foo usage (this->config.foo), the LSP will show JustValue<bool> which gives a good intuition for what to expect from it.

@Ericson2314
Copy link
Member Author

If the Linux tests fail again, I think it might something to do with UBSan? I'll need to figure out how to run that locally.

@Ericson2314 Ericson2314 force-pushed the new-store-settings branch 2 times, most recently from 9511fe1 to 668c465 Compare April 13, 2025 21:30
@Ericson2314 Ericson2314 marked this pull request as ready for review April 13, 2025 22:19
@Ericson2314
Copy link
Member Author

Yay, fixed the UBSAN issues!

Comment on lines 30 to 32
static const std::string name() { return "Local Daemon Store"; }

std::string doc() override;
static std::string doc();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downgrade to constants?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We I think we could, but I am tempted to do in a separate commit since this one is already so big. How does that sound?

} catch (nlohmann::json::exception &) {
// if its not valid JSON...
if (k == "remote-program" || k == "system-features") {
// Back compat hack! Split and take that array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not do hacks? Presumably this needs to be part of the non-JSON parsing logic only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to allow query parameters to also be JSON, however. For example, I made it so ssh://localhost?remote-program=["nix","store","--serve"] works (provided one does enough HTML % escaping).


void adl_serializer<ref<const StoreConfig>>::to_json(json & obj, ref<const StoreConfig> s)
{
// TODO, for tests maybe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or nix store show/info

#include <optional>

namespace nix::config {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs documentation. I believe this was part of an earlier review.

@Ericson2314 Ericson2314 force-pushed the new-store-settings branch 3 times, most recently from d146a6e to c7a1caa Compare April 16, 2025 18:45
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch 2 times, most recently from 6249504 to 1ceada0 Compare April 28, 2025 18:46
* settings record in order to ensure don't forgot to parse or document
* settings field.
*/
template<template<typename> class F>
Copy link
Member

@roberth roberth Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
template<template<typename> class F>
template<template<typename> class Mode>
Suggested change
template<template<typename> class F>
template<template<typename> class Container>
Suggested change
template<template<typename> class F>
template<template<typename> class Setting>
Suggested change
template<template<typename> class F>
template<template<typename> class Interpretation>

between stores if they have the same `store` setting.
)"};
const Path storeDir = storeDir_;
F<Path> _storeDir;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If use Setting<F, Path> = F<Path>; works and needs no extra initializer braces:

Suggested change
F<Path> _storeDir;
Setting<F, Path> _storeDir;

Comment on lines 91 to 97
#if 0
/**
* @see SettingInfo::aliases
*/
std::set<std::string> aliases;
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not very clear why this is if 0'd out. Is this some placeholder for future work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, to subsume the old configuration system more completely (not just what is in-use for store settings) we would add this stuff also

Comment on lines 1595 to 1605
auto config = make_ref<LocalStore::Config>(*getLocalStore().config);
config->pathInfoCacheSize.value = 0;
config->stateDir.value = "/no-such-path";
config->logDir.value = "/no-such-path";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good for readability if PlainValue overloaded operator= so that we could just do this:

Suggested change
auto config = make_ref<LocalStore::Config>(*getLocalStore().config);
config->pathInfoCacheSize.value = 0;
config->stateDir.value = "/no-such-path";
config->logDir.value = "/no-such-path";
auto config = make_ref<LocalStore::Config>(*getLocalStore().config);
config->pathInfoCacheSize = 0;
config->stateDir = "/no-such-path";
config->logDir = "/no-such-path";

Might be worth adding something like:

template <std::convertible_to<T> U>
PlainValue & operator=(U rhs) &
{
    value = static_cast<T>(rhs);
    return *this;
}

wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
resolvedSubstConfig.wantMassQuery.value =
config->storeConfig.wantMassQuery.optValue.value_or(cacheInfo->wantMassQuery);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite a mouthful. Can't we just forward value_or method to the OptValue class so that this would work:

Suggested change
config->storeConfig.wantMassQuery.optValue.value_or(cacheInfo->wantMassQuery);
config->storeConfig.wantMassQuery.value_or(cacheInfo->wantMassQuery);

This works:

template<typename T>
struct OptValue : private std::optional<T>
{
private:
    using OptBase = std::optional<T>;

public:
    OptValue(OptBase opt) : OptBase(std::move(opt)) {}
    using OptBase::OptBase;
    using OptBase::value_or;
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I can just use std::optional directly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't really have a strong opinion on this, but that a good example of the Law of Demeter and saves on some typing. But again, not a blocking suggestion in any way.

@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from 1ceada0 to 02446fd Compare May 7, 2025 19:25
Ericson2314 added a commit to obsidiansystems/nix that referenced this pull request May 7, 2025
Fix NixOS#10766

See that ticket for details.

Progress (I hope!) towards NixOS#11139.
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from 02446fd to a4a23a7 Compare May 7, 2025 23:30
@Ericson2314 Ericson2314 marked this pull request as draft May 7, 2025 23:37
Ericson2314 added a commit to obsidiansystems/nix that referenced this pull request May 7, 2025
Fix NixOS#10766

See that ticket for details.

Progress (I hope!) towards NixOS#11139.
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from a4a23a7 to 9a01d3b Compare May 7, 2025 23:47
Ericson2314 added a commit to obsidiansystems/nix that referenced this pull request May 8, 2025
Fix NixOS#10766

See that ticket for details.

Progress (I hope!) towards NixOS#11139.
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from 9a01d3b to 48a8c2c Compare May 8, 2025 16:42
Ericson2314 added a commit to obsidiansystems/nix that referenced this pull request May 13, 2025
Fix NixOS#10766

See that ticket for details.

Progress (I hope!) towards NixOS#11139.

Co-Authored-By: Sergei Zimmerman <[email protected]>
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from 48a8c2c to e302ab6 Compare May 13, 2025 20:09
Mic92 pushed a commit to Mic92/nix-1 that referenced this pull request May 14, 2025
Fix NixOS#10766

See that ticket for details.

Progress (I hope!) towards NixOS#11139.

Co-Authored-By: Sergei Zimmerman <[email protected]>
Copy link

dpulls bot commented May 14, 2025

🎉 All dependencies have been resolved !

@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from e302ab6 to 2daf1ee Compare May 14, 2025 19:15
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch 3 times, most recently from 9b5b2b9 to 2ea9f59 Compare May 21, 2025 21:59
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from 2ea9f59 to 6c4c845 Compare May 23, 2025 19:21
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from 93f958b to c7f126b Compare May 23, 2025 19:39
Motivation:

See the linked issues for details.

The most notable user-relevant bits are:

- This cleans up the `MountedSSHStore`: decomposed into its orthogonal parts

- This brings us pretty close to being able to then implement a JSON-based config.
   - Store query parameters can be JSON
   - Stores can entirely be specified via JSON objects, but this is not yet hooked up to anything.

Also behind the scenes have these benefits:

1. The docs are moved out of the headers, good for less rebuilding when they changes
2. Stores are always constructed from store configs
3. Use JSON, avoid custom serializers

Context:

Part of NixOS#11106

Co-Authored-By: Robert Hensing <[email protected]>
Co-authored-by: Sergei Zimmerman <[email protected]>
@Ericson2314 Ericson2314 force-pushed the new-store-settings branch from c7f126b to fe8b42d Compare May 23, 2025 20:50
@Ericson2314 Ericson2314 removed this from Nix team May 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c api Nix as a C library with a stable interface documentation new-cli Relating to the "nix" command repl The Read Eval Print Loop, "nix repl" command and debugger store Issues and pull requests concerning the Nix store with-tests Issues related to testing. PRs with tests have some priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants