NixOS / nix

Nix, the purely functional package manager
https://nixos.org/
GNU Lesser General Public License v2.1
12.92k stars 1.53k forks source link

Provide builtins.currentSystem as a input to flakes or make system a parameter #3843

Open Mic92 opened 4 years ago

Mic92 commented 4 years ago

Is your feature request related to a problem? Please describe.

Right now one has to explicitly define system in flake outputs. The nix flake itself already comes with boiler code like this:

outputs = { self } : let
   # ...
   forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
{
  defaultPackage = forAllSystems (system: self.packages.${system}.nix);;  
};

I think having to use that much code hurts portability of the ecosystem because people will only specify the minimum and there seems no easy way of overriding it without changing the flake itself.

Describe the solution you'd like

outputs = { self, system } : {
  defaultPackage.${system} = self.packages.${system};  
};

Describe alternatives you've considered

Use boilercode in every project or rely on external libraries like https://github.com/numtide/flake-utils

If someone needs to explicitly specify platforms i.e. to build packages for different architectures with hydra this should be still possible.

Mic92 commented 4 years ago

cc @zimbatm who is involved in flake-utils

edolstra commented 4 years ago

Not passing in system (or any other arguments) is intentional:

zimbatm commented 4 years ago

There are multiple usability issues with the current design:

The list of supported systems is currently internal to the flake. As a flake author, it is useful to be able to specify which systems are supported. As a flake user, it means that it is not possible to change that list without forking the repository. Especially once the flake ecosystem starts to grow, it will quickly become prohibitive to introduce new platforms.

The amount of boilerplate that is needed to specify all the permutations of systems for all the derivations. That's why I wrote flake-utils, to make that usage a bit less cumbersome. The outputs are mixing both system-dependent and independent keys so it's not as easy as passing the system as an argument.

Typing x86_64-linux is error-prone. When a typo happens, the feedback system is not able to point to the typo directly. The user will see that their system is not supported, and then have to make the mental connection to look for a typo.

Each flake ends-up re-initializing nixpkgs. This adds to the boilerplate and evaluation time.


One possibility would be to introduce a new systems attribute to the top-level that lists all the supported systems by the flake. This has the advantage of making that list discoverable with tooling. And introduce typo checks for common archs. Then split the outputs to generic and system-dependent:

{
  description = "Usability flakes";
  systems = [ "x86_64-linux" "x86_64-darwin"];
  inputs = {}; # nothing changes here

  # system-independent outputs
  exports = inputs: {
    nixosConfiguration = {};
    overlay = final: prev: {};
    lib = {};
  };

  # system-dependent outputs
  outputs = system: { self, nixpkgs, ... }@inputs:
  let
    # I don't have a good idea to avoid that boilerplate
    pkgs = import nixpkgs {
      # Notice that the overlay is accessible via `self`
      overlays = [ self.overlay ];
      inherit system;
    };
  in
  {
    packages.hello = pkgs.hello;
    apps = {};
  };
}

One of the use-case that this design prevents is combining the derivations of multiple systems. This is for example if you want to create a release tarball that includes multiple architectures. In practice, that kind of construct is quite difficult to build outside of CI systems which have all the archs attached as remote builders.

zimbatm commented 4 years ago

Another possibility is that systems could inherit the list from the nixpkgs flake if left unspecified. That way, if nixpkgs gain a new architecture it will be easier to upgrade all of the flakes.

7c6f434c commented 4 years ago

Could we recognise the usefulness of ternary logic known-good/known-broken/unsure? The less overhead there is in checking whether something never-tried works due to upstream efforts, the better information we will have available.

DavHau commented 4 years ago

The original RFC states:

Flakes provide an attribute set of values, such as packages, Nixpkgs overlays, NixOS modules, library functions, Hydra jobs, nix-shell definitions, etc.

Is my assumption correct, that the idea of exposinglibrary functions has been abandoned? Since it's not possible to pass any arguments, it will never be possible to use a function of a flake?

