about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2012-01-04T16·22+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2012-01-04T16·22+0000
commitadaf64a99b0a882249e35768c3f4fe3de104cbb2 (patch)
tree89b3becb5f9197f3d453355262549ea8bf08b9d2
parent63227d434cefaa9faeb14afe28ebeb9b2d449ee2 (diff)
parent9936da6b546d1ce643eca21ac76c6e7d568de1c2 (diff)
* Merge the multiple-outputs-sandbox branch (svn merge --reintegrate
  ^/nix/branches/multiple-outputs-sandbox).  Multiple output support
  still isn't complete, but it wasn't complete in the trunk either, so
  it doesn't hurt.

-rw-r--r--.gitignore27
-rw-r--r--configure.ac4
-rw-r--r--corepkgs/Makefile.am2
-rw-r--r--corepkgs/derivation.nix27
-rw-r--r--perl/lib/Nix/Manifest.pm1
-rw-r--r--src/libexpr/eval.cc4
-rw-r--r--src/libexpr/primops.cc49
-rw-r--r--src/libstore/build.cc7
-rw-r--r--src/libstore/gc.cc30
-rw-r--r--src/libstore/local-store.cc30
-rw-r--r--src/libstore/local-store.hh2
-rw-r--r--src/libstore/misc.cc36
-rw-r--r--src/libstore/remote-store.cc10
-rw-r--r--src/libstore/remote-store.hh2
-rw-r--r--src/libstore/store-api.hh7
-rw-r--r--src/libstore/worker-protocol.hh1
-rw-r--r--src/nix-store/nix-store.cc106
-rw-r--r--src/nix-worker/nix-worker.cc10
-rw-r--r--tests/Makefile.am4
-rw-r--r--tests/multiple-outputs.nix67
-rw-r--r--tests/multiple-outputs.sh42
21 files changed, 343 insertions, 125 deletions
diff --git a/.gitignore b/.gitignore
index d4e8d17de8cf..5e1561829379 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,23 +61,11 @@
 # /externals/
 /externals/Makefile
 /externals/Makefile.in
-/externals/aterm-*
-/externals/have-aterm
-/externals/build-aterm
-/externals/inst-aterm
 /externals/bzip2-*
-/externals/have-bzip2
 /externals/build-bzip2
 /externals/inst-bzip2
-
-# /make/examples/aterm/
-/make/examples/aterm/result*
-
-# /make/examples/aterm/aterm/
-/make/examples/aterm/aterm/*
-
-# /make/examples/aterm/test/
-/make/examples/aterm/test/*
+/externals/sqlite-*
+/externals/build-sqlite
 
 # /misc/
 /misc/Makefile.in
@@ -100,13 +88,16 @@
 /scripts/nix-channel
 /scripts/nix-build
 /scripts/nix-copy-closure
-/scripts/readmanifest.pm
-/scripts/readconfig.pm
+/scripts/nix-generate-patches
+/scripts/NixConfig.pm
+/scripts/NixManifest.pm
+/scripts/GeneratePatches.pm
 /scripts/download-using-manifests.pl
 /scripts/copy-from-other-stores.pl
-/scripts/generate-patches.pl
 /scripts/find-runtime-roots.pl
 /scripts/build-remote.pl
+/scripts/nix-reduce-build
+/scripts/nix-http-export.cgi
 
 # /src/
 /src/Makefile
@@ -168,6 +159,7 @@
 /src/libstore/derivations-ast.cc
 /src/libstore/derivations-ast.hh
 /src/libstore/.libs
+/src/libstore/schema.sql.hh
 
 # /src/libutil/
 /src/libutil/Makefile
@@ -242,6 +234,7 @@
 /tests/config.nix
 /tests/common.sh
 /tests/dummy
+/tests/result*
 
 # /tests/lang/
 /tests/lang/*.out
diff --git a/configure.ac b/configure.ac
index 52c92b76bf10..1e8665992c6b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -274,8 +274,8 @@ AC_SUBST(sqlite_bin)
 # Whether to use the Boehm garbage collector.
 AC_ARG_ENABLE(gc, AC_HELP_STRING([--enable-gc],
   [enable garbage collection in the Nix expression evaluator (requires Boehm GC)]),
-  gc=$enableval, gc=)
-if test -n "$gc"; then
+  gc=$enableval, gc=no)
+if test "$gc" = yes; then
   PKG_CHECK_MODULES([BDW_GC], [bdw-gc])
   CXXFLAGS="$BDW_GC_CFLAGS $CXXFLAGS"
   AC_DEFINE(HAVE_BOEHMGC, 1, [Whether to use the Boehm garbage collector.])
diff --git a/corepkgs/Makefile.am b/corepkgs/Makefile.am
index 86d7027ed06e..a8de601657b1 100644
--- a/corepkgs/Makefile.am
+++ b/corepkgs/Makefile.am
@@ -1,6 +1,6 @@
 all-local: config.nix
 
-files = nar.nix buildenv.nix buildenv.pl unpack-channel.nix unpack-channel.sh
+files = nar.nix buildenv.nix buildenv.pl unpack-channel.nix unpack-channel.sh derivation.nix
 
 install-exec-local:
 	$(INSTALL) -d $(DESTDIR)$(datadir)/nix/corepkgs
diff --git a/corepkgs/derivation.nix b/corepkgs/derivation.nix
new file mode 100644
index 000000000000..757108be3614
--- /dev/null
+++ b/corepkgs/derivation.nix
@@ -0,0 +1,27 @@
+/* This is the implementation of the ‘derivation’ builtin function.
+   It's actually a wrapper around the ‘derivationStrict’ primop. */
+
+drvAttrs @ { outputs ? [ "out" ], ... }:
+
+let
+
+  strict = derivationStrict drvAttrs;
+  
+  commonAttrs = drvAttrs // (builtins.listToAttrs outputsList) //
+    { all = map (x: x.value) outputsList;
+      inherit drvAttrs;
+    };
+
+  outputToAttrListElement = outputName:
+    { name = outputName;
+      value = commonAttrs // {
+        outPath = builtins.getAttr outputName strict;
+        drvPath = strict.drvPath;
+        type = "derivation";
+        inherit outputName;
+      };
+    };
+    
+  outputsList = map outputToAttrListElement outputs;
+    
+in (builtins.head outputsList).value
diff --git a/perl/lib/Nix/Manifest.pm b/perl/lib/Nix/Manifest.pm
index d1717a0a8ebd..909604a44938 100644
--- a/perl/lib/Nix/Manifest.pm
+++ b/perl/lib/Nix/Manifest.pm
@@ -307,6 +307,7 @@ EOF
     
     for my $manifestLink (glob "$manifestDir/*.nixmanifest") {
         my $manifest = Cwd::abs_path($manifestLink);
+        next unless -f $manifest;
         my $timestamp = lstat($manifest)->mtime;
         $seen{$manifest} = 1;
 
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 2b97b76fb776..bba14bc35491 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -148,8 +148,6 @@ EvalState::EvalState()
     nrAttrsets = nrOpUpdates = nrOpUpdateValuesCopied = 0;
     deepestStack = (char *) -1;
 
-    createBaseEnv();
-    
     allowUnsafeEquality = getEnv("NIX_NO_UNSAFE_EQ", "") == "";
 
 #if HAVE_BOEHMGC
@@ -188,6 +186,8 @@ EvalState::EvalState()
     foreach (Strings::iterator, i, paths) addToSearchPath(*i);
     addToSearchPath("nix=" + nixDataDir + "/nix/corepkgs");
     searchPathInsertionPoint = searchPath.begin();
+
+    createBaseEnv();
 }
 
 
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 66173cdaf05f..21c9fb351d98 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -356,27 +356,31 @@ static void prim_derivationStrict(EvalState & state, Value * * args, Value & v)
            inputs to ensure that they are available when the builder
            runs. */
         if (path.at(0) == '=') {
-            path = string(path, 1);
-            PathSet refs; computeFSClosure(*store, path, refs);
+            /* !!! This doesn't work if readOnlyMode is set. */
+            PathSet refs; computeFSClosure(*store, string(path, 1), refs);
             foreach (PathSet::iterator, j, refs) {
                 drv.inputSrcs.insert(*j);
                 if (isDerivation(*j))
-                    drv.inputDrvs[*j] = singleton<StringSet>("out");
+                    drv.inputDrvs[*j] = store->queryDerivationOutputNames(*j);
             }
         }
 
         /* See prim_unsafeDiscardOutputDependency. */
