nix-prefetch-url: Support unpacking tarballs

This allows nix-prefetch-url to prefetch the output of fetchzip and
its wrappers (like fetchFromGitHub). For example:

  $ nix-prefetch-url --unpack https://github.com/NixOS/patchelf/archive/0.8.tar.gz

or from a Nix expression:

  $ nix-prefetch-url -A nix-repl.src

In the latter case, --unpack can be omitted because nix-repl.src is a
fetchFromGitHub derivation and thus has "outputHashMode" set to
"recursive".
This commit is contained in:
Eelco Dolstra 2015-10-07 14:40:10 +02:00
parent 1abda8e173
commit b54f447df9
2 changed files with 68 additions and 21 deletions

View file

@ -81,6 +81,16 @@ downloaded file in the Nix store is also printed.</para>
</varlistentry> </varlistentry>
<varlistentry><term><option>--unpack</option></term>
<listitem><para>Unpack the archive (which must be a tarball or zip
file) and add the result to the Nix store. The resulting hash can
be used with functions such as Nixpkgss
<varname>fetchzip</varname> or
<varname>fetchFromGitHub</varname>.</para></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsection> </refsection>
@ -94,7 +104,12 @@ $ nix-prefetch-url ftp://ftp.gnu.org/pub/gnu/hello/hello-2.10.tar.gz
$ nix-prefetch-url --print-path mirror://gnu/hello/hello-2.10.tar.gz $ nix-prefetch-url --print-path mirror://gnu/hello/hello-2.10.tar.gz
0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i 0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i
/nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz</screen> /nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz
$ nix-prefetch-url --unpack --print-path https://github.com/NixOS/patchelf/archive/0.8.tar.gz
079agjlv0hrv7fxnx9ngipx14gyncbkllxrp9cccnh3a50fxcmy7
/nix/store/19zrmhm3m40xxaw81c8cqm6aljgrnwj2-0.8.tar.gz
</screen>
</refsection> </refsection>

View file

@ -53,6 +53,7 @@ int main(int argc, char * * argv)
bool fromExpr = false; bool fromExpr = false;
string attrPath; string attrPath;
std::map<string, string> autoArgs_; std::map<string, string> autoArgs_;
bool unpack = false;
parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) { parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
if (*arg == "--help") if (*arg == "--help")
@ -71,6 +72,8 @@ int main(int argc, char * * argv)
fromExpr = true; fromExpr = true;
attrPath = getArg(*arg, arg, end); attrPath = getArg(*arg, arg, end);
} }
else if (*arg == "--unpack")
unpack = true;
else if (parseAutoArgs(arg, end, autoArgs_)) else if (parseAutoArgs(arg, end, autoArgs_))
; ;
else if (parseSearchPathArg(arg, end, searchPath)) else if (parseSearchPathArg(arg, end, searchPath))
@ -103,13 +106,22 @@ int main(int argc, char * * argv)
state.evalFile(path, vRoot); state.evalFile(path, vRoot);
Value & v(*findAlongAttrPath(state, attrPath, autoArgs, vRoot)); Value & v(*findAlongAttrPath(state, attrPath, autoArgs, vRoot));
state.forceAttrs(v); state.forceAttrs(v);
auto urls = v.attrs->find(state.symbols.create("urls"));
if (urls == v.attrs->end()) /* Extract the URI. */
auto attr = v.attrs->find(state.symbols.create("urls"));
if (attr == v.attrs->end())
throw Error("attribute set does not contain a urls attribute"); throw Error("attribute set does not contain a urls attribute");
state.forceList(*urls->value); state.forceList(*attr->value);
if (urls->value->listSize() < 1) if (attr->value->listSize() < 1)
throw Error("urls list is empty"); throw Error("urls list is empty");
uri = state.forceString(*urls->value->listElems()[0]); uri = state.forceString(*attr->value->listElems()[0]);
/* Extract the hash mode. */
attr = v.attrs->find(state.symbols.create("outputHashMode"));
if (attr == v.attrs->end())
printMsg(lvlInfo, "warning: this does not look like a fetchurl call");
else
unpack = state.forceString(*attr->value) == "recursive";
} }
/* Figure out a name in the Nix store. */ /* Figure out a name in the Nix store. */
@ -123,7 +135,7 @@ int main(int argc, char * * argv)
Path storePath; Path storePath;
if (args.size() == 2) { if (args.size() == 2) {
expectedHash = parseHash16or32(ht, args[1]); expectedHash = parseHash16or32(ht, args[1]);
storePath = makeFixedOutputPath(false, ht, expectedHash, name); storePath = makeFixedOutputPath(unpack, ht, expectedHash, name);
if (store->isValidPath(storePath)) if (store->isValidPath(storePath))
hash = expectedHash; hash = expectedHash;
else else
@ -134,28 +146,48 @@ int main(int argc, char * * argv)
auto actualUri = resolveMirrorUri(state, uri); auto actualUri = resolveMirrorUri(state, uri);
if (uri != actualUri)
printMsg(lvlInfo, format("%1% expands to %2%") % uri % actualUri);
/* Download the file. */ /* Download the file. */
printMsg(lvlInfo, format("downloading %1%...") % actualUri);
auto result = downloadFile(actualUri); auto result = downloadFile(actualUri);
AutoDelete tmpDir(createTempDir(), true);
Path tmpFile = (Path) tmpDir + "/tmp";
writeFile(tmpFile, result.data);
/* Optionally unpack the file. */
if (unpack) {
printMsg(lvlInfo, "unpacking...");
Path unpacked = (Path) tmpDir + "/unpacked";
createDirs(unpacked);
if (hasSuffix(baseNameOf(uri), ".zip"))
runProgram("unzip", true, {"-qq", tmpFile, "-d", unpacked}, "");
else
// FIXME: this requires GNU tar for decompression.
runProgram("tar", true, {"xf", tmpFile, "-C", unpacked}, "");
/* If the archive unpacks to a single file/directory, then use
that as the top-level. */
auto entries = readDirectory(unpacked);
if (entries.size() == 1)
tmpFile = unpacked + "/" + entries[0].name;
else
tmpFile = unpacked;
}
/* FIXME: inefficient; addToStore() will also hash
this. */
hash = unpack ? hashPath(ht, tmpFile).first : hashString(ht, result.data);
if (expectedHash != Hash(ht) && expectedHash != hash)
throw Error(format("hash mismatch for %1%") % uri);
/* Copy the file to the Nix store. FIXME: if RemoteStore /* Copy the file to the Nix store. FIXME: if RemoteStore
implemented addToStoreFromDump() and downloadFile() implemented addToStoreFromDump() and downloadFile()
supported a sink, we could stream the download directly supported a sink, we could stream the download directly
into the Nix store. */ into the Nix store. */
AutoDelete tmpDir(createTempDir(), true); storePath = store->addToStore(name, tmpFile, unpack, ht);
Path tmpFile = (Path) tmpDir + "/tmp";
writeFile(tmpFile, result.data);
/* FIXME: inefficient; addToStore() will also hash assert(storePath == makeFixedOutputPath(unpack, ht, hash, name));
this. */
hash = hashString(ht, result.data);
if (expectedHash != Hash(ht) && expectedHash != hash)
throw Error(format("hash mismatch for %1%") % uri);
storePath = store->addToStore(name, tmpFile, false, ht);
} }
if (!printPath) if (!printPath)