about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--misc/docker/Dockerfile16
-rw-r--r--mk/tests.mk29
-rw-r--r--release-common.nix4
-rw-r--r--release.nix2
-rw-r--r--scripts/install-darwin-multi-user.sh8
-rw-r--r--src/libstore/download.cc57
-rw-r--r--src/libutil/args.hh2
-rw-r--r--src/libutil/thread-pool.cc81
-rw-r--r--src/libutil/thread-pool.hh9
-rw-r--r--src/nix/command.cc16
-rw-r--r--src/nix/command.hh6
-rw-r--r--src/nix/copy.cc1
-rw-r--r--src/nix/installables.cc28
-rw-r--r--src/nix/progress-bar.cc2
-rw-r--r--src/nix/run.cc2
-rw-r--r--src/nix/show-derivation.cc119
-rw-r--r--src/nix/why-depends.cc6
-rw-r--r--tests/binary-cache.sh4
-rw-r--r--tests/nix-channel.sh2
-rw-r--r--tests/repair.sh2
21 files changed, 313 insertions, 87 deletions
diff --git a/.gitignore b/.gitignore
index 6163087384d8..ce22fa007dc7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -99,16 +99,20 @@ perl/Makefile.config
 /misc/systemd/nix-daemon.socket
 /misc/upstart/nix-daemon.conf
 
+/src/resolve-system-dependencies/resolve-system-dependencies
+
 inst/
 
 *.a
 *.o
 *.so
+*.dylib
 *.dll
 *.exe
 *.dep
 *~
 *.pc
+*.plist
 
 # GNU Global
 GPATH
diff --git a/misc/docker/Dockerfile b/misc/docker/Dockerfile
index fb6f73517bb6..d6b88c7e91a5 100644
--- a/misc/docker/Dockerfile
+++ b/misc/docker/Dockerfile
@@ -8,19 +8,19 @@ RUN wget -O- https://nixos.org/releases/nix/nix-1.11.14/nix-1.11.14-x86_64-linux
   && addgroup -g 30000 -S nixbld \
   && for i in $(seq 1 30); do adduser -S -D -h /var/empty -g "Nix build user $i" -u $((30000 + i)) -G nixbld nixbld$i ; done \
   && mkdir -m 0755 /nix && USER=root sh nix-*-x86_64-linux/install \
-  && ln -s /root/.nix-profile/etc/profile.d/nix.sh /etc/profile.d/ \
+  && ln -s /nix/var/nix/profiles/default/etc/profile.d/nix.sh /etc/profile.d/ \
   && rm -r /nix-*-x86_64-linux \
   && rm -r /var/cache/apk/*
 
 ONBUILD ENV \
     ENV=/etc/profile \
-    PATH=/root/.nix-profile/bin:/root/.nix-profile/sbin:/bin:/sbin:/usr/bin:/usr/sbin \
-    GIT_SSL_CAINFO=/root/.nix-profile/etc/ssl/certs/ca-bundle.crt \
-    NIX_SSL_CERT_FILE=/root/.nix-profile/etc/ssl/certs/ca-bundle.crt
+    PATH=/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin \
+    GIT_SSL_CAINFO=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt \
+    NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt
 
 ENV \
     ENV=/etc/profile \
-    PATH=/root/.nix-profile/bin:/root/.nix-profile/sbin:/bin:/sbin:/usr/bin:/usr/sbin \
-    GIT_SSL_CAINFO=/root/.nix-profile/etc/ssl/certs/ca-bundle.crt \
-    NIX_SSL_CERT_FILE=/root/.nix-profile/etc/ssl/certs/ca-bundle.crt \
-    NIX_PATH=/nix/var/nix/profiles/per-user/root/channels/
+    PATH=/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin \
+    GIT_SSL_CAINFO=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt \
+    NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt \
+    NIX_PATH=/nix/var/nix/profiles/per-user/root/channels
diff --git a/mk/tests.mk b/mk/tests.mk
index 004a48028616..1138857c3c16 100644
--- a/mk/tests.mk
+++ b/mk/tests.mk
@@ -7,20 +7,37 @@ define run-install-test
 
 endef
 
+# Color code from https://unix.stackexchange.com/a/10065
 installcheck:
-	@total=0; failed=0; for i in $(_installcheck-list); do \
+	@total=0; failed=0; \
+	red=""; \
+	green=""; \
+	normal=""; \
+	if [ -t 1 ]; then \
+		ncolors="$$(tput colors)"; \
+		if [ -n "$$ncolors" ] && [ "$$ncolors" -ge 8 ]; then \
+			red="$$(tput setaf 1)"; \
+			green="$$(tput setaf 2)"; \
+			normal="$$(tput sgr0)"; \
+		fi; \
+	fi; \
+	for i in $(_installcheck-list); do \
 	  total=$$((total + 1)); \
-	  echo "running test $$i"; \
-	  if (cd $$(dirname $$i) && $(tests-environment) $$(basename $$i)); then \
-	    echo "PASS: $$i"; \
+	  printf "running test $$i..."; \
+	  log="$$(cd $$(dirname $$i) && $(tests-environment) $$(basename $$i) 2>&1)"; \
+	  if [ $$? -eq 0 ]; then \
+	    echo " [$${green}PASS$$normal]"; \
 	  else \
-	    echo "FAIL: $$i"; \
+	    echo " [$${red}FAIL$$normal]"; \
+	    echo "$$log" | sed 's/^/    /'; \
 	    failed=$$((failed + 1)); \
 	  fi; \
 	done; \
 	if [ "$$failed" != 0 ]; then \
-	  echo "$$failed out of $$total tests failed "; \
+	  echo "$${red}$$failed out of $$total tests failed $$normal"; \
 	  exit 1; \
+	else \
+		echo "$${green}All tests succeeded"; \
 	fi
 
 .PHONY: check installcheck
diff --git a/release-common.nix b/release-common.nix
index c64fc619df6d..4553118e1f56 100644
--- a/release-common.nix
+++ b/release-common.nix
@@ -7,8 +7,8 @@ rec {
     enableMinimal = true;
     extraConfig = ''
       CONFIG_ASH y
-      CONFIG_ASH_BUILTIN_ECHO y
-      CONFIG_ASH_BUILTIN_TEST y
+      CONFIG_ASH_ECHO y
+      CONFIG_ASH_TEST y
       CONFIG_ASH_OPTIMIZE_FOR_SIZE y
     '';
   };
diff --git a/release.nix b/release.nix
index 29cd36860ad4..a98199258842 100644
--- a/release.nix
+++ b/release.nix
@@ -332,7 +332,7 @@ let
       src = jobs.tarball;
       diskImage = (diskImageFun vmTools.diskImageFuns)
         { extraPackages =
-            [ "libsqlite3-dev" "libbz2-dev" "libcurl-dev" "libcurl3-nss" "libssl-dev" "liblzma-dev" "libseccomp-dev" ]
+            [ "libsqlite3-dev" "libbz2-dev" "libcurl-dev" "libcurl3-nss" "libssl-dev" "liblzma-dev" "libseccomp-dev" "ncurses-bin" ]
             ++ extraPackages; };
       memSize = 1024;
       meta.schedulingPriority = 50;
diff --git a/scripts/install-darwin-multi-user.sh b/scripts/install-darwin-multi-user.sh
index 5b466ac3c882..cea25eb8adf3 100644
--- a/scripts/install-darwin-multi-user.sh
+++ b/scripts/install-darwin-multi-user.sh
@@ -318,7 +318,7 @@ EOF
 
     for file in ~/.bash_profile ~/.bash_login ~/.profile ~/.zshenv ~/.zprofile ~/.zshrc ~/.zlogin; do
         if [ -f "$file" ]; then
-            if grep -l ".nix-profile" "$file"; then
+            if grep -l "^[^#].*.nix-profile" "$file"; then
                 failure <<EOF
 I found a reference to a ".nix-profile" in $file.
 This has a high chance of breaking a new nix installation. It was most
@@ -730,13 +730,13 @@ configure_shell_profile() {
 
 setup_default_profile() {
     _sudo "to installing a bootstrapping Nix in to the default Profile" \
-          -i "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_NIX"
+          HOME=$ROOT_HOME "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_NIX"
 
     _sudo "to installing a bootstrapping SSL certificate just for Nix in to the default Profile" \
-          -i "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_CACERT"
+          HOME=$ROOT_HOME "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_CACERT"
 
     _sudo "to update the default channel in the default profile" \
-          -i NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs
+          HOME=$ROOT_HOME NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs
 }
 
 
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 3f5e744dde19..608b8fd399b4 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -278,26 +278,43 @@ struct CurlDownloader : public Downloader
                     callFailure(failure, std::current_exception());
                 }
             } else {
-                Error err =
-                    (httpStatus == 404 || code == CURLE_FILE_COULDNT_READ_FILE) ? NotFound :
-                    httpStatus == 403 ? Forbidden :
-                    (httpStatus == 408 || httpStatus == 500 || httpStatus == 503
-                        || httpStatus == 504  || httpStatus == 522 || httpStatus == 524
-                        || code == CURLE_COULDNT_RESOLVE_HOST
-                        || code == CURLE_RECV_ERROR
-
-                        // this seems to occur occasionally for retriable reasons, and shows up in an error like this:
-                        //   curl: (23) Failed writing body (315 != 16366)
-                        || code == CURLE_WRITE_ERROR
-
-                        // this is a generic SSL failure that in some cases (e.g., certificate error) is permanent but also appears in transient cases, so we consider it retryable
-                        || code == CURLE_SSL_CONNECT_ERROR
-#if LIBCURL_VERSION_NUM >= 0x073200
-                        || code == CURLE_HTTP2
-                        || code == CURLE_HTTP2_STREAM
-#endif
-                        ) ? Transient :
-                    Misc;
+                // We treat most errors as transient, but won't retry when hopeless
+                Error err = Transient;
+
+                if (httpStatus == 404 || code == CURLE_FILE_COULDNT_READ_FILE) {
+                    // The file is definitely not there
+                    err = NotFound;
+                } else if (httpStatus == 401 || httpStatus == 403 || httpStatus == 407) {
+                    // Don't retry on authentication/authorization failures
+                    err = Forbidden;
+                } else if (httpStatus >= 400 && httpStatus < 500 && httpStatus != 408) {
+                    // Most 4xx errors are client errors and are probably not worth retrying:
+                    //   * 408 means the server timed out waiting for us, so we try again
+                    err = Misc;
+                } else if (httpStatus == 501 || httpStatus == 505 || httpStatus == 511) {
+                    // Let's treat most 5xx (server) errors as transient, except for a handful:
+                    //   * 501 not implemented
+                    //   * 505 http version not supported
+                    //   * 511 we're behind a captive portal
+                    err = Misc;
+                } else {
+                    // Don't bother retrying on certain cURL errors either
+                    switch (code) {
+                        case CURLE_FAILED_INIT:
+                        case CURLE_NOT_BUILT_IN:
+                        case CURLE_REMOTE_ACCESS_DENIED:
+                        case CURLE_FILE_COULDNT_READ_FILE:
+                        case CURLE_FUNCTION_NOT_FOUND:
+                        case CURLE_ABORTED_BY_CALLBACK:
+                        case CURLE_BAD_FUNCTION_ARGUMENT:
+                        case CURLE_INTERFACE_FAILED:
+                        case CURLE_UNKNOWN_OPTION:
+                        err = Misc;
+                        break;
+                        default: // Shut up warnings
+                        break;
+                    }
+                }
 
                 attempt++;
 
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index 401bebbabc08..37e31825ab37 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -90,7 +90,7 @@ public:
 
         template<class T>
         FlagMaker & set(T * dest, const T & val) {
-            flag->arity = 1;
+            flag->arity = 0;
             flag->handler = [=](Strings ss) { *dest = val; };
             return *this;
         };
diff --git a/src/libutil/thread-pool.cc b/src/libutil/thread-pool.cc
index ce126d36db8e..857ee91f87d0 100644
--- a/src/libutil/thread-pool.cc
+++ b/src/libutil/thread-pool.cc
@@ -13,11 +13,16 @@ ThreadPool::ThreadPool(size_t _maxThreads)
         if (!maxThreads) maxThreads = 1;
     }
 
-    debug(format("starting pool of %d threads") % maxThreads);
+    debug("starting pool of %d threads", maxThreads - 1);
 }
 
 ThreadPool::~ThreadPool()
 {
+    shutdown();
+}
+
+void ThreadPool::shutdown()
+{
     std::vector<std::thread> workers;
     {
         auto state(state_.lock());
@@ -25,7 +30,9 @@ ThreadPool::~ThreadPool()
         std::swap(workers, state->workers);
     }
 
-    debug(format("reaping %d worker threads") % workers.size());
+    if (workers.empty()) return;
+
+    debug("reaping %d worker threads", workers.size());
 
     work.notify_all();
 
@@ -38,32 +45,43 @@ void ThreadPool::enqueue(const work_t & t)
     auto state(state_.lock());
     if (quit)
         throw ThreadPoolShutDown("cannot enqueue a work item while the thread pool is shutting down");
-    state->left.push(t);
-    if (state->left.size() > state->workers.size() && state->workers.size() < maxThreads)
-        state->workers.emplace_back(&ThreadPool::workerEntry, this);
+    state->pending.push(t);
+    /* Note: process() also executes items, so count it as a worker. */
+    if (state->pending.size() > state->workers.size() + 1 && state->workers.size() + 1 < maxThreads)
+        state->workers.emplace_back(&ThreadPool::doWork, this, false);
     work.notify_one();
 }
 
 void ThreadPool::process()
 {
-    /* Loop until there are no active work items *and* there either
-       are no queued items or there is an exception. The
-       post-condition is that no new items will become active. */
-    while (true) {
+    state_.lock()->draining = true;
+
+    /* Do work until no more work is pending or active. */
+    try {
+        doWork(true);
+
         auto state(state_.lock());
-        if (!state->active) {
-            if (state->exception)
-                std::rethrow_exception(state->exception);
-            if (state->left.empty())
-                break;
-        }
-        state.wait(done);
+
+        assert(quit);
+
+        if (state->exception)
+            std::rethrow_exception(state->exception);
+
+    } catch (...) {
+        /* In the exceptional case, some workers may still be
+           active. They may be referencing the stack frame of the
+           caller. So wait for them to finish. (~ThreadPool also does
+           this, but it might be destroyed after objects referenced by
+           the work item lambdas.) */
+        shutdown();
+        throw;
     }
 }
 
-void ThreadPool::workerEntry()
+void ThreadPool::doWork(bool mainThread)
 {
-    interruptCheck = [&]() { return (bool) quit; };
+    if (!mainThread)
+        interruptCheck = [&]() { return (bool) quit; };
 
     bool didWork = false;
     std::exception_ptr exc;
@@ -99,24 +117,27 @@ void ThreadPool::workerEntry()
                 }
             }
 
-            /* Wait until a work item is available or another thread
-               had an exception or we're asked to quit. */
+            /* Wait until a work item is available or we're asked to
+               quit. */
             while (true) {
-                if (quit) {
-                    if (!state->active)
-                        done.notify_one();
-                    return;
-                }
-                if (!state->left.empty()) break;
-                if (!state->active) {
-                    done.notify_one();
+                if (quit) return;
+
+                if (!state->pending.empty()) break;
+
+                /* If there are no active or pending items, and the
+                   main thread is running process(), then no new items
+                   can be added. So exit. */
+                if (!state->active && state->draining) {
+                    quit = true;
+                    work.notify_all();
                     return;
                 }
+
                 state.wait(work);
             }
 
-            w = std::move(state->left.front());
-            state->left.pop();
+            w = std::move(state->pending.front());
+            state->pending.pop();
             state->active++;
         }
 
diff --git a/src/libutil/thread-pool.hh b/src/libutil/thread-pool.hh
index 06a097ab5ea7..bb16b639a591 100644
--- a/src/libutil/thread-pool.hh
+++ b/src/libutil/thread-pool.hh
@@ -44,19 +44,22 @@ private:
 
     struct State
     {
-        std::queue<work_t> left;
+        std::queue<work_t> pending;
         size_t active = 0;
         std::exception_ptr exception;
         std::vector<std::thread> workers;
+        bool draining = false;
     };
 
     std::atomic_bool quit{false};
 
     Sync<State> state_;
 
-    std::condition_variable work, done;
+    std::condition_variable work;
 
-    void workerEntry();
+    void doWork(bool mainThread);
+
+    void shutdown();
 };
 
 /* Process in parallel a set of items of type T that have a partial
diff --git a/src/nix/command.cc b/src/nix/command.cc
index f69c56896567..0f6bb294b38c 100644
--- a/src/nix/command.cc
+++ b/src/nix/command.cc
@@ -100,9 +100,21 @@ void StoreCommand::run()
     run(getStore());
 }
 
-StorePathsCommand::StorePathsCommand()
+StorePathsCommand::StorePathsCommand(bool recursive)
+    : recursive(recursive)
 {
-    mkFlag('r', "recursive", "apply operation to closure of the specified paths", &recursive);
+    if (recursive)
+        mkFlag()
+            .longName("no-recursive")
+            .description("apply operation to specified paths only")
+            .set(&this->recursive, false);
+    else
+        mkFlag()
+            .longName("recursive")
+            .shortName('r')
+            .description("apply operation to closure of the specified paths")
+            .set(&this->recursive, true);
+
     mkFlag(0, "all", "apply operation to the entire store", &all);
 }
 
diff --git a/src/nix/command.hh b/src/nix/command.hh
index 182b01ef92e3..bf897f620db6 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -141,7 +141,7 @@ private:
 
 public:
 
-    StorePathsCommand();
+    StorePathsCommand(bool recursive = false);
 
     using StoreCommand::run;
 
@@ -207,4 +207,8 @@ PathSet toStorePaths(ref<Store> store, RealiseMode mode,
 Path toStorePath(ref<Store> store, RealiseMode mode,
     std::shared_ptr<Installable> installable);
 
+PathSet toDerivations(ref<Store> store,
+    std::vector<std::shared_ptr<Installable>> installables,
+    bool useDeriver = false);
+
 }
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index 8d7c6a0e8e4c..071ac3890aa9 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -17,6 +17,7 @@ struct CmdCopy : StorePathsCommand
     SubstituteFlag substitute = NoSubstitute;
 
     CmdCopy()
+        : StorePathsCommand(true)
     {
         mkFlag(0, "from", "store-uri", "URI of the source Nix store", &srcUri);
         mkFlag(0, "to", "store-uri", "URI of the destination Nix store", &dstUri);
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 76df05fa32d2..c83d6316d3f3 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -267,7 +267,9 @@ Buildables toBuildables(ref<Store> store, RealiseMode mode,
                     outputNames.insert(output.first);
                 pathsToBuild.insert(
                     b.drvPath + "!" + concatStringsSep(",", outputNames));
-            }
+            } else
+                for (auto & output : b.outputs)
+                    pathsToBuild.insert(output.second);
             buildables.push_back(std::move(b));
         }
     }
@@ -303,6 +305,30 @@ Path toStorePath(ref<Store> store, RealiseMode mode,
     return *paths.begin();
 }
 
+PathSet toDerivations(ref<Store> store,
+    std::vector<std::shared_ptr<Installable>> installables, bool useDeriver)
+{
+    PathSet drvPaths;
+
+    for (auto & i : installables)
+        for (auto & b : i->toBuildables()) {
+            if (b.drvPath.empty()) {
+                if (!useDeriver)
+                    throw Error("argument '%s' did not evaluate to a derivation", i->what());
+                for (auto & output : b.outputs) {
+                    auto derivers = store->queryValidDerivers(output.second);
+                    if (derivers.empty())
+                        throw Error("'%s' does not have a known deriver", i->what());
+                    // FIXME: use all derivers?
+                    drvPaths.insert(*derivers.begin());
+                }
+            } else
+                drvPaths.insert(b.drvPath);
+        }
+
+    return drvPaths;
+}
+
 void InstallablesCommand::prepare()
 {
     installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables());
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
index 0c42fe5cc136..76138b2cce28 100644
--- a/src/nix/progress-bar.cc
+++ b/src/nix/progress-bar.cc
@@ -165,7 +165,7 @@ public:
 
         if (type == actQueryPathInfo) {
             auto name = storePathToName(getS(fields, 0));
-            i->s = fmt("querying about " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
+            i->s = fmt("querying " ANSI_BOLD "%s" ANSI_NORMAL " on %s", name, getS(fields, 1));
         }
 
         if ((type == actDownload && hasAncestor(*state, actCopyPath, parent))
diff --git a/src/nix/run.cc b/src/nix/run.cc
index c72ede99c1c2..2f93ca351502 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -130,6 +130,8 @@ struct CmdRun : InstallablesCommand
 
         stopProgressBar();
 
+        restoreSignals();
+
         /* If this is a diverted store (i.e. its "logical" location
            (typically /nix/store) differs from its "physical" location
            (e.g. /home/eelco/nix/store), then run the command in a
diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc
new file mode 100644
index 000000000000..ee94fded364f
--- /dev/null
+++ b/src/nix/show-derivation.cc
@@ -0,0 +1,119 @@
+// FIXME: integrate this with nix path-info?
+
+#include "command.hh"
+#include "common-args.hh"
+#include "store-api.hh"
+#include "archive.hh"
+#include "json.hh"
+#include "derivations.hh"
+
+using namespace nix;
+
+struct CmdShowDerivation : InstallablesCommand
+{
+    bool recursive = false;
+
+    CmdShowDerivation()
+    {
+        mkFlag()
+            .longName("recursive")
+            .shortName('r')
+            .description("include the dependencies of the specified derivations")
+            .set(&recursive, true);
+    }
+
+    std::string name() override
+    {
+        return "show-derivation";
+    }
+
+    std::string description() override
+    {
+        return "show the contents of a store derivation";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To show the store derivation that results from evaluating the Hello package:",
+                "nix show-derivation nixpkgs.hello"
+            },
+            Example{
+                "To show the full derivation graph (if available) that produced your NixOS system:",
+                "nix show-derivation -r /run/current-system"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        auto drvPaths = toDerivations(store, installables, true);
+
+        if (recursive) {
+            PathSet closure;
+            store->computeFSClosure(drvPaths, closure);
+            drvPaths = closure;
+        }
+
+        {
+
+        JSONObject jsonRoot(std::cout, true);
+
+        for (auto & drvPath : drvPaths) {
+            if (!isDerivation(drvPath)) continue;
+
+            auto drvObj(jsonRoot.object(drvPath));
+
+            auto drv = readDerivation(drvPath);
+
+            {
+                auto outputsObj(drvObj.object("outputs"));
+                for (auto & output : drv.outputs) {
+                    auto outputObj(outputsObj.object(output.first));
+                    outputObj.attr("path", output.second.path);
+                    if (output.second.hash != "") {
+                        outputObj.attr("hashAlgo", output.second.hashAlgo);
+                        outputObj.attr("hash", output.second.hash);
+                    }
+                }
+            }
+
+            {
+                auto inputsList(drvObj.list("inputSrcs"));
+                for (auto & input : drv.inputSrcs)
+                    inputsList.elem(input);
+            }
+
+            {
+                auto inputDrvsObj(drvObj.object("inputDrvs"));
+                for (auto & input : drv.inputDrvs) {
+                    auto inputList(inputDrvsObj.list(input.first));
+                    for (auto & outputId : input.second)
+                        inputList.elem(outputId);
+                }
+            }
+
+            drvObj.attr("platform", drv.platform);
+            drvObj.attr("builder", drv.builder);
+
+            {
+                auto argsList(drvObj.list("args"));
+                for (auto & arg : drv.args)
+                    argsList.elem(arg);
+            }
+
+            {
+                auto envObj(drvObj.object("env"));
+                for (auto & var : drv.env)
+                    envObj.attr(var.first, var.second);
+            }
+        }
+
+        }
+
+        std::cout << "\n";
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdShowDerivation>());
diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc
index a90d07ed26ee..17e0595ae887 100644
--- a/src/nix/why-depends.cc
+++ b/src/nix/why-depends.cc
@@ -156,7 +156,7 @@ struct CmdWhyDepends : SourceExprCommand
 
         printNode = [&](Node & node, const string & firstPad, const string & tailPad) {
             assert(node.dist != inf);
-            std::cerr << fmt("%s%s%s%s" ANSI_NORMAL "\n",
+            std::cout << fmt("%s%s%s%s" ANSI_NORMAL "\n",
                 firstPad,
                 node.visited ? "\e[38;5;244m" : "",
                 firstPad != "" ? "=> " : "",
@@ -209,7 +209,7 @@ struct CmdWhyDepends : SourceExprCommand
                     for (auto & hash : hashes) {
                         auto pos = contents.find(hash);
                         if (pos != std::string::npos) {
-                            size_t margin = 16;
+                            size_t margin = 32;
                             auto pos2 = pos >= margin ? pos - margin : 0;
                             hits[hash].emplace_back(fmt("%s: …%s…\n",
                                     p2,
@@ -244,7 +244,7 @@ struct CmdWhyDepends : SourceExprCommand
 
                 for (auto & hit : hits[hash]) {
                     bool first = hit == *hits[hash].begin();
-                    std::cerr << tailPad
+                    std::cout << tailPad
                               << (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine))
                               << hit;
                     if (!all) break;
diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh
index 532099d02142..30d7cc6ba88c 100644
--- a/tests/binary-cache.sh
+++ b/tests/binary-cache.sh
@@ -6,7 +6,7 @@ clearCache
 # Create the binary cache.
 outPath=$(nix-build dependencies.nix --no-out-link)
 
-nix copy --recursive --to file://$cacheDir $outPath
+nix copy --to file://$cacheDir $outPath
 
 
 basicTests() {
@@ -117,7 +117,7 @@ badKey="$(cat $TEST_ROOT/pk2)"
 res=($(nix-store --generate-binary-cache-key foo.nixos.org-1 $TEST_ROOT/sk3 $TEST_ROOT/pk3))
 otherKey="$(cat $TEST_ROOT/pk3)"
 
-nix copy --recursive --to file://$cacheDir?secret-key=$TEST_ROOT/sk1 $outPath
+nix copy --to file://$cacheDir?secret-key=$TEST_ROOT/sk1 $outPath
 
 
 # Downloading should fail if we don't provide a key.
diff --git a/tests/nix-channel.sh b/tests/nix-channel.sh
index 553ada51d9f7..55f1695c4412 100644
--- a/tests/nix-channel.sh
+++ b/tests/nix-channel.sh
@@ -15,7 +15,7 @@ nix-channel --remove xyzzy
 # Create a channel.
 rm -rf $TEST_ROOT/foo
 mkdir -p $TEST_ROOT/foo
-nix copy --recursive --to file://$TEST_ROOT/foo?compression="bzip2" $(nix-store -r $(nix-instantiate dependencies.nix))
+nix copy --to file://$TEST_ROOT/foo?compression="bzip2" $(nix-store -r $(nix-instantiate dependencies.nix))
 rm -rf $TEST_ROOT/nixexprs
 mkdir -p $TEST_ROOT/nixexprs
 cp config.nix dependencies.nix dependencies.builder*.sh $TEST_ROOT/nixexprs/
diff --git a/tests/repair.sh b/tests/repair.sh
index 57152d450a17..7c928e3be73c 100644
--- a/tests/repair.sh
+++ b/tests/repair.sh
@@ -46,7 +46,7 @@ fi
 # --verify can fix it.
 clearCache
 
-nix copy --recursive --to file://$cacheDir $path
+nix copy --to file://$cacheDir $path
 
 chmod u+w $path2
 rm -rf $path2