Merge pull request #6614 from RasmusRendal/spaces

Implement support for percent encoded filepaths for flakerefs
This commit is contained in:
Théophane Hufschmitt 2023-09-26 02:27:09 +02:00 committed by GitHub
commit 9a78d87bc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 330 additions and 266 deletions

View file

@ -1 +1,5 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
- [URL flake references](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references) now support [percent-encoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) characters.
- [Path-like flake references](@docroot@/command-ref/new-cli/nix3-flake.md#path-like-syntax) now accept arbitrary unicode characters (except `#` and `?`).

View file

@ -69,32 +69,130 @@ std::optional<FlakeRef> maybeParseFlakeRef(
} }
} }
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment( std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
const std::string & url, const std::string & url,
const std::optional<Path> & baseDir, const std::optional<Path> & baseDir,
bool allowMissing, bool allowMissing,
bool isFlake) bool isFlake)
{ {
using namespace fetchers; std::string path = url;
std::string fragment = "";
std::map<std::string, std::string> query;
auto pathEnd = url.find_first_of("#?");
auto fragmentStart = pathEnd;
if (pathEnd != std::string::npos && url[pathEnd] == '?') {
fragmentStart = url.find("#");
}
if (pathEnd != std::string::npos) {
path = url.substr(0, pathEnd);
}
if (fragmentStart != std::string::npos) {
fragment = percentDecode(url.substr(fragmentStart+1));
}
if (pathEnd != std::string::npos && fragmentStart != std::string::npos) {
query = decodeQuery(url.substr(pathEnd+1, fragmentStart));
}
static std::string fnRegex = "[0-9a-zA-Z-._~!$&'\"()*+,;=]+"; if (baseDir) {
/* 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). */
static std::regex pathUrlRegex( path = absPath(path, baseDir);
"(/?" + fnRegex + "(?:/" + fnRegex + ")*/?)"
+ "(?:\\?(" + queryRegex + "))?" if (isFlake) {
+ "(?:#(" + queryRegex + "))?",
std::regex::ECMAScript); if (!allowMissing && !pathExists(path + "/flake.nix")){
notice("path '%s' does not contain a 'flake.nix', searching up",path);
// Save device to detect filesystem boundary
dev_t device = lstat(path).st_dev;
bool found = false;
while (path != "/") {
if (pathExists(path + "/flake.nix")) {
found = true;
break;
} else if (pathExists(path + "/.git"))
throw Error("path '%s' is not part of a flake (neither it nor its parent directories contain a 'flake.nix' file)", path);
else {
if (lstat(path).st_dev != device)
throw Error("unable to find a flake before encountering filesystem boundary at '%s'", path);
}
path = dirOf(path);
}
if (!found)
throw BadURL("could not find a flake.nix file");
}
if (!S_ISDIR(lstat(path).st_mode))
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
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 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 = query,
};
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);
}
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
FlakeRef(fetchers::Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")),
fragment);
}
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
flakeRoot = dirOf(flakeRoot);
}
}
} else {
if (!hasPrefix(path, "/"))
throw BadURL("flake reference '%s' is not an absolute path", url);
path = canonPath(path + "/" + getOr(query, "dir", ""));
}
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "path");
attrs.insert_or_assign("path", path);
return std::make_pair(FlakeRef(fetchers::Input::fromAttrs(std::move(attrs)), ""), fragment);
};
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(
const std::string & url,
bool isFlake
)
{
std::smatch match;
static std::regex flakeRegex( static std::regex flakeRegex(
"((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)" "((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)"
+ "(?:#(" + queryRegex + "))?", + "(?:#(" + queryRegex + "))?",
std::regex::ECMAScript); std::regex::ECMAScript);
std::smatch match;
/* 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)) { if (std::regex_match(url, match, flakeRegex)) {
auto parsedURL = ParsedURL{ auto parsedURL = ParsedURL{
.url = url, .url = url,
@ -105,111 +203,53 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
}; };
return std::make_pair( return std::make_pair(
FlakeRef(Input::fromURL(parsedURL, isFlake), ""), FlakeRef(fetchers::Input::fromURL(parsedURL, isFlake), ""),
percentDecode(match.str(6))); percentDecode(match.str(6)));
} }
else if (std::regex_match(url, match, pathUrlRegex)) { return {};
std::string path = match[1]; }
std::string fragment = percentDecode(match.str(3));
if (baseDir) { std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
/* Check if 'url' is a path (either absolute or relative const std::string & url,
to 'baseDir'). If so, search upward to the root of the const std::optional<Path> & baseDir,
repo (i.e. the directory containing .git). */ bool isFlake
)
path = absPath(path, baseDir); {
ParsedURL parsedURL;
if (isFlake) { try {
parsedURL = parseURL(url);
if (!allowMissing && !pathExists(path + "/flake.nix")){ } catch (BadURL &) {
notice("path '%s' does not contain a 'flake.nix', searching up",path); return std::nullopt;
// Save device to detect filesystem boundary
dev_t device = lstat(path).st_dev;
bool found = false;
while (path != "/") {
if (pathExists(path + "/flake.nix")) {
found = true;
break;
} else if (pathExists(path + "/.git"))
throw Error("path '%s' is not part of a flake (neither it nor its parent directories contain a 'flake.nix' file)", path);
else {
if (lstat(path).st_dev != device)
throw Error("unable to find a flake before encountering filesystem boundary at '%s'", path);
}
path = dirOf(path);
}
if (!found)
throw BadURL("could not find a flake.nix file");
}
if (!S_ISDIR(lstat(path).st_mode))
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
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 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);
}
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")),
fragment);
}
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
flakeRoot = dirOf(flakeRoot);
}
}
} else {
if (!hasPrefix(path, "/"))
throw BadURL("flake reference '%s' is not an absolute path", url);
auto query = decodeQuery(match[2]);
path = canonPath(path + "/" + getOr(query, "dir", ""));
}
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "path");
attrs.insert_or_assign("path", path);
return std::make_pair(FlakeRef(Input::fromAttrs(std::move(attrs)), ""), fragment);
} }
else { std::string fragment;
auto parsedURL = parseURL(url); std::swap(fragment, parsedURL.fragment);
std::string fragment;
std::swap(fragment, parsedURL.fragment);
auto input = Input::fromURL(parsedURL, isFlake); auto input = fetchers::Input::fromURL(parsedURL, isFlake);
input.parent = baseDir; input.parent = baseDir;
return std::make_pair( return std::make_pair(
FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")), FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
fragment); fragment);
}
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
const std::string & url,
const std::optional<Path> & baseDir,
bool allowMissing,
bool isFlake)
{
using namespace fetchers;
std::smatch match;
if (auto res = parseFlakeIdRef(url, isFlake)) {
return *res;
} else if (auto res = parseURLFlakeRef(url, baseDir, isFlake)) {
return *res;
} else {
return parsePathFlakeRefWithFragment(url, baseDir, allowMissing, isFlake);
} }
} }

