Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 61 additions & 18 deletions src/libfetchers/fetchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,31 @@

namespace nix::fetchers {

std::unique_ptr<std::vector<std::shared_ptr<InputScheme>>> inputSchemes = nullptr;
using InputSchemeMap = std::map<std::string_view, std::shared_ptr<InputScheme>>;

std::unique_ptr<InputSchemeMap> inputSchemes = nullptr;

void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
{
if (!inputSchemes) inputSchemes = std::make_unique<std::vector<std::shared_ptr<InputScheme>>>();
inputSchemes->push_back(std::move(inputScheme));
if (!inputSchemes)
inputSchemes = std::make_unique<InputSchemeMap>();
auto schemeName = inputScheme->schemeName();
if (inputSchemes->count(schemeName) > 0)
throw Error("Input scheme with name %s already registered", schemeName);
inputSchemes->insert_or_assign(schemeName, std::move(inputScheme));
}

nlohmann::json dumpRegisterInputSchemeInfo() {
using nlohmann::json;

auto res = json::object();

for (auto & [name, scheme] : *inputSchemes) {
auto & r = res[name] = json::object();
r["allowedAttrs"] = scheme->allowedAttrs();
}

return res;
}

Input Input::fromURL(const std::string & url, bool requireTree)
Expand All @@ -33,7 +52,7 @@ static void fixupInput(Input & input)

Input Input::fromURL(const ParsedURL & url, bool requireTree)
{
for (auto & inputScheme : *inputSchemes) {
for (auto & [_, inputScheme] : *inputSchemes) {
auto res = inputScheme->inputFromURL(url, requireTree);
if (res) {
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
Expand All @@ -48,20 +67,44 @@ Input Input::fromURL(const ParsedURL & url, bool requireTree)

Input Input::fromAttrs(Attrs && attrs)
{
for (auto & inputScheme : *inputSchemes) {
auto res = inputScheme->inputFromAttrs(attrs);
if (res) {
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
res->scheme = inputScheme;
fixupInput(*res);
return std::move(*res);
}
}
auto schemeName = ({
auto schemeNameOpt = maybeGetStrAttr(attrs, "type");
if (!schemeNameOpt)
throw Error("'type' attribute to specify input scheme is required but not provided");
*std::move(schemeNameOpt);
});

Input input;
input.attrs = attrs;
fixupInput(input);
return input;
auto raw = [&]() {
// Return an input without a scheme; most operations will fail,
// but not all of them. Doing this is to support those other
// operations which are supposed to be robust on
// unknown/uninterpretable inputs.
Input input;
input.attrs = attrs;
fixupInput(input);
return input;
};

std::shared_ptr<InputScheme> inputScheme = ({
auto i = inputSchemes->find(schemeName);
i == inputSchemes->end() ? nullptr : i->second;
});

if (!inputScheme) return raw();

experimentalFeatureSettings.require(inputScheme->experimentalFeature());

auto allowedAttrs = inputScheme->allowedAttrs();

for (auto & [name, _] : attrs)
if (name != "type" && allowedAttrs.count(name) == 0)
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);

auto res = inputScheme->inputFromAttrs(attrs);
if (!res) return raw();
res->scheme = inputScheme;
fixupInput(*res);
return std::move(*res);
}

ParsedURL Input::toURL() const
Expand Down Expand Up @@ -307,7 +350,7 @@ void InputScheme::clone(const Input & input, const Path & destDir) const
throw Error("do not know how to clone input '%s'", input.to_string());
}

std::optional<ExperimentalFeature> InputScheme::experimentalFeature()
std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const
{
return {};
}
Expand Down
23 changes: 22 additions & 1 deletion src/libfetchers/fetchers.hh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "url.hh"

#include <memory>
#include <nlohmann/json_fwd.hpp>

namespace nix { class Store; }

Expand Down Expand Up @@ -126,6 +127,24 @@ struct InputScheme

virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;

/**
* What is the name of the scheme?
*
* The `type` attribute is used to select which input scheme is
* used, and then the other fields are forwarded to that input
* scheme.
*/
virtual std::string_view schemeName() const = 0;

/**
* Allowed attributes in an attribute set that is converted to an
* input.
*
* `type` is not included from this set, because the `type` field is
parsed first to choose which scheme; `type` is always required.
*/
virtual StringSet allowedAttrs() const = 0;

virtual ParsedURL toURL(const Input & input) const;

virtual Input applyOverrides(
Expand All @@ -144,12 +163,14 @@ struct InputScheme
/**
* Is this `InputScheme` part of an experimental feature?
*/
virtual std::optional<ExperimentalFeature> experimentalFeature();
virtual std::optional<ExperimentalFeature> experimentalFeature() const;

virtual bool isDirect(const Input & input) const
{ return true; }
};

void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);

nlohmann::json dumpRegisterInputSchemeInfo();

}
28 changes: 23 additions & 5 deletions src/libfetchers/git.cc
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,32 @@ struct GitInputScheme : InputScheme
return inputFromAttrs(attrs);
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override

std::string_view schemeName() const override
{
if (maybeGetStrAttr(attrs, "type") != "git") return {};
return "git";
}

for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name" && name != "dirtyRev" && name != "dirtyShortRev")
throw Error("unsupported Git input attribute '%s'", name);
StringSet allowedAttrs() const override
{
return {
"url",
"ref",
"rev",
"shallow",
"submodules",
"lastModified",
"revCount",
"narHash",
"allRefs",
"name",
"dirtyRev",
"dirtyShortRev",
};
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
maybeGetBoolAttr(attrs, "shallow");
maybeGetBoolAttr(attrs, "submodules");
maybeGetBoolAttr(attrs, "allRefs");
Expand Down
35 changes: 20 additions & 15 deletions src/libfetchers/github.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@ std::regex hostRegex(hostRegexS, std::regex::ECMAScript);

struct GitArchiveInputScheme : InputScheme
{
virtual std::string type() const = 0;

virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;

std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{
if (url.scheme != type()) return {};
if (url.scheme != schemeName()) return {};

auto path = tokenizeString<std::vector<std::string>>(url.path, "/");

Expand Down Expand Up @@ -91,7 +89,7 @@ struct GitArchiveInputScheme : InputScheme
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev());

Input input;
input.attrs.insert_or_assign("type", type());
input.attrs.insert_or_assign("type", std::string { schemeName() });
input.attrs.insert_or_assign("owner", path[0]);
input.attrs.insert_or_assign("repo", path[1]);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
Expand All @@ -101,14 +99,21 @@ struct GitArchiveInputScheme : InputScheme
return input;
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
StringSet allowedAttrs() const override
{
if (maybeGetStrAttr(attrs, "type") != type()) return {};

for (auto & [name, value] : attrs)
if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host")
throw Error("unsupported input attribute '%s'", name);
return {
"owner",
"repo",
"ref",
"rev",
"narHash",
"lastModified",
"host",
};
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
getStrAttr(attrs, "owner");
getStrAttr(attrs, "repo");

Expand All @@ -128,7 +133,7 @@ struct GitArchiveInputScheme : InputScheme
if (ref) path += "/" + *ref;
if (rev) path += "/" + rev->to_string(HashFormat::Base16, false);
return ParsedURL {
.scheme = type(),
.scheme = std::string { schemeName() },
.path = path,
};
}
Expand Down Expand Up @@ -220,15 +225,15 @@ struct GitArchiveInputScheme : InputScheme
return {result.storePath, input};
}

std::optional<ExperimentalFeature> experimentalFeature() override
std::optional<ExperimentalFeature> experimentalFeature() const override
{
return Xp::Flakes;
}
};

struct GitHubInputScheme : GitArchiveInputScheme
{
std::string type() const override { return "github"; }
std::string_view schemeName() const override { return "github"; }

std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{
Expand Down Expand Up @@ -309,7 +314,7 @@ struct GitHubInputScheme : GitArchiveInputScheme

struct GitLabInputScheme : GitArchiveInputScheme
{
std::string type() const override { return "gitlab"; }
std::string_view schemeName() const override { return "gitlab"; }

std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{
Expand Down Expand Up @@ -377,7 +382,7 @@ struct GitLabInputScheme : GitArchiveInputScheme

struct SourceHutInputScheme : GitArchiveInputScheme
{
std::string type() const override { return "sourcehut"; }
std::string_view schemeName() const override { return "sourcehut"; }

std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{
Expand Down
21 changes: 15 additions & 6 deletions src/libfetchers/indirect.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,23 @@ struct IndirectInputScheme : InputScheme
return input;
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
std::string_view schemeName() const override
{
if (maybeGetStrAttr(attrs, "type") != "indirect") return {};
return "indirect";
}

for (auto & [name, value] : attrs)
if (name != "type" && name != "id" && name != "ref" && name != "rev" && name != "narHash")
throw Error("unsupported indirect input attribute '%s'", name);
StringSet allowedAttrs() const override
{
return {
"id",
"ref",
"rev",
"narHash",
};
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
auto id = getStrAttr(attrs, "id");
if (!std::regex_match(id, flakeRegex))
throw BadURL("'%s' is not a valid flake ID", id);
Expand Down Expand Up @@ -92,7 +101,7 @@ struct IndirectInputScheme : InputScheme
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
}

std::optional<ExperimentalFeature> experimentalFeature() override
std::optional<ExperimentalFeature> experimentalFeature() const override
{
return Xp::Flakes;
}
Expand Down
21 changes: 16 additions & 5 deletions src/libfetchers/mercurial.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,25 @@ struct MercurialInputScheme : InputScheme
return inputFromAttrs(attrs);
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
std::string_view schemeName() const override
{
if (maybeGetStrAttr(attrs, "type") != "hg") return {};
return "hg";
}

for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "revCount" && name != "narHash" && name != "name")
throw Error("unsupported Mercurial input attribute '%s'", name);
StringSet allowedAttrs() const override
{
return {
"url",
"ref",
"rev",
"revCount",
"narHash",
"name",
};
}

std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{
parseURL(getStrAttr(attrs, "url"));

if (auto ref = maybeGetStrAttr(attrs, "ref")) {
Expand Down
Loading