diff --git a/doc/manual/rl-next/deprecat-nix-path-shadow.md b/doc/manual/rl-next/deprecat-nix-path-shadow.md new file mode 100644 index 000000000..e868d7284 --- /dev/null +++ b/doc/manual/rl-next/deprecat-nix-path-shadow.md @@ -0,0 +1,13 @@ +--- +synopsis: "Deprecate shadowing internal files through the Nix search path" +issues: [998] +cls: [4632] +category: "Breaking Changes" +credits: [thubrecht] +--- + +As Lix uses the path `` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable. + +To alleviate potential issues, Lix now emits a warning when the Nix search path contains potential shadows for internal files, which will be changed to an error in a future release. + +The warning can be disabled by enabling the deprecated feature `nix-path-shadow`. diff --git a/lix/libexpr/eval.cc b/lix/libexpr/eval.cc index 7f12867c9..aff596108 100644 --- a/lix/libexpr/eval.cc +++ b/lix/libexpr/eval.cc @@ -4,12 +4,14 @@ #include "lix/libutil/archive.hh" #include "lix/libutil/ansicolor.hh" #include "lix/libutil/async.hh" +#include "lix/libutil/deprecated-features.hh" #include "lix/libutil/error.hh" #include "lix/libutil/english.hh" #include "lix/libutil/fmt.hh" #include "lix/libexpr/primops.hh" #include "lix/libexpr/print-options.hh" #include "lix/libmain/shared.hh" +#include "lix/libutil/logging.hh" #include "lix/libutil/suggestions.hh" #include "lix/libutil/types.hh" #include "lix/libstore/store-api.hh" @@ -347,6 +349,61 @@ EvalPaths::EvalPaths( allowPath(path); } } + +#if LIX_MAJOR >= 3 || (LIX_MAJOR == 2 && LIX_MINOR >= 96) +#warning \ + "The feature nix-path-shadow was deprecated in 2.95 with a warning, which needs to be turned into an error in 2.96" +#endif + if (!featureSettings.isEnabled(DeprecatedFeature::NixPathShadow)) { + for (auto & [prefix, path] : searchPath_.elements) { + // Match on the 'nix' prefix + if (prefix.s == "nix") { + logWarning( + {.msg = HintFmt( + "The prefix '%s' is reserved for internal use by Lix in the Nix search " + "path, its usage is deprecated and will be forbidden in the future.\n" + "Use %s to silence this warning.\n" + "This is due to adding '%s=%s' in the Nix search path, either through the " + "environment variable '%s' or by passing the flag %s to the nix " + "invocation.", + "nix", + "--extra-deprecated-features nix-path-shadow", + prefix.s, + path.s, + "NIX_PATH", + "-I" + )} + ); + } else + // Match prefixless paths that contain a `nix` directory + if (auto res = + prefix.suffixIfPotentialMatch("nix").and_then([&](std::string_view s) { + return aio.blockOn(resolveSearchPathPath(path)) + .and_then([&](std::string r) { + Path res = s.length() ? concatStrings(r, "/", s): r; + + return pathExists(res) ? std::optional(res) : std::nullopt; + }); + })) + { + logWarning( + {.msg = HintFmt( + "Shadowing '%s' by configuring the nix-path is deprecated and " + "will be forbidden in the future.\n" + "Use %s to silence this warning.\n" + "This is due to adding '%s' to the nix-path without a prefix, " + "either by passing the flag '-I %s' to the nix invocation or by " + "adding this path to the environment variable '%s'.", + "", + "--extra-deprecated-features nix-path-shadow", + path.s, + path.s, + "NIX_PATH" + )} + ); + } + } + } } Evaluator::Evaluator( diff --git a/lix/libutil/deprecated-features/nix-path-shadow.md b/lix/libutil/deprecated-features/nix-path-shadow.md new file mode 100644 index 000000000..34840dec1 --- /dev/null +++ b/lix/libutil/deprecated-features/nix-path-shadow.md @@ -0,0 +1,6 @@ +--- +name: nix-path-shadow +internalName: NixPathShadow +--- + +Allows shadowing `` by configuration of the [*nix path*](@docroot@/language/builtin-constants.html#builtins-nixPath) to a value containing `nix=/some/path`. diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index ec7861154..4cab67543 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -172,6 +172,7 @@ deprecated_feature_definitions = files( # keep-sorted start 'deprecated-features/ancient-let.md', 'deprecated-features/cr-line-endings.md', + 'deprecated-features/nix-path-shadow.md', 'deprecated-features/nul-bytes.md', 'deprecated-features/rec-set-overrides.md', 'deprecated-features/shadow-internal-symbols.md', diff --git a/tests/functional2/lang/search-path/eval-okay-fetchurl.out.exp b/tests/functional2/lang/search-path/eval-okay-fetchurl.out.exp new file mode 100644 index 000000000..b4b2d7b11 --- /dev/null +++ b/tests/functional2/lang/search-path/eval-okay-fetchurl.out.exp @@ -0,0 +1 @@ +/__corepkgs__/fetchurl.nix diff --git a/tests/functional2/lang/search-path/eval-okay-prefixed.err.exp b/tests/functional2/lang/search-path/eval-okay-prefixed.err.exp new file mode 100644 index 000000000..79b54f511 --- /dev/null +++ b/tests/functional2/lang/search-path/eval-okay-prefixed.err.exp @@ -0,0 +1,3 @@ +warning: The prefix 'nix' is reserved for internal use by Lix in the Nix search path, its usage is deprecated and will be forbidden in the future. + Use --extra-deprecated-features nix-path-shadow to silence this warning. + This is due to adding 'nix=nix-shadow' in the Nix search path, either through the environment variable 'NIX_PATH' or by passing the flag -I to the nix invocation. diff --git a/tests/functional2/lang/search-path/eval-okay-prefixed.out.exp b/tests/functional2/lang/search-path/eval-okay-prefixed.out.exp new file mode 100644 index 000000000..b4b2d7b11 --- /dev/null +++ b/tests/functional2/lang/search-path/eval-okay-prefixed.out.exp @@ -0,0 +1 @@ +/__corepkgs__/fetchurl.nix diff --git a/tests/functional2/lang/search-path/eval-okay-prefixless.err.exp b/tests/functional2/lang/search-path/eval-okay-prefixless.err.exp new file mode 100644 index 000000000..cd78fe0f9 --- /dev/null +++ b/tests/functional2/lang/search-path/eval-okay-prefixless.err.exp @@ -0,0 +1,3 @@ +warning: Shadowing '' by configuring the nix-path is deprecated and will be forbidden in the future. + Use --extra-deprecated-features nix-path-shadow to silence this warning. + This is due to adding 'nix-shadow' to the nix-path without a prefix, either by passing the flag '-I nix-shadow' to the nix invocation or by adding this path to the environment variable 'NIX_PATH'. diff --git a/tests/functional2/lang/search-path/eval-okay-prefixless.out.exp b/tests/functional2/lang/search-path/eval-okay-prefixless.out.exp new file mode 100644 index 000000000..2f823df66 --- /dev/null +++ b/tests/functional2/lang/search-path/eval-okay-prefixless.out.exp @@ -0,0 +1 @@ +/pwd/nix-shadow/nix/fetchurl.nix diff --git a/tests/functional2/lang/search-path/in-fetchurl.nix b/tests/functional2/lang/search-path/in-fetchurl.nix new file mode 100644 index 000000000..8417f2899 --- /dev/null +++ b/tests/functional2/lang/search-path/in-fetchurl.nix @@ -0,0 +1 @@ + diff --git a/tests/functional2/lang/search-path/nix-shadow/nix/fetchurl.nix b/tests/functional2/lang/search-path/nix-shadow/nix/fetchurl.nix new file mode 100644 index 000000000..eaa3c083e --- /dev/null +++ b/tests/functional2/lang/search-path/nix-shadow/nix/fetchurl.nix @@ -0,0 +1 @@ +assert false; "womp womp" diff --git a/tests/functional2/lang/search-path/test_search_path.py b/tests/functional2/lang/search-path/test_search_path.py index cad85b9ce..de16ddc4f 100644 --- a/tests/functional2/lang/search-path/test_search_path.py +++ b/tests/functional2/lang/search-path/test_search_path.py @@ -2,7 +2,7 @@ from collections.abc import Callable from pathlib import Path from functional2.lang.test_lang import test_eval as nix_eval -from functional2.testlib.fixtures.file_helper import with_files, CopyTree, CopyFile, AssetSymlink +from functional2.testlib.fixtures.file_helper import AssetSymlink, CopyFile, CopyTree, with_files from functional2.testlib.fixtures.nix import Nix from functional2.testlib.fixtures.snapshot import Snapshot @@ -36,3 +36,39 @@ def test_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]) ], snapshot, ) + + +@with_files( + { + "nix-shadow": CopyTree("nix-shadow"), + "in.nix": CopyFile("in-fetchurl.nix"), + "out.exp": AssetSymlink("eval-okay-prefixed.out.exp"), + "err.exp": AssetSymlink("eval-okay-prefixed.err.exp"), + } +) +def test_prefixed_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]): + nix.env.set_env("NIX_PATH", "nix=nix-shadow") + nix_eval(files, nix, [], snapshot) + + +@with_files( + { + "nix-shadow": CopyTree("nix-shadow"), + "in.nix": CopyFile("in-fetchurl.nix"), + "out.exp": AssetSymlink("eval-okay-prefixless.out.exp"), + "err.exp": AssetSymlink("eval-okay-prefixless.err.exp"), + } +) +def test_prefixless_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]): + nix_eval(files, nix, ["-I", "nix-shadow"], snapshot) + + +@with_files( + { + "in.nix": CopyFile("in-fetchurl.nix"), + "out.exp": AssetSymlink("eval-okay-fetchurl.out.exp"), + "err.exp": AssetSymlink("eval-okay-fetchurl.err.exp"), + } +) +def test_empty_search_path(files: Path, nix: Nix, snapshot: Callable[[str], Snapshot]): + nix_eval(files, nix, [], snapshot)