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 0092] Computed derivations #92

Merged
merged 59 commits into from
Jan 12, 2022
Merged
Changes from 38 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
3a8338a
Initial draft "ret-cont" recursive Nix
Ericson2314 Feb 1, 2019
3b8422a
Fix typos and finish trailing sentance
Ericson2314 Feb 5, 2019
4da9193
Switch to advocating temp store rather than daemon socket
Ericson2314 Feb 5, 2019
800b5f3
ret-cont-recursive-nix: Fix typo
langston-barrett Feb 6, 2019
f708983
ret-cont-recursive-nix: Fix typo
Mic92 Feb 6, 2019
6a87c1b
ret-cont-recursive-nix: Fix typo
globin Feb 7, 2019
36193e5
ret-cont-recursive-nix: Fix typo
langston-barrett Feb 8, 2019
ffb9203
ret-cont-recursive-nix: Clean up motivation, adding examples
Ericson2314 Feb 10, 2019
5564fdb
ret-cont-recursive-nix: Improve syntax highlighting
Ericson2314 Feb 10, 2019
22f8322
Do a lousy job formalizing the detailed design
Ericson2314 Feb 11, 2019
7f5f854
ret-cont-recursive-nix: Mention `builtins.exec` in alternatives
Ericson2314 Feb 11, 2019
5c9f1fb
ret-cont-recursive-nix: Fix typo
Mic92 Feb 11, 2019
5e56f21
ret-cont-recursive-nix: Remove dangling "$o"
Ericson2314 Feb 25, 2019
ba7dcce
Update rfcs/0000-ret-cont-recursive-nix.md
Ericson2314 Aug 15, 2019
8bcb4e6
ret-cont-recursive: Fix typo
Ericson2314 Nov 2, 2019
baae1e6
ret-cont: Add examples and expand future work
Ericson2314 Nov 2, 2019
9448a2a
ret-cont: Fix syntax error
Ericson2314 Nov 2, 2019
37a643e
ret-cont: Mention Ninja's upcomming `dyndep` and C++ oppertunity
Ericson2314 Nov 2, 2019
14b134d
ret-cont: Fix missing explicit `outputs` and `__recursive`
Ericson2314 Nov 2, 2019
1b0a6a1
ret-cont: "caching builds" -> "caching evaluation"
Ericson2314 Nov 5, 2019
3fe1c3d
ret-cont: Improve formalism and reference #62
Ericson2314 Dec 12, 2019
a38f245
drv-build-drv: Start drafting from old ret-cont-recursive-nix RFC
Ericson2314 Feb 19, 2021
4022058
Merge remote-tracking branch 'upstream/master' into ret-cont
Ericson2314 Feb 19, 2021
6c01e4d
drv-buiild-drv: WIP rewrite
Ericson2314 Apr 4, 2021
6d97de1
plan-dynamism: Rewrite RFC yet again
Ericson2314 Apr 26, 2021
2079528
plan-dynamism: Rename file accordingly
Ericson2314 Apr 26, 2021
fd7bb41
plan-dynanism: Fix typo
Ericson2314 Apr 26, 2021
ec622cd
plan-dynanism: Fix formalism slightly
Ericson2314 Apr 26, 2021
ad74b68
Apply suggestions from code review
Ericson2314 Apr 26, 2021
0eaa6bb
plan-dynamism: `Buildables` -> `DerivedPathsWithHints`
Ericson2314 Apr 27, 2021
49070db
plan-dynamism: Add semantics and examples for `!` syntax
Ericson2314 Apr 27, 2021
c50ee43
plan-dynamism: Too many dashes in `--derivation`
Ericson2314 Apr 27, 2021
98ea32a
plan-dynanism: Put pupose of text hashing before name
Ericson2314 Jun 6, 2021
c01f07c
Apply suggestions from code review
Ericson2314 Jun 6, 2021
3f187a3
Apply suggestions from code review
Ericson2314 Jun 6, 2021
7d18780
Apply suggestions from code review
Ericson2314 Jun 6, 2021
5e92cc9
Update rfcs/0000-plan-dynanism.md
Ericson2314 Jun 6, 2021
1e2012c
plan-dynanism: Fix bad sentence
Ericson2314 Jul 21, 2021
5836c02
plan-dynamism: Number the two parts
Ericson2314 Sep 3, 2021
970ff43
plan-dynamism: Rip out part 2
Ericson2314 Oct 12, 2021
d6d6b17
plan-dynamism: New motivation
Ericson2314 Oct 12, 2021
6ab3338
plan-dynamism: Fix typo
Ericson2314 Oct 12, 2021
5f7d4da
TEMP PLES AMEND
Ericson2314 Sep 17, 2021
ea388e7
[RFC 0092] Rename file
L-as Oct 19, 2021
f6741c4
[RFC 0092] Fix YAML header
L-as Oct 19, 2021
b5aa21d
[RFC 0092] Rewrite summary
L-as Oct 19, 2021
866dc5e
[RFC 0092] Add link to documentation
L-as Oct 19, 2021
098fe68
[RFC 0092] Rewrite example section
L-as Oct 19, 2021
fed8991
[RFC 0092] Small fix
L-as Oct 19, 2021
854fd9b
[RFC 0092] Rewrite drawbacks and alternatives
L-as Oct 19, 2021
f853a6f
[RFC 0092] Improve alternatives section
L-as Oct 19, 2021
1abaf30
[RFC 0092] Fix syntax error
L-as Oct 19, 2021
ef1d9aa
[RFC 0092] Small change
L-as Oct 19, 2021
5115ebb
[RFC 0092] Remove unnecessary file
L-as Oct 20, 2021
fdbf778
[RFC 0092] Add comment about IFD
L-as Oct 20, 2021
2388cbe
[RFC 0092] Fix typo
L-as Oct 20, 2021
404ad6d
Update rfcs/0092-plan-dynamism.md
edolstra Nov 3, 2021
a906a7c
plan-dynamism-experiment: Make clear is experimental
Ericson2314 Dec 8, 2021
4d579ed
plan-dynamism-experiment: Fix typo
Ericson2314 Dec 8, 2021
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
261 changes: 261 additions & 0 deletions rfcs/0000-plan-dynanism.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
---
feature: plan-dynamism
start-date: 2019-02-01
author: John Ericson (@Ericson2314)
co-authors: (find a buddy later to help out with the RFC)
shepherd-team: (names, to be nominated and accepted by RFC steering committee)
shepherd-leader: (name to be appointed by RFC steering committee)
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
shepherd-team: (names, to be nominated and accepted by RFC steering committee)
shepherd-leader: (name to be appointed by RFC steering committee)
shepherd-team: @tomberek, @ldesgoui, @gytis-ivaskevicius, @L-as
shepherd-leader: @tomberek