-        bool useDrvAsSrc = false;
-        if (path.at(0) == '~') {
-            path = string(path, 1);
-            useDrvAsSrc = true;
+        else if (path.at(0) == '~')
+            drv.inputSrcs.insert(string(path, 1));
+
+        /* Handle derivation outputs of the form ‘!<name>!<path>’. */
+        else if (path.at(0) == '!') {
+            size_t index = path.find("!", 1);
+            drv.inputDrvs[string(path, index + 1)].insert(string(path, 1, index - 1));
         }
 
-        assert(isStorePath(path));
+        /* Handle derivation contexts returned by
+           ‘builtins.storePath’. */
+        else if (isDerivation(path))
+            drv.inputDrvs[path] = store->queryDerivationOutputNames(path);
 
-        debug(format("derivation uses `%1%'") % path);
-        if (!useDrvAsSrc && isDerivation(path))
-            drv.inputDrvs[path] = singleton<StringSet>("out");
+        /* Otherwise it's a source file. */
         else
             drv.inputSrcs.insert(path);
     }
@@ -447,10 +451,8 @@ static void prim_derivationStrict(EvalState & state, Value * * args, Value & v)
     state.mkAttrs(v, 1 + drv.outputs.size());
     mkString(*state.allocAttr(v, state.sDrvPath), drvPath, singleton<PathSet>("=" + drvPath));
     foreach (DerivationOutputs::iterator, i, drv.outputs) {
-        /* The output path of an output X is ‘<X>Path’,
-           e.g. ‘outPath’. */
-        mkString(*state.allocAttr(v, state.symbols.create(i->first + "Path")),
-            i->second.path, singleton<PathSet>(drvPath));
+        mkString(*state.allocAttr(v, state.symbols.create(i->first)),
+            i->second.path, singleton<PathSet>("!" + i->first + "!" + drvPath));
     }
     v.attrs->sort();
 }
@@ -1042,15 +1044,6 @@ void EvalState::createBaseEnv()
     addPrimOp("__getEnv", 1, prim_getEnv);
     addPrimOp("__trace", 2, prim_trace);
 
-    // Derivations
-    addPrimOp("derivationStrict", 1, prim_derivationStrict);
-
-    /* Add a wrapper around the derivation primop that computes the
-       `drvPath' and `outPath' attributes lazily. */
-    string s = "attrs: let res = derivationStrict attrs; in attrs // { drvPath = res.drvPath; outPath = res.outPath; type = \"derivation\"; }";
-    mkThunk_(v, parseExprFromString(s, "/"));
-    addConstant("derivation", v);
-
     // Paths
     addPrimOp("__toPath", 1, prim_toPath);
     addPrimOp("__storePath", 1, prim_storePath);
@@ -1099,6 +1092,14 @@ void EvalState::createBaseEnv()
     addPrimOp("__parseDrvName", 1, prim_parseDrvName);
     addPrimOp("__compareVersions", 2, prim_compareVersions);
 
+    // Derivations
+    addPrimOp("derivationStrict", 1, prim_derivationStrict);
+
+    /* Add a wrapper around the derivation primop that computes the
+       `drvPath' and `outPath' attributes lazily. */
+    mkThunk_(v, parseExprFromFile(findFile("nix/derivation.nix")));
+    addConstant("derivation", v);
+
     /* Now that we've added all primops, sort the `builtins' attribute
        set, because attribute lookups expect it to be sorted. */
     baseEnv.values[0]->attrs->sort();
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 149cd8b09783..d8f8826e1990 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -278,10 +278,6 @@ public:
 };
 
 
-MakeError(SubstError, Error)
-MakeError(BuildError, Error) /* denotes a permanent build failure */
-
-
 //////////////////////////////////////////////////////////////////////
 
 
@@ -1982,7 +1978,8 @@ void DerivationGoal::computeClosure()
     }
 
     /* Register each output path as valid, and register the sets of
-       paths referenced by each of them. */
+       paths referenced by each of them.  If there are cycles in the
+       outputs, this will fail. */
     ValidPathInfos infos;
     foreach (DerivationOutputs::iterator, i, drv.outputs) {
         ValidPathInfo info;
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index feaab573ef30..14c8ba0bfe90 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -371,36 +371,6 @@ static void addAdditionalRoots(StoreAPI & store, PathSet & roots)
 }
 
 
-static void dfsVisit(StoreAPI & store, const PathSet & paths,
-    const Path & path, PathSet & visited, Paths & sorted)
-{
-    if (visited.find(path) != visited.end()) return;
-    visited.insert(path);
-    
-    PathSet references;
-    if (store.isValidPath(path))
-        store.queryReferences(path, references);
-    
-    foreach (PathSet::iterator, i, references)
-        /* Don't traverse into paths that don't exist.  That can
-           happen due to substitutes for non-existent paths. */
-        if (*i != path && paths.find(*i) != paths.end())
-            dfsVisit(store, paths, *i, visited, sorted);
-
-    sorted.push_front(path);
-}
-
-
-Paths topoSortPaths(StoreAPI & store, const PathSet & paths)
-{
-    Paths sorted;
-    PathSet visited;
-    foreach (PathSet::const_iterator, i, paths)
-        dfsVisit(store, paths, *i, visited, sorted);
-    return sorted;
-}
-
-
 struct GCLimitReached { };
 
 
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index cf0e2ad1b13b..771776f6a2c7 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -820,6 +820,28 @@ PathSet LocalStore::queryDerivationOutputs(const Path & path)
 }
 
 
