From e8015836e33b07ee1f1d5738bb030f7fe0b69d63 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Tue, 3 Mar 2026 02:42:17 +0100 Subject: [PATCH] libcmd/profiles: keep derivations if keep-env-derivations = true nix3 CLI never honored that setting. When we update a nix3 profile element with new built store paths for which we know the deriver (the derivation path which built it), we add the derivation paths to the profile element to prevent garbage collection the same way the nix2 CLI logic does. Fixes #1095. Change-Id: Icc236d174c5ce5ffe042d1c8e1968f6cb5b50359 Signed-off-by: Raito Bezarius --- .../rl-next/keep-env-derivations-for-nix3.md | 13 +++++++ lix/libcmd/cmd-profiles.cc | 4 ++ tests/functional2/cli/test_profile.py | 37 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 doc/manual/rl-next/keep-env-derivations-for-nix3.md diff --git a/doc/manual/rl-next/keep-env-derivations-for-nix3.md b/doc/manual/rl-next/keep-env-derivations-for-nix3.md new file mode 100644 index 000000000..a0009cb20 --- /dev/null +++ b/doc/manual/rl-next/keep-env-derivations-for-nix3.md @@ -0,0 +1,13 @@ +--- +synopsis: "keep-env-derivations is now supported for nix3 CLI (nix profile)" +cls: [5332] +issues: [fj#1095] +category: "Features" +credits: [raito] +--- + +The `keep-env-derivations` feature is now available for `nix profile`. This allows users to prevent the garbage collection of derivations used to install a profile, even when `keep-derivations = false` (set to `true` by default). + +Previously, `nix-env` supported this feature, but `nix profile` **never** did. This caused issues when garbage collection removed the associated `.drv` files, which are required, for example, by vulnerability management tools (e.g. [vulnix](https://github.com/nix-community/vulnix)) for proper operation. + +This issue has now been resolved. diff --git a/lix/libcmd/cmd-profiles.cc b/lix/libcmd/cmd-profiles.cc index e41b37d37..8dc00f19b 100644 --- a/lix/libcmd/cmd-profiles.cc +++ b/lix/libcmd/cmd-profiles.cc @@ -98,6 +98,10 @@ void ProfileElement::updateStorePaths( for (auto & output : bfd.outputs) { storePaths.insert(output.second); } + + if (settings.envKeepDerivations) { + storePaths.insert(bfd.drvPath.path); + } }, }, buildable.raw() diff --git a/tests/functional2/cli/test_profile.py b/tests/functional2/cli/test_profile.py index cdca0df4d..2cb22c7da 100644 --- a/tests/functional2/cli/test_profile.py +++ b/tests/functional2/cli/test_profile.py @@ -2,6 +2,7 @@ import re import json import pytest from textwrap import dedent +from pathlib import Path from testlib.fixtures.file_helper import File, with_files from testlib.fixtures.nix import Nix @@ -279,3 +280,39 @@ def test_conflict_resolution_cppnix_8284(nix: Nix): expr = f'(builtins.getFlake "./flake2").packages.{system}.default' nix.nix(["profile", "install", "--impure", "--expr", expr]).run().expect(1) + + +class TestKeepEnvDerivations: + @with_files({"flake1": flake}) + def test_keep_derivations_enabled(self, nix: Nix, env: ManagedEnv): + nix.settings["keep-env-derivations"] = "true" + nix.settings["keep-derivations"] = "false" # NOTE: This defaults to `true`. + + nix.nix(["profile", "install", "./flake1", "-L"]).run().ok() + assert run_hello(env) == "Hello World\n" + res = nix.nix(["eval", "--raw", "./flake1#default.drvPath"]).run().ok() + drv_path = Path(res.stdout_s.strip()) + + assert drv_path.exists() + + # The derivation should NOT be collected because `keep-env-derivations` is true + nix.nix(["store", "gc"]).run().ok() + + assert drv_path.exists() + + @with_files({"flake1": flake}) + def test_keep_derivations_disabled(self, nix: Nix, env: ManagedEnv): + nix.settings["keep-derivations"] = "false" # NOTE: This defaults to `true` + + nix.nix(["profile", "install", "./flake1", "-L"]).run().ok() + assert run_hello(env) == "Hello World\n" + + res = nix.nix(["eval", "--raw", "./flake1#default.drvPath"]).run().ok() + drv_path = Path(res.stdout_s.strip()) + + assert drv_path.exists() + + # The derivation should be collected because `keep-env-derivations` is false AND `keep-derivations` is false. + res = nix.nix(["store", "gc"]).run().ok() + + assert not drv_path.exists()