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

Source complete env in nix-shell with __structuredAttrs = true; #4770

Merged
merged 10 commits into from
Jul 12, 2021

Conversation

Ma27
Copy link
Member

@Ma27 Ma27 commented May 4, 2021

Notes
  • This is intended to help getting structured attrs in nixpkgs forward (stdenv: enable __structuredAttrs nixpkgs#72074)
  • Missing things:
    • Move logic to generate .attrs.json & .attrs.sh into ParsedDerivation
    • Don't create .attrs.json in PWD
    • Address review comments
    • nix develop

CCing @regnat (because I had to touch input rewrites inside {Local,}DerivationGoal a bit), @Ericson2314 (because he touched a lot of code down there), @globin (because of the nixpkgs PR), @edolstra

Commit message

This is needed to push the adoption of structured attrs[1] forward. It's
now checked if a __json exists in the environment-map of the derivation
to be openend in a nix-shell.

Derivations with structured attributes enabled also make use of a file
named .attrs.json containing every environment variable represented as
JSON which is useful for e.g. exportReferencesGraph[2]. To
provide an environment similar to the build sandbox, nix-shell now
adds a .attrs.json to cwd (which is mostly equal to the one in the
build sandbox) and removes it using an exit hook when closing the shell.

To avoid leaking internals of the build-process to the nix-shell, the
entire logic to generate JSON and shell code for structured attrs was
moved into the ParsedDerivation class.

[1] https://nixos.mayflower.consulting/blog/2020/01/20/structured-attrs/
[2] https://nixos.org/manual/nix/unstable/expressions/advanced-attributes.html#advanced-attributes

@Ma27
Copy link
Member Author

Ma27 commented May 4, 2021

Also, anybody with sufficient Darwin & C++ knowledge to tell me what to use instead of std::filesystem::current_path()? 😅

@thufschmitt
Copy link
Member

Also, anybody with sufficient Darwin & C++ knowledge to tell me what to use instead of std::filesystem::current_path()? sweat_smile

I think it’s an old-llvm issue rather than a darwin one: https://releases.llvm.org/7.0.0/projects/libcxx/docs/UsingLibcxx.html#using-filesystem-and-libc-fs . Apparently you need to add a -lc++fs linker flag (or upgrade to clang9, but that’s another issue I guess)

Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

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

Did a first review.

  • A big 👍 on the end-goal
  • Implementation-wise, I’m not really fond of inlining the DerivationGoal in nix-build.cc. That’s quite a big abstraction breach (and time has already brought enough of these…)
  • I’m also not thrilled by the .attrs.json in PWD. It’d be nice if there were an alternative way of doing that − if only because I’d like to start two nix-shells from the same directory without them meddling with each other.

(this also made me realise that nix-shell doesn’t really support CA derivations right now… but that’s another topic)

src/libstore/build/derivation-goal.cc Outdated Show resolved Hide resolved
src/libstore/build/derivation-goal.cc Outdated Show resolved Hide resolved
worker.store.pathInfoToJSON(jsonRoot,
exportReferences(storePaths), false, true);
}
json[i.key()] = nlohmann::json::parse(str.str()); // urgh
Copy link
Member

Choose a reason for hiding this comment

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

I guess you can rewrite pathInfoToJSON to use nlohmann::json to avoid that (letting @edolstra judge on that because that would probably mean making store-api.cc depend on <nlohmann/json.hpp>)

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, should this also happen in this PR?

Copy link
Member

Choose a reason for hiding this comment

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

Nah, I just didn’t notice that this was only appearing in the diff because it was code that moved around rather than new code. So can be left as a follow-up :)

src/libstore/build/local-derivation-goal.cc Outdated Show resolved Hide resolved
src/nix-build/nix-build.cc Outdated Show resolved Hide resolved
src/nix-build/nix-build.cc Outdated Show resolved Hide resolved
src/nix-build/nix-build.cc Outdated Show resolved Hide resolved
@Ericson2314
Copy link
Member

Ericson2314 commented May 7, 2021

@regnat wrote

Also, does this really need to be a member of DerivationGoal or could it be a standalone function just taking parsedDrv as input? (or maybe even a method of ParsedDerivation?)

Yes i would strongly prefer that too. Let's always take things apart as much as possible.

@Ma27
Copy link
Member Author

Ma27 commented May 7, 2021

@regnat @Ericson2314 I'm not sure about it and this is in fact my primary pain point as well. The problem is that from my understanding exportReferences (from derivation goal) is needed to be able to create the value for exportReferencesGraph and IMHO this variable should also appear in a nix-shell. Do you have a hint how to do that without using DerivationGoal itself? As I said in the OP, I'm not too experienced with Nix internals :)

Other than that, it's totally possible to move this to ParsedDerivation (in fact I even have something for it in my git stash lying around, but I didn't use it in the end because of my problem with exportReferencesGraph :D).

Regarding .attrs.json being in cwd: I've seen a few things such as pkgs.closureInfo making use of .attrs.json by assuming in the buildCommand that it's in the cwd. As nix-shell should emulate the build env as good as possible, this isn't such a big problem from my PoV. However I just realized that I have to make sure to only remove .attrs.json if it was added by nix-shell and didn't exist there already.

@globin
Copy link
Member

globin commented May 7, 2021

