Compare commits
1 Commits
string-str
...
cancelled-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cd5e62402 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -174,7 +174,7 @@ jobs:
|
||||
echo "installer-url=file://$GITHUB_WORKSPACE/out" >> "$GITHUB_OUTPUT"
|
||||
TARBALL_PATH="$(find "$GITHUB_WORKSPACE/out" -name 'nix*.tar.xz' -print | head -n 1)"
|
||||
echo "tarball-path=file://$TARBALL_PATH" >> "$GITHUB_OUTPUT"
|
||||
- uses: cachix/install-nix-action@7ec16f2c061ab07b235a7245e06ed46fe9a1cab6 # v31.8.3
|
||||
- uses: cachix/install-nix-action@456688f15bc354bef6d396e4a35f4f89d40bf2b7 # v31.8.2
|
||||
if: ${{ !matrix.experimental-installer }}
|
||||
with:
|
||||
install_url: ${{ format('{0}/install', steps.installer-tarball-url.outputs.installer-url) }}
|
||||
|
||||
@@ -33,8 +33,7 @@ EvalSettings evalSettings{
|
||||
// FIXME `parseFlakeRef` should take a `std::string_view`.
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string{rest}, {}, true, false);
|
||||
debug("fetching flake search path element '%s''", rest);
|
||||
auto [accessor, lockedRef] =
|
||||
flakeRef.resolve(fetchSettings, state.store).lazyFetch(fetchSettings, state.store);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
|
||||
auto storePath = nix::fetchToStore(
|
||||
state.fetchSettings,
|
||||
*state.store,
|
||||
@@ -132,7 +131,7 @@ MixEvalArgs::MixEvalArgs()
|
||||
fetchers::Attrs extraAttrs;
|
||||
if (to.subdir != "")
|
||||
extraAttrs["dir"] = to.subdir;
|
||||
fetchers::overrideRegistry(fetchSettings, from.input, to.input, extraAttrs);
|
||||
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
||||
}},
|
||||
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
|
||||
completeFlakeRef(completions, openStore(), prefix);
|
||||
@@ -188,7 +187,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
|
||||
else if (hasPrefix(s, "flake:")) {
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(fetchSettings, state.store).lazyFetch(fetchSettings, state.store);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
|
||||
auto storePath = nix::fetchToStore(
|
||||
state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
|
||||
state.allowPath(storePath);
|
||||
|
||||
@@ -185,7 +185,6 @@ MixFlakeOptions::MixFlakeOptions()
|
||||
}
|
||||
|
||||
overrideRegistry(
|
||||
fetchSettings,
|
||||
fetchers::Input::fromAttrs(fetchSettings, {{"type", "indirect"}, {"id", inputName}}),
|
||||
input3->lockedRef.input,
|
||||
extraAttrs);
|
||||
|
||||
@@ -738,7 +738,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||
}
|
||||
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked(fetchSettings))
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
|
||||
Value v;
|
||||
|
||||
@@ -137,8 +137,6 @@ nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Sto
|
||||
|
||||
void nix_eval_state_builder_free(nix_eval_state_builder * builder)
|
||||
{
|
||||
if (builder)
|
||||
builder->~nix_eval_state_builder();
|
||||
operator delete(builder, static_cast<std::align_val_t>(alignof(nix_eval_state_builder)));
|
||||
}
|
||||
|
||||
@@ -205,8 +203,6 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
|
||||
|
||||
void nix_state_free(EvalState * state)
|
||||
{
|
||||
if (state)
|
||||
state->~EvalState();
|
||||
operator delete(state, static_cast<std::align_val_t>(alignof(EvalState)));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace nixC {
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_eval_state_lookup_path)
|
||||
TEST_F(nix_api_store_test, nix_eval_state_lookup_path)
|
||||
{
|
||||
auto tmpDir = nix::createTempDir();
|
||||
auto delTmpDir = std::make_unique<nix::AutoDelete>(tmpDir, true);
|
||||
@@ -42,16 +42,12 @@ TEST_F(nix_api_expr_test, nix_eval_state_lookup_path)
|
||||
nix_expr_eval_from_string(ctx, state, "builtins.seq <nixos-config> <nixpkgs>", ".", value);
|
||||
assert_ctx_ok();
|
||||
|
||||
nix_state_free(state);
|
||||
|
||||
ASSERT_EQ(nix_get_type(ctx, value), NIX_TYPE_PATH);
|
||||
assert_ctx_ok();
|
||||
|
||||
auto pathStr = nix_get_path_string(ctx, value);
|
||||
assert_ctx_ok();
|
||||
ASSERT_EQ(0, strcmp(pathStr, nixpkgs.c_str()));
|
||||
|
||||
nix_gc_decref(nullptr, value);
|
||||
}
|
||||
|
||||
TEST_F(nix_api_expr_test, nix_expr_eval_from_string)
|
||||
|
||||
@@ -661,14 +661,8 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
CASE(R"(null)", ""),
|
||||
CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"),
|
||||
CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"),
|
||||
CASE(R"({ outPath = "foo"; })", "foo")
|
||||
// this is broken on cygwin because canonPath("//./test", false) returns //./test
|
||||
// FIXME: don't use canonPath
|
||||
#ifndef __CYGWIN__
|
||||
,
|
||||
CASE(R"(./test)", "/test")
|
||||
#endif
|
||||
));
|
||||
CASE(R"({ outPath = "foo"; })", "foo"),
|
||||
CASE(R"(./test)", "/test")));
|
||||
#undef CASE
|
||||
|
||||
TEST_F(PrimOpTest, substring)
|
||||
|
||||
@@ -517,16 +517,15 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
||||
if (primOp.arity == 0) {
|
||||
primOp.arity = 1;
|
||||
auto vPrimOp = allocValue();
|
||||
vPrimOp->mkPrimOp(new PrimOp(std::move(primOp)));
|
||||
vPrimOp->mkPrimOp(new PrimOp(primOp));
|
||||
Value v;
|
||||
v.mkApp(vPrimOp, vPrimOp);
|
||||
auto & primOp1 = *vPrimOp->primOp();
|
||||
return addConstant(
|
||||
primOp1.name,
|
||||
primOp.name,
|
||||
v,
|
||||
{
|
||||
.type = nThunk, // FIXME
|
||||
.doc = primOp1.doc ? primOp1.doc->c_str() : nullptr,
|
||||
.doc = primOp.doc,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -566,14 +565,13 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||
{
|
||||
if (v.isPrimOp()) {
|
||||
auto v2 = &v;
|
||||
auto & primOp = *v2->primOp();
|
||||
if (primOp.doc)
|
||||
if (auto * doc = v2->primOp()->doc)
|
||||
return Doc{
|
||||
.pos = {},
|
||||
.name = primOp.name,
|
||||
.arity = primOp.arity,
|
||||
.args = primOp.args,
|
||||
.doc = primOp.doc->c_str(),
|
||||
.name = v2->primOp()->name,
|
||||
.arity = v2->primOp()->arity,
|
||||
.args = v2->primOp()->args,
|
||||
.doc = doc,
|
||||
};
|
||||
}
|
||||
if (v.isLambda()) {
|
||||
|
||||
@@ -108,7 +108,7 @@ struct PrimOp
|
||||
/**
|
||||
* Optional free-form documentation about the primop.
|
||||
*/
|
||||
std::optional<std::string> doc;
|
||||
const char * doc = nullptr;
|
||||
|
||||
/**
|
||||
* Add a trace item, while calling the `<name>` builtin.
|
||||
|
||||
@@ -81,7 +81,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value ** ar
|
||||
attrs.insert_or_assign("rev", rev->gitRev());
|
||||
auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||
|
||||
auto [storePath, input2] = input.fetchToStore(state.fetchSettings, state.store);
|
||||
auto [storePath, input2] = input.fetchToStore(state.store);
|
||||
|
||||
auto attrs2 = state.buildBindings(8);
|
||||
state.mkStorePathString(storePath, attrs2.alloc(state.s.outPath));
|
||||
|
||||
@@ -82,7 +82,7 @@ struct FetchTreeParams
|
||||
static void fetchTree(
|
||||
EvalState & state, const PosIdx pos, Value ** args, Value & v, const FetchTreeParams & params = FetchTreeParams{})
|
||||
{
|
||||
fetchers::Input input{};
|
||||
fetchers::Input input{state.fetchSettings};
|
||||
NixStringContext context;
|
||||
std::optional<std::string> type;
|
||||
auto fetcher = params.isFetchGit ? "fetchGit" : "fetchTree";
|
||||
@@ -194,9 +194,9 @@ static void fetchTree(
|
||||
}
|
||||
|
||||
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
input = lookupInRegistries(state.fetchSettings, state.store, input, fetchers::UseRegistries::Limited).first;
|
||||
input = lookupInRegistries(state.store, input, fetchers::UseRegistries::Limited).first;
|
||||
|
||||
if (state.settings.pureEval && !input.isLocked(state.fetchSettings)) {
|
||||
if (state.settings.pureEval && !input.isLocked()) {
|
||||
if (input.getNarHash())
|
||||
warn(
|
||||
"Input '%s' is unlocked (e.g. lacks a Git revision) but is checked by NAR hash. "
|
||||
@@ -219,8 +219,7 @@ static void fetchTree(
|
||||
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
|
||||
}
|
||||
|
||||
auto cachedInput =
|
||||
state.inputCache->getAccessor(state.fetchSettings, state.store, input, fetchers::UseRegistries::No);
|
||||
auto cachedInput = state.inputCache->getAccessor(state.store, input, fetchers::UseRegistries::No);
|
||||
|
||||
auto storePath = state.mountInput(cachedInput.lockedInput, input, cachedInput.accessor);
|
||||
|
||||
@@ -235,127 +234,229 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, V
|
||||
static RegisterPrimOp primop_fetchTree({
|
||||
.name = "fetchTree",
|
||||
.args = {"input"},
|
||||
.doc = []() -> std::string {
|
||||
std::string doc = stripIndentation(R"(
|
||||
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
|
||||
.doc = R"(
|
||||
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
|
||||
|
||||
- the resulting fixed-output [store path](@docroot@/store/store-path.md)
|
||||
- the corresponding [NAR](@docroot@/store/file-system-object/content-address.md#serial-nix-archive) hash
|
||||
- backend-specific metadata (currently not documented). <!-- TODO: document output attributes -->
|
||||
- the resulting fixed-output [store path](@docroot@/store/store-path.md)
|
||||
- the corresponding [NAR](@docroot@/store/file-system-object/content-address.md#serial-nix-archive) hash
|
||||
- backend-specific metadata (currently not documented). <!-- TODO: document output attributes -->
|
||||
|
||||
*input* must be an attribute set with the following attributes:
|
||||
*input* must be an attribute set with the following attributes:
|
||||
|
||||
- `type` (String, required)
|
||||
- `type` (String, required)
|
||||
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
|
||||
- `narHash` (String, optional)
|
||||
- `narHash` (String, optional)
|
||||
|
||||
The `narHash` parameter can be used to substitute the source of the tree.
|
||||
It also allows for verification of tree contents that may not be provided by the underlying transfer mechanism.
|
||||
If `narHash` is set, the source is first looked up is the Nix store and [substituters](@docroot@/command-ref/conf-file.md#conf-substituters), and only fetched if not available.
|
||||
The `narHash` parameter can be used to substitute the source of the tree.
|
||||
It also allows for verification of tree contents that may not be provided by the underlying transfer mechanism.
|
||||
If `narHash` is set, the source is first looked up is the Nix store and [substituters](@docroot@/command-ref/conf-file.md#conf-substituters), and only fetched if not available.
|
||||
|
||||
A subset of the output attributes of `fetchTree` can be re-used for subsequent calls to `fetchTree` to produce the same result again.
|
||||
That is, `fetchTree` is idempotent.
|
||||
A subset of the output attributes of `fetchTree` can be re-used for subsequent calls to `fetchTree` to produce the same result again.
|
||||
That is, `fetchTree` is idempotent.
|
||||
|
||||
Downloads are cached in `$XDG_CACHE_HOME/nix`.
|
||||
The remote source is fetched from the network if both are true:
|
||||
- A NAR hash is supplied and the corresponding store path is not [valid](@docroot@/glossary.md#gloss-validity), that is, not available in the store
|
||||
Downloads are cached in `$XDG_CACHE_HOME/nix`.
|
||||
The remote source is fetched from the network if both are true:
|
||||
- A NAR hash is supplied and the corresponding store path is not [valid](@docroot@/glossary.md#gloss-validity), that is, not available in the store
|
||||
|
||||
> **Note**
|
||||
> **Note**
|
||||
>
|
||||
> [Substituters](@docroot@/command-ref/conf-file.md#conf-substituters) are not used in fetching.
|
||||
|
||||
- There is no cache entry or the cache entry is older than [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl)
|
||||
|
||||
## Source types
|
||||
|
||||
The following source types and associated input attributes are supported.
|
||||
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
|
||||
- `"file"`
|
||||
|
||||
Place a plain file into the Nix store.
|
||||
This is similar to [`builtins.fetchurl`](@docroot@/language/builtins.md#builtins-fetchurl)
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
Supported protocols:
|
||||
|
||||
- `https`
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> [Substituters](@docroot@/command-ref/conf-file.md#conf-substituters) are not used in fetching.
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "https://example.com/index.html";
|
||||
> }
|
||||
> ```
|
||||
|
||||
- There is no cache entry or the cache entry is older than [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl)
|
||||
- `http`
|
||||
|
||||
## Source types
|
||||
Insecure HTTP transfer for legacy sources.
|
||||
|
||||
The following source types and associated input attributes are supported.
|
||||
> **Warning**
|
||||
>
|
||||
> HTTP performs no encryption or authentication.
|
||||
> Use a `narHash` known in advance to ensure the output has expected contents.
|
||||
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
)");
|
||||
- `file`
|
||||
|
||||
auto indentString = [](std::string const & str, std::string const & indent) {
|
||||
std::string result;
|
||||
std::istringstream stream(str);
|
||||
std::string line;
|
||||
bool first = true;
|
||||
while (std::getline(stream, line)) {
|
||||
if (!first)
|
||||
result += "\n";
|
||||
result += indent + line;
|
||||
first = false;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
A file on the local file system.
|
||||
|
||||
for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) {
|
||||
doc += "\n- `" + quoteString(schemeName, '"') + "`\n\n";
|
||||
doc += indentString(scheme->schemeDescription(), " ");
|
||||
if (!doc.empty() && doc.back() != '\n')
|
||||
doc += "\n";
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "file:///home/eelco/nix/README.md";
|
||||
> }
|
||||
> ```
|
||||
|
||||
for (const auto & [attrName, attribute] : scheme->allowedAttrs()) {
|
||||
doc += "\n - `" + attrName + "` (" + attribute.type + ", "
|
||||
+ (attribute.required ? "required" : "optional") + ")\n\n";
|
||||
doc += indentString(stripIndentation(attribute.doc), " ");
|
||||
if (!doc.empty() && doc.back() != '\n')
|
||||
doc += "\n";
|
||||
}
|
||||
}
|
||||
- `"git"`
|
||||
|
||||
doc += "\n" + stripIndentation(R"(
|
||||
The following input types are still subject to change:
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
|
||||
- `"path"`
|
||||
- `"github"`
|
||||
- `"gitlab"`
|
||||
- `"sourcehut"`
|
||||
- `"mercurial"`
|
||||
- `allRefs` (Bool, optional)
|
||||
|
||||
*input* can also be a [URL-like reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references).
|
||||
The additional input types and the URL-like syntax requires the [`flakes` experimental feature](@docroot@/development/experimental-features.md#xp-feature-flakes) to be enabled.
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
With this argument being true, it's possible to load a `rev` from *any* `ref`.
|
||||
(Without setting this option, only `rev`s from the specified `ref` are supported).
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `lastModified` (Integer, optional)
|
||||
|
||||
Unix timestamp of the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
|
||||
- `lfs` (Bool, optional)
|
||||
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `ref` (String, optional)
|
||||
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
A [Git reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References), such as a branch or tag name.
|
||||
|
||||
Default: `"HEAD"`
|
||||
|
||||
- `rev` (String, optional)
|
||||
|
||||
A Git revision; a commit hash.
|
||||
|
||||
Default: the tip of `ref`
|
||||
|
||||
- `revCount` (Integer, optional)
|
||||
|
||||
Number of revisions in the history of the Git repository before the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
|
||||
- `shallow` (Bool, optional)
|
||||
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
|
||||
Default: `true`
|
||||
|
||||
- `submodules` (Bool, optional)
|
||||
|
||||
Also fetch submodules if available.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
The URL formats supported are the same as for Git itself.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch a GitHub repository using the attribute set representation:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree {
|
||||
> type = "github";
|
||||
> owner = "NixOS";
|
||||
> repo = "nixpkgs";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> This evaluates to the following attribute set:
|
||||
>
|
||||
> ```nix
|
||||
> {
|
||||
> lastModified = 1686503798;
|
||||
> lastModifiedDate = "20230611171638";
|
||||
> narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=";
|
||||
> outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> shortRev = "ae2e6b3";
|
||||
> fetchTree {
|
||||
> type = "git";
|
||||
> url = "git@github.com:NixOS/nixpkgs.git";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Example**
|
||||
> **Note**
|
||||
>
|
||||
> Fetch the same GitHub repository using the URL-like syntax:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||
> ```
|
||||
)");
|
||||
> If the URL points to a local directory, and no `ref` or `rev` is given, Nix only considers files added to the Git index, as listed by `git ls-files` but use the *current file contents* of the Git working directory.
|
||||
|
||||
return doc;
|
||||
}(),
|
||||
- `"tarball"`
|
||||
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underlying implementation as [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball)
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "tarball";
|
||||
> url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11";
|
||||
> }
|
||||
> ```
|
||||
|
||||
The following input types are still subject to change:
|
||||
|
||||
- `"path"`
|
||||
- `"github"`
|
||||
- `"gitlab"`
|
||||
- `"sourcehut"`
|
||||
- `"mercurial"`
|
||||
|
||||
*input* can also be a [URL-like reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references).
|
||||
The additional input types and the URL-like syntax requires the [`flakes` experimental feature](@docroot@/development/experimental-features.md#xp-feature-flakes) to be enabled.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch a GitHub repository using the attribute set representation:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree {
|
||||
> type = "github";
|
||||
> owner = "NixOS";
|
||||
> repo = "nixpkgs";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> This evaluates to the following attribute set:
|
||||
>
|
||||
> ```nix
|
||||
> {
|
||||
> lastModified = 1686503798;
|
||||
> lastModifiedDate = "20230611171638";
|
||||
> narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=";
|
||||
> outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> shortRev = "ae2e6b3";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch the same GitHub repository using the URL-like syntax:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||
> ```
|
||||
)",
|
||||
.fun = prim_fetchTree,
|
||||
.experimentalFeature = Xp::FetchTree,
|
||||
});
|
||||
|
||||
@@ -196,7 +196,7 @@ TEST_F(GitTest, submodulePeriodSupport)
|
||||
{"ref", "main"},
|
||||
});
|
||||
|
||||
auto [accessor, i] = input.getAccessor(settings, store);
|
||||
auto [accessor, i] = input.getAccessor(store);
|
||||
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("deps/sub/lib.txt")), "hello from submodule\n");
|
||||
}
|
||||
|
||||
@@ -26,9 +26,18 @@ void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
|
||||
throw Error("Input scheme with name %s already registered", schemeName);
|
||||
}
|
||||
|
||||
const InputSchemeMap & getAllInputSchemes()
|
||||
nlohmann::json dumpRegisterInputSchemeInfo()
|
||||
{
|
||||
return inputSchemes();
|
||||
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 Settings & settings, const std::string & url, bool requireTree)
|
||||
@@ -80,7 +89,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
|
||||
// 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 input{settings};
|
||||
input.attrs = attrs;
|
||||
fixupInput(input);
|
||||
return input;
|
||||
@@ -150,9 +159,9 @@ bool Input::isDirect() const
|
||||
return !scheme || scheme->isDirect(*this);
|
||||
}
|
||||
|
||||
bool Input::isLocked(const Settings & settings) const
|
||||
bool Input::isLocked() const
|
||||
{
|
||||
return scheme && scheme->isLocked(settings, *this);
|
||||
return scheme && scheme->isLocked(*this);
|
||||
}
|
||||
|
||||
bool Input::isFinal() const
|
||||
@@ -189,17 +198,17 @@ bool Input::contains(const Input & other) const
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
std::pair<StorePath, Input> Input::fetchToStore(const Settings & settings, ref<Store> store) const
|
||||
std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
|
||||
{
|
||||
if (!scheme)
|
||||
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
|
||||
|
||||
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
|
||||
try {
|
||||
auto [accessor, result] = getAccessorUnchecked(settings, store);
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
|
||||
auto storePath =
|
||||
nix::fetchToStore(settings, *store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
nix::fetchToStore(*settings, *store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(storePath)->narHash;
|
||||
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
@@ -288,10 +297,10 @@ void Input::checkLocks(Input specified, Input & result)
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settings, ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(ref<Store> store) const
|
||||
{
|
||||
try {
|
||||
auto [accessor, result] = getAccessorUnchecked(settings, store);
|
||||
auto [accessor, result] = getAccessorUnchecked(store);
|
||||
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
|
||||
@@ -304,7 +313,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settin
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings & settings, ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> store) const
|
||||
{
|
||||
// FIXME: cache the accessor
|
||||
|
||||
@@ -340,7 +349,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings
|
||||
if (accessor->fingerprint) {
|
||||
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
|
||||
auto cacheKey = makeFetchToStoreCacheKey(getName(), *accessor->fingerprint, method, "/");
|
||||
settings.getCache()->upsert(cacheKey, *store, {}, storePath);
|
||||
settings->getCache()->upsert(cacheKey, *store, {}, storePath);
|
||||
}
|
||||
|
||||
accessor->setPathDisplay("«" + to_string() + "»");
|
||||
@@ -351,7 +360,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings
|
||||
}
|
||||
}
|
||||
|
||||
auto [accessor, result] = scheme->getAccessor(settings, store, *this);
|
||||
auto [accessor, result] = scheme->getAccessor(store, *this);
|
||||
|
||||
if (!accessor->fingerprint)
|
||||
accessor->fingerprint = result.getFingerprint(store);
|
||||
@@ -368,10 +377,10 @@ Input Input::applyOverrides(std::optional<std::string> ref, std::optional<Hash>
|
||||
return scheme->applyOverrides(*this, ref, rev);
|
||||
}
|
||||
|
||||
void Input::clone(const Settings & settings, const Path & destDir) const
|
||||
void Input::clone(const Path & destDir) const
|
||||
{
|
||||
assert(scheme);
|
||||
scheme->clone(settings, *this, destDir);
|
||||
scheme->clone(*this, destDir);
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> Input::getSourcePath() const
|
||||
@@ -484,7 +493,7 @@ void InputScheme::putFile(
|
||||
throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path);
|
||||
}
|
||||
|
||||
void InputScheme::clone(const Settings & settings, const Input & input, const Path & destDir) const
|
||||
void InputScheme::clone(const Input & input, const Path & destDir) const
|
||||
{
|
||||
throw Error("do not know how to clone input '%s'", input.to_string());
|
||||
}
|
||||
|
||||
@@ -194,183 +194,28 @@ struct GitInputScheme : InputScheme
|
||||
return "git";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
StringSet allowedAttrs() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
)");
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{
|
||||
.type = "String",
|
||||
.required = true,
|
||||
.doc = R"(
|
||||
The URL formats supported are the same as for Git itself.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "git";
|
||||
> url = "git@github.com:NixOS/nixpkgs.git";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If the URL points to a local directory, and no `ref` or `rev` is given, Nix only considers files added to the Git index, as listed by `git ls-files` but uses the *current file contents* of the Git working directory.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{
|
||||
.type = "String",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
A [Git reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References), such as a branch or tag name.
|
||||
|
||||
Default: `"HEAD"`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{
|
||||
.type = "String",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
A Git revision; a commit hash.
|
||||
|
||||
Default: the tip of `ref`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"shallow",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
|
||||
Default: `true`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"submodules",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Also fetch submodules if available.
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"lfs",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"exportIgnore",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{
|
||||
.type = "Integer",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Unix timestamp of the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{
|
||||
.type = "Integer",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Number of revisions in the history of the Git repository before the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"allRefs",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
With this argument being true, it's possible to load a `rev` from *any* `ref`.
|
||||
(Without setting this option, only `rev`s from the specified `ref` are supported).
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"dirtyRev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"dirtyShortRev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"verifyCommit",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"keytype",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"publicKey",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"publicKeys",
|
||||
{},
|
||||
},
|
||||
return {
|
||||
"url",
|
||||
"ref",
|
||||
"rev",
|
||||
"shallow",
|
||||
"submodules",
|
||||
"lfs",
|
||||
"exportIgnore",
|
||||
"lastModified",
|
||||
"revCount",
|
||||
"narHash",
|
||||
"allRefs",
|
||||
"name",
|
||||
"dirtyRev",
|
||||
"dirtyShortRev",
|
||||
"verifyCommit",
|
||||
"keytype",
|
||||
"publicKey",
|
||||
"publicKeys",
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -384,7 +229,7 @@ struct GitInputScheme : InputScheme
|
||||
if (auto ref = maybeGetStrAttr(attrs, "ref"); ref && !isLegalRefName(*ref))
|
||||
throw BadURL("invalid Git branch/tag name '%s'", *ref);
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs = attrs;
|
||||
input.attrs["url"] = fixGitURL(getStrAttr(attrs, "url")).to_string();
|
||||
getShallowAttr(input);
|
||||
@@ -433,7 +278,7 @@ struct GitInputScheme : InputScheme
|
||||
return res;
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
{
|
||||
auto repoInfo = getRepoInfo(input);
|
||||
|
||||
@@ -778,7 +623,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessorFromCommit(const Settings & settings, ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
getAccessorFromCommit(ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
{
|
||||
assert(!repoInfo.workdirInfo.isDirty);
|
||||
|
||||
@@ -888,10 +733,10 @@ struct GitInputScheme : InputScheme
|
||||
|
||||
auto rev = *input.getRev();
|
||||
|
||||
input.attrs.insert_or_assign("lastModified", getLastModified(settings, repoInfo, repoDir, rev));
|
||||
input.attrs.insert_or_assign("lastModified", getLastModified(*input.settings, repoInfo, repoDir, rev));
|
||||
|
||||
if (!getShallowAttr(input))
|
||||
input.attrs.insert_or_assign("revCount", getRevCount(settings, repoInfo, repoDir, rev));
|
||||
input.attrs.insert_or_assign("revCount", getRevCount(*input.settings, repoInfo, repoDir, rev));
|
||||
|
||||
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
|
||||
|
||||
@@ -934,8 +779,8 @@ struct GitInputScheme : InputScheme
|
||||
attrs.insert_or_assign("submodules", Explicit<bool>{true});
|
||||
attrs.insert_or_assign("lfs", Explicit<bool>{smudgeLfs});
|
||||
attrs.insert_or_assign("allRefs", Explicit<bool>{true});
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(settings, store);
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(store);
|
||||
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
||||
mounts.insert_or_assign(submodule.path, submoduleAccessor);
|
||||
}
|
||||
@@ -952,7 +797,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessorFromWorkdir(const Settings & settings, ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
getAccessorFromWorkdir(ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
{
|
||||
auto repoPath = repoInfo.getPath().value();
|
||||
|
||||
@@ -984,8 +829,8 @@ struct GitInputScheme : InputScheme
|
||||
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
|
||||
// attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(settings, store);
|
||||
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||
auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(store);
|
||||
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
||||
|
||||
/* If the submodule is dirty, mark this repo dirty as
|
||||
@@ -1012,12 +857,12 @@ struct GitInputScheme : InputScheme
|
||||
input.attrs.insert_or_assign("rev", rev.gitRev());
|
||||
if (!getShallowAttr(input)) {
|
||||
input.attrs.insert_or_assign(
|
||||
"revCount", rev == nullRev ? 0 : getRevCount(settings, repoInfo, repoPath, rev));
|
||||
"revCount", rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev));
|
||||
}
|
||||
|
||||
verifyCommit(input, repo);
|
||||
} else {
|
||||
repoInfo.warnDirty(settings);
|
||||
repoInfo.warnDirty(*input.settings);
|
||||
|
||||
if (repoInfo.workdirInfo.headRev) {
|
||||
input.attrs.insert_or_assign("dirtyRev", repoInfo.workdirInfo.headRev->gitRev() + "-dirty");
|
||||
@@ -1029,14 +874,14 @@ struct GitInputScheme : InputScheme
|
||||
|
||||
input.attrs.insert_or_assign(
|
||||
"lastModified",
|
||||
repoInfo.workdirInfo.headRev ? getLastModified(settings, repoInfo, repoPath, *repoInfo.workdirInfo.headRev)
|
||||
: 0);
|
||||
repoInfo.workdirInfo.headRev
|
||||
? getLastModified(*input.settings, repoInfo, repoPath, *repoInfo.workdirInfo.headRev)
|
||||
: 0);
|
||||
|
||||
return {accessor, std::move(input)};
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
|
||||
@@ -1052,8 +897,8 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
auto [accessor, final] = input.getRef() || input.getRev() || !repoInfo.getPath()
|
||||
? getAccessorFromCommit(settings, store, repoInfo, std::move(input))
|
||||
: getAccessorFromWorkdir(settings, store, repoInfo, std::move(input));
|
||||
? getAccessorFromCommit(store, repoInfo, std::move(input))
|
||||
: getAccessorFromWorkdir(store, repoInfo, std::move(input));
|
||||
|
||||
return {accessor, std::move(final)};
|
||||
}
|
||||
@@ -1089,7 +934,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
}
|
||||
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
bool isLocked(const Input & input) const override
|
||||
{
|
||||
auto rev = input.getRev();
|
||||
return rev && rev != nullRev;
|
||||
|
||||
@@ -92,7 +92,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
if (ref && rev)
|
||||
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url, *ref, rev->gitRev());
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
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]);
|
||||
@@ -110,43 +110,18 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
StringSet allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"owner",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"repo",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"host",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"treeHash",
|
||||
{},
|
||||
},
|
||||
return {
|
||||
"owner",
|
||||
"repo",
|
||||
"ref",
|
||||
"rev",
|
||||
"narHash",
|
||||
"lastModified",
|
||||
"host",
|
||||
"treeHash",
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const fetchers::Settings & settings, const Attrs & attrs) const override
|
||||
@@ -154,7 +129,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
getStrAttr(attrs, "owner");
|
||||
getStrAttr(attrs, "repo");
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -258,9 +233,9 @@ struct GitArchiveInputScheme : InputScheme
|
||||
std::optional<Hash> treeHash;
|
||||
};
|
||||
|
||||
virtual RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const = 0;
|
||||
virtual RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const = 0;
|
||||
|
||||
virtual DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const = 0;
|
||||
virtual DownloadUrl getDownloadUrl(const Input & input) const = 0;
|
||||
|
||||
struct TarballInfo
|
||||
{
|
||||
@@ -268,7 +243,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
time_t lastModified;
|
||||
};
|
||||
|
||||
std::pair<Input, TarballInfo> downloadArchive(const Settings & settings, ref<Store> store, Input input) const
|
||||
std::pair<Input, TarballInfo> downloadArchive(ref<Store> store, Input input) const
|
||||
{
|
||||
if (!maybeGetStrAttr(input.attrs, "ref"))
|
||||
input.attrs.insert_or_assign("ref", "HEAD");
|
||||
@@ -277,7 +252,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
|
||||
auto rev = input.getRev();
|
||||
if (!rev) {
|
||||
auto refInfo = getRevFromRef(settings, store, input);
|
||||
auto refInfo = getRevFromRef(store, input);
|
||||
rev = refInfo.rev;
|
||||
upstreamTreeHash = refInfo.treeHash;
|
||||
debug("HEAD revision for '%s' is %s", input.to_string(), refInfo.rev.gitRev());
|
||||
@@ -286,7 +261,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
input.attrs.erase("ref");
|
||||
input.attrs.insert_or_assign("rev", rev->gitRev());
|
||||
|
||||
auto cache = settings.getCache();
|
||||
auto cache = input.settings->getCache();
|
||||
|
||||
Cache::Key treeHashKey{"gitRevToTreeHash", {{"rev", rev->gitRev()}}};
|
||||
Cache::Key lastModifiedKey{"gitRevToLastModified", {{"rev", rev->gitRev()}}};
|
||||
@@ -295,7 +270,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
if (auto lastModifiedAttrs = cache->lookup(lastModifiedKey)) {
|
||||
auto treeHash = getRevAttr(*treeHashAttrs, "treeHash");
|
||||
auto lastModified = getIntAttr(*lastModifiedAttrs, "lastModified");
|
||||
if (settings.getTarballCache()->hasObject(treeHash))
|
||||
if (input.settings->getTarballCache()->hasObject(treeHash))
|
||||
return {std::move(input), TarballInfo{.treeHash = treeHash, .lastModified = (time_t) lastModified}};
|
||||
else
|
||||
debug("Git tree with hash '%s' has disappeared from the cache, refetching...", treeHash.gitRev());
|
||||
@@ -303,7 +278,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
}
|
||||
|
||||
/* Stream the tarball into the tarball cache. */
|
||||
auto url = getDownloadUrl(settings, input);
|
||||
auto url = getDownloadUrl(input);
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
FileTransferRequest req(url.url);
|
||||
@@ -315,7 +290,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
*logger, lvlInfo, actUnknown, fmt("unpacking '%s' into the Git cache", input.to_string()));
|
||||
|
||||
TarArchive archive{*source};
|
||||
auto tarballCache = settings.getTarballCache();
|
||||
auto tarballCache = input.settings->getTarballCache();
|
||||
auto parseSink = tarballCache->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
auto tree = parseSink->flush();
|
||||
@@ -340,10 +315,9 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return {std::move(input), tarballInfo};
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
{
|
||||
auto [input, tarballInfo] = downloadArchive(settings, store, _input);
|
||||
auto [input, tarballInfo] = downloadArchive(store, _input);
|
||||
|
||||
#if 0
|
||||
input.attrs.insert_or_assign("treeHash", tarballInfo.treeHash.gitRev());
|
||||
@@ -351,18 +325,19 @@ struct GitArchiveInputScheme : InputScheme
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
|
||||
|
||||
auto accessor =
|
||||
settings.getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
|
||||
input.settings->getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
|
||||
|
||||
return {accessor, input};
|
||||
}
|
||||
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
bool isLocked(const Input & input) const override
|
||||
{
|
||||
/* Since we can't verify the integrity of the tarball from the
|
||||
Git revision alone, we also require a NAR hash for
|
||||
locking. FIXME: in the future, we may want to require a Git
|
||||
tree hash instead of a NAR hash. */
|
||||
return input.getRev().has_value() && (settings.trustTarballsFromGitForges || input.getNarHash().has_value());
|
||||
return input.getRev().has_value()
|
||||
&& (input.settings->trustTarballsFromGitForges || input.getNarHash().has_value());
|
||||
}
|
||||
|
||||
std::optional<ExperimentalFeature> experimentalFeature() const override
|
||||
@@ -386,12 +361,6 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return "github";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Github supports PAT/OAuth2 tokens and HTTP Basic
|
||||
@@ -418,7 +387,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return getStrAttr(input.attrs, "repo");
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
auto url = fmt(
|
||||
@@ -428,9 +397,9 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
getRepo(input),
|
||||
*input.getRef());
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
|
||||
auto downloadResult = downloadFile(store, settings, url, "source", headers);
|
||||
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
@@ -439,11 +408,11 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
.treeHash = Hash::parseAny(std::string{json["commit"]["tree"]["sha"]}, HashAlgorithm::SHA1)};
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
|
||||
// If we have no auth headers then we default to the public archive
|
||||
// urls so we do not run into rate limits.
|
||||
@@ -457,12 +426,12 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
Input::fromURL(settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
|
||||
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(settings, destDir);
|
||||
.clone(destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -473,12 +442,6 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return "gitlab";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Gitlab supports 4 kinds of authorization, two of which are
|
||||
@@ -498,7 +461,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return std::make_pair(token.substr(0, fldsplit), token.substr(fldsplit + 1));
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// See rate limiting note below
|
||||
@@ -509,9 +472,9 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
getStrAttr(input.attrs, "repo"),
|
||||
*input.getRef());
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
|
||||
auto downloadResult = downloadFile(store, settings, url, "source", headers);
|
||||
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
@@ -525,7 +488,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
}
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
{
|
||||
// This endpoint has a rate limit threshold that may be
|
||||
// server-specific and vary based whether the user is
|
||||
@@ -540,19 +503,19 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(HashFormat::Base16, false));
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// FIXME: get username somewhere
|
||||
Input::fromURL(
|
||||
settings,
|
||||
*input.settings,
|
||||
fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(settings, destDir);
|
||||
.clone(destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -563,12 +526,6 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
return "sourcehut";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// SourceHut supports both PAT and OAuth2. See
|
||||
@@ -579,7 +536,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
// Once it is implemented, however, should work as expected.
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
{
|
||||
// TODO: In the future, when the sourcehut graphql API is implemented for mercurial
|
||||
// and with anonymous access, this method should use it instead.
|
||||
@@ -590,11 +547,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
auto base_url =
|
||||
fmt("https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"));
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
|
||||
std::string refUri;
|
||||
if (ref == "HEAD") {
|
||||
auto downloadFileResult = downloadFile(store, settings, fmt("%s/HEAD", base_url), "source", headers);
|
||||
auto downloadFileResult = downloadFile(store, *input.settings, fmt("%s/HEAD", base_url), "source", headers);
|
||||
auto contents = store->requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
|
||||
auto remoteLine = git::parseLsRemoteLine(getLine(contents).first);
|
||||
@@ -607,7 +564,8 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
}
|
||||
std::regex refRegex(refUri);
|
||||
|
||||
auto downloadFileResult = downloadFile(store, settings, fmt("%s/info/refs", base_url), "source", headers);
|
||||
auto downloadFileResult =
|
||||
downloadFile(store, *input.settings, fmt("%s/info/refs", base_url), "source", headers);
|
||||
auto contents = store->requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
std::istringstream is(contents);
|
||||
|
||||
@@ -625,7 +583,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
return RefInfo{.rev = Hash::parseAny(*id, HashAlgorithm::SHA1)};
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const override
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||
auto url =
|
||||
@@ -635,18 +593,18 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(HashFormat::Base16, false));
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(settings, host, input);
|
||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Input & input, const Path & destDir) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||
Input::fromURL(
|
||||
settings,
|
||||
*input.settings,
|
||||
fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(settings, destDir);
|
||||
.clone(destDir);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,13 @@ struct Input
|
||||
{
|
||||
friend struct InputScheme;
|
||||
|
||||
const Settings * settings;
|
||||
|
||||
Input(const Settings & settings)
|
||||
: settings{&settings}
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<InputScheme> scheme; // note: can be null
|
||||
Attrs attrs;
|
||||
|
||||
@@ -80,7 +87,7 @@ public:
|
||||
* attributes like a Git revision or NAR hash that uniquely
|
||||
* identify its contents.
|
||||
*/
|
||||
bool isLocked(const Settings & settings) const;
|
||||
bool isLocked() const;
|
||||
|
||||
/**
|
||||
* Only for relative path flakes, i.e. 'path:./foo', returns the
|
||||
@@ -113,7 +120,7 @@ public:
|
||||
* Fetch the entire input into the Nix store, returning the
|
||||
* location in the Nix store and the locked input.
|
||||
*/
|
||||
std::pair<StorePath, Input> fetchToStore(const Settings & settings, ref<Store> store) const;
|
||||
std::pair<StorePath, Input> fetchToStore(ref<Store> store) const;
|
||||
|
||||
/**
|
||||
* Check the locking attributes in `result` against
|
||||
@@ -133,17 +140,17 @@ public:
|
||||
* input without copying it to the store. Also return a possibly
|
||||
* unlocked input.
|
||||
*/
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(const Settings & settings, ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store) const;
|
||||
|
||||
private:
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessorUnchecked(const Settings & settings, ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessorUnchecked(ref<Store> store) const;
|
||||
|
||||
public:
|
||||
|
||||
Input applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const;
|
||||
|
||||
void clone(const Settings & settings, const Path & destDir) const;
|
||||
void clone(const Path & destDir) const;
|
||||
|
||||
std::optional<std::filesystem::path> getSourcePath() const;
|
||||
|
||||
@@ -203,33 +210,20 @@ struct InputScheme
|
||||
*/
|
||||
virtual std::string_view schemeName() const = 0;
|
||||
|
||||
/**
|
||||
* Longform description of this scheme, for documentation purposes.
|
||||
*/
|
||||
virtual std::string schemeDescription() const = 0;
|
||||
|
||||
// TODO remove these defaults
|
||||
struct AttributeInfo
|
||||
{
|
||||
const char * type = "String";
|
||||
bool required = true;
|
||||
const char * doc = "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Allowed attributes in an attribute set that is converted to an
|
||||
* input, and documentation for each attribute.
|
||||
* input.
|
||||
*
|
||||
* `type` is not included from this map, because the `type` field is
|
||||
* `type` is not included from this set, because the `type` field is
|
||||
parsed first to choose which scheme; `type` is always required.
|
||||
*/
|
||||
virtual const std::map<std::string, AttributeInfo> & allowedAttrs() const = 0;
|
||||
virtual StringSet allowedAttrs() const = 0;
|
||||
|
||||
virtual ParsedURL toURL(const Input & input) const;
|
||||
|
||||
virtual Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const;
|
||||
|
||||
virtual void clone(const Settings & settings, const Input & input, const Path & destDir) const;
|
||||
virtual void clone(const Input & input, const Path & destDir) const;
|
||||
|
||||
virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const;
|
||||
|
||||
@@ -239,8 +233,7 @@ struct InputScheme
|
||||
std::string_view contents,
|
||||
std::optional<std::string> commitMsg) const;
|
||||
|
||||
virtual std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & input) const = 0;
|
||||
virtual std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & input) const = 0;
|
||||
|
||||
/**
|
||||
* Is this `InputScheme` part of an experimental feature?
|
||||
@@ -257,7 +250,7 @@ struct InputScheme
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
virtual bool isLocked(const Settings & settings, const Input & input) const
|
||||
virtual bool isLocked(const Input & input) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -276,12 +269,7 @@ struct InputScheme
|
||||
|
||||
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
|
||||
|
||||
using InputSchemeMap = std::map<std::string_view, std::shared_ptr<InputScheme>>;
|
||||
|
||||
/**
|
||||
* Use this for docs, not for finding a specific scheme
|
||||
*/
|
||||
const InputSchemeMap & getAllInputSchemes();
|
||||
nlohmann::json dumpRegisterInputSchemeInfo();
|
||||
|
||||
struct PublicKey
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace nix::fetchers {
|
||||
|
||||
enum class UseRegistries : int;
|
||||
struct Settings;
|
||||
|
||||
struct InputCache
|
||||
{
|
||||
@@ -15,8 +14,7 @@ struct InputCache
|
||||
Attrs extraAttrs;
|
||||
};
|
||||
|
||||
CachedResult
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & originalInput, UseRegistries useRegistries);
|
||||
CachedResult getAccessor(ref<Store> store, const Input & originalInput, UseRegistries useRegistries);
|
||||
|
||||
struct CachedInput
|
||||
{
|
||||
|
||||
@@ -59,7 +59,7 @@ Path getUserRegistryPath();
|
||||
|
||||
Registries getRegistries(const Settings & settings, ref<Store> store);
|
||||
|
||||
void overrideRegistry(const Settings & settings, const Input & from, const Input & to, const Attrs & extraAttrs);
|
||||
void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs);
|
||||
|
||||
enum class UseRegistries : int {
|
||||
No,
|
||||
@@ -71,7 +71,6 @@ enum class UseRegistries : int {
|
||||
* Rewrite a flakeref using the registries. If `filter` is set, only
|
||||
* use the registries for which the filter function returns true.
|
||||
*/
|
||||
std::pair<Input, Attrs>
|
||||
lookupInRegistries(const Settings & settings, ref<Store> store, const Input & input, UseRegistries useRegistries);
|
||||
std::pair<Input, Attrs> lookupInRegistries(ref<Store> store, const Input & input, UseRegistries useRegistries);
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -44,7 +44,7 @@ struct IndirectInputScheme : InputScheme
|
||||
|
||||
// FIXME: forbid query params?
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs.insert_or_assign("type", "indirect");
|
||||
input.attrs.insert_or_assign("id", id);
|
||||
if (rev)
|
||||
@@ -60,33 +60,14 @@ struct IndirectInputScheme : InputScheme
|
||||
return "indirect";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
StringSet allowedAttrs() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"id",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
return {
|
||||
"id",
|
||||
"ref",
|
||||
"rev",
|
||||
"narHash",
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -95,7 +76,7 @@ struct IndirectInputScheme : InputScheme
|
||||
if (!std::regex_match(id, flakeRegex))
|
||||
throw BadURL("'%s' is not a valid flake ID", id);
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -125,8 +106,7 @@ struct IndirectInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & input) const override
|
||||
{
|
||||
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
|
||||
}
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
InputCache::CachedResult InputCache::getAccessor(
|
||||
const Settings & settings, ref<Store> store, const Input & originalInput, UseRegistries useRegistries)
|
||||
InputCache::CachedResult
|
||||
InputCache::getAccessor(ref<Store> store, const Input & originalInput, UseRegistries useRegistries)
|
||||
{
|
||||
auto fetched = lookup(originalInput);
|
||||
Input resolvedInput = originalInput;
|
||||
|
||||
if (!fetched) {
|
||||
if (originalInput.isDirect()) {
|
||||
auto [accessor, lockedInput] = originalInput.getAccessor(settings, store);
|
||||
auto [accessor, lockedInput] = originalInput.getAccessor(store);
|
||||
fetched.emplace(CachedInput{.lockedInput = lockedInput, .accessor = accessor});
|
||||
} else {
|
||||
if (useRegistries != UseRegistries::No) {
|
||||
auto [res, extraAttrs] = lookupInRegistries(settings, store, originalInput, useRegistries);
|
||||
auto [res, extraAttrs] = lookupInRegistries(store, originalInput, useRegistries);
|
||||
resolvedInput = std::move(res);
|
||||
fetched = lookup(resolvedInput);
|
||||
if (!fetched) {
|
||||
auto [accessor, lockedInput] = resolvedInput.getAccessor(settings, store);
|
||||
auto [accessor, lockedInput] = resolvedInput.getAccessor(store);
|
||||
fetched.emplace(
|
||||
CachedInput{.lockedInput = lockedInput, .accessor = accessor, .extraAttrs = extraAttrs});
|
||||
}
|
||||
|
||||
@@ -68,41 +68,16 @@ struct MercurialInputScheme : InputScheme
|
||||
return "hg";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
StringSet allowedAttrs() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
return {
|
||||
"url",
|
||||
"ref",
|
||||
"rev",
|
||||
"revCount",
|
||||
"narHash",
|
||||
"name",
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -114,7 +89,7 @@ struct MercurialInputScheme : InputScheme
|
||||
throw BadURL("invalid Mercurial branch/tag name '%s'", *ref);
|
||||
}
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -179,7 +154,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return {isLocal, isLocal ? renderUrlPathEnsureLegal(url.path) : url.to_string()};
|
||||
}
|
||||
|
||||
StorePath fetchToStore(const Settings & settings, ref<Store> store, Input & input) const
|
||||
StorePath fetchToStore(ref<Store> store, Input & input) const
|
||||
{
|
||||
auto origRev = input.getRev();
|
||||
|
||||
@@ -201,10 +176,10 @@ struct MercurialInputScheme : InputScheme
|
||||
/* This is an unclean working tree. So copy all tracked
|
||||
files. */
|
||||
|
||||
if (!settings.allowDirty)
|
||||
if (!input.settings->allowDirty)
|
||||
throw Error("Mercurial tree '%s' is unclean", actualUrl);
|
||||
|
||||
if (settings.warnDirty)
|
||||
if (input.settings->warnDirty)
|
||||
warn("Mercurial tree '%s' is unclean", actualUrl);
|
||||
|
||||
input.attrs.insert_or_assign("ref", chomp(runHg({"branch", "-R", actualUrl})));
|
||||
@@ -265,13 +240,13 @@ struct MercurialInputScheme : InputScheme
|
||||
Cache::Key refToRevKey{"hgRefToRev", {{"url", actualUrl}, {"ref", *input.getRef()}}};
|
||||
|
||||
if (!input.getRev()) {
|
||||
if (auto res = settings.getCache()->lookupWithTTL(refToRevKey))
|
||||
if (auto res = input.settings->getCache()->lookupWithTTL(refToRevKey))
|
||||
input.attrs.insert_or_assign("rev", getRevAttr(*res, "rev").gitRev());
|
||||
}
|
||||
|
||||
/* If we have a rev, check if we have a cached store path. */
|
||||
if (auto rev = input.getRev()) {
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(*rev), *store))
|
||||
if (auto res = input.settings->getCache()->lookupStorePath(revInfoKey(*rev), *store))
|
||||
return makeResult(res->value, res->storePath);
|
||||
}
|
||||
|
||||
@@ -325,7 +300,7 @@ struct MercurialInputScheme : InputScheme
|
||||
|
||||
/* Now that we have the rev, check the cache again for a
|
||||
cached store path. */
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(rev), *store))
|
||||
if (auto res = input.settings->getCache()->lookupStorePath(revInfoKey(rev), *store))
|
||||
return makeResult(res->value, res->storePath);
|
||||
|
||||
Path tmpDir = createTempDir();
|
||||
@@ -342,19 +317,18 @@ struct MercurialInputScheme : InputScheme
|
||||
});
|
||||
|
||||
if (!origRev)
|
||||
settings.getCache()->upsert(refToRevKey, {{"rev", rev.gitRev()}});
|
||||
input.settings->getCache()->upsert(refToRevKey, {{"rev", rev.gitRev()}});
|
||||
|
||||
settings.getCache()->upsert(revInfoKey(rev), *store, infoAttrs, storePath);
|
||||
input.settings->getCache()->upsert(revInfoKey(rev), *store, infoAttrs, storePath);
|
||||
|
||||
return makeResult(infoAttrs, std::move(storePath));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
|
||||
auto storePath = fetchToStore(settings, store, input);
|
||||
auto storePath = fetchToStore(store, input);
|
||||
auto accessor = store->requireStoreObjectAccessor(storePath);
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
@@ -362,7 +336,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return {accessor, input};
|
||||
}
|
||||
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
bool isLocked(const Input & input) const override
|
||||
{
|
||||
return (bool) input.getRev();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ struct PathInputScheme : InputScheme
|
||||
if (url.authority && url.authority->host.size())
|
||||
throw Error("path URL '%s' should not have an authority ('%s')", url, *url.authority);
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs.insert_or_assign("type", "path");
|
||||
input.attrs.insert_or_assign("path", renderUrlPathEnsureLegal(url.path));
|
||||
|
||||
@@ -40,49 +40,27 @@ struct PathInputScheme : InputScheme
|
||||
return "path";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
StringSet allowedAttrs() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"path",
|
||||
{},
|
||||
},
|
||||
return {
|
||||
"path",
|
||||
/* Allow the user to pass in "fake" tree info
|
||||
attributes. This is useful for making a pinned tree work
|
||||
the same as the repository from which is exported (e.g.
|
||||
path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...).
|
||||
*/
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
"rev",
|
||||
"revCount",
|
||||
"lastModified",
|
||||
"narHash",
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
{
|
||||
getStrAttr(attrs, "path");
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs = attrs;
|
||||
return input;
|
||||
}
|
||||
@@ -123,7 +101,7 @@ struct PathInputScheme : InputScheme
|
||||
return path;
|
||||
}
|
||||
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
bool isLocked(const Input & input) const override
|
||||
{
|
||||
return (bool) input.getNarHash();
|
||||
}
|
||||
@@ -138,8 +116,7 @@ struct PathInputScheme : InputScheme
|
||||
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
auto path = getStrAttr(input.attrs, "path");
|
||||
@@ -168,7 +145,7 @@ struct PathInputScheme : InputScheme
|
||||
auto info = store->queryPathInfo(*storePath);
|
||||
accessor->fingerprint =
|
||||
fmt("path:%s", store->queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
|
||||
settings.getCache()->upsert(
|
||||
input.settings->getCache()->upsert(
|
||||
makeFetchToStoreCacheKey(
|
||||
input.getName(), *accessor->fingerprint, ContentAddressMethod::Raw::NixArchive, "/"),
|
||||
*store,
|
||||
|
||||
@@ -131,9 +131,9 @@ std::shared_ptr<Registry> getFlagRegistry(const Settings & settings)
|
||||
return flagRegistry;
|
||||
}
|
||||
|
||||
void overrideRegistry(const Settings & settings, const Input & from, const Input & to, const Attrs & extraAttrs)
|
||||
void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs)
|
||||
{
|
||||
getFlagRegistry(settings)->add(from, to, extraAttrs);
|
||||
getFlagRegistry(*from.settings)->add(from, to, extraAttrs);
|
||||
}
|
||||
|
||||
static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, ref<Store> store)
|
||||
@@ -172,8 +172,7 @@ Registries getRegistries(const Settings & settings, ref<Store> store)
|
||||
return registries;
|
||||
}
|
||||
|
||||
std::pair<Input, Attrs>
|
||||
lookupInRegistries(const Settings & settings, ref<Store> store, const Input & _input, UseRegistries useRegistries)
|
||||
std::pair<Input, Attrs> lookupInRegistries(ref<Store> store, const Input & _input, UseRegistries useRegistries)
|
||||
{
|
||||
Attrs extraAttrs;
|
||||
int n = 0;
|
||||
@@ -188,7 +187,7 @@ restart:
|
||||
if (n > 100)
|
||||
throw Error("cycle detected in flake registry for '%s'", input.to_string());
|
||||
|
||||
for (auto & registry : getRegistries(settings, store)) {
|
||||
for (auto & registry : getRegistries(*input.settings, store)) {
|
||||
if (useRegistries == UseRegistries::Limited
|
||||
&& !(registry->type == fetchers::Registry::Flag || registry->type == fetchers::Registry::Global))
|
||||
continue;
|
||||
|
||||
@@ -224,7 +224,7 @@ ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings,
|
||||
|
||||
auto input = Input::fromAttrs(settings, std::move(attrs));
|
||||
|
||||
return input.getAccessor(settings, store).first;
|
||||
return input.getAccessor(store).first;
|
||||
}
|
||||
|
||||
// An input scheme corresponding to a curl-downloadable resource.
|
||||
@@ -252,7 +252,7 @@ struct CurlInputScheme : InputScheme
|
||||
if (!isValidURL(_url, requireTree))
|
||||
return std::nullopt;
|
||||
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
|
||||
auto url = _url;
|
||||
|
||||
@@ -278,7 +278,7 @@ struct CurlInputScheme : InputScheme
|
||||
HTTP request. Now that we've processed the Nix-specific
|
||||
attributes above, remove them so we don't also send them as
|
||||
part of the HTTP request. */
|
||||
for (auto & [param, _] : allowedAttrs())
|
||||
for (auto & param : allowedAttrs())
|
||||
url.query.erase(param);
|
||||
|
||||
input.attrs.insert_or_assign("type", std::string{schemeName()});
|
||||
@@ -286,88 +286,23 @@ struct CurlInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
static const std::map<std::string, AttributeInfo> & allowedAttrsImpl()
|
||||
StringSet allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{
|
||||
.type = "String",
|
||||
.required = true,
|
||||
.doc = R"(
|
||||
Supported protocols:
|
||||
|
||||
- `https`
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "https://example.com/index.html";
|
||||
> }
|
||||
> ```
|
||||
|
||||
- `http`
|
||||
|
||||
Insecure HTTP transfer for legacy sources.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> HTTP performs no encryption or authentication.
|
||||
> Use a `narHash` known in advance to ensure the output has expected contents.
|
||||
|
||||
- `file`
|
||||
|
||||
A file on the local file system.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "file:///home/eelco/nix/README.md";
|
||||
> }
|
||||
> ```
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"unpack",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
return {
|
||||
"type",
|
||||
"url",
|
||||
"narHash",
|
||||
"name",
|
||||
"unpack",
|
||||
"rev",
|
||||
"revCount",
|
||||
"lastModified",
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
return allowedAttrsImpl();
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
{
|
||||
Input input{};
|
||||
Input input{settings};
|
||||
input.attrs = attrs;
|
||||
|
||||
// input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
|
||||
@@ -384,7 +319,7 @@ struct CurlInputScheme : InputScheme
|
||||
return url;
|
||||
}
|
||||
|
||||
bool isLocked(const Settings & settings, const Input & input) const override
|
||||
bool isLocked(const Input & input) const override
|
||||
{
|
||||
return (bool) input.getNarHash();
|
||||
}
|
||||
@@ -397,14 +332,6 @@ struct FileInputScheme : CurlInputScheme
|
||||
return "file";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Place a plain file into the Nix store.
|
||||
This is similar to [`builtins.fetchurl`](@docroot@/language/builtins.md#builtins-fetchurl)
|
||||
)");
|
||||
}
|
||||
|
||||
bool isValidURL(const ParsedURL & url, bool requireTree) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
@@ -413,8 +340,7 @@ struct FileInputScheme : CurlInputScheme
|
||||
: (!requireTree && !hasTarballExtension(url)));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
{
|
||||
auto input(_input);
|
||||
|
||||
@@ -422,7 +348,7 @@ struct FileInputScheme : CurlInputScheme
|
||||
the Nix store directly, since there is little deduplication
|
||||
benefit in using the Git cache for single big files like
|
||||
tarballs. */
|
||||
auto file = downloadFile(store, settings, getStrAttr(input.attrs, "url"), input.getName());
|
||||
auto file = downloadFile(store, *input.settings, getStrAttr(input.attrs, "url"), input.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(file.storePath)->narHash;
|
||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
@@ -442,34 +368,6 @@ struct TarballInputScheme : CurlInputScheme
|
||||
return "tarball";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underlying implementation as [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball)
|
||||
)");
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = [] {
|
||||
auto attrs = CurlInputScheme::allowedAttrsImpl();
|
||||
// Override the "url" attribute to add tarball-specific example
|
||||
attrs["url"].doc = R"(
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "tarball";
|
||||
> url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11";
|
||||
> }
|
||||
> ```
|
||||
)";
|
||||
return attrs;
|
||||
}();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
bool isValidURL(const ParsedURL & url, bool requireTree) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
@@ -479,15 +377,15 @@ struct TarballInputScheme : CurlInputScheme
|
||||
: (requireTree || hasTarballExtension(url)));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(ref<Store> store, const Input & _input) const override
|
||||
{
|
||||
auto input(_input);
|
||||
|
||||
auto result = downloadTarball_(settings, getStrAttr(input.attrs, "url"), {}, "«" + input.to_string() + "»");
|
||||
auto result =
|
||||
downloadTarball_(*input.settings, getStrAttr(input.attrs, "url"), {}, "«" + input.to_string() + "»");
|
||||
|
||||
if (result.immutableUrl) {
|
||||
auto immutableInput = Input::fromURL(settings, *result.immutableUrl);
|
||||
auto immutableInput = Input::fromURL(*input.settings, *result.immutableUrl);
|
||||
// FIXME: would be nice to support arbitrary flakerefs
|
||||
// here, e.g. git flakes.
|
||||
if (immutableInput.getType() != "tarball")
|
||||
@@ -500,7 +398,9 @@ struct TarballInputScheme : CurlInputScheme
|
||||
|
||||
input.attrs.insert_or_assign(
|
||||
"narHash",
|
||||
settings.getTarballCache()->treeHashToNarHash(settings, result.treeHash).to_string(HashFormat::SRI, true));
|
||||
input.settings->getTarballCache()
|
||||
->treeHashToNarHash(*input.settings, result.treeHash)
|
||||
.to_string(HashFormat::SRI, true));
|
||||
|
||||
return {result.accessor, input};
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ PrimOp getFlake(const Settings & settings)
|
||||
std::string flakeRefS(
|
||||
state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
||||
auto flakeRef = nix::parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
|
||||
if (state.settings.pureEval && !flakeRef.input.isLocked(state.fetchSettings))
|
||||
if (state.settings.pureEval && !flakeRef.input.isLocked())
|
||||
throw Error(
|
||||
"cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)",
|
||||
flakeRefS,
|
||||
|
||||
@@ -372,8 +372,7 @@ static Flake getFlake(
|
||||
const InputAttrPath & lockRootAttrPath)
|
||||
{
|
||||
// Fetch a lazy tree first.
|
||||
auto cachedInput =
|
||||
state.inputCache->getAccessor(state.fetchSettings, state.store, originalRef.input, useRegistries);
|
||||
auto cachedInput = state.inputCache->getAccessor(state.store, originalRef.input, useRegistries);
|
||||
|
||||
auto subdir = fetchers::maybeGetStrAttr(cachedInput.extraAttrs, "dir").value_or(originalRef.subdir);
|
||||
auto resolvedRef = FlakeRef(std::move(cachedInput.resolvedInput), subdir);
|
||||
@@ -389,8 +388,7 @@ static Flake getFlake(
|
||||
debug("refetching input '%s' due to self attribute", newLockedRef);
|
||||
// FIXME: need to remove attrs that are invalidated by the changed input attrs, such as 'narHash'.
|
||||
newLockedRef.input.attrs.erase("narHash");
|
||||
auto cachedInput2 = state.inputCache->getAccessor(
|
||||
state.fetchSettings, state.store, newLockedRef.input, fetchers::UseRegistries::No);
|
||||
auto cachedInput2 = state.inputCache->getAccessor(state.store, newLockedRef.input, fetchers::UseRegistries::No);
|
||||
cachedInput.accessor = cachedInput2.accessor;
|
||||
lockedRef = FlakeRef(std::move(cachedInput2.lockedInput), newLockedRef.subdir);
|
||||
}
|
||||
@@ -706,8 +704,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
||||
this input. */
|
||||
debug("creating new input '%s'", inputAttrPathS);
|
||||
|
||||
if (!lockFlags.allowUnlocked && !input.ref->input.isLocked(state.fetchSettings)
|
||||
&& !input.ref->input.isRelative())
|
||||
if (!lockFlags.allowUnlocked && !input.ref->input.isLocked() && !input.ref->input.isRelative())
|
||||
throw Error("cannot update unlocked flake input '%s' in pure mode", inputAttrPathS);
|
||||
|
||||
/* Note: in case of an --override-input, we use
|
||||
@@ -756,7 +753,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
||||
return {*resolvedPath, *input.ref};
|
||||
} else {
|
||||
auto cachedInput = state.inputCache->getAccessor(
|
||||
state.fetchSettings, state.store, input.ref->input, useRegistriesInputs);
|
||||
state.store, input.ref->input, useRegistriesInputs);
|
||||
|
||||
auto lockedRef = FlakeRef(std::move(cachedInput.lockedInput), input.ref->subdir);
|
||||
|
||||
|
||||
@@ -64,10 +64,9 @@ std::ostream & operator<<(std::ostream & str, const FlakeRef & flakeRef)
|
||||
return str;
|
||||
}
|
||||
|
||||
FlakeRef FlakeRef::resolve(
|
||||
const fetchers::Settings & fetchSettings, ref<Store> store, fetchers::UseRegistries useRegistries) const
|
||||
FlakeRef FlakeRef::resolve(ref<Store> store, fetchers::UseRegistries useRegistries) const
|
||||
{
|
||||
auto [input2, extraAttrs] = lookupInRegistries(fetchSettings, store, input, useRegistries);
|
||||
auto [input2, extraAttrs] = lookupInRegistries(store, input, useRegistries);
|
||||
return FlakeRef(std::move(input2), fetchers::maybeGetStrAttr(extraAttrs, "dir").value_or(subdir));
|
||||
}
|
||||
|
||||
@@ -288,10 +287,9 @@ FlakeRef FlakeRef::fromAttrs(const fetchers::Settings & fetchSettings, const fet
|
||||
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef>
|
||||
FlakeRef::lazyFetch(const fetchers::Settings & fetchSettings, ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) const
|
||||
{
|
||||
auto [accessor, lockedInput] = input.getAccessor(fetchSettings, store);
|
||||
auto [accessor, lockedInput] = input.getAccessor(store);
|
||||
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
|
||||
}
|
||||
|
||||
|
||||
@@ -71,15 +71,11 @@ struct FlakeRef
|
||||
|
||||
fetchers::Attrs toAttrs() const;
|
||||
|
||||
FlakeRef resolve(
|
||||
const fetchers::Settings & fetchSettings,
|
||||
ref<Store> store,
|
||||
fetchers::UseRegistries useRegistries = fetchers::UseRegistries::All) const;
|
||||
FlakeRef resolve(ref<Store> store, fetchers::UseRegistries useRegistries = fetchers::UseRegistries::All) const;
|
||||
|
||||
static FlakeRef fromAttrs(const fetchers::Settings & fetchSettings, const fetchers::Attrs & attrs);
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef>
|
||||
lazyFetch(const fetchers::Settings & fetchSettings, ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> lazyFetch(ref<Store> store) const;
|
||||
|
||||
/**
|
||||
* Canonicalize a flakeref for the purpose of comparing "old" and
|
||||
|
||||
@@ -74,7 +74,7 @@ LockedNode::LockedNode(const fetchers::Settings & fetchSettings, const nlohmann:
|
||||
, parentInputAttrPath(
|
||||
json.find("parent") != json.end() ? (std::optional<InputAttrPath>) json["parent"] : std::nullopt)
|
||||
{
|
||||
if (!lockedRef.input.isLocked(fetchSettings) && !lockedRef.input.isRelative()) {
|
||||
if (!lockedRef.input.isLocked() && !lockedRef.input.isRelative()) {
|
||||
if (lockedRef.input.getNarHash())
|
||||
warn(
|
||||
"Lock file entry '%s' is unlocked (e.g. lacks a Git revision) but is checked by NAR hash. "
|
||||
@@ -282,7 +282,7 @@ std::optional<FlakeRef> LockFile::isUnlocked(const fetchers::Settings & fetchSet
|
||||
latter case, we can verify the input but we may not be able to
|
||||
fetch it from anywhere. */
|
||||
auto isConsideredLocked = [&](const fetchers::Input & input) {
|
||||
return input.isLocked(fetchSettings) || (fetchSettings.allowDirtyLocks && input.getNarHash());
|
||||
return input.isLocked() || (fetchSettings.allowDirtyLocks && input.getNarHash());
|
||||
};
|
||||
|
||||
for (auto & i : nodes) {
|
||||
|
||||
@@ -40,7 +40,7 @@ static BuildResult::Success::Status successStatusFromString(std::string_view str
|
||||
throw Error("unknown built result success status '%s'", str);
|
||||
}
|
||||
|
||||
static constexpr std::array<std::pair<BuildResult::Failure::Status, std::string_view>, 12> failureStatusStrings{{
|
||||
static constexpr std::array<std::pair<BuildResult::Failure::Status, std::string_view>, 13> failureStatusStrings{{
|
||||
#define ENUM_ENTRY(e) {BuildResult::Failure::e, #e}
|
||||
ENUM_ENTRY(PermanentFailure),
|
||||
ENUM_ENTRY(InputRejected),
|
||||
@@ -54,6 +54,7 @@ static constexpr std::array<std::pair<BuildResult::Failure::Status, std::string_
|
||||
ENUM_ENTRY(NotDeterministic),
|
||||
ENUM_ENTRY(NoSubstituters),
|
||||
ENUM_ENTRY(HashMismatch),
|
||||
ENUM_ENTRY(Cancelled),
|
||||
#undef ENUM_ENTRY
|
||||
}};
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ struct BuildResult
|
||||
/// know about this one, so change it back to `OutputRejected`
|
||||
/// before serialization.
|
||||
HashMismatch = 15,
|
||||
Cancelled = 16,
|
||||
} status = MiscFailure;
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
/**
|
||||
* Build result.
|
||||
*/
|
||||
BuildResult buildResult;
|
||||
BuildResult buildResult = {.inner = BuildResult::Failure{.status = BuildResult::Failure::Cancelled}};
|
||||
|
||||
/**
|
||||
* Suspend our goal and wait until we get `work`-ed again.
|
||||
|
||||
@@ -1385,6 +1385,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||
checkInterrupt();
|
||||
auto name = link.path().filename();
|
||||
printMsg(lvlTalkative, "checking contents of %s", name);
|
||||
PosixSourceAccessor accessor;
|
||||
std::string hash = hashPath(
|
||||
PosixSourceAccessor::createAtRoot(link.path()),
|
||||
FileIngestionMethod::NixArchive,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// TODO: investigate why this is hanging on cygwin
|
||||
#if !defined(_WIN32) && !defined(__CYGWIN__)
|
||||
#ifndef _WIN32
|
||||
|
||||
# include "nix/util/util.hh"
|
||||
# include "nix/util/monitor-fd.hh"
|
||||
|
||||
@@ -103,9 +103,9 @@ void SourceAccessor::dumpPath(const CanonPath & path, Sink & sink, PathFilter &
|
||||
|
||||
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
auto path2 = PosixSourceAccessor::createAtRoot(path, /*trackLastModified=*/true);
|
||||
auto path2 = PosixSourceAccessor::createAtRoot(path);
|
||||
path2.dumpPath(sink, filter);
|
||||
return path2.accessor->getLastModified().value();
|
||||
return path2.accessor.dynamic_pointer_cast<PosixSourceAccessor>()->mtime;
|
||||
}
|
||||
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <ranges>
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
|
||||
@@ -123,70 +122,33 @@ public:
|
||||
return &cs[1];
|
||||
}
|
||||
|
||||
class Iterator
|
||||
struct Iterator
|
||||
{
|
||||
/**
|
||||
* Helper class with overloaded operator-> for "drill-down" behavior.
|
||||
* This was a "temporary" string_view doesn't have to be stored anywhere.
|
||||
*/
|
||||
class PointerProxy
|
||||
{
|
||||
std::string_view segment;
|
||||
|
||||
public:
|
||||
PointerProxy(std::string_view segment_)
|
||||
: segment(segment_)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string_view * operator->() const
|
||||
{
|
||||
return &segment;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
using value_type = std::string_view;
|
||||
using reference_type = const std::string_view;
|
||||
using pointer_type = PointerProxy;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
std::string_view remaining;
|
||||
size_t slash;
|
||||
|
||||
/**
|
||||
* Dummy default constructor required for forward iterators. Doesn't return
|
||||
* a usable iterator.
|
||||
*/
|
||||
Iterator()
|
||||
: remaining()
|
||||
, slash(0)
|
||||
{
|
||||
}
|
||||
|
||||
Iterator(std::string_view remaining)
|
||||
: remaining(remaining)
|
||||
, slash(remaining.find('/'))
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const Iterator & x) const
|
||||
bool operator!=(const Iterator & x) const
|
||||
{
|
||||
return remaining.data() == x.remaining.data();
|
||||
return remaining.data() != x.remaining.data();
|
||||
}
|
||||
|
||||
reference_type operator*() const
|
||||
bool operator==(const Iterator & x) const
|
||||
{
|
||||
return !(*this != x);
|
||||
}
|
||||
|
||||
const std::string_view operator*() const
|
||||
{
|
||||
return remaining.substr(0, slash);
|
||||
}
|
||||
|
||||
pointer_type operator->() const
|
||||
{
|
||||
return PointerProxy(**this);
|
||||
}
|
||||
|
||||
Iterator & operator++()
|
||||
void operator++()
|
||||
{
|
||||
if (slash == remaining.npos)
|
||||
remaining = remaining.substr(remaining.size());
|
||||
@@ -194,19 +156,9 @@ public:
|
||||
remaining = remaining.substr(slash + 1);
|
||||
slash = remaining.find('/');
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator operator++(int)
|
||||
{
|
||||
auto tmp = *this;
|
||||
++*this;
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(std::forward_iterator<Iterator>);
|
||||
|
||||
Iterator begin() const
|
||||
{
|
||||
return Iterator(rel());
|
||||
@@ -313,8 +265,6 @@ public:
|
||||
friend std::size_t hash_value(const CanonPath &);
|
||||
};
|
||||
|
||||
static_assert(std::ranges::forward_range<CanonPath>);
|
||||
|
||||
std::ostream & operator<<(std::ostream & stream, const CanonPath & path);
|
||||
|
||||
inline std::size_t hash_value(const CanonPath & path)
|
||||
|
||||
@@ -9,7 +9,7 @@ struct SourcePath;
|
||||
/**
|
||||
* A source accessor that uses the Unix filesystem.
|
||||
*/
|
||||
class PosixSourceAccessor : virtual public SourceAccessor
|
||||
struct PosixSourceAccessor : virtual SourceAccessor
|
||||
{
|
||||
/**
|
||||
* Optional root path to prefix all operations into the native file
|
||||
@@ -18,12 +18,8 @@ class PosixSourceAccessor : virtual public SourceAccessor
|
||||
*/
|
||||
const std::filesystem::path root;
|
||||
|
||||
const bool trackLastModified = false;
|
||||
|
||||
public:
|
||||
|
||||
PosixSourceAccessor();
|
||||
PosixSourceAccessor(std::filesystem::path && root, bool trackLastModified = false);
|
||||
PosixSourceAccessor(std::filesystem::path && root);
|
||||
|
||||
/**
|
||||
* The most recent mtime seen by lstat(). This is a hack to
|
||||
@@ -47,9 +43,6 @@ public:
|
||||
* Create a `PosixSourceAccessor` and `SourcePath` corresponding to
|
||||
* some native path.
|
||||
*
|
||||
* @param Whether the accessor should return a non-null getLastModified.
|
||||
* When true the accessor must be used only by a single thread.
|
||||
*
|
||||
* The `PosixSourceAccessor` is rooted as far up the tree as
|
||||
* possible, (e.g. on Windows it could scoped to a drive like
|
||||
* `C:\`). This allows more `..` parent accessing to work.
|
||||
@@ -71,12 +64,7 @@ public:
|
||||
* and
|
||||
* [`std::filesystem::path::relative_path`](https://en.cppreference.com/w/cpp/filesystem/path/relative_path).
|
||||
*/
|
||||
static SourcePath createAtRoot(const std::filesystem::path & path, bool trackLastModified = false);
|
||||
|
||||
std::optional<std::time_t> getLastModified() override
|
||||
{
|
||||
return trackLastModified ? std::optional{mtime} : std::nullopt;
|
||||
}
|
||||
static SourcePath createAtRoot(const std::filesystem::path & path);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -114,69 +114,4 @@ std::string dropEmptyInitThenConcatStringsSep(const std::string_view sep, const
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consteval helper for the "constexpr 2-step" pattern.
|
||||
* Converts a std::string to a std::array<char, N> for stable compile-time storage.
|
||||
*
|
||||
* Usage:
|
||||
* static constexpr auto str = stripIndentationConsteval(...);
|
||||
* static constexpr auto arr = toArray<str.size()>(str);
|
||||
* static constexpr std::string_view view{arr.data(), arr.size()};
|
||||
*/
|
||||
template<size_t N>
|
||||
consteval std::string_view toArray(const std::string & s)
|
||||
{
|
||||
static std::array<char, N> arr{};
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
arr[i] = s[i];
|
||||
}
|
||||
return {arr.data(), N};
|
||||
}
|
||||
|
||||
constexpr std::string stripIndentationImpl(std::string_view s)
|
||||
{
|
||||
size_t minIndent = 10000;
|
||||
size_t curIndent = 0;
|
||||
bool atStartOfLine = true;
|
||||
|
||||
for (auto & c : s) {
|
||||
if (atStartOfLine && c == ' ')
|
||||
curIndent++;
|
||||
else if (c == '\n') {
|
||||
if (atStartOfLine)
|
||||
minIndent = std::max(minIndent, curIndent);
|
||||
curIndent = 0;
|
||||
atStartOfLine = true;
|
||||
} else {
|
||||
if (atStartOfLine) {
|
||||
minIndent = std::min(minIndent, curIndent);
|
||||
atStartOfLine = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string res;
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < s.size()) {
|
||||
auto eol = s.find('\n', pos);
|
||||
if (eol == s.npos)
|
||||
eol = s.size();
|
||||
if (eol - pos > minIndent)
|
||||
res.append(s.substr(pos + minIndent, eol - pos - minIndent));
|
||||
res.push_back('\n');
|
||||
pos = eol + 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<std::string_view s>
|
||||
consteval std::string_view stripIndentationConsteval()
|
||||
{
|
||||
constexpr auto stripped = stripIndentationImpl(s);
|
||||
constexpr size_t n = stripped.size();
|
||||
return toArray<n>(stripped);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -132,13 +132,6 @@ std::string optionalBracket(std::string_view prefix, const std::optional<T> & co
|
||||
return optionalBracket(prefix, std::string_view(*content), suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove common leading whitespace from the lines in the string
|
||||
* 's'. For example, if every line is indented by at least 3 spaces,
|
||||
* then we remove 3 spaces from the start of every line.
|
||||
*/
|
||||
std::string stripIndentation(std::string_view s);
|
||||
|
||||
/**
|
||||
* Hash implementation that can be used for zero-copy heterogenous lookup from
|
||||
* P1690R1[1] in unordered containers.
|
||||
|
||||
@@ -33,28 +33,15 @@ auto concatStrings(Parts &&... parts)
|
||||
return concatStringsSep({}, views);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add quotes around a string.
|
||||
*/
|
||||
inline std::string quoteString(std::string_view s, char quote = '\'')
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(s.size() + 2);
|
||||
result += quote;
|
||||
result += s;
|
||||
result += quote;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add quotes around a collection of strings.
|
||||
*/
|
||||
template<class C>
|
||||
Strings quoteStrings(const C & c, char quote = '\'')
|
||||
Strings quoteStrings(const C & c)
|
||||
{
|
||||
Strings res;
|
||||
for (auto & s : c)
|
||||
res.push_back(quoteString(s, quote));
|
||||
res.push_back("'" + s + "'");
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -228,6 +215,13 @@ constexpr char treeLast[] = "└───";
|
||||
constexpr char treeLine[] = "│ ";
|
||||
constexpr char treeNull[] = " ";
|
||||
|
||||
/**
|
||||
* Remove common leading whitespace from the lines in the string
|
||||
* 's'. For example, if every line is indented by at least 3 spaces,
|
||||
* then we remove 3 spaces from the start of every line.
|
||||
*/
|
||||
std::string stripIndentation(std::string_view s);
|
||||
|
||||
/**
|
||||
* Get the prefix of 's' up to and excluding the next line break (LF
|
||||
* optionally preceded by CR), and the remainder following the line
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && argRoot, bool trackLastModified)
|
||||
PosixSourceAccessor::PosixSourceAccessor(std::filesystem::path && argRoot)
|
||||
: root(std::move(argRoot))
|
||||
, trackLastModified(trackLastModified)
|
||||
{
|
||||
assert(root.empty() || root.is_absolute());
|
||||
displayPrefix = root.string();
|
||||
@@ -20,11 +19,11 @@ PosixSourceAccessor::PosixSourceAccessor()
|
||||
{
|
||||
}
|
||||
|
||||
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path, bool trackLastModified)
|
||||
SourcePath PosixSourceAccessor::createAtRoot(const std::filesystem::path & path)
|
||||
{
|
||||
std::filesystem::path path2 = absPath(path);
|
||||
return {
|
||||
make_ref<PosixSourceAccessor>(path2.root_path(), trackLastModified),
|
||||
make_ref<PosixSourceAccessor>(path2.root_path()),
|
||||
CanonPath{path2.relative_path().string()},
|
||||
};
|
||||
}
|
||||
@@ -115,12 +114,9 @@ std::optional<SourceAccessor::Stat> PosixSourceAccessor::maybeLstat(const CanonP
|
||||
auto st = cachedLstat(path);
|
||||
if (!st)
|
||||
return std::nullopt;
|
||||
|
||||
/* The contract is that trackLastModified implies that the caller uses the accessor
|
||||
from a single thread. Thus this is not a CAS loop. */
|
||||
if (trackLastModified)
|
||||
mtime = std::max(mtime, st->st_mtime);
|
||||
|
||||
// This makes the accessor thread-unsafe, but we only seem to use the actual value in a single threaded context in
|
||||
// `src/libfetchers/path.cc`.
|
||||
mtime = std::max(mtime, st->st_mtime);
|
||||
return Stat{
|
||||
.type = S_ISREG(st->st_mode) ? tRegular
|
||||
: S_ISDIR(st->st_mode) ? tDirectory
|
||||
|
||||
@@ -152,8 +152,4 @@ std::string optionalBracket(std::string_view prefix, std::string_view content, s
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string stripIndentation(std::string_view s) {
|
||||
return stripIndentationImpl(s);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -250,6 +250,44 @@ void ignoreExceptionExceptInterrupt(Verbosity lvl)
|
||||
}
|
||||
}
|
||||
|
||||
std::string stripIndentation(std::string_view s)
|
||||
{
|
||||
size_t minIndent = 10000;
|
||||
size_t curIndent = 0;
|
||||
bool atStartOfLine = true;
|
||||
|
||||
for (auto & c : s) {
|
||||
if (atStartOfLine && c == ' ')
|
||||
curIndent++;
|
||||
else if (c == '\n') {
|
||||
if (atStartOfLine)
|
||||
minIndent = std::max(minIndent, curIndent);
|
||||
curIndent = 0;
|
||||
atStartOfLine = true;
|
||||
} else {
|
||||
if (atStartOfLine) {
|
||||
minIndent = std::min(minIndent, curIndent);
|
||||
atStartOfLine = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string res;
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < s.size()) {
|
||||
auto eol = s.find('\n', pos);
|
||||
if (eol == s.npos)
|
||||
eol = s.size();
|
||||
if (eol - pos > minIndent)
|
||||
res.append(s.substr(pos + minIndent, eol - pos - minIndent));
|
||||
res.push_back('\n');
|
||||
pos = eol + 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::pair<std::string_view, std::string_view> getLine(std::string_view s)
|
||||
{
|
||||
auto newline = s.find('\n');
|
||||
|
||||
@@ -45,7 +45,7 @@ struct CmdFlakePrefetchInputs : FlakeCommand
|
||||
if (auto lockedNode = dynamic_cast<const LockedNode *>(&node)) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown, fmt("fetching '%s'", lockedNode->lockedRef));
|
||||
auto accessor = lockedNode->lockedRef.input.getAccessor(fetchSettings, store).first;
|
||||
auto accessor = lockedNode->lockedRef.input.getAccessor(store).first;
|
||||
fetchToStore(
|
||||
fetchSettings, *store, accessor, FetchMode::Copy, lockedNode->lockedRef.input.getName());
|
||||
} catch (Error & e) {
|
||||
|
||||
@@ -245,7 +245,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
||||
printJSON(j);
|
||||
} else {
|
||||
logger->cout(ANSI_BOLD "Resolved URL:" ANSI_NORMAL " %s", flake.resolvedRef.to_string());
|
||||
if (flake.lockedRef.input.isLocked(fetchSettings))
|
||||
if (flake.lockedRef.input.isLocked())
|
||||
logger->cout(ANSI_BOLD "Locked URL:" ANSI_NORMAL " %s", flake.lockedRef.to_string());
|
||||
if (flake.description)
|
||||
logger->cout(ANSI_BOLD "Description:" ANSI_NORMAL " %s", *flake.description);
|
||||
@@ -1049,7 +1049,7 @@ struct CmdFlakeClone : FlakeCommand
|
||||
if (destDir.empty())
|
||||
throw Error("missing flag '--dest'");
|
||||
|
||||
getFlakeRef().resolve(fetchSettings, store).input.clone(fetchSettings, destDir);
|
||||
getFlakeRef().resolve(store).input.clone(destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1100,7 +1100,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun, MixNoCheckSigs
|
||||
std::optional<StorePath> storePath;
|
||||
if (!(*inputNode)->lockedRef.input.isRelative()) {
|
||||
storePath = dryRun ? (*inputNode)->lockedRef.input.computeStorePath(*store)
|
||||
: (*inputNode)->lockedRef.input.fetchToStore(fetchSettings, store).first;
|
||||
: (*inputNode)->lockedRef.input.fetchToStore(store).first;
|
||||
sources.insert(*storePath);
|
||||
}
|
||||
if (json) {
|
||||
@@ -1324,10 +1324,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
||||
try {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else {
|
||||
auto name = visitor.getAttrPathStr(state->s.name);
|
||||
logger->warn(fmt("%s is not a derivation", name));
|
||||
}
|
||||
else
|
||||
throw Error("expected a derivation");
|
||||
} catch (IFDError & e) {
|
||||
if (!json) {
|
||||
logger->cout(
|
||||
@@ -1498,8 +1496,8 @@ struct CmdFlakePrefetch : FlakeCommand, MixJSON
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
auto originalRef = getFlakeRef();
|
||||
auto resolvedRef = originalRef.resolve(fetchSettings, store);
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(getEvalState()->fetchSettings, store);
|
||||
auto resolvedRef = originalRef.resolve(store);
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(store);
|
||||
auto storePath =
|
||||
fetchToStore(getEvalState()->fetchSettings, *store, accessor, FetchMode::Copy, lockedRef.input.getName());
|
||||
auto hash = store->queryPathInfo(storePath)->narHash;
|
||||
|
||||
@@ -193,38 +193,20 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
|
||||
|
||||
std::string dumpCli()
|
||||
{
|
||||
using nlohmann::json;
|
||||
|
||||
auto res = json::object();
|
||||
auto res = nlohmann::json::object();
|
||||
|
||||
res["args"] = toJSON();
|
||||
|
||||
{
|
||||
auto & stores = res["stores"] = json::object();
|
||||
for (auto & [storeName, implem] : Implementations::registered()) {
|
||||
auto & j = stores[storeName];
|
||||
j["doc"] = implem.doc;
|
||||
j["uri-schemes"] = implem.uriSchemes;
|
||||
j["settings"] = implem.getConfig()->toJSON();
|
||||
j["experimentalFeature"] = implem.experimentalFeature;
|
||||
}
|
||||
auto stores = nlohmann::json::object();
|
||||
for (auto & [storeName, implem] : Implementations::registered()) {
|
||||
auto & j = stores[storeName];
|
||||
j["doc"] = implem.doc;
|
||||
j["uri-schemes"] = implem.uriSchemes;
|
||||
j["settings"] = implem.getConfig()->toJSON();
|
||||
j["experimentalFeature"] = implem.experimentalFeature;
|
||||
}
|
||||
|
||||
{
|
||||
auto & fetchers = res["fetchers"] = json::object();
|
||||
|
||||
for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) {
|
||||
auto & s = fetchers[schemeName] = json::object();
|
||||
s["description"] = scheme->schemeDescription();
|
||||
auto & attrs = s["allowedAttrs"] = json::object();
|
||||
for (auto & [fieldName, field] : scheme->allowedAttrs()) {
|
||||
auto & f = attrs[fieldName] = json::object();
|
||||
f["type"] = field.type;
|
||||
f["required"] = field.required;
|
||||
f["doc"] = stripIndentation(field.doc);
|
||||
}
|
||||
}
|
||||
};
|
||||
res["stores"] = std::move(stores);
|
||||
res["fetchers"] = fetchers::dumpRegisterInputSchemeInfo();
|
||||
|
||||
return res.dump();
|
||||
}
|
||||
@@ -458,7 +440,7 @@ void mainWrapped(int argc, char ** argv)
|
||||
if (!primOp->doc)
|
||||
continue;
|
||||
b["args"] = primOp->args;
|
||||
b["doc"] = trim(stripIndentation(*primOp->doc));
|
||||
b["doc"] = trim(stripIndentation(primOp->doc));
|
||||
if (primOp->experimentalFeature)
|
||||
b["experimental-feature"] = primOp->experimentalFeature;
|
||||
builtinsJson.emplace(state.symbols[builtin.name], std::move(b));
|
||||
|
||||
@@ -711,7 +711,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||
element.identifier());
|
||||
continue;
|
||||
}
|
||||
if (element.source->originalRef.input.isLocked(getEvalState()->fetchSettings)) {
|
||||
if (element.source->originalRef.input.isLocked()) {
|
||||
warn(
|
||||
"Found package '%s', but it was added from a locked flake reference so it can't be upgraded!",
|
||||
element.identifier());
|
||||
@@ -740,8 +740,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||
assert(infop);
|
||||
auto & info = *infop;
|
||||
|
||||
if (info.flake.lockedRef.input.isLocked(getEvalState()->fetchSettings)
|
||||
&& element.source->lockedRef == info.flake.lockedRef)
|
||||
if (info.flake.lockedRef.input.isLocked() && element.source->lockedRef == info.flake.lockedRef)
|
||||
continue;
|
||||
|
||||
printInfo(
|
||||
|
||||
@@ -190,9 +190,8 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand
|
||||
auto ref = parseFlakeRef(fetchSettings, url);
|
||||
auto lockedRef = parseFlakeRef(fetchSettings, locked);
|
||||
registry->remove(ref.input);
|
||||
auto resolvedInput = lockedRef.resolve(fetchSettings, store).input;
|
||||
auto resolved = resolvedInput.getAccessor(fetchSettings, store).second;
|
||||
if (!resolved.isLocked(fetchSettings))
|
||||
auto resolved = lockedRef.resolve(store).input.getAccessor(store).second;
|
||||
if (!resolved.isLocked())
|
||||
warn("flake '%s' is not locked", resolved.to_string());
|
||||
fetchers::Attrs extraAttrs;
|
||||
if (ref.subdir != "")
|
||||
|
||||
@@ -107,26 +107,3 @@ in
|
||||
assert show_output.packages.${builtins.currentSystem}.default == { };
|
||||
true
|
||||
'
|
||||
|
||||
|
||||
# Test that nix keeps going even when packages.$SYSTEM contains not derivations
|
||||
cat >flake.nix <<EOF
|
||||
{
|
||||
outputs = inputs: {
|
||||
packages.$system = {
|
||||
drv1 = import ./simple.nix;
|
||||
not-a-derivation = 42;
|
||||
drv2 = import ./simple.nix;
|
||||
};
|
||||
};
|
||||
}
|
||||
EOF
|
||||
nix flake show --json --all-systems > show-output.json
|
||||
# shellcheck disable=SC2016
|
||||
nix eval --impure --expr '
|
||||
let show_output = builtins.fromJSON (builtins.readFile ./show-output.json);
|
||||
in
|
||||
assert show_output.packages.${builtins.currentSystem}.not-a-derivation == {};
|
||||
true
|
||||
'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user