+StringSet LocalStore::queryDerivationOutputNames(const Path & path)
+{
+    SQLiteTxn txn(db);
+    
+    SQLiteStmtUse use(stmtQueryDerivationOutputs);
+    stmtQueryDerivationOutputs.bind(queryValidPathId(path));
+    
+    StringSet outputNames;
+    int r;
+    while ((r = sqlite3_step(stmtQueryDerivationOutputs)) == SQLITE_ROW) {
+        const char * s = (const char *) sqlite3_column_text(stmtQueryDerivationOutputs, 0);
+        assert(s);
+        outputNames.insert(s);
+    }
+    
+    if (r != SQLITE_DONE)
+        throwSQLiteError(db, format("error getting output names of `%1%'") % path);
+
+    return outputNames;
+}
+
+
 void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & run)
 {
     if (run.pid != -1) return;
@@ -944,12 +966,14 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
     while (1) {
         try {
             SQLiteTxn txn(db);
+            PathSet paths;
     
             foreach (ValidPathInfos::const_iterator, i, infos) {
                 assert(i->hash.type == htSHA256);
                 /* !!! Maybe the registration info should be updated if the
                    path is already valid. */
                 if (!isValidPath(i->path)) addValidPath(*i);
+                paths.insert(i->path);
             }
 
             foreach (ValidPathInfos::const_iterator, i, infos) {
@@ -958,6 +982,12 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
                     addReference(referrer, queryValidPathId(*j));
             }
 
+            /* Do a topological sort of the paths.  This will throw an
+               error if a cycle is detected and roll back the
+               transaction.  Cycles can only occur when a derivation
+               has multiple outputs. */
+            topoSortPaths(*this, paths);
+
             txn.commit();
             break;
         } catch (SQLiteBusy & e) {
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 4cb905f67231..2739c4eea69d 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -118,6 +118,8 @@ public:
     PathSet queryValidDerivers(const Path & path);
 
     PathSet queryDerivationOutputs(const Path & path);
+
+    StringSet queryDerivationOutputNames(const Path & path);
     
     PathSet querySubstitutablePaths();
     
diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc
index d4bbccd111ba..4ac0afe844b6 100644
--- a/src/libstore/misc.cc
+++ b/src/libstore/misc.cc
@@ -97,4 +97,40 @@ void queryMissing(StoreAPI & store, const PathSet & targets,
 }
 
  
+static void dfsVisit(StoreAPI & store, const PathSet & paths,
+    const Path & path, PathSet & visited, Paths & sorted,
+    PathSet & parents)
+{
+    if (parents.find(path) != parents.end())
+        throw BuildError(format("cycle detected in the references of `%1%'") % path);
+    
+    if (visited.find(path) != visited.end()) return;
+    visited.insert(path);
+    parents.insert(path);
+    
+    PathSet references;
+    if (store.isValidPath(path))
+        store.queryReferences(path, references);
+    
+    foreach (PathSet::iterator, i, references)
+        /* Don't traverse into paths that don't exist.  That can
+           happen due to substitutes for non-existent paths. */
+        if (*i != path && paths.find(*i) != paths.end())
+            dfsVisit(store, paths, *i, visited, sorted, parents);
+
+    sorted.push_front(path);
+    parents.erase(path);
+}
+
+
+Paths topoSortPaths(StoreAPI & store, const PathSet & paths)
+{
+    Paths sorted;
+    PathSet visited, parents;
+    foreach (PathSet::const_iterator, i, paths)
+        dfsVisit(store, paths, *i, visited, sorted, parents);
+    return sorted;
+}
+
+
 }
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 942c5bcf1c7c..6e9921ede79f 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -326,6 +326,16 @@ PathSet RemoteStore::queryDerivationOutputs(const Path & path)
 }
 
 
+PathSet RemoteStore::queryDerivationOutputNames(const Path & path)
+{
+    openConnection();
+    writeInt(wopQueryDerivationOutputNames, to);
+    writeString(path, to);
+    processStderr();
+    return readStrings<PathSet>(from);
+}
+
+
 Path RemoteStore::addToStore(const Path & _srcPath,
     bool recursive, HashType hashAlgo, PathFilter & filter)
 {
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 34a2d91dfcd1..c5853ef53646 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -41,6 +41,8 @@ public:
     
     PathSet queryDerivationOutputs(const Path & path);
     
+    StringSet queryDerivationOutputNames(const Path & path);
+
     bool hasSubstitutes(const Path & path);
     
     bool querySubstitutablePathInfo(const Path & path,
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index d4997c886207..61bcaf50507f 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -140,6 +140,9 @@ public:
 
     /* Query the outputs of the derivation denoted by `path'. */
     virtual PathSet queryDerivationOutputs(const Path & path) = 0;
+
+    /* Query the output names of the derivation denoted by `path'. */
+    virtual StringSet queryDerivationOutputNames(const Path & path) = 0;
     
     /* Query whether a path has substitutes. */
     virtual bool hasSubstitutes(const Path & path) = 0;
@@ -346,6 +349,10 @@ void exportPaths(StoreAPI & store, const Paths & paths,
     bool sign, Sink & sink);
 
 
+MakeError(SubstError, Error)
+MakeError(BuildError, Error) /* denotes a permanent build failure */
+
+
 }
 
 
diff --git a/src/libstore/worker-protocol.hh b/src/libstore/worker-protocol.hh
index 760d08a747ae..ef1e0993df28 100644
--- a/src/libstore/worker-protocol.hh
+++ b/src/libstore/worker-protocol.hh
@@ -39,6 +39,7 @@ typedef enum {
     wopClearFailedPaths = 25,
     wopQueryPathInfo = 26,
     wopImportPaths = 27,
+    wopQueryDerivationOutputNames = 28,
 } WorkerOp;
 
 
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index e92ccb153d12..b9f8d927a3a6 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -50,26 +50,30 @@ static Path useDeriver(Path path)
 }
 
 
-/* Realisation the given path.  For a derivation that means build it;
-   for other paths it means ensure their validity. */
-static Path realisePath(const Path & path)
+/* Realise the given path.  For a derivation that means build it; for
+   other paths it means ensure their validity. */
+static PathSet realisePath(const Path & path)
 {
     if (isDerivation(path)) {
-        PathSet paths;
-        paths.insert(path);
-        store->buildDerivations(paths);
-        Path outPath = findOutput(derivationFromPath(*store, path), "out");
-        
-        if (gcRoot == "")
-            printGCWarning();
-        else
-            outPath = addPermRoot(*store, outPath,
-                makeRootName(gcRoot, rootNr), indirectRoot);
-        
-        return outPath;
-    } else {
+        store->buildDerivations(singleton<PathSet>(path));
+        Derivation drv = derivationFromPath(*store, path);
+
+        PathSet outputs;
+        foreach (DerivationOutputs::iterator, i, drv.outputs) {
+            Path outPath = i->second.path;
+            if (gcRoot == "")
+                printGCWarning();
+            else
+                outPath = addPermRoot(*store, outPath,
+                    makeRootName(gcRoot, rootNr), indirectRoot);
+            outputs.insert(outPath);
+        }
+        return outputs;
+    }
+
+    else {
         store->ensurePath(path);
-        return path;
+        return singleton<PathSet>(path);
     }
 }
 
@@ -96,8 +100,11 @@ static void opRealise(Strings opFlags, Strings opArgs)
         if (isDerivation(*i)) drvPaths.insert(*i);
     store->buildDerivations(drvPaths);
 
-    foreach (Strings::iterator, i, opArgs)
-        cout << format("%1%\n") % realisePath(*i);
+    foreach (Strings::iterator, i, opArgs) {
+        PathSet paths = realisePath(*i);
+        foreach (PathSet::iterator, j, paths)
+            cout << format("%1%\n") % *j;
+    }
 }
 
 
@@ -157,14 +164,17 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs)
 }
 
 
-static Path maybeUseOutput(const Path & storePath, bool useOutput, bool forceRealise)
+static PathSet maybeUseOutputs(const Path & storePath, bool useOutput, bool forceRealise)
 {
     if (forceRealise) realisePath(storePath);
     if (useOutput && isDerivation(storePath)) {
         Derivation drv = derivationFromPath(*store, storePath);
-        return findOutput(drv, "out");
+        PathSet outputs;
+        foreach (DerivationOutputs::iterator, i, drv.outputs)
+            outputs.insert(i->second.path);
+        return outputs;
     }
-    else return storePath;
+    else return singleton<PathSet>(storePath);
 }
 
 
@@ -257,7 +267,8 @@ static void opQuery(Strings opFlags, Strings opArgs)
                 *i = followLinksToStorePath(*i);
                 if (forceRealise) realisePath(*i);
                 Derivation drv = derivationFromPath(*store, *i);
-                cout << format("%1%\n") % findOutput(drv, "out");
+                foreach (DerivationOutputs::iterator, j, drv.outputs)
+                    cout << format("%1%\n") % j->second.path;
             }
             break;
         }
@@ -268,11 +279,13 @@ static void opQuery(Strings opFlags, Strings opArgs)
         case qReferrersClosure: {
             PathSet paths;
             foreach (Strings::iterator, i, opArgs) {
-                Path path = maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise);
-                if (query == qRequisites) computeFSClosure(*store, path, paths, false, includeOutputs);
-                else if (query == qReferences) store->queryReferences(path, paths);
-                else if (query == qReferrers) store->queryReferrers(path, paths);
-                else if (query == qReferrersClosure) computeFSClosure(*store, path, paths, true);
+                PathSet ps = maybeUseOutputs(followLinksToStorePath(*i), useOutput, forceRealise);
+                foreach (PathSet::iterator, j, ps) {
+                    if (query == qRequisites) computeFSClosure(*store, *j, paths, false, includeOutputs);
+                    else if (query == qReferences) store->queryReferences(*j, paths);
+                    else if (query == qReferrers) store->queryReferrers(*j, paths);
+                    else if (query == qReferrersClosure) computeFSClosure(*store, *j, paths, true);
+                }
             }
             Paths sorted = topoSortPaths(*store, paths);
             for (Paths::reverse_iterator i = sorted.rbegin(); 
@@ -304,13 +317,15 @@ static void opQuery(Strings opFlags, Strings opArgs)
         case qHash:
         case qSize:
             foreach (Strings::iterator, i, opArgs) {
-                Path path = maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise);
-                ValidPathInfo info = store->queryPathInfo(path);
-                if (query == qHash) {
-                    assert(info.hash.type == htSHA256);
-                    cout << format("sha256:%1%\n") % printHash32(info.hash);
-                } else if (query == qSize) 
-                    cout << format("%1%\n") % info.narSize;
+                PathSet paths = maybeUseOutputs(followLinksToStorePath(*i), useOutput, forceRealise);
+                foreach (PathSet::iterator, j, paths) {
+                    ValidPathInfo info = store->queryPathInfo(*j);
+                    if (query == qHash) {
+                        assert(info.hash.type == htSHA256);
+                        cout << format("sha256:%1%\n") % printHash32(info.hash);
+                    } else if (query == qSize) 
+                        cout << format("%1%\n") % info.narSize;
+                }
             }
             break;
 
@@ -323,16 +338,20 @@ static void opQuery(Strings opFlags, Strings opArgs)
             
         case qGraph: {
             PathSet roots;
-            foreach (Strings::iterator, i, opArgs)
-                roots.insert(maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise));
+            foreach (Strings::iterator, i, opArgs) {
+                PathSet paths = maybeUseOutputs(followLinksToStorePath(*i), useOutput, forceRealise);
+                roots.insert(paths.begin(), paths.end());
+            }
             printDotGraph(roots);
             break;
         }
 
         case qXml: {
             PathSet roots;
-            foreach (Strings::iterator, i, opArgs)
-                roots.insert(maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise));
+            foreach (Strings::iterator, i, opArgs) {
+                PathSet paths = maybeUseOutputs(followLinksToStorePath(*i), useOutput, forceRealise);
+                roots.insert(paths.begin(), paths.end());
+            }
             printXmlGraph(roots);
             break;
         }
@@ -345,10 +364,11 @@ static void opQuery(Strings opFlags, Strings opArgs)
             
         case qRoots: {
             PathSet referrers;
-            foreach (Strings::iterator, i, opArgs)
-                computeFSClosure(*store,
-                    maybeUseOutput(followLinksToStorePath(*i), useOutput, forceRealise),
-                    referrers, true);
+            foreach (Strings::iterator, i, opArgs) {
+                PathSet paths = maybeUseOutputs(followLinksToStorePath(*i), useOutput, forceRealise);
+                foreach (PathSet::iterator, j, paths)
+                    computeFSClosure(*store, *j, referrers, true);
+            }
             Roots roots = store->findRoots();
             foreach (Roots::iterator, i, roots)
                 if (referrers.find(i->second) != referrers.end())
diff --git a/src/nix-worker/nix-worker.cc b/src/nix-worker/nix-worker.cc
index 5f57b2981d9a..f28905a24b8d 100644
--- a/src/nix-worker/nix-worker.cc
+++ b/src/nix-worker/nix-worker.cc
@@ -331,6 +331,16 @@ static void performOp(unsigned int clientVersion,
         break;
     }
 
+    case wopQueryDerivationOutputNames: {
+        Path path = readStorePath(from);
+        startWork();
+        StringSet names;
+        names = store->queryDerivationOutputNames(path);
+        stopWork();
+        writeStrings(names, to);
+        break;
+    }
+
     case wopQueryDeriver: {
         Path path = readStorePath(from);
         startWork();
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dbafd553ac82..15c103ec4660 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -8,7 +8,8 @@ TESTS = init.sh hash.sh lang.sh add.sh simple.sh dependencies.sh \
   referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \
   gc-runtime.sh install-package.sh check-refs.sh filter-source.sh \
   remote-store.sh export.sh export-graph.sh negative-caching.sh \
-  binary-patching.sh timeout.sh secure-drv-outputs.sh nix-channel.sh
+  binary-patching.sh timeout.sh secure-drv-outputs.sh nix-channel.sh \
+  multiple-outputs.sh
 
 XFAIL_TESTS =
 
@@ -35,5 +36,6 @@ EXTRA_DIST = $(TESTS) \
   binary-patching.nix \
   timeout.nix timeout.builder.sh \
   secure-drv-outputs.nix \
+  multiple-outputs.nix \
   $(wildcard lang/*.nix) $(wildcard lang/*.exp) $(wildcard lang/*.exp.xml) $(wildcard lang/*.flags) $(wildcard lang/dir*/*.nix) \
   common.sh.in
diff --git a/tests/multiple-outputs.nix b/tests/multiple-outputs.nix
new file mode 100644
index 000000000000..6add7dd6f997
--- /dev/null
+++ b/tests/multiple-outputs.nix
@@ -0,0 +1,67 @@
+with import ./config.nix;
+
+rec {
+
+  a = mkDerivation {
+    name = "multiple-outputs-a";
+    outputs = [ "first" "second" ];
+    builder = builtins.toFile "builder.sh"
+      ''
+        mkdir $first $second
+        test -z $all
+        echo "second" > $first/file
+        echo "first" > $second/file
+      '';
+    helloString = "Hello, world!";
+  };
+
+  b = mkDerivation {
+    defaultOutput = assert a.second.helloString == "Hello, world!"; a;
+    firstOutput = assert a.outputName == "first"; a.first.first;
+    secondOutput = assert a.second.outputName == "second"; a.second.first.first.second.second.first.second;
+    allOutputs = a.all;
+    name = "multiple-outputs-b";
+    builder = builtins.toFile "builder.sh"
+      ''
+        mkdir $out
+        test "$firstOutput $secondOutput" = "$allOutputs"
+        test "$defaultOutput" = "$firstOutput"
+        test "$(cat $firstOutput/file)" = "second"
+        test "$(cat $secondOutput/file)" = "first"
+        echo "success" > $out/file
+      '';
+  };
+
+  c = mkDerivation {
+    name = "multiple-outputs-c";
+    drv = b.drvPath;
+    builder = builtins.toFile "builder.sh"
+      ''
+        mkdir $out
+        ln -s $drv $out/drv
+      '';
+  };
+
+  d = mkDerivation {
+    name = "multiple-outputs-d";
+    drv = builtins.unsafeDiscardOutputDependency b.drvPath;
+    builder = builtins.toFile "builder.sh"
+      ''
+        mkdir $out
+        echo $drv > $out/drv
+      '';
+  };
+
+  cyclic = (mkDerivation {
+    name = "cyclic-outputs";
+    outputs = [ "a" "b" "c" ];
+    builder = builtins.toFile "builder.sh"
+      ''
+        mkdir $a $b $c
+        echo $a > $b/foo
+        echo $b > $c/bar
+        echo $c > $a/baz
+      '';
+  }).a;
+
+}
diff --git a/tests/multiple-outputs.sh b/tests/multiple-outputs.sh
new file mode 100644
index 000000000000..20f3380a10c8
--- /dev/null
+++ b/tests/multiple-outputs.sh
@@ -0,0 +1,42 @@
+source common.sh
+
+clearStore
+
+# Test whether read-only evaluation works when referring to the
+# ‘drvPath’ attribute.
+echo "evaluating c..."
+#drvPath=$(nix-instantiate multiple-outputs.nix -A c --readonly-mode)
+
+# And check whether the resulting derivation explicitly depends on all
+# outputs.
+drvPath=$(nix-instantiate multiple-outputs.nix -A c)
+#[ "$drvPath" = "$drvPath2" ]
+grep -q 'multiple-outputs-a.drv",\["first","second"\]' $drvPath
+grep -q 'multiple-outputs-b.drv",\["out"\]' $drvPath
+
+# While we're at it, test the ‘unsafeDiscardOutputDependency’ primop.
+outPath=$(nix-build multiple-outputs.nix -A d)
+drvPath=$(cat $outPath/drv)
+outPath=$(nix-store -q $drvPath)
+! [ -e "$outPath" ]
+
+# Do a build of something that depends on a derivation with multiple
+# outputs.
+echo "building b..."
+outPath=$(nix-build multiple-outputs.nix -A b)
+echo "output path is $outPath"
+[ "$(cat "$outPath"/file)" = "success" ]
+
+# Make sure that nix-build works on derivations with multiple outputs.
+echo "building a.first..."
+nix-build multiple-outputs.nix -A a.first
+
+# Cyclic outputs should be rejected.
+echo "building cyclic..."
+if nix-build multiple-outputs.nix -A cyclic; then
+    echo "Cyclic outputs incorrectly accepted!"
+    exit 1
+fi
+
+echo "collecting garbage..."
+nix-store --gc