From ed3edf2de5510a9fd864f0c640d6055b2adfe5da Mon Sep 17 00:00:00 2001
From: Robert Hensing <robert@roberthensing.nl>
Date: Thu, 24 Oct 2024 13:18:06 +0200
Subject: [PATCH] Add option disallow-copy-paths

---
 src/libexpr/eval-settings.hh            |  9 +++++++++
 src/libexpr/eval.cc                     |  3 +++
 tests/functional/disallow-copy-paths.sh | 16 ++++++++++++++++
 tests/functional/local.mk               |  1 +
 tests/functional/meson.build            |  1 +
 5 files changed, 30 insertions(+)
 create mode 100755 tests/functional/disallow-copy-paths.sh

diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh
index 115e3ee501b..ec6b609f422 100644
--- a/src/libexpr/eval-settings.hh
+++ b/src/libexpr/eval-settings.hh
@@ -247,6 +247,15 @@ struct EvalSettings : Config
 
           This option can be enabled by setting `NIX_ABORT_ON_WARN=1` in the environment.
         )"};
+
+    Setting<std::set<std::string>> disallowCopyPaths{this, {}, "disallow-copy-paths",
+        R"(
+          A list of paths that are not allowed to be copied.
+
+          This is useful for finding expressions which copy sources, which can slow down evaluation.
+          You may find copied sources by running `nix` commands with increased verbosity, such as `nix build -vvvv 2>&1 | grep /nix/store`.
+          After identifying one more more paths, run `nix build --option disallow-copy-paths /nix/store/... --show-trace` to find the expression that copies the path, or add `--debugger`.
+        )"};
 };
 
 /**
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 25354fb1f92..640ca777e04 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -2384,6 +2384,9 @@ StorePath EvalState::fetchToStore(
     PathFilter * filter,
     RepairFlag repair)
 {
+    if (path.accessor == rootFS && settings.disallowCopyPaths.get().contains(path.path.abs())) {
+        error<EvalError>("not allowed to copy '%1%' due to option '%2%'", path.path.abs(), settings.disallowCopyPaths.name).debugThrow();
+    }
     return ::nix::fetchToStore(*store, path, mode, name, method, filter, repair);
 }
 
diff --git a/tests/functional/disallow-copy-paths.sh b/tests/functional/disallow-copy-paths.sh
new file mode 100755
index 00000000000..4cd1ff827f1
--- /dev/null
+++ b/tests/functional/disallow-copy-paths.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+source common.sh
+
+clearStoreIfPossible
+
+# shellcheck disable=SC2016
+path="$(nix eval --raw --impure --expr '"${./disallow-copy-paths.sh}"')"
+
+# shellcheck disable=SC2016
+expectStderr 1 nix-instantiate \
+  --disallow-copy-paths "$path" \
+  --expr --strict \
+  --argstr path "$path" \
+  '{ path }: "${/. + path}" + "bla bla"' \
+  | grepQuiet "error.*not allowed to copy.*$path.* due to option.*disallow-copy-paths"
diff --git a/tests/functional/local.mk b/tests/functional/local.mk
index e50b5eaf1df..26c6a5644bf 100644
--- a/tests/functional/local.mk
+++ b/tests/functional/local.mk
@@ -1,5 +1,6 @@
 nix_tests = \
   test-infra.sh \
+  disallow-copy-paths.sh \
   gc.sh \
   nix-collect-garbage-d.sh \
   remote-store.sh \
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index 54f3e7a0158..8f3a2cae90c 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -69,6 +69,7 @@ suites = [
     'deps': [],
     'tests': [
       'test-infra.sh',
+      'disallow-copy-paths.sh',
       'gc.sh',
       'nix-collect-garbage-d.sh',
       'remote-store.sh',