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

[RFC 0049] Flakes #49

Closed
wants to merge 42 commits into from
Closed
Changes from 29 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
13b4857
Flakes RFC
edolstra Jul 9, 2019
734d920
Updates
edolstra Jul 9, 2019
3b16530
Update date
edolstra Jul 9, 2019
c83185c
fix typo
zimbatm Jul 11, 2019
cc5d0a2
Rename 'epoch' -> 'edition'
edolstra Jul 11, 2019
f2cf8a8
Rename to RFC #49
edolstra Jul 15, 2019
1441693
Typo
edolstra Jul 15, 2019
9334af1
Update rfcs/0049-flakes.md
zimbatm Jul 16, 2019
57f139b
Update rfcs/0049-flakes.md
zimbatm Jul 16, 2019
368d932
Remove mention of apps and other minor fixes
edolstra Jul 19, 2019
f2c6f44
Typo
edolstra Jul 19, 2019
4bcf196
Note about caching
edolstra Jul 19, 2019
15f1c51
Lock file is only created if it doesn't exist
edolstra Jul 19, 2019
b2d6b9c
Transposition typo GitHub <-> Git
Jul 23, 2019
0d94186
Merge pull request #1 from toonn/flakes
edolstra Jul 29, 2019
859d44a
Add shepherd metadata
shlevy Aug 1, 2019
efa667f
Remove mention of evaluation caching
edolstra Aug 16, 2019
79e33d8
Mention XDG_CONFIG_HOME
edolstra Aug 16, 2019
970c31c
Use git+ prefix in URIs
edolstra Aug 16, 2019
d63b4cc
Typo
edolstra Aug 16, 2019
d053724
Remove superfluous name attribute
edolstra Aug 16, 2019
e04ad16
Document the new inputs syntax
edolstra Aug 30, 2019
2bdbe70
Change flakeref/attrpath seperator from ':' to '#' to reduce ambiguity
edolstra Sep 20, 2019
e09a75e
Drop Cargo/NPM comparison
edolstra Oct 1, 2019
fe0386d
Address why flakes shouldn't be done in a separate tool
edolstra Oct 1, 2019
7e9497c
uri -> url
edolstra Oct 8, 2019
c24dca8
Big simplification
edolstra Nov 4, 2019
3058b36
Bump edition
edolstra Nov 4, 2019
dd6c657
Tweaks
edolstra Nov 6, 2019
abb2799
Update example lock file
edolstra Feb 4, 2020
b60f889
Add system type to examples
edolstra Feb 4, 2020
95a9ff7
Document narHash attribute
edolstra Feb 4, 2020
969b079
Link to require.nix
edolstra Feb 4, 2020
3c734b8
Document input overrides / 'follows' attribute
edolstra Feb 4, 2020
07767e6
Change the lock file to a directed graph
edolstra Mar 6, 2020
421a9cd
Remove the 'edition' field
edolstra Apr 10, 2020
6fa3b7b
Fix example
edolstra Apr 12, 2020
bfa627e
lastModified -> lastModifiedDate
edolstra Apr 15, 2020
8c36f76
Typo
edolstra May 14, 2020
f053ecf
Update rfcs/0049-flakes.md
domenkozar May 29, 2020
0dbf231
Update to lock file version 6
edolstra May 29, 2020
ca2845b
Mention GitLab
edolstra Jun 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 381 additions & 0 deletions rfcs/0049-flakes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
---
feature: flakes
start-date: 2019-07-09
author: Eelco Dolstra
co-authors: TBD
shepherd-team: Domen Kožar, Alyssa Ross, Shea Levy, John Ericson
shepherd-leader: Domen Kožar
related-issues: (will contain links to implementation PRs)
---

# Summary
[summary]: #summary

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.

# Motivation
[motivation]: #motivation

Flakes are motivated by a number of serious shortcomings in Nix:

* While Nix pioneered reproducible builds, sadly, Nix expressions are
Copy link
Member

Choose a reason for hiding this comment

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

Mention how pure evaluation mode fits in and if/why that is not sufficient.

not nearly as reproducible as Nix builds. Nix expressions can access
arbitrary files (such as `~/.config/nixpkgs/config.nix`),
environment variables, and Git repositories. This means for instance
that it is hard to ensure reproducible evaluation of NixOS or NixOps
configurations.

* Nix projects lack discoverability and a standard structure. For
example, it's just convention that a repository has a `release.nix`
for Hydra jobs and a `default.nix` for packages.
Copy link
Member

Choose a reason for hiding this comment

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

Isn't default.nix and shell.nix assumed by enough tools to be a bit more than mere convention?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, default.nix has no well-known structure. I've seen it contain a single package at top-level, an attrset of packages, and an attrset of arbitrary stuff (like functions).


