* 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.
This commit is contained in:
		
						commit
						adaf64a99b
					
				
					 21 changed files with 343 additions and 125 deletions
				
			
		
							
								
								
									
										27
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -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 | ||||
|  |  | |||
|  | @ -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.]) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										27
									
								
								corepkgs/derivation.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								corepkgs/derivation.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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 { }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -119,6 +119,8 @@ public: | |||
| 
 | ||||
|     PathSet queryDerivationOutputs(const Path & path); | ||||
| 
 | ||||
|     StringSet queryDerivationOutputNames(const Path & path); | ||||
|      | ||||
|     PathSet querySubstitutablePaths(); | ||||
|      | ||||
|     bool hasSubstitutes(const Path & path); | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| { | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -141,6 +141,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 */ | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,6 +39,7 @@ typedef enum { | |||
|     wopClearFailedPaths = 25, | ||||
|     wopQueryPathInfo = 26, | ||||
|     wopImportPaths = 27, | ||||
|     wopQueryDerivationOutputNames = 28, | ||||
| } WorkerOp; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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"); | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
|         return outPath; | ||||
|     } else { | ||||
|     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,14 +317,16 @@ 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); | ||||
|                 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; | ||||
| 
 | ||||
|         case qTree: { | ||||
|  | @ -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()) | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										67
									
								
								tests/multiple-outputs.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								tests/multiple-outputs.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										42
									
								
								tests/multiple-outputs.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								tests/multiple-outputs.sh
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue