aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/flake/flakeref.cc
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2020-01-21 16:27:53 +0100
committerEelco Dolstra <edolstra@gmail.com>2020-01-21 22:56:04 +0100
commit9f4d8c6170517c9452e25dc29c56a6fbb43d40a1 (patch)
tree25295dae9cd204f603b41ae59bc32cd9cb0ce88e /src/libexpr/flake/flakeref.cc
parent1bf9eb21b75f0d93d9c1633ea2e6fdf840047e79 (diff)
Pluggable fetchers
Flakes are now fetched using an extensible mechanism. Also lots of other flake cleanups.
Diffstat (limited to 'src/libexpr/flake/flakeref.cc')
-rw-r--r--src/libexpr/flake/flakeref.cc362
1 files changed, 122 insertions, 240 deletions
diff --git a/src/libexpr/flake/flakeref.cc b/src/libexpr/flake/flakeref.cc
index ff7c725cb..397b1b84b 100644
--- a/src/libexpr/flake/flakeref.cc
+++ b/src/libexpr/flake/flakeref.cc
@@ -1,283 +1,165 @@
#include "flakeref.hh"
#include "store-api.hh"
-
-#include <regex>
+#include "fetchers/parse.hh"
+#include "fetchers/fetchers.hh"
+#include "fetchers/registry.hh"
+#include "fetchers/regex.hh"
namespace nix {
-// A Git ref (i.e. branch or tag name).
-const static std::string refRegex = "[a-zA-Z0-9][a-zA-Z0-9_.-]*"; // FIXME: check
-
-// A Git revision (a SHA-1 commit hash).
-const static std::string revRegexS = "[0-9a-fA-F]{40}";
-std::regex revRegex(revRegexS, std::regex::ECMAScript);
-
-// A Git ref or revision.
-const static std::string revOrRefRegex = "(?:(" + revRegexS + ")|(" + refRegex + "))";
-
-// A rev ("e72daba8250068216d79d2aeef40d4d95aff6666"), or a ref
-// optionally followed by a rev (e.g. "master" or
-// "master/e72daba8250068216d79d2aeef40d4d95aff6666").
-const static std::string refAndOrRevRegex = "(?:(" + revRegexS + ")|(?:(" + refRegex + ")(?:/(" + revRegexS + "))?))";
-
-const static std::string flakeId = "[a-zA-Z][a-zA-Z0-9_-]*";
-
-// GitHub references.
-const static std::string ownerRegex = "[a-zA-Z][a-zA-Z0-9_-]*";
-const static std::string repoRegex = "[a-zA-Z][a-zA-Z0-9_-]*";
-
-// URI stuff.
-const static std::string schemeRegex = "[a-z+]+";
-const static std::string authorityRegex = "[a-zA-Z0-9._~-]*";
-const static std::string segmentRegex = "[a-zA-Z0-9._~-]+";
-const static std::string pathRegex = "/?" + segmentRegex + "(?:/" + segmentRegex + ")*";
-
+#if 0
// 'dir' path elements cannot start with a '.'. We also reject
// potentially dangerous characters like ';'.
const static std::string subDirElemRegex = "(?:[a-zA-Z0-9_-]+[a-zA-Z0-9._-]*)";
const static std::string subDirRegex = subDirElemRegex + "(?:/" + subDirElemRegex + ")*";
+#endif
-FlakeRef::FlakeRef(const std::string & uri_, bool allowRelative)
+std::string FlakeRef::to_string() const
{
- // FIXME: could combine this into one regex.
-
- static std::regex flakeRegex(
- "(?:flake:)?(" + flakeId + ")(?:/(?:" + refAndOrRevRegex + "))?",
- std::regex::ECMAScript);
-
- static std::regex githubRegex(
- "github:(" + ownerRegex + ")/(" + repoRegex + ")(?:/" + revOrRefRegex + ")?",
- std::regex::ECMAScript);
-
- static std::regex uriRegex(
- "((" + schemeRegex + "):" +
- "(?://(" + authorityRegex + "))?" +
- "(" + pathRegex + "))",
- std::regex::ECMAScript);
+ return input->to_string();
+}
- static std::regex refRegex2(refRegex, std::regex::ECMAScript);
+bool FlakeRef::isDirect() const
+{
+ return input->isDirect();
+}
- static std::regex subDirRegex2(subDirRegex, std::regex::ECMAScript);
+bool FlakeRef::isImmutable() const
+{
+ return input->isImmutable();
+}
- auto [uri2, params] = splitUriAndParams(uri_);
- std::string uri(uri2);
+std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef)
+{
+ str << flakeRef.to_string();
+ return str;
+}
- auto handleSubdir = [&](const std::string & name, const std::string & value) {
- if (name == "dir") {
- if (value != "" && !std::regex_match(value, subDirRegex2))
- throw BadFlakeRef("flake '%s' has invalid subdirectory '%s'", uri, value);
- subdir = value;
- return true;
- } else
- return false;
- };
+bool FlakeRef::operator==(const FlakeRef & other) const
+{
+ return *input == *other.input && subdir == other.subdir;
+}
- auto handleGitParams = [&](const std::string & name, const std::string & value) {
- if (name == "rev") {
- if (!std::regex_match(value, revRegex))
- throw BadFlakeRef("invalid Git revision '%s'", value);
- rev = Hash(value, htSHA1);
- } else if (name == "ref") {
- if (!std::regex_match(value, refRegex2))
- throw BadFlakeRef("invalid Git ref '%s'", value);
- ref = value;
- } else if (handleSubdir(name, value))
- ;
- else return false;
- return true;
- };
+FlakeRef FlakeRef::resolve(ref<Store> store) const
+{
+ return FlakeRef(lookupInRegistries(store, input), subdir);
+}
- std::smatch match;
- if (std::regex_match(uri, match, flakeRegex)) {
- IsId d;
- d.id = match[1];
- if (match[2].matched)
- rev = Hash(match[2], htSHA1);
- else if (match[3].matched) {
- ref = match[3];
- if (match[4].matched)
- rev = Hash(match[4], htSHA1);
- }
- data = d;
- }
+FlakeRef parseFlakeRef(
+ const std::string & url, const std::optional<Path> & baseDir)
+{
+ auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir);
+ if (fragment != "")
+ throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url);
+ return flakeRef;
+}
- else if (std::regex_match(uri, match, githubRegex)) {
- IsGitHub d;
- d.owner = match[1];
- d.repo = match[2];
- if (match[3].matched)
- rev = Hash(match[3], htSHA1);
- else if (match[4].matched) {
- ref = match[4];
- }
- for (auto & param : params) {
- if (handleSubdir(param.first, param.second))
- ;
- else
- throw BadFlakeRef("invalid Git flakeref parameter '%s', in '%s'", param.first, uri);
- }
- data = d;
+std::optional<FlakeRef> maybeParseFlakeRef(
+ const std::string & url, const std::optional<Path> & baseDir)
+{
+ try {
+ return parseFlakeRef(url, baseDir);
+ } catch (Error &) {
+ return {};
}
+}
- else if (std::regex_match(uri, match, uriRegex)) {
- auto & scheme = match[2];
- if (scheme == "git" ||
- scheme == "git+http" ||
- scheme == "git+https" ||
- scheme == "git+ssh" ||
- scheme == "git+file" ||
- scheme == "file")
- {
- IsGit d;
- d.uri = match[1];
- for (auto & param : params) {
- if (handleGitParams(param.first, param.second))
- ;
- else
- // FIXME: should probably pass through unknown parameters
- throw BadFlakeRef("invalid Git flakeref parameter '%s', in '%s'", param.first, uri);
- }
- if (rev && !ref)
- throw BadFlakeRef("flake URI '%s' lacks a Git ref", uri);
- data = d;
- } else
- throw BadFlakeRef("unsupported URI scheme '%s' in flake reference '%s'", scheme, uri);
- }
+std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
+ const std::string & url, const std::optional<Path> & baseDir)
+{
+ using namespace fetchers;
- else if ((hasPrefix(uri, "/") || (allowRelative && (hasPrefix(uri, "./") || hasPrefix(uri, "../") || uri == ".")))
- && uri.find(':') == std::string::npos)
- {
- IsPath d;
- if (allowRelative) {
- d.path = absPath(uri);
- try {
- if (!S_ISDIR(lstat(d.path).st_mode))
- throw MissingFlake("path '%s' is not a flake (sub)directory", d.path);
- } catch (SysError & e) {
- if (e.errNo == ENOENT || e.errNo == EISDIR)
- throw MissingFlake("flake '%s' does not exist", d.path);
- throw;
- }
- while (true) {
- if (pathExists(d.path + "/.git")) break;
- subdir = std::string(baseNameOf(d.path)) + (subdir.empty() ? "" : "/" + subdir);
- d.path = dirOf(d.path);
- if (d.path == "/")
- throw MissingFlake("path '%s' is not a flake (because it does not reference a Git repository)", uri);
- }
- } else
- d.path = canonPath(uri);
- data = d;
- for (auto & param : params) {
- if (handleGitParams(param.first, param.second))
- ;
- else
- throw BadFlakeRef("invalid Git flakeref parameter '%s', in '%s'", param.first, uri);
- }
- }
+ static std::regex pathUrlRegex(
+ "(" + pathRegex + "/?)"
+ + "(?:\\?(" + queryRegex + "))?"
+ + "(?:#(" + queryRegex + "))?",
+ std::regex::ECMAScript);
- else
- throw BadFlakeRef("'%s' is not a valid flake reference", uri);
-}
+ static std::regex flakeRegex(
+ "((" + flakeId + ")(?:/(?:" + refAndOrRevRegex + "))?)"
+ + "(?:#(" + queryRegex + "))?",
+ std::regex::ECMAScript);
-std::string FlakeRef::to_string() const
-{
- std::string string;
- bool first = true;
+ std::smatch match;
- auto addParam =
- [&](const std::string & name, std::string value) {
- string += first ? '?' : '&';
- first = false;
- string += name;
- string += '=';
- string += value; // FIXME: escaping
+ /* Check if 'url' is a flake ID. This is an abbreviated syntax for
+ 'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
+
+ if (std::regex_match(url, match, flakeRegex)) {
+ auto parsedURL = ParsedURL{
+ .url = url,
+ .base = "flake:" + std::string(match[1]),
+ .scheme = "flake",
+ .authority = "",
+ .path = match[1],
+ .fragment = percentDecode(std::string(match[6]))
};
- if (auto refData = std::get_if<FlakeRef::IsId>(&data)) {
- string = refData->id;
- if (ref) string += '/' + *ref;
- if (rev) string += '/' + rev->gitRev();
+ return std::make_pair(
+ FlakeRef(inputFromURL(parsedURL), ""),
+ parsedURL.fragment);
}
- else if (auto refData = std::get_if<FlakeRef::IsPath>(&data)) {
- string = refData->path;
- if (ref) addParam("ref", *ref);
- if (rev) addParam("rev", rev->gitRev());
- if (subdir != "") addParam("dir", subdir);
- }
+ /* Check if 'url' is a path (either absolute or relative to
+ 'baseDir'). If so, search upward to the root of the repo
+ (i.e. the directory containing .git). */
+
+ else if (std::regex_match(url, match, pathUrlRegex)) {
+ std::string path = match[1];
+ if (!baseDir && !hasPrefix(path, "/"))
+ throw BadURL("flake reference '%s' is not an absolute path", url);
+ path = absPath(path, baseDir, true);
+
+ 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);
+ }
- else if (auto refData = std::get_if<FlakeRef::IsGitHub>(&data)) {
- assert(!(ref && rev));
- string = "github:" + refData->owner + "/" + refData->repo;
- if (ref) { string += '/'; string += *ref; }
- if (rev) { string += '/'; string += rev->gitRev(); }
- if (subdir != "") addParam("dir", subdir);
- }
+ auto base = std::string("git+file://") + flakeRoot;
- else if (auto refData = std::get_if<FlakeRef::IsGit>(&data)) {
- assert(!rev || ref);
- string = refData->uri;
+ auto parsedURL = ParsedURL{
+ .url = base, // FIXME
+ .base = base,
+ .scheme = "git+file",
+ .authority = "",
+ .path = flakeRoot,
+ .query = decodeQuery(match[2]),
+ .fragment = percentDecode(std::string(match[3]))
+ };
- if (ref) {
- addParam("ref", *ref);
- if (rev)
- addParam("rev", rev->gitRev());
+ if (subdir != "") {
+ if (parsedURL.query.count("subdir"))
+ throw Error("flake URL '%s' has an inconsistent 'subdir' parameter", url);
+ parsedURL.query.insert_or_assign("subdir", subdir);
}
- if (subdir != "") addParam("dir", subdir);
+ return std::make_pair(
+ FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "subdir").value_or("")),
+ parsedURL.fragment);
}
- else abort();
-
- assert(FlakeRef(string) == *this);
-
- return string;
-}
-
-std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef)
-{
- str << flakeRef.to_string();
- return str;
-}
-
-bool FlakeRef::isImmutable() const
-{
- return (bool) rev;
-}
-
-FlakeRef FlakeRef::baseRef() const // Removes the ref and rev from a FlakeRef.
-{
- FlakeRef result(*this);
- result.ref = std::nullopt;
- result.rev = std::nullopt;
- return result;
-}
-
-bool FlakeRef::contains(const FlakeRef & other) const
-{
- if (!(data == other.data))
- return false;
-
- if (ref && ref != other.ref)
- return false;
-
- if (rev && rev != other.rev)
- return false;
-
- if (subdir != other.subdir)
- return false;
-
- return true;
+ else {
+ auto parsedURL = parseURL(url);
+ return std::make_pair(
+ FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "subdir").value_or("")),
+ parsedURL.fragment);
+ }
}
-std::optional<FlakeRef> parseFlakeRef(
- const std::string & uri, bool allowRelative)
+std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
+ const std::string & url, const std::optional<Path> & baseDir)
{
try {
- return FlakeRef(uri, allowRelative);
- } catch (BadFlakeRef & e) {
+ return parseFlakeRefWithFragment(url, baseDir);
+ } catch (Error & e) {
+ printError("FOO: %s", e.what());
return {};
}
}