* There is no standard way to compose Nix projects. Typical ways are
to rely on the Nix search path (e.g. `import <nixpkgs>`) or to use
`fetchGit` or `fetchTarball`. The former has poor reproducibility,
while the latter is bad UX because of the need to manually update
Git hashes to update dependencies.

* `nix-channel` needs a replacement: channels are hard to create,
users cannot easily pin specific versions of channels, channels
interact in *ad hoc* ways with the Nix search path, and so on.

The flakes mechanism seeks to address all these problems. This RFC,
however, only describes the format and semantics of flakes; it doesn't
describe changes to the `nix` command to support flakes.

# Detailed design
[design]: #detailed-design

## Flakes

A flake is a directory that contains a file named `flake.nix` in the
edolstra marked this conversation as resolved.
Show resolved Hide resolved
root directory. `flake.nix` specifies some metadata about the flake
such as dependencies (called *inputs*), as well as its *outputs* (the
Nix values such as packages or NixOS modules provided by the flake).

As an example, below is the `flake.nix` of
[`dwarffs`](https://github.com/edolstra/dwarffs) (a FUSE filesystem
for automatically fetching DWARF debug symbols by ELF build ID). It
depends on the Nixpkgs flake and provides a package (i.e. an
installable derivation) and a NixOS module.

```
{
edition = 201911;

description = "A filesystem that fetches DWARF debug info from the Internet on demand";
Copy link
Member

Choose a reason for hiding this comment

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

Should we have an explicit name attribute like with derivations?

Copy link
Member Author

Choose a reason for hiding this comment

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

There was a name attribute, but I got rid of it because it wasn't used anymore. (Originally it was used to determine the names of the arguments to the outputs function, but now those are determined by the inputs attrset.)

Copy link
Member

Choose a reason for hiding this comment

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

Isn't having a name for talking of a flake in general (and not in terms of a specific URL) useful on its own? For example, when people inevitably end up fetching the flake sources into the store…

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe, but we can add such an attribute when there is a use case. Without that we can only speculate about what the semantics should be.

Copy link
Member

Choose a reason for hiding this comment

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

OK, I guess we could continue once the «download-all-dependencies» use case is addressed fully.


outputs = { self, nixpkgs }: rec {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the set be recursive? Just read yesterday about how recursive overlays should be avoided and the proper way is to use the self fixpoint.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it doesn't have to be recursive, you can also use self to refer to other outputs.

Copy link
Contributor

Choose a reason for hiding this comment

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

So the advice about overlays, specifically about a "recursive attrset", doesn't apply?

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, because this is not an overlay and there is no override mechanism at the moment (so the rec is the same as self.outputs).

packages.dwarffs =
with nixpkgs.packages;
with nixpkgs.builders;
Copy link
Member

Choose a reason for hiding this comment

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

Just tried this example and it failed with:

error: attribute 'builders' missing, at /nix/store/bwa7j11q87qcs02qh9z0wdqc4sc25q4g-source/flake.nix:7:12

After that I ran into this issue:

error: attribute 'packages' missing, at /nix/store/zi21mr9l9gn20q1an9z455s0fn9zpm54-source/flake.nix:6:12

I ended up with the following flake file:

{
  description = "A filesystem that fetches DWARF debug info from the Internet on demand";

  outputs = { self, nixpkgs }: rec {
    packages.x86_64-linux.dwarffs =
      with nixpkgs.packages.x86_64-linux;
#      with nixpkgs.builders;
      with nixpkgs.lib;

      stdenv.mkDerivation {
        name = "dwarffs-0.1.${substring 0 8 self.lastModifiedDate}";

        buildInputs = [ fuse nix nlohmann_json boost ];

        NIX_CFLAGS_COMPILE = "-I ${nix.dev}/include/nix -include ${nix.dev}/include/nix/config.h -D_FILE_OFFSET_BITS=64";

        src = self;

        installPhase =
          ''
            mkdir -p $out/bin $out/lib/systemd/system

            cp dwarffs $out/bin/
            ln -s dwarffs $out/bin/mount.fuse.dwarffs

            cp ${./run-dwarffs.mount} $out/lib/systemd/system/run-dwarffs.mount
            cp ${./run-dwarffs.automount} $out/lib/systemd/system/run-dwarffs.automount
          '';
      };

#    nixosModules.dwarffs = ...;

    defaultPackage.x86_64-linux = packages.x86_64-linux.dwarffs;

    checks.build = packages.x86_64-linux.dwarffs;
  };
}

Steps to reproduce:

  1. Spawn a nix-shell with nixFlakes: nix-shell -p nixFlakes (on recent/current nixos-unstable channel, commit 029a5de0839 in my case)
  2. Enable the flakes and nix-command feature in ~/.config/nix/nix.conf (verify via nix show-config | grep experi)
  3. Write the above flake.nix into a new (empty?) directory
  4. change to that new directory and execute nix build flake:.

Maybe it is worth exchanging the example with something simpler. How about a variant of the example(s) presented in the blog post?

with nixpkgs.lib;

stdenv.mkDerivation {
name = "dwarffs-0.1.${substring 0 8 self.lastModified}";

buildInputs = [ fuse nix nlohmann_json boost ];

NIX_CFLAGS_COMPILE = "-I ${nix.dev}/include/nix -include ${nix.dev}/include/nix/config.h -D_FILE_OFFSET_BITS=64";

src = self;

installPhase =
''
mkdir -p $out/bin $out/lib/systemd/system

cp dwarffs $out/bin/
ln -s dwarffs $out/bin/mount.fuse.dwarffs

cp ${./run-dwarffs.mount} $out/lib/systemd/system/run-dwarffs.mount
cp ${./run-dwarffs.automount} $out/lib/systemd/system/run-dwarffs.automount
'';
};

nixosModules.dwarffs = ...;
Copy link
Member

@Mic92 Mic92 Jul 6, 2020

Choose a reason for hiding this comment

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

Can I nest modules here? i.e. nixosModules.lenovo.thinkpad.x280
NixOS/nixos-hardware#176 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the current flake outputs (except legacyPackages) don't support nested attrsets in order to make it possible to query them efficiently.

Copy link
Member

Choose a reason for hiding this comment

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

I noticed this as well and wonder how we are going to handle sub package sets then. Maybe that's something for later after this RFC is accepted.

Copy link
Member

@Mic92 Mic92 Jul 6, 2020

Choose a reason for hiding this comment

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

We definitely need python3Packages.<foo> to work.


defaultPackage = packages.dwarffs;

checks.build = packages.dwarffs;
};
}
```
edolstra marked this conversation as resolved.
Show resolved Hide resolved

A flake has the following attributes:
Copy link
Member

Choose a reason for hiding this comment

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

Are those the only allowed values? Is a flake.nix invalid if it has other attribute?

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, any other attribute is an error.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should add that to the document. It currently leaves space for interpretation.

Copy link
Member

Choose a reason for hiding this comment

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

@edolstra would it then make sense to have a top-level attribute like meta for arbitrary configuration one might want per flake?


* `edition`: A number that specifies the version of the flake
Copy link
Member

Choose a reason for hiding this comment

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

(nitpick) while edition is a valid term, it's somewhat vague to me. It's common to use revision or version.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

But it has a very different meaning. In rust edition is opt-in: When a new edition becomes available in the compiler, crates must explicitly opt in to it to take full advantage.

According to text below it seems Nix will force the user to upgrade the version, so it's a different meaning.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the intent is definitely that Nix would support previous editions (though maybe not forever). You only get an error if you use a flake that uses an edition more recent than what your version of Nix supports.

Copy link

Choose a reason for hiding this comment

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

I think this should be optional for the first edition, since it's just line noise if we end up never breaking compatibility.

Copy link
Member

Choose a reason for hiding this comment

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

@edolstra I misunderstood the RFC then. Proposal to change:

It also enables some evolution of
the Nix language; for example, the Nix files in the flake could be
parsed using a syntax determined by the edition

to

Edition is opt-in, not bumping it means user keeps the current interpretation for the given edition.

Copy link
Member

Choose a reason for hiding this comment

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

I know Rust has had a lot of popularity with adding an edition flag, and we kinda did that because we wanted to fix things that were just bad in the initial design (namely, the module system which was confusing, and the way that macros are exported, which was more of a technical limitation of the time when 1.0 was stabilised).

I feel that declaring up front that nix will have editions, without being very clear about what kind of scope the syntax or semantics changes can include opens you up to "edition creep" of future versions, also potentially over-bloating the nix daemon because it will have to support all of these new editions.

Don't get me wrong: editions can be a great tool to fix mistakes of the past in a way that doesn't break everything that depends on them. But it's a commitment and also a maintenance burden. And without a clear roadmap of what should be fixed and why, just willy nilly adding an edition flag is, in my opinion, very dangerous!

syntax/semantics to be used. This allows the interpretation of
flakes to change in the future. It also enables some evolution of
the Nix language; for example, the Nix files in the flake could be
parsed using a syntax determined by the edition. The only currently
allowed value is `201911`. Nix rejects flakes with an unsupported
edition.

* `description`: A short description of the flake.

* `inputs`: An attrset specifying the dependencies of the flake
(described below).
Copy link
Member

Choose a reason for hiding this comment

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

Is it permissible to have some of the input attributes be computed based on the other inputs?


