From 6521c92ce8289a5f9e959c6789ab24dacdad082e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 27 Apr 2020 22:53:11 +0200 Subject: [PATCH] Improve path:// handling In particular, doing 'nix build /path/to/dir' now works if /path/to/dir is not a Git tree (it only has to contain a flake.nix file). Also, 'nix flake init' no longer requires a Git tree (but it will do a 'git add flake.nix' if it's a Git tree) --- src/libexpr/flake/flake.cc | 4 +- src/libexpr/flake/flakeref.cc | 70 +++++++++++++++++++---------------- src/libexpr/flake/flakeref.hh | 4 +- src/libfetchers/path.cc | 24 +++++++++++- src/nix/flake.cc | 11 +++--- src/nix/installables.cc | 47 +++++++++++++---------- 6 files changed, 99 insertions(+), 61 deletions(-) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 55a4fe65b..0f5770019 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -176,7 +176,7 @@ static FlakeInput parseFlakeInput(EvalState & state, if (!attrs.empty()) throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos); if (url) - input.ref = parseFlakeRef(*url); + input.ref = parseFlakeRef(*url, {}, true); } return input; @@ -630,7 +630,7 @@ void callFlake(EvalState & state, static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) { auto flakeRefS = state.forceStringNoCtx(*args[0], pos); - auto flakeRef = parseFlakeRef(flakeRefS); + auto flakeRef = parseFlakeRef(flakeRefS, {}, true); if (evalSettings.pureEval && !flakeRef.input->isImmutable()) throw Error("cannot call 'getFlake' on mutable flake reference '%s', at %s (use --impure to override)", flakeRefS, pos); diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc index de91f2eed..a70261a41 100644 --- a/src/libexpr/flake/flakeref.cc +++ b/src/libexpr/flake/flakeref.cc @@ -47,9 +47,9 @@ FlakeRef FlakeRef::resolve(ref store) const } FlakeRef parseFlakeRef( - const std::string & url, const std::optional & baseDir) + const std::string & url, const std::optional & baseDir, bool allowMissing) { - auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir); + auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir, allowMissing); if (fragment != "") throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url); return flakeRef; @@ -66,7 +66,7 @@ std::optional maybeParseFlakeRef( } std::pair parseFlakeRefWithFragment( - const std::string & url, const std::optional & baseDir) + const std::string & url, const std::optional & baseDir, bool allowMissing) { using namespace fetchers; @@ -115,39 +115,47 @@ std::pair parseFlakeRefWithFragment( if (!S_ISDIR(lstat(path).st_mode)) throw BadURL("path '%s' is not a flake (because it's not a directory)", path); - auto flakeRoot = path; - std::string subdir; - - while (true) { - if (pathExists(flakeRoot + "/.git")) break; - subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir); - flakeRoot = dirOf(flakeRoot); - if (flakeRoot == "/") - throw BadURL("path '%s' is not a flake (because it does not reference a Git repository)", path); - } - - auto base = std::string("git+file://") + flakeRoot; - - auto parsedURL = ParsedURL{ - .url = base, // FIXME - .base = base, - .scheme = "git+file", - .authority = "", - .path = flakeRoot, - .query = decodeQuery(match[2]), - }; + if (!allowMissing && !pathExists(path + "/flake.nix")) + throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path); auto fragment = percentDecode(std::string(match[3])); - if (subdir != "") { - if (parsedURL.query.count("dir")) - throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url); - parsedURL.query.insert_or_assign("dir", subdir); + auto flakeRoot = path; + std::string subdir; + + while (flakeRoot != "/") { + if (pathExists(flakeRoot + "/.git")) { + auto base = std::string("git+file://") + flakeRoot; + + auto parsedURL = ParsedURL{ + .url = base, // FIXME + .base = base, + .scheme = "git+file", + .authority = "", + .path = flakeRoot, + .query = decodeQuery(match[2]), + }; + + if (subdir != "") { + if (parsedURL.query.count("dir")) + throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url); + parsedURL.query.insert_or_assign("dir", subdir); + } + + return std::make_pair( + FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "dir").value_or("")), + fragment); + } + + subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir); + flakeRoot = dirOf(flakeRoot); } - return std::make_pair( - FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "dir").value_or("")), - fragment); + fetchers::Attrs attrs; + attrs.insert_or_assign("type", "path"); + attrs.insert_or_assign("path", path); + + return std::make_pair(FlakeRef(inputFromAttrs(attrs), ""), fragment); } else { diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh index 1dbb6e54d..72cbb2908 100644 --- a/src/libexpr/flake/flakeref.hh +++ b/src/libexpr/flake/flakeref.hh @@ -41,13 +41,13 @@ struct FlakeRef std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef); FlakeRef parseFlakeRef( - const std::string & url, const std::optional & baseDir = {}); + const std::string & url, const std::optional & baseDir = {}, bool allowMissing = false); std::optional maybeParseFlake( const std::string & url, const std::optional & baseDir = {}); std::pair parseFlakeRefWithFragment( - const std::string & url, const std::optional & baseDir = {}); + const std::string & url, const std::optional & baseDir = {}, bool allowMissing = false); std::optional> maybeParseFlakeRefWithFragment( const std::string & url, const std::optional & baseDir = {}); diff --git a/src/libfetchers/path.cc b/src/libfetchers/path.cc index ba2cc192e..77fe87d59 100644 --- a/src/libfetchers/path.cc +++ b/src/libfetchers/path.cc @@ -32,7 +32,7 @@ struct PathInput : Input bool isImmutable() const override { - return (bool) narHash; + return narHash || rev; } ParsedURL toURL() const override @@ -56,9 +56,20 @@ struct PathInput : Input attrs.emplace("revCount", *revCount); if (lastModified) attrs.emplace("lastModified", *lastModified); + if (!rev && narHash) + attrs.emplace("narHash", narHash->to_string(SRI)); return attrs; } + std::optional getSourcePath() const override + { + return path; + } + + void markChangedFile(std::string_view file, std::optional commitMsg) const override + { + } + std::pair> fetchTreeInternal(nix::ref store) const override { auto input = std::make_shared(*this); @@ -74,6 +85,8 @@ struct PathInput : Input // FIXME: try to substitute storePath. storePath = store->addToStore("source", path); + input->narHash = store->queryPathInfo(*storePath)->narHash; + return { Tree { @@ -99,6 +112,9 @@ struct PathInputScheme : InputScheme auto input = std::make_unique(); input->path = url.path; + if (url.authority && *url.authority != "") + throw Error("path URL '%s' should not have an authority ('%s')", url.url, *url.authority); + for (auto & [name, value] : url.query) if (name == "rev") input->rev = Hash(value, htSHA1); @@ -114,6 +130,9 @@ struct PathInputScheme : InputScheme throw Error("path URL '%s' has invalid parameter '%s'", url.to_string(), name); input->lastModified = lastModified; } + else if (name == "narHash") + // FIXME: require SRI hash. + input->narHash = Hash(value); else throw Error("path URL '%s' has unsupported parameter '%s'", url.to_string(), name); @@ -134,6 +153,9 @@ struct PathInputScheme : InputScheme input->revCount = getIntAttr(attrs, "revCount"); else if (name == "lastModified") input->lastModified = getIntAttr(attrs, "lastModified"); + else if (name == "narHash") + // FIXME: require SRI hash. + input->narHash = Hash(getStrAttr(attrs, "narHash")); else if (name == "type" || name == "path") ; else diff --git a/src/nix/flake.cc b/src/nix/flake.cc index c09ba3610..c47d51fe4 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -551,17 +551,18 @@ struct CmdFlakeInit : virtual Args, Command { Path flakeDir = absPath("."); - if (!pathExists(flakeDir + "/.git")) - throw Error("the directory '%s' is not a Git repository", flakeDir); - Path flakePath = flakeDir + "/flake.nix"; if (pathExists(flakePath)) throw Error("file '%s' already exists", flakePath); writeFile(flakePath, -#include "flake-template.nix.gen.hh" - ); + #include "flake-template.nix.gen.hh" + ); + + if (pathExists(flakeDir + "/.git")) + runProgram("git", true, + { "-C", flakeDir, "add", "--intent-to-add", "flake.nix" }); } }; diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 3871536e1..6b9e2ee96 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -438,14 +438,6 @@ std::vector> SourceExprCommand::parseInstallables( } else { - auto follow = [&](const std::string & s) -> std::optional { - try { - return store->followLinksToStorePath(s); - } catch (NotInStore &) { - return {}; - } - }; - for (auto & s : ss) { if (hasPrefix(s, "nixpkgs.")) { bool static warned; @@ -456,23 +448,38 @@ std::vector> SourceExprCommand::parseInstallables( } else { - auto res = maybeParseFlakeRefWithFragment(s, absPath(".")); - if (res) { - auto &[flakeRef, fragment] = *res; + std::exception_ptr ex; + + try { + auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath(".")); result.push_back(std::make_shared( *this, std::move(flakeRef), fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment}, getDefaultFlakeAttrPathPrefixes())); - } else { - std::optional storePath; - if (s.find('/') != std::string::npos && (storePath = follow(s))) - result.push_back(std::make_shared(store, store->printStorePath(*storePath))); - else - throw Error( - pathExists(s) - ? "path '%s' is not a flake or a store path" - : "don't know how to handle argument '%s'", s); + continue; + } catch (...) { + ex = std::current_exception(); } + + if (s.find('/') != std::string::npos) { + try { + result.push_back(std::make_shared(store, store->printStorePath(store->followLinksToStorePath(s)))); + continue; + } catch (NotInStore &) { + } catch (...) { + if (!ex) + ex = std::current_exception(); + } + } + + std::rethrow_exception(ex); + + /* + throw Error( + pathExists(s) + ? "path '%s' is not a flake or a store path" + : "don't know how to handle argument '%s'", s); + */ } } }