forked from lix-project/lix
Merge pull request #9061 from edolstra/stabilize-fetchTree
fetchTree cleanup
This commit is contained in:
commit
2084312313
|
@ -25,7 +25,6 @@ void emitTreeAttrs(
|
||||||
|
|
||||||
auto attrs = state.buildBindings(10);
|
auto attrs = state.buildBindings(10);
|
||||||
|
|
||||||
|
|
||||||
state.mkStorePathString(tree.storePath, attrs.alloc(state.sOutPath));
|
state.mkStorePathString(tree.storePath, attrs.alloc(state.sOutPath));
|
||||||
|
|
||||||
// FIXME: support arbitrary input attributes.
|
// FIXME: support arbitrary input attributes.
|
||||||
|
@ -71,36 +70,10 @@ void emitTreeAttrs(
|
||||||
v.mkAttrs(attrs);
|
v.mkAttrs(attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string fixURI(std::string uri, EvalState & state, const std::string & defaultScheme = "file")
|
|
||||||
{
|
|
||||||
state.checkURI(uri);
|
|
||||||
if (uri.find("://") == std::string::npos) {
|
|
||||||
const auto p = ParsedURL {
|
|
||||||
.scheme = defaultScheme,
|
|
||||||
.authority = "",
|
|
||||||
.path = uri
|
|
||||||
};
|
|
||||||
return p.to_string();
|
|
||||||
} else {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fixURIForGit(std::string uri, EvalState & state)
|
|
||||||
{
|
|
||||||
/* Detects scp-style uris (e.g. git@github.com:NixOS/nix) and fixes
|
|
||||||
* them by removing the `:` and assuming a scheme of `ssh://`
|
|
||||||
* */
|
|
||||||
static std::regex scp_uri("([^/]*)@(.*):(.*)");
|
|
||||||
if (uri[0] != '/' && std::regex_match(uri, scp_uri))
|
|
||||||
return fixURI(std::regex_replace(uri, scp_uri, "$1@$2/$3"), state, "ssh");
|
|
||||||
else
|
|
||||||
return fixURI(uri, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FetchTreeParams {
|
struct FetchTreeParams {
|
||||||
bool emptyRevFallback = false;
|
bool emptyRevFallback = false;
|
||||||
bool allowNameArgument = false;
|
bool allowNameArgument = false;
|
||||||
|
bool isFetchGit = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void fetchTree(
|
static void fetchTree(
|
||||||
|
@ -108,11 +81,12 @@ static void fetchTree(
|
||||||
const PosIdx pos,
|
const PosIdx pos,
|
||||||
Value * * args,
|
Value * * args,
|
||||||
Value & v,
|
Value & v,
|
||||||
std::optional<std::string> type,
|
|
||||||
const FetchTreeParams & params = FetchTreeParams{}
|
const FetchTreeParams & params = FetchTreeParams{}
|
||||||
) {
|
) {
|
||||||
fetchers::Input input;
|
fetchers::Input input;
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
|
std::optional<std::string> type;
|
||||||
|
if (params.isFetchGit) type = "git";
|
||||||
|
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
|
|
||||||
|
@ -142,10 +116,8 @@ static void fetchTree(
|
||||||
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
||||||
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
|
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
|
||||||
attrs.emplace(state.symbols[attr.name],
|
attrs.emplace(state.symbols[attr.name],
|
||||||
state.symbols[attr.name] == "url"
|
params.isFetchGit && state.symbols[attr.name] == "url"
|
||||||
? type == "git"
|
? fixGitURL(s)
|
||||||
? fixURIForGit(s, state)
|
|
||||||
: fixURI(s, state)
|
|
||||||
: s);
|
: s);
|
||||||
}
|
}
|
||||||
else if (attr.value->type() == nBool)
|
else if (attr.value->type() == nBool)
|
||||||
|
@ -170,22 +142,24 @@ static void fetchTree(
|
||||||
"while evaluating the first argument passed to the fetcher",
|
"while evaluating the first argument passed to the fetcher",
|
||||||
false, false).toOwned();
|
false, false).toOwned();
|
||||||
|
|
||||||
if (type == "git") {
|
if (params.isFetchGit) {
|
||||||
fetchers::Attrs attrs;
|
fetchers::Attrs attrs;
|
||||||
attrs.emplace("type", "git");
|
attrs.emplace("type", "git");
|
||||||
attrs.emplace("url", fixURIForGit(url, state));
|
attrs.emplace("url", fixGitURL(url));
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
} else {
|
} else {
|
||||||
input = fetchers::Input::fromURL(fixURI(url, state));
|
input = fetchers::Input::fromURL(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!evalSettings.pureEval && !input.isDirect())
|
if (!evalSettings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||||
input = lookupInRegistries(state.store, input).first;
|
input = lookupInRegistries(state.store, input).first;
|
||||||
|
|
||||||
if (evalSettings.pureEval && !input.isLocked())
|
if (evalSettings.pureEval && !input.isLocked())
|
||||||
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
|
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
|
||||||
|
|
||||||
|
state.checkURI(input.toURLString());
|
||||||
|
|
||||||
auto [tree, input2] = input.fetch(state.store);
|
auto [tree, input2] = input.fetch(state.store);
|
||||||
|
|
||||||
state.allowPath(tree.storePath);
|
state.allowPath(tree.storePath);
|
||||||
|
@ -195,7 +169,7 @@ static void fetchTree(
|
||||||
|
|
||||||
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
|
fetchTree(state, pos, args, v, { });
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchTree({
|
static RegisterPrimOp primop_fetchTree({
|
||||||
|
@ -203,12 +177,12 @@ static RegisterPrimOp primop_fetchTree({
|
||||||
.args = {"input"},
|
.args = {"input"},
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
Fetch a source tree or a plain file using one of the supported backends.
|
Fetch a source tree or a plain file using one of the supported backends.
|
||||||
*input* can be an attribute set representation of [flake reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references) or a URL.
|
*input* must be a [flake reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references), either in attribute set representation or in the URL-like syntax.
|
||||||
The input should be "locked", that is, it should contain a commit hash or content hash unless impure evaluation (`--impure`) is allowed.
|
The input should be "locked", that is, it should contain a commit hash or content hash unless impure evaluation (`--impure`) is enabled.
|
||||||
|
|
||||||
Here are some examples of how to use `fetchTree`:
|
Here are some examples of how to use `fetchTree`:
|
||||||
|
|
||||||
- Fetch a GitHub repository:
|
- Fetch a GitHub repository using the attribute set representation:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
builtins.fetchTree {
|
builtins.fetchTree {
|
||||||
|
@ -219,7 +193,7 @@ static RegisterPrimOp primop_fetchTree({
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This evaluates to attribute set:
|
This evaluates to the following attribute set:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
|
@ -231,10 +205,11 @@ static RegisterPrimOp primop_fetchTree({
|
||||||
shortRev = "ae2e6b3";
|
shortRev = "ae2e6b3";
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Fetch a single file from a URL:
|
|
||||||
|
|
||||||
```nix
|
- Fetch the same GitHub repository using the URL-like syntax:
|
||||||
builtins.fetchTree "https://example.com/"
|
|
||||||
|
```
|
||||||
|
builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||||
```
|
```
|
||||||
)",
|
)",
|
||||||
.fun = prim_fetchTree,
|
.fun = prim_fetchTree,
|
||||||
|
@ -388,7 +363,12 @@ static RegisterPrimOp primop_fetchTarball({
|
||||||
|
|
||||||
static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
|
fetchTree(state, pos, args, v,
|
||||||
|
FetchTreeParams {
|
||||||
|
.emptyRevFallback = true,
|
||||||
|
.allowNameArgument = true,
|
||||||
|
.isFetchGit = true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchGit({
|
static RegisterPrimOp primop_fetchGit({
|
||||||
|
|
|
@ -293,7 +293,6 @@ struct GitInputScheme : InputScheme
|
||||||
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")
|
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);
|
throw Error("unsupported Git input attribute '%s'", name);
|
||||||
|
|
||||||
parseURL(getStrAttr(attrs, "url"));
|
|
||||||
maybeGetBoolAttr(attrs, "shallow");
|
maybeGetBoolAttr(attrs, "shallow");
|
||||||
maybeGetBoolAttr(attrs, "submodules");
|
maybeGetBoolAttr(attrs, "submodules");
|
||||||
maybeGetBoolAttr(attrs, "allRefs");
|
maybeGetBoolAttr(attrs, "allRefs");
|
||||||
|
@ -305,6 +304,9 @@ struct GitInputScheme : InputScheme
|
||||||
|
|
||||||
Input input;
|
Input input;
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
|
auto url = fixGitURL(getStrAttr(attrs, "url"));
|
||||||
|
parseURL(url);
|
||||||
|
input.attrs["url"] = url;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,4 +158,21 @@ ParsedUrlScheme parseUrlScheme(std::string_view scheme)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string fixGitURL(const std::string & url)
|
||||||
|
{
|
||||||
|
std::regex scpRegex("([^/]*)@(.*):(.*)");
|
||||||
|
if (!hasPrefix(url, "/") && std::regex_match(url, scpRegex))
|
||||||
|
return std::regex_replace(url, scpRegex, "ssh://$1@$2/$3");
|
||||||
|
else {
|
||||||
|
if (url.find("://") == std::string::npos) {
|
||||||
|
return (ParsedURL {
|
||||||
|
.scheme = "file",
|
||||||
|
.authority = "",
|
||||||
|
.path = url
|
||||||
|
}).to_string();
|
||||||
|
} else
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,4 +45,9 @@ struct ParsedUrlScheme {
|
||||||
|
|
||||||
ParsedUrlScheme parseUrlScheme(std::string_view scheme);
|
ParsedUrlScheme parseUrlScheme(std::string_view scheme);
|
||||||
|
|
||||||
|
/* Detects scp-style uris (e.g. git@github.com:NixOS/nix) and fixes
|
||||||
|
them by removing the `:` and assuming a scheme of `ssh://`. Also
|
||||||
|
changes absolute paths into file:// URLs. */
|
||||||
|
std::string fixGitURL(const std::string & url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,6 +182,12 @@ Currently the `type` attribute can be one of the following:
|
||||||
git(+http|+https|+ssh|+git|+file|):(//<server>)?<path>(\?<params>)?
|
git(+http|+https|+ssh|+git|+file|):(//<server>)?<path>(\?<params>)?
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
<user>@<server>:<path>
|
||||||
|
```
|
||||||
|
|
||||||
The `ref` attribute defaults to resolving the `HEAD` reference.
|
The `ref` attribute defaults to resolving the `HEAD` reference.
|
||||||
|
|
||||||
The `rev` attribute must denote a commit that exists in the branch
|
The `rev` attribute must denote a commit that exists in the branch
|
||||||
|
|
|
@ -35,6 +35,8 @@ unset _NIX_FORCE_HTTP
|
||||||
path0=$(nix eval --impure --raw --expr "(builtins.fetchGit file://$TEST_ROOT/worktree).outPath")
|
path0=$(nix eval --impure --raw --expr "(builtins.fetchGit file://$TEST_ROOT/worktree).outPath")
|
||||||
path0_=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = file://$TEST_ROOT/worktree; }).outPath")
|
path0_=$(nix eval --impure --raw --expr "(builtins.fetchTree { type = \"git\"; url = file://$TEST_ROOT/worktree; }).outPath")
|
||||||
[[ $path0 = $path0_ ]]
|
[[ $path0 = $path0_ ]]
|
||||||
|
path0_=$(nix eval --impure --raw --expr "(builtins.fetchTree git+file://$TEST_ROOT/worktree).outPath")
|
||||||
|
[[ $path0 = $path0_ ]]
|
||||||
export _NIX_FORCE_HTTP=1
|
export _NIX_FORCE_HTTP=1
|
||||||
[[ $(tail -n 1 $path0/hello) = "hello" ]]
|
[[ $(tail -n 1 $path0/hello) = "hello" ]]
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,10 @@ in
|
||||||
client.succeed("nix registry pin nixpkgs")
|
client.succeed("nix registry pin nixpkgs")
|
||||||
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
|
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
|
||||||
|
|
||||||
|
# Test fetchTree on a github URL.
|
||||||
|
hash = client.succeed(f"nix eval --raw --expr '(fetchTree {info['url']}).narHash'")
|
||||||
|
assert hash == info['locked']['narHash']
|
||||||
|
|
||||||
# Shut down the web server. The flake should be cached on the client.
|
# Shut down the web server. The flake should be cached on the client.
|
||||||
github.succeed("systemctl stop httpd.service")
|
github.succeed("systemctl stop httpd.service")
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue