* 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:
Eelco Dolstra 2012-01-04 16:22:25 +00:00
commit adaf64a99b
21 changed files with 343 additions and 125 deletions

27
.gitignore vendored
View file

@ -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

View file

@ -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.])

View file

@ -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
View 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

View file

@ -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;

View file

@ -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();
}

View file

@ -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();

View file

@ -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;

View file

@ -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 { };

View file

@ -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) {

View file

@ -119,6 +119,8 @@ public:
PathSet queryDerivationOutputs(const Path & path);
StringSet queryDerivationOutputNames(const Path & path);
PathSet querySubstitutablePaths();
bool hasSubstitutes(const Path & path);

View file

@ -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;
}
}

View file

@ -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)
{

View file

@ -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,

View file

@ -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 */
}

View file

@ -39,6 +39,7 @@ typedef enum {
wopClearFailedPaths = 25,
wopQueryPathInfo = 26,
wopImportPaths = 27,
wopQueryDerivationOutputNames = 28,
} WorkerOp;

View file

@ -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);
if (gcRoot == "")
printGCWarning();
else
outPath = addPermRoot(*store, outPath,
makeRootName(gcRoot, rootNr), indirectRoot);
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,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())

View file

@ -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();

View file

@ -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

View 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
View 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