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 0083] Common interface package sets #83

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
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
268 changes: 268 additions & 0 deletions rfcs/0083-common-interface-package-sets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
---
feature: common-interface-package-sets
start-date: 2020-12-19
author: Frederik Rietdijk (@FRidh)
co-authors:
related-issues:
---

# Summary
[summary]: #summary

The Nixpkgs package set consists of a large amount of packages of which a
significant amount of grouped into sub package sets. This RFC recommends a
common interface for these package sets.

# Motivation
[motivation]: #motivation

The Nixpkgs package set consists of a large amount of packages of which a
significant amount of grouped into sub package sets. Sets exist for various
languages, frameworks and plugins.

They are typically grouped together for one of the following two reasons:
- clarity, e.g. because they're written in the same language or framework;
- necessity, e.g. for compatibility reasons.

Over time different methods for defining package sets were created. Currently
multiple methods are in use in Nixpkgs and pull requests are opened to modify
sub package sets interfaces from one kind to another. Not only is this confusing
for users but it also causes trouble; in some cases overriding of derivations
inside a set is not possible or cross-compilation is broken.

This RFC thus aims to unify the package sets to a common interface for the
following reasons:
- simplify usage of package sets and reduce confusion surrounding them;
- single approach for dealing with overrides;
- handle variants of a package set;
- ensure cross-compilation works.

Often one also wants to build an environment with an interpreter or main program
and some additional packages or plugins. This RFC will therefore also recommend
a function each package set should offer for doing so, when appliceable that is.

## Related issues
Copy link
Member

Choose a reason for hiding this comment

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

Somewhat related NixOS/nixpkgs#75485


TODO: refer to these issues in correct place.

- Common override interface derivations https://github.com/NixOS/rfcs/pull/67
- Make PHP packages overrideable https://github.com/NixOS/nixpkgs/pull/107044
- Change in emacs interface https://github.com/NixOS/nixpkgs/pull/107152
- Package set for Octave https://github.com/NixOS/nixpkgs/issues/65398#issuecomment-743926570
- Python package set is not overrideable
https://github.com/NixOS/nixpkgs/issues/44426
- Support `overrideScope'` in Python package set
https://github.com/NixOS/nixpkgs/pull/105374
- Common `overrideArgs` for sub package sets
https://github.com/NixOS/nixpkgs/pull/46842. May be resolved using
`overrideAuto` in https://github.com/NixOS/rfcs/pull/67.

# Detailed design
[design]: #detailed-design

We will now look in more detail at what a common interface should offer.

## Attribute name of the package set: `fooPackages` versus `foo.pkgs`

Two different interfaces are common in Nixpkgs when referring to package sets:
- `fooPackages`
- `foo.pkgs`

TODO which one to pick? Consider also overriding of the interpreter or main program and how that should propagate. Consider also the function for generating variants, where you need to have a name under which your interpreter or main program is available in the subpackage set.
Copy link
Member

Choose a reason for hiding this comment

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

I though we were moving towards foo.pkgs as it makes it harder to have a mismatch of foo versions.

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 don't follow you here. Do you refer to how we pass in say python or pythonPackages instead of the individual Python packages to a non-Python package? That is indeed to prevent mismatches, however, it does not matter whether it is python or pythonPackages.

Copy link
Member

Choose a reason for hiding this comment

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

I meant that if we have two top-level attributes, overriding just one in an overlay can cause mismatches (unless they refer to each other through scoped names but that has its own downsides). Stuffing everything under the interpreter allows us greater control over references thanks to encapsulation (but maybe at performance cost compared to just bare scopes?).

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we do: fooPackages = lib.recurseIntoAttrs foo.pkgs;.

Copy link
Member

Choose a reason for hiding this comment

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

recurseIntoAttrs only has effect on whether tools will try to find packages under that attribute. How coupled the two attributes are depends on which scope does foo come from.

Copy link
Member

@Ericson2314 Ericson2314 Jan 11, 2021

Choose a reason for hiding this comment

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

I've always been wary of foo.pkgs because one compiler/interpreter can build multiple package sets. Having the package set of a tool is philosophically backwards, it should be the compiler/interpreter of the package set.

Copy link
Member

Choose a reason for hiding this comment

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

it should be the compiler/interpreter of the package set

In theory, I would agree. In practice however this makes it so that you have to take care to not pass an incompatible Python version along with it's set.
I think the practicality of this outweighs any philosophical notion.

Copy link
Member

Choose a reason for hiding this comment

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

But in practice both python3.pkgs and python2.pkgs would be the same sets, but with different behavior depending on the python versions which dynamically determine the value of disabled and similar stuff. In that sense it isn't even possible to pass an incompatible python interpreter if I'm not mistaken.

Also in other language-specific package sets there is much less breakage between compiler versions than between python2 and python3 where it would make even less sense.

## Variants
Often multiple variants of a package set need to be created. E.g., in case of
emacs or Python there are different versions of the program and each of them
should have their own package set. For this reason it is important that one can
easily create a new variant

```nix
fooPackagesFor = foo: import ./foo-packages.nix { ... };
fooPackages_3_6 = fooPackagesFor foo_3_6;
Copy link
Member

Choose a reason for hiding this comment

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

For PHP, we create the php.pkgs internally in the PHP derivation “template” function. That is, a package set for the new variant is just created automatically when the variant is created (e.g. using import "${nixpkgs}/pkgs/development/interpreters/php/generic.nix") { version = "..."; sha256 = "..."; }.

Copy link
Member Author

Choose a reason for hiding this comment

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

From the looks of it is somewhat similar to the passthru for Python.

```

## Set overriding
It should be possible to override packages in a sub package set and have the
other packages in the set take that override into account. To that end, a scope
Copy link

@roosemberth roosemberth Jun 3, 2021

Choose a reason for hiding this comment

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

It may be my lack of familiarity with scoped overrides, but as an outsider, it is not clear to me what the other packages in the set implies.

Are those the packages in the sub package set or does it propagate to packages outside that set?

What happens when a package outside that subset consumes a package from the subset?

e.g.

rec {
  foo = langPkgs.overrideScope { bar = bar2; } (callPackage ... {});
  bar = ...;
  bar2 = bar.override(...);
  baz = callPackage ... { inherit foo bar; };
}

In either case, I think some more explaination is needed to understand this feature. A reference would do if it's already defined else-where :)

is created

```nix
lib.makeScope pkgs.newScope (self: with self; {
...
}
```

that can be overridden using `overrideScope'`

```nix
fooPackages_3_6.overrideScope' overrides;
```

where `overrides` is of the form

```nix
(final: previous: { ... })
```

In case one uses overlays with Nixpkgs, one could now make the overrides
composible using

```nix
fooPackages_3_6 = fooPackages_3_6.overrideScope' overrides;
```

## Package overriding

Now that it is possible to override a set, a common interface to overriding the
packages inside the set is needed as well. This is treated in [RFC
67](https://github.com/NixOS/rfcs/pull/67).

## Cross-compilation

For cross-compilation it is important that `callPackage` in the sub package set
has access to the spliced versions of the sub package set. Until recently, there
were no spliced sub package sets in Nixpkgs, but support was added for Python
utilizing the `makeScopeWithSplicing` function. There is [room for
improvement](https://github.com/NixOS/nixpkgs/pull/105374).

An important aspect for making this work is that, when constructing the package
set, it needs to know its own top-level 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.

Referring to the incorrect set NixOS/nixpkgs#114538


...


To support nested package sets, the full attribute path is needed, including
Copy link
Member

Choose a reason for hiding this comment

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

Do we even want nesting? For example, for PHP, we could have two clearly separated package sets:

  • tools – those are just scripts written in PHP, they would probably not even need to be under PHP, just like we do not put Python applications to python.pkgs.
  • extensions – shared libraries linked against PHP that PHP needs to load at startup

PHP does not really need something like python.withPackages since PHP packages are just text and it is the application, not PHP runtime, responsible for loading them. (AFAIK unlike in Python, anything binary needs to be an extension, not a regular autoloaded package.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we even want nesting?

I don't know actually. So far it seems we have not, so we can just leave it out of the RFC.

dots.

```nix
`fooPackages_3_6.subFooPackages_2_5`
```

## Single function for creating a package set

To ease the creation of a package set, a single function is proposed for
creating a set given a main program and an attribute name.


```nix
makePackageSet = ...
```


A package set can then be created

```
fooPackagesFor = foo: callPackages

fooPackages =

```

## Composing an environment

Thus far we considered building a package set and overriding it. Somewhat
orthogonal yet still commonly needed, is a method to compose an environment of
packages inside the package set.

Starting with Haskell and later adopted by other package sets is a
`withPackages(ps: [...])` function that allows you to compose an environment with chosen
packages from the set.

This RFC recommends each package set should have such a function, in case it is
appliceable. An example of where it would not make sense to include such a
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
appliceable. An example of where it would not make sense to include such a
applicable. An example of where it would not make sense to include such a

function is in case of a [Qt package
set](https://github.com/NixOS/nixpkgs/pull/102168) because you would not be
interested in having a Qt with libraries during runtime and nothing else.

Haskell introduced the function as part of its package set, that is, one uses
`haskellPackages.ghcWithPackages (ps: [...])`. Some time later Python added such
function, but as part of the interpreter `python3.withPackages(ps: [...])`.
Since then, many other sets also added `withPackages` as part of the
interpreter or main program.

Should the `withPackages` function be part of the main program or the package
Copy link
Member

Choose a reason for hiding this comment

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

For PHP, withPackages does not make sense either, since we do not actually package any packages other than applications.

For the extensions, I think that it should be php.withExtensions since that does not create an environment but creates a config file listing the extensions and wraps php with a path to the config 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.

I think this shows why withPackages is often enough, but not always and it hence should be a recommendation, but not a rule. In the end, we need a more powerful buildEnv that can compose an environment given a mixture of types of packages using certain rules.

Copy link
Contributor

Choose a reason for hiding this comment

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

"powerful buildEnv" -> #75 ...

set? If we override the package set using `overrideScope'`, then the updated
package set is visible to attributes of the package set, that is

```
(fooPackages.overrideScope' overrides).withPackages
```

and

```
(fooPackages.overrideScope' overrides).foo.withPackages
```

will consider the updates.

### `fooPackages` versus `foo.pkgs` again...

TODO

Unfortunately, having `withPackages` as part of the main program makes it
somewhat more difficult to use it when overrides are needed as well. One would have to write

```nix
(foo_3_6.pkgs.overrideScope' overrides).foo.withPackages(ps: [...])
```
Indeed, the `withPackages` of the main program inside the sub set needs to be used. Using

TODO the actual recommendation

# Examples and Interactions

## Create a package set

The following example shows how to create a package set

...

## Override a package set

The following example shows how to override a package set

```nix
fooPackages.overrideScope' (final: previous: {
...
})
```

## Compose an environment

The following example shows how to compose an environment

TODO

```
fooPackages.foo.withPackages(ps: [...])
```

or

```
fooPackages.withPackages(ps: [...])
```

## Deprecating old interfaces

It it is recommended that package sets adopt the new interface and deprecate
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 it is recommended that package sets adopt the new interface and deprecate
It is recommended that package sets adopt the new interface and deprecate

their current one. For compatibility reasons it may not be possible to use the
recommended functions to construct the new interface, requiring custom
solutions.

# Drawbacks
[drawbacks]: #drawbacks

By not recommending a common interface, package sets may continue to have
different interfaces and change even in opposite directions.

# Alternatives
[alternatives]: #alternatives

# Future work
[future]: #future-work

- Document the recommended interface in the Nixpkgs manual.
- Adopt the recommended interface in existing package sets in Nixpkgs.
- Encourage tools that create package sets, e.g. from lock files, to also adopt
this interface.