diff options
34 files changed, 865 insertions, 246 deletions
diff --git a/doc/manual/expressions/language-constructs.xml b/doc/manual/expressions/language-constructs.xml index 7535e64ea9a5..fe69dba837a1 100644 --- a/doc/manual/expressions/language-constructs.xml +++ b/doc/manual/expressions/language-constructs.xml @@ -202,24 +202,6 @@ in concat { x = "foo"; y = "bar"; }</programlisting> </para> -<para>A set that has a <literal>__functor</literal> attribute whose value -is callable (i.e. is itself a function or a set with a -<literal>__functor</literal> attribute whose value is callable) can be -applied as if it were a function, with the set itself passed in first -, e.g., - -<programlisting> -let add = { __functor = self: x: x + self.x; }; - inc = add // { x = 1; }; -in inc 1 -</programlisting> - -evaluates to <literal>2</literal>. This can be used to attach metadata to a -function without the caller needing to treat it specially, or to implement -a form of object-oriented programming, for example. - -</para> - </simplesect> diff --git a/doc/manual/expressions/language-values.xml b/doc/manual/expressions/language-values.xml index f1174ecb5d8d..b90baac5054c 100644 --- a/doc/manual/expressions/language-values.xml +++ b/doc/manual/expressions/language-values.xml @@ -276,6 +276,23 @@ added to the set: This will evaluate to <literal>{}</literal> if <literal>foo</literal> evaluates to <literal>false</literal>.</para> +<para>A set that has a <literal>__functor</literal> attribute whose value +is callable (i.e. is itself a function or a set with a +<literal>__functor</literal> attribute whose value is callable) can be +applied as if it were a function, with the set itself passed in first +, e.g., + +<programlisting> +let add = { __functor = self: x: x + self.x; }; + inc = add // { x = 1; }; +in inc 1 +</programlisting> + +evaluates to <literal>2</literal>. This can be used to attach metadata to a +function without the caller needing to treat it specially, or to implement +a form of object-oriented programming, for example. + +</para> </simplesect> diff --git a/maintainers/upload-release.pl b/maintainers/upload-release.pl new file mode 100755 index 000000000000..5c6bb5685fcc --- /dev/null +++ b/maintainers/upload-release.pl @@ -0,0 +1,117 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i perl -p perl perlPackages.LWPUserAgent perlPackages.LWPProtocolHttps perlPackages.FileSlurp gnupg1 + +use strict; +use Data::Dumper; +use File::Basename; +use File::Path; +use File::Slurp; +use JSON::PP; +use LWP::UserAgent; + +my $evalId = $ARGV[0] or die "Usage: $0 EVAL-ID\n"; + +my $releasesDir = "/home/eelco/mnt/releases"; + +# FIXME: cut&paste from nixos-channel-scripts. +sub fetch { + my ($url, $type) = @_; + + my $ua = LWP::UserAgent->new; + $ua->default_header('Accept', $type) if defined $type; + + my $response = $ua->get($url); + die "could not download $url: ", $response->status_line, "\n" unless $response->is_success; + + return $response->decoded_content; +} + +my $evalUrl = "https://hydra.nixos.org/eval/$evalId"; +my $evalInfo = decode_json(fetch($evalUrl, 'application/json')); +#print Dumper($evalInfo); + +my $nixRev = $evalInfo->{jobsetevalinputs}->{nix}->{revision} or die; + +my $tarballInfo = decode_json(fetch("$evalUrl/job/tarball", 'application/json')); + +my $releaseName = $tarballInfo->{releasename}; +$releaseName =~ /nix-(.*)$/ or die; +my $version = $1; + +print STDERR "Nix revision is $nixRev, version is $version\n"; + +File::Path::make_path($releasesDir); +if (system("mountpoint -q $releasesDir") != 0) { + system("sshfs hydra-mirror:/releases $releasesDir") == 0 or die; +} + +my $releaseDir = "$releasesDir/nix/$releaseName"; +File::Path::make_path($releaseDir); + +sub downloadFile { + my ($jobName, $productNr, $dstName) = @_; + + my $buildInfo = decode_json(fetch("$evalUrl/job/$jobName", 'application/json')); + + my $srcFile = $buildInfo->{buildproducts}->{$productNr}->{path} or die; + $dstName //= basename($srcFile); + my $dstFile = "$releaseDir/" . $dstName; + + if (! -e $dstFile) { + print STDERR "downloading $srcFile to $dstFile...\n"; + system("NIX_REMOTE=https://cache.nixos.org/ nix cat-store '$srcFile' > '$dstFile.tmp'") == 0 + or die "unable to fetch $srcFile\n"; + rename("$dstFile.tmp", $dstFile) or die; + } + + my $sha256_expected = $buildInfo->{buildproducts}->{$productNr}->{sha256hash} or die; + my $sha256_actual = `nix hash-file --type sha256 '$dstFile'`; + chomp $sha256_actual; + if ($sha256_expected ne $sha256_actual) { + print STDERR "file $dstFile is corrupt\n"; + exit 1; + } + + write_file("$dstFile.sha256", $sha256_expected); + + return ($dstFile, $sha256_expected); +} + +downloadFile("tarball", "2"); # PDF +downloadFile("tarball", "3"); # .tar.bz2 +my ($tarball, $tarballHash) = downloadFile("tarball", "4"); # .tar.xz +my ($tarball_i686_linux, $tarball_i686_linux_hash) = downloadFile("binaryTarball.i686-linux", "1"); +my ($tarball_x86_64_linux, $tarball_x86_64_linux_hash) = downloadFile("binaryTarball.x86_64-linux", "1"); +my ($tarball_x86_64_darwin, $tarball_x86_64_darwin_hash) = downloadFile("binaryTarball.x86_64-darwin", "1"); + +# Extract the HTML manual. +File::Path::make_path("$releaseDir/manual"); + +system("tar xvf $tarball --strip-components=3 -C $releaseDir/manual --wildcards '*/doc/manual/*.html' '*/doc/manual/*.css' '*/doc/manual/*.gif' '*/doc/manual/*.png'") == 0 or die; + +if (! -e "$releaseDir/manual/index.html") { + symlink("manual.html", "$releaseDir/manual/index.html") or die; +} + +# Update the "latest" symlink. +symlink("$releaseName", "$releasesDir/nix/latest-tmp") or die; +rename("$releasesDir/nix/latest-tmp", "$releasesDir/nix/latest") or die; + +# Tag the release in Git. +chdir("/home/eelco/Dev/nix-pristine") or die; +system("git remote update origin") == 0 or die; +system("git tag --force --sign $version $nixRev -m 'Tagging release $version'") == 0 or die; + +# Update the website. +my $siteDir = "/home/eelco/Dev/nixos-homepage-pristine"; +write_file("$siteDir/nix-release.tt", + "[%-\n" . + "latestNixVersion = \"$version\"\n" . + "nix_hash_i686_linux = \"$tarball_i686_linux_hash\"\n" . + "nix_hash_x86_64_linux = \"$tarball_x86_64_linux_hash\"\n" . + "nix_hash_x86_64_darwin = \"$tarball_x86_64_darwin_hash\"\n" . + "-%]\n"); + +system("cd $siteDir && nix-shell --run 'make nix/install nix/install.sig'") == 0 or die; + +system("cd $siteDir && git commit -a -m 'Nix $version released'") == 0 or die; diff --git a/misc/launchd/org.nixos.nix-daemon.plist.in b/misc/launchd/org.nixos.nix-daemon.plist.in index 66fcd155ee9b..0dd665db635f 100644 --- a/misc/launchd/org.nixos.nix-daemon.plist.in +++ b/misc/launchd/org.nixos.nix-daemon.plist.in @@ -12,5 +12,10 @@ <string>/var/log/nix-daemon.log</string> <key>StandardOutPath</key> <string>/dev/null</string> + <key>EnvironmentVariables</key> + <dict> + <key>SSL_CERT_FILE</key> + <string>/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt</string> + </dict> </dict> </plist> diff --git a/misc/systemd/nix-daemon.service.in b/misc/systemd/nix-daemon.service.in index 5fc04a3f5713..fcd799e177d0 100644 --- a/misc/systemd/nix-daemon.service.in +++ b/misc/systemd/nix-daemon.service.in @@ -7,3 +7,4 @@ ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket [Service] ExecStart=@@bindir@/nix-daemon nix-daemon --daemon KillMode=process +Environment=XDG_CACHE_HOME=/root/.cache diff --git a/nix.spec.in b/nix.spec.in index 61da404a3844..edbc12d8f05c 100644 --- a/nix.spec.in +++ b/nix.spec.in @@ -10,7 +10,7 @@ License: LGPLv2+ Group: Applications/System %endif URL: http://nixos.org/ -Source0: %{name}-%{version}.tar.xz +Source0: %{name}-%{version}.tar.bz2 %if 0%{?el5} BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) %endif diff --git a/release.nix b/release.nix index 8b8d96a2ec55..d236cdae9bf0 100644 --- a/release.nix +++ b/release.nix @@ -172,10 +172,6 @@ let }; - rpm_fedora19i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora19i386) []; - rpm_fedora19x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora19x86_64) []; - rpm_fedora20i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora20i386) []; - rpm_fedora20x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora20x86_64) []; rpm_fedora21i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora21i386) [ "libsodium-devel" ]; rpm_fedora21x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora21x86_64) [ "libsodium-devel" ]; @@ -183,10 +179,6 @@ let deb_debian8i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.debian8i386) [ "libsodium-dev" ] [ "libsodium13" ]; deb_debian8x86_64 = makeDeb_x86_64 (diskImageFunsFun: diskImageFunsFun.debian8x86_64) [ "libsodium-dev" ] [ "libsodium13" ]; - deb_ubuntu1310i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1310i386) [] []; - deb_ubuntu1310x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1310x86_64) [] []; - deb_ubuntu1404i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1404i386) [] []; - deb_ubuntu1404x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1404x86_64) [] []; deb_ubuntu1410i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1410i386) [] []; deb_ubuntu1410x86_64 = makeDeb_x86_64 (diskImageFuns: diskImageFuns.ubuntu1410x86_64) [] []; deb_ubuntu1504i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.ubuntu1504i386) [ "libsodium-dev" ] [ "libsodium13" ]; @@ -263,12 +255,8 @@ let binaryTarball.x86_64-linux deb_debian8i386 deb_debian8x86_64 - deb_ubuntu1404i386 # LTS - deb_ubuntu1404x86_64 # LTS deb_ubuntu1504i386 deb_ubuntu1504x86_64 - rpm_fedora20i386 - rpm_fedora20x86_64 rpm_fedora21i386 rpm_fedora21x86_64 tests.remoteBuilds diff --git a/src/buildenv/local.mk b/src/buildenv/local.mk index 493acfadfb45..17ec13b235f4 100644 --- a/src/buildenv/local.mk +++ b/src/buildenv/local.mk @@ -4,6 +4,6 @@ buildenv_DIR := $(d) buildenv_INSTALL_DIR := $(libexecdir)/nix -buildenv_LIBS = libmain libutil libformat +buildenv_LIBS = libmain libstore libutil libformat buildenv_SOURCES := $(d)/buildenv.cc diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0833603b2a9e..64f3874db614 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -293,6 +293,8 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store) , sColumn(symbols.create("column")) , sFunctor(symbols.create("__functor")) , sToString(symbols.create("__toString")) + , sRight(symbols.create("right")) + , sWrong(symbols.create("wrong")) , store(store) , baseEnv(allocEnv(128)) , staticBaseEnv(false, 0) @@ -379,9 +381,9 @@ void EvalState::addPrimOp(const string & name, } -void EvalState::getBuiltin(const string & name, Value & v) +Value & EvalState::getBuiltin(const string & name) { - v = *baseEnv.values[0]->attrs->find(symbols.create(name))->value; + return *baseEnv.values[0]->attrs->find(symbols.create(name))->value; } @@ -462,7 +464,7 @@ void mkString(Value & v, const char * s) } -void mkString(Value & v, const string & s, const PathSet & context) +Value & mkString(Value & v, const string & s, const PathSet & context) { mkString(v, s.c_str()); if (!context.empty()) { @@ -473,6 +475,7 @@ void mkString(Value & v, const string & s, const PathSet & context) v.string.context[n++] = dupString(i.c_str()); v.string.context[n] = 0; } + return v; } @@ -993,11 +996,18 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po if (fun.type == tAttrs) { auto found = fun.attrs->find(sFunctor); if (found != fun.attrs->end()) { + /* fun may be allocated on the stack of the calling function, + * but for functors we may keep a reference, so heap-allocate + * a copy and use that instead. + */ + auto & fun2 = *allocValue(); + fun2 = fun; + /* !!! Should we use the attr pos here? */ forceValue(*found->value, pos); - Value * v2 = allocValue(); - callFunction(*found->value, fun, *v2, pos); - forceValue(*v2, pos); - return callFunction(*v2, arg, v, pos); + Value v2; + callFunction(*found->value, fun2, v2, pos); + forceValue(v2, pos); + return callFunction(v2, arg, v, pos); } } @@ -1368,11 +1378,11 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos) } -bool EvalState::forceBool(Value & v) +bool EvalState::forceBool(Value & v, const Pos & pos) { forceValue(v); if (v.type != tBool) - throwTypeError("value is %1% while a Boolean was expected", v); + throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos); return v.boolean; } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 80e369f2d68f..195cb0db3acc 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -8,10 +8,6 @@ #include <map> -#if HAVE_BOEHMGC -#include <gc/gc_allocator.h> -#endif - namespace nix { @@ -43,7 +39,7 @@ struct Env }; -void mkString(Value & v, const string & s, const PathSet & context = PathSet()); +Value & mkString(Value & v, const string & s, const PathSet & context = PathSet()); void copyContext(const Value & v, PathSet & context); @@ -71,7 +67,8 @@ public: const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, - sFile, sLine, sColumn, sFunctor, sToString; + sFile, sLine, sColumn, sFunctor, sToString, + sRight, sWrong; Symbol sDerivationNix; /* If set, force copying files to the Nix store even if they @@ -108,6 +105,8 @@ public: void addToSearchPath(const string & s); + SearchPath getSearchPath() { return searchPath; } + Path checkSourcePath(const Path & path); /* Parse a Nix expression from the specified file. */ @@ -154,7 +153,7 @@ public: /* Force `v', and then verify that it has the expected type. */ NixInt forceInt(Value & v, const Pos & pos); NixFloat forceFloat(Value & v, const Pos & pos); - bool forceBool(Value & v); + bool forceBool(Value & v, const Pos & pos); inline void forceAttrs(Value & v); inline void forceAttrs(Value & v, const Pos & pos); inline void forceList(Value & v); @@ -204,7 +203,7 @@ private: public: - void getBuiltin(const string & name, Value & v); + Value & getBuiltin(const string & name); private: diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index b06c539de0fb..dc5def911ca0 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -301,7 +301,7 @@ static void getDerivations(EvalState & state, Value & vIn, `recurseForDerivations = true' attribute. */ if (v2.type == tAttrs) { Bindings::iterator j = v2.attrs->find(state.symbols.create("recurseForDerivations")); - if (j != v2.attrs->end() && state.forceBool(*j->value)) + if (j != v2.attrs->end() && state.forceBool(*j->value, *j->pos)) getDerivations(state, v2, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); } } diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc index 1daf84600dca..f671802bcc24 100644 --- a/src/libexpr/json-to-value.cc +++ b/src/libexpr/json-to-value.cc @@ -12,15 +12,6 @@ static void skipWhitespace(const char * & s) } -#if HAVE_BOEHMGC -typedef std::vector<Value *, gc_allocator<Value *> > ValueVector; -typedef std::map<Symbol, Value *, std::less<Symbol>, gc_allocator<Value *> > ValueMap; -#else -typedef std::vector<Value *> ValueVector; -typedef std::map<Symbol, Value *> ValueMap; -#endif - - static string parseJSONString(const char * & s) { string res; diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index c456e9b96a53..3b965f209bb2 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -477,7 +477,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * bool ignoreNulls = false; attr = args[0]->attrs->find(state.sIgnoreNulls); if (attr != args[0]->attrs->end()) - ignoreNulls = state.forceBool(*attr->value); + ignoreNulls = state.forceBool(*attr->value, pos); /* Build the derivation expression by processing the attributes. */ Derivation drv; @@ -673,6 +673,19 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * * } +/* Return a placeholder string for the specified output that will be + substituted by the corresponding output path at build time. For + example, ‘placeholder "out"’ returns the string + /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9. At build + time, any occurence of this string in an derivation attribute will + be replaced with the concrete path in the Nix store of the output + ‘out’. */ +static void prim_placeholder(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + mkString(v, hashPlaceholder(state.forceStringNoCtx(*args[0], pos))); +} + + /************************************************************* * Paths *************************************************************/ @@ -912,9 +925,10 @@ struct FilterFromExpr : PathFilter { EvalState & state; Value & filter; + Pos pos; - FilterFromExpr(EvalState & state, Value & filter) - : state(state), filter(filter) + FilterFromExpr(EvalState & state, Value & filter, const Pos & pos) + : state(state), filter(filter), pos(pos) { } @@ -942,7 +956,7 @@ struct FilterFromExpr : PathFilter Value res; state.callFunction(fun2, arg2, res, noPos); - return state.forceBool(res); + return state.forceBool(res, pos); } }; @@ -958,7 +972,7 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args if (args[0]->type != tLambda) throw TypeError(format("first argument in call to ‘filterSource’ is not a function but %1%, at %2%") % showType(*args[0]) % pos); - FilterFromExpr filter(state, *args[0]); + FilterFromExpr filter(state, *args[0], pos); path = state.checkSourcePath(path); @@ -1278,7 +1292,7 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu for (unsigned int n = 0; n < args[1]->listSize(); ++n) { Value res; state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos); - if (state.forceBool(res)) + if (state.forceBool(res, pos)) vs[k++] = args[1]->listElems()[n]; else same = false; @@ -1354,7 +1368,7 @@ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * arg Value vTmp; for (unsigned int n = 0; n < args[1]->listSize(); ++n) { state.callFunction(*args[0], *args[1]->listElems()[n], vTmp, pos); - bool res = state.forceBool(vTmp); + bool res = state.forceBool(vTmp, pos); if (res == any) { mkBool(v, any); return; @@ -1420,7 +1434,7 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value Value vTmp1, vTmp2; state.callFunction(*args[0], *a, vTmp1, pos); state.callFunction(vTmp1, *b, vTmp2, pos); - return state.forceBool(vTmp2); + return state.forceBool(vTmp2, pos); }; /* FIXME: std::sort can segfault if the comparator is not a strict @@ -1430,6 +1444,40 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value } +static void prim_partition(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + state.forceFunction(*args[0], pos); + state.forceList(*args[1], pos); + + auto len = args[1]->listSize(); + + ValueVector right, wrong; + + for (unsigned int n = 0; n < len; ++n) { + auto vElem = args[1]->listElems()[n]; + state.forceValue(*vElem); + Value res; + state.callFunction(*args[0], *vElem, res, pos); + if (state.forceBool(res, pos)) + right.push_back(vElem); + else + wrong.push_back(vElem); + } + + state.mkAttrs(v, 2); + + Value * vRight = state.allocAttr(v, state.sRight); + state.mkList(*vRight, right.size()); + memcpy(vRight->listElems(), right.data(), sizeof(Value *) * right.size()); + + Value * vWrong = state.allocAttr(v, state.sWrong); + state.mkList(*vWrong, wrong.size()); + memcpy(vWrong->listElems(), wrong.data(), sizeof(Value *) * wrong.size()); + + v.attrs->sort(); +} + + /************************************************************* * Integer arithmetic *************************************************************/ @@ -1620,13 +1668,18 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar if (args[0]->listSize() != args[1]->listSize()) throw EvalError(format("‘from’ and ‘to’ arguments to ‘replaceStrings’ have different lengths, at %1%") % pos); - Strings from; + vector<string> from; + from.reserve(args[0]->listSize()); for (unsigned int n = 0; n < args[0]->listSize(); ++n) - from.push_back(state.forceStringNoCtx(*args[0]->listElems()[n], pos)); + from.push_back(state.forceString(*args[0]->listElems()[n], pos)); - Strings to; - for (unsigned int n = 0; n < args[1]->listSize(); ++n) - to.push_back(state.forceStringNoCtx(*args[1]->listElems()[n], pos)); + vector<std::pair<string, PathSet>> to; + to.reserve(args[1]->listSize()); + for (unsigned int n = 0; n < args[1]->listSize(); ++n) { + PathSet ctx; + auto s = state.forceString(*args[1]->listElems()[n], ctx, pos); + to.push_back(std::make_pair(std::move(s), std::move(ctx))); + } PathSet context; auto s = state.forceString(*args[2], context, pos); @@ -1634,11 +1687,16 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar string res; for (size_t p = 0; p < s.size(); ) { bool found = false; - for (auto i = from.begin(), j = to.begin(); i != from.end(); ++i, ++j) + auto i = from.begin(); + auto j = to.begin(); + for (; i != from.end(); ++i, ++j) if (s.compare(p, i->size(), *i) == 0) { found = true; p += i->size(); - res += *j; + res += j->first; + for (auto& path : j->second) + context.insert(path); + j->second.clear(); break; } if (!found) res += s[p++]; @@ -1682,6 +1740,7 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, { string url; Hash expectedHash; + string name; state.forceValue(*args[0]); @@ -1690,11 +1749,13 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, state.forceAttrs(*args[0], pos); for (auto & attr : *args[0]->attrs) { - string name(attr.name); - if (name == "url") + string n(attr.name); + if (n == "url") url = state.forceStringNoCtx(*attr.value, *attr.pos); - else if (name == "sha256") + else if (n == "sha256") expectedHash = parseHash16or32(htSHA256, state.forceStringNoCtx(*attr.value, *attr.pos)); + else if (n == "name") + name = state.forceStringNoCtx(*attr.value, *attr.pos); else throw EvalError(format("unsupported argument ‘%1%’ to ‘%2%’, at %3%") % attr.name % who % attr.pos); } @@ -1708,7 +1769,7 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, if (state.restricted && !expectedHash) throw Error(format("‘%1%’ is not allowed in restricted mode") % who); - Path res = makeDownloader()->downloadCached(state.store, url, unpack, expectedHash); + Path res = makeDownloader()->downloadCached(state.store, url, unpack, name, expectedHash); mkString(v, res, PathSet({res})); } @@ -1855,6 +1916,7 @@ void EvalState::createBaseEnv() addPrimOp("__all", 2, prim_all); addPrimOp("__genList", 2, prim_genList); addPrimOp("__sort", 2, prim_sort); + addPrimOp("__partition", 2, prim_partition); // Integer arithmetic addPrimOp("__add", 2, prim_add); @@ -1880,6 +1942,7 @@ void EvalState::createBaseEnv() // Derivations addPrimOp("derivationStrict", 1, prim_derivationStrict); + addPrimOp("placeholder", 1, prim_placeholder); // Networking addPrimOp("__fetchurl", 1, prim_fetchurl); diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 47ee324a6e4f..72e413e4491e 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -1,4 +1,5 @@ #include "value-to-json.hh" +#include "json.hh" #include "eval-inline.hh" #include "util.hh" @@ -8,24 +9,8 @@ namespace nix { - -void escapeJSON(std::ostream & str, const string & s) -{ - str << "\""; - for (auto & i : s) - if (i == '\"' || i == '\\') str << "\\" << i; - else if (i == '\n') str << "\\n"; - else if (i == '\r') str << "\\r"; - else if (i == '\t') str << "\\t"; - else if (i >= 0 && i < 32) - str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) i << std::dec; - else str << i; - str << "\""; -} - - void printValueAsJSON(EvalState & state, bool strict, - Value & v, std::ostream & str, PathSet & context) + Value & v, JSONPlaceholder & out, PathSet & context) { checkInterrupt(); @@ -34,58 +19,58 @@ void printValueAsJSON(EvalState & state, bool strict, switch (v.type) { case tInt: - str << v.integer; + out.write(v.integer); break; case tBool: - str << (v.boolean ? "true" : "false"); + out.write(v.boolean); break; case tString: copyContext(v, context); - escapeJSON(str, v.string.s); + out.write(v.string.s); break; case tPath: - escapeJSON(str, state.copyPathToStore(context, v.path)); + out.write(state.copyPathToStore(context, v.path)); break; case tNull: - str << "null"; + out.write(nullptr); break; case tAttrs: { Bindings::iterator i = v.attrs->find(state.sOutPath); if (i == v.attrs->end()) { - JSONObject json(str); + auto obj(out.object()); StringSet names; for (auto & j : *v.attrs) names.insert(j.name); for (auto & j : names) { Attr & a(*v.attrs->find(state.symbols.create(j))); - json.attr(j); - printValueAsJSON(state, strict, *a.value, str, context); + auto placeholder(obj.placeholder(j)); + printValueAsJSON(state, strict, *a.value, placeholder, context); } } else - printValueAsJSON(state, strict, *i->value, str, context); + printValueAsJSON(state, strict, *i->value, out, context); break; } case tList1: case tList2: case tListN: { - JSONList json(str); + auto list(out.list()); for (unsigned int n = 0; n < v.listSize(); ++n) { - json.elem(); - printValueAsJSON(state, strict, *v.listElems()[n], str, context); + auto placeholder(list.placeholder()); + printValueAsJSON(state, strict, *v.listElems()[n], placeholder, context); } break; } case tExternal: - v.external->printValueAsJSON(state, strict, str, context); + v.external->printValueAsJSON(state, strict, out, context); break; case tFloat: - str << v.fpoint; + out.write(v.fpoint); break; default: @@ -93,9 +78,15 @@ void printValueAsJSON(EvalState & state, bool strict, } } +void printValueAsJSON(EvalState & state, bool strict, + Value & v, std::ostream & str, PathSet & context) +{ + JSONPlaceholder out(str); + printValueAsJSON(state, strict, v, out, context); +} void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, - std::ostream & str, PathSet & context) const + JSONPlaceholder & out, PathSet & context) const { throw TypeError(format("cannot convert %1% to JSON") % showType()); } diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh index c59caf5641bc..67fed6487dd9 100644 --- a/src/libexpr/value-to-json.hh +++ b/src/libexpr/value-to-json.hh @@ -8,73 +8,12 @@ namespace nix { -void printValueAsJSON(EvalState & state, bool strict, - Value & v, std::ostream & out, PathSet & context); - -void escapeJSON(std::ostream & str, const string & s); +class JSONPlaceholder; -struct JSONObject -{ - std::ostream & str; - bool first; - JSONObject(std::ostream & str) : str(str), first(true) - { - str << "{"; - } - ~JSONObject() - { - str << "}"; - } - void attr(const string & s) - { - if (!first) str << ","; else first = false; - escapeJSON(str, s); - str << ":"; - } - void attr(const string & s, const string & t) - { - attr(s); - escapeJSON(str, t); - } - void attr(const string & s, const char * t) - { - attr(s); - escapeJSON(str, t); - } - void attr(const string & s, bool b) - { - attr(s); - str << (b ? "true" : "false"); - } - template<typename T> - void attr(const string & s, const T & n) - { - attr(s); - str << n; - } -}; +void printValueAsJSON(EvalState & state, bool strict, + Value & v, JSONPlaceholder & out, PathSet & context); -struct JSONList -{ - std::ostream & str; - bool first; - JSONList(std::ostream & str) : str(str), first(true) - { - str << "["; - } - ~JSONList() - { - str << "]"; - } - void elem() - { - if (!first) str << ","; else first = false; - } - void elem(const string & s) - { - elem(); - escapeJSON(str, s); - } -}; +void printValueAsJSON(EvalState & state, bool strict, + Value & v, std::ostream & str, PathSet & context); } diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 62bdd9281f08..271e6a1b24a2 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -1,7 +1,12 @@ #pragma once +#include "config.h" #include "symbol-table.hh" +#if HAVE_BOEHMGC +#include <gc/gc_allocator.h> +#endif + namespace nix { @@ -36,6 +41,7 @@ class Symbol; struct Pos; class EvalState; class XMLWriter; +class JSONPlaceholder; typedef long NixInt; @@ -73,7 +79,7 @@ class ExternalValueBase /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ virtual void printValueAsJSON(EvalState & state, bool strict, - std::ostream & str, PathSet & context) const; + JSONPlaceholder & out, PathSet & context) const; /* Print the value as XML. Defaults to unevaluated */ virtual void printValueAsXML(EvalState & state, bool strict, bool location, @@ -249,4 +255,13 @@ void mkPath(Value & v, const char * s); size_t valueSize(Value & v); +#if HAVE_BOEHMGC +typedef std::vector<Value *, gc_allocator<Value *> > ValueVector; +typedef std::map<Symbol, Value *, std::less<Symbol>, gc_allocator<Value *> > ValueMap; +#else +typedef std::vector<Value *> ValueVector; +typedef std::map<Symbol, Value *> ValueMap; +#endif + + } diff --git a/src/libstore/build.cc b/src/libstore/build.cc index cfab0b0dc32c..e0eb702a4f82 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -193,6 +193,7 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) { struct Child { WeakGoalPtr goal; + Goal * goal2; // ugly hackery set<int> fds; bool respectTimeouts; bool inBuildSlot; @@ -284,7 +285,7 @@ public: false if there is no sense in waking up goals that are sleeping because they can't run yet (e.g., there is no free build slot, or the hook would still say `postpone'). */ - void childTerminated(GoalPtr goal, bool wakeSleepers = true); + void childTerminated(Goal * goal, bool wakeSleepers = true); /* Put `goal' to sleep until a build slot becomes available (which might be right away). */ @@ -652,18 +653,15 @@ HookInstance::~HookInstance() ////////////////////////////////////////////////////////////////////// -typedef map<string, string> HashRewrites; +typedef map<std::string, std::string> StringRewrites; -string rewriteHashes(string s, const HashRewrites & rewrites) +std::string rewriteStrings(std::string s, const StringRewrites & rewrites) { for (auto & i : rewrites) { - assert(i.first.size() == i.second.size()); size_t j = 0; - while ((j = s.find(i.first, j)) != string::npos) { - debug(format("rewriting @ %1%") % j); - s.replace(j, i.second.size(), i.second); - } + while ((j = s.find(i.first, j)) != string::npos) + s.replace(j, i.first.size(), i.second); } return s; } @@ -782,7 +780,7 @@ private: #endif /* Hash rewriting. */ - HashRewrites rewritesToTmp, rewritesFromTmp; + StringRewrites inputRewrites, outputRewrites; typedef map<Path, Path> RedirectedOutputs; RedirectedOutputs redirectedOutputs; @@ -938,7 +936,7 @@ DerivationGoal::~DerivationGoal() void DerivationGoal::killChild() { if (pid != -1) { - worker.childTerminated(shared_from_this()); + worker.childTerminated(this); if (buildUser.enabled()) { /* If we're using a build user, then there is a tricky @@ -1412,7 +1410,7 @@ void DerivationGoal::buildDone() debug(format("builder process for ‘%1%’ finished") % drvPath); /* So the child is gone now. */ - worker.childTerminated(shared_from_this()); + worker.childTerminated(this); /* Close the read side of the logger pipe. */ if (hook) { @@ -1774,6 +1772,10 @@ void DerivationGoal::startBuilder() for (auto & i : varNames) env[i] = getEnv(i); } + /* Substitute output placeholders with the actual output paths. */ + for (auto & output : drv->outputs) + inputRewrites[hashPlaceholder(output.first)] = output.second.path; + /* The `exportReferencesGraph' feature allows the references graph to be passed to a builder. This attribute should be a list of pairs [name1 path1 name2 path2 ...]. The references graph of @@ -2418,7 +2420,7 @@ void DerivationGoal::runChild() /* Fill in the environment. */ Strings envStrs; for (auto & i : env) - envStrs.push_back(rewriteHashes(i.first + "=" + i.second, rewritesToTmp)); + envStrs.push_back(rewriteStrings(i.first + "=" + i.second, inputRewrites)); /* If we are running in `build-users' mode, then switch to the user we allocated above. Make sure that we drop all root @@ -2560,7 +2562,7 @@ void DerivationGoal::runChild() } for (auto & i : drv->args) - args.push_back(rewriteHashes(i, rewritesToTmp)); + args.push_back(rewriteStrings(i, inputRewrites)); restoreSIGPIPE(); @@ -2682,7 +2684,7 @@ void DerivationGoal::registerOutputs() /* Apply hash rewriting if necessary. */ bool rewritten = false; - if (!rewritesFromTmp.empty()) { + if (!outputRewrites.empty()) { printMsg(lvlError, format("warning: rewriting hashes in ‘%1%’; cross fingers") % path); /* Canonicalise first. This ensures that the path we're @@ -2694,7 +2696,7 @@ void DerivationGoal::registerOutputs() StringSink sink; dumpPath(actualPath, sink); deletePath(actualPath); - sink.s = make_ref<std::string>(rewriteHashes(*sink.s, rewritesFromTmp)); + sink.s = make_ref<std::string>(rewriteStrings(*sink.s, outputRewrites)); StringSource source(*sink.s); restorePath(actualPath, source); @@ -3033,8 +3035,8 @@ Path DerivationGoal::addHashRewrite(const Path & path) Path p = worker.store.storeDir + "/" + h2 + string(path, worker.store.storeDir.size() + 33); deletePath(p); assert(path.size() == p.size()); - rewritesToTmp[h1] = h2; - rewritesFromTmp[h2] = h1; + inputRewrites[h1] = h2; + outputRewrites[h2] = h1; redirectedOutputs[path] = p; return p; } @@ -3140,8 +3142,9 @@ SubstitutionGoal::~SubstitutionGoal() { try { if (thr.joinable()) { + // FIXME: signal worker thread to quit. thr.join(); - //worker.childTerminated(shared_from_this()); // FIXME + worker.childTerminated(this); } } catch (...) { ignoreException(); @@ -3296,7 +3299,7 @@ void SubstitutionGoal::finished() trace("substitute finished"); thr.join(); - worker.childTerminated(shared_from_this()); + worker.childTerminated(this); try { promise.get_future().get(); @@ -3449,6 +3452,7 @@ void Worker::childStarted(GoalPtr goal, const set<int> & fds, { Child child; child.goal = goal; + child.goal2 = goal.get(); child.fds = fds; child.timeStarted = child.lastOutput = time(0); child.inBuildSlot = inBuildSlot; @@ -3458,10 +3462,10 @@ void Worker::childStarted(GoalPtr goal, const set<int> & fds, } -void Worker::childTerminated(GoalPtr goal, bool wakeSleepers) +void Worker::childTerminated(Goal * goal, bool wakeSleepers) { auto i = std::find_if(children.begin(), children.end(), - [&](const Child & child) { return child.goal.lock() == goal; }); + [&](const Child & child) { return child.goal2 == goal; }); assert(i != children.end()); if (i->inBuildSlot) { diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 7dcf71d468df..f051f10bd018 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -390,4 +390,11 @@ Sink & operator << (Sink & out, const BasicDerivation & drv) } +std::string hashPlaceholder(const std::string & outputName) +{ + // FIXME: memoize? + return "/" + printHash32(hashString(htSHA256, "nix-output:" + outputName)); +} + + } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 974de78c58d5..9717a81e469c 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -117,4 +117,6 @@ struct Sink; Source & readDerivation(Source & in, Store & store, BasicDerivation & drv); Sink & operator << (Sink & out, const BasicDerivation & drv); +std::string hashPlaceholder(const std::string & outputName); + } diff --git a/src/libstore/download.cc b/src/libstore/download.cc index 95c7d2255afc..5305a48950c4 100644 --- a/src/libstore/download.cc +++ b/src/libstore/download.cc @@ -230,7 +230,7 @@ struct CurlDownloader : public Downloader } catch (DownloadError & e) { attempt++; if (e.error != Transient || attempt >= options.tries) throw; - auto ms = 25 * (1 << (attempt - 1)); + auto ms = options.baseRetryTimeMs * (1 << (attempt - 1)); printMsg(lvlError, format("warning: %s; retrying in %d ms") % e.what() % ms); std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } @@ -243,13 +243,14 @@ ref<Downloader> makeDownloader() return make_ref<CurlDownloader>(); } -Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpack, const Hash & expectedHash) +Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpack, string name, const Hash & expectedHash) { auto url = resolveUri(url_); - string name; - auto p = url.rfind('/'); - if (p != string::npos) name = string(url, p + 1); + if (name == "") { + auto p = url.rfind('/'); + if (p != string::npos) name = string(url, p + 1); + } Path expectedStorePath; if (expectedHash) { diff --git a/src/libstore/download.hh b/src/libstore/download.hh index 1f6098759a2d..f22e688645b0 100644 --- a/src/libstore/download.hh +++ b/src/libstore/download.hh @@ -14,6 +14,7 @@ struct DownloadOptions enum { yes, no, automatic } showProgress = yes; bool head = false; size_t tries = 1; + unsigned int baseRetryTimeMs = 100; }; struct DownloadResult @@ -29,7 +30,7 @@ struct Downloader { virtual DownloadResult download(string url, const DownloadOptions & options) = 0; - Path downloadCached(ref<Store> store, const string & url, bool unpack, + Path downloadCached(ref<Store> store, const string & url, bool unpack, string name = "", const Hash & expectedHash = Hash()); enum Error { NotFound, Forbidden, Misc, Transient }; diff --git a/src/libstore/http-binary-cache-store.cc b/src/libstore/http-binary-cache-store.cc index 42e9099b8e20..bdcd2fd3998b 100644 --- a/src/libstore/http-binary-cache-store.cc +++ b/src/libstore/http-binary-cache-store.cc @@ -80,7 +80,8 @@ protected: auto downloader(downloaders.get()); DownloadOptions options; options.showProgress = DownloadOptions::no; - options.tries = 3; + options.tries = 5; + options.baseRetryTimeMs = 1000; try { return downloader->download(cacheUri + "/" + path, options).data; } catch (DownloadError & e) { diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index d3f0c4787e17..10056f2f1fd8 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -760,7 +760,7 @@ Path LocalStore::queryPathFromHashPart(const string & hashPart) Path prefix = storeDir + "/" + hashPart; - return retrySQLite<Path>([&]() { + return retrySQLite<Path>([&]() -> std::string { auto state(_state.lock()); auto useQueryPathFromHashPart(state->stmtQueryPathFromHashPart.use()(prefix)); diff --git a/src/libutil/json.cc b/src/libutil/json.cc new file mode 100644 index 000000000000..ecc3fdfe514e --- /dev/null +++ b/src/libutil/json.cc @@ -0,0 +1,176 @@ +#include "json.hh" + +#include <iomanip> +#include <cstring> + +namespace nix { + +void toJSON(std::ostream & str, const char * start, const char * end) +{ + str << '"'; + for (auto i = start; i != end; i++) + if (*i == '\"' || *i == '\\') str << '\\' << *i; + else if (*i == '\n') str << "\\n"; + else if (*i == '\r') str << "\\r"; + else if (*i == '\t') str << "\\t"; + else if (*i >= 0 && *i < 32) + str << "\\u" << std::setfill('0') << std::setw(4) << std::hex << (uint16_t) *i << std::dec; + else str << *i; + str << '"'; +} + +void toJSON(std::ostream & str, const std::string & s) +{ + toJSON(str, s.c_str(), s.c_str() + s.size()); +} + +void toJSON(std::ostream & str, const char * s) +{ + if (!s) str << "null"; else toJSON(str, s, s + strlen(s)); +} + +void toJSON(std::ostream & str, unsigned long long n) +{ + str << n; +} + +void toJSON(std::ostream & str, unsigned long n) +{ + str << n; +} + +void toJSON(std::ostream & str, long n) +{ + str << n; +} + +void toJSON(std::ostream & str, double f) +{ + str << f; +} + +void toJSON(std::ostream & str, bool b) +{ + str << (b ? "true" : "false"); +} + +JSONWriter::JSONWriter(std::ostream & str, bool indent) + : state(new JSONState(str, indent)) +{ + state->stack.push_back(this); +} + +JSONWriter::JSONWriter(JSONState * state) + : state(state) +{ + state->stack.push_back(this); +} + +JSONWriter::~JSONWriter() +{ + assertActive(); + state->stack.pop_back(); + if (state->stack.empty()) delete state; +} + +void JSONWriter::comma() +{ + assertActive(); + if (first) { + first = false; + } else { + state->str << ','; + } + if (state->indent) indent(); +} + +void JSONWriter::indent() +{ + state->str << '\n' << std::string(state->depth * 2, ' '); +} + +void JSONList::open() +{ + state->depth++; + state->str << '['; +} + +JSONList::~JSONList() +{ + state->depth--; + if (state->indent && !first) indent(); + state->str << "]"; +} + +JSONList JSONList::list() +{ + comma(); + return JSONList(state); +} + +JSONObject JSONList::object() +{ + comma(); + return JSONObject(state); +} + +JSONPlaceholder JSONList::placeholder() +{ + comma(); + return JSONPlaceholder(state); +} + +void JSONObject::open() +{ + state->depth++; + state->str << '{'; +} + +JSONObject::~JSONObject() +{ + state->depth--; + if (state->indent && !first) indent(); + state->str << "}"; +} + +void JSONObject::attr(const std::string & s) +{ + comma(); + toJSON(state->str, s); + state->str << ':'; + if (state->indent) state->str << ' '; +} + +JSONList JSONObject::list(const std::string & name) +{ + attr(name); + return JSONList(state); +} + +JSONObject JSONObject::object(const std::string & name) +{ + attr(name); + return JSONObject(state); +} + +JSONPlaceholder JSONObject::placeholder(const std::string & name) +{ + attr(name); + return JSONPlaceholder(state); +} + +JSONList JSONPlaceholder::list() +{ + assertValid(); + first = false; + return JSONList(state); +} + +JSONObject JSONPlaceholder::object() +{ + assertValid(); + first = false; + return JSONObject(state); +} + +} diff --git a/src/libutil/json.hh b/src/libutil/json.hh new file mode 100644 index 000000000000..aec456845056 --- /dev/null +++ b/src/libutil/json.hh @@ -0,0 +1,184 @@ +#pragma once + +#include <iostream> +#include <vector> +#include <cassert> + +namespace nix { + +void toJSON(std::ostream & str, const char * start, const char * end); +void toJSON(std::ostream & str, const std::string & s); +void toJSON(std::ostream & str, const char * s); +void toJSON(std::ostream & str, unsigned long long n); +void toJSON(std::ostream & str, unsigned long n); +void toJSON(std::ostream & str, long n); +void toJSON(std::ostream & str, double f); +void toJSON(std::ostream & str, bool b); + +class JSONWriter +{ +protected: + + struct JSONState + { + std::ostream & str; + bool indent; + size_t depth = 0; + std::vector<JSONWriter *> stack; + JSONState(std::ostream & str, bool indent) : str(str), indent(indent) { } + ~JSONState() + { + assert(stack.empty()); + } + }; + + JSONState * state; + + bool first = true; + + JSONWriter(std::ostream & str, bool indent); + + JSONWriter(JSONState * state); + + ~JSONWriter(); + + void assertActive() + { + assert(!state->stack.empty() && state->stack.back() == this); + } + + void comma(); + + void indent(); +}; + +class JSONObject; +class JSONPlaceholder; + +class JSONList : JSONWriter +{ +private: + + friend class JSONObject; + friend class JSONPlaceholder; + + void open(); + + JSONList(JSONState * state) + : JSONWriter(state) + { + open(); + } + +public: + + JSONList(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + open(); + } + + ~JSONList(); + + template<typename T> + JSONList & elem(const T & v) + { + comma(); + toJSON(state->str, v); + return *this; + } + + JSONList list(); + + JSONObject object(); + + JSONPlaceholder placeholder(); +}; + +class JSONObject : JSONWriter +{ +private: + + friend class JSONList; + friend class JSONPlaceholder; + + void open(); + + JSONObject(JSONState * state) + : JSONWriter(state) + { + open(); + } + + void attr(const std::string & s); + +public: + + JSONObject(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + open(); + } + + ~JSONObject(); + + template<typename T> + JSONObject & attr(const std::string & name, const T & v) + { + attr(name); + toJSON(state->str, v); + return *this; + } + + JSONList list(const std::string & name); + + JSONObject object(const std::string & name); + + JSONPlaceholder placeholder(const std::string & name); +}; + +class JSONPlaceholder : JSONWriter +{ + +private: + + friend class JSONList; + friend class JSONObject; + + JSONPlaceholder(JSONState * state) + : JSONWriter(state) + { + } + + void assertValid() + { + assertActive(); + assert(first); + } + +public: + + JSONPlaceholder(std::ostream & str, bool indent = false) + : JSONWriter(str, indent) + { + } + + ~JSONPlaceholder() + { + assert(!first || std::uncaught_exception()); + } + + template<typename T> + void write(const T & v) + { + assertValid(); + first = false; + toJSON(state->str, v); + } + + JSONList list(); + + JSONObject object(); +}; + +} diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 3f0486bb6541..6a557e8ac9db 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -10,6 +10,7 @@ #include "store-api.hh" #include "user-env.hh" #include "util.hh" +#include "json.hh" #include "value-to-json.hh" #include "xml-writer.hh" @@ -128,9 +129,8 @@ static void getAllExprs(EvalState & state, } attrs.insert(attrName); /* Load the expression on demand. */ - Value & vFun(*state.allocValue()); + Value & vFun = state.getBuiltin("import"); Value & vArg(*state.allocValue()); - state.getBuiltin("import", vFun); mkString(vArg, path2); if (v.attrs->size() == v.attrs->capacity()) throw Error(format("too many Nix expressions in directory ‘%1%’") % path); @@ -861,26 +861,24 @@ static VersionDiff compareVersionAgainstSet( static void queryJSON(Globals & globals, vector<DrvInfo> & elems) { - JSONObject topObj(cout); + JSONObject topObj(cout, true); for (auto & i : elems) { - topObj.attr(i.attrPath); - JSONObject pkgObj(cout); + JSONObject pkgObj = topObj.object(i.attrPath); pkgObj.attr("name", i.name); pkgObj.attr("system", i.system); - pkgObj.attr("meta"); - JSONObject metaObj(cout); + JSONObject metaObj = pkgObj.object("meta"); StringSet metaNames = i.queryMetaNames(); for (auto & j : metaNames) { - metaObj.attr(j); + auto placeholder = metaObj.placeholder(j); Value * v = i.queryMeta(j); if (!v) { printMsg(lvlError, format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i.name % j); - cout << "null"; + placeholder.write(nullptr); } else { PathSet context; - printValueAsJSON(*globals.state, true, *v, cout, context); + printValueAsJSON(*globals.state, true, *v, placeholder, context); } } } diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 6257c7679af9..8341bbc5a3a4 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -9,6 +9,41 @@ namespace nix { +Value * MixInstallables::buildSourceExpr(EvalState & state) +{ + Value * vRoot = state.allocValue(); + + if (file != "") { + Expr * e = state.parseExprFromFile(resolveExprPath(lookupFileArg(state, file))); + state.eval(e, *vRoot); + } + + else { + + /* Construct the installation source from $NIX_PATH. */ + + auto searchPath = state.getSearchPath(); + + state.mkAttrs(*vRoot, searchPath.size()); + + std::unordered_set<std::string> seen; + + for (auto & i : searchPath) { + if (i.first == "") continue; + if (seen.count(i.first)) continue; + seen.insert(i.first); + if (!pathExists(i.second)) continue; + mkApp(*state.allocAttr(*vRoot, state.symbols.create(i.first)), + state.getBuiltin("import"), + mkString(*state.allocValue(), i.second)); + } + + vRoot->attrs->sort(); + } + + return vRoot; +} + UserEnvElems MixInstallables::evalInstallables(ref<Store> store) { UserEnvElems res; @@ -46,15 +81,12 @@ UserEnvElems MixInstallables::evalInstallables(ref<Store> store) EvalState state({}, store); - Expr * e = state.parseExprFromFile(resolveExprPath(lookupFileArg(state, file))); - - Value vRoot; - state.eval(e, vRoot); + auto vRoot = buildSourceExpr(state); std::map<string, string> autoArgs_; Bindings & autoArgs(*evalAutoArgs(state, autoArgs_)); - Value & v(*findAlongAttrPath(state, installable, autoArgs, vRoot)); + Value & v(*findAlongAttrPath(state, installable, autoArgs, *vRoot)); state.forceValue(v); DrvInfos drvs; diff --git a/src/nix/installables.hh b/src/nix/installables.hh index 5eb897d46148..a58f7dc59bb4 100644 --- a/src/nix/installables.hh +++ b/src/nix/installables.hh @@ -21,10 +21,13 @@ struct UserEnvElem typedef std::vector<UserEnvElem> UserEnvElems; +struct Value; +class EvalState; + struct MixInstallables : virtual Args { Strings installables; - Path file = "<nixpkgs>"; + Path file; MixInstallables() { @@ -33,6 +36,13 @@ struct MixInstallables : virtual Args } UserEnvElems evalInstallables(ref<Store> store); + + /* Return a value representing the Nix expression from which we + are installing. This is either the file specified by ‘--file’, + or an attribute set constructed from $NIX_PATH, e.g. ‘{ nixpkgs + = import ...; bla = import ...; }’. */ + Value * buildSourceExpr(EvalState & state); + }; } diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index dca22240b1cb..d8b6232d47ca 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -1,6 +1,7 @@ #include "command.hh" #include "shared.hh" #include "store-api.hh" +#include "json.hh" #include <iomanip> #include <algorithm> @@ -12,12 +13,14 @@ struct CmdPathInfo : StorePathsCommand bool showSize = false; bool showClosureSize = false; bool showSigs = false; + bool json = false; CmdPathInfo() { mkFlag('s', "size", "print size of the NAR dump of each path", &showSize); mkFlag('S', "closure-size", "print sum size of the NAR dumps of the closure of each path", &showClosureSize); mkFlag(0, "sigs", "show signatures", &showSigs); + mkFlag(0, "json", "produce JSON output", &json); } std::string name() override @@ -41,6 +44,10 @@ struct CmdPathInfo : StorePathsCommand "To check the existence of a path in a binary cache:", "nix path-info -r /nix/store/7qvk5c91...-geeqie-1.1 --store https://cache.nixos.org/" }, + Example{ + "To print the 10 most recently added paths (using --json and the jq(1) command):", + "nix path-info --all --json | jq -r 'sort_by(.registrationTime)[-11:-1][].path'" + }, }; } @@ -50,36 +57,85 @@ struct CmdPathInfo : StorePathsCommand for (auto & storePath : storePaths) pathLen = std::max(pathLen, storePath.size()); - for (auto storePath : storePaths) { - auto info = store->queryPathInfo(storePath); - storePath = info->path; // FIXME: screws up padding + auto getClosureSize = [&](const Path & storePath) -> unsigned long long { + unsigned long long totalSize = 0; + PathSet closure; + store->computeFSClosure(storePath, closure, false, false); + for (auto & p : closure) + totalSize += store->queryPathInfo(p)->narSize; + return totalSize; + }; - std::cout << storePath << std::string(std::max(0, (int) pathLen - (int) storePath.size()), ' '); + if (json) { + JSONList jsonRoot(std::cout, true); - if (showSize) { - std::cout << '\t' << std::setw(11) << info->narSize; - } + for (auto storePath : storePaths) { + auto info = store->queryPathInfo(storePath); + storePath = info->path; + + auto jsonPath = jsonRoot.object(); + jsonPath + .attr("path", storePath) + .attr("narHash", info->narHash.to_string()) + .attr("narSize", info->narSize); + + if (showClosureSize) + jsonPath.attr("closureSize", getClosureSize(storePath)); + + if (info->deriver != "") + jsonPath.attr("deriver", info->deriver); + + { + auto jsonRefs = jsonPath.list("references"); + for (auto & ref : info->references) + jsonRefs.elem(ref); + } + + if (info->registrationTime) + jsonPath.attr("registrationTime", info->registrationTime); + + if (info->ultimate) + jsonPath.attr("ultimate", info->ultimate); - if (showClosureSize) { - size_t totalSize = 0; - PathSet closure; - store->computeFSClosure(storePath, closure, false, false); - for (auto & p : closure) - totalSize += store->queryPathInfo(p)->narSize; - std::cout << '\t' << std::setw(11) << totalSize; + if (info->ca != "") + jsonPath.attr("ca", info->ca); + + if (!info->sigs.empty()) { + auto jsonSigs = jsonPath.list("signatures"); + for (auto & sig : info->sigs) + jsonSigs.elem(sig); + } } + } + + else { + + for (auto storePath : storePaths) { + auto info = store->queryPathInfo(storePath); + storePath = info->path; // FIXME: screws up padding + + std::cout << storePath << std::string(std::max(0, (int) pathLen - (int) storePath.size()), ' '); - if (showSigs) { - std::cout << '\t'; - Strings ss; - if (info->ultimate) ss.push_back("ultimate"); - if (info->ca != "") ss.push_back("ca:" + info->ca); - for (auto & sig : info->sigs) ss.push_back(sig); - std::cout << concatStringsSep(" ", ss); + if (showSize) + std::cout << '\t' << std::setw(11) << info->narSize; + + if (showClosureSize) + std::cout << '\t' << std::setw(11) << getClosureSize(storePath); + + if (showSigs) { + std::cout << '\t'; + Strings ss; + if (info->ultimate) ss.push_back("ultimate"); + if (info->ca != "") ss.push_back("ca:" + info->ca); + for (auto & sig : info->sigs) ss.push_back(sig); + std::cout << concatStringsSep(" ", ss); + } + + std::cout << std::endl; } - std::cout << std::endl; } + } }; diff --git a/tests/config.nix b/tests/config.nix index 6244a15fa48a..76388fdd5b95 100644 --- a/tests/config.nix +++ b/tests/config.nix @@ -13,7 +13,7 @@ rec { derivation ({ inherit system; builder = shell; - args = ["-e" args.builder]; + args = ["-e" args.builder or (builtins.toFile "builder.sh" "eval \"$buildCommand\"")]; PATH = path; } // removeAttrs args ["builder" "meta"]) // { meta = args.meta or {}; }; diff --git a/tests/lang/eval-okay-partition.exp b/tests/lang/eval-okay-partition.exp new file mode 100644 index 000000000000..cd8b8b020c05 --- /dev/null +++ b/tests/lang/eval-okay-partition.exp @@ -0,0 +1 @@ +{ right = [ 0 2 4 6 8 10 100 102 104 106 108 110 ]; wrong = [ 1 3 5 7 9 101 103 105 107 109 ]; } diff --git a/tests/lang/eval-okay-partition.nix b/tests/lang/eval-okay-partition.nix new file mode 100644 index 000000000000..846d2ce49486 --- /dev/null +++ b/tests/lang/eval-okay-partition.nix @@ -0,0 +1,5 @@ +with import ./lib.nix; + +builtins.partition + (x: x / 2 * 2 == x) + (builtins.concatLists [ (range 0 10) (range 100 110) ]) diff --git a/tests/local.mk b/tests/local.mk index 1e5439f061e2..2ca52144baee 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -10,7 +10,8 @@ nix_tests = \ timeout.sh secure-drv-outputs.sh nix-channel.sh \ multiple-outputs.sh import-derivation.sh fetchurl.sh optimise-store.sh \ binary-cache.sh nix-profile.sh repair.sh dump-db.sh case-hack.sh \ - check-reqs.sh pass-as-file.sh tarball.sh restricted.sh + check-reqs.sh pass-as-file.sh tarball.sh restricted.sh \ + placeholders.sh # parallel.sh install-tests += $(foreach x, $(nix_tests), tests/$(x)) diff --git a/tests/placeholders.sh b/tests/placeholders.sh new file mode 100644 index 000000000000..071cfe2dc893 --- /dev/null +++ b/tests/placeholders.sh @@ -0,0 +1,22 @@ +source common.sh + +clearStore + +nix-build --no-out-link -E ' + with import ./config.nix; + + mkDerivation { + name = "placeholders"; + outputs = [ "out" "bin" "dev" ]; + buildCommand = " + echo foo1 > $out + echo foo2 > $bin + echo foo3 > $dev + [[ $(cat ${placeholder "out"}) = foo1 ]] + [[ $(cat ${placeholder "bin"}) = foo2 ]] + [[ $(cat ${placeholder "dev"}) = foo3 ]] + "; + } +' + +echo XYZZY |