Skip to content
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

Flake schemas #8892

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open

Flake schemas #8892

wants to merge 30 commits into from

Conversation

edolstra
Copy link
Member

@edolstra edolstra commented Aug 31, 2023

Motivation

A big problem with current flakes is that commands like nix flake check and nix flake show have built-in support for a limited set of flake output types. For instance, they know about nixosConfigurations but not homeConfigurations.

This PR moves support for flake output types out of C++ and into Nix functions supplied by a new schemas flake output. This allows users to define their own flake output types, and have them show up in nix flake show and checked by nix flake check. As a result, there are no more flake output types that are more "blessed" than others.

There is no central registry of schemas. Instead a flake should add an appropriate schemas flake output for the outputs it wants to have checked. Typically this will include schemas from other flakes. In particular, I've made a flake-schemas flake with schemas for the flake output types that were previously built into Nix.

The format of schemas is documented in doc/manual/src/protocols/flake-schemas.md.

To do:

  • The evalChecks schema attribute need to be refined to allow checkers to return warnings/errors in a more sensible way than an assertion failure.

Context

Fixes #3487, #6453, #6454.

Checklist for maintainers

Maintainers: tick if completed or explain if not relevant

  • agreed on idea
  • agreed on implementation strategy
  • tests, as appropriate
    • functional tests - tests/**.sh
    • unit tests - src/*/tests
    • integration tests - tests/nixos/*
  • documentation in the manual
  • documentation in the internal API docs
  • code and comments are self-explanatory
  • commit message explains why the change was made
  • new feature or incompatible change: updated release notes

Priorities

Add 👍 to pull requests you find important.

Flake schemas allow us to get rid of output-specific code in commands
like `nix flake [show|check|search]`. This is done by allowing flakes
to have a `schemas` attribute` that defines how to enumerate the
contents of the output (including documentation), and how to check
it.

In the future, this can be extended to support configurability of
flakes in a discoverable way.
@github-actions github-actions bot added documentation new-cli Relating to the "nix" command labels Aug 31, 2023
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/flake-schemas-making-flake-outputs-extensible/32421/1

@NobbZ
Copy link
Contributor

NobbZ commented Aug 31, 2023

If I understand the description correctly, this would also solve #6453 and #6454.

flake.nix Outdated
@@ -5,8 +5,9 @@
inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; };
inputs.flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
inputs.flake-schemas.url = github:DeterminateSystems/flake-schemas;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be owned by the NixOS organization?

Copy link
Member Author

Choose a reason for hiding this comment

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

Definitely, if this PR gets accepted.

This comment was marked as duplicate.

# FIXME: a pre-cached copy of the flake-schemas needs to be built in to the nix binary
defaultSchemas = (builtins.getFlake "github:DeterminateSystems/flake-schemas/3b4d5fef938f698c8737515532a1be53bf6355f2").schemas;

schemaOverrides = {}; # FIXME
Copy link
Member

Choose a reason for hiding this comment

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

What does FIXME mean here for schemaOverrides?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was intended to support adding/overriding (some) schemas from the command line, but I didn't implement that yet.

@roberth
Copy link
Member

roberth commented Sep 1, 2023

Considering that the schema returns the children, this defines a new "derivation" centric, tree shaped view of the flake that is determined by the flake itself, as the flake defines the schema. Shouldn't other commands go through the schema then? Otherwise, we are still making assumptions about the schema contents.

Should the evaluated schema be exposed to flakes that consume the flake as an input?

Do we need the interface between the CLI and the flake need to be schema-like? Why don't we expose the desired functionality directly by defining one or two new outputs that don't rely on the concept of a schema? For example:

outputs = inputs@{ self, ... }: {
  evalChecks = {
    myChecks = ...;
    fooChecks = inputs.foo.lib.evalChecks self;
  };

  treeView = {
    hasPackage = 1;
    children = {
      defaultPackage = { # picked this simpler one for brevity. Users won't write this by hand, just as they generally don't write schemas
        children = {
          isDict = true;
          role = "system";
          children = {
            x86_64-linux = {
              isPackage = true;
              value = ...;
            };
          };
        };        
      };
    };
  };
}

Outputs like this seem conceptually simpler to me, and they seem easier to use from within the language.

They will be similarly easy to use when the outputs are defined through a library.
Considering that the flake-schemas repo is already a library, this condition holds.

If we do decide that we need a concept of schema, is the metaschema as simple as it can be, or can it leverage more of the language?

@KevinCathcart
Copy link

Considering that the schema returns the children, this defines a new "derivation" centric, tree shaped view of the flake that is determined by the flake itself, as the flake defines the schema. Shouldn't other commands go through the schema then? Otherwise, we are still making assumptions about the schema contents.

I didn't think this is intended to be a new view of the flake at all. And it is absolutely not intended to be derivation centric. The real concept being embodied is conceptual outputs that user care about. That is to say: packages, modules, checks, configurations, ci jobs, overlays, deployment definitions, etc. These big picture concept that people would generally hope would show up in nix flake show output. Of course, some of them may be smaller picture items too, like a library flake's conceptual outputs are the library functions it exports. We don't have a great name for these conceptual outputs, so I will use "artifact" for them.

The problem, of course, is that there is no simple way to enumerate these artifacts. It is not like these are all top level output attributes. Indeed, they pretty deliberately are mostly not top level attributes, since having some structure makes it possible for tooling to distinguish outputs in useful ways. Most standard output attributes are either attribute sets of these, or attribute sets of attribute sets of these. In practice these artifacts are almost always just nix values that can be reached as an attribute path from output attribute set.

Given that in practice in order to use any of these artifacts you need to know how to reference them from an imported flake, it would really make no sense to have the schema define a different tree structure then the output's attributes already have, albeit truncated.

Do we need the interface between the CLI and the flake need to be schema-like? Why don't we expose the desired functionality directly by defining one or two new outputs that don't rely on the concept of a schema? For example:
...
Outputs like this seem conceptually simpler to me, and they seem easier to use from within the language.

This is a possibility. From a flake authoring perspective, importing evalchecks sounds like a fairly low level thing to be working with, while importing a schema from a flake and assigning that sounds more high level. Similarly from an authoring perspective to add a new schema would be schemas = flake-schemas.schemas // other-schemas.schemas; but with this approach, I'd need to not only do something similar for eval checks, but also need to compose the standard attributes treeview generating function with the one for the community defined output type and assign that. Doable, but just more boilerplate, and there is already more boilerplate stuff with flakes than many people like.

@roberth
Copy link
Member

roberth commented Sep 3, 2023

I didn't think this is intended to be a new view of the flake at all.

I agree about the intent, but it's the details of the design that matter.
The children attribute does seem to form a view of the flake, which could be expressed more simply as another output attribute.

It is worth repeating that Nix is highly constrained when it comes to making changes, because we've successfully provided compatibility with old expressions, and would like to keep that unique property. While this doesn't apply during the experimental phase of a feature, we will eventually have to judge it by the usual standard before we can call it stable.
So a feature like this could further delay the stabilization of flakes if it isn't as simple as it could be.

Another important observation is that the less we do in C++ and the more we do in Nix expressions, we get better opportunities to iterate on our solutions, because users can pin or lock such expressions and adapt to any changes at their own pace, or not at all if we're talking about support for old expressions.

Hence I'd take a PR that reifies CLI behavior as an expression-based interface over a PR that introduces new concepts any day - as long as it's clear what the CLI should do, and in case of enumerating and checking that seems rather clear. Extensions to that behavior can be represented by following the dict vs record pattern we've described in the JSON guideline (applied to attrsets instead).


Leaf nodes can have the following attributes:

* `derivation`: The main derivation of this node, if any. It must evaluate for `nix flake check` and `nix flake show` to succeed.
Copy link

Choose a reason for hiding this comment

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

Some outputs are not technically a derivation, but rather a path, yet they are paths referring to the output of a derivation. apps.*.program for example. How can they be represented in this schema?

Ref: DeterminateSystems/flake-schemas#5

flake.nix Outdated Show resolved Hide resolved
Co-authored-by: Greg Pfeil <[email protected]>
jlesquembre added a commit to jlesquembre/clj-nix that referenced this pull request Dec 4, 2023
That was causing issues, and now we have a better solution:
NixOS/nix#8892
@aanderse
Copy link
Member

any update on this? i don't think i saw any mention of this on any of the team meetings i glanced through recently

@grahamc
Copy link
Member

grahamc commented Feb 24, 2024 via email

@sellout
Copy link
Contributor

sellout commented Feb 26, 2024

For what it’s worth we’re using them a lot at determinate systems to good effect.

I’ve also had success with them. Here’s a fairly rich schema I use in most of my projects: https://github.com/sellout/project-manager/blob/495cb847eb2cbf6b7981576c3b1610cc077c4768/nix/schemas.nix#L6-L80

@aanderse
Copy link
Member

sounds lovely

i wonder what it would take to move forward with this

@github-actions github-actions bot added the with-tests Issues related to testing. PRs with tests have some priority label Mar 11, 2024
@adamcstephens
Copy link

For what it’s worth we’re using them a lot at determinate systems to good effect.

That's great to hear. What is the status of y'all moving this forward?

@MattSturgeon
Copy link

Forgive my ignorance, but how are people using schemas without the nix command supporting them?

Or are people running a build of this PR branch instead of upstream?

Flake lock file updates:

• Updated input 'flake-schemas':
    'github:DeterminateSystems/flake-schemas/764932025c817d4e500a8d2a4d8c565563923d29' (2023-10-16)
  → 'github:DeterminateSystems/flake-schemas/427266c62c9ed33ad15691a4c5edd3eec46bc129?narHash=sha256-mmVvW7WF39MXuqHdPQwqT6eVmKyapaMAydkw5EBwsGE%3D' (2024-06-25)
Flake lock file updates:

• Updated input 'flake-schemas':
    'github:DeterminateSystems/flake-schemas/427266c62c9ed33ad15691a4c5edd3eec46bc129?narHash=sha256-mmVvW7WF39MXuqHdPQwqT6eVmKyapaMAydkw5EBwsGE%3D' (2024-06-25)
  → 'github:DeterminateSystems/flake-schemas/e2723a64e9b2e4e68eed857a470fc6786ff31507?narHash=sha256-twVbB0VtoL21skVW7dv9IjyfAe3RwNJLtRykGKe0SDw%3D' (2024-06-28)
Flake lock file updates:

• Updated input 'flake-schemas':
    'github:DeterminateSystems/flake-schemas/e2723a64e9b2e4e68eed857a470fc6786ff31507?narHash=sha256-twVbB0VtoL21skVW7dv9IjyfAe3RwNJLtRykGKe0SDw%3D' (2024-06-28)
  → 'github:DeterminateSystems/flake-schemas/e647c825db1d60e20d79047d090cbda0e1f25c3d?narHash=sha256-n6UwIart9yr2UhTTNWaQYVlvyXLg/uEJCDj72npf%2BRI%3D' (2024-07-01)
Flake lock file updates:

• Updated input 'flake-schemas':
    'github:DeterminateSystems/flake-schemas/e647c825db1d60e20d79047d090cbda0e1f25c3d?narHash=sha256-n6UwIart9yr2UhTTNWaQYVlvyXLg/uEJCDj72npf%2BRI%3D' (2024-07-01)
  → 'github:DeterminateSystems/flake-schemas/61a02d7183d4241962025e6c6307a22a0bb72a21?narHash=sha256-wM%2B8JtoKBkahHiKn%2BEM1ikurMnitwRQrZ91hipJIJK8%3D' (2024-07-01)
@edolstra edolstra marked this pull request as ready for review July 2, 2024 12:31
@edolstra
Copy link
Member Author

edolstra commented Jul 9, 2024

Shouldn't other commands go through the schema?

No, the schemas reflect what those commands do (e.g. the nixosConfigurations schema reflects that nixos-rebuild requires a config.system.build.toplevel attribute that evaluates to a derivation), they don't determine what those commands do.

In the future, some commands could use schemas, e.g. nix build .#nixosConfigurations.my-machine could use the nixosConfigurations schema to figure out that it needs to expand my-machine to my-machine.config.system.build.toplevel. That would be pretty nice.

Continuing the previous point, we have an opportunity to clarify the meanings of the various attributes - specifically whether they related to the build or host system (or target - probably rare and to be avoided)

Assigning a meaning to system attributes is up to the schemas. E.g. if some tool has a more refined notion of system types, the corresponding schema could check those types.

Explore what can be un-hardcoded from Nix. Perhaps logic from src/nix/call-flake-schemas.nix could be moved into the schemas repo?

call-flake-schemas.nix is a fairly minimal bit of helper code to evaluate schemas. It's completely internal to nix::flake_schemas::call(). It could be done in C++, but that would be more code.

Give inventory a static counterpart that declares something about the contents without having them, as schemas do. This is important for discoverability.

Schemas already have some static info (like a doc attribute). However, statically declaring what the contents should look like sounds like a type system, which is a much bigger project. The flake schema format is extensible so it could support that though.

@roberth
Copy link
Member

roberth commented Jul 15, 2024

Discussed in the meeting today. We didn't have a lot of time because we were towards the end of it, so I'll just summarize the thoughts I shared.

Naming

  • "Schemas" has a fairly well-defined meaning in software that doesn't quite align with this. Perhaps we could change the name. We've considered "flake protocols" (similarly vague and not a massive improvement), "flake interfaces" (might interfere with potential terminology for Nix types we might have at some point) and "flake descriptors" (worth considering?)
  • Schemas may evolve into types when that happens. I think it would be good to change the name at that point, because that's a significant change, and they could coexist (certainly during a "migration" phase)

Is it possible to export a flake schema into a serializable form? (Or documentation)

  • It could evolve to support that?
  • It'd be beneficial to have an expression-level indirection that mediates between users and a minimal Nix<->expressions interface. This indirection would feel similar to the module system, but users would write even fewer lambdas. It could live in the flake-schemas repo.
    • Similar to a mkDerivation or a libc (in Nix at least where you can have multiple libcs). This layer provides ease of use to take the pressure of the kernel (ie Nix's C++ code) to incorporate conveniences that need to evolve over time.
    • Also allows the extension of the Nix<->expressions interface. For instance support for outputting a serializable schema or documentation would be a matter of Nix calling .schema or .docs instead of .check.
      • Ie docs is an attribute that the flake schemas repo synthesizes from the individual doc attributes it processes.
    • Would be usable without Nix assigning semantics to those attributes on the schema attrsets
  • Users could still talk to the low level Nix<->expressions interface directly, but they wouldn't get the benefits from the flake schemas repo indirection. I don't think that's a problem; just a missed opportunity for them.

srid pushed a commit to juspay/omnix that referenced this pull request Aug 2, 2024
This uses the still WIP flake schemas feature implemented in NixOS/nix#8892 with our own fix for Intel Mac.
Comment on lines +776 to +783
if (auto what = flake_schemas::what(leaf))
obj.emplace("what", what);

if (auto shortDescription = flake_schemas::shortDescription(leaf))
obj.emplace("shortDescription", shortDescription);

if (auto drv = flake_schemas::derivation(leaf))
obj.emplace("derivationName", drv->getAttr(state->sName)->getString());
Copy link
Member

@shivaraj-bh shivaraj-bh Sep 2, 2024

Choose a reason for hiding this comment

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

Apart from recognising what, shortDescription and derivation as the leaf types, it would be nice to also recognise arbitrary values in the configuration.

Perhaps like:

From 1d23c1e871981f5666a12c4409bd0574fc1e1e02 Mon Sep 17 00:00:00 2001
From: shivaraj-bh <[email protected]>
Date: Wed, 21 Aug 2024 15:14:03 +0530
Subject: [PATCH] support value as leaf node

---
 src/nix/flake.cc | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index f470d847bf8..6960a3e9e89 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -18,6 +18,7 @@
 #include "markdown.hh"
 #include "users.hh"
 #include "flake-schemas.hh"
+#include "value-to-json.hh"
 
 #include <nlohmann/json.hpp>
 #include <queue>
@@ -782,6 +783,13 @@ struct CmdFlakeShow : FlakeCommand, MixJSON, flake_schemas::MixFlakeSchemas
                         if (auto drv = flake_schemas::derivation(leaf))
                             obj.emplace("derivationName", drv->getAttr(state->sName)->getString());
 
+                        // TODO: Add a function in flake-schemas.hh to handle this
+                        if (auto value = leaf->maybeGetAttr("value")) {
+                            auto v = value->forceValue();
+                            NixStringContext context;
+                            obj.emplace("value", printValueAsJSON(*state, true, v, noPos, context, false));
+                        }
+
                         // FIXME: add more stuff
                     },
 

If this is out of scope of this PR, I could raise a separate PR after this is merged, describing the use-case in more detail.

JayRovacsek added a commit to JayRovacsek/nix-config that referenced this pull request Oct 8, 2024
refactor(flake): remove schema code - readd once NixOS/nix#8892 is closed
@PedroHLC
Copy link
Member

PedroHLC commented Nov 3, 2024

Is there any rebased version of this branch?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation new-cli Relating to the "nix" command 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.

Custom flake checkers (as plugins?)