Regarding .attrs.json being in cwd: I've seen a few things such as pkgs.closureInfo making use of .attrs.json by assuming in the buildCommand that it's in the cwd. As nix-shell should emulate the build env as good as possible, this isn't such a big problem from my PoV. However I just realized that I have to make sure to only remove .attrs.json if it was added by nix-shell and didn't exist there already.

There are quite a number of uses of .attrs.sh with structured-attrs, e.g. passAsFile is not supported and needs to be replaced by jq parsing .attrs.json also if one wants to create a builder not relying on bash it will most probably use .attrs.json, although that will create other problems with nix-shell.

Maybe calling nix-shell could create a subdir in the cwd to mitigate the cleanup problem? Although that creates other problems with nix-shell, when wanting to run only selected phases and not the whole genericBuild.
With Linux 5.11 overlayfs mounts are supported for non-privileged users, maybe that might be a viable route in the longer run.

@thufschmitt
Copy link
Member

from my understanding exportReferences (from derivation goal) is needed to be able to create the value for exportReferencesGraph and IMHO this variable should also appear in a nix-shell. Do you have a hint how to do that without using DerivationGoal itself? As I said in the OP, I'm not too experienced with Nix internals :)

Ahah, I missed that ;)
Looking at exportReferences, I think it could also be removed from DerivationGoal (except maybe the part that checks that we don’t try to access something outside of the closure, but that can probably be pulled out, and we can ignore it for nix-shell).

Regarding .attrs.json being in cwd: I've seen a few things such as pkgs.closureInfo making use of .attrs.json by assuming in the buildCommand that it's in the cwd. As nix-shell should emulate the build env as good as possible, this isn't such a big problem from my PoV. However I just realized that I have to make sure to only remove .attrs.json if it was added by nix-shell and didn't exist there already.

You’d need to be a bit more careful − somehow keep a reference count of all the nix-shell that still point to it (for example I can open two nix-shell in a row, and then close the first one but keep the second open)… that sounds quite complex.

I wonder whether we could strengthen the semantics of __structuredAttrs to say that the .attrs.json file isn’t necessarily in CWD should be accessed via some environment variable ATTRS_JSON_FILE (with some magic to somehow stay backwards compatible, like have ATTRS_JSON_FILE=$PWD/.attrs.json for a normal build) so that we have more flexibility here.

@Ma27
Copy link
Member Author

Ma27 commented May 7, 2021

Looking at exportReferences, I think it could also be removed from DerivationGoal (except maybe the part that checks that we don’t try to access something outside of the closure, but that can probably be pulled out, and we can ignore it for nix-shell).

Ok, that's good to know. I'll check if that's doable without having to fix too much stuff after that. @regnat do you think it makes sense to add it to the Store itself, the {Parsed,}Derivation (if yes, which? :)). Or do you have a more suitable place in mind?

You’d need to be a bit more careful − somehow keep a reference count of all the nix-shell that still point to it (for example I can open two nix-shell in a row, and then close the first one but keep the second open)… that sounds quite complex.

Ohhhh, you're absolutely right, I didn't think of that, sorry!!

I actually think that ATTRS_JSON_FILE may be the way to go: for the builder we'd only need to set it to $PWD/.attrs.json, for nix-shell we could use a temporary directory. I'm happy to change that accordingly if @globin and @edolstra would be fine with that.

@Ma27 Ma27 requested a review from thufschmitt May 9, 2021 15:48
@Ma27
Copy link
Member Author

Ma27 commented May 9, 2021

@regnat changed your primary two issues (moved code to generate .attrs.{sh,json} into ParsedDerivation, use env vars to reference these files in both the nix-shell & the build sandbox). Does the approach look okay to you now? If yes, I can resolve the remaining review comments to get this completely ready :)

(would also appreciate a review from @Ericson2314 & @edolstra btw)

Copy link
Member

@Ericson2314 Ericson2314 left a comment

Choose a reason for hiding this comment

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

I'm on my phone so can't review so well, but very much liking the look of the split. That's good even if we weren't fixing functionality with it.

@@ -695,6 +695,8 @@ public:

const Stats & getStats();

StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths);
Copy link
Member

Choose a reason for hiding this comment

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

To be honest, I'm a bit way of putting more odd and ends (i.e. non-virtual methods) in the Store class (I prefer to keep "interfaces" slim like type classes.

But then again, this method doesn't have any other types it might be associated with (Store paths are "upstream" of stores), so maybe it's fine.

@edolstra
Copy link
Member

I actually think that ATTRS_JSON_FILE may be the way to go: for the builder we'd only need to set it to $PWD/.attrs.json, for nix-shell we could use a temporary directory. I'm happy to change that accordingly if @globin and @edolstra would be fine with that.

Yes, I think that's a good idea. Writing to ./.attrs.json breaks if I have two nix-shells with a different -A attribute.

BTW given the legacy status of nix-shell, it's more important to consider how structured attrs should work with nix develop. I think it mostly already works since nix develop builds a pseudo-derivation to extract the stdenv environment, and the structured attrs are present in that build.

Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

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

@regnat changed your primary two issues (moved code to generate .attrs.{sh,json} into ParsedDerivation, use env vars to reference these files in both the nix-shell & the build sandbox). Does the approach look okay to you now?

Yes, looks great. And I have to say that I’m utterly pleased by
image
😄

@Ma27
Copy link
Member Author

Ma27 commented May 10, 2021

Yes, I think that's a good idea. Writing to ./.attrs.json breaks if I have two nix-shells with a different -A attribute.

After realising that implementing this isn't as hard as I thought, I added it in 2a247b8

I think it mostly already works since nix develop

I'm currently on nix (Nix) 2.4pre20210503_6d2553a (both CLI & daemon) and it doesn't work for me on @globin's structured-attrs branch:

λ ma27 [~/Projects/nixpkgs-structured-attrs] at  structured-attrs ?
→ nix develop .#hello --builders ''                                                                                                                                                                    [7c3a400378b]
error: builder for '/nix/store/s3d3hljq64g46riqv7y2hni62q3cy42w-hello-2.10-env.drv' failed to produce output path for output 'out' at '/nix/store/s3d3hljq64g46riqv7y2hni62q3cy42w-hello-2.10-env.drv.chroot/nix/store/pr9771b3i92n8k2lk98aibwgfl0crnq5-hello-2.10-env'

BTW given the legacy status of nix-shell, it's more important to consider how structured attrs should work with nix develop

I'm happy to take a look at this as well at some point :)

Btw given that 2.4 doesn't seem to have a roadmap and @globin told me that he'd like to try getting this into one of the upcoming NixOS releases (not 21.05 though), I'd like to raise the question whether it'd be okay for you to backport the nix-shell part. I know that the diff to 2.3 is well, kinda large and a lot of the code would probably have to be written a second time, but I'd like now know in the first place if this would be okay for you.

@Ma27
Copy link
Member Author

Ma27 commented May 12, 2021

Btw @edolstra @regnat I also fixed the integration with nix develop and structured attrs :)

@Ma27
Copy link
Member Author

Ma27 commented May 13, 2021

Since I'm not sure if we should add even more changes in this already non-trivial (but kinda ready) PR and AFAIU the other suggestion isn't applicable. I split the JSON and bash generation into two methods of ParsedDerivation which resolves the other two comments, so I'd re-request a review now :)

@Ma27 Ma27 requested a review from thufschmitt May 13, 2021 14:23
Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

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

LGTM, except the nix develop part which looks like black magic to me, so is probably worth some explanations and tests

(or alternatively you can drop it for the time being and add it back as a follow-up)

src/nix/get-env.sh Show resolved Hide resolved
src/nix/develop.cc Outdated Show resolved Hide resolved
@Ma27
Copy link
Member Author

Ma27 commented May 18, 2021

@regnat Added a testcase and a few comments :)

@Ma27 Ma27 requested a review from thufschmitt May 18, 2021 13:15
Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

It would probably be nicer if the --redirect feature of nix develop wasn’t depending on stdenv, but as nix develop is already partially tied to it I guess it’s not a really big issue

@Ma27
Copy link
Member Author

Ma27 commented May 28, 2021

@edolstra anything missing here to get this merged? :)

src/libstore/build/local-derivation-goal.cc Outdated Show resolved Hide resolved
src/libstore/build/local-derivation-goal.cc Outdated Show resolved Hide resolved
}
json[i.key()] = nlohmann::json::parse(str.str()); // urgh
}
if (auto structAttrsJson = parsedDrv->prepareStructuredAttrs(inputRewrites, worker.store, inputPaths)) {
Copy link
Member

Choose a reason for hiding this comment

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

Does prepareStructuredAttrs need inputRewrites? It should be possible to apply the rewrites to the structAttrsJson.value() right?

Copy link
Member Author

Choose a reason for hiding this comment

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

@edolstra Yeah I kept it in this codepath as it used to be there previously and seemed kinda correct to do the rewrite when building the datastructure. But if you'd prefer to move it to LocalDerivationGoal, that'd work for me as well :)

Copy link
Member Author

Choose a reason for hiding this comment

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

@edolstra OK actually I think you're right, given that nix-shell uses std::nullopt for input rewrites it's not a strict part of prepareStructuredAttrs, so I moved it to LocalDerivationGoal. Gonna re-request a review from @regnat to make sure I didn't do anything wrong with CA integrations there :)

src/libstore/parsed-derivations.cc Outdated Show resolved Hide resolved
src/libstore/parsed-derivations.hh Outdated Show resolved Hide resolved
src/libstore/store-api.hh Show resolved Hide resolved
Ma27 added 6 commits June 22, 2021 19:15
This is needed to push the adoption of structured attrs[1] forward. It's
now checked if a `__json` exists in the environment-map of the derivation
to be openend in a `nix-shell`.

Derivations with structured attributes enabled also make use of a file
named `.attrs.json` containing every environment variable represented as
JSON which is useful for e.g. `exportReferencesGraph`[2]. To
provide an environment similar to the build sandbox, `nix-shell` now
adds a `.attrs.json` to `cwd` (which is mostly equal to the one in the
build sandbox) and removes it using an exit hook when closing the shell.

To avoid leaking internals of the build-process to the `nix-shell`, the
entire logic to generate JSON and shell code for structured attrs was
moved into the `ParsedDerivation` class.

[1] https://nixos.mayflower.consulting/blog/2020/01/20/structured-attrs/
[2] https://nixos.org/manual/nix/unstable/expressions/advanced-attributes.html#advanced-attributes
This way no derivation has to expect that these files are in the `cwd`
during the build. This is problematic for `nix-shell` where these files
would have to be inserted into the nix-shell's `cwd` which can become
problematic with e.g. recursive `nix-shell`.

To remain backwards-compatible, the location inside the build sandbox
will be kept, however using these files directly should be deprecated
from now on.
Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

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

Just had a look at the last commit (9952e4e), but it looks OK to me.

The thing with the rewrites of the json is a bit weird, but it was already weird before the PR, so it’s not really a problem for now

src/libstore/parsed-derivations.cc Outdated Show resolved Hide resolved
@Ma27
Copy link
Member Author

Ma27 commented Jul 2, 2021

cc @edolstra anything else tbd to get this merged? :)

 Conflicts:
        src/nix/develop.cc
        src/nix/get-env.sh
        tests/shell.nix
@Ma27 Ma27 requested a review from thufschmitt July 12, 2021 13:52
@Ma27
Copy link
Member Author

Ma27 commented Jul 12, 2021

@edolstra @regnat merge master into this branch. Another review would be appreciated :)

If nothing else is to be done, we can also merge this btw :)

@edolstra edolstra merged commit e06c272 into NixOS:master Jul 12, 2021
@Ma27 Ma27 deleted the structured-attrs-shell branch July 12, 2021 16:44
@Ma27 Ma27 mentioned this pull request Sep 30, 2021
Ma27 added a commit to Ma27/nix that referenced this pull request Sep 24, 2023
In NixOS#4770 I implemented proper `nix-shell(1)` support for derivations
using `__structuredAttrs = true;`. Back then we decided to introduce two
new environment variables, `NIX_ATTRS_SH_FILE` for `.attrs.sh` and
`NIX_ATTRS_JSON_FILE` for `.attrs.json`. This was to avoid having to
copy these files to `$NIX_BUILD_TOP` in a `nix-shell(1)` session which
effectively meant copying these files to the project dir without
cleaning up afterwords[1].

On last NixCon I resumed hacking on `__structuredAttrs = true;` by
default for `nixpkgs` with a few other folks and I realized that
the shell experience isn't as nice as I remembered and I identified
the following problems:

* A lot of builders in `nixpkgs` don't care about the env vars and
  assume that `.attrs.sh` is in `$NIX_BUILD_TOP`. I considered changing
  Nix that way, but then we'd have to either move $NIX_BUILD_TOP for
  shell sessions to a temporary location (and thus breaking a lot of
  assumptions) or we'd reintroduce all the problems we solved back then
  by using these two env vars.

  I think this is partly because I didn't document these variables back
  then (mea culpa), so I decided to drop all mentions of
  `.attrs.{json,sh}` in the  manual and only refer to `$NIX_ATTRS_SH_FILE`
  and `$NIX_ATTRS_JSON_FILE`. The same applies to all our integration tests.
  Theoretically we could deprecated using `"$NIX_BUILD_TOP"/.attrs.sh` in
  the future now.

* `nix develop` and `nix print-dev-env` don't support this environment
  variable at all even though they're supposed to be part of the replacement
  for `nix-shell` - for the drv debugging part to be precise.

  This isn't a big deal for the vast majority of derivations, i.e.
  derivations relying on nixpkgs' `stdenv` wiring things together
  properly. This is because `nix develop` effectively "clones" the
  derivation and replaces the builder with a script that dumps all of
  the environment, shell variables, functions etc, so the state of
  structured attrs being "sourced" is transmitted into the dev shell and
  most of the time you don't need to worry about `.attrs.sh` not
  existing because the shell is correctly configured and the

      if [ -e .attrs.sh ]; then source .attrs.sh; fi

  is simply omitted.

  However, this will break when having a derivation that reads e.g. from
  `.attrs.json` like

      with import <nixpkgs> {};
      runCommand "foo" { __structuredAttrs = true; foo.bar = 23; } ''
        cat $NIX_ATTRS_JSON_FILE # doesn't work because it points to /build/.attrs.json
      ''

  To work around this I employed a similar approach as it exists for
  `nix-shell`: the `NIX_ATTRS_{JSON,SH}_FILE` vars are replaced with
  temporary locations.

  The contents of `.attrs.sh` and `.attrs.json` are now written into the
  JSON by `get-env.sh`, the builder that `nix develop` injects into the
  derivation it's debugging. So finally the exact file contents are
  present and exported by `nix develop`.

  I also made `.attrs.json` a JSON string in the JSON printed by
  `get-env.sh` on purpose because then it's not necessary to serialize
  the object structure again. `nix develop` only needs the JSON
  as string because it's only written into the temporary file.

  I'm not entirely sure if it makes sense to also use a temporary
  location for `nix print-dev-env` (rather than just skipping the
  rewrite in there), but this would probably break certain cases where
  it's relied upon `$NIX_ATTRS_SH_FILE` to exist (prime example are the
  `nix print-dev-env` test-cases I wrote in this patch using
  `tests/shell.nix`, these would fail because the env var exists, but it
  cannot read from it).

[1] NixOS#4770 (comment)
Ma27 added a commit to Ma27/nix that referenced this pull request Sep 28, 2023
In NixOS#4770 I implemented proper `nix-shell(1)` support for derivations
using `__structuredAttrs = true;`. Back then we decided to introduce two
new environment variables, `NIX_ATTRS_SH_FILE` for `.attrs.sh` and
`NIX_ATTRS_JSON_FILE` for `.attrs.json`. This was to avoid having to
copy these files to `$NIX_BUILD_TOP` in a `nix-shell(1)` session which
effectively meant copying these files to the project dir without
cleaning up afterwords[1].

On last NixCon I resumed hacking on `__structuredAttrs = true;` by
default for `nixpkgs` with a few other folks and getting back to it,
I identified a few problems with the current state and how it's
used in `nixpkgs`:

* A lot of builders in `nixpkgs` don't care about the env vars and
  assume that `.attrs.sh` and `.attrs.json` are in `$NIX_BUILD_TOP`.
  The sole reason why this works is that `nix-shell(1)` sources
  the contents of `.attrs.sh` and then sources `$stdenv/setup` if it
  exists. This may not be pretty, but it works.

  However, the same assumption cannot be made for `.attrs.json` which
  can only be used in a `nix-shell(1)` session when using
  `$NIX_ATTRS_JSON_FILE`.

  I considered changing Nix to be compatible with what nixpkgs
  effectively does, but then we'd have to either move $NIX_BUILD_TOP for
  shell sessions to a temporary location (and thus breaking a lot of
  assumptions) or we'd reintroduce all the problems we solved back then
  by using these two env vars.

  In other words, this is pretty inconsistent with how the buildscripts
  behave in an actual build vs in a debug shell and I think that relying
  solely on the environment variabels is the best way to solve that.

  Also, I think this is partly because I didn't document these variables back
  then (mea culpa), so I decided to drop all mentions of
  `.attrs.{json,sh}` in the  manual and only refer to `$NIX_ATTRS_SH_FILE`
  and `$NIX_ATTRS_JSON_FILE`. The same applies to all our integration tests.
  Theoretically we could deprecated using `"$NIX_BUILD_TOP"/.attrs.sh` in
  the future now.

* `nix develop` and `nix print-dev-env` don't support this environment
  variable at all even though they're supposed to be part of the replacement
  for `nix-shell` - for the drv debugging part to be precise.

  This isn't a big deal for the vast majority of derivations, i.e.
  derivations relying on nixpkgs' `stdenv` wiring things together
  properly. This is because `nix develop` effectively "clones" the
  derivation and replaces the builder with a script that dumps all of
  the environment, shell variables, functions etc, so the state of
  structured attrs being "sourced" is transmitted into the dev shell and
  most of the time you don't need to worry about `.attrs.sh` not
  existing because the shell is correctly configured and the

      if [ -e .attrs.sh ]; then source .attrs.sh; fi

  is simply omitted.

  However, this will break when having a derivation that reads e.g. from
  `.attrs.json` like

      with import <nixpkgs> {};
      runCommand "foo" { __structuredAttrs = true; foo.bar = 23; } ''
        cat $NIX_ATTRS_JSON_FILE # doesn't work because it points to /build/.attrs.json
      ''

  To work around this I employed a similar approach as it exists for
  `nix-shell`: the `NIX_ATTRS_{JSON,SH}_FILE` vars are replaced with
  temporary locations.

  The contents of `.attrs.sh` and `.attrs.json` are now written into the
  JSON by `get-env.sh`, the builder that `nix develop` injects into the
  derivation it's debugging. So finally the exact file contents are
  present and exported by `nix develop`.

  I also made `.attrs.json` a JSON string in the JSON printed by
  `get-env.sh` on purpose because then it's not necessary to serialize
  the object structure again. `nix develop` only needs the JSON
  as string because it's only written into the temporary file.

  I'm not entirely sure if it makes sense to also use a temporary
  location for `nix print-dev-env` (rather than just skipping the
  rewrite in there), but this would probably break certain cases where
  it's relied upon `$NIX_ATTRS_SH_FILE` to exist (prime example are the
  `nix print-dev-env` test-cases I wrote in this patch using
  `tests/shell.nix`, these would fail because the env var exists, but it
  cannot read from it).

[1] NixOS#4770 (comment)
Ma27 added a commit to Ma27/nix that referenced this pull request Sep 28, 2023
In NixOS#4770 I implemented proper `nix-shell(1)` support for derivations
using `__structuredAttrs = true;`. Back then we decided to introduce two
new environment variables, `NIX_ATTRS_SH_FILE` for `.attrs.sh` and
`NIX_ATTRS_JSON_FILE` for `.attrs.json`. This was to avoid having to
copy these files to `$NIX_BUILD_TOP` in a `nix-shell(1)` session which
effectively meant copying these files to the project dir without
cleaning up afterwords[1].

On last NixCon I resumed hacking on `__structuredAttrs = true;` by
default for `nixpkgs` with a few other folks and getting back to it,
I identified a few problems with the how it's used in `nixpkgs`:

* A lot of builders in `nixpkgs` don't care about the env vars and
  assume that `.attrs.sh` and `.attrs.json` are in `$NIX_BUILD_TOP`.
  The sole reason why this works is that `nix-shell(1)` sources
  the contents of `.attrs.sh` and then sources `$stdenv/setup` if it
  exists. This may not be pretty, but it mostly works. One notable
  difference when using nixpkgs' stdenv as of now is however that
  `$__structuredAttrs` is set to `1` on regular builds, but set to
  an empty string in a shell session.

  Also, `.attrs.json` cannot be used in shell sessions because
  it can only be accessed by `$NIX_ATTRS_JSON_FILE` and not by
  `$NIX_BUILD_TOP/.attrs.json`.

  I considered changing Nix to be compatible with what nixpkgs
  effectively does, but then we'd have to either move $NIX_BUILD_TOP for
  shell sessions to a temporary location (and thus breaking a lot of
  assumptions) or we'd reintroduce all the problems we solved back then
  by using these two env vars.

  This is partly because I didn't document these variables back
  then (mea culpa), so I decided to drop all mentions of
  `.attrs.{json,sh}` in the  manual and only refer to `$NIX_ATTRS_SH_FILE`
  and `$NIX_ATTRS_JSON_FILE`. The same applies to all our integration tests.
  Theoretically we could deprecated using `"$NIX_BUILD_TOP"/.attrs.sh` in
  the future now.

