Don't require .tar/.zip extension for tarball flakerefs

Special-casing the file name is rather ugly, so we shouldn't do
that. So now any {file,http,https} URL is handled by
TarballInputScheme, except for non-flake inputs (i.e. inputs that have
the attribute `flake = false`).
This commit is contained in:
Eelco Dolstra 2023-08-01 16:07:20 +02:00
parent dcdd5fed74
commit d9e7758f47
10 changed files with 37 additions and 25 deletions

View file

@ -105,7 +105,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
}; };
return std::make_pair( return std::make_pair(
FlakeRef(Input::fromURL(parsedURL), ""), FlakeRef(Input::fromURL(parsedURL, isFlake), ""),
percentDecode(match.str(6))); percentDecode(match.str(6)));
} }
@ -176,7 +176,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
parsedURL.query.insert_or_assign("shallow", "1"); parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair( return std::make_pair(
FlakeRef(Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")), FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")),
fragment); fragment);
} }
@ -204,7 +204,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::string fragment; std::string fragment;
std::swap(fragment, parsedURL.fragment); std::swap(fragment, parsedURL.fragment);
auto input = Input::fromURL(parsedURL); auto input = Input::fromURL(parsedURL, isFlake);
input.parent = baseDir; input.parent = baseDir;
return std::make_pair( return std::make_pair(

View file

@ -13,9 +13,9 @@ void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
inputSchemes->push_back(std::move(inputScheme)); inputSchemes->push_back(std::move(inputScheme));
} }
Input Input::fromURL(const std::string & url) Input Input::fromURL(const std::string & url, bool requireTree)
{ {
return fromURL(parseURL(url)); return fromURL(parseURL(url), requireTree);
} }
static void fixupInput(Input & input) static void fixupInput(Input & input)
@ -31,10 +31,10 @@ static void fixupInput(Input & input)
input.locked = true; input.locked = true;
} }
Input Input::fromURL(const ParsedURL & url) Input Input::fromURL(const ParsedURL & url, bool requireTree)
{ {
for (auto & inputScheme : *inputSchemes) { for (auto & inputScheme : *inputSchemes) {
auto res = inputScheme->inputFromURL(url); auto res = inputScheme->inputFromURL(url, requireTree);
if (res) { if (res) {
res->scheme = inputScheme; res->scheme = inputScheme;
fixupInput(*res); fixupInput(*res);

View file

@ -44,9 +44,9 @@ struct Input
std::optional<Path> parent; std::optional<Path> parent;
public: public:
static Input fromURL(const std::string & url); static Input fromURL(const std::string & url, bool requireTree = true);
static Input fromURL(const ParsedURL & url); static Input fromURL(const ParsedURL & url, bool requireTree = true);
static Input fromAttrs(Attrs && attrs); static Input fromAttrs(Attrs && attrs);
@ -129,7 +129,7 @@ struct InputScheme
virtual ~InputScheme() virtual ~InputScheme()
{ } { }
virtual std::optional<Input> inputFromURL(const ParsedURL & url) const = 0; virtual std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const = 0;
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0; virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;

View file

@ -256,7 +256,7 @@ std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, co
struct GitInputScheme : InputScheme struct GitInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "git" && if (url.scheme != "git" &&
url.scheme != "git+http" && url.scheme != "git+http" &&

View file

@ -30,7 +30,7 @@ struct GitArchiveInputScheme : InputScheme
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) 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) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != type()) return {}; if (url.scheme != type()) return {};

View file

@ -7,7 +7,7 @@ std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
struct IndirectInputScheme : InputScheme struct IndirectInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "flake") return {}; if (url.scheme != "flake") return {};

View file

@ -43,7 +43,7 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
struct MercurialInputScheme : InputScheme struct MercurialInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "hg+http" && if (url.scheme != "hg+http" &&
url.scheme != "hg+https" && url.scheme != "hg+https" &&

View file

@ -6,7 +6,7 @@ namespace nix::fetchers {
struct PathInputScheme : InputScheme struct PathInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "path") return {}; if (url.scheme != "path") return {};

View file

@ -194,11 +194,11 @@ struct CurlInputScheme : InputScheme
|| hasSuffix(path, ".tar.zst"); || hasSuffix(path, ".tar.zst");
} }
virtual bool isValidURL(const ParsedURL & url) const = 0; virtual bool isValidURL(const ParsedURL & url, bool requireTree) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & _url) const override std::optional<Input> inputFromURL(const ParsedURL & _url, bool requireTree) const override
{ {
if (!isValidURL(_url)) if (!isValidURL(_url, requireTree))
return std::nullopt; return std::nullopt;
Input input; Input input;
@ -265,13 +265,13 @@ struct FileInputScheme : CurlInputScheme
{ {
const std::string inputType() const override { return "file"; } const std::string inputType() const override { return "file"; }
bool isValidURL(const ParsedURL & url) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType() ? parsedUrlScheme.application.value() == inputType()
: !hasTarballExtension(url.path)); : (!requireTree && !hasTarballExtension(url.path)));
} }
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
@ -285,14 +285,14 @@ struct TarballInputScheme : CurlInputScheme
{ {
const std::string inputType() const override { return "tarball"; } const std::string inputType() const override { return "tarball"; }
bool isValidURL(const ParsedURL & url) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType() ? parsedUrlScheme.application.value() == inputType()
: hasTarballExtension(url.path)); : (requireTree || hasTarballExtension(url.path)));
} }
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override

View file

@ -27,6 +27,7 @@ test_file_flake_input () {
mkdir inputs mkdir inputs
echo foo > inputs/test_input_file echo foo > inputs/test_input_file
echo '{ outputs = { self }: { }; }' > inputs/flake.nix
tar cfa test_input.tar.gz inputs tar cfa test_input.tar.gz inputs
cp test_input.tar.gz test_input_no_ext cp test_input.tar.gz test_input_no_ext
input_tarball_hash="$(nix hash path test_input.tar.gz)" input_tarball_hash="$(nix hash path test_input.tar.gz)"
@ -50,6 +51,9 @@ test_file_flake_input () {
url = "file+file://$PWD/test_input.tar.gz"; url = "file+file://$PWD/test_input.tar.gz";
flake = false; flake = false;
}; };
inputs.flake_no_ext = {
url = "file://$PWD/test_input_no_ext";
};
outputs = { ... }: {}; outputs = { ... }: {};
} }
EOF EOF
@ -58,7 +62,7 @@ EOF
nix eval --file - <<EOF nix eval --file - <<EOF
with (builtins.fromJSON (builtins.readFile ./flake.lock)); with (builtins.fromJSON (builtins.readFile ./flake.lock));
# Url inputs whose extension doesnt match a known archive format should # Non-flake inputs whose extension doesnt match a known archive format should
# not be unpacked by default # not be unpacked by default
assert (nodes.no_ext_default_no_unpack.locked.type == "file"); assert (nodes.no_ext_default_no_unpack.locked.type == "file");
assert (nodes.no_ext_default_no_unpack.locked.unpack or false == false); assert (nodes.no_ext_default_no_unpack.locked.unpack or false == false);
@ -75,8 +79,16 @@ EOF
# Explicitely passing the unpack parameter should enforce the desired behavior # Explicitely passing the unpack parameter should enforce the desired behavior
assert (nodes.no_ext_explicit_unpack.locked.narHash == nodes.tarball_default_unpack.locked.narHash); assert (nodes.no_ext_explicit_unpack.locked.narHash == nodes.tarball_default_unpack.locked.narHash);
assert (nodes.tarball_explicit_no_unpack.locked.narHash == nodes.no_ext_default_no_unpack.locked.narHash); assert (nodes.tarball_explicit_no_unpack.locked.narHash == nodes.no_ext_default_no_unpack.locked.narHash);
# Flake inputs should always be tarballs
assert (nodes.flake_no_ext.locked.type == "tarball");
true true
EOF EOF
# Test tarball URLs on the command line.
[[ $(nix flake metadata --json file://$PWD/test_input_no_ext | jq -r .resolved.type) = tarball ]]
popd popd
[[ -z "${NIX_DAEMON_PACKAGE-}" ]] && return 0 [[ -z "${NIX_DAEMON_PACKAGE-}" ]] && return 0