I'm looking for exactly that use case. I'd like to expose a function that produces a python derivation, given a set of constraints. Currently I have to use a really weird hack where I generate an infinitely deep attribute set, to allow the user to select packages:

nix run github:davhau/mach-nix#with.requests.tensorflow.aiohttp

With this trick, it's already possible to build constructs which allow passing arguments (kind of). Why not support it properly?

edolstra commented 4 years ago

No, you can definitely have library functions as flake outputs. You just can't call them from the command line.

Why not support it properly?

See my comment above.

Having said that, the configs branch does add experimental support for setting Nix module options from the command line (dc4a280318650762a79447dbb299aef887f61d2e).

nixos-discourse commented 4 years ago

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

https://discourse.nixos.org/t/flakes-with-unfree-licenses/9405/1

MagicRB commented 3 years ago

I have similar problem as @DavHau, I'd like to give users of my flake the ability to compile firmware from the command line. The ideal UX is that one would write a config file, describing the build for the build system of the original project and then a derivation would be built, which would contain just the resulting .bin. This is currently impossible with flakes as I can't pass arguments into my flake from the command line.

What's the alternative?

zimbatm commented 3 years ago

What format is the config file in? nix run github:user/repo#compile --config ./myconfig.toml would work fine for example. nix run and nix develop use the current system to find the attribute. If the user wants to compose Nix files then they need another flake.

MagicRB commented 3 years ago

it's Kconfig, the kernel like build config

domenkozar commented 3 years ago

I wrote a proposal without knowing about this issue.

@Mic92 pointed me to it later on and it's interesting to observe that I've come to the same conclusions.

https://www.notion.so/Flakes-system-as-an-input-instead-of-an-output-2d2cdef8eac2434a833d6faae15b35c0

thufschmitt commented 3 years ago

I strongly support the idea of making the system handling simpler − esp. given that 80% of the time people just don’t care about it.

I think the current design has a really good thing though, in that it’s conceptually very simple (the only part of the Nix codebase that has to know about the notion of a flake “system” is the cli shortcut that expands foo#bar into foo#packages.x86_64-linux.bar). So although it’s painful and verbose, it’s explicit and doesn’t have any hidden magic.

(commenting here on @domenkozar's doc because I can't comment inline on it)

Flake would still pass the system implicitly by default (as it already does today when selecting the output attribute) but it would be explicit in flake.nix inputs. There's already --system XXX argument to nix command line that can be used to override the default value matching host.

I guess you'd need a way to override the system arguments of the input flakes too (and separately from --system) for building multi-arch systems (Edit: I see this is handled by still having a .{system} attribute in the flake outputs, but that makes things slightly weird as it means that the system becomes both an input and an output)

We would also need to add top-level attribute supportedSystems = [ ... ] that would allow building hydraJobs and checks for all systems.

Then that leaves the problem that “The list of supported systems is currently internal to the flake” (Edit: you use inputs.nixpkgs.supportedSystems = [ ... ]; in a code snippet, which seems to imply that this could be overriden. In that case it's worth pointing out explicitely).

It is hermetic, the outputs would depend on the system input. Flakes caching can take the system into the account when calculating the key cache.

Maybe it’s the case (need to think it a bit more), but this only holds as long as Nix can generate a system-independent lockfile (because it would be pretty bad if the lockfile couldn’t be shared between two different systems)

There could be --local-system argument to flake commands to restrict evaluation to only one system.

Alternatively, that could be the default with --all-system providing the current default.

That bit isn't really clear to me: If system is an input, then evaluating the flake will only evaluate it for the system that's given as input. Or do you mean that a flake would actually evaluate to something like map (system: outputs (inputs // { inherit system; }) supportedSystems?

You'll have to evaluate each input with a different system. That can now be done in parallel.

I don’t think it’s really easier to parallelize this than the current version. The blocker in both cases is that the evaluator isn’t reentrant.

thufschmitt commented 3 years ago

Maybe a (relatively) small change to solve the problem of systems being hardcoded in a flake (which might also solve other issues) would be to allow raw Nix values as input for a flake (this shouldn’t mess-up caching as long as these values end-up in the lockfile). Then we could have a convention that flakes accept a supportedSystems input − in which case it could be overriden without having to fork the flake.

That does in a way add yet-more-convention and doesn’t do anything for reducing the boilerplate so I’m not sure it’s a good idea as-it-is, but there might be something to build on top of that.

domenkozar commented 3 years ago

I strongly support the idea of making the system handling simpler − esp. given that 80% of the time people just don’t care about it.

Thanks for putting it so succinctly!

I think the current design has a really good thing though, in that it’s conceptually very simple (the only part of the Nix codebase that has to know about the notion of a flake “system” is the cli shortcut that expands foo#bar into foo#packages.x86_64-linux.bar). So although it’s painful and verbose, it’s explicit and doesn’t have any hidden magic.

That's because it pushes most of the questions to the writer of the flake. I consider that a con, because things have to be implemented over and over again.

I guess you'd need a way to override the system arguments of the input flakes too (and separately from --system) for building multi-arch systems (Edit: I see this is handled by still having a .{system} attribute in the flake outputs, but that makes things slightly weird as it means that the system becomes both an input and an output)

It wouldn't be an output, but you'd declare it as a setting for the input itself - evaluating the same input for different systems. I'll update the document to clarify this.

Then that leaves the problem that “The list of supported systems is currently internal to the flake”

It gives the space for warning the user that the system isn't supported by the flake, instead of getting an attribute error. That seems quite an improvement for the experience.

Maybe it’s the case (need to think it a bit more), but this only holds as long as Nix can generate a system-independent lockfile (because it would be pretty bad if the lockfile couldn’t be shared between two different systems)

Looking at the lockfiles (there's, unfortunately, no documentation about them), what does narHash consist of?

That bit isn't really clear to me: If system is an input, then evaluating the flake will only evaluate it for the system that's given as input. Or do you mean that a flake would actually evaluate to something like map (system: outputs (inputs // { inherit system; }) supportedSystems?

It would mean you can evaluate the flake for all given systems, for example on a CI to make sure everything evaluates.

I don’t think it’s really easier to parallelize this than the current version. The blocker in both cases is that the evaluator isn’t reentrant.

It's parallelizable because one needs to pass system as an input, so when evaluating for more than one system, that's one evaluation per system.

Maybe a (relatively) small change to solve the problem of systems being hardcoded in a flake (which might also solve other issues) would be to allow raw Nix values as input for a flake (this shouldn’t mess-up caching as long as these values end-up in the lockfile). Then we could have a convention that flakes accept a supportedSystems input − in which case it could be overriden without having to fork the flake.

That will relative flakes would really go a long way with modularity. I'd still make supportedSystem a special meaning for warnings, etc as mentioned above.

That does in a way add yet-more-convention and doesn’t do anything for reducing the boilerplate so I’m not sure it’s a good idea as-it-is, but there might be something to build on top of that.

Yes, this design change is "convention over configuration" applied to dealing with system. Why do you think more conventions is bad?

nixos-discourse commented 3 years ago

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

https://discourse.nixos.org/t/improving-flakes/12831/24

Ericson2314 commented 3 years ago

I agree with the gist of this change. The larger issue though is Flakes is constantly lagging behind idioms in Nixpkgs that have been polished over the years. For example:

  1. We don't just pass one system, we pass stdenv.{build,host,target}Platform which provided much more detailed information and supports cross compilation.
  2. lib.platforms used to be not a list of systems (again, too crude) but an arbitrary predicate on the host platform, which allowed intent to be conveyed much more precisely. This was reverted because it broke some (IMO layer-violating) feature of Hydra or nix-env.

We could incorporate all this stuff in Nix for Flakes, but that leaves Flakes quite brittle when we can and do refine this stuff from time to time in Nixpkgs. Or maybe we should implement more of Flakes in Nix. Factoring out lib/ and such things to make a standard library of sorts so we still have some agility. But that that point, what are flakes? Just something to manage inputs / lockfile without much extra structure?

edolstra commented 3 years ago

The larger issue though is Flakes is constantly lagging behind idioms in Nixpkgs that have been polished over the years.

Maybe the nix UI, but not flakes, because...

Just something to manage inputs / lockfile without much extra structure?

That's basically correct. The flake file format doesn't impose a lot of requirements on what the output attributes of a flake are. Thus it can accommodate whatever idioms regarding systems/platforms we may come up with in the future. (This is also why we shouldn't have a system input attribute, because that would lock us into a particular idiom.)

OTOH, various tools can define "well-known" flake output attributes, e.g. nix build looks for packages.<name>.<system> by default, as a convenience. These can of course evolve, e.g. we could have a packages_v2 output that uses a different encoding for system types.

This was reverted because it broke some (IMO layer-violating) feature of Hydra or nix-env.

The semantics of those attributes are determined by the tools that operate on them. You can't just change the meaning of platforms and then complain that nix-env barfs :-)

edolstra commented 3 years ago

Looking at the lockfiles (there's, unfortunately, no documentation about them), what does narHash consist of?

Lock files are documented here: https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html#lock-files

It's parallelizable because one needs to pass system as an input, so when evaluating for more than one system, that's one evaluation per system.

The current hydra-evaluator is already parallel. Having to deal with a system input type would just make it more complex since it then has two axes of parallelism to deal with: the system type and the jobs for each system type.

gilligan commented 3 years ago

That's basically correct. The flake file format doesn't impose a lot of requirements on what the output attributes of a flake are. Thus it can accommodate whatever idioms regarding systems/platforms we may come up with in the future. (This is also why we shouldn't have a system input attribute, because that would lock us into a particular idiom.)

I understand what you are arguing for, but I am confused because to me it seems like this might at odds with the goals of nix flakes as declared in the abstract of the RFC

Abstract: This RFC proposes a mechanism to package Nix expressions into composable entities called "flakes". Flakes allow hermetic, reproducible evaluation of multi-repository Nix projects; impose a discoverable, standard structure on Nix projects; and replace previous mechanisms such as Nix channels and the Nix search path.

So "standard structure" is indeed only to be understood as "something to manage inputs / lockfile" as @Ericson2314 put it above?

edolstra commented 3 years ago

No, the standard interface to Nix projects is on the one hand the file format and hermetic evaluation model, and on the other hand having some standard flake output attributes recognized by tools (so that e.g. running nix develop in a project does the right thing). But the latter is not required by the file format.

dnr commented 3 years ago

(With the caveat that most people on this thread have thought about this much longer than I have:)

The flake file format doesn't impose a lot of requirements on what the output attributes of a flake are. Thus it can accommodate whatever idioms regarding systems/platforms we may come up with in the future. (This is also why we shouldn't have a system input attribute, because that would lock us into a particular idiom.)

It sounds like this is drawing a distinction between two levels of schema: the flake file format, and the well-known output attributes, and suggesting that the latter is easier to evolve, and the former should be basically locked and never evolve.

As a user, it seems like there's not actually a practical difference between the two levels. Most of the implementation of both is in C++ that I'm not going to look at or modify (and would take a big effort to understand if I needed to).

If we did move from packages to packages_v2 with different schema, almost all tools in the ecosystem would have to change, because pretty much everything wants to know about packages. But moving to outputs_v2 with a different signature would require pretty much the same set of tools to change. If the changes required to evolve outputs instead of packages would be in different source code files in the nix binary, and even if they were more extensive, that's all pretty much irrelevant to me, since I consider the nix binary a monolithic thing.

domenkozar commented 3 years ago

Another good example is nix-darwin, which calls nixpkgs inside the darwin configuration.

This is where the design has to be different to NixOS, because in NixOS nixpkgs is evaluated outside the supplied configuration.

So the output interface starts to differ, introducing cognitive overhead.

NixOS:

nixosConfigurations.<hostname> = {};

nix-darwin:

darwinConfigurations.<system>.<name> = {};

The other alternative is to tell people to hardcode the system, but that's another way to wrestle with the design.

Consider also the case where aarch64-darwin was introduced to only one system in nix-darwin, if system was an input this wouldn't be a breaking change.

Mic92 commented 3 years ago

In my experience the evaluation cache is also not very useful as any changed bit will invalidate the cache - it's only useful for searching flakes, where one could have a way simpler implementation.

nixos-discourse commented 3 years ago

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

https://discourse.nixos.org/t/passing-options-to-flakes/7579/3

roberth commented 3 years ago

Having each and every project enumerate systems leads to nonsense churn prs like this https://github.com/cachix/pre-commit-hooks.nix/pull/122.

edolstra commented 3 years ago

Having each and every project enumerate systems leads to nonsense churn prs like this cachix/pre-commit-hooks.nix#122.

Isn't this exactly the opposite of what you describe? That project was relying on an external list of systems (defaultSystems in flake-utils) rather than enumerating supported systems itself.

roberth commented 3 years ago

There's no reason for it not to support any system really. If a system is not supported, that's a problem in a dependency, not in the flake itself.

edolstra commented 3 years ago

No, if a flake exposes an attribute like packages.x86_64-linux.foo, it advertises that the foo package works on x86_64-linux. If it doesn't work then it shouldn't expose that attribute.

roberth commented 3 years ago

Sure, that's nice, but not something flakes can "guarantee" without preemptively breaking support for less-used systems. Heck, attributes like overlays and nixosModules can never be guaranteed to work. Requiring the enumeration of systems is too simplistic of an approach.

Ericson2314 commented 3 years ago

This is where honestly flakes are a regression in my book. In projects I had worked on, there was a default.nix which was just the reusable part, with no whitelist of supported systems or anything like that. and then a release.nix that enumerated the known-good arguments (be they the system, or other sorts of arguments) to the default.nix for CI , and the release.nix wasn't meant to be used downstream,

Flakes trying to put all that in one file is an unavoidably a mistake, in my book. It's like combing the foo.cabal and cabal.project into one file (Like Cargo does really awkwardly).

Also, enumerating the systems is not even enough. https://github.com/NixOS/nix/pull/4996 shows we often need more grunularity. Seem thing if we remember that the localSystem and crossSystem arguments to Nixpkgs also contain far more info. I think while Flakes conflate the two concerns I described, there is a pressure to make it all work with just builtin.system to cut down on complexity, but if we had too files, we would feel more comfortable making the CI one as complex as it needs to be, since it won't complicate anything downstream.

roberth commented 3 years ago

They wouldn't have to be two files for the separation of concerns to be achieved. The same can be done with two attributes (or groups of attributes), so flake.nix can be a single file, like in the current design.

dnr commented 3 years ago

A short story: A while ago, I had an armv7l system running Arch. I wanted to build a package from the AUR, so I downloaded the PKGBUILD and tried to build it, and it didn't work, because armv7l was missing from the arch list. I manually added it, and it built and ran perfectly. But it felt kind of weird: why shouldn't it just let me build it and see what happens?

This feels like a similar situation: what if I'm using a flake that declares a few x86 systems because that's all the author bothered to test, but I want to build it on my ARM system? I have to fork it and make changes to the list? (And the list isn't even guaranteed to be static.. in the worse case I could have to read and understand code that derives it, dig into dependencies, etc.)

It's one thing for the author to provide lists of known-working, or supported, systems (which might be different lists!), and it would be great to get a warning if I try to build on a system that isn't known to work, but the tools should certainly let me just try it.

Ericson2314 commented 3 years ago

They wouldn't have to be two files for the separation of concerns to be achieved. The same can be done with two attributes (or groups of attributes), so flake.nix can be a single file, like in the current design.

That is true, I just would want to make sure the other stuff is inaccessable downstream so it is very separated "by construction".

mstone commented 3 years ago

(Trying this comment again, since it has proved challenging to get right 😔)

I’d like to offer a slight reframing of the challenge here to see if it unlocks any new solution directions.

Thus: maybe part of what’s hard here is that while it’s completely reasonable at the nix CLI UX level to want to be able to show a list of packages that a developer has asserted should work, IMO at the level of the flake ecosystem it seems to be really beneficial:

a. that the build recipes encoded into flakes be able to be easily re-used by downstream flake authors, and

b. that these build recipes be able to be re-used by those downstream flake authors with minimal (and IMO preferably ~overriding-based rather than source-modification-based) changes on systems not tested, foreseen, or even not existing at the time the original flake was written.

Now, how can this be achieved?

Honestly, I’m not sure yet.

That said, what I think I do see is that the outputs well-known attr we have today only solves this combined description of the problem partially because while it does give a convenient interface to use to combine inputs with build recipes and to advertise packages for supported platforms, it does do in ways that interfere with both the “build recipe visibility” and “build recipe tweakability” goals that I’ve sketched above.

Thus, some questions:

  1. is there some way (maybe via documentation, templates, and examples) that we could make it easier for flake authors to write flakes that conform to current conventions while also exposing build recipes in a convenient-to-reuse-and-override form?
  2. (probably a terrible, or at least fraught idea, but for sake of completeness): could we give nix authors some more powerful tools to introspect over the nix-exprs that were evaluated to produce certain values in order to given them additional tweaking and reuse capabilities at runtime by default, so that source-level changes become less necessary? (e.g. some sort of dynamic scope for function arguments?)
  3. Alternatively, and maybe marginally more reasonably: is there some way that we could make things better here with a better “standard library”, perhaps one that makes it easier to graft support for a new architecture onto existing flakes, maybe as a sort of “middleware” that transforms inputs or their outputs before they are used to define the top-level flake’s outputs so that they ~magically become more broadly usable?
roberth commented 3 years ago

@domenkozar

Another good example is nix-darwin, which calls nixpkgs inside the darwin configuration.

This is where the design has to be different to NixOS, because in NixOS nixpkgs is evaluated outside the supplied configuration.

This isn't quite true. The nixpkgs.nix in NixOS gives you the flexibility to set pkgs directly from outside using the lexical scope, but this is somewhat unusual.

nix-darwin and NixOS are quite similar with respect to system. The main difference is that nix-darwin needs to get its nixpkgs from somewhere, but that is not the problem here.

So the output interface starts to differ, introducing cognitive overhead.

NixOS:

nixosConfigurations.<hostname> = {};

nix-darwin:

darwinConfigurations.<system>.<name> = {};

Isn't it already darwinConfigurations.<hostname>?

The other alternative is to tell people to hardcode the system, but that's another way to wrestle with the design.

Unlike darwinModules, aren't darwinConfigurations supposed to be configurations for real-world systems? Those have a native system which makes sense to specify. On NixOS I'd expect to find it in hardware-configuration.nix. Nix-darwin doesn't have that.

Consider also the case where aarch64-darwin was introduced to only one system in nix-darwin, if system was an input this wouldn't be a breaking change.

If you want to do remote deployments, you can not assume the remote host's system to be the same as the currentSystem.

It could be argued that if you intend your darwin configuration to be system-independent, you should use darwinModules. Migrating to a new machine is handled by adding and removing darwinConfigurations. Migrating from Rosetta to native amounts to changing darwinConfiguration's system config value (presumably; no experience with this specifically).


This shows that we need two functions in a flake to represent the two contexts: one where system is passed in and one where it isn't. It does not rely on an implicit currentSystem. I've described this idea here as well https://github.com/NixOS/nix/issues/5256#issuecomment-930038843. It could be generalized not just to system but to cross compilation configs as well.

DavHau commented 3 years ago

Not allowing users to easily build outputs for unsupported platforms will just force maintainers into a dilemma, which will eventually lead to an inconsistency across the ecosystem. Why is that?

Example: I develop something on linux and I'm collaborating with people who develop on darwin. Whenever I release something, I'm in the following dilemma:

  1. Do I only add my own system to supportedSystems, not allowing macos users to build anything or
  2. Do I already add darwin to supportedSystems, despite I cannot test it, because I don't want to make them go through the annoyance of having to make another PR.

It does not matter what my personal opinion is about which of the two approaches is the correct one. The community will inevitable split into two camps, ones who decide to signal platform support by the existence of flake attributes, and ones who do not.

This results in an inconsistency across the ecosystem.

However, if we allowed users to signal support for platforms without forcing them to restrict building, there wouldn't be a reason for this inconsistency to evolve..

kamadorueda commented 2 years ago

Summary of the discussion as of 2022-03-10

https://github.com/NixOS/nix/issues/3843

This focuses on the motivation and not the solutions, so that we can understand what we are trying to solve before jumping into implementation details

roberth commented 2 years ago

Great summary. Thanks @kamadorueda!

  • Evaluation cache is fragile and thus not useful, a tiny bit change invalidates it, so maybe it's not worth sacrificing other features in the name of having a cache.

This is even a false dilemma, although I'll be happy to see the flake cache replaced by something more general and fine-grained like #6228. system is not a problem for an evaluation cache. It just means that the lookup key should include system. You can already simulate this by creating a flake that defines a single system string in lib and use it as an input in other flakes. Voila, a cached, overridable system (inputs.*.follows).

Ericson2314 commented 2 years ago

@roberth is exactly right. Sorry I missed the call today but looks like all good points were made!

gytis-ivaskevicius commented 2 years ago

Let's get this discussion back on track. By the looks of things, everyone except Eelco are in favor of this feature but that definitely does not mean that @edolstra is wrong, if anything he is protecting us from being caught with our pants down

@edolstra there must be some middle ground that we could agree on, here are a couple of ideas:

  1. flake.nix schema versioning
  2. Multi schema support. We could have multiple flake.nix schemas that get added/deprecated as the Nix ecosystem evolves. Template type could be passed as part of the file name (for example: xyz.flake.nix)
  3. Passing system as an attribute set instead of a string. This should keep the schema flexible and resolve this issue. Example:

    { 
    inputs.nixpkgs.url = github:NixOS/nixpkgs;
    
    nixConfig.systems = inputs: [
    { __toString = it: it.hostSystem; targetSystem = "x86_64-linux"; hostSystem = "x86_64-linux"; buildSystem = "x86_64-linux";}
    ];  
    
    outputs = { nixpkgs, system, ...}@inputs {
    
    };
    }
roberth commented 2 years ago

The following schema is more flexible and is self-contained within outputs, which is great for flakes that rely on libraries to define their outputs.


{
  outputs = inputs@{ nixpkgs, ... }: {
    perSystem = system: {
      #                old style legacyPackages just for reference
      packages.hello = nixpkgs.legacyPackages.${system}.hello;

      packages.default = (self.perSystem system).packages.hello;
    };

    # This allows Nix to memoize `perSystem` as part of the `self` fixpoint
    # using a `genAttrs` call. To be added in `call-flake.nix`
    systems = [ "x86_64-linux" "aarch64-darwin" ];
  };
  nixosConfigurations.zeus = let self' = self.perSystem "x86_64-linux"; in  .....;
}

call-flake.nix line where perSystem memoization can be implemented: https://github.com/NixOS/nix/blob/694b12052a2f3c830daa3acc7696b31a04afe329/src/libexpr/flake/call-flake.nix#L44

-self = result;
+self = memoize result;

where

memoize = outputs:
  let
    allSystems = genAttrs outputs.systems flake.perSystem;
    perSystem = system: allSystems.${system};
        # potentially: allSystems.${system} or (warn "system ${system} unsupported; trying to continue; ymmv" perSystem system);
        # might want to have more flexible memoization for that; see memoise branch
  in
    flake // { inherit perSystem; }; # perhaps also inherit allSystems

This also leaves room for attributes that are not generic across systems, such as configurations for specific machines. Those have a fixed system and it wouldn't make sense to build them for all systems. Same for nixpkgs.lib, nixosModules, etc.

Side note: Not coincidentally, this is very close to the original flake-parts schema when the perSystem type was an explicit functionTo submodule. Recent versions use deferredModule instead, so that system can be a module argument instead; a cosmetic change really. Users seem quite happy with the schema. You can find it here, but it's diluted by the original flake schema, which is also supported, and the back and forth conversion machinery. (and apologies for the primitive site as of writing) I guess what I was trying to say is that this schema has proven to be valid and useful.

In my example, the system is still a string, but I don't see a fundamental reason why it'd have to be. genAttrs goes out of the window, but could be replaced by a fancier memoization scheme, or a proper function memoization builtin as exists in the memoise branch. This makes the type of system a mostly orthogonal problem that can be addressed independently after implementing the flake schema outlined here.

flake.nix schema versioning

Technically avoidable while flakes are experimental. Might piss some people off. flake-parts can probably offer compatibility; haven't tried though. flake-utils might be able to provide a simple migration path too, as long as its users don't do a bunch of recursiveUpdate-esque processing on the returned outputs.

domenkozar commented 2 years ago

@thufschmitt do you have an opinion about this?

thufschmitt commented 2 years ago

@thufschmitt do you have an opinion about this?

I must confess that I haven't followed the last updates here. I still agree with the fact that there's a problem but I would need more time to really get an opinion on the solution

edolstra commented 2 years ago

Addressing this is more of a Flakes 2.0 feature, we should get the initial version of flakes stabilized first. Then we can think about potential solutions like configurable derivations (#6583) that would allow flakes to declare options (like system) in a discoverable way.

roberth commented 2 years ago

I wish there was something I could do, but it didn't work out that way.

arianvp commented 2 years ago

@edolstra could you give a quick summary (Maybe with an example config) how configurable derivations would solve this compared to the other suggestions here? Mostly so that it's clear from the discussion why it was concluded to not fix this now.

tobiasBora commented 1 year ago

Too bad, I hope that Flake 2.0 will quickly be on the road as it seems to me a crucial point to address (and we mostly agree on that with the @NixOS/documentation-team, as it's unclear for now what we should recommend to users, what template we should provide… as numtide/flake-utils has also multiple issues, see e.g. below).

One of the problem I have with the current system (beside the dirty and complex boilerplate, lack of flexibility and more…) is that it could easily lead to an important security issue : for now most of the flakes rely on numtide/flake-utils, which is not directly maintained by the nixpkgs team. If the github accound of any numtide's member is corrupted or hacked, it can really easily be easy to introduce a malware into every single flake based on it, that would be introduced on the next software update. This issue https://github.com/numtide/flake-utils/issues/51 was meant to transfer this repository back to nixpkgs, but it's wanting… mostly because people believe that we should, once for all, sort this issue properly.

Otherwise, just a few random ideas:

nixos-discourse commented 1 year ago

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

https://discourse.nixos.org/t/why-is-arg-and-argstr-incompatible-with-flakes/27800/2

nixos-discourse commented 1 year ago

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

https://discourse.nixos.org/t/can-i-use-flakes-within-a-git-repo-without-committing-flake-nix/18196/33