related-issues: (will contain links to implementation PRs)
---

# Summary
[summary]: #summary
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this section could use a paragraph actually describing the benefit. You list some problems that are hard to solve in the Motivation but a succinct summary would be useful. Possibly something like:

Plan Dynamism would allow making Nix derivations available for large numbers of packages from language-specific package repositories such as NPM or crates.io with very little per-package effort


We need build plan dynamism -- interleaved building and planning -- to cope with the ever-growing world of language-specific package managers.
Propose to allow derivations to build derivations, and depend on those built derivations, as the core primitive for this.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Propose to allow derivations to build derivations, and depend on those built derivations, as the core primitive for this.
This proposes to allow derivations to build derivations, and depend on those built derivations, as the core primitive for this interleaving.

Additionally, introduce a new primop to leverage this in making "import from derivation" (IFD), still the gold standard for ease of use, more efficient and compatible with `hydra.nixos.org`'s queue runner.

# Motivation
[motivation]: #motivation

Nix's design encourages a separation of build *planning* from build *execution*:
evaluation of the Nix language produces derivations, and then then those derivations are built.
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
evaluation of the Nix language produces derivations, and then then those derivations are built.
Evaluation of the Nix language produces derivations, and then those derivations are built.

This usually a great thing.
It's enforced the separation of the more complex Nix expression language from the simpler derivation language.
Copy link
Contributor

Choose a reason for hiding this comment

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

Which do you mean?

  • Separation of planning and execution is enforced by the separation of...
  • Separation of planning and execution has enforced the separation of...

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
It's enforced the separation of the more complex Nix expression language from the simpler derivation language.
It has enforced the separation of the more complex Nix expression language from the simpler derivation language.

("has" is usually only contracted to "'s" when part of "has got".)