* `nix develop` and `nix print-dev-env` don't support this environment
  variable at all even though they're supposed to be part of the replacement
  for `nix-shell` - for the drv debugging part to be precise.

  This isn't a big deal for the vast majority of derivations, i.e.
  derivations relying on nixpkgs' `stdenv` wiring things together
  properly. This is because `nix develop` effectively "clones" the
  derivation and replaces the builder with a script that dumps all of
  the environment, shell variables, functions etc, so the state of
  structured attrs being "sourced" is transmitted into the dev shell and
  most of the time you don't need to worry about `.attrs.sh` not
  existing because the shell is correctly configured and the

      if [ -e .attrs.sh ]; then source .attrs.sh; fi

  is simply omitted.

  However, this will break when having a derivation that reads e.g. from
  `.attrs.json` like

      with import <nixpkgs> {};
      runCommand "foo" { __structuredAttrs = true; foo.bar = 23; } ''
        cat $NIX_ATTRS_JSON_FILE # doesn't work because it points to /build/.attrs.json
      ''

  To work around this I employed a similar approach as it exists for
  `nix-shell`: the `NIX_ATTRS_{JSON,SH}_FILE` vars are replaced with
  temporary locations.

  The contents of `.attrs.sh` and `.attrs.json` are now written into the
  JSON by `get-env.sh`, the builder that `nix develop` injects into the
  derivation it's debugging. So finally the exact file contents are
  present and exported by `nix develop`.

  I also made `.attrs.json` a JSON string in the JSON printed by
  `get-env.sh` on purpose because then it's not necessary to serialize
  the object structure again. `nix develop` only needs the JSON
  as string because it's only written into the temporary file.

  I'm not entirely sure if it makes sense to also use a temporary
  location for `nix print-dev-env` (rather than just skipping the
  rewrite in there), but this would probably break certain cases where
  it's relied upon `$NIX_ATTRS_SH_FILE` to exist (prime example are the
  `nix print-dev-env` test-cases I wrote in this patch using
  `tests/shell.nix`, these would fail because the env var exists, but it
  cannot read from it).

[1] NixOS#4770 (comment)
Ma27 added a commit to Ma27/nix that referenced this pull request Sep 28, 2023
In NixOS#4770 I implemented proper `nix-shell(1)` support for derivations
using `__structuredAttrs = true;`. Back then we decided to introduce two
new environment variables, `NIX_ATTRS_SH_FILE` for `.attrs.sh` and
`NIX_ATTRS_JSON_FILE` for `.attrs.json`. This was to avoid having to
copy these files to `$NIX_BUILD_TOP` in a `nix-shell(1)` session which
effectively meant copying these files to the project dir without
cleaning up afterwords[1].

On last NixCon I resumed hacking on `__structuredAttrs = true;` by
default for `nixpkgs` with a few other folks and getting back to it,
I identified a few problems with the how it's used in `nixpkgs`:

* A lot of builders in `nixpkgs` don't care about the env vars and
  assume that `.attrs.sh` and `.attrs.json` are in `$NIX_BUILD_TOP`.
  The sole reason why this works is that `nix-shell(1)` sources
  the contents of `.attrs.sh` and then sources `$stdenv/setup` if it
  exists. This may not be pretty, but it mostly works. One notable
  difference when using nixpkgs' stdenv as of now is however that
  `$__structuredAttrs` is set to `1` on regular builds, but set to
  an empty string in a shell session.

  Also, `.attrs.json` cannot be used in shell sessions because
  it can only be accessed by `$NIX_ATTRS_JSON_FILE` and not by
  `$NIX_BUILD_TOP/.attrs.json`.

  I considered changing Nix to be compatible with what nixpkgs
  effectively does, but then we'd have to either move $NIX_BUILD_TOP for
  shell sessions to a temporary location (and thus breaking a lot of
  assumptions) or we'd reintroduce all the problems we solved back then
  by using these two env vars.

  This is partly because I didn't document these variables back
  then (mea culpa), so I decided to drop all mentions of
  `.attrs.{json,sh}` in the  manual and only refer to `$NIX_ATTRS_SH_FILE`
  and `$NIX_ATTRS_JSON_FILE`. The same applies to all our integration tests.
  Theoretically we could deprecated using `"$NIX_BUILD_TOP"/.attrs.sh` in
  the future now.

