2020-03-30 20:14:17 +03:00
|
|
|
#include "eval.hh"
|
2023-02-03 21:53:40 +02:00
|
|
|
#include "installable-flake.hh"
|
2023-02-06 06:28:18 +02:00
|
|
|
#include "command-installable-value.hh"
|
2020-03-30 20:14:17 +03:00
|
|
|
#include "common-args.hh"
|
|
|
|
#include "shared.hh"
|
|
|
|
#include "store-api.hh"
|
2023-01-10 18:27:19 +02:00
|
|
|
#include "outputs-spec.hh"
|
2020-03-30 20:14:17 +03:00
|
|
|
#include "derivations.hh"
|
|
|
|
#include "progress-bar.hh"
|
2021-07-27 15:23:24 +03:00
|
|
|
#include "run.hh"
|
2020-03-30 20:14:17 +03:00
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
#include <iterator>
|
2021-08-19 23:42:13 +03:00
|
|
|
#include <memory>
|
2021-07-09 01:47:57 +03:00
|
|
|
#include <nlohmann/json.hpp>
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
#include <algorithm>
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
using namespace nix;
|
|
|
|
|
2020-10-26 15:24:25 +02:00
|
|
|
struct DevelopSettings : Config
|
|
|
|
{
|
|
|
|
Setting<std::string> bashPrompt{this, "", "bash-prompt",
|
|
|
|
"The bash prompt (`PS1`) in `nix develop` shells."};
|
|
|
|
|
2022-05-10 23:53:22 +03:00
|
|
|
Setting<std::string> bashPromptPrefix{this, "", "bash-prompt-prefix",
|
|
|
|
"Prefix prepended to the `PS1` environment variable in `nix develop` shells."};
|
|
|
|
|
2020-10-26 15:24:25 +02:00
|
|
|
Setting<std::string> bashPromptSuffix{this, "", "bash-prompt-suffix",
|
|
|
|
"Suffix appended to the `PS1` environment variable in `nix develop` shells."};
|
|
|
|
};
|
|
|
|
|
|
|
|
static DevelopSettings developSettings;
|
|
|
|
|
|
|
|
static GlobalConfig::Register rDevelopSettings(&developSettings);
|
|
|
|
|
2020-03-30 20:14:17 +03:00
|
|
|
struct BuildEnvironment
|
|
|
|
{
|
2021-07-09 01:47:57 +03:00
|
|
|
struct String
|
|
|
|
{
|
|
|
|
bool exported;
|
|
|
|
std::string value;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
bool operator == (const String & other) const
|
|
|
|
{
|
|
|
|
return exported == other.exported && value == other.value;
|
|
|
|
}
|
2021-07-09 01:47:57 +03:00
|
|
|
};
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
using Array = std::vector<std::string>;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
using Associative = std::map<std::string, std::string>;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
using Value = std::variant<String, Array, Associative>;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
std::map<std::string, Value> vars;
|
|
|
|
std::map<std::string, std::string> bashFunctions;
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
std::optional<std::pair<std::string, std::string>> structuredAttrs;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
static BuildEnvironment fromJSON(std::string_view in)
|
2021-07-09 01:47:57 +03:00
|
|
|
{
|
|
|
|
BuildEnvironment res;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
std::set<std::string> exported;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
auto json = nlohmann::json::parse(in);
|
2020-10-21 18:54:21 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
for (auto & [name, info] : json["variables"].items()) {
|
|
|
|
std::string type = info["type"];
|
|
|
|
if (type == "var" || type == "exported")
|
|
|
|
res.vars.insert({name, BuildEnvironment::String { .exported = type == "exported", .value = info["value"] }});
|
|
|
|
else if (type == "array")
|
|
|
|
res.vars.insert({name, (Array) info["value"]});
|
|
|
|
else if (type == "associative")
|
|
|
|
res.vars.insert({name, (Associative) info["value"]});
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
for (auto & [name, def] : json["bashFunctions"].items()) {
|
|
|
|
res.bashFunctions.insert({name, def});
|
2020-04-30 15:39:26 +03:00
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
if (json.contains("structuredAttrs")) {
|
|
|
|
res.structuredAttrs = {json["structuredAttrs"][".attrs.json"], json["structuredAttrs"][".attrs.sh"]};
|
|
|
|
}
|
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
return res;
|
|
|
|
}
|
2020-10-21 18:54:21 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
std::string toJSON() const
|
|
|
|
{
|
|
|
|
auto res = nlohmann::json::object();
|
|
|
|
|
|
|
|
auto vars2 = nlohmann::json::object();
|
|
|
|
for (auto & [name, value] : vars) {
|
|
|
|
auto info = nlohmann::json::object();
|
|
|
|
if (auto str = std::get_if<String>(&value)) {
|
|
|
|
info["type"] = str->exported ? "exported" : "var";
|
|
|
|
info["value"] = str->value;
|
|
|
|
}
|
|
|
|
else if (auto arr = std::get_if<Array>(&value)) {
|
|
|
|
info["type"] = "array";
|
|
|
|
info["value"] = *arr;
|
|
|
|
}
|
|
|
|
else if (auto arr = std::get_if<Associative>(&value)) {
|
|
|
|
info["type"] = "associative";
|
|
|
|
info["value"] = *arr;
|
|
|
|
}
|
|
|
|
vars2[name] = std::move(info);
|
|
|
|
}
|
|
|
|
res["variables"] = std::move(vars2);
|
2020-04-30 15:39:26 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
res["bashFunctions"] = bashFunctions;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
if (providesStructuredAttrs()) {
|
|
|
|
auto contents = nlohmann::json::object();
|
|
|
|
contents[".attrs.sh"] = getAttrsSH();
|
|
|
|
contents[".attrs.json"] = getAttrsJSON();
|
|
|
|
res["structuredAttrs"] = std::move(contents);
|
|
|
|
}
|
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
auto json = res.dump();
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
assert(BuildEnvironment::fromJSON(json) == *this);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
return json;
|
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
bool providesStructuredAttrs() const
|
|
|
|
{
|
|
|
|
return structuredAttrs.has_value();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getAttrsJSON() const
|
|
|
|
{
|
|
|
|
assert(providesStructuredAttrs());
|
|
|
|
return structuredAttrs->first;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getAttrsSH() const
|
|
|
|
{
|
|
|
|
assert(providesStructuredAttrs());
|
|
|
|
return structuredAttrs->second;
|
|
|
|
}
|
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
void toBash(std::ostream & out, const std::set<std::string> & ignoreVars) const
|
|
|
|
{
|
|
|
|
for (auto & [name, value] : vars) {
|
2021-07-09 02:02:47 +03:00
|
|
|
if (!ignoreVars.count(name)) {
|
2021-07-09 01:47:57 +03:00
|
|
|
if (auto str = std::get_if<String>(&value)) {
|
|
|
|
out << fmt("%s=%s\n", name, shellEscape(str->value));
|
|
|
|
if (str->exported)
|
|
|
|
out << fmt("export %s\n", name);
|
|
|
|
}
|
|
|
|
else if (auto arr = std::get_if<Array>(&value)) {
|
|
|
|
out << "declare -a " << name << "=(";
|
|
|
|
for (auto & s : *arr)
|
|
|
|
out << shellEscape(s) << " ";
|
|
|
|
out << ")\n";
|
|
|
|
}
|
|
|
|
else if (auto arr = std::get_if<Associative>(&value)) {
|
|
|
|
out << "declare -A " << name << "=(";
|
|
|
|
for (auto & [n, v] : *arr)
|
|
|
|
out << "[" << shellEscape(n) << "]=" << shellEscape(v) << " ";
|
|
|
|
out << ")\n";
|
|
|
|
}
|
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
for (auto & [name, def] : bashFunctions) {
|
|
|
|
out << name << " ()\n{\n" << def << "}\n";
|
2020-04-30 15:39:26 +03:00
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
2020-04-30 15:39:26 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
static std::string getString(const Value & value)
|
|
|
|
{
|
|
|
|
if (auto str = std::get_if<String>(&value))
|
|
|
|
return str->value;
|
|
|
|
else
|
|
|
|
throw Error("bash variable is not a string");
|
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
static Array getStrings(const Value & value)
|
|
|
|
{
|
|
|
|
if (auto str = std::get_if<String>(&value))
|
|
|
|
return tokenizeString<Array>(str->value);
|
|
|
|
else if (auto arr = std::get_if<Array>(&value)) {
|
|
|
|
return *arr;
|
2021-07-12 16:46:41 +03:00
|
|
|
} else if (auto assoc = std::get_if<Associative>(&value)) {
|
|
|
|
Array assocKeys;
|
|
|
|
std::for_each(assoc->begin(), assoc->end(), [&](auto & n) { assocKeys.push_back(n.first); });
|
|
|
|
return assocKeys;
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
2021-07-09 01:47:57 +03:00
|
|
|
else
|
|
|
|
throw Error("bash variable is not a string or array");
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
bool operator == (const BuildEnvironment & other) const
|
|
|
|
{
|
|
|
|
return vars == other.vars && bashFunctions == other.bashFunctions;
|
|
|
|
}
|
2022-12-23 17:28:26 +02:00
|
|
|
|
|
|
|
std::string getSystem() const
|
|
|
|
{
|
|
|
|
if (auto v = get(vars, "system"))
|
|
|
|
return getString(*v);
|
|
|
|
else
|
|
|
|
return settings.thisSystem;
|
|
|
|
}
|
2021-07-09 01:47:57 +03:00
|
|
|
};
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-04-30 14:05:29 +03:00
|
|
|
const static std::string getEnvSh =
|
|
|
|
#include "get-env.sh.gen.hh"
|
|
|
|
;
|
|
|
|
|
2020-03-30 20:14:17 +03:00
|
|
|
/* Given an existing derivation, return the shell environment as
|
|
|
|
initialised by stdenv's setup script. We do this by building a
|
|
|
|
modified derivation with the same dependencies and nearly the same
|
|
|
|
initial environment variables, that just writes the resulting
|
|
|
|
environment to a file and exits. */
|
2021-07-19 16:43:08 +03:00
|
|
|
static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore, const StorePath & drvPath)
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
2021-07-19 16:43:08 +03:00
|
|
|
auto drv = evalStore->derivationFromPath(drvPath);
|
2020-04-30 15:39:26 +03:00
|
|
|
|
2020-03-30 20:14:17 +03:00
|
|
|
auto builder = baseNameOf(drv.builder);
|
|
|
|
if (builder != "bash")
|
2020-06-04 11:57:40 +03:00
|
|
|
throw Error("'nix develop' only works on derivations that use 'bash' as their builder");
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2023-11-09 04:11:48 +02:00
|
|
|
auto getEnvShPath = ({
|
|
|
|
StringSource source { getEnvSh };
|
|
|
|
evalStore->addToStoreFromDump(
|
2024-01-19 06:57:26 +02:00
|
|
|
source, "get-env.sh", FileSerialisationMethod::Flat, TextIngestionMethod {}, HashAlgorithm::SHA256, {});
|
2023-11-09 04:11:48 +02:00
|
|
|
});
|
2020-04-30 14:05:29 +03:00
|
|
|
|
|
|
|
drv.args = {store->printStorePath(getEnvShPath)};
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
/* Remove derivation checks. */
|
|
|
|
drv.env.erase("allowedReferences");
|
|
|
|
drv.env.erase("allowedRequisites");
|
|
|
|
drv.env.erase("disallowedReferences");
|
|
|
|
drv.env.erase("disallowedRequisites");
|
2022-12-19 22:16:06 +02:00
|
|
|
drv.env.erase("name");
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
/* Rehash and write the derivation. FIXME: would be nice to use
|
|
|
|
'buildDerivation', but that's privileged. */
|
2020-08-09 23:32:35 +03:00
|
|
|
drv.name += "-env";
|
2022-12-19 22:16:06 +02:00
|
|
|
drv.env.emplace("name", drv.name);
|
2020-04-30 14:05:29 +03:00
|
|
|
drv.inputSrcs.insert(std::move(getEnvShPath));
|
2023-03-17 16:33:48 +02:00
|
|
|
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
2021-06-11 14:31:19 +03:00
|
|
|
for (auto & output : drv.outputs) {
|
2022-03-18 00:29:15 +02:00
|
|
|
output.second = DerivationOutput::Deferred {},
|
2021-06-21 16:52:01 +03:00
|
|
|
drv.env[output.first] = hashPlaceholder(output.first);
|
2021-06-11 14:31:19 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (auto & output : drv.outputs) {
|
2022-03-18 04:07:31 +02:00
|
|
|
output.second = DerivationOutput::Deferred { };
|
2021-06-11 14:31:19 +03:00
|
|
|
drv.env[output.first] = "";
|
|
|
|
}
|
2022-03-16 15:21:09 +02:00
|
|
|
auto hashesModulo = hashDerivationModulo(*evalStore, drv, true);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-06-11 14:31:19 +03:00
|
|
|
for (auto & output : drv.outputs) {
|
2022-03-16 15:21:09 +02:00
|
|
|
Hash h = hashesModulo.hashes.at(output.first);
|
2021-06-11 14:31:19 +03:00
|
|
|
auto outPath = store->makeOutputPath(output.first, h, drv.name);
|
2022-03-18 00:29:15 +02:00
|
|
|
output.second = DerivationOutput::InputAddressed {
|
|
|
|
.path = outPath,
|
|
|
|
};
|
2021-06-11 14:31:19 +03:00
|
|
|
drv.env[output.first] = store->printStorePath(outPath);
|
|
|
|
}
|
2020-08-28 19:16:03 +03:00
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-19 16:43:08 +03:00
|
|
|
auto shellDrvPath = writeDerivation(*evalStore, drv);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
/* Build the derivation. */
|
2023-01-11 23:32:30 +02:00
|
|
|
store->buildPaths(
|
|
|
|
{ DerivedPath::Built {
|
Make the Derived Path family of types inductive for dynamic derivations
We want to be able to write down `foo.drv^bar.drv^baz`:
`foo.drv^bar.drv` is the dynamic derivation (since it is itself a
derivation output, `bar.drv` from `foo.drv`).
To that end, we create `Single{Derivation,BuiltPath}` types, that are
very similar except instead of having multiple outputs (in a set or
map), they have a single one. This is for everything to the left of the
rightmost `^`.
`NixStringContextElem` has an analogous change, and now can reuse
`SingleDerivedPath` at the top level. In fact, if we ever get rid of
`DrvDeep`, `NixStringContextElem` could be replaced with
`SingleDerivedPath` entirely!
Important note: some JSON formats have changed.
We already can *produce* dynamic derivations, but we can't refer to them
directly. Today, we can merely express building or example at the top
imperatively over time by building `foo.drv^bar.drv`, and then with a
second nix invocation doing `<result-from-first>^baz`, but this is not
declarative. The ethos of Nix of being able to write down the full plan
everything you want to do, and then execute than plan with a single
command, and for that we need the new inductive form of these types.
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
2023-01-16 00:39:04 +02:00
|
|
|
.drvPath = makeConstantStorePathRef(shellDrvPath),
|
2023-01-11 23:32:30 +02:00
|
|
|
.outputs = OutputsSpec::All { },
|
|
|
|
}},
|
|
|
|
bmNormal, evalStore);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-19 16:43:08 +03:00
|
|
|
for (auto & [_0, optPath] : evalStore->queryPartialDerivationOutputMap(shellDrvPath)) {
|
2020-08-28 22:59:14 +03:00
|
|
|
assert(optPath);
|
|
|
|
auto & outPath = *optPath;
|
2020-08-28 19:16:03 +03:00
|
|
|
assert(store->isValidPath(outPath));
|
|
|
|
auto outPathS = store->toRealPath(outPath);
|
|
|
|
if (lstat(outPathS).st_size)
|
|
|
|
return outPath;
|
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-08-28 19:16:03 +03:00
|
|
|
throw Error("get-env.sh failed to produce an environment");
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2023-05-09 18:50:22 +03:00
|
|
|
struct Common : InstallableCommand, MixProfile
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
2021-07-09 01:47:57 +03:00
|
|
|
std::set<std::string> ignoreVars{
|
2020-03-30 20:14:17 +03:00
|
|
|
"BASHOPTS",
|
|
|
|
"HOME", // FIXME: don't ignore in pure mode?
|
|
|
|
"NIX_BUILD_TOP",
|
|
|
|
"NIX_ENFORCE_PURITY",
|
|
|
|
"NIX_LOG_FD",
|
2021-06-15 13:06:01 +03:00
|
|
|
"NIX_REMOTE",
|
2020-03-30 20:14:17 +03:00
|
|
|
"PPID",
|
|
|
|
"SHELLOPTS",
|
|
|
|
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
|
|
|
|
"TEMP",
|
|
|
|
"TEMPDIR",
|
|
|
|
"TERM",
|
|
|
|
"TMP",
|
|
|
|
"TMPDIR",
|
|
|
|
"TZ",
|
|
|
|
"UID",
|
|
|
|
};
|
|
|
|
|
2020-10-18 22:06:36 +03:00
|
|
|
std::vector<std::pair<std::string, std::string>> redirects;
|
|
|
|
|
|
|
|
Common()
|
|
|
|
{
|
|
|
|
addFlag({
|
|
|
|
.longName = "redirect",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Redirect a store path to a mutable location.",
|
2020-10-18 22:06:36 +03:00
|
|
|
.labels = {"installable", "outputs-dir"},
|
|
|
|
.handler = {[&](std::string installable, std::string outputsDir) {
|
|
|
|
redirects.push_back({installable, outputsDir});
|
|
|
|
}}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-08-28 19:16:03 +03:00
|
|
|
std::string makeRcScript(
|
2020-10-18 22:06:36 +03:00
|
|
|
ref<Store> store,
|
2020-08-28 19:16:03 +03:00
|
|
|
const BuildEnvironment & buildEnvironment,
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
const Path & tmpDir,
|
2020-08-28 19:16:03 +03:00
|
|
|
const Path & outputsDir = absPath(".") + "/outputs")
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
2022-06-22 12:24:20 +03:00
|
|
|
// A list of colon-separated environment variables that should be
|
|
|
|
// prepended to, rather than overwritten, in order to keep the shell usable.
|
|
|
|
// Please keep this list minimal in order to avoid impurities.
|
|
|
|
static const char * const savedVars[] = {
|
|
|
|
"PATH", // for commands
|
|
|
|
"XDG_DATA_DIRS", // for loadable completion
|
|
|
|
};
|
|
|
|
|
2020-08-28 19:16:03 +03:00
|
|
|
std::ostringstream out;
|
|
|
|
|
2020-04-30 15:46:51 +03:00
|
|
|
out << "unset shellHook\n";
|
|
|
|
|
2022-07-15 09:11:02 +03:00
|
|
|
for (auto & var : savedVars) {
|
|
|
|
out << fmt("%s=${%s:-}\n", var, var);
|
2022-06-22 12:24:20 +03:00
|
|
|
out << fmt("nix_saved_%s=\"$%s\"\n", var, var);
|
2022-07-15 09:11:02 +03:00
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-07-09 01:47:57 +03:00
|
|
|
buildEnvironment.toBash(out, ignoreVars);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2022-06-22 12:24:20 +03:00
|
|
|
for (auto & var : savedVars)
|
2023-03-12 13:40:47 +02:00
|
|
|
out << fmt("%s=\"$%s${nix_saved_%s:+:$nix_saved_%s}\"\n", var, var, var, var);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-01-29 05:55:18 +02:00
|
|
|
out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
|
2020-03-30 20:14:17 +03:00
|
|
|
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
|
|
|
|
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
|
|
|
|
|
2024-02-04 06:02:06 +02:00
|
|
|
out << "eval \"${shellHook:-}\"\n";
|
2020-08-28 19:16:03 +03:00
|
|
|
|
2020-10-18 22:06:36 +03:00
|
|
|
auto script = out.str();
|
|
|
|
|
2020-08-28 19:16:03 +03:00
|
|
|
/* Substitute occurrences of output paths. */
|
2021-07-09 01:47:57 +03:00
|
|
|
auto outputs = buildEnvironment.vars.find("outputs");
|
|
|
|
assert(outputs != buildEnvironment.vars.end());
|
2020-08-28 19:16:03 +03:00
|
|
|
|
|
|
|
// FIXME: properly unquote 'outputs'.
|
|
|
|
StringMap rewrites;
|
2021-07-09 01:47:57 +03:00
|
|
|
for (auto & outputName : BuildEnvironment::getStrings(outputs->second)) {
|
|
|
|
auto from = buildEnvironment.vars.find(outputName);
|
|
|
|
assert(from != buildEnvironment.vars.end());
|
2020-08-28 19:16:03 +03:00
|
|
|
// FIXME: unquote
|
2021-07-09 01:47:57 +03:00
|
|
|
rewrites.insert({BuildEnvironment::getString(from->second), outputsDir + "/" + outputName});
|
2020-08-28 19:16:03 +03:00
|
|
|
}
|
|
|
|
|
2020-10-18 22:06:36 +03:00
|
|
|
/* Substitute redirects. */
|
2020-10-19 13:03:15 +03:00
|
|
|
for (auto & [installable_, dir_] : redirects) {
|
|
|
|
auto dir = absPath(dir_);
|
|
|
|
auto installable = parseInstallable(store, installable_);
|
2023-12-21 20:14:54 +02:00
|
|
|
auto builtPaths = Installable::toStorePathSet(
|
2021-07-16 17:04:47 +03:00
|
|
|
getEvalStore(), store, Realise::Nothing, OperateOn::Output, {installable});
|
2021-05-17 09:45:08 +03:00
|
|
|
for (auto & path: builtPaths) {
|
2020-10-18 22:06:36 +03:00
|
|
|
auto from = store->printStorePath(path);
|
|
|
|
if (script.find(from) == std::string::npos)
|
|
|
|
warn("'%s' (path '%s') is not used by this build environment", installable->what(), from);
|
|
|
|
else {
|
|
|
|
printInfo("redirecting '%s' to '%s'", from, dir);
|
|
|
|
rewrites.insert({from, dir});
|
|
|
|
}
|
2021-05-17 09:45:08 +03:00
|
|
|
}
|
2020-10-18 22:06:36 +03:00
|
|
|
}
|
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
if (buildEnvironment.providesStructuredAttrs()) {
|
|
|
|
fixupStructuredAttrs(
|
|
|
|
"sh",
|
|
|
|
"NIX_ATTRS_SH_FILE",
|
|
|
|
buildEnvironment.getAttrsSH(),
|
|
|
|
rewrites,
|
|
|
|
buildEnvironment,
|
|
|
|
tmpDir
|
|
|
|
);
|
|
|
|
fixupStructuredAttrs(
|
|
|
|
"json",
|
|
|
|
"NIX_ATTRS_JSON_FILE",
|
|
|
|
buildEnvironment.getAttrsJSON(),
|
|
|
|
rewrites,
|
|
|
|
buildEnvironment,
|
|
|
|
tmpDir
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-18 22:06:36 +03:00
|
|
|
return rewriteStrings(script, rewrites);
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
/**
|
|
|
|
* Replace the value of NIX_ATTRS_*_FILE (`/build/.attrs.*`) with a tmp file
|
|
|
|
* that's accessible from the interactive shell session.
|
|
|
|
*/
|
|
|
|
void fixupStructuredAttrs(
|
|
|
|
const std::string & ext,
|
|
|
|
const std::string & envVar,
|
|
|
|
const std::string & content,
|
|
|
|
StringMap & rewrites,
|
|
|
|
const BuildEnvironment & buildEnvironment,
|
|
|
|
const Path & tmpDir)
|
|
|
|
{
|
|
|
|
auto targetFilePath = tmpDir + "/.attrs." + ext;
|
|
|
|
writeFile(targetFilePath, content);
|
|
|
|
|
|
|
|
auto fileInBuilderEnv = buildEnvironment.vars.find(envVar);
|
|
|
|
assert(fileInBuilderEnv != buildEnvironment.vars.end());
|
|
|
|
rewrites.insert({BuildEnvironment::getString(fileInBuilderEnv->second), targetFilePath});
|
|
|
|
}
|
|
|
|
|
2019-05-02 22:10:13 +03:00
|
|
|
Strings getDefaultFlakeAttrPaths() override
|
|
|
|
{
|
2022-02-11 19:11:08 +02:00
|
|
|
Strings paths{
|
|
|
|
"devShells." + settings.thisSystem.get() + ".default",
|
|
|
|
"devShell." + settings.thisSystem.get(),
|
|
|
|
};
|
|
|
|
for (auto & p : SourceExprCommand::getDefaultFlakeAttrPaths())
|
|
|
|
paths.push_back(p);
|
|
|
|
return paths;
|
2019-05-02 22:10:13 +03:00
|
|
|
}
|
2022-02-11 19:11:08 +02:00
|
|
|
|
2021-07-13 12:44:19 +03:00
|
|
|
Strings getDefaultFlakeAttrPathPrefixes() override
|
|
|
|
{
|
|
|
|
auto res = SourceExprCommand::getDefaultFlakeAttrPathPrefixes();
|
2021-08-21 02:24:03 +03:00
|
|
|
res.emplace_front("devShells." + settings.thisSystem.get() + ".");
|
2021-07-13 12:44:19 +03:00
|
|
|
return res;
|
|
|
|
}
|
2019-07-12 17:16:27 +03:00
|
|
|
|
2023-05-09 18:50:22 +03:00
|
|
|
StorePath getShellOutPath(ref<Store> store, ref<Installable> installable)
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
|
|
|
auto path = installable->getStorePath();
|
|
|
|
if (path && hasSuffix(path->to_string(), "-env"))
|
2020-06-16 23:20:18 +03:00
|
|
|
return *path;
|
2020-03-30 20:14:17 +03:00
|
|
|
else {
|
2022-03-02 14:54:08 +02:00
|
|
|
auto drvs = Installable::toDerivations(store, {installable});
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
if (drvs.size() != 1)
|
|
|
|
throw Error("'%s' needs to evaluate to a single derivation, but it evaluated to %d derivations",
|
|
|
|
installable->what(), drvs.size());
|
|
|
|
|
|
|
|
auto & drvPath = *drvs.begin();
|
|
|
|
|
2021-07-19 16:43:08 +03:00
|
|
|
return getDerivationEnvironment(store, getEvalStore(), drvPath);
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Make command infra less stateful and more regular
Already, we had classes like `BuiltPathsCommand` and `StorePathsCommand`
which provided alternative `run` virtual functions providing the
implementation with more arguments. This was a very nice and easy way to
make writing command; just fill in the virtual functions and it is
fairly clear what to do.
However, exception to this pattern were `Installable{,s}Command`. These
two classes instead just had a field where the installables would be
stored, and various side-effecting `prepare` and `load` machinery too
fill them in. Command would wish out those fields.
This isn't so clear to use.
What this commit does is make those command classes like the others,
with richer `run` functions.
Not only does this restore the pattern making commands easier to write,
it has a number of other benefits:
- `prepare` and `load` are gone entirely! One command just hands just
hands off to the next.
- `useDefaultInstallables` because `defaultInstallables`. This takes
over `prepare` for the one case that needs it, and provides enough
flexiblity to handle `nix repl`'s idiosyncratic migration.
- We can use `ref` instead of `std::shared_ptr`. The former must be
initialized (so it is like Rust's `Box` rather than `Option<Box>`,
This expresses the invariant that the installable are in fact
initialized much better.
This is possible because since we just have local variables not
fields, we can stop worrying about the not-yet-initialized case.
- Fewer lines of code! (Finally I have a large refactor that makes the
number go down not up...)
- `nix repl` is now implemented in a clearer way.
The last item deserves further mention. `nix repl` is not like the other
installable commands because instead working from once-loaded
installables, it needs to be able to load them again and again.
To properly support this, we make a new superclass
`RawInstallablesCommand`. This class has the argument parsing and
completion logic, but does *not* hand off parsed installables but
instead just the raw string arguments.
This is exactly what `nix repl` needs, and allows us to instead of
having the logic awkwardly split between `prepare`,
`useDefaultInstallables,` and `load`, have everything right next to each
other. I think this will enable future simplifications of that argument
defaulting logic, but I am saving those for a future PR --- best to keep
code motion and more complicated boolean expression rewriting separate
steps.
The "diagnostic ignored `-Woverloaded-virtual`" pragma helps because C++
doesn't like our many `run` methods. In our case, we don't mind the
shadowing it all --- it is *intentional* that the derived class only
provides a `run` method, and doesn't call any of the overridden `run`
methods.
Helps with https://github.com/NixOS/rfcs/pull/134
2023-02-04 19:03:47 +02:00
|
|
|
std::pair<BuildEnvironment, std::string>
|
2023-05-09 18:50:22 +03:00
|
|
|
getBuildEnvironment(ref<Store> store, ref<Installable> installable)
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
Make command infra less stateful and more regular
Already, we had classes like `BuiltPathsCommand` and `StorePathsCommand`
which provided alternative `run` virtual functions providing the
implementation with more arguments. This was a very nice and easy way to
make writing command; just fill in the virtual functions and it is
fairly clear what to do.
However, exception to this pattern were `Installable{,s}Command`. These
two classes instead just had a field where the installables would be
stored, and various side-effecting `prepare` and `load` machinery too
fill them in. Command would wish out those fields.
This isn't so clear to use.
What this commit does is make those command classes like the others,
with richer `run` functions.
Not only does this restore the pattern making commands easier to write,
it has a number of other benefits:
- `prepare` and `load` are gone entirely! One command just hands just
hands off to the next.
- `useDefaultInstallables` because `defaultInstallables`. This takes
over `prepare` for the one case that needs it, and provides enough
flexiblity to handle `nix repl`'s idiosyncratic migration.
- We can use `ref` instead of `std::shared_ptr`. The former must be
initialized (so it is like Rust's `Box` rather than `Option<Box>`,
This expresses the invariant that the installable are in fact
initialized much better.
This is possible because since we just have local variables not
fields, we can stop worrying about the not-yet-initialized case.
- Fewer lines of code! (Finally I have a large refactor that makes the
number go down not up...)
- `nix repl` is now implemented in a clearer way.
The last item deserves further mention. `nix repl` is not like the other
installable commands because instead working from once-loaded
installables, it needs to be able to load them again and again.
To properly support this, we make a new superclass
`RawInstallablesCommand`. This class has the argument parsing and
completion logic, but does *not* hand off parsed installables but
instead just the raw string arguments.
This is exactly what `nix repl` needs, and allows us to instead of
having the logic awkwardly split between `prepare`,
`useDefaultInstallables,` and `load`, have everything right next to each
other. I think this will enable future simplifications of that argument
defaulting logic, but I am saving those for a future PR --- best to keep
code motion and more complicated boolean expression rewriting separate
steps.
The "diagnostic ignored `-Woverloaded-virtual`" pragma helps because C++
doesn't like our many `run` methods. In our case, we don't mind the
shadowing it all --- it is *intentional* that the derived class only
provides a `run` method, and doesn't call any of the overridden `run`
methods.
Helps with https://github.com/NixOS/rfcs/pull/134
2023-02-04 19:03:47 +02:00
|
|
|
auto shellOutPath = getShellOutPath(store, installable);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-04-27 20:12:54 +03:00
|
|
|
auto strPath = store->printStorePath(shellOutPath);
|
|
|
|
|
2020-03-30 20:14:17 +03:00
|
|
|
updateProfile(shellOutPath);
|
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
debug("reading environment file '%s'", strPath);
|
|
|
|
|
2021-07-19 16:43:08 +03:00
|
|
|
return {BuildEnvironment::fromJSON(readFile(store->toRealPath(shellOutPath))), strPath};
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-06-04 11:57:40 +03:00
|
|
|
struct CmdDevelop : Common, MixEnvironment
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
|
|
|
std::vector<std::string> command;
|
2020-08-28 20:24:29 +03:00
|
|
|
std::optional<std::string> phase;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-06-04 11:57:40 +03:00
|
|
|
CmdDevelop()
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
2020-05-04 23:40:19 +03:00
|
|
|
addFlag({
|
|
|
|
.longName = "command",
|
|
|
|
.shortName = 'c',
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Instead of starting an interactive shell, start the specified command and arguments.",
|
2020-05-04 23:40:19 +03:00
|
|
|
.labels = {"command", "args"},
|
|
|
|
.handler = {[&](std::vector<std::string> ss) {
|
2020-03-30 20:14:17 +03:00
|
|
|
if (ss.empty()) throw UsageError("--command requires at least one argument");
|
|
|
|
command = ss;
|
2020-05-04 23:40:19 +03:00
|
|
|
}}
|
|
|
|
});
|
2020-08-28 20:24:29 +03:00
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "phase",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "The stdenv phase to run (e.g. `build` or `configure`).",
|
2020-08-28 20:24:29 +03:00
|
|
|
.labels = {"phase-name"},
|
|
|
|
.handler = {&phase},
|
|
|
|
});
|
|
|
|
|
2021-10-09 01:19:50 +03:00
|
|
|
addFlag({
|
|
|
|
.longName = "unpack",
|
|
|
|
.description = "Run the `unpack` phase.",
|
|
|
|
.handler = {&phase, {"unpack"}},
|
|
|
|
});
|
|
|
|
|
2020-08-28 20:24:29 +03:00
|
|
|
addFlag({
|
|
|
|
.longName = "configure",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Run the `configure` phase.",
|
2020-08-28 20:24:29 +03:00
|
|
|
.handler = {&phase, {"configure"}},
|
|
|
|
});
|
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "build",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Run the `build` phase.",
|
2020-08-28 20:24:29 +03:00
|
|
|
.handler = {&phase, {"build"}},
|
|
|
|
});
|
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "check",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Run the `check` phase.",
|
2020-08-28 20:24:29 +03:00
|
|
|
.handler = {&phase, {"check"}},
|
|
|
|
});
|
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "install",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Run the `install` phase.",
|
2020-08-28 20:24:29 +03:00
|
|
|
.handler = {&phase, {"install"}},
|
|
|
|
});
|
|
|
|
|
|
|
|
addFlag({
|
|
|
|
.longName = "installcheck",
|
2021-01-13 15:18:04 +02:00
|
|
|
.description = "Run the `installcheck` phase.",
|
2020-08-28 20:24:29 +03:00
|
|
|
.handler = {&phase, {"installCheck"}},
|
|
|
|
});
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "run a bash shell that provides the build environment of a derivation";
|
|
|
|
}
|
|
|
|
|
2020-12-08 22:52:22 +02:00
|
|
|
std::string doc() override
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
2020-12-08 22:52:22 +02:00
|
|
|
return
|
|
|
|
#include "develop.md"
|
|
|
|
;
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2023-05-09 18:50:22 +03:00
|
|
|
void run(ref<Store> store, ref<Installable> installable) override
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
Make command infra less stateful and more regular
Already, we had classes like `BuiltPathsCommand` and `StorePathsCommand`
which provided alternative `run` virtual functions providing the
implementation with more arguments. This was a very nice and easy way to
make writing command; just fill in the virtual functions and it is
fairly clear what to do.
However, exception to this pattern were `Installable{,s}Command`. These
two classes instead just had a field where the installables would be
stored, and various side-effecting `prepare` and `load` machinery too
fill them in. Command would wish out those fields.
This isn't so clear to use.
What this commit does is make those command classes like the others,
with richer `run` functions.
Not only does this restore the pattern making commands easier to write,
it has a number of other benefits:
- `prepare` and `load` are gone entirely! One command just hands just
hands off to the next.
- `useDefaultInstallables` because `defaultInstallables`. This takes
over `prepare` for the one case that needs it, and provides enough
flexiblity to handle `nix repl`'s idiosyncratic migration.
- We can use `ref` instead of `std::shared_ptr`. The former must be
initialized (so it is like Rust's `Box` rather than `Option<Box>`,
This expresses the invariant that the installable are in fact
initialized much better.
This is possible because since we just have local variables not
fields, we can stop worrying about the not-yet-initialized case.
- Fewer lines of code! (Finally I have a large refactor that makes the
number go down not up...)
- `nix repl` is now implemented in a clearer way.
The last item deserves further mention. `nix repl` is not like the other
installable commands because instead working from once-loaded
installables, it needs to be able to load them again and again.
To properly support this, we make a new superclass
`RawInstallablesCommand`. This class has the argument parsing and
completion logic, but does *not* hand off parsed installables but
instead just the raw string arguments.
This is exactly what `nix repl` needs, and allows us to instead of
having the logic awkwardly split between `prepare`,
`useDefaultInstallables,` and `load`, have everything right next to each
other. I think this will enable future simplifications of that argument
defaulting logic, but I am saving those for a future PR --- best to keep
code motion and more complicated boolean expression rewriting separate
steps.
The "diagnostic ignored `-Woverloaded-virtual`" pragma helps because C++
doesn't like our many `run` methods. In our case, we don't mind the
shadowing it all --- it is *intentional* that the derived class only
provides a `run` method, and doesn't call any of the overridden `run`
methods.
Helps with https://github.com/NixOS/rfcs/pull/134
2023-02-04 19:03:47 +02:00
|
|
|
auto [buildEnvironment, gcroot] = getBuildEnvironment(store, installable);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
auto [rcFileFd, rcFilePath] = createTempFile("nix-shell");
|
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
AutoDelete tmpDir(createTempDir("", "nix-develop"), true);
|
|
|
|
|
|
|
|
auto script = makeRcScript(store, buildEnvironment, (Path) tmpDir);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-08-28 20:24:29 +03:00
|
|
|
if (verbosity >= lvlDebug)
|
|
|
|
script += "set -x\n";
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-05-17 23:07:00 +03:00
|
|
|
script += fmt("command rm -f '%s'\n", rcFilePath);
|
2020-08-28 20:24:29 +03:00
|
|
|
|
|
|
|
if (phase) {
|
|
|
|
if (!command.empty())
|
|
|
|
throw UsageError("you cannot use both '--command' and '--phase'");
|
|
|
|
// FIXME: foundMakefile is set by buildPhase, need to get
|
|
|
|
// rid of that.
|
|
|
|
script += fmt("foundMakefile=1\n");
|
|
|
|
script += fmt("runHook %1%Phase\n", *phase);
|
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-08-28 20:24:29 +03:00
|
|
|
else if (!command.empty()) {
|
2020-03-30 20:14:17 +03:00
|
|
|
std::vector<std::string> args;
|
|
|
|
for (auto s : command)
|
|
|
|
args.push_back(shellEscape(s));
|
2020-08-28 19:16:03 +03:00
|
|
|
script += fmt("exec %s\n", concatStringsSep(" ", args));
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2020-10-09 23:02:00 +03:00
|
|
|
else {
|
2021-05-17 23:07:00 +03:00
|
|
|
script = "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n" + script;
|
2020-10-26 15:24:25 +02:00
|
|
|
if (developSettings.bashPrompt != "")
|
2022-01-21 18:55:51 +02:00
|
|
|
script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n",
|
|
|
|
shellEscape(developSettings.bashPrompt.get()));
|
2022-05-10 23:53:22 +03:00
|
|
|
if (developSettings.bashPromptPrefix != "")
|
|
|
|
script += fmt("[ -n \"$PS1\" ] && PS1=%s\"$PS1\";\n",
|
|
|
|
shellEscape(developSettings.bashPromptPrefix.get()));
|
2020-10-26 15:24:25 +02:00
|
|
|
if (developSettings.bashPromptSuffix != "")
|
2022-01-21 18:55:51 +02:00
|
|
|
script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n",
|
|
|
|
shellEscape(developSettings.bashPromptSuffix.get()));
|
2020-10-09 23:02:00 +03:00
|
|
|
}
|
|
|
|
|
2020-08-28 19:16:03 +03:00
|
|
|
writeFull(rcFileFd.get(), script);
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
setEnviron();
|
2020-04-27 22:18:26 +03:00
|
|
|
// prevent garbage collection until shell exits
|
2024-04-04 19:48:54 +03:00
|
|
|
setEnv("NIX_GCROOT", gcroot.c_str());
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2020-07-06 18:08:54 +03:00
|
|
|
Path shell = "bash";
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto state = getEvalState();
|
|
|
|
|
2021-06-29 11:13:42 +03:00
|
|
|
auto nixpkgsLockFlags = lockFlags;
|
|
|
|
nixpkgsLockFlags.inputOverrides = {};
|
|
|
|
nixpkgsLockFlags.inputUpdates = {};
|
|
|
|
|
2023-05-09 18:50:22 +03:00
|
|
|
auto nixpkgs = defaultNixpkgsFlakeRef();
|
|
|
|
if (auto * i = dynamic_cast<const InstallableFlake *>(&*installable))
|
|
|
|
nixpkgs = i->nixpkgsFlakeRef();
|
|
|
|
|
Make command infra less stateful and more regular
Already, we had classes like `BuiltPathsCommand` and `StorePathsCommand`
which provided alternative `run` virtual functions providing the
implementation with more arguments. This was a very nice and easy way to
make writing command; just fill in the virtual functions and it is
fairly clear what to do.
However, exception to this pattern were `Installable{,s}Command`. These
two classes instead just had a field where the installables would be
stored, and various side-effecting `prepare` and `load` machinery too
fill them in. Command would wish out those fields.
This isn't so clear to use.
What this commit does is make those command classes like the others,
with richer `run` functions.
Not only does this restore the pattern making commands easier to write,
it has a number of other benefits:
- `prepare` and `load` are gone entirely! One command just hands just
hands off to the next.
- `useDefaultInstallables` because `defaultInstallables`. This takes
over `prepare` for the one case that needs it, and provides enough
flexiblity to handle `nix repl`'s idiosyncratic migration.
- We can use `ref` instead of `std::shared_ptr`. The former must be
initialized (so it is like Rust's `Box` rather than `Option<Box>`,
This expresses the invariant that the installable are in fact
initialized much better.
This is possible because since we just have local variables not
fields, we can stop worrying about the not-yet-initialized case.
- Fewer lines of code! (Finally I have a large refactor that makes the
number go down not up...)
- `nix repl` is now implemented in a clearer way.
The last item deserves further mention. `nix repl` is not like the other
installable commands because instead working from once-loaded
installables, it needs to be able to load them again and again.
To properly support this, we make a new superclass
`RawInstallablesCommand`. This class has the argument parsing and
completion logic, but does *not* hand off parsed installables but
instead just the raw string arguments.
This is exactly what `nix repl` needs, and allows us to instead of
having the logic awkwardly split between `prepare`,
`useDefaultInstallables,` and `load`, have everything right next to each
other. I think this will enable future simplifications of that argument
defaulting logic, but I am saving those for a future PR --- best to keep
code motion and more complicated boolean expression rewriting separate
steps.
The "diagnostic ignored `-Woverloaded-virtual`" pragma helps because C++
doesn't like our many `run` methods. In our case, we don't mind the
shadowing it all --- it is *intentional* that the derived class only
provides a `run` method, and doesn't call any of the overridden `run`
methods.
Helps with https://github.com/NixOS/rfcs/pull/134
2023-02-04 19:03:47 +02:00
|
|
|
auto bashInstallable = make_ref<InstallableFlake>(
|
2021-02-17 18:32:10 +02:00
|
|
|
this,
|
2020-07-06 18:08:54 +03:00
|
|
|
state,
|
2023-05-09 18:50:22 +03:00
|
|
|
std::move(nixpkgs),
|
2022-02-14 21:39:44 +02:00
|
|
|
"bashInteractive",
|
2023-08-16 19:29:23 +03:00
|
|
|
ExtendedOutputsSpec::Default(),
|
2022-02-14 21:39:44 +02:00
|
|
|
Strings{},
|
2020-07-06 18:08:54 +03:00
|
|
|
Strings{"legacyPackages." + settings.thisSystem.get() + "."},
|
2021-06-29 11:13:42 +03:00
|
|
|
nixpkgsLockFlags);
|
2020-07-06 18:08:54 +03:00
|
|
|
|
2022-05-10 17:42:35 +03:00
|
|
|
bool found = false;
|
|
|
|
|
2023-12-21 20:14:54 +02:00
|
|
|
for (auto & path : Installable::toStorePathSet(getEvalStore(), store, Realise::Outputs, OperateOn::Output, {bashInstallable})) {
|
2022-05-10 17:42:35 +03:00
|
|
|
auto s = store->printStorePath(path) + "/bin/bash";
|
|
|
|
if (pathExists(s)) {
|
|
|
|
shell = s;
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found)
|
|
|
|
throw Error("package 'nixpkgs#bashInteractive' does not provide a 'bin/bash'");
|
|
|
|
|
2020-07-06 18:08:54 +03:00
|
|
|
} catch (Error &) {
|
|
|
|
ignoreException();
|
|
|
|
}
|
|
|
|
|
2023-03-13 22:14:19 +02:00
|
|
|
// Override SHELL with the one chosen for this environment.
|
|
|
|
// This is to make sure the system shell doesn't leak into the build environment.
|
2024-04-04 19:48:54 +03:00
|
|
|
setEnv("SHELL", shell.c_str());
|
2023-03-13 22:14:19 +02:00
|
|
|
|
2020-11-07 16:00:22 +02:00
|
|
|
// If running a phase or single command, don't want an interactive shell running after
|
2020-10-20 19:48:07 +03:00
|
|
|
// Ctrl-C, so don't pass --rcfile
|
2020-11-07 16:00:22 +02:00
|
|
|
auto args = phase || !command.empty() ? Strings{std::string(baseNameOf(shell)), rcFilePath}
|
2020-10-20 19:48:07 +03:00
|
|
|
: Strings{std::string(baseNameOf(shell)), "--rcfile", rcFilePath};
|
2020-03-30 20:14:17 +03:00
|
|
|
|
2021-08-19 23:42:13 +03:00
|
|
|
// Need to chdir since phases assume in flake directory
|
|
|
|
if (phase) {
|
|
|
|
// chdir if installable is a flake of type git+file or path
|
Make command infra less stateful and more regular
Already, we had classes like `BuiltPathsCommand` and `StorePathsCommand`
which provided alternative `run` virtual functions providing the
implementation with more arguments. This was a very nice and easy way to
make writing command; just fill in the virtual functions and it is
fairly clear what to do.
However, exception to this pattern were `Installable{,s}Command`. These
two classes instead just had a field where the installables would be
stored, and various side-effecting `prepare` and `load` machinery too
fill them in. Command would wish out those fields.
This isn't so clear to use.
What this commit does is make those command classes like the others,
with richer `run` functions.
Not only does this restore the pattern making commands easier to write,
it has a number of other benefits:
- `prepare` and `load` are gone entirely! One command just hands just
hands off to the next.
- `useDefaultInstallables` because `defaultInstallables`. This takes
over `prepare` for the one case that needs it, and provides enough
flexiblity to handle `nix repl`'s idiosyncratic migration.
- We can use `ref` instead of `std::shared_ptr`. The former must be
initialized (so it is like Rust's `Box` rather than `Option<Box>`,
This expresses the invariant that the installable are in fact
initialized much better.
This is possible because since we just have local variables not
fields, we can stop worrying about the not-yet-initialized case.
- Fewer lines of code! (Finally I have a large refactor that makes the
number go down not up...)
- `nix repl` is now implemented in a clearer way.
The last item deserves further mention. `nix repl` is not like the other
installable commands because instead working from once-loaded
installables, it needs to be able to load them again and again.
To properly support this, we make a new superclass
`RawInstallablesCommand`. This class has the argument parsing and
completion logic, but does *not* hand off parsed installables but
instead just the raw string arguments.
This is exactly what `nix repl` needs, and allows us to instead of
having the logic awkwardly split between `prepare`,
`useDefaultInstallables,` and `load`, have everything right next to each
other. I think this will enable future simplifications of that argument
defaulting logic, but I am saving those for a future PR --- best to keep
code motion and more complicated boolean expression rewriting separate
steps.
The "diagnostic ignored `-Woverloaded-virtual`" pragma helps because C++
doesn't like our many `run` methods. In our case, we don't mind the
shadowing it all --- it is *intentional* that the derived class only
provides a `run` method, and doesn't call any of the overridden `run`
methods.
Helps with https://github.com/NixOS/rfcs/pull/134
2023-02-04 19:03:47 +02:00
|
|
|
auto installableFlake = installable.dynamic_pointer_cast<InstallableFlake>();
|
2021-08-19 23:42:13 +03:00
|
|
|
if (installableFlake) {
|
|
|
|
auto sourcePath = installableFlake->getLockedFlake()->flake.resolvedRef.input.getSourcePath();
|
|
|
|
if (sourcePath) {
|
|
|
|
if (chdir(sourcePath->c_str()) == -1) {
|
|
|
|
throw SysError("chdir to '%s' failed", *sourcePath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-01 16:35:21 +02:00
|
|
|
runProgramInStore(store, UseSearchPath::Use, shell, args, buildEnvironment.getSystem());
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-07-09 13:10:48 +03:00
|
|
|
struct CmdPrintDevEnv : Common, MixJSON
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "print shell code that can be sourced by bash to reproduce the build environment of a derivation";
|
|
|
|
}
|
|
|
|
|
2020-12-08 22:52:22 +02:00
|
|
|
std::string doc() override
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
2020-12-08 22:52:22 +02:00
|
|
|
return
|
|
|
|
#include "print-dev-env.md"
|
|
|
|
;
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
|
2020-05-05 16:18:23 +03:00
|
|
|
Category category() override { return catUtility; }
|
|
|
|
|
2023-05-09 18:50:22 +03:00
|
|
|
void run(ref<Store> store, ref<Installable> installable) override
|
2020-03-30 20:14:17 +03:00
|
|
|
{
|
Make command infra less stateful and more regular
Already, we had classes like `BuiltPathsCommand` and `StorePathsCommand`
which provided alternative `run` virtual functions providing the
implementation with more arguments. This was a very nice and easy way to
make writing command; just fill in the virtual functions and it is
fairly clear what to do.
However, exception to this pattern were `Installable{,s}Command`. These
two classes instead just had a field where the installables would be
stored, and various side-effecting `prepare` and `load` machinery too
fill them in. Command would wish out those fields.
This isn't so clear to use.
What this commit does is make those command classes like the others,
with richer `run` functions.
Not only does this restore the pattern making commands easier to write,
it has a number of other benefits:
- `prepare` and `load` are gone entirely! One command just hands just
hands off to the next.
- `useDefaultInstallables` because `defaultInstallables`. This takes
over `prepare` for the one case that needs it, and provides enough
flexiblity to handle `nix repl`'s idiosyncratic migration.
- We can use `ref` instead of `std::shared_ptr`. The former must be
initialized (so it is like Rust's `Box` rather than `Option<Box>`,
This expresses the invariant that the installable are in fact
initialized much better.
This is possible because since we just have local variables not
fields, we can stop worrying about the not-yet-initialized case.
- Fewer lines of code! (Finally I have a large refactor that makes the
number go down not up...)
- `nix repl` is now implemented in a clearer way.
The last item deserves further mention. `nix repl` is not like the other
installable commands because instead working from once-loaded
installables, it needs to be able to load them again and again.
To properly support this, we make a new superclass
`RawInstallablesCommand`. This class has the argument parsing and
completion logic, but does *not* hand off parsed installables but
instead just the raw string arguments.
This is exactly what `nix repl` needs, and allows us to instead of
having the logic awkwardly split between `prepare`,
`useDefaultInstallables,` and `load`, have everything right next to each
other. I think this will enable future simplifications of that argument
defaulting logic, but I am saving those for a future PR --- best to keep
code motion and more complicated boolean expression rewriting separate
steps.
The "diagnostic ignored `-Woverloaded-virtual`" pragma helps because C++
doesn't like our many `run` methods. In our case, we don't mind the
shadowing it all --- it is *intentional* that the derived class only
provides a `run` method, and doesn't call any of the overridden `run`
methods.
Helps with https://github.com/NixOS/rfcs/pull/134
2023-02-04 19:03:47 +02:00
|
|
|
auto buildEnvironment = getBuildEnvironment(store, installable).first;
|
2020-03-30 20:14:17 +03:00
|
|
|
|
|
|
|
stopProgressBar();
|
|
|
|
|
structured attrs: improve support / usage of NIX_ATTRS_{SH,JSON}_FILE
In #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] https://github.com/NixOS/nix/pull/4770#issuecomment-836799719
2023-09-24 14:19:04 +03:00
|
|
|
if (json) {
|
|
|
|
logger->writeToStdout(buildEnvironment.toJSON());
|
|
|
|
} else {
|
|
|
|
AutoDelete tmpDir(createTempDir("", "nix-dev-env"), true);
|
|
|
|
logger->writeToStdout(makeRcScript(store, buildEnvironment, tmpDir));
|
|
|
|
}
|
2020-03-30 20:14:17 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-06 14:36:55 +03:00
|
|
|
static auto rCmdPrintDevEnv = registerCommand<CmdPrintDevEnv>("print-dev-env");
|
|
|
|
static auto rCmdDevelop = registerCommand<CmdDevelop>("develop");
|