* `outputs`: A function that, given an attribute set containing the
outputs of each of the input flakes keyed by their identifier,
yields the Nix values provided by this flake. Thus, in the example
above, `inputs.nixpkgs` contains the result of the call to the
`outputs` function of the `nixpkgs` flake, and
`inputs.nixpkgs.packages.fuse` refers to the `packages.fuse` output
attribute of `nixpkgs`.

In addition to the outputs of each input, each input in `inputs`
edolstra marked this conversation as resolved.
Show resolved Hide resolved

Choose a reason for hiding this comment

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

Some of this metatdata seems to be quite input-type dependent (e.g. rev), whereas others seem like they'll be valid for any input type (e.g. outPath). Could we only include the generic ones here, and put the type-specific metadata along with the descriptions of the input types?

also contains some metadata about the inputs. These are:

Choose a reason for hiding this comment

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

What happens if a flake provides an output with the same name as one of the metadata attributes?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a more verbose way to refer to outputs of an input, namely inputs.<input-name>.outputs.<output-name> which doesn't contain the metadata attributes.


* `outPath`: The path in the Nix store of the flake's source
tree. This means that you could import Nixpkgs in a more
legacy-ish way by writing

with import inputs.nixpkgs { system = "x86_64-linux"; };
edolstra marked this conversation as resolved.
Show resolved Hide resolved

since `nixpkgs` still contains a `/default.nix`. In this case we
bypass its outputs entirely and only use the flake mechanism to
get its source tree.

* `rev`: The commit hash of the flake's Git repository.

Choose a reason for hiding this comment

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

nitpicking: how about Mercurial changeset IDs, etc? :p


* `revCount`: The number of ancestors of the revision `rev`. This is
not available for `github` repositories (see below), since they're
fetched as tarballs rather than as Git repositories.

* `lastModified`: The commit time of the revision `rev`, in the
format `%Y%m%d%H%M%S` (e.g. `20181231100934`). Unlike `revCount`,
this is available for both Git and GitHub repositories, so it's
useful for generating (hopefully) monotonically increasing version
edolstra marked this conversation as resolved.
Show resolved Hide resolved
strings.

Choose a reason for hiding this comment

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

Lost here as to what and why is lastModifiedDate. Is it an alternative for rev? Or is it an alternative for revCount (which then doesn't make sense for tarball/github fetcher)? Would a option tag serve the purpose?
"hopefully" and "version string" combination sounds scary, what is it expected to be used for?

Copy link
Member Author

Choose a reason for hiding this comment

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

revCount and lastModifiedDate are primarily there to help you generate version strings (e.g. in the name attribute of a derivation). For example, the dwarffs package from the dwarffs flake has a version like 0.1.20200511. lastModifiedDate is indeed an alternative to revCount since the latter isn't always available.

The "hopefully" refers to the fact that you can always fake timestamps with Git, but that's a case of "just don't do that" :-)


The value returned by the `outputs` function must be an attribute
set. The attributes can have arbitrary values; however, some tools
may require specific attributes to have a specific value (e.g. the
`nix` command may expect the value of `packages` to be an attribute
set of derivations).

## Flake inputs

The attribute `inputs` specifies the dependencies of a flake. These
specify the location of the dependency, or a symbolic flake identifier
Copy link
Member

Choose a reason for hiding this comment

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

Will it be possible to force-override an input location (with the user being responsible for validity of such subsitution)?

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.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe mention it somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

Well, I removed all user interface aspects from the RFC :-) But it's something like --override-flake OLD NEW.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I underestimated the completeness of removal of UI mentions. I thought it is about «how something is done», but some of the «what should be possible to do» is still specified.

that is looked up in a registry or in a command-line flag. For
example, the following specifies a dependency on the Nixpkgs and Hydra
domenkozar marked this conversation as resolved.
Show resolved Hide resolved
repositories:

# A GitHub repository.
inputs.import-cargo = {
type = "github";
owner = "edolstra";
repo = "import-cargo";
};

# An indirection through the flake registry.
inputs.nixpkgs.id = "nixpkgs";

Each input is fetched, evaluated and passed to the `outputs` function
as a set of attributes with the same name as the corresponding
input. The special input named `self` refers to the outputs and source
tree of *this* flake. Thus, a typical `outputs` function looks like
this:

outputs = { self, nixpkgs, import-cargo }: {
... outputs ...
};

It is also possible to omit inputs entirely and *only* list them as
expected function arguments in `outputs`. Thus,

outputs = { self, nixpkgs }: ...;

without an `inputs.nixpkgs` attribute will simply look up `nixpkgs` in
the flake registry.
Comment on lines +190 to +196

Choose a reason for hiding this comment

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

There is no specification for any sort of flake registry in this spec, what is this referring to?

Copy link
Member

Choose a reason for hiding this comment

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

https://github.com/NixOS/flake-registry

AFAIK it's like aliases for longer URLs

(there's a section on it in the blog post above)

Choose a reason for hiding this comment

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

Well yeah, but that's not part of the spec here, is it? I don't think it's a good idea to go ahead and specify "this implicitly talks to the flake registry" without specifying how the flake registry behaves, and how it will be ran.

Copy link
Member

Choose a reason for hiding this comment

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

True. Maybe it was dropped as part of this: https://github.com/NixOS/rfcs/pull/49/files#r354288001


Repositories that don't contain a `flake.nix` can also be used as
inputs, by setting the input's `flake` attribute to `false`:

inputs.grcov = {
type = "github";
owner = "mozilla";
repo = "grcov";
flake = false;
};

outputs = { self, nixpkgs, grcov }: {
packages.grcov = stdenv.mkDerivation {
src = grcov;
...
};
};

The following input types are specified at present:
Copy link
Member

Choose a reason for hiding this comment

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

Specified where? This doesn’t go into any more detail, and they’re never mentioned again.

Choose a reason for hiding this comment

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

It would be really nice to be able to define new input/fetcher types. This would be somewhat tricky, since most such definitions would rely on a nixpkgs, so you need a kind of overlay-style process where the fetchers and inputs are mutually defined (see nmattia/niv#137 for a POC implementation of such a scheme in niv).

However, otherwise I would guess that every fetcher needs to be implemented in the flake tool (nix, presumably), which makes it much harder to add, tweak, or improve them.


* `git`: A Git repository or dirty local working tree.

Choose a reason for hiding this comment

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

git protocol doesn't let you get to the working tree, so should be two-three different options

  • git repo (at tag/branch/commit)
  • local path
  • git repo (at tag/branch/commit) + path inside repo

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'll have to check - it might be that this description is out of date. It may be that git+file:///path already doesn't support a dirty tree.


* `github`: A more efficient scheme to fetch repositories from GitHub
edolstra marked this conversation as resolved.
Show resolved Hide resolved
as tarballs. These have slightly different semantics from `git`
(in particular, the `revCount` attribute is not available).
Copy link

@divanorama divanorama May 19, 2020

Choose a reason for hiding this comment

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

imo github option shouldn't be a part of the spec (but may have special shortcuts/tools in cli)

  • tarball fetch is just an instance of http fetch
  • the way github generates tarball urls may change in future
  • for private repos user may have configured ssh access but not http (via tokens)
  • github enterprise (on premise) may need some other setup

Copy link
Member

Choose a reason for hiding this comment

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

It would be nice for things like github to be provided by external plugins, but github fetch does have different semantics than http fetch, in part because of the things you call out here! IMO it should be a separate primitive.

Choose a reason for hiding this comment

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

Bitbucket for example also has tarball downloads of git repos, GitLab too, likely many others. So going this way would probably mean that in future more git (or even non-git) hosting services will need special handling.

Copy link

Choose a reason for hiding this comment

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

Other hosting services aren't the center of NixOS coordination, though..

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, most Nix-related repos are on GitHub, so in the interest of UX, it's nice to provide a shorter syntax and better semantics for GitHub. We don't have to support every other hosting service out there.

Copy link
Member

Choose a reason for hiding this comment

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

I find that most repos use feature/<name> branches, but I didn't find a way to make a URI containing this branch name. Is this expected behavior?

Copy link
Member

Choose a reason for hiding this comment

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

Right, most Nix-related repos are on GitHub, so in the interest of UX, it's nice to provide a shorter syntax and better semantics for GitHub. We don't have to support every other hosting service out there.

@rycee uses many GitLab inputs for the home-manager repo. Is GitLab may be also desired?

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, especially if we can get the rev attribute from GitLab.

Copy link
Member

Choose a reason for hiding this comment

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

I create a Libfetcher for GitLab: NixOS/nix#3636


* `tarball`: A `.tar.{gz,xz,bz2}` file.
Copy link
Member

Choose a reason for hiding this comment

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

Is this local, URL, or URL that can also be file:// URL?


* `path`: A directory in the file system. This generally should be
avoided in favor of `git` inputs, since `path` inputs have no
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
avoided in favor of `git` inputs, since `path` inputs have no
avoided in favor of `git` or `hg` inputs, since `path` inputs have no

concept of revisions (only a content hash) or tracked files
(anything in the source directory is copied).

* `hg`: A Mercurial repository.

## Lock files

Inputs specified in `flake.nix` are typically "unlocked" in that they
don't specify an exact revision. To ensure reproducibility, Nix will
automatically generate and use a *lock file* called `flake.lock` in
Copy link
Member

Choose a reason for hiding this comment

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

If a flake contains a locked (already in flake.nix) input, should there be an option to use that revision for all unlocked instances of the same input?

the flake's directory. The lock file contains a tree of mappings from
the inputs specified in `flake.nix` to inputs specifications that
contain revisions.

For example, if `flake.nix` has the inputs in the example above, then
the resulting lock file might be:
```
{
"version": 4,
"inputs": {
"import-cargo": {
"inputs": {},
"narHash": "sha256-mxwKMDFOrhjrBQhIWwwm8mmEugyx/oVlvBH1CKxchlw=",
"original": {
"type": "github",
"owner": "edolstra",
"repo": import-cargo"
},
"resolved": {
"type": "github",
"owner": "edolstra",
"repo": "import-cargo",
"rev": "c33e13881386931038d46a7aca4c9561144d582e"
}
},
"nixpkgs": {
"inputs": {},
"narHash": "sha256-p7UqhvhwS5MZfqUbLbFm+nfG/SMJrgpNXxWpRMFif8c=",
"original": {
"type": "github",
"owner": "NixOS",
"repo": nixpkgs"
},
"resolved": {
"type": "github",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4a7047c6e93e8480eb4ca7fd1fd5a2aa457d9082"
}
}
}
}
```

Thus, when we build this flake, the input `nixpkgs` is mapped to
revision `c33e13881386931038d46a7aca4c9561144d582e` of the
`edolstra/import-cargo` repository on GitHub. Nix will also check that
the content hash of the input is equal to the one recorded in the lock
file. This check is superfluous for Git repositories (since the commit
hash serves a similar purpose), but for GitHub archives, we cannot
directly check that the contents match the commit hash.

Note that lock files are only used at top-level: the `flake.lock`
files in dependencies (if they exist) are ignored. The lock file
transitively locks direct as well as indirect dependencies.
Copy link
Member

Choose a reason for hiding this comment

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

It would be useful to have an option when fetching a flake as input to use the built version of that flake according to its own lockfile. But that can be a future enhancement.


## Reproducible evaluation

Lock files are not sufficient by themselves to ensure reproducible
evaluation. We also need to disallow certain impurities that the Nix
language previously allowed. In particular, the following are
disallowed in a flake:
Comment on lines +412 to +415

Choose a reason for hiding this comment

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

Flakes for the most part feels like an attempt to solve the centralisation problem: it's too difficult / awkward to manage things outside of the nixpkgs monorepo. I can see how improving on reproducability is important, but why must it be inextricably linked with flakes?

These impurities have allowed existing tools (like my own nix-wrangle) to serve as testing grounds for similar approaches to flakes, which lets us try out new ideas before baking them into nix.

It seems reasonable for flakes to be able to lock down these features - i.e. to have an attribute which opts in or out of this strict reproducible mode, and have that be enforced by the evaluator. A strict flake couldn't depend on a non-strict flake, and there could potentially be a toplevel command line argument to nix to treat the toplevel environment as strict/lax, a bit like build-use-chroot and similar settings.

Copy link
Member

@7c6f434c 7c6f434c Dec 1, 2019

Choose a reason for hiding this comment

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

I am not sure flakes are going to solve the problems with packages maintained outside Nixpkgs at scale, so maybe reproducible evaluation is the main thing that has a chance of working with flakes. And maybe some UI-adjacent stuff for package enumeration.


* Access to files outside of the top-level flake or its inputs, as

Choose a reason for hiding this comment

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

Reading this, it's not clear to me whether I'd be able to do things like

packages.foo = inputs.nixpkgs.callPackage ./foo.nix {};

Is this "inside" the current flake?

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, ./foo.nix is inside the current flake.

well as paths fetched using `fetchTarball`, `fetchGit` and so on
without a commit hash or content hash. In particular this means that
Nixpkgs will not be able to use `~/.config/nixpkgs` anymore.
edolstra marked this conversation as resolved.
Show resolved Hide resolved

* Access to the environment. This means that `builtins.getEnv "<var>"`
always returns an empty string.
edolstra marked this conversation as resolved.
Show resolved Hide resolved

* Access to the system type (`builtins.currentSystem`).

* Access to the current time (`builtins.currentTime`).

* Use of the Nix search path (`<...>`); composition must be done
Copy link
Member

Choose a reason for hiding this comment

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

What rationale is there for continuing the <...> support in pure mode? It seems like it is a commonly disliked feature of Nix that we have a chance to remove! I would rather this be disallowed completely and instead rely on ordinary function arguments in these cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe you misread that, because <...> isn't allowed in pure mode. (That was already the case before flakes.)

through flake inputs or `fetchX` builtins.
Copy link
Member

@matthewbauer matthewbauer Sep 27, 2019

Choose a reason for hiding this comment

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

Does this mean that the fetchX builtins can still be used impurely? For instance can I do this:

(import (builtins.fetchTarball <nixpkgs>) {}).test

when I haven't added nixpkgs to my flakes.nix?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, they can't. You can only use the fetchX builtins if you supply a content/commit hash. (There's something to be said for banning them completely in flakes. That way, all external dependencies must be specified in inputs. But that might be going a bit too far right now.)

Copy link
Member

Choose a reason for hiding this comment

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

Given that we have more single-project fetchers in Nixpkgs than will be ever supported inside Nix, allowing use of FOD for import does sound like a good idea…


# Drawbacks
[drawbacks]: #drawbacks

Pure evaluation breaks certain workflows. In particular, it breaks the
use of the Nixpkgs configuration file. Similarly, there are people who
rely on `$NIX_PATH` to pass configuration data to NixOps
configurations.

edolstra marked this conversation as resolved.
Show resolved Hide resolved
# Alternatives
[alternatives]: #alternatives

For composition of multi-repository projects, the main alternative is
to continue on with explicit `fetchGit` / `fetchTarball` calls to pull
in other repositories. However, since there is no explicit listing of
dependencies, this does not provide automatic updating.

Choose a reason for hiding this comment

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

I'd still like to see some comparison with the state-of-the-art alternatives, e.g. niv. niv does have explicit listing of dependencies, and supports automatic updating.

Choose a reason for hiding this comment

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

Not sure what "explicit listing of dependencies" and "automated updating" mean here, can't match it with motivation section items. Is it about supplying updated nixpkgs to a flake and it's transitive dependencies?


Instead of a `flake.nix`, flakes could store their metadata in a
simpler format such as JSON or TOML. This avoids the Turing tarpit
where getting flake metadata requires the execution of an arbitrarily
complex, possibly non-terminating program.
edolstra marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Maybe a part of the flake metadata (edition, description, name if added) could live in a JSON file and the rest like outputs in flake.nix

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that's the idea.

Copy link
Member

Choose a reason for hiding this comment

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

Well, inputs are also almost metadata, and there keeping things in Turing-complete Nix has some benefits (and some drawbacks, sure); as written the alternative seems a bit too binary.

(I am among those who support basic part of the metdata being in JSON, but I do support input definitions near the output definitions, in Nix)


Flakes could be implemented as an external tool on top of Nix. Indeed,
there is nothing that flakes allow you to do that couldn't previously
be done using `fetchGit`, the `--pure-eval` flag and some shell
scripting. However, implementing flake-like functionality in an
external tool would defeat the goals of this RFC. First, it probably
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasoning behind Flakes deserving to be blessed as the standard before they've proven their merit or even having given other implementations a chance? Or is the intention to race RFCs in becoming the standard?

I like the Flakes RFC btw. I just don't see why it should be treated differently.

Copy link
Member Author

Choose a reason for hiding this comment

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

What's the reasoning behind Flakes deserving to be blessed as the standard before they've proven their merit or even having given other implementations a chance?

Well, no Nix feature has ever followed that process before being included, except nix-repl (and that was only because I didn't want an ncurses dependency in Nix at the time).

I'm very skeptical that external implementations would ever end up being merged in practice. See yum, which is still separate from rpm after 20 years.

Copy link
Member

Choose a reason for hiding this comment

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

Counterexamples: Cargo, go mod.

Copy link
Member

Choose a reason for hiding this comment

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

If anything, I think for a shift such as the one this RFC suggests, trying it out in practice somewhat at scale would make it easier to adopt it. That way, potential issues in the design can be revealed and iterated on/addressed before we bolt the design down.

I'm not really familiar with the rpm ecosystem, so I don't know why rpm and yum are separate tools. It might in part just boil down to historical reasons though.

I'm not sure I see why the (to me fairly marginal) risk of having two tools instead of one is bigger than the risk of the community moving in a direction and then looking back in retrospect and wanting to make changes to the flake design.

Don't get me wrong, I like a lot in the Flakes design. I just don't want us to have to repeat this process in a few years' time just because we didn't test the design well enough before giving it its blessing.

Choose a reason for hiding this comment

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

Given the current revision of this RFC to only talk about the flake format, this paragraph seems out of place.

Copy link
Member

Choose a reason for hiding this comment

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

I’m not sure it is. Whether it would be possible to generate this lockfile using an out-of-tree tool, or has to be implemented in Nix itself, is an important thing to know.

Choose a reason for hiding this comment

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

I don't know the full history, but I believe families like apt/deb, yum/rpm/dnf, gem/bundler were often built separately so as not to risk disruption by drastically modifying the (legacy) original tool, or to add on extra layers of functionality which only a subset of the community wanted (at the time).

If external-flakes is built with the explicit goal that it will be modified until it's either made part of nix or abandoned, that seems like it would alleviate that risk perfectly well.

I for one would love to try out a real implementation before comitting to it, since it's much harder to spot some usability or functionality gaps in an RFC than in practice. And I have a bunch of my own projects which are ripe for trying out a distributed approach to nix expressions.

Copy link
Member

Choose a reason for hiding this comment

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

I'm very skeptical that external implementations would ever end up being merged in practice. See yum, which is still separate from rpm after 20 years.

Technically speaking, Nix and Nixpkgs are still separate repositories, and there are some cases where it causes a need for more complicated coordination. Still, Nixpkgs is the defacto repository of packages for Nix, but there are forks that are usable by anyone who is interested.

At the same time, NixOS was a separate repository at some point, then got into the same monorepo. So merges happen here if they make sense.

What is the specific problem being addressed by flakes being built-in and not just the defacto tool?

wouldn't lead to a standard way to structure and compose Nix projects,
since we might well end up with numerous competing
Copy link
Member

Choose a reason for hiding this comment

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

I think home-manager is an example where we had exactly that, then one standard got majority?

"standards". Second, it would degrade rather than improve the Nix UX,
since users would now have to deal with Nix *and* the flake-like tool
on top of it.
Copy link
Member

Choose a reason for hiding this comment

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

The tool being a part of Nix doesn't do much to remove needing to keep track of flake-specific invokations

Copy link
Member Author

Choose a reason for hiding this comment

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

Not true, commands like nix build or nix dev-shell "just work" when invoked in a flake directory.

Copy link
Member

Choose a reason for hiding this comment

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

This part can be achieved by specifying that flake should have a default.nix (that imports flake.nix etc.)


# Unresolved questions
[unresolved]: #unresolved-questions

* Should flakes have arguments (like "system type")? This must be done
in a way that maintains hermetic evaluation and evaluation caching.

Choose a reason for hiding this comment

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

This seems very important to me. Nix and nixpkgs are shaping up to be a best-in-class solution to cross-compiling complex software. It would be a shame if flakes made this harder again.

Copy link
Member

Choose a reason for hiding this comment

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

Could this be implemented via inputs that are literal Nix values?


* Currently, if flake dependencies (repositories or branches) get
deleted upstream, rebuilding the flake may fail. (This is similar to
`fetchurl` referencing a stale URL.) We need a command to gather all
flake dependencies and copy them somewhere else (possibly vendor
them into the repository of the calling flake).
Copy link
Member

@7c6f434c 7c6f434c Dec 1, 2019

Choose a reason for hiding this comment

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

Could existing binary cache logic be reused here? In a locked flake all inputs are checksummed, so they can be built as FODs, which could be made fetchable even from untrusted caches.

Maybe this requires an extra field in flake metadata.

Anyway, as long as one doesn't vendor or binary cache all the sources and bootstrap binaries, one doesn't get fully safe.

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, in principle you could use a binary cache for this.

NixOS/nix#3253


* Maybe flake metadata should be stored in a `flake.json` or
`flake.toml` file. This would prevent ambiguities when the Nix
language changes in a future edition.
Copy link
Member

Choose a reason for hiding this comment

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

Sorry for being so late to the party, but while I understand that there were valid reasons to remove the edition-field in the end, wouldn't it make sense to keep a similar flag to make sure that flake.* files are interpreted properly even if we switch back and forth between e.g. .nix, .toml and sth else?

I'm asking because @edolstra stated in #49 (comment) that it's not 100% clear whether .nix-files are the way to go for flakes and I could imagine that we'd run into problems if we switch back to .nix with a different structure after several iterations.

Copy link
Member

@domenkozar domenkozar Jun 4, 2020

Choose a reason for hiding this comment

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

The decision was to name next version flakes2.nix or similar so there wouldn't be need of interpreting such files in a complex manner.


# Future work
[future]: #future-work
Copy link
Member

Choose a reason for hiding this comment

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

All of the CLI, etc. work should be mentioned here.

Copy link
Member

Choose a reason for hiding this comment

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

I also think that at least a short roadmap of what happens next should be added here before accepting the RFC. At least I'd feel more confident in this if I know how we'll proceed.


* The "edition" feature enables future Nix changes, including language
changes. For example, changing the parsing of multiline strings
(https://github.com/NixOS/nix/pull/2490) could be conditional on the
flake's edition.
edolstra marked this conversation as resolved.
Show resolved Hide resolved

* Currently flake outputs are untyped; we only have some conventions
about what they should be (e.g. `packages` should be an attribute
set of derivations). For discoverability, it would be nice if
outputs were typed. Maybe this could be done via the Nix
configurations concept
(https://gist.github.com/edolstra/29ce9d8ea399b703a7023073b0dbc00d).

# Acknowledgments

Funding for the development of the flakes prototype was provided by
[Target Corporation](https://www.target.com/).