about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
12 files changed, 267 insertions, 62 deletions
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;