It's also encouraged Nixpkgs to take the "birds eye" view and successful grapple a ton of complexity that would have overwhelmed a more traditional package repository.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
It's also encouraged Nixpkgs to take the "birds eye" view and successful grapple a ton of complexity that would have overwhelmed a more traditional package repository.
It has also encouraged Nixpkgs to take the "birds eye" view and successful grapple a ton of complexity that would have overwhelmed a more traditional package repository.


Nixpkgs, along with every other distro, also faces a looming crisis: new open source software is increasingly not intended to be packaged by distros at all.
Many languages now support very large library ecosystems, with dependencies expressed in a language-specific package manager.
To this new generation of developers, the distro (or homebrew) is a crufty relic from an earlier age to bootstrap modernity, and then be forgotten about.

Right now, to deal with these packages, we either convert by hand, or commit lots of generated code into Nixpkgs.
But I don't think either of those options is healthy or sustainable.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
But I don't think either of those options is healthy or sustainable.
Neither of those options is healthy or sustainable.

Nit: I would recommend trying to avoid "think" and similar and just state as fact.

The problem with the first is sheer effort; we'll never be able to keep up.
The problem with the second is bloating Nixpkgs but more importantly reproducability: If someone wants to update that generated code it is unclear how.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it may be worth expanding on this slightly. Basically each language system will have it's own script or command to update these generated files. This inconsistency is difficult to learn and hard to tool in a generic way.

All these mean that potential users coming from this new model of development find Nix / Nixpkgs cumbersome and unsuited to their needs.

The core feature here, derivations that build derivations, is the best fundamental primitive for this problem.
It's very performant, being well-adapted for Nix's current scheduler.
Unlike recursive Nix, there's is no potential for half-built dependencies to sit around waiting for other builds, wasting resources.
Each build step (derivation) always runs start to finish blocking on nothing.
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
Each build step (derivation) always runs start to finish blocking on nothing.
Each build step (derivation) always runs start to finish, blocking on nothing.

comma for clarity

It's very efficient, because it doesn't obligate the use of the Nix expression language.
Copy link
Member

Choose a reason for hiding this comment

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

The direct object of "obligate" should be the person(s) obligated to do something, not the thing to be done.

Maybe

Suggested change
It's very efficient, because it doesn't obligate the use of the Nix expression language.
It's very efficient, because it doesn't require the use of the Nix expression language.

or

Suggested change
It's very efficient, because it doesn't obligate the use of the Nix expression language.
It's very efficient, because it doesn't mandate the use of the Nix expression language.

or

Suggested change
It's very efficient, because it doesn't obligate the use of the Nix expression language.
It's very efficient, because it doesn't obligate anyone to use the Nix expression language.

Finally, it's quite compatible with `--dry-run`.
Copy link
Member

Choose a reason for hiding this comment

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

Just "quite" compatible? What does that mean?


However, "import from derivation" is still far and away the easiest method to use, and the one that existing tools to convert to Nix use.
Ericson2314 marked this conversation as resolved.
Show resolved Hide resolved
\[Actually it's not just `import` which is notable, one can `builtins.readFile` a not-yet-buit path and it will also block today, and probably other such primops.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
\[Actually it's not just `import` which is notable, one can `builtins.readFile` a not-yet-buit path and it will also block today, and probably other such primops.
\[Actually it's not just `import` which is notable, one can `builtins.readFile` a not-yet-built path and it will also block today, and probably other such primops.

Copy link
Member

Choose a reason for hiding this comment

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

Consider splitting this sentence into two:

Suggested change
\[Actually it's not just `import` which is notable, one can `builtins.readFile` a not-yet-buit path and it will also block today, and probably other such primops.
\[Actually it's not just `import` which is notable:
One can `builtins.readFile` a not-yet-built path and it will also block today, and probably other such primops.

Copy link
Member

Choose a reason for hiding this comment

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

What are "primops"?

The exact primop doesn't matter --- all are noticeably more ergonomic than alternatives, and our proposal here is agnostic to the prim-op which would trigger the blocking.
I will continue to use "IFD" as an umbrella acronym despite its deficiencies because it is best known.\]
We should continue to support it, finding a way for `hydra.nixos.org` to allow it, so those tools with IFD can be used in Nixpkgs and become first-class members of the Nix ecosystem.
We have a fairly straightforward mechanism, only slightly more cumbersome than IFD today, to allow evaluation to be deferred after imported things are built.
This frees up the Hydra evaluator to finish before building, and also meshes well with any plan to build more than one eval-realized path at a time.
This should allow us to drop the `hydra.nixos.org` restriction.
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 generally known what restriction this refers to?


With these steps, I think we will be able to successfully convert to Nix a bunch of developers that mainly work in one language, and didn't even think they were in need of a better distro.
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
With these steps, I think we will be able to successfully convert to Nix a bunch of developers that mainly work in one language, and didn't even think they were in need of a better distro.
With these steps, we hope to successfully convert to Nix a bunch of developers that mainly work in one language, and didn't even think they were in need of a better distro.

In turn, I hope these upstream packages and ecosystems might even care about packaging and integration of the sort that we do.
Copy link
Member

Choose a reason for hiding this comment

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

Packages and ecosystems can't care. Their developers and maintainers can and might.

This would create a virtuous cycle where Nix is easier to use by more people, and Nixpkgs is easier to maintain as upstream packages better match our values.

# Detailed design
[design]: #detailed-design

We can break this down nicely into steps.

## Dynamic derivations

*This is implemented in https://github.com/NixOS/nix/pull/4628.*

1. Derivation outputs can be valid derivations.
\[If one tries to output a drv file today, they will find Nix doesn't accept the output as such because these small paper cuts.
This list item and its children should be thought of as "lifting artificial restrictions".\]

1. Allow derivation outputs to be content addressed in the same manner as drv files.
(The little-exposed name for this is "text" content addressing).

2. Lift the (perhaps not yet documented) restriction barring derivations output paths from ending in `.drv`, but only for derivation outputs that are so content-addressed.
\[There are probably other ways to make store paths that end in `.drv` that aren't valid derivations, so we could make the simpler change of lifting this restriction entirely without breaking invariants. But I'm fine keeping it for the wrong sorts of derivations as a useful guard rail.\]

2. Extend the CLI to take advantage of such derivations:
Copy link
Member

Choose a reason for hiding this comment

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

Specifying the CLI here feels a bit odd. Shouldn't this feature, at first, be transparent to the CLI? If someone uses the new hydra-safe IFD that shouldn't matter to the CLI.

In general overloading the "installables" attribute more and more (first flakes with # and ! and introducing *) feels very weird. They might be safe on posix shells but they are to me not the obvious way of handling these situations.

Can't we be explicit about the intentions?

# build the out output
$ nix build -f /nix/store/something-....drv out

# build the man output
$ nix build -f /nix/store/something-....drv man

# build the foo attribute of the "default" flake/expression in the current directory
$ nix build foo

# build bar.man from the default.nix in the current dir
$ nix build -f default.nix bar.man


# build the pythonPackages.black attribute of nixpkgs
$ nix build -f channel:nixos-unstable pythonPacakges.black

This would at least keep a clear separation of attributes and expression/derivation to build without adding more and more complexity into "installables".

Copy link
Member Author

Choose a reason for hiding this comment

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

So Flake's # is purely a higher level concern. My thing however allows a fully faithful representation of today's' DerivedPath, which is a solidly libnixstore concept corresponding to the sorts of top level "goals" we have in the schedule.

Here's another way to think about it: what would

# build the out output
$ nix path-info /nix/store/something-....drv out

mean with your proposal?
nix build -f /nix/store/something-....drv out looks (to me) like this might be a nix build only thing, but mine is supposed to make clear that /nix/store/something-....drv!out is one concept regardless of whether we are building, getting the path info, or any other operation: it's purposefully a new type of argument to all command not new type of command.

Copy link
Member Author

Choose a reason for hiding this comment

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

Even more concretely nix path-info /nix/store/something-....drv!out would tell you about a realization, which may or may not be filled in yet by substitution or building, and even if it does exist may or may not point to a path one actually has.

I hope that this example shows how /nix/store/something-....drv!out is a useful concept for many different actions, which is why I want to make it a "first class" argument type.

Copy link
Contributor

@asymmetric asymmetric Jun 7, 2021

Choose a reason for hiding this comment

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

On the topic of !: I would find both @ and ~ more intuitive, since they have come to represent a pointer to something (a user or a host), which seems to match what we're doing here.


We hopefully will soon allow CLI "installable" args in the form
```
single-installable ::= <path> ! <output-name>
```
Ericson2314 marked this conversation as resolved.
Show resolved Hide resolved
where the first path is a derivation, and the second is the output we want to build.

We should generalize the grammar like so:
```
single-installable ::= <single-installable> ! <output-name>
| <path>

multi-installable ::= <single-installable>
| <single-installable> ! *
```

Plain paths just mean that path itself is the goal, while `!` indexing indicates one more outputs of the derivation to the left of the `!` is the goal.

> For example,
> ```
> nix build /nix/store/…foo.drv
> ```
> would just obtain `/nix/store/…foo.drv` and not build it, while
> ```
> nix build /nix/store/…foo.drv!*
Copy link
Member

Choose a reason for hiding this comment

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

Are we sure using * here is a great idea? That required quoting in shells yet again, no?

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 picked ! purely for historical reasons, happy for it to be something else.

Copy link
Member Author

@Ericson2314 Ericson2314 Jun 6, 2021

Choose a reason for hiding this comment

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

Oops maybe I replied to the wrong one. * has only recently been added internally, so "historical reasons" is a weak argument. I still think * and {x,y,z} are good syntaxes despite or perhaps because they overlap with shell syntax; c.f. how Git also takes wildcards --- I at least use that feature all the time. ! I am far less attached to because it was chosen based on indexing in functional languages (or an older precedent I'm not aware of), and that is a completely unrelated syntactic tradition than ! in shells (which is...more like C trigraphs?!).

> ```
> would obtain (by building or substituting) all its outputs.
> ```
> nix build /nix/store/…foo.drv!out!out
> ```
> would obtain the `out` output of whatever derivation `/nix/store/…foo.drv!out` produces.

Now that we have `path` vs `path!*`, we also don't need `--derivation` as a disambiguator, and so that should be removed along with all the complexity that goes with it.
(`toDerivedPathsWithHints` in the the nix commands should always be pure functions and not consult the store.)

3. Extend the scheduler and derivation dependencies similarly:

- Derivations can depend on the outputs of derivations that are themselves derivation outputs.
The scheduler will substitute derivations to simplify dependencies as computed derivations are built, just like how floating content addressed derivations are realized.

- Missing derivations get their own full fledged goals so they can be built, not just fetched from substituters.

4. Add a new `outputOf` primop:

`builtins.outputOf drv outputName` produces a placeholder string with the appropriate string context to access the output of that name produced by that derivation.
The placeholder string is quite analogous to that used for floating content addressed derivation outputs.
\[With just floating content-addressed derivations but no computed derivations, derivations are always known statically but their outputs aren't.
With this RFC, since drv files themselves can be floating CA derivation outputs, we also might not know the derivations statically, so we need "deep" placeholders to account for arbitrary layers of dynamism.
This also corresponds to the use of arbitrary many `!` in the CLI.\]
Copy link
Member

Choose a reason for hiding this comment

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

It also makes it so that you don't need ! on the CLI if the expression accounts for the correct number of outputOfs, iiuc.

Copy link
Member

@roberth roberth Jul 17, 2021

Choose a reason for hiding this comment

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

How will a package definition look when it uses this functionality? We'll want to provide certain package attributes "statically", so they don't depend on anything in the derivation, so as not to necessitate IFD for meta, etc. I don't see an obvious way to extend mkDerivation to do this. Even if we do, its name is not quite appropriate anymore, because it mks more than one derivation.

Perhaps we could define "package" as an attrset with outputs, meta, etc, according to current conventions. We could then have a new mkPackage to take care of the fixpoint, overriding, meta and passthru, but leave derivation-specific aspects to the old mkDerivation (buildInputs, etc). It might look like:

# mkPackage: tie the knot, return the `public` attrset, add `overridePackage` function
mkPackage (self: {
  public = self.finalOutputs // {
    # other public attrs we care about
    version = "1.2.3";
    tests = f self.public;
    meta = {
      description = /* ... */;
    };
  };
  # An arbitrary internal attribute to allow overriding a lockfile
  lockFile = /* ... */;
  # Generate the derivation
  drvDrv = stdenv.mkDerivation {
    inherit (self.public) version;
    inherit (self) lockFile;
    outputHashMethodThingy = "text"; # output is a derivation (using wrong syntax, sorry)
    /* ... */
  };
  finalOutputs =
    # lib.packageOutputsOf wraps builtins.outputOf returning outputs in the usual format
    lib.packageOutputsOf ["out"] self.drvDrv;
})

mkPackage allows any number of derivations to be part of the package. It removes most of the need for PR NixOS/nixpkgs#119942. Also public is more self-evident than passthru and we get overridable protected attributes. Fewer public attributes by default is a good thing. If you need to access internals, you can overridePackage to expose them if you really have to.

It probably needs some refinement, but I feel like this could be a good change for Nixpkgs.

UPDATE: I don't think it should replace #119942, as a pragmatic choice to solve problems for the current packages without having to switch them over to mkPackage.


## Deferred import from derivation.

Create a new primop `assumeDerivation` that takes a single expression.
It's semantics are as follows:

- If the underlying expression evaluates (shallowly) without needing to build derivations (for `import`, `readFile`, etc.), and is a derivation, simply return that.
```
e ⇓ e'
e' is derivation
------
builtins.assumeDerivation e ⇓ e'
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a section explaining this syntax? I assume it's familiar for PLT folks, but it's very cryptic to me :)

Copy link
Member

Choose a reason for hiding this comment

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

Those are inference rules expressed in operational semantics.

You can read that as if e evaluates to e' and e' is a derivation, then builtins.assumeDerivation e evaluates to e'.

I agree with asymmetric, we're not a PLT-focused symposium, prose is fine here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think a more accessible presentation would be better. This is pseudo-formal, because (AFAIK) the and is derivation judgements aren't actually defined anywhere.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed. Unfortunately not everyone has that background.

Copy link
Contributor

Choose a reason for hiding this comment

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

A "translation" would suffice. Those compaxt bits of formalisms are great, once one has learned to parse them.

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'll put it this way:

  1. I think CA derivations' various notions of store coherence (by far the subtlest part), GUIX-style "grafting", mixing native- and cross-compiled packages, and probably other things I can't anticipate are going to repeatedly keep us thinking about these sorts of inductive relations. Some sort of sequent-ish syntax, or prolog's horn clause syntax, or many other such things are far and away the clearest and most concise way to convey this information once the reader is familiar --- it's supposed to be nice and "pictoral" not inscrutable ivory tower. I think it be best if we start moving in that direction.

  2. Even if I right some prose now, It's going to a pain to update. VS with this sort of syntax it can be easier to just stare at and have all the important bits come out, so it's easier to revise.

So I am fine writing some sort of prose explanation, but I rather do it once the RFC is further along.


P.S. whether you love our hate this stuff, https://www.youtube.com/watch?v=dCuZkaaou0Q is a fun talk.

Copy link
Member

Choose a reason for hiding this comment

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

You could include a link to a short introduction to natural deduction.

Copy link
Member

Choose a reason for hiding this comment

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

Since this is gone we can remove this.


- If the underlying expression cannot evaluate (shallowly) without building one or more paths, defer evaluation into a derivation-producing-derivation, and take it's output with `builtins.outputOf`:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is "shallow evaluation" defined anywhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

The phrase defines it on spot in the surrounding words. I had no difficulty parsing this.

"Shallow: without building"

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 actually mean weak head normal form. On analogy to seq vs deepseq I called this "shallow". It wouldn't really makes sense otherwise because Nix is lazy / we don't want to put in builtins.outputOf needlessly early, but I thought the parenthetical would help --- I can just remove it if it in fact hurts.

Copy link
Member

Choose a reason for hiding this comment

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

This is removed from the RFC.

```
e gets stuck on builds
Copy link
Contributor

Choose a reason for hiding this comment

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

What is "stuck"?

Copy link
Contributor

Choose a reason for hiding this comment

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

"synchronous waiting"

Copy link
Member Author

@Ericson2314 Ericson2314 Jun 6, 2021

Choose a reason for hiding this comment

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

stuck term, term that cannot finish evaluating. Traditionally that would be an open term with a free variable "blocking" a ~~regex~ redex. I consider an unbuilt path that is to be inspected quite analogous as it also a "dangling" reference of sorts.

Copy link
Member

Choose a reason for hiding this comment

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

regex

regex -> redex (reducible expression)
Defined to be reducible (ie evaluation) in the language. In this case, not immediately reducible because of the need to build.

defer = derivation {
inputDrvs = builds;
buildCommand = "nix-instantiate ${reified e} > $out"
Copy link
Member

Choose a reason for hiding this comment

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

One issue that is somewhat independent of this RFC, but relevant for it to be useful: We also need a “standalone” nix-instantiate which can evaluate nix expressions and write derivation files to arbitrary locations independently of a nix store and without the nix daemon.

Copy link
Member

Choose a reason for hiding this comment

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

Is nix-instantiate supposed to work in that derivation? Is nix always available in the build?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure of that? Evaluation is current designed so that like it or not, writing drv files is a side effect.

Copy link
Member

Choose a reason for hiding this comment

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

Note that at the time of writing I was not aware of recursive nix being possible in nixUnstable. Am I correct in assuming that this hypothetical derivation would use recursive nix to generate the .drv file?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup. I would want to limit recursive nix so one can just add paths to store (including DRVs), not build things.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, I think that should at least be mentioned for clarity. If I understand it correctly, then maybe the following derivation would be clearer:

derivation {
  buildCommand = "cp $(nix-instantiate ${reifed e}) $out";
}

In this case, the inner (recursive) instantiation would add the derivation file to the outer store, but due to content addressability of derivations, its outPath would resolve to the same file it produced as a side effect, right?

Copy link
Member

Choose a reason for hiding this comment

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

This reification is rather hand-wavy.

The language implementation can't serialize arbitrary closures. Such a feature isn't impossible but has consequences:

  • We'll need a serialization format that is stable, because when used in a non-floating derivation, hashes will be based off it. Changes to the format will not be acceptable or Nix needs to let go of its stability promises.
  • While thunks are fairly small, anything that's been evaluated a bit further in the entire scope will have to be serialized. This will tend to include the attributes of nixpkgs, including a serialization of each evaluated package's attrsets etc.
  • That's assuming serializing thunks shallowly is even acceptable. I don't think it is, because we can't let the serialization depend on the evaluation order. We can't have nix build . produce a different derivation for z than nix build .#z.
  • The closure of an expression may contain references to files that only the Nix client is normally able to access. This applies to local paths. I don't think we want to send the contents of ~ along. It also applies to the general context of the Nix client, like git credentials, Nix plugins, etc. (indeed mostly represented by files, but accessed through abstractions like the builtin fetchers)
  • The closure may contain confidential values, such as NixOps' deployment.keys.<name>.text or similar. We don't want those in the store.

Compared to a somewhat naive serialization, another partial solution is to do "event sourcing" in the spirit of transient (Haskell). To implement that, the evaluator must only be accessible through a facade interface that tracks exactly how an evaluation came to be: which files were provided to it and which external traversals and function calls were made, etc. This method, if it even applies here, seems to solve the thunk dilemma, but doesn't solve the other problems and requires significant changes to the evaluator. The other problems remain unsolved.

Is not serializing the closure an option? We could suspend the evaluation by just retaining the reference to the thunk, assigning it a random id and wait for the daemon to get back to us. This defeats the desired separation between the evaluation and build processes. It does let a derivation like fetchNodeModules lockFile access confidential git repos via builtins.fetchGit, if the connected client has access to those. That'd be neat.

I hope I've just misunderstood something, because I'd like this to succeed. I just don't see a good way to implement this.
I feel like the old ret-cont is conceptually simpler, doesn't create this problem and doesn't create unrealistic expectations.

}
------
builtins.assumeDerivation e ⇓ builtins.outputOf defer "out"
```

This allows downstream non-dynamic derivations to be evaluated without getting stuck on their dynamic upstream ones.
They just depend on the derivation computed by the deferred eval derivations, and those substitutions are performed by the scheduler.

# Examples and Interactions
Copy link
Member

Choose a reason for hiding this comment

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

[examples-and-interactions]: #examples-and-interactions

Here is everything put together with a Haskell and `cabal2nix` example.

Given the following code, and assuming `bar` will depend on `foo`
```nix
mkScope (self: {
foo = builtins.assumeDerivation (self.callCabal2nix "foo" ./foo);
bar = builtins.assumeDerivation (self.callCabal2nix "bar" ./bar);
})
```
After some evaluation, we get something like the following derivations with dependencies:
```
(cabal2nix foo)!out ----> deFoo = deferred eval (fooNix: self.callPackage fooNix)
(cabal2nix bar)!out ----> deBar = deferred eval (barNix: self.callPackage barNix)
Copy link
Contributor

Choose a reason for hiding this comment

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

Where does deFoo come from? I'm not quite sure what this means.

Copy link
Member

Choose a reason for hiding this comment

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

deFoo is the name assigned to the defferred eval thing for the next few lines, I'm pretty sure.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's only used to disambiguate this text and tell drv apart from deferred drv.

However, I was wondering if this (or something similar) could be made a convention of assumeDerivation to more easily idententify asyncly built 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.

Yeah this example is a mess of unclear notation. (I am disgruntled by this in complete contrast to my standing by the sequent notation I like a lot.)

```
and evaluated nix
```nix
mkScope (self: {
foo = builtins.outputOf /nix/store/deFoo.drv "out";
bar = builtins.outputOf /nix/store/deBar.drv "out";
})
```

If we then build `bar` we will get something steps like:

1. ```
deBar!out
```
2. Expand `deBar` in to see dep
```
((cabal2nix bar)!out ----> (deferred eval (fooNix: self.callPackage barNix {}))!out
```
3. Build cabal2nix and inline path for simplicity (or if cabal2nix job is floating CA derivation)
```
(deferred eval self.callPackage built-barNix)!out
```
4. Build deferred eval job and substitute
```
deFoo!out ----> bar.drv!out
```
5. Expand `deFoo` in to see dep
```
((cabal2nix foo)!out ----> (deferred eval (fooNix: self.callPackage fooNix {}))!out ----> bar.drv!out
```
6. Build cabal2nix and inline path for simplicity (or if cabal2nix job is floating CA derivation)
```
(deferred eval self.callPackage built-fooNix)!out ----> bar.drv!out
```
7. Build deferred eval job and substitute
```
foo.drv!out ----> bar.drv!out
```
8. Build foo and realize bar
```
bar.drv[foo-path/foo!out]!out
```
9. Build bar
```
bar-path
```

The above is no doubt hard to read -- I am sorry about that --- but here are a few things to note:

- The scheduler "substitutes on demand" giving us a lazy evaluation of sorts.
This means that in the extreme case where we to make to, e.g., make a derivation for every C compiler invocation, we can avoid storing a very large completely static graph all at once.

- At the same time, the derivations can be built in many different orders, so one can intentionally build all the `cabal2nix` derivations first and try to accumulate up the biggest static graph with `--dry-run`.
This approximates what would happen in the "infinitely parallel" case when the scheduler will try to dole out work to do as fast as it can.

# Drawbacks
[drawbacks]: #drawbacks

The main drawback is that these stub expressions are *only* "pure" derivations --- placeholder strings (with the proper string context) and not attrsets with all the niceties we are used to getting from `mkDerivation`.
This is true even when the deferred evaluation in fact *does* use `mkDerivation` and would provide those niceties.
For other sort of values, we have no choice but wait; that would require a fully incremental / deferral evaluation which is a completely separate feature not an extension of this.
Concretely, our design means we cannot defer the `pname` `meta` etc. fields: either make do with the bare string `builtins.outputOf` provides, or *statically* add a fake `name` and `meta` etc. that must be manually synced with the deferred eval derivation if it is to match.

# Alternatives
[alternatives]: #alternatives

- Do nothing, and continue to have no good answer for language-specific package managers.

- Instead of deferring only certain portions of the evaluation with `builtins.asssumeDerivation`, simply restart the entire eval.
Quite simple, and no existing IFD code today needs to change.

- Abandon IFD and just let users use the underlying dynamic derivation feature.
This was my first idea, but I became worried that this was too hard to use for simple experiments.

# Unresolved questions
[unresolved]: #unresolved-questions

The exact way the outputs refer to the replacement derivations / their outputs is subject to bikeshedding.

# Future work
[future]: #future-work

1. Actually use this stuff in Nixpkgs with modification to the existing "lang2nix" tools.
This is the lowest hanging fruit and most import thing.

2. Try to breach the build system package manager divide.
Just as there are foreign packages graphs to convert to Nix, there are Ninja and Make graphs we can also convert to Nix.
This might really help with big builds like Chromium and LLVM.

3. Try to convince upstream tools to use Nix like CMake, Meson, etc. use Ninja.
Rather than converting Ninja plans, we might convince those tools to have purpose-built Nix backends.
Language-specific package mangers that don't use Ninja today might also be modified to "let Nix do that actual building".