* `nix develop` and `nix print-dev-env` don't support this environment
  variable at all even though they're supposed to be part of the replacement
  for `nix-shell` - for the drv debugging part to be precise.

  This isn't a big deal for the vast majority of derivations, i.e.
  derivations relying on nixpkgs' `stdenv` wiring things together
  properly. This is because `nix develop` effectively "clones" the
  derivation and replaces the builder with a script that dumps all of
  the environment, shell variables, functions etc, so the state of
  structured attrs being "sourced" is transmitted into the dev shell and
  most of the time you don't need to worry about `.attrs.sh` not
  existing because the shell is correctly configured and the

      if [ -e .attrs.sh ]; then source .attrs.sh; fi

  is simply omitted.

  However, this will break when having a derivation that reads e.g. from
  `.attrs.json` like

      with import <nixpkgs> {};
      runCommand "foo" { __structuredAttrs = true; foo.bar = 23; } ''
        cat $NIX_ATTRS_JSON_FILE # doesn't work because it points to /build/.attrs.json
      ''

  To work around this I employed a similar approach as it exists for
  `nix-shell`: the `NIX_ATTRS_{JSON,SH}_FILE` vars are replaced with
  temporary locations.

  The contents of `.attrs.sh` and `.attrs.json` are now written into the
  JSON by `get-env.sh`, the builder that `nix develop` injects into the
  derivation it's debugging. So finally the exact file contents are
  present and exported by `nix develop`.

  I also made `.attrs.json` a JSON string in the JSON printed by
  `get-env.sh` on purpose because then it's not necessary to serialize
  the object structure again. `nix develop` only needs the JSON
  as string because it's only written into the temporary file.

  I'm not entirely sure if it makes sense to also use a temporary
  location for `nix print-dev-env` (rather than just skipping the
  rewrite in there), but this would probably break certain cases where
  it's relied upon `$NIX_ATTRS_SH_FILE` to exist (prime example are the
  `nix print-dev-env` test-cases I wrote in this patch using
  `tests/shell.nix`, these would fail because the env var exists, but it
  cannot read from it).

[1] NixOS#4770 (comment)
Ma27 added a commit to Ma27/nixpkgs that referenced this pull request Sep 28, 2023
Relying on `.attrs.sh` to exist in `$NIX_BUILD_TOP` is problematic
because that's not compatible with how `nix-shell(1)` behaves. It places
`.attrs.{json,sh}` into a temporary directory and makes them accessible via
`$NIX_ATTRS_{SH,JSON}_FILE` in the environment[1]. The sole reason that
`nix-shell(1)` still works with structured-attrs enabled derivations
is that the contents of `.attrs.sh` are sourced into the
shell before sourcing `$stdenv/setup` (if `$stdenv` exists) by `nix-shell`.

However, the assumption that two files called `.attrs.sh` and
`.attrs.json` exist in `$NIX_BUILD_TOP` is wrong in an interactive shell
session and thus an inconsistency between shell debug session and actual
builds which can lead to unexpected problems.

To be precise, we currently have the following problem: an expression
like

  with import ./. {};
  runCommand "foo" { __structuredAttrs = true; foo.bar = [ 1 2 3 ]; }
    ''
      echo "''${__structuredAttrs@Q}"
      touch $out
    ''

prints `1` in its build-log. However when building interactively in a
`nix-shell`, it doesn't.

Because of that, I'm considering to propose a full deprecation of
`$NIX_BUILD_TOP/.attrs.{json,sh}`. A first step is to only mention the
environment variables, but not the actual paths anymore in Nix's
manual[2]. The second step - this patch - is to fix nixpkgs' stdenv
accordingly.

Please note that we cannot check for `-e "$NIX_ATTRS_JSON_FILE"` because
certain outdated Nix minors (that are still in the range of supported
Nix versions in `nixpkgs`) have a bug where `NIX_ATTRS_JSON_FILE` points
to the wrong file while building[3].

Also, for compatibility with Nix 2.3 which doesn't provide these
environment variables at all we still need to check for the existence of
.attrs.json/.attrs.sh here. As soon as we bump nixpkgs' minver to 2.4,
this can be dropped.

Finally, dropped the check for ATTRS_SH_FILE because that was never
relevant. In nix#4770 the ATTRS_SH_FILE variable was introduced[4] and
in a review iteration prefixed with NIX_[5]. In other words, these
variables were never part of a release and you'd only have this problem
if you'd use a Nix from a git revision of my branch from back then. In
other words, that's dead code.

[1] NixOS/nix#4770 (comment)
[2] NixOS/nix#9032
[3] NixOS/nix#6736
[4] NixOS/nix@3944a12
[5] NixOS/nix@27ce722
roberth pushed a commit to Ma27/nix that referenced this pull request Oct 1, 2023
In NixOS#4770 I implemented proper `nix-shell(1)` support for derivations
using `__structuredAttrs = true;`. Back then we decided to introduce two
new environment variables, `NIX_ATTRS_SH_FILE` for `.attrs.sh` and
`NIX_ATTRS_JSON_FILE` for `.attrs.json`. This was to avoid having to
copy these files to `$NIX_BUILD_TOP` in a `nix-shell(1)` session which
effectively meant copying these files to the project dir without
cleaning up afterwords[1].

On last NixCon I resumed hacking on `__structuredAttrs = true;` by
default for `nixpkgs` with a few other folks and getting back to it,
I identified a few problems with the how it's used in `nixpkgs`:

* A lot of builders in `nixpkgs` don't care about the env vars and
  assume that `.attrs.sh` and `.attrs.json` are in `$NIX_BUILD_TOP`.
  The sole reason why this works is that `nix-shell(1)` sources
  the contents of `.attrs.sh` and then sources `$stdenv/setup` if it
  exists. This may not be pretty, but it mostly works. One notable
  difference when using nixpkgs' stdenv as of now is however that
  `$__structuredAttrs` is set to `1` on regular builds, but set to
  an empty string in a shell session.

  Also, `.attrs.json` cannot be used in shell sessions because
  it can only be accessed by `$NIX_ATTRS_JSON_FILE` and not by
  `$NIX_BUILD_TOP/.attrs.json`.

  I considered changing Nix to be compatible with what nixpkgs
  effectively does, but then we'd have to either move $NIX_BUILD_TOP for
  shell sessions to a temporary location (and thus breaking a lot of
  assumptions) or we'd reintroduce all the problems we solved back then
  by using these two env vars.

  This is partly because I didn't document these variables back
  then (mea culpa), so I decided to drop all mentions of
  `.attrs.{json,sh}` in the  manual and only refer to `$NIX_ATTRS_SH_FILE`
  and `$NIX_ATTRS_JSON_FILE`. The same applies to all our integration tests.
  Theoretically we could deprecated using `"$NIX_BUILD_TOP"/.attrs.sh` in
  the future now.

* `nix develop` and `nix print-dev-env` don't support this environment
  variable at all even though they're supposed to be part of the replacement
  for `nix-shell` - for the drv debugging part to be precise.

  This isn't a big deal for the vast majority of derivations, i.e.
  derivations relying on nixpkgs' `stdenv` wiring things together
  properly. This is because `nix develop` effectively "clones" the
  derivation and replaces the builder with a script that dumps all of
  the environment, shell variables, functions etc, so the state of
  structured attrs being "sourced" is transmitted into the dev shell and
  most of the time you don't need to worry about `.attrs.sh` not
  existing because the shell is correctly configured and the

      if [ -e .attrs.sh ]; then source .attrs.sh; fi

  is simply omitted.

  However, this will break when having a derivation that reads e.g. from
  `.attrs.json` like

      with import <nixpkgs> {};
      runCommand "foo" { __structuredAttrs = true; foo.bar = 23; } ''
        cat $NIX_ATTRS_JSON_FILE # doesn't work because it points to /build/.attrs.json
      ''

  To work around this I employed a similar approach as it exists for
  `nix-shell`: the `NIX_ATTRS_{JSON,SH}_FILE` vars are replaced with
  temporary locations.

  The contents of `.attrs.sh` and `.attrs.json` are now written into the
  JSON by `get-env.sh`, the builder that `nix develop` injects into the
  derivation it's debugging. So finally the exact file contents are
  present and exported by `nix develop`.

  I also made `.attrs.json` a JSON string in the JSON printed by
  `get-env.sh` on purpose because then it's not necessary to serialize
  the object structure again. `nix develop` only needs the JSON
  as string because it's only written into the temporary file.

  I'm not entirely sure if it makes sense to also use a temporary
  location for `nix print-dev-env` (rather than just skipping the
  rewrite in there), but this would probably break certain cases where
  it's relied upon `$NIX_ATTRS_SH_FILE` to exist (prime example are the
  `nix print-dev-env` test-cases I wrote in this patch using
  `tests/shell.nix`, these would fail because the env var exists, but it
  cannot read from it).

[1] NixOS#4770 (comment)
Ma27 added a commit to Ma27/nixpkgs that referenced this pull request Oct 4, 2023
Relying on `.attrs.sh` to exist in `$NIX_BUILD_TOP` is problematic
because that's not compatible with how `nix-shell(1)` behaves. It places
`.attrs.{json,sh}` into a temporary directory and makes them accessible via
`$NIX_ATTRS_{SH,JSON}_FILE` in the environment[1]. The sole reason that
`nix-shell(1)` still works with structured-attrs enabled derivations
is that the contents of `.attrs.sh` are sourced into the
shell before sourcing `$stdenv/setup` (if `$stdenv` exists) by `nix-shell`.

However, the assumption that two files called `.attrs.sh` and
`.attrs.json` exist in `$NIX_BUILD_TOP` is wrong in an interactive shell
session and thus an inconsistency between shell debug session and actual
builds which can lead to unexpected problems.

To be precise, we currently have the following problem: an expression
like

  with import ./. {};
  runCommand "foo" { __structuredAttrs = true; foo.bar = [ 1 2 3 ]; }
    ''
      echo "''${__structuredAttrs@Q}"
      touch $out
    ''

prints `1` in its build-log. However when building interactively in a
`nix-shell`, it doesn't.

Because of that, I'm considering to propose a full deprecation of
`$NIX_BUILD_TOP/.attrs.{json,sh}`. A first step is to only mention the
environment variables, but not the actual paths anymore in Nix's
manual[2]. The second step - this patch - is to fix nixpkgs' stdenv
accordingly.

Please note that we cannot check for `-e "$NIX_ATTRS_JSON_FILE"` because
certain outdated Nix minors (that are still in the range of supported
Nix versions in `nixpkgs`) have a bug where `NIX_ATTRS_JSON_FILE` points
to the wrong file while building[3].

Also, for compatibility with Nix 2.3 which doesn't provide these
environment variables at all we still need to check for the existence of
.attrs.json/.attrs.sh here. As soon as we bump nixpkgs' minver to 2.4,
this can be dropped.

Finally, dropped the check for ATTRS_SH_FILE because that was never
relevant. In nix#4770 the ATTRS_SH_FILE variable was introduced[4] and
in a review iteration prefixed with NIX_[5]. In other words, these
variables were never part of a release and you'd only have this problem
if you'd use a Nix from a git revision of my branch from back then. In
other words, that's dead code.

[1] NixOS/nix#4770 (comment)
[2] NixOS/nix#9032
[3] NixOS/nix#6736
[4] NixOS/nix@3944a12
[5] NixOS/nix@27ce722
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants