about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2015-03-25T16·12+0100
committerEelco Dolstra <eelco.dolstra@logicblox.com>2015-03-25T16·29+0100
commit000b5a000f8ceef9d79c7e8a9835fde9a98c367f (patch)
treecd984cf60991a2065850d4923a3a948c2e17343d
parent7ea6ecf855dee69a1cd938c3fe7e4e132be00b24 (diff)
Add fetchTarball builtin
This function downloads and unpacks the given URL at evaluation
time. This is primarily intended to make it easier to deal with Nix
expressions that have external dependencies. For instance, to fetch
Nixpkgs 14.12:

  with import (fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-14.12.tar.gz) {};

Or to fetch a specific revision:

  with import (fetchTarball https://github.com/NixOS/nixpkgs/archive/2766a4b44ee6eafae03a042801270c7f6b8ed32a.tar.gz) {};

This patch also adds a ‘fetchurl’ builtin that downloads but doesn't
unpack its argument. Not sure if it's useful though.
-rw-r--r--src/libexpr/local.mk2
-rw-r--r--src/libexpr/primops.cc119
2 files changed, 120 insertions, 1 deletions
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index 4c1f4de19187..35e84980a6dd 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -8,7 +8,7 @@ libexpr_SOURCES := $(wildcard $(d)/*.cc) $(d)/lexer-tab.cc $(d)/parser-tab.cc
 
 libexpr_LIBS = libutil libstore libformat
 
-libexpr_LDFLAGS = -ldl
+libexpr_LDFLAGS = -ldl -lcurl
 
 # The dependency on libgc must be propagated (i.e. meaning that
 # programs/libraries that use libexpr must explicitly pass -lgc),
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index ac7652d37806..e818496460ea 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -18,6 +18,8 @@
 #include <cstring>
 #include <dlfcn.h>
 
+#include <curl/curl.h>
+
 
 namespace nix {
 
@@ -1480,6 +1482,119 @@ static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * a
 
 
 /*************************************************************
+ * Networking
+ *************************************************************/
+
+
+struct Curl
+{
+    CURL * curl;
+    string data;
+
+    static size_t writeCallback(void * contents, size_t size, size_t nmemb, void * userp)
+    {
+        Curl & c(* (Curl *) userp);
+        size_t realSize = size * nmemb;
+        c.data.append((char *) contents, realSize);
+        return realSize;
+    }
+
+    Curl()
+    {
+        curl = curl_easy_init();
+        if (!curl) throw Error("unable to initialize curl");
+
+        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+        curl_easy_setopt(curl, CURLOPT_CAINFO, getEnv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt").c_str());
+        curl_easy_setopt(curl, CURLOPT_USERAGENT, ("Nix/" + nixVersion).c_str());
+
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &curl);
+    }
+
+    ~Curl()
+    {
+        if (curl) curl_easy_cleanup(curl);
+    }
+
+    string fetch(const string & url)
+    {
+        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
+
+        data.clear();
+
+        CURLcode res = curl_easy_perform(curl);
+        if (res != CURLE_OK)
+            throw Error(format("unable to download ‘%1%’: %2%")
+                % url % curl_easy_strerror(res));
+
+        return data;
+    }
+};
+
+
+void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
+    const string & who, bool unpack)
+{
+    if (state.restricted) throw Error(format("‘%1%’ is not allowed in restricted mode") % who);
+
+    string url;
+
+    state.forceValue(*args[0]);
+
+    if (args[0]->type == tAttrs) {
+
+        state.forceAttrs(*args[0], pos);
+
+        for (auto & attr : *args[0]->attrs) {
+            string name(attr.name);
+            if (name == "url")
+                url = state.forceStringNoCtx(*attr.value, *attr.pos);
+            else
+                throw EvalError(format("unsupported argument ‘%1%’ to ‘%2%’, at %3%") % attr.name % who % attr.pos);
+        }
+
+        if (url.empty())
+            throw EvalError(format("‘url’ argument required, at %1%") % pos);
+
+    } else
+        url = state.forceStringNoCtx(*args[0], pos);
+
+    // TODO: cache downloads.
+
+    Curl curl;
+    string data = curl.fetch(url);
+
+    string name;
+    string::size_type p = url.rfind('/');
+    if (p != string::npos) name = string(url, p + 1);
+
+    Path storePath = store->addTextToStore(name, data, PathSet(), state.repair);
+
+    if (unpack) {
+        Path tmpDir = createTempDir();
+        AutoDelete autoDelete(tmpDir, true);
+        runProgram("tar", true, {"xf", storePath, "-C", tmpDir, "--strip-components", "1"}, "");
+        storePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, state.repair);
+    }
+
+    mkString(v, storePath, singleton<PathSet>(storePath));
+}
+
+
+static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    fetch(state, pos, args, v, "fetchurl", false);
+}
+
+
+static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+    fetch(state, pos, args, v, "fetchTarball", true);
+}
+
+
+/*************************************************************
  * Primop registration
  *************************************************************/
 
@@ -1617,6 +1732,10 @@ void EvalState::createBaseEnv()
     // Derivations
     addPrimOp("derivationStrict", 1, prim_derivationStrict);
 
+    // Networking
+    addPrimOp("__fetchurl", 1, prim_fetchurl);
+    addPrimOp("fetchTarball", 1, prim_fetchTarball);
+
     /* Add a wrapper around the derivation primop that computes the
        `drvPath' and `outPath' attributes lazily. */
     string path = findFile("nix/derivation.nix");