Allow URLs in the Nix search path
E.g. to install "hello" from the latest Nixpkgs:
$ nix-build '<nixpkgs>' -A hello -I nixpkgs=https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz
Or to install a specific version of NixOS:
$ nixos-rebuild switch -I nixpkgs=63def04891
.tar.gz
This commit is contained in:
parent
35d30d67eb
commit
9451ef3731
6 changed files with 123 additions and 99 deletions
|
@ -1,6 +1,8 @@
|
||||||
#include "download.hh"
|
#include "download.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
#include "hash.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
@ -134,4 +136,91 @@ DownloadResult downloadFile(string url, string expectedETag)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Path downloadFileCached(const string & url, bool unpack)
|
||||||
|
{
|
||||||
|
Path cacheDir = getEnv("XDG_CACHE_HOME", getEnv("HOME", "") + "/.cache") + "/nix/tarballs";
|
||||||
|
createDirs(cacheDir);
|
||||||
|
|
||||||
|
string urlHash = printHash32(hashString(htSHA256, url));
|
||||||
|
|
||||||
|
Path dataFile = cacheDir + "/" + urlHash + ".info";
|
||||||
|
Path fileLink = cacheDir + "/" + urlHash + "-file";
|
||||||
|
|
||||||
|
Path storePath;
|
||||||
|
|
||||||
|
string expectedETag;
|
||||||
|
|
||||||
|
int ttl = settings.get("tarball-ttl", 60 * 60);
|
||||||
|
bool skip = false;
|
||||||
|
|
||||||
|
if (pathExists(fileLink) && pathExists(dataFile)) {
|
||||||
|
storePath = readLink(fileLink);
|
||||||
|
store->addTempRoot(storePath);
|
||||||
|
if (store->isValidPath(storePath)) {
|
||||||
|
auto ss = tokenizeString<vector<string>>(readFile(dataFile), "\n");
|
||||||
|
if (ss.size() >= 3 && ss[0] == url) {
|
||||||
|
time_t lastChecked;
|
||||||
|
if (string2Int(ss[2], lastChecked) && lastChecked + ttl >= time(0))
|
||||||
|
skip = true;
|
||||||
|
else if (!ss[1].empty()) {
|
||||||
|
printMsg(lvlDebug, format("verifying previous ETag ‘%1%’") % ss[1]);
|
||||||
|
expectedETag = ss[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
storePath = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
string name;
|
||||||
|
auto p = url.rfind('/');
|
||||||
|
if (p != string::npos) name = string(url, p + 1);
|
||||||
|
|
||||||
|
if (!skip) {
|
||||||
|
|
||||||
|
if (storePath.empty())
|
||||||
|
printMsg(lvlInfo, format("downloading ‘%1%’...") % url);
|
||||||
|
else
|
||||||
|
printMsg(lvlInfo, format("checking ‘%1%’...") % url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto res = downloadFile(url, expectedETag);
|
||||||
|
|
||||||
|
if (!res.cached)
|
||||||
|
storePath = store->addTextToStore(name, res.data, PathSet(), false);
|
||||||
|
|
||||||
|
assert(!storePath.empty());
|
||||||
|
replaceSymlink(storePath, fileLink);
|
||||||
|
|
||||||
|
writeFile(dataFile, url + "\n" + res.etag + "\n" + int2String(time(0)) + "\n");
|
||||||
|
} catch (DownloadError & e) {
|
||||||
|
if (storePath.empty()) throw;
|
||||||
|
printMsg(lvlError, format("warning: %1%; using cached result") % e.msg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unpack) {
|
||||||
|
Path unpackedLink = cacheDir + "/" + baseNameOf(storePath) + "-unpacked";
|
||||||
|
Path unpackedStorePath;
|
||||||
|
if (pathExists(unpackedLink)) {
|
||||||
|
unpackedStorePath = readLink(unpackedLink);
|
||||||
|
store->addTempRoot(unpackedStorePath);
|
||||||
|
if (!store->isValidPath(unpackedStorePath))
|
||||||
|
unpackedStorePath = "";
|
||||||
|
}
|
||||||
|
if (unpackedStorePath.empty()) {
|
||||||
|
printMsg(lvlInfo, format("unpacking ‘%1%’...") % url);
|
||||||
|
Path tmpDir = createTempDir();
|
||||||
|
AutoDelete autoDelete(tmpDir, true);
|
||||||
|
runProgram("tar", true, {"xf", storePath, "-C", tmpDir, "--strip-components", "1"}, "");
|
||||||
|
unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, false);
|
||||||
|
}
|
||||||
|
replaceSymlink(unpackedStorePath, unpackedLink);
|
||||||
|
return unpackedStorePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return storePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ struct DownloadResult
|
||||||
|
|
||||||
DownloadResult downloadFile(string url, string expectedETag = "");
|
DownloadResult downloadFile(string url, string expectedETag = "");
|
||||||
|
|
||||||
|
Path downloadFileCached(const string & url, bool unpack);
|
||||||
|
|
||||||
MakeError(DownloadError, Error)
|
MakeError(DownloadError, Error)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,6 +527,8 @@ formal
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <eval.hh>
|
#include <eval.hh>
|
||||||
|
#include <download.hh>
|
||||||
|
#include <store-api.hh>
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -599,6 +601,15 @@ Expr * EvalState::parseExprFromString(const string & s, const Path & basePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool isUri(const string & s)
|
||||||
|
{
|
||||||
|
size_t pos = s.find("://");
|
||||||
|
if (pos == string::npos) return false;
|
||||||
|
string scheme(s, 0, pos);
|
||||||
|
return scheme == "http" || scheme == "https";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::addToSearchPath(const string & s, bool warn)
|
void EvalState::addToSearchPath(const string & s, bool warn)
|
||||||
{
|
{
|
||||||
size_t pos = s.find('=');
|
size_t pos = s.find('=');
|
||||||
|
@ -611,6 +622,9 @@ void EvalState::addToSearchPath(const string & s, bool warn)
|
||||||
path = string(s, pos + 1);
|
path = string(s, pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUri(path))
|
||||||
|
path = downloadFileCached(path, true);
|
||||||
|
|
||||||
path = absPath(path);
|
path = absPath(path);
|
||||||
if (pathExists(path)) {
|
if (pathExists(path)) {
|
||||||
debug(format("adding path ‘%1%’ to the search path") % path);
|
debug(format("adding path ‘%1%’ to the search path") % path);
|
||||||
|
@ -629,16 +643,17 @@ Path EvalState::findFile(const string & path)
|
||||||
|
|
||||||
Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos)
|
Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos)
|
||||||
{
|
{
|
||||||
foreach (SearchPath::iterator, i, searchPath) {
|
for (auto & i : searchPath) {
|
||||||
|
assert(!isUri(i.second));
|
||||||
Path res;
|
Path res;
|
||||||
if (i->first.empty())
|
if (i.first.empty())
|
||||||
res = i->second + "/" + path;
|
res = i.second + "/" + path;
|
||||||
else {
|
else {
|
||||||
if (path.compare(0, i->first.size(), i->first) != 0 ||
|
if (path.compare(0, i.first.size(), i.first) != 0 ||
|
||||||
(path.size() > i->first.size() && path[i->first.size()] != '/'))
|
(path.size() > i.first.size() && path[i.first.size()] != '/'))
|
||||||
continue;
|
continue;
|
||||||
res = i->second +
|
res = i.second +
|
||||||
(path.size() == i->first.size() ? "" : "/" + string(path, i->first.size()));
|
(path.size() == i.first.size() ? "" : "/" + string(path, i.first.size()));
|
||||||
}
|
}
|
||||||
if (pathExists(res)) return canonPath(res);
|
if (pathExists(res)) return canonPath(res);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||||
}
|
}
|
||||||
w.attrs->sort();
|
w.attrs->sort();
|
||||||
Value fun;
|
Value fun;
|
||||||
state.evalFile(state.findFile("nix/imported-drv-to-derivation.nix"), fun);
|
state.evalFile(settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix", fun);
|
||||||
state.forceFunction(fun, pos);
|
state.forceFunction(fun, pos);
|
||||||
mkApp(v, fun, w);
|
mkApp(v, fun, w);
|
||||||
state.forceAttrs(v, pos);
|
state.forceAttrs(v, pos);
|
||||||
|
@ -1512,88 +1512,7 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
} else
|
} else
|
||||||
url = state.forceStringNoCtx(*args[0], pos);
|
url = state.forceStringNoCtx(*args[0], pos);
|
||||||
|
|
||||||
Path cacheDir = getEnv("XDG_CACHE_HOME", getEnv("HOME", "") + "/.cache") + "/nix/tarballs";
|
mkString(v, downloadFileCached(url, unpack), PathSet({url}));
|
||||||
createDirs(cacheDir);
|
|
||||||
|
|
||||||
string urlHash = printHash32(hashString(htSHA256, url));
|
|
||||||
|
|
||||||
Path dataFile = cacheDir + "/" + urlHash + ".info";
|
|
||||||
Path fileLink = cacheDir + "/" + urlHash + "-file";
|
|
||||||
|
|
||||||
Path storePath;
|
|
||||||
|
|
||||||
string expectedETag;
|
|
||||||
|
|
||||||
int ttl = settings.get("tarball-ttl", 60 * 60);
|
|
||||||
bool skip = false;
|
|
||||||
|
|
||||||
if (pathExists(fileLink) && pathExists(dataFile)) {
|
|
||||||
storePath = readLink(fileLink);
|
|
||||||
store->addTempRoot(storePath);
|
|
||||||
if (store->isValidPath(storePath)) {
|
|
||||||
auto ss = tokenizeString<vector<string>>(readFile(dataFile), "\n");
|
|
||||||
if (ss.size() >= 3 && ss[0] == url) {
|
|
||||||
time_t lastChecked;
|
|
||||||
if (string2Int(ss[2], lastChecked) && lastChecked + ttl >= time(0))
|
|
||||||
skip = true;
|
|
||||||
else if (!ss[1].empty()) {
|
|
||||||
printMsg(lvlDebug, format("verifying previous ETag ‘%1%’") % ss[1]);
|
|
||||||
expectedETag = ss[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
storePath = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
string name;
|
|
||||||
auto p = url.rfind('/');
|
|
||||||
if (p != string::npos) name = string(url, p + 1);
|
|
||||||
|
|
||||||
if (!skip) {
|
|
||||||
|
|
||||||
if (storePath.empty())
|
|
||||||
printMsg(lvlInfo, format("downloading ‘%1%’...") % url);
|
|
||||||
else
|
|
||||||
printMsg(lvlInfo, format("checking ‘%1%’...") % url);
|
|
||||||
|
|
||||||
try {
|
|
||||||
auto res = downloadFile(url, expectedETag);
|
|
||||||
|
|
||||||
if (!res.cached)
|
|
||||||
storePath = store->addTextToStore(name, res.data, PathSet(), state.repair);
|
|
||||||
|
|
||||||
assert(!storePath.empty());
|
|
||||||
replaceSymlink(storePath, fileLink);
|
|
||||||
|
|
||||||
writeFile(dataFile, url + "\n" + res.etag + "\n" + int2String(time(0)) + "\n");
|
|
||||||
} catch (DownloadError & e) {
|
|
||||||
if (storePath.empty()) throw;
|
|
||||||
printMsg(lvlError, format("warning: %1%; using cached result") % e.msg());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unpack) {
|
|
||||||
Path unpackedLink = cacheDir + "/" + baseNameOf(storePath) + "-unpacked";
|
|
||||||
Path unpackedStorePath;
|
|
||||||
if (pathExists(unpackedLink)) {
|
|
||||||
unpackedStorePath = readLink(unpackedLink);
|
|
||||||
store->addTempRoot(unpackedStorePath);
|
|
||||||
if (!store->isValidPath(unpackedStorePath))
|
|
||||||
unpackedStorePath = "";
|
|
||||||
}
|
|
||||||
if (unpackedStorePath.empty()) {
|
|
||||||
printMsg(lvlDebug, format("unpacking ‘%1%’...") % storePath);
|
|
||||||
Path tmpDir = createTempDir();
|
|
||||||
AutoDelete autoDelete(tmpDir, true);
|
|
||||||
runProgram("tar", true, {"xf", storePath, "-C", tmpDir, "--strip-components", "1"}, "");
|
|
||||||
unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, state.repair);
|
|
||||||
}
|
|
||||||
replaceSymlink(unpackedStorePath, unpackedLink);
|
|
||||||
mkString(v, unpackedStorePath, singleton<PathSet>(unpackedStorePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
mkString(v, storePath, singleton<PathSet>(storePath));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1753,8 +1672,7 @@ void EvalState::createBaseEnv()
|
||||||
|
|
||||||
/* Add a wrapper around the derivation primop that computes the
|
/* Add a wrapper around the derivation primop that computes the
|
||||||
`drvPath' and `outPath' attributes lazily. */
|
`drvPath' and `outPath' attributes lazily. */
|
||||||
string path = findFile("nix/derivation.nix");
|
string path = settings.nixDataDir + "/nix/corepkgs/derivation.nix";
|
||||||
assert(!path.empty());
|
|
||||||
sDerivationNix = symbols.create(path);
|
sDerivationNix = symbols.create(path);
|
||||||
evalFile(path, v);
|
evalFile(path, v);
|
||||||
addConstant("derivation", v);
|
addConstant("derivation", v);
|
||||||
|
|
|
@ -1423,6 +1423,8 @@ int main(int argc, char * * argv)
|
||||||
|
|
||||||
if (!op) throw UsageError("no operation specified");
|
if (!op) throw UsageError("no operation specified");
|
||||||
|
|
||||||
|
store = openStore();
|
||||||
|
|
||||||
globals.state = std::shared_ptr<EvalState>(new EvalState(searchPath));
|
globals.state = std::shared_ptr<EvalState>(new EvalState(searchPath));
|
||||||
globals.state->repair = repair;
|
globals.state->repair = repair;
|
||||||
|
|
||||||
|
@ -1441,8 +1443,6 @@ int main(int argc, char * * argv)
|
||||||
: canonPath(settings.nixStateDir + "/profiles/default");
|
: canonPath(settings.nixStateDir + "/profiles/default");
|
||||||
}
|
}
|
||||||
|
|
||||||
store = openStore();
|
|
||||||
|
|
||||||
op(globals, opFlags, opArgs);
|
op(globals, opFlags, opArgs);
|
||||||
|
|
||||||
globals.state->printStats();
|
globals.state->printStats();
|
||||||
|
|
|
@ -155,14 +155,16 @@ int main(int argc, char * * argv)
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (evalOnly && !wantsReadWrite)
|
||||||
|
settings.readOnlyMode = true;
|
||||||
|
|
||||||
|
store = openStore();
|
||||||
|
|
||||||
EvalState state(searchPath);
|
EvalState state(searchPath);
|
||||||
state.repair = repair;
|
state.repair = repair;
|
||||||
|
|
||||||
Bindings & autoArgs(*evalAutoArgs(state, autoArgs_));
|
Bindings & autoArgs(*evalAutoArgs(state, autoArgs_));
|
||||||
|
|
||||||
if (evalOnly && !wantsReadWrite)
|
|
||||||
settings.readOnlyMode = true;
|
|
||||||
|
|
||||||
if (attrPaths.empty()) attrPaths.push_back("");
|
if (attrPaths.empty()) attrPaths.push_back("");
|
||||||
|
|
||||||
if (findFile) {
|
if (findFile) {
|
||||||
|
@ -174,8 +176,6 @@ int main(int argc, char * * argv)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store = openStore();
|
|
||||||
|
|
||||||
if (readStdin) {
|
if (readStdin) {
|
||||||
Expr * e = parseStdin(state);
|
Expr * e = parseStdin(state);
|
||||||
processExpr(state, attrPaths, parseOnly, strict, autoArgs,
|
processExpr(state, attrPaths, parseOnly, strict, autoArgs,
|
||||||
|
|
Loading…
Reference in a new issue