View file

@ -335,4 +335,13 @@ namespace nix {
ASSERT_EQ(d, s); ASSERT_EQ(d, s);
} }
TEST(percentEncode, yen) {
// https://en.wikipedia.org/wiki/Percent-encoding#Character_data
std::string s = reinterpret_cast<const char*>(u8"");
std::string e = "%E5%86%86";
ASSERT_EQ(percentEncode(s), e);
ASSERT_EQ(percentDecode(e), s);
}
} }

View file

@ -103,7 +103,7 @@ std::string percentEncode(std::string_view s, std::string_view keep)
|| keep.find(c) != std::string::npos) || keep.find(c) != std::string::npos)
res += c; res += c;
else else
res += fmt("%%%02X", (unsigned int) c); res += fmt("%%%02X", c & 0xFF);
return res; return res;
} }

View file

@ -67,6 +67,11 @@ inputs.nixpkgs = {
}; };
``` ```
Following [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1),
characters outside of the allowed range (i.e. neither [reserved characters](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2)
nor [unreserved characters](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3))
must be percent-encoded.
### Examples ### Examples
Here are some examples of flake references in their URL-like representation: Here are some examples of flake references in their URL-like representation:
@ -103,10 +108,14 @@ The semantic of such a path is as follows:
2. The filesystem root (/), or 2. The filesystem root (/), or
3. A folder on a different mount point. 3. A folder on a different mount point.
Contrary to URL-like references, path-like flake references can contain arbitrary unicode characters (except `#` and `?`).
### Examples ### Examples
* `.`: The flake to which the current directory belongs to. * `.`: The flake to which the current directory belongs to.
* `/home/alice/src/patchelf`: A flake in some other directory. * `/home/alice/src/patchelf`: A flake in some other directory.
* `./../sub directory/with Ûñî©ôδ€`: A flake in another relative directory that
has Unicode characters in its name.
## Flake reference attributes ## Flake reference attributes

View file

@ -61,10 +61,10 @@ createGitRepo() {
local repo="$1" local repo="$1"
local extraArgs="${2-}" local extraArgs="${2-}"
rm -rf $repo $repo.tmp rm -rf "$repo" "$repo".tmp
mkdir -p $repo mkdir -p "$repo"
git -C $repo init $extraArgs git -C "$repo" init $extraArgs
git -C $repo config user.email "foobar@example.com" git -C "$repo" config user.email "foobar@example.com"
git -C $repo config user.name "Foobar" git -C "$repo" config user.name "Foobar"
} }

View file

@ -6,27 +6,29 @@ clearStore
rm -rf $TEST_HOME/.cache $TEST_HOME/.config rm -rf $TEST_HOME/.cache $TEST_HOME/.config
flake1Dir=$TEST_ROOT/flake1 flake1Dir=$TEST_ROOT/flake1
flake2Dir=$TEST_ROOT/flake2 flake2Dir=$TEST_ROOT/flake\ 2
flake3Dir=$TEST_ROOT/flake3 percentEncodedFlake2Dir=$TEST_ROOT/flake%202
flake3Dir=$TEST_ROOT/flake%20
percentEncodedFlake3Dir=$TEST_ROOT/flake%2520
flake5Dir=$TEST_ROOT/flake5 flake5Dir=$TEST_ROOT/flake5
flake7Dir=$TEST_ROOT/flake7 flake7Dir=$TEST_ROOT/flake7
nonFlakeDir=$TEST_ROOT/nonFlake nonFlakeDir=$TEST_ROOT/nonFlake
badFlakeDir=$TEST_ROOT/badFlake badFlakeDir=$TEST_ROOT/badFlake
flakeGitBare=$TEST_ROOT/flakeGitBare flakeGitBare=$TEST_ROOT/flakeGitBare
for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $nonFlakeDir; do for repo in "$flake1Dir" "$flake2Dir" "$flake3Dir" "$flake7Dir" "$nonFlakeDir"; do
# Give one repo a non-main initial branch. # Give one repo a non-main initial branch.
extraArgs= extraArgs=
if [[ $repo == $flake2Dir ]]; then if [[ "$repo" == "$flake2Dir" ]]; then
extraArgs="--initial-branch=main" extraArgs="--initial-branch=main"
fi fi
createGitRepo "$repo" "$extraArgs" createGitRepo "$repo" "$extraArgs"
done done
createSimpleGitFlake $flake1Dir createSimpleGitFlake "$flake1Dir"
cat > $flake2Dir/flake.nix <<EOF cat > "$flake2Dir/flake.nix" <<EOF
{ {
description = "Fnord"; description = "Fnord";
@ -36,10 +38,10 @@ cat > $flake2Dir/flake.nix <<EOF
} }
EOF EOF
git -C $flake2Dir add flake.nix git -C "$flake2Dir" add flake.nix
git -C $flake2Dir commit -m 'Initial' git -C "$flake2Dir" commit -m 'Initial'
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
description = "Fnord"; description = "Fnord";
@ -53,26 +55,26 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
cat > $flake3Dir/default.nix <<EOF cat > "$flake3Dir/default.nix" <<EOF
{ x = 123; } { x = 123; }
EOF EOF
git -C $flake3Dir add flake.nix default.nix git -C "$flake3Dir" add flake.nix default.nix
git -C $flake3Dir commit -m 'Initial' git -C "$flake3Dir" commit -m 'Initial'
cat > $nonFlakeDir/README.md <<EOF cat > "$nonFlakeDir/README.md" <<EOF
FNORD FNORD
EOF EOF
git -C $nonFlakeDir add README.md git -C "$nonFlakeDir" add README.md
git -C $nonFlakeDir commit -m 'Initial' git -C "$nonFlakeDir" commit -m 'Initial'
# Construct a custom registry, additionally test the --registry flag # Construct a custom registry, additionally test the --registry flag
nix registry add --registry $registry flake1 git+file://$flake1Dir nix registry add --registry "$registry" flake1 "git+file://$flake1Dir"
nix registry add --registry $registry flake2 git+file://$flake2Dir nix registry add --registry "$registry" flake2 "git+file://$percentEncodedFlake2Dir"
nix registry add --registry $registry flake3 git+file://$flake3Dir nix registry add --registry "$registry" flake3 "git+file://$percentEncodedFlake3Dir"
nix registry add --registry $registry flake4 flake3 nix registry add --registry "$registry" flake4 flake3
nix registry add --registry $registry nixpkgs flake1 nix registry add --registry "$registry" nixpkgs flake1
# Test 'nix registry list'. # Test 'nix registry list'.
[[ $(nix registry list | wc -l) == 5 ]] [[ $(nix registry list | wc -l) == 5 ]]
@ -84,100 +86,100 @@ nix flake metadata flake1
nix flake metadata flake1 | grepQuiet 'Locked URL:.*flake1.*' nix flake metadata flake1 | grepQuiet 'Locked URL:.*flake1.*'
# Test 'nix flake metadata' on a local flake. # Test 'nix flake metadata' on a local flake.
(cd $flake1Dir && nix flake metadata) | grepQuiet 'URL:.*flake1.*' (cd "$flake1Dir" && nix flake metadata) | grepQuiet 'URL:.*flake1.*'
(cd $flake1Dir && nix flake metadata .) | grepQuiet 'URL:.*flake1.*' (cd "$flake1Dir" && nix flake metadata .) | grepQuiet 'URL:.*flake1.*'
nix flake metadata $flake1Dir | grepQuiet 'URL:.*flake1.*' nix flake metadata "$flake1Dir" | grepQuiet 'URL:.*flake1.*'
# Test 'nix flake metadata --json'. # Test 'nix flake metadata --json'.
json=$(nix flake metadata flake1 --json | jq .) json=$(nix flake metadata flake1 --json | jq .)
[[ $(echo "$json" | jq -r .description) = 'Bla bla' ]] [[ $(echo "$json" | jq -r .description) = 'Bla bla' ]]
[[ -d $(echo "$json" | jq -r .path) ]] [[ -d $(echo "$json" | jq -r .path) ]]
[[ $(echo "$json" | jq -r .lastModified) = $(git -C $flake1Dir log -n1 --format=%ct) ]] [[ $(echo "$json" | jq -r .lastModified) = $(git -C "$flake1Dir" log -n1 --format=%ct) ]]
hash1=$(echo "$json" | jq -r .revision) hash1=$(echo "$json" | jq -r .revision)
echo foo > $flake1Dir/foo echo foo > "$flake1Dir/foo"
git -C $flake1Dir add $flake1Dir/foo git -C "$flake1Dir" add $flake1Dir/foo
[[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "$hash1-dirty" ]] [[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "$hash1-dirty" ]]
echo -n '# foo' >> $flake1Dir/flake.nix echo -n '# foo' >> "$flake1Dir/flake.nix"
flake1OriginalCommit=$(git -C $flake1Dir rev-parse HEAD) flake1OriginalCommit=$(git -C "$flake1Dir" rev-parse HEAD)
git -C $flake1Dir commit -a -m 'Foo' git -C "$flake1Dir" commit -a -m 'Foo'
flake1NewCommit=$(git -C $flake1Dir rev-parse HEAD) flake1NewCommit=$(git -C "$flake1Dir" rev-parse HEAD)
hash2=$(nix flake metadata flake1 --json --refresh | jq -r .revision) hash2=$(nix flake metadata flake1 --json --refresh | jq -r .revision)
[[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "null" ]] [[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "null" ]]
[[ $hash1 != $hash2 ]] [[ $hash1 != $hash2 ]]
# Test 'nix build' on a flake. # Test 'nix build' on a flake.
nix build -o $TEST_ROOT/result flake1#foo nix build -o "$TEST_ROOT/result" flake1#foo
[[ -e $TEST_ROOT/result/hello ]] [[ -e "$TEST_ROOT/result/hello" ]]
# Test packages.default. # Test packages.default.
nix build -o $TEST_ROOT/result flake1 nix build -o "$TEST_ROOT/result" flake1
[[ -e $TEST_ROOT/result/hello ]] [[ -e "$TEST_ROOT/result/hello" ]]
nix build -o $TEST_ROOT/result $flake1Dir nix build -o "$TEST_ROOT/result" "$flake1Dir"
nix build -o $TEST_ROOT/result git+file://$flake1Dir nix build -o "$TEST_ROOT/result" "git+file://$flake1Dir"
# Check that store symlinks inside a flake are not interpreted as flakes. # Check that store symlinks inside a flake are not interpreted as flakes.
nix build -o $flake1Dir/result git+file://$flake1Dir nix build -o "$flake1Dir/result" "git+file://$flake1Dir"
nix path-info $flake1Dir/result nix path-info "$flake1Dir/result"
# 'getFlake' on an unlocked flakeref should fail in pure mode, but # 'getFlake' on an unlocked flakeref should fail in pure mode, but
# succeed in impure mode. # succeed in impure mode.
(! nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default") (! nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default")
nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default" --impure nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default" --impure
# 'getFlake' on a locked flakeref should succeed even in pure mode. # 'getFlake' on a locked flakeref should succeed even in pure mode.
nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"git+file://$flake1Dir?rev=$hash2\").packages.$system.default" nix build -o "$TEST_ROOT/result" --expr "(builtins.getFlake \"git+file://$flake1Dir?rev=$hash2\").packages.$system.default"
# Building a flake with an unlocked dependency should fail in pure mode. # Building a flake with an unlocked dependency should fail in pure mode.
(! nix build -o $TEST_ROOT/result flake2#bar --no-registries) (! nix build -o "$TEST_ROOT/result" flake2#bar --no-registries)
(! nix build -o $TEST_ROOT/result flake2#bar --no-use-registries) (! nix build -o "$TEST_ROOT/result" flake2#bar --no-use-registries)
(! nix eval --expr "builtins.getFlake \"$flake2Dir\"") (! nix eval --expr "builtins.getFlake \"$flake2Dir\"")
# But should succeed in impure mode. # But should succeed in impure mode.
(! nix build -o $TEST_ROOT/result flake2#bar --impure) (! nix build -o "$TEST_ROOT/result" flake2#bar --impure)
nix build -o $TEST_ROOT/result flake2#bar --impure --no-write-lock-file nix build -o "$TEST_ROOT/result" flake2#bar --impure --no-write-lock-file
nix eval --expr "builtins.getFlake \"$flake2Dir\"" --impure nix eval --expr "builtins.getFlake \"$flake2Dir\"" --impure
# Building a local flake with an unlocked dependency should fail with --no-update-lock-file. # Building a local flake with an unlocked dependency should fail with --no-update-lock-file.
expect 1 nix build -o $TEST_ROOT/result $flake2Dir#bar --no-update-lock-file 2>&1 | grep 'requires lock file changes' expect 1 nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --no-update-lock-file 2>&1 | grep 'requires lock file changes'
# But it should succeed without that flag. # But it should succeed without that flag.
nix build -o $TEST_ROOT/result $flake2Dir#bar --no-write-lock-file nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --no-write-lock-file
expect 1 nix build -o $TEST_ROOT/result $flake2Dir#bar --no-update-lock-file 2>&1 | grep 'requires lock file changes' expect 1 nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --no-update-lock-file 2>&1 | grep 'requires lock file changes'
nix build -o $TEST_ROOT/result $flake2Dir#bar --commit-lock-file nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --commit-lock-file
[[ -e $flake2Dir/flake.lock ]] [[ -e "$flake2Dir/flake.lock" ]]
[[ -z $(git -C $flake2Dir diff main || echo failed) ]] [[ -z $(git -C "$flake2Dir" diff main || echo failed) ]]
# Rerunning the build should not change the lockfile. # Rerunning the build should not change the lockfile.
nix build -o $TEST_ROOT/result $flake2Dir#bar nix build -o "$TEST_ROOT/result" "$flake2Dir#bar"
[[ -z $(git -C $flake2Dir diff main || echo failed) ]] [[ -z $(git -C "$flake2Dir" diff main || echo failed) ]]
# Building with a lockfile should not require a fetch of the registry. # Building with a lockfile should not require a fetch of the registry.
nix build -o $TEST_ROOT/result --flake-registry file:///no-registry.json $flake2Dir#bar --refresh nix build -o "$TEST_ROOT/result" --flake-registry file:///no-registry.json "$flake2Dir#bar" --refresh
nix build -o $TEST_ROOT/result --no-registries $flake2Dir#bar --refresh nix build -o "$TEST_ROOT/result" --no-registries "$flake2Dir#bar" --refresh
nix build -o $TEST_ROOT/result --no-use-registries $flake2Dir#bar --refresh nix build -o "$TEST_ROOT/result" --no-use-registries "$flake2Dir#bar" --refresh
# Updating the flake should not change the lockfile. # Updating the flake should not change the lockfile.
nix flake lock $flake2Dir nix flake lock "$flake2Dir"
[[ -z $(git -C $flake2Dir diff main || echo failed) ]] [[ -z $(git -C "$flake2Dir" diff main || echo failed) ]]
# Now we should be able to build the flake in pure mode. # Now we should be able to build the flake in pure mode.
nix build -o $TEST_ROOT/result flake2#bar nix build -o "$TEST_ROOT/result" flake2#bar
# Or without a registry. # Or without a registry.
nix build -o $TEST_ROOT/result --no-registries git+file://$flake2Dir#bar --refresh nix build -o "$TEST_ROOT/result" --no-registries "git+file://$percentEncodedFlake2Dir#bar" --refresh
nix build -o $TEST_ROOT/result --no-use-registries git+file://$flake2Dir#bar --refresh nix build -o "$TEST_ROOT/result" --no-use-registries "git+file://$percentEncodedFlake2Dir#bar" --refresh
# Test whether indirect dependencies work. # Test whether indirect dependencies work.
nix build -o $TEST_ROOT/result $flake3Dir#xyzzy nix build -o "$TEST_ROOT/result" "$flake3Dir#xyzzy"
git -C $flake3Dir add flake.lock git -C "$flake3Dir" add flake.lock
# Add dependency to flake3. # Add dependency to flake3.
rm $flake3Dir/flake.nix rm "$flake3Dir/flake.nix"
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
description = "Fnord"; description = "Fnord";
@ -188,44 +190,44 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
git -C $flake3Dir add flake.nix git -C "$flake3Dir" add flake.nix
git -C $flake3Dir commit -m 'Update flake.nix' git -C "$flake3Dir" commit -m 'Update flake.nix'
# Check whether `nix build` works with an incomplete lockfile # Check whether `nix build` works with an incomplete lockfile
nix build -o $TEST_ROOT/result $flake3Dir#"sth sth" nix build -o $TEST_ROOT/result "$flake3Dir#sth sth"
nix build -o $TEST_ROOT/result $flake3Dir#"sth%20sth" nix build -o $TEST_ROOT/result "$flake3Dir#sth%20sth"
# Check whether it saved the lockfile # Check whether it saved the lockfile
[[ -n $(git -C $flake3Dir diff master) ]] [[ -n $(git -C "$flake3Dir" diff master) ]]
git -C $flake3Dir add flake.lock git -C "$flake3Dir" add flake.lock
git -C $flake3Dir commit -m 'Add lockfile' git -C "$flake3Dir" commit -m 'Add lockfile'
# Test whether registry caching works. # Test whether registry caching works.
nix registry list --flake-registry file://$registry | grepQuiet flake3 nix registry list --flake-registry "file://$registry" | grepQuiet flake3
mv $registry $registry.tmp mv "$registry" "$registry.tmp"
nix store gc nix store gc
nix registry list --flake-registry file://$registry --refresh | grepQuiet flake3 nix registry list --flake-registry "file://$registry" --refresh | grepQuiet flake3
mv $registry.tmp $registry mv "$registry.tmp" "$registry"
# Test whether flakes are registered as GC roots for offline use. # Test whether flakes are registered as GC roots for offline use.
# FIXME: use tarballs rather than git. # FIXME: use tarballs rather than git.
rm -rf $TEST_HOME/.cache rm -rf "$TEST_HOME/.cache"
nix store gc # get rid of copies in the store to ensure they get fetched to our git cache nix store gc # get rid of copies in the store to ensure they get fetched to our git cache
_NIX_FORCE_HTTP=1 nix build -o $TEST_ROOT/result git+file://$flake2Dir#bar _NIX_FORCE_HTTP=1 nix build -o "$TEST_ROOT/result" "git+file://$percentEncodedFlake2Dir#bar"
mv $flake1Dir $flake1Dir.tmp mv "$flake1Dir" "$flake1Dir.tmp"
mv $flake2Dir $flake2Dir.tmp mv "$flake2Dir" "$flake2Dir.tmp"
nix store gc nix store gc
_NIX_FORCE_HTTP=1 nix build -o $TEST_ROOT/result git+file://$flake2Dir#bar _NIX_FORCE_HTTP=1 nix build -o "$TEST_ROOT/result" "git+file://$percentEncodedFlake2Dir#bar"
_NIX_FORCE_HTTP=1 nix build -o $TEST_ROOT/result git+file://$flake2Dir#bar --refresh _NIX_FORCE_HTTP=1 nix build -o "$TEST_ROOT/result" "git+file://$percentEncodedFlake2Dir#bar" --refresh
mv $flake1Dir.tmp $flake1Dir mv "$flake1Dir.tmp" "$flake1Dir"
mv $flake2Dir.tmp $flake2Dir mv "$flake2Dir.tmp" "$flake2Dir"
# Add nonFlakeInputs to flake3. # Add nonFlakeInputs to flake3.
rm $flake3Dir/flake.nix rm "$flake3Dir/flake.nix"
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs = { inputs = {
flake1 = {}; flake1 = {};
@ -266,46 +268,46 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
cp ../config.nix $flake3Dir cp ../config.nix "$flake3Dir"
git -C $flake3Dir add flake.nix config.nix git -C "$flake3Dir" add flake.nix config.nix
git -C $flake3Dir commit -m 'Add nonFlakeInputs' git -C "$flake3Dir" commit -m 'Add nonFlakeInputs'
# Check whether `nix build` works with a lockfile which is missing a # Check whether `nix build` works with a lockfile which is missing a
# nonFlakeInputs. # nonFlakeInputs.
nix build -o $TEST_ROOT/result $flake3Dir#sth --commit-lock-file nix build -o "$TEST_ROOT/result" "$flake3Dir#sth" --commit-lock-file
nix build -o $TEST_ROOT/result flake3#fnord nix build -o "$TEST_ROOT/result" flake3#fnord
[[ $(cat $TEST_ROOT/result) = FNORD ]] [[ $(cat $TEST_ROOT/result) = FNORD ]]
# Check whether flake input fetching is lazy: flake3#sth does not # Check whether flake input fetching is lazy: flake3#sth does not
# depend on flake2, so this shouldn't fail. # depend on flake2, so this shouldn't fail.
rm -rf $TEST_HOME/.cache rm -rf "$TEST_HOME/.cache"
clearStore clearStore
mv $flake2Dir $flake2Dir.tmp mv "$flake2Dir" "$flake2Dir.tmp"
mv $nonFlakeDir $nonFlakeDir.tmp mv "$nonFlakeDir" "$nonFlakeDir.tmp"
nix build -o $TEST_ROOT/result flake3#sth nix build -o "$TEST_ROOT/result" flake3#sth
(! nix build -o $TEST_ROOT/result flake3#xyzzy) (! nix build -o "$TEST_ROOT/result" flake3#xyzzy)
(! nix build -o $TEST_ROOT/result flake3#fnord) (! nix build -o "$TEST_ROOT/result" flake3#fnord)
mv $flake2Dir.tmp $flake2Dir mv "$flake2Dir.tmp" "$flake2Dir"
mv $nonFlakeDir.tmp $nonFlakeDir mv "$nonFlakeDir.tmp" "$nonFlakeDir"
nix build -o $TEST_ROOT/result flake3#xyzzy flake3#fnord nix build -o "$TEST_ROOT/result" flake3#xyzzy flake3#fnord
# Test doing multiple `lookupFlake`s # Test doing multiple `lookupFlake`s
nix build -o $TEST_ROOT/result flake4#xyzzy nix build -o "$TEST_ROOT/result" flake4#xyzzy
# Test 'nix flake update' and --override-flake. # Test 'nix flake update' and --override-flake.
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
[[ -z $(git -C $flake3Dir diff master || echo failed) ]] [[ -z $(git -C "$flake3Dir" diff master || echo failed) ]]
nix flake update $flake3Dir --override-flake flake2 nixpkgs nix flake update "$flake3Dir" --override-flake flake2 nixpkgs
[[ ! -z $(git -C $flake3Dir diff master || echo failed) ]] [[ ! -z $(git -C "$flake3Dir" diff master || echo failed) ]]
# Make branch "removeXyzzy" where flake3 doesn't have xyzzy anymore # Make branch "removeXyzzy" where flake3 doesn't have xyzzy anymore
git -C $flake3Dir checkout -b removeXyzzy git -C "$flake3Dir" checkout -b removeXyzzy
rm $flake3Dir/flake.nix rm "$flake3Dir/flake.nix"
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs = { inputs = {
nonFlake = { nonFlake = {
@ -330,14 +332,14 @@ cat > $flake3Dir/flake.nix <<EOF
}; };
} }
EOF EOF
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
git -C $flake3Dir add flake.nix flake.lock git -C "$flake3Dir" add flake.nix flake.lock
git -C $flake3Dir commit -m 'Remove packages.xyzzy' git -C "$flake3Dir" commit -m 'Remove packages.xyzzy'
git -C $flake3Dir checkout master git -C "$flake3Dir" checkout master
# Test whether fuzzy-matching works for registry entries. # Test whether fuzzy-matching works for registry entries.
(! nix build -o $TEST_ROOT/result flake4/removeXyzzy#xyzzy) (! nix build -o "$TEST_ROOT/result" flake4/removeXyzzy#xyzzy)
nix build -o $TEST_ROOT/result flake4/removeXyzzy#sth nix build -o "$TEST_ROOT/result" flake4/removeXyzzy#sth
# Testing the nix CLI # Testing the nix CLI
nix registry add flake1 flake3 nix registry add flake1 flake3
@ -351,7 +353,7 @@ nix registry remove flake1
# Test 'nix registry list' with a disabled global registry. # Test 'nix registry list' with a disabled global registry.
nix registry add user-flake1 git+file://$flake1Dir nix registry add user-flake1 git+file://$flake1Dir
nix registry add user-flake2 git+file://$flake2Dir nix registry add user-flake2 "git+file://$percentEncodedFlake2Dir"
[[ $(nix --flake-registry "" registry list | wc -l) == 2 ]] [[ $(nix --flake-registry "" registry list | wc -l) == 2 ]]
nix --flake-registry "" registry list | grepQuietInverse '^global' # nothing in global registry nix --flake-registry "" registry list | grepQuietInverse '^global' # nothing in global registry
nix --flake-registry "" registry list | grepQuiet '^user' nix --flake-registry "" registry list | grepQuiet '^user'
@ -365,7 +367,7 @@ nix flake clone flake1 --dest $TEST_ROOT/flake1-v2
[ -e $TEST_ROOT/flake1-v2/flake.nix ] [ -e $TEST_ROOT/flake1-v2/flake.nix ]
# Test 'follows' inputs. # Test 'follows' inputs.
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs.foo = { inputs.foo = {
type = "indirect"; type = "indirect";
@ -378,10 +380,10 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["foo"]' ]] [[ $(jq -c .nodes.root.inputs.bar "$flake3Dir/flake.lock") = '["foo"]' ]]
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs.bar.follows = "flake2/flake1"; inputs.bar.follows = "flake2/flake1";
@ -390,10 +392,10 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["flake2","flake1"]' ]] [[ $(jq -c .nodes.root.inputs.bar "$flake3Dir/flake.lock") = '["flake2","flake1"]' ]]
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs.bar.follows = "flake2"; inputs.bar.follows = "flake2";
@ -402,15 +404,15 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["flake2"]' ]] [[ $(jq -c .nodes.root.inputs.bar "$flake3Dir/flake.lock") = '["flake2"]' ]]
# Test overriding inputs of inputs. # Test overriding inputs of inputs.
writeTrivialFlake $flake7Dir writeTrivialFlake $flake7Dir
git -C $flake7Dir add flake.nix git -C $flake7Dir add flake.nix
git -C $flake7Dir commit -m 'Initial' git -C $flake7Dir commit -m 'Initial'
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs.flake2.inputs.flake1 = { inputs.flake2.inputs.flake1 = {
type = "git"; type = "git";
@ -422,10 +424,10 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
[[ $(jq .nodes.flake1.locked.url $flake3Dir/flake.lock) =~ flake7 ]] [[ $(jq .nodes.flake1.locked.url "$flake3Dir/flake.lock") =~ flake7 ]]
cat > $flake3Dir/flake.nix <<EOF cat > "$flake3Dir/flake.nix" <<EOF
{ {
inputs.flake2.inputs.flake1.follows = "foo"; inputs.flake2.inputs.flake1.follows = "foo";
inputs.foo.url = git+file://$flake7Dir; inputs.foo.url = git+file://$flake7Dir;
@ -435,9 +437,9 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
nix flake update $flake3Dir nix flake update "$flake3Dir"
[[ $(jq -c .nodes.flake2.inputs.flake1 $flake3Dir/flake.lock) =~ '["foo"]' ]] [[ $(jq -c .nodes.flake2.inputs.flake1 "$flake3Dir/flake.lock") =~ '["foo"]' ]]
[[ $(jq .nodes.foo.locked.url $flake3Dir/flake.lock) =~ flake7 ]] [[ $(jq .nodes.foo.locked.url "$flake3Dir/flake.lock") =~ flake7 ]]
# Test git+file with bare repo. # Test git+file with bare repo.
rm -rf $flakeGitBare rm -rf $flakeGitBare
@ -464,25 +466,25 @@ nix build -o $TEST_ROOT/result $url
expectStderr 102 nix build -o $TEST_ROOT/result "file://$TEST_ROOT/flake.tar.gz?narHash=sha256-qQ2Zz4DNHViCUrp6gTS7EE4+RMqFQtUfWF2UNUtJKS0=" | grep 'NAR hash mismatch' expectStderr 102 nix build -o $TEST_ROOT/result "file://$TEST_ROOT/flake.tar.gz?narHash=sha256-qQ2Zz4DNHViCUrp6gTS7EE4+RMqFQtUfWF2UNUtJKS0=" | grep 'NAR hash mismatch'
# Test --override-input. # Test --override-input.
git -C $flake3Dir reset --hard git -C "$flake3Dir" reset --hard
nix flake lock $flake3Dir --override-input flake2/flake1 file://$TEST_ROOT/flake.tar.gz -vvvvv nix flake lock "$flake3Dir" --override-input flake2/flake1 file://$TEST_ROOT/flake.tar.gz -vvvvv
[[ $(jq .nodes.flake1_2.locked.url $flake3Dir/flake.lock) =~ flake.tar.gz ]] [[ $(jq .nodes.flake1_2.locked.url "$flake3Dir/flake.lock") =~ flake.tar.gz ]]
nix flake lock $flake3Dir --override-input flake2/flake1 flake1 nix flake lock "$flake3Dir" --override-input flake2/flake1 flake1
[[ $(jq -r .nodes.flake1_2.locked.rev $flake3Dir/flake.lock) =~ $hash2 ]] [[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") =~ $hash2 ]]
nix flake lock $flake3Dir --override-input flake2/flake1 flake1/master/$hash1 nix flake lock "$flake3Dir" --override-input flake2/flake1 flake1/master/$hash1
[[ $(jq -r .nodes.flake1_2.locked.rev $flake3Dir/flake.lock) =~ $hash1 ]] [[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") =~ $hash1 ]]
# Test --update-input. # Test --update-input.
nix flake lock $flake3Dir nix flake lock "$flake3Dir"
[[ $(jq -r .nodes.flake1_2.locked.rev $flake3Dir/flake.lock) = $hash1 ]] [[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") = $hash1 ]]
nix flake lock $flake3Dir --update-input flake2/flake1 nix flake lock "$flake3Dir" --update-input flake2/flake1
[[ $(jq -r .nodes.flake1_2.locked.rev $flake3Dir/flake.lock) =~ $hash2 ]] [[ $(jq -r .nodes.flake1_2.locked.rev "$flake3Dir/flake.lock") =~ $hash2 ]]
# Test 'nix flake metadata --json'. # Test 'nix flake metadata --json'.
nix flake metadata $flake3Dir --json | jq . nix flake metadata "$flake3Dir" --json | jq .
# Test flake in store does not evaluate. # Test flake in store does not evaluate.
rm -rf $badFlakeDir rm -rf $badFlakeDir
@ -495,17 +497,17 @@ nix store delete $(nix store add-path $badFlakeDir)
# Test fetching flakerefs in the legacy CLI. # Test fetching flakerefs in the legacy CLI.
[[ $(nix-instantiate --eval flake:flake3 -A x) = 123 ]] [[ $(nix-instantiate --eval flake:flake3 -A x) = 123 ]]
[[ $(nix-instantiate --eval flake:git+file://$flake3Dir -A x) = 123 ]] [[ $(nix-instantiate --eval "flake:git+file://$percentEncodedFlake3Dir" -A x) = 123 ]]
[[ $(nix-instantiate -I flake3=flake:flake3 --eval '<flake3>' -A x) = 123 ]] [[ $(nix-instantiate -I flake3=flake:flake3 --eval '<flake3>' -A x) = 123 ]]
[[ $(NIX_PATH=flake3=flake:flake3 nix-instantiate --eval '<flake3>' -A x) = 123 ]] [[ $(NIX_PATH=flake3=flake:flake3 nix-instantiate --eval '<flake3>' -A x) = 123 ]]
# Test alternate lockfile paths. # Test alternate lockfile paths.
nix flake lock $flake2Dir --output-lock-file $TEST_ROOT/flake2.lock nix flake lock "$flake2Dir" --output-lock-file $TEST_ROOT/flake2.lock
cmp $flake2Dir/flake.lock $TEST_ROOT/flake2.lock >/dev/null # lockfiles should be identical, since we're referencing flake2's original one cmp "$flake2Dir/flake.lock" $TEST_ROOT/flake2.lock >/dev/null # lockfiles should be identical, since we're referencing flake2's original one
nix flake lock $flake2Dir --output-lock-file $TEST_ROOT/flake2-overridden.lock --override-input flake1 git+file://$flake1Dir?rev=$flake1OriginalCommit nix flake lock "$flake2Dir" --output-lock-file $TEST_ROOT/flake2-overridden.lock --override-input flake1 git+file://$flake1Dir?rev=$flake1OriginalCommit
expectStderr 1 cmp $flake2Dir/flake.lock $TEST_ROOT/flake2-overridden.lock expectStderr 1 cmp "$flake2Dir/flake.lock" $TEST_ROOT/flake2-overridden.lock
nix flake metadata $flake2Dir --reference-lock-file $TEST_ROOT/flake2-overridden.lock | grepQuiet $flake1OriginalCommit nix flake metadata "$flake2Dir" --reference-lock-file $TEST_ROOT/flake2-overridden.lock | grepQuiet $flake1OriginalCommit
# reference-lock-file can only be used if allow-dirty is set. # reference-lock-file can only be used if allow-dirty is set.
expectStderr 1 nix flake metadata $flake2Dir --no-allow-dirty --reference-lock-file $TEST_ROOT/flake2-overridden.lock expectStderr 1 nix flake metadata "$flake2Dir" --no-allow-dirty --reference-lock-file $TEST_ROOT/flake2-overridden.lock