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 0075] Declarative wrappers #75

Draft
wants to merge 38 commits into
base: master
Choose a base branch
from
Draft
Changes from 33 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ef81400
[RFC 55]: Declarative Wrappers
doronbehar May 10, 2020
b780229
Declarative wrappers
doronbehar Aug 15, 2020
d09a058
Finish motivation
doronbehar Aug 16, 2020
37c7a8c
Remove old RFC file
doronbehar Aug 16, 2020
2a0a630
Add more relevant issues regarding compiling wrappers
doronbehar Aug 16, 2020
c9d7055
Write most of the design and more
doronbehar Aug 16, 2020
abb7609
Small improvements to summary vs motivation
doronbehar Aug 16, 2020
d1cadb6
Rephrase a bit orchestrating motivation section
doronbehar Aug 16, 2020
a570b1a
Add small ()
doronbehar Aug 16, 2020
8db3605
More rephrasings and additions
doronbehar Aug 16, 2020
5ddd60b
Fix possibly fixable title
doronbehar Aug 16, 2020
778bc99
Design sec improvements
doronbehar Aug 16, 2020
44a3b87
Improve examples
doronbehar Aug 16, 2020
0b259fa
Improve ending
doronbehar Aug 16, 2020
d75c73e
Last (?) rephrasings
doronbehar Aug 16, 2020
94038bd
75: Introduction on run-time dependencies and wrappers.
FRidh Aug 20, 2020
0fa64ab
Merge pull request #1 from FRidh/75-intro
doronbehar Aug 25, 2020
ffacf5f
Add SANE_CONFIG_DIR issue to motivation
doronbehar Aug 30, 2020
f754ad7
Mark @nmattia's idea as unsuitable because of IFD.
doronbehar Sep 8, 2020
4ab0448
Revert "Mark @nmattia's idea as unsuitable because of IFD."
doronbehar Sep 11, 2020
60d3825
Add shepherds
edolstra Nov 19, 2020
f3416a0
Remove out scope Nixpkgs issue #32790 from motivation
doronbehar Feb 24, 2021
c092848
Add date to header
doronbehar Feb 24, 2021
732246d
Mention a new hplip wrapping issue
doronbehar Feb 24, 2021
67b002b
Update link to gnome docs
doronbehar Feb 24, 2021
cac0cbb
Better explain Nixpkgs issue #86369
doronbehar Feb 24, 2021
7b0c9e1
Remove some not directly related issues to wrapping
doronbehar Feb 24, 2021
034a13e
Rewrite most of RFC after 1st meeting
doronbehar Feb 24, 2021
edbf315
Small rephrasings
doronbehar Mar 19, 2021
48f9118
Add shepherd leader metadata
lheckemann Jun 10, 2021
73421fd
Apply suggestions by @lheckermann
doronbehar Jul 18, 2021
f0c2533
Rename wrappersInfo -> combineWrappersInfo
doronbehar Jul 18, 2021
391f5d0
Add subtitles for the motivation categories
doronbehar Jul 18, 2021
fb0dd98
Small rephrasings
doronbehar Aug 6, 2021
c51c9c0
Use a list of glob patterns for the wrapping
doronbehar Aug 6, 2021
31a24a0
Small rephrasings
doronbehar Aug 14, 2021
a6123b9
Explain default behaviour of order and separators
doronbehar Aug 14, 2021
a65ac77
Make combineWrappersInfo a nix function that returns a shell command
doronbehar Aug 14, 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
372 changes: 372 additions & 0 deletions rfcs/0075-declarative-wrappers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@
---
feature: Declarative Wrappers
start-date: 2020-08-16
author: Doron Behar <[email protected]>
co-authors: (find a buddy later to help out with the RFC)
shepherd-team: @FRidh, @lheckemann, @edolstra
shepherd-leader: @FRidh
related-issues: POC Implementation at [#85103](https://github.com/NixOS/nixpkgs/pull/85103).
---

# Summary
[summary]: #summary

Manage the environment of wrappers declaratively and deprecate shell based
methods for calculating runtime environment of packages. Make it easier to
debug why env vars are added to an executable, by storing this information
inside `/nix/store/.../nix-support/` of the dependencies that using them
requires require runtime environment. Create a new `makeWrapperAuto` hook that
will make the `fixupPhase` read all of the deps environment that's needed and
automatically wrap the executables with the proper environment.

# Motivation
[motivation]: #motivation

Every Nix build produced is stored in a separate path in the store. For a
build to find its runtime dependencies purely, they need to be hardcoded in
the build. Thus, a complete dependency specification is needed.

In case of compiled languages this process is already largely automated;
absolute paths to shared libraries are encoded in the `rpath` of binaries.
Executables that are invoked by binaries need to be dealt with manually,
however. The preferred choice is here to manually patch the source so that
binaries are invoked using absolute paths as well.

This is not always trivial and thus a common work-around is to wrap executables,
setting `$PATH` to include the locations of dependencies. This is a pragmatic
solution that typically works fine, but it comes with a risk: *environment
variables leak*. Executables that shell out may pass along the modified
variables, causing at times unwanted behaviour.

Programs written in interpreted languages tend to import their runtime
dependencies using some kind of search path. E.g., in case of Python there is a
process for building up the `sys.path` variable which is then considered when
importing modules. Like with compiled languages, the preferred choice would also
here be to embed the absolute paths in the code, which is often not done. Note
that in case of Python this *is* done. Like with compiled languages, programs
may shell out and likewise the preferred solution is to patch the invocations to
use absolute paths. Similarly, in case a programs wants to `dlopen` a shared
library this should be patched to include an absolute path, instead of using
`LD_LIBRARY_PATH`.

It is recognized that wrappers setting environment variables are typically not
the preferred choice because of the above mentioned leakage risk, however, often
there is simply not a better or reasonable alternative available.

We have numerous issues regarding wrappers and our wrapper shell hooks. Here's
a list of them, sorted to categories.
doronbehar marked this conversation as resolved.
Show resolved Hide resolved

* [Missing environment variables](#missing-environment)
* [Orchestrating wrapping hooks](#orchestrating-wrapping-hooks)
* [Issues _Possibly_ fixable by declarative wrappers](#issues-possibly-fixable-by-declarative-wrappers-)
* [Unreported Issues](#unreported-issues-afaik)
* [Other Issues](#other-issues)

## Missing environment

- [pull 83321](https://github.com/NixOS/nixpkgs/pull/83321)
- [pull 53816](https://github.com/NixOS/nixpkgs/pull/53816)

@rnhmjoj & @timokau How unfortunate it is that Python's `buildEnv` doesn't know
to do anything besides setting `NIX_PYTHONPATH` - it knows nothing about other
env vars, which some deps rely upon when eventually used. Declarative wrappers
don't care about the meaning of env vars - all of them are treated equally,
considering all of the inputs of a derivation equally.

- [pull 75851](https://github.com/NixOS/nixpkgs/pull/75851)
- [issue 87667](https://github.com/NixOS/nixpkgs/issues/87667)

Fixable with our current wrapping tools (I guess?) but it's unfortunate that we
have to trigger a rebuild of VLC and potentially increase it's closure size,
just because of a missing env var for only _some_ users. If only our wrapping
requirements were accessible via Nix attrsets, we could have instructed our
modules to consider this information when building the wrappers of the packages
in `environment.systemPackages`.

- [issue 87883](https://github.com/NixOS/nixpkgs/issues/87883) (Fixed)

@jtojnar wouldn't it be wonderful if the wrapper of gimp would have known
exactly what `NIX_PYTHONPATH` to use when wrapping gimp, just because `pygtk`
was in it's inputs? Declarative wrappers would also allow us to merge the
wrappings of such derivation to reduce double wrappings, as currently done at
[`wrapper.nix`](https://github.com/NixOS/nixpkgs/blob/b7be00ad5ed0cdbba73fa7fd7fadcb842831f137/pkgs/applications/graphics/gimp/wrapper.nix#L16-L28)
and
[`default.nix`](https://github.com/NixOS/nixpkgs/blob/b7be00ad5ed0cdbba73fa7fd7fadcb842831f137/pkgs/applications/graphics/gimp/default.nix#L142-L145).

- [issue 85306](https://github.com/NixOS/nixpkgs/issues/85306)
- [issue 84249](https://github.com/NixOS/nixpkgs/issues/84249)

`git-remote-hg` and `qttools` are not wrapped properly.

- [issue 86048](https://github.com/NixOS/nixpkgs/issues/86048)
- [issue 114051](https://github.com/NixOS/nixpkgs/issues/114051)

I guess we don't wrap HPLIP because not everybody want to use these binaries
and hence want these GUI deps in their closure (if they were wrapped with a
setup hook)? Declarative wrappers would allow _some_ users to use the wrapped
binaries and others not to need this wrapping. Via an override or a NixOS
config flag, without triggering a rebuild of HPLIP itself, these users would be
easily satisfied.

## Orchestrating wrapping hooks

- [issue 78792](https://github.com/NixOS/nixpkgs/issues/78792)

@worldofpeace you are correct. All of these setup-hooks are a mess. At least we
have documented, (yet not totally implemented) [this section of the
manual](https://github.com/NixOS/nixpkgs/blob/2df97e4b0ab73f0087af2e6f33e694140150db1b/doc/languages-frameworks/gnome.section.md#L120-L166)

Declarative wrappers will deprecate the usage of our shell based hooks and will
wrap all executables automatically according to their needs, without requiring
the contributor a lot of knowledge of the wrapping system. Also, double
wrappings will become a problem of the past.

- [issue 86369](https://github.com/NixOS/nixpkgs/issues/86369)

@ttuegel with declarative wrappers, we can symlink all qt plugins into 1
directory and wrap the executable with only 1 `QT_PLUGIN_PATH` in their
environment, which should decrease the plugin load of every qt package.

## Issues _possibly_ fixable by declarative wrappers (?)

- [pull 61213](https://github.com/NixOS/nixpkgs/pull/61213)

I'm not sure what's the issue there. But, I'm sure that a declarative, Nix
based builder of a Python environment, even if this environment is used only
for a build, should make it easier to control and alter it's e.g `$PATH`.

- [issue 49132](https://github.com/NixOS/nixpkgs/issues/49132)
- [issue 54278](https://github.com/NixOS/nixpkgs/issues/54278)
- [issue 39493](https://github.com/NixOS/nixpkgs/issues/39493)

`GDK_PIXBUF` compatibilities? I haven't investigated them to the details, so feel
Copy link
Member

Choose a reason for hiding this comment

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

Both of the issues related to gdk-pixbuf only happened for non-wrapped derivations and current wrapGAppsHook would already fix them.

The issue was, IIRC, that gdk-pixbuf NixOS module sets GDK_PIXBUF_MODULE_FILE environment variable, which was enabling binary gdk-pixbuf modules with ABI incompatible with the gdk-pixbuf the programs linked against. In one case it was caused by a bug that slipped through the ABI stability guarantees, in the other by running a program for i686 on x86_64.

Either way, it is somewhat orthogonal to wrappers. Only that we might want to always wrap graphical applications with librsvg gdk-pixbuf module.

Copy link
Member

Choose a reason for hiding this comment

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

Though there is one concern around GDK_PIXBUF_MODULE_FILE – the environment variable supports only a single path so we need to build the cache containing all the modules using gdk-pixbuf-query-loaders – the current hook actually does not actually do that! but it is required for some image formats to work in e.g. eog. We will need to take inspiration in the module.

free @jtojnar to review me and tell me that declarative wrappers won't help.

## Unreported issues (AFAIK)

Issues that bother me personally, but I haven't bothered to open an issue since
I doubt it would be feasible to fix with our current wrapping ecosystem, excuse
my pessimism `wrap{G,Qt}AppsHook` authors.

`kdeconnect` has `kdoctools` in it's closure because it's wrapper has
`kdoctools` due to it picked by `wrapQtAppsHook`:

```
$ nix why-depends -f. kdeconnect kdoctools
/nix/store/sh42k6cz4j48br4cxi2qn173rys4japp-kdeconnect-1.3.5
╚═══bin/kdeconnect-cli: …xport XDG_DATA_DIRS='/nix/store/m16681i5dhhkhszi9w42ir037jvbnab9-kdoctools-5.71.0/share'${XDG_DA…
=> /nix/store/m16681i5dhhkhszi9w42ir037jvbnab9-kdoctools-5.71.0
```

A similar issue is with `kconfigwidgets.dev` and `kdeconnect`:

```
$ nix why-depends -f. kdeconnect kdeframeworks.kconfigwidgets.dev
/nix/store/sh42k6cz4j48br4cxi2qn173rys4japp-kdeconnect-1.3.5
╚═══bin/kdeconnect-cli: …port QT_PLUGIN_PATH='/nix/store/qssjj6ki7jiskw2kfygvfiy8fxrclwrl-kconfigwidgets-5.71.0-dev/lib/q…
=> /nix/store/qssjj6ki7jiskw2kfygvfiy8fxrclwrl-kconfigwidgets-5.71.0-dev
```

Also similar (but possibly fixable by moving `gobject-introspection` to a
different inputs list?):

```
$ nix why-depends -f. beets gobject-introspection.dev
/nix/store/93lfrhm8vp17m8ziqi8vp6v4cff67wkb-beets-1.4.9
╚═══bin/beet: …-expat-2.2.8-dev/bin:/nix/store/y3ym76wrak3300vsjyf3klr52cnzmxwd-gobject-introspection-1.64.1-de…
=> /nix/store/y3ym76wrak3300vsjyf3klr52cnzmxwd-gobject-introspection-1.64.1-dev
```

## Other issues
Copy link
Member

Choose a reason for hiding this comment

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

More related issues with wrappers NixOS/nixpkgs#160923 and the ones linked at NixOS/nixpkgs#160923 (comment)


- [issue 60260](https://github.com/NixOS/nixpkgs/issues/60260)

General, justified complaint about wrappers.

- [issue 95027](https://github.com/NixOS/nixpkgs/issues/95027)
- [issue 23018](https://github.com/NixOS/nixpkgs/issues/23018)
- [issue 11133](https://github.com/NixOS/nixpkgs/issues/11133)
- [pull 95569](https://github.com/NixOS/nixpkgs/pull/95569)

Since our wrappers are shell scripts, `gdb` can't run them. What if we had
written a C based wrapper, that perhaps would read what environment it needs to
set from a JSON file, and it will call the unwrapped original executable? I
need feedback regarding whether `gdb` will play nice with this.
Copy link
Member

Choose a reason for hiding this comment

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

An alternative to this is the libtool approach, which still allow you to use strace,gdb etc: https://www.gnu.org/software/libtool/manual/html_node/Invoking-libtool.html
If a binary wrapper is used than it should be compiled for every target as this increases package size/build time. Instead having the wrapper reading its actual configuration from the same directory might work.

Copy link
Contributor

Choose a reason for hiding this comment

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

Compiling a binary wrapper for each target is probably a small overhead. All it needs to do is modify the environment and exec the target. This should be a trivial C program. I think I would rather it be something trivial like that then needing to read config files which is somewhat more complicated. Although if we really want to optimize we would need some proper profiling about build time and execution time (including cache effects).

But either way I think this proposal enables us to solve this problem, we can figure out the details when start to tackle it.

Copy link
Member

Choose a reason for hiding this comment

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

@Mic92 do you mean it should not be compiled for each package?

Copy link
Member

Choose a reason for hiding this comment

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

Worked a bit on a wrapper tool some time ago: svep. Maybe there are some useful ideas there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Worked a bit on a wrapper tool some time ago: svep. Maybe there are some useful ideas there?

That's a nice tool :), but I'm not sure it helps a lot, it seems it only supplies an alternative to makeCWrapper, but that depends on rust, and not pure C99.

We won't be able for example to use it to save the wrapping information of a package, as we want to access that information independently, from ${pkg}/nix-support/, and not from the binaries that the package may distribute.


This issue may not directly relate to declarative wrappers, and it is already
addressed in @FRidh's [pull 95569](https://github.com/NixOS/nixpkgs/pull/95569), but perhaps
both ideas could be integrated into an alternative, simpler creation method of
binary wrappers. See [my
comment](https://github.com/NixOS/nixpkgs/pull/95569#issuecomment-674508806).

- `hardware.sane` module installs sane impurly
- [issue 90201](https://github.com/NixOS/nixpkgs/issues/90201)
- [issue 90184](https://github.com/NixOS/nixpkgs/issues/90184)

The current way NixOS enables to configure access to scanner, is via the
`hardware.sane` module, which interacts [just a
bit](https://github.com/NixOS/nixpkgs/blob/5d8dd5c2598a74761411bc9bef7c9111d43d2429/nixos/modules/services/hardware/sane.nix#L34)
wish `saned`, but mostly does nothing besides setting `SANE_CONFIG_DIR` and
`LD_LIBRARY_PATH`. This is bad because:

1. Running `nixos-rebuild` after a change to sane or it's configuration,
requires the user to logout and login, to see effects of the changes.

2.
[Apparently](https://github.com/NixOS/nixpkgs/issues/90201#issuecomment-683304279)
`LD_LIBRARY_PATH` is cleared when an application runs with extra capabilities.
This causes the current gnome-shell wayland wrapper to unset the
`LD_LIBRARY_PATH` many scanner related programs now rely upon.

Declarative wrappers should enable us to make such applications that use a
`SANE_CONFIG_DIR` and `LD_LIBRARY_PATH` to be configured during the wrap phase,
and get rid of these global environment variables.

# Detailed design
[design]: #detailed-design

The end goal is to make the experience of getting a derivation wrapped as
automatic as possible. A derivation that needs some environment variables in
order to work will get these environment variables set in the wrapper by
`mkDerivation`'s standard `fixupPhase`. As a start, we'll (me probably) will
introduce a new `makeWrapperAuto` setup hook that will take care of this in the
way described as follows.

As a start, we'll need to think about all packages in Nixpkgs that using them
requires some environment variables to be set. Every such package will put in
doronbehar marked this conversation as resolved.
Show resolved Hide resolved
`$dev/nix-support/wrappers.json` a list of environment variables that are
"linked" to this package. "linked" means that using this package requires these
environment variables to be set in runtime.

`makeWrapperAuto` will traverse all `buildInputs` and `propagatedBuildInputs`
of a derivation, and look for a `wrappers.json` file in these inputs. It will
collect all the environment variables that need to be set in the resulting
executables, by merging all of the values of the environment variables in all
of the inputs' `wrappers.json` files. `wrappers.json` might look like this for
a given package:

```json
{
"GI_TYPELIB_PATH": [
"/nix/...gobject-introspection.../...",
"/nix/...librsvg.../...",
],
"GIO_EXTRA_MODULES": [
"/nix/...dconf.../lib/gio/modules"
],
"XDG_DATA_DIRS": [
"/nix/...gtk+3.../...",
"/nix/...gsettings-desktop-schemas.../..."
],
Copy link
Contributor

Choose a reason for hiding this comment

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

The "store API" is Dict[String, List[StorePath]] but the "runtime API" is Dict[String, String] which raises a number of questions on how we implement that.

  • How does the tool know how to join these elements?
    • : is the most common but there are exceptions.
    • What if escaping is needed?
    • Do we need to provide any ordering guarantees?
  • Does this support setting non-list variables?

We can also go further. Instead of setting $PATH to a massive list does it make more sense to build a folder of symlinks and set $PATH to a single element? If so will we special-case some variables or should we use a more detailed schema (as proposed below)?

Furthermore does this replace or prepend or append? Replace seems the purest but commands such as parallel -- command-from-my-path are not uncommon. In that case there seems to be no good option.

So it seems to me that no single set of answers to the above questions will handle the cases we already know about. It seems like we will need to provide more info.

{
  "NAME": {
    "paths": [ ... ],
    "separator": ":",
    "sort": "topological",
  }
}

Although I think that is probably going to lead to huge complexity (for example do we need escaping for any var?) It probably makes more sense to have more semantic options.

{
  "NAME": {
    "$type": "search-path",
    "paths": [ ... ],
  }
}

Then we can define types that work for PATH, LUA_PATH and so on. Furthermore this leaves space for "arguments" to these types such as escaping modes or whether to allow/deny/force making a directory of symlinks rather than a large variable. (These don't need to be implemented upfront)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about answers to some of these questions back when I implemented https://github.com/NixOS/nixpkgs/pull/85103/files#diff-aad93ba22adfe208c7b970a7b9d0f73dba9306b8a4fc4eaacfb2ac331aed9ea1R51-R74 . I prepared some answers in the RFC to be pushed, but I don't know:

  • Why order would matter?
  • I never encountered a case where paths should be escaped, or when other values should be escaped. In case of environment variables values that need escaping, they probably will not be originated from packages, and that can be handled in the postInstall snippet example below, by manually quoting values in the json file..

We can also go further. Instead of setting $PATH to a massive list does it make more sense to build a folder of symlinks and set $PATH to a single element? If so will we special-case some variables or should we use a more detailed schema (as proposed below)?

I thought about this exactly even back when I implemented NixOS/nixpkgs#85103 so it will be possible to avoid what NixOS/nixpkgs#84689 fixed. I addressed this idea in the changes I made to the RFC that I haven't pushed yet.

I'm not sure I understand:

Furthermore does this replace or prepend or append? Replace seems the purest but commands such as parallel -- command-from-my-path are not uncommon. In that case there seems to be no good option.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why order would matter?

If two packages have a bin/foo then the order will decide which one is used. For most of these PATH-like variables the first match is used.

I never encountered a case where paths should be escaped, or when other values should be escaped.

It is rare but does happen. For things like PATH I don't think there is even a way to quote elements. You simply can't have a path that contains : in your PATH. At the very least it would be nice to raise an error. It would be even better if we can "alias" the path to a safe name or escape the invalid characters (if supported by the variable, which is rare)

I'm not sure I understand:

My point is that the argument to a parallel is a command to run, but that command should be pulled from the PATH of the calling shell, not a PATH that may be set but a wrapper in the parallel derivation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why order would matter?

If two packages have a bin/foo then the order will decide which one is used. For most of these PATH-like variables the first match is used.

I think it's super rare to have two packages providing the same binary like that, and finding them used together in the wrapping while wishing only one package's out/bin to rule..

Also paths escaping is too rare, so I think it'll be best to leave these topics out of scope for the RFC.

As for parallel commands. Do you mean parallel invocations in the builds of derivations? something like:

  preInstall = ''
    parallel -- commands that take a-long time
  '';

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think collisions are super rare. After all nix has a priority system for some reason. I think we should at least define how this is handled.

As for the escaping I think that it is fine to ignore this for now. But I think it should be mentioned explicitly.

For the "parallel problem" I mean in general. Any time you have a wrapper that both needs commands itself as well as forwards a user's command we have this problem. I think we should discuss how this is handled. Currently in nixpkgs it is very inconsistent.

But the most important point still stands. We need some configuration. If only to specify the separator. So I think that needs to be included.

Copy link
Member

Choose a reason for hiding this comment

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

PATH order is support important. Tools like trash, safe-rm and molly-guard heavily rely on it. Also I commonly use a higher priority entry in PATH to create wrappers for none interactive shells. For environment variables which are similar to PATH like NIX_PATH order is also super important and ordering the entries for example alphabetically breaks things.

What is safe to do is deduplication when an entry is encountered the second time and I think this should be done by default to prevent to long environment variables.

"GDK_PIXBUF_MODULE_FILE": "/nix/...librsvg.../lib/gdk.../loaders.cache",
}
```

The information found inside an input's `wrappers.json` will specify the
wrapper information not only for itself, but for all its dependencies (including transitives) as well. Thus, in
contrast to the [POC Nixpkgs PR](https://github.com/NixOS/nixpkgs/pull/85103)
and the [original design of the
RFC](https://github.com/doronbehar/rfcs/blob/60d3825fdd4e6574b7e5d70264445d1c801368c6/rfcs/0075-declarative-wrappers.md#L251),
prior to [the 1st
meeting](https://github.com/NixOS/rfcs/pull/75#issuecomment-760942876),
traversing all the inputs and the inputs' inputs, will not happen during eval
time and only partly, during build time - every package already built will
provide its reverse dependencies all the information they need about
environment variables.

Most of the work to do will be:

1. Gather information about what environment variables are "linked" to each
package, and edit these derivations to include a `wrappers.json` in them.
This should be done with `makeWrapperAuto` as well, see (2).
2. Design the `makeWrapperAuto` shell hook:
- It should introduce a shell function (to be called `combineWrappersInfo`) that
will allow piping a JSON string from `builtins.toJSON` and spit a
`wrappers.json` that will include both what was piped into it, and the
content from the package's various inputs' `wrappers.json` files.
- It should wrap the executables in `$out/bin/` according to
doronbehar marked this conversation as resolved.
Show resolved Hide resolved
what's currently in this package's `wrappers.json`, during `fixupPhase`.
- The above should be also possible to do manually for executables outside
`$out/bin/` by setting `wrapExtraPrograms` on the derivation:

```nix
wrapExtraPrograms = [ "/libexec/" "/share/scripts" ];
```

3. Most of the packages with linked environment variables, have lots of reverse
dependencies, so once `makeWrapperAuto` is ready, it'd nice to have a hydra
job that will build all of these packages with the `wrappers.json` file in
them. For instance these packages include:
- `gdk-pixbuf`
- `gsettings-desktop-schemas`
- `pango`
- `gtk3`

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

When switching to `makeWrapperAuto` from `makeWrapper` there shouldn't be
manual usage of `wrapProgram` for most cases. A package that uses `wrapProgram`
should be able to switch to `combineWrappersInfo` and declare any nontrivial
environment variables with it to get propagated to reverse dependencies and to
it's executables automatically.

Currently I imagine the usage of `combineWrappersInfo` (the name can be debated) as
so:

```nix
# Propagate GST plugins' path
postInstall = ''
echo "${builtins.toJSON {
GST_PLUGIN_SYSTEM_PATH_1_0 = [
# @out@ should be expanded by `combineWrappersInfo` to what's in `$out`, see:
Copy link
Member

Choose a reason for hiding this comment

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

We should probably use ${placeholder "out"} here? Or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should probably use ${placeholder "out"} here? Or am I missing something?

A lot of people ask that, see NixOS/nixpkgs#85103 (comment) - I linked that explanation in the comment in the code snippet.

# https://github.com/NixOS/nixpkgs/pull/85103#issuecomment-613071343
"@out@/lib/gstreamer-1.0"
];
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to have quoting issues?

Either way would it be easier to pass the JSON in as an env var? Then it can just be passed as an argument? Or just use a well-known env var that can be automatically used by the command.

Copy link
Contributor Author

@doronbehar doronbehar Aug 7, 2021

Choose a reason for hiding this comment

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

I don't see quoting issues.. We can use:

let
  envInfo = {
    GST_PLUGIN_SYSTEM_PATH_1_0 = [
      "@out@/lib/gstreamer-1.0"
    ];
  };
in stdenv.mkDerivation {
  # ...
  postInstall = ''
    echo "${builtins.toJSON envInfo}" | combineWrappersInfo
  '';
  # ...
}

But that's of course is only a visual change.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't echo "${builtins.toJSON { foo = "bar"; }}" result in echo "{"foo":"bar"}"? Or am I missing something here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't echo "${builtins.toJSON { foo = "bar"; }}" result in echo "{"foo":"bar"}"? Or am I missing something here?

Oh I understand now.. Perhaps we should also use lib.strings.escapeShellArg. I have changed this composition into something much nicer.

}}" | combineWrappersInfo
'';
```

`wrapQtAppsHook` and `wrapGAppsHook` should be replaced with `makeWrapperAuto`
while enable derivations to get rid of well known workarounds such as:
doronbehar marked this conversation as resolved.
Show resolved Hide resolved

```nix
# hook for gobject-introspection doesn't like strictDeps
# https://github.com/NixOS/nixpkgs/issues/56943
strictDeps = false;
```

And often seen in Python + Qt programs:

```nix
preFixup = ''
makeWrapperArgs+=("''${qtWrapperArgs[@]}")
'';
```

# Drawbacks
[drawbacks]: #drawbacks

Using `wrapProgram` will be simpler then using `combineWrappersInfo` and it might be
doronbehar marked this conversation as resolved.
Show resolved Hide resolved
hard to explain why is there no `wrapProgramAuto`. However, this interface
might get improved in design through this RFC or in the future and in any case
proper documentation should help.

# Alternatives
[alternatives]: #alternatives

Perhaps our shell hooks _can_ be fixed / improved, and we could help make it
easier to debug them via `NIX_DEBUG`. Then it might help us track down e.g why
environment variables are added twice etc. Still though, this wouldn't solve
many issues presented above.

# Unresolved questions
[unresolved]: #unresolved-questions

Discussing the design I guess, here or in the Nixpkgs PR that will follow this
RFC.

# Future work
[future]: #future-work

Fix all wrapper related issues declaratively!