mirror of
https://github.com/NixOS/nix
synced 2025-06-28 01:11:15 +02:00
Merge remote-tracking branch 'origin/master' into detsys-main
This commit is contained in:
commit
042c2ae3ac
222 changed files with 3295 additions and 1254 deletions
|
@ -116,9 +116,12 @@ let
|
||||||
storeInfo = commandInfo.stores;
|
storeInfo = commandInfo.stores;
|
||||||
inherit inlineHTML;
|
inherit inlineHTML;
|
||||||
};
|
};
|
||||||
|
hasInfix = infix: content:
|
||||||
|
builtins.stringLength content != builtins.stringLength (replaceStrings [ infix ] [ "" ] content);
|
||||||
in
|
in
|
||||||
optionalString (details ? doc) (
|
optionalString (details ? doc) (
|
||||||
if match ".*@store-types@.*" details.doc != null
|
# An alternate implementation with builtins.match stack overflowed on some systems.
|
||||||
|
if hasInfix "@store-types@" details.doc
|
||||||
then help-stores
|
then help-stores
|
||||||
else details.doc
|
else details.doc
|
||||||
);
|
);
|
||||||
|
|
53
doc/manual/rl-next/repl-doc-renders-doc-comments.md
Normal file
53
doc/manual/rl-next/repl-doc-renders-doc-comments.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
synopsis: "`nix-repl`'s `:doc` shows documentation comments"
|
||||||
|
significance: significant
|
||||||
|
issues:
|
||||||
|
- 3904
|
||||||
|
- 10771
|
||||||
|
prs:
|
||||||
|
- 1652
|
||||||
|
- 9054
|
||||||
|
- 11072
|
||||||
|
---
|
||||||
|
|
||||||
|
`nix repl` has a `:doc` command that previously only rendered documentation for internally defined functions.
|
||||||
|
This feature has been extended to also render function documentation comments, in accordance with [RFC 145].
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-repl> :doc lib.toFunction
|
||||||
|
Function toFunction
|
||||||
|
… defined at /home/user/h/nixpkgs/lib/trivial.nix:1072:5
|
||||||
|
|
||||||
|
Turns any non-callable values into constant functions. Returns
|
||||||
|
callable values as is.
|
||||||
|
|
||||||
|
Inputs
|
||||||
|
|
||||||
|
v
|
||||||
|
|
||||||
|
: Any value
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
:::{.example}
|
||||||
|
|
||||||
|
## lib.trivial.toFunction usage example
|
||||||
|
|
||||||
|
| nix-repl> lib.toFunction 1 2
|
||||||
|
| 1
|
||||||
|
|
|
||||||
|
| nix-repl> lib.toFunction (x: x + 1) 2
|
||||||
|
| 3
|
||||||
|
|
||||||
|
:::
|
||||||
|
```
|
||||||
|
|
||||||
|
Known limitations:
|
||||||
|
- It does not render documentation for "formals", such as `{ /** the value to return */ x, ... }: x`.
|
||||||
|
- Some extensions to markdown are not yet supported, as you can see in the example above.
|
||||||
|
|
||||||
|
We'd like to acknowledge Yingchi Long for proposing a proof of concept for this functionality in [#9054](https://github.com/NixOS/nix/pull/9054), as well as @sternenseemann and Johannes Kirschbauer for their contributions, proposals, and their work on [RFC 145].
|
||||||
|
|
||||||
|
[RFC 145]: https://github.com/NixOS/rfcs/pull/145
|
|
@ -91,7 +91,7 @@ A environment variables that Google Test accepts are also worth knowing:
|
||||||
Putting the two together, one might run
|
Putting the two together, one might run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GTEST_BREIF=1 GTEST_FILTER='ErrorTraceTest.*' meson test nix-expr-tests -v
|
GTEST_BRIEF=1 GTEST_FILTER='ErrorTraceTest.*' meson test nix-expr-tests -v
|
||||||
```
|
```
|
||||||
|
|
||||||
for short but comprensive output.
|
for short but comprensive output.
|
||||||
|
|
|
@ -143,6 +143,7 @@
|
||||||
''^src/libstore/common-protocol-impl\.hh$''
|
''^src/libstore/common-protocol-impl\.hh$''
|
||||||
''^src/libstore/common-protocol\.cc$''
|
''^src/libstore/common-protocol\.cc$''
|
||||||
''^src/libstore/common-protocol\.hh$''
|
''^src/libstore/common-protocol\.hh$''
|
||||||
|
''^src/libstore/common-ssh-store-config\.hh$''
|
||||||
''^src/libstore/content-address\.cc$''
|
''^src/libstore/content-address\.cc$''
|
||||||
''^src/libstore/content-address\.hh$''
|
''^src/libstore/content-address\.hh$''
|
||||||
''^src/libstore/daemon\.cc$''
|
''^src/libstore/daemon\.cc$''
|
||||||
|
@ -215,7 +216,6 @@
|
||||||
''^src/libstore/serve-protocol\.hh$''
|
''^src/libstore/serve-protocol\.hh$''
|
||||||
''^src/libstore/sqlite\.cc$''
|
''^src/libstore/sqlite\.cc$''
|
||||||
''^src/libstore/sqlite\.hh$''
|
''^src/libstore/sqlite\.hh$''
|
||||||
''^src/libstore/ssh-store-config\.hh$''
|
|
||||||
''^src/libstore/ssh-store\.cc$''
|
''^src/libstore/ssh-store\.cc$''
|
||||||
''^src/libstore/ssh\.cc$''
|
''^src/libstore/ssh\.cc$''
|
||||||
''^src/libstore/ssh\.hh$''
|
''^src/libstore/ssh\.hh$''
|
||||||
|
@ -499,7 +499,6 @@
|
||||||
''^misc/bash/completion\.sh$''
|
''^misc/bash/completion\.sh$''
|
||||||
''^misc/fish/completion\.fish$''
|
''^misc/fish/completion\.fish$''
|
||||||
''^misc/zsh/completion\.zsh$''
|
''^misc/zsh/completion\.zsh$''
|
||||||
''^scripts/check-hydra-status\.sh$''
|
|
||||||
''^scripts/create-darwin-volume\.sh$''
|
''^scripts/create-darwin-volume\.sh$''
|
||||||
''^scripts/install-darwin-multi-user\.sh$''
|
''^scripts/install-darwin-multi-user\.sh$''
|
||||||
''^scripts/install-multi-user\.sh$''
|
''^scripts/install-multi-user\.sh$''
|
||||||
|
|
|
@ -11,11 +11,28 @@
|
||||||
versionSuffix,
|
versionSuffix,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
prevStdenv = stdenv;
|
||||||
|
in
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
root = ../.;
|
root = ../.;
|
||||||
|
|
||||||
|
stdenv = if prevStdenv.isDarwin && prevStdenv.isx86_64
|
||||||
|
then darwinStdenv
|
||||||
|
else prevStdenv;
|
||||||
|
|
||||||
|
# Fix the following error with the default x86_64-darwin SDK:
|
||||||
|
#
|
||||||
|
# error: aligned allocation function of type 'void *(std::size_t, std::align_val_t)' is only available on macOS 10.13 or newer
|
||||||
|
#
|
||||||
|
# Despite the use of the 10.13 deployment target here, the aligned
|
||||||
|
# allocation function Clang uses with this setting actually works
|
||||||
|
# all the way back to 10.6.
|
||||||
|
darwinStdenv = pkgs.overrideSDK prevStdenv { darwinMinVersion = "10.13"; };
|
||||||
|
|
||||||
# Nixpkgs implements this by returning a subpath into the fetched Nix sources.
|
# Nixpkgs implements this by returning a subpath into the fetched Nix sources.
|
||||||
resolvePath = p: p;
|
resolvePath = p: p;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "serialise.hh"
|
#include "serialise.hh"
|
||||||
#include "build-result.hh"
|
#include "build-result.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "strings.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
#include "legacy.hh"
|
#include "legacy.hh"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "built-path.hh"
|
#include "built-path.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "comparator.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
@ -8,30 +9,24 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
#define CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, COMPARATOR) \
|
// Custom implementation to avoid `ref` ptr equality
|
||||||
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
|
GENERATE_CMP_EXT(
|
||||||
{ \
|
,
|
||||||
const MY_TYPE* me = this; \
|
std::strong_ordering,
|
||||||
auto fields1 = std::tie(*me->drvPath, me->FIELD); \
|
SingleBuiltPathBuilt,
|
||||||
me = &other; \
|
*me->drvPath,
|
||||||
auto fields2 = std::tie(*me->drvPath, me->FIELD); \
|
me->output);
|
||||||
return fields1 COMPARATOR fields2; \
|
|
||||||
}
|
|
||||||
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
|
|
||||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, ==) \
|
|
||||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
|
|
||||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
|
|
||||||
|
|
||||||
#define FIELD_TYPE std::pair<std::string, StorePath>
|
// Custom implementation to avoid `ref` ptr equality
|
||||||
CMP(SingleBuiltPath, SingleBuiltPathBuilt, output)
|
|
||||||
#undef FIELD_TYPE
|
|
||||||
|
|
||||||
#define FIELD_TYPE std::map<std::string, StorePath>
|
// TODO no `GENERATE_CMP_EXT` because no `std::set::operator<=>` on
|
||||||
CMP(SingleBuiltPath, BuiltPathBuilt, outputs)
|
// Darwin, per header.
|
||||||
#undef FIELD_TYPE
|
GENERATE_EQUAL(
|
||||||
|
,
|
||||||
#undef CMP
|
BuiltPathBuilt ::,
|
||||||
#undef CMP_ONE
|
BuiltPathBuilt,
|
||||||
|
*me->drvPath,
|
||||||
|
me->outputs);
|
||||||
|
|
||||||
StorePath SingleBuiltPath::outPath() const
|
StorePath SingleBuiltPath::outPath() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,8 @@ struct SingleBuiltPathBuilt {
|
||||||
static SingleBuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
|
static SingleBuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
|
||||||
nlohmann::json toJSON(const StoreDirConfig & store) const;
|
nlohmann::json toJSON(const StoreDirConfig & store) const;
|
||||||
|
|
||||||
DECLARE_CMP(SingleBuiltPathBuilt);
|
bool operator ==(const SingleBuiltPathBuilt &) const noexcept;
|
||||||
|
std::strong_ordering operator <=>(const SingleBuiltPathBuilt &) const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
using _SingleBuiltPathRaw = std::variant<
|
using _SingleBuiltPathRaw = std::variant<
|
||||||
|
@ -33,6 +34,9 @@ struct SingleBuiltPath : _SingleBuiltPathRaw {
|
||||||
using Opaque = DerivedPathOpaque;
|
using Opaque = DerivedPathOpaque;
|
||||||
using Built = SingleBuiltPathBuilt;
|
using Built = SingleBuiltPathBuilt;
|
||||||
|
|
||||||
|
bool operator == (const SingleBuiltPath &) const = default;
|
||||||
|
auto operator <=> (const SingleBuiltPath &) const = default;
|
||||||
|
|
||||||
inline const Raw & raw() const {
|
inline const Raw & raw() const {
|
||||||
return static_cast<const Raw &>(*this);
|
return static_cast<const Raw &>(*this);
|
||||||
}
|
}
|
||||||
|
@ -59,11 +63,13 @@ struct BuiltPathBuilt {
|
||||||
ref<SingleBuiltPath> drvPath;
|
ref<SingleBuiltPath> drvPath;
|
||||||
std::map<std::string, StorePath> outputs;
|
std::map<std::string, StorePath> outputs;
|
||||||
|
|
||||||
|
bool operator == (const BuiltPathBuilt &) const noexcept;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
//std::strong_ordering operator <=> (const BuiltPathBuilt &) const noexcept;
|
||||||
|
|
||||||
std::string to_string(const StoreDirConfig & store) const;
|
std::string to_string(const StoreDirConfig & store) const;
|
||||||
static BuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
|
static BuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
|
||||||
nlohmann::json toJSON(const StoreDirConfig & store) const;
|
nlohmann::json toJSON(const StoreDirConfig & store) const;
|
||||||
|
|
||||||
DECLARE_CMP(BuiltPathBuilt);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using _BuiltPathRaw = std::variant<
|
using _BuiltPathRaw = std::variant<
|
||||||
|
@ -82,6 +88,10 @@ struct BuiltPath : _BuiltPathRaw {
|
||||||
using Opaque = DerivedPathOpaque;
|
using Opaque = DerivedPathOpaque;
|
||||||
using Built = BuiltPathBuilt;
|
using Built = BuiltPathBuilt;
|
||||||
|
|
||||||
|
bool operator == (const BuiltPath &) const = default;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
//auto operator <=> (const BuiltPath &) const = default;
|
||||||
|
|
||||||
inline const Raw & raw() const {
|
inline const Raw & raw() const {
|
||||||
return static_cast<const Raw &>(*this);
|
return static_cast<const Raw &>(*this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
#include "markdown.hh"
|
#include "markdown.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
@ -6,8 +8,7 @@
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
#include "profiles.hh"
|
#include "profiles.hh"
|
||||||
#include "repl.hh"
|
#include "repl.hh"
|
||||||
|
#include "strings.hh"
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
extern char * * environ __attribute__((weak));
|
extern char * * environ __attribute__((weak));
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ ref<EvalState> EvalCommand::getEvalState()
|
||||||
#else
|
#else
|
||||||
std::make_shared<EvalState>(
|
std::make_shared<EvalState>(
|
||||||
#endif
|
#endif
|
||||||
lookupPath, getEvalStore(), evalSettings, getStore())
|
lookupPath, getEvalStore(), fetchSettings, evalSettings, getStore())
|
||||||
;
|
;
|
||||||
|
|
||||||
evalState->repair = repair;
|
evalState->repair = repair;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "fetch-settings.hh"
|
||||||
#include "eval-settings.hh"
|
#include "eval-settings.hh"
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
|
@ -7,6 +8,7 @@
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "registry.hh"
|
#include "registry.hh"
|
||||||
#include "flake/flakeref.hh"
|
#include "flake/flakeref.hh"
|
||||||
|
#include "flake/settings.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
#include "tarball.hh"
|
#include "tarball.hh"
|
||||||
|
@ -16,6 +18,10 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
fetchers::Settings fetchSettings;
|
||||||
|
|
||||||
|
static GlobalConfig::Register rFetchSettings(&fetchSettings);
|
||||||
|
|
||||||
EvalSettings evalSettings {
|
EvalSettings evalSettings {
|
||||||
settings.readOnlyMode,
|
settings.readOnlyMode,
|
||||||
{
|
{
|
||||||
|
@ -23,7 +29,7 @@ EvalSettings evalSettings {
|
||||||
"flake",
|
"flake",
|
||||||
[](ref<Store> store, std::string_view rest) {
|
[](ref<Store> store, std::string_view rest) {
|
||||||
// FIXME `parseFlakeRef` should take a `std::string_view`.
|
// FIXME `parseFlakeRef` should take a `std::string_view`.
|
||||||
auto flakeRef = parseFlakeRef(std::string { rest }, {}, true, false);
|
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
|
||||||
debug("fetching flake search path element '%s''", rest);
|
debug("fetching flake search path element '%s''", rest);
|
||||||
auto storePath = flakeRef.resolve(store).fetchTree(store).first;
|
auto storePath = flakeRef.resolve(store).fetchTree(store).first;
|
||||||
return store->toRealPath(storePath);
|
return store->toRealPath(storePath);
|
||||||
|
@ -34,6 +40,12 @@ EvalSettings evalSettings {
|
||||||
|
|
||||||
static GlobalConfig::Register rEvalSettings(&evalSettings);
|
static GlobalConfig::Register rEvalSettings(&evalSettings);
|
||||||
|
|
||||||
|
|
||||||
|
flake::Settings flakeSettings;
|
||||||
|
|
||||||
|
static GlobalConfig::Register rFlakeSettings(&flakeSettings);
|
||||||
|
|
||||||
|
|
||||||
CompatibilitySettings compatibilitySettings {};
|
CompatibilitySettings compatibilitySettings {};
|
||||||
|
|
||||||
static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings);
|
static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings);
|
||||||
|
@ -170,8 +182,8 @@ MixEvalArgs::MixEvalArgs()
|
||||||
.category = category,
|
.category = category,
|
||||||
.labels = {"original-ref", "resolved-ref"},
|
.labels = {"original-ref", "resolved-ref"},
|
||||||
.handler = {[&](std::string _from, std::string _to) {
|
.handler = {[&](std::string _from, std::string _to) {
|
||||||
auto from = parseFlakeRef(_from, absPath("."));
|
auto from = parseFlakeRef(fetchSettings, _from, absPath("."));
|
||||||
auto to = parseFlakeRef(_to, absPath("."));
|
auto to = parseFlakeRef(fetchSettings, _to, absPath("."));
|
||||||
fetchers::Attrs extraAttrs;
|
fetchers::Attrs extraAttrs;
|
||||||
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
||||||
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
||||||
|
@ -228,7 +240,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (hasPrefix(s, "flake:")) {
|
else if (hasPrefix(s, "flake:")) {
|
||||||
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
|
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
|
||||||
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
|
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
|
||||||
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,32 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
|
|
||||||
|
namespace fetchers { struct Settings; }
|
||||||
|
|
||||||
class EvalState;
|
class EvalState;
|
||||||
struct EvalSettings;
|
struct EvalSettings;
|
||||||
struct CompatibilitySettings;
|
struct CompatibilitySettings;
|
||||||
class Bindings;
|
class Bindings;
|
||||||
struct SourcePath;
|
struct SourcePath;
|
||||||
|
|
||||||
|
namespace flake { struct Settings; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Get rid of global setttings variables
|
||||||
|
*/
|
||||||
|
extern fetchers::Settings fetchSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Get rid of global setttings variables
|
* @todo Get rid of global setttings variables
|
||||||
*/
|
*/
|
||||||
extern EvalSettings evalSettings;
|
extern EvalSettings evalSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Get rid of global setttings variables
|
||||||
|
*/
|
||||||
|
extern flake::Settings flakeSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings that control behaviors that have changed since Nix 2.3.
|
* Settings that control behaviors that have changed since Nix 2.3.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -196,7 +196,8 @@ std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
|
||||||
flake::LockFlags lockFlagsApplyConfig = lockFlags;
|
flake::LockFlags lockFlagsApplyConfig = lockFlags;
|
||||||
// FIXME why this side effect?
|
// FIXME why this side effect?
|
||||||
lockFlagsApplyConfig.applyNixConfig = true;
|
lockFlagsApplyConfig.applyNixConfig = true;
|
||||||
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
|
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(
|
||||||
|
flakeSettings, *state, flakeRef, lockFlagsApplyConfig));
|
||||||
}
|
}
|
||||||
return _lockedFlake;
|
return _lockedFlake;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "common-eval-args.hh"
|
||||||
#include "installable-value.hh"
|
#include "installable-value.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -78,7 +79,7 @@ struct InstallableFlake : InstallableValue
|
||||||
*/
|
*/
|
||||||
static inline FlakeRef defaultNixpkgsFlakeRef()
|
static inline FlakeRef defaultNixpkgsFlakeRef()
|
||||||
{
|
{
|
||||||
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
|
return FlakeRef::fromAttrs(fetchSettings, {{"type","indirect"}, {"id", "nixpkgs"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
ref<eval_cache::EvalCache> openEvalCache(
|
ref<eval_cache::EvalCache> openEvalCache(
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "strings-inline.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void completeFlakeInputPath(
|
void completeFlakeInputPath(
|
||||||
|
@ -129,7 +131,7 @@ MixFlakeOptions::MixFlakeOptions()
|
||||||
lockFlags.writeLockFile = false;
|
lockFlags.writeLockFile = false;
|
||||||
lockFlags.inputOverrides.insert_or_assign(
|
lockFlags.inputOverrides.insert_or_assign(
|
||||||
flake::parseInputPath(inputPath),
|
flake::parseInputPath(inputPath),
|
||||||
parseFlakeRef(flakeRef, absPath(getCommandBaseDir()), true));
|
parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir()), true));
|
||||||
}},
|
}},
|
||||||
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
|
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
|
@ -170,14 +172,15 @@ MixFlakeOptions::MixFlakeOptions()
|
||||||
.handler = {[&](std::string flakeRef) {
|
.handler = {[&](std::string flakeRef) {
|
||||||
auto evalState = getEvalState();
|
auto evalState = getEvalState();
|
||||||
auto flake = flake::lockFlake(
|
auto flake = flake::lockFlake(
|
||||||
|
flakeSettings,
|
||||||
*evalState,
|
*evalState,
|
||||||
parseFlakeRef(flakeRef, absPath(getCommandBaseDir())),
|
parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir())),
|
||||||
{ .writeLockFile = false });
|
{ .writeLockFile = false });
|
||||||
for (auto & [inputName, input] : flake.lockFile.root->inputs) {
|
for (auto & [inputName, input] : flake.lockFile.root->inputs) {
|
||||||
auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes
|
auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes
|
||||||
if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) {
|
if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) {
|
||||||
overrideRegistry(
|
overrideRegistry(
|
||||||
fetchers::Input::fromAttrs({{"type","indirect"}, {"id", inputName}}),
|
fetchers::Input::fromAttrs(fetchSettings, {{"type","indirect"}, {"id", inputName}}),
|
||||||
input3->lockedRef.input,
|
input3->lockedRef.input,
|
||||||
{});
|
{});
|
||||||
}
|
}
|
||||||
|
@ -289,10 +292,10 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
|
||||||
|
|
||||||
if (v2.type() == nAttrs) {
|
if (v2.type() == nAttrs) {
|
||||||
for (auto & i : *v2.attrs()) {
|
for (auto & i : *v2.attrs()) {
|
||||||
std::string name = state->symbols[i.name];
|
std::string_view name = state->symbols[i.name];
|
||||||
if (name.find(searchWord) == 0) {
|
if (name.find(searchWord) == 0) {
|
||||||
if (prefix_ == "")
|
if (prefix_ == "")
|
||||||
completions.add(name);
|
completions.add(std::string(name));
|
||||||
else
|
else
|
||||||
completions.add(prefix_ + "." + name);
|
completions.add(prefix_ + "." + name);
|
||||||
}
|
}
|
||||||
|
@ -338,10 +341,11 @@ void completeFlakeRefWithFragment(
|
||||||
auto flakeRefS = std::string(prefix.substr(0, hash));
|
auto flakeRefS = std::string(prefix.substr(0, hash));
|
||||||
|
|
||||||
// TODO: ideally this would use the command base directory instead of assuming ".".
|
// TODO: ideally this would use the command base directory instead of assuming ".".
|
||||||
auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
|
auto flakeRef = parseFlakeRef(fetchSettings, expandTilde(flakeRefS), absPath("."));
|
||||||
|
|
||||||
auto evalCache = openEvalCache(*evalState,
|
auto evalCache = openEvalCache(*evalState,
|
||||||
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
|
std::make_shared<flake::LockedFlake>(lockFlake(
|
||||||
|
flakeSettings, *evalState, flakeRef, lockFlags)));
|
||||||
|
|
||||||
auto root = evalCache->getRoot();
|
auto root = evalCache->getRoot();
|
||||||
|
|
||||||
|
@ -372,6 +376,7 @@ void completeFlakeRefWithFragment(
|
||||||
auto attrPath2 = (*attr)->getAttrPath(attr2);
|
auto attrPath2 = (*attr)->getAttrPath(attr2);
|
||||||
/* Strip the attrpath prefix. */
|
/* Strip the attrpath prefix. */
|
||||||
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
||||||
|
// FIXME: handle names with dots
|
||||||
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
|
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,7 +405,7 @@ void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::strin
|
||||||
Args::completeDir(completions, 0, prefix);
|
Args::completeDir(completions, 0, prefix);
|
||||||
|
|
||||||
/* Look for registry entries that match the prefix. */
|
/* Look for registry entries that match the prefix. */
|
||||||
for (auto & registry : fetchers::getRegistries(store)) {
|
for (auto & registry : fetchers::getRegistries(fetchSettings, store)) {
|
||||||
for (auto & entry : registry->entries) {
|
for (auto & entry : registry->entries) {
|
||||||
auto from = entry.from.to_string();
|
auto from = entry.from.to_string();
|
||||||
if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
|
if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
|
||||||
|
@ -526,7 +531,8 @@ Installables SourceExprCommand::parseInstallables(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath(getCommandBaseDir()));
|
auto [flakeRef, fragment] = parseFlakeRefWithFragment(
|
||||||
|
fetchSettings, std::string { prefix }, absPath(getCommandBaseDir()));
|
||||||
result.push_back(make_ref<InstallableFlake>(
|
result.push_back(make_ref<InstallableFlake>(
|
||||||
this,
|
this,
|
||||||
getEvalState(),
|
getEvalState(),
|
||||||
|
@ -843,6 +849,7 @@ std::vector<FlakeRef> RawInstallablesCommand::getFlakeRefsForCompletion()
|
||||||
std::vector<FlakeRef> res;
|
std::vector<FlakeRef> res;
|
||||||
for (auto i : rawInstallables)
|
for (auto i : rawInstallables)
|
||||||
res.push_back(parseFlakeRefWithFragment(
|
res.push_back(parseFlakeRefWithFragment(
|
||||||
|
fetchSettings,
|
||||||
expandTilde(i),
|
expandTilde(i),
|
||||||
absPath(getCommandBaseDir())).first);
|
absPath(getCommandBaseDir())).first);
|
||||||
return res;
|
return res;
|
||||||
|
@ -865,6 +872,7 @@ std::vector<FlakeRef> InstallableCommand::getFlakeRefsForCompletion()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
parseFlakeRefWithFragment(
|
parseFlakeRefWithFragment(
|
||||||
|
fetchSettings,
|
||||||
expandTilde(_installable),
|
expandTilde(_installable),
|
||||||
absPath(getCommandBaseDir())).first
|
absPath(getCommandBaseDir())).first
|
||||||
};
|
};
|
||||||
|
|
|
@ -73,7 +73,7 @@ static int listPossibleCallback(char * s, char *** avp)
|
||||||
{
|
{
|
||||||
auto possible = curRepl->completePrefix(s);
|
auto possible = curRepl->completePrefix(s);
|
||||||
|
|
||||||
if (possible.size() > (INT_MAX / sizeof(char *)))
|
if (possible.size() > (std::numeric_limits<int>::max() / sizeof(char *)))
|
||||||
throw Error("too many completions");
|
throw Error("too many completions");
|
||||||
|
|
||||||
int ac = 0;
|
int ac = 0;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
#include "repl-interacter.hh"
|
#include "repl-interacter.hh"
|
||||||
#include "repl.hh"
|
#include "repl.hh"
|
||||||
|
@ -10,8 +9,6 @@
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "config-global.hh"
|
#include "config-global.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
#include "eval-cache.hh"
|
|
||||||
#include "eval-inline.hh"
|
|
||||||
#include "eval-settings.hh"
|
#include "eval-settings.hh"
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
|
@ -29,12 +26,16 @@
|
||||||
#include "markdown.hh"
|
#include "markdown.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
#include "print.hh"
|
#include "print.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
#include "value.hh"
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
#define GC_INCLUDE_NEW
|
#define GC_INCLUDE_NEW
|
||||||
#include <gc/gc_cpp.h>
|
#include <gc/gc_cpp.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -616,6 +617,38 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||||
|
|
||||||
else if (command == ":doc") {
|
else if (command == ":doc") {
|
||||||
Value v;
|
Value v;
|
||||||
|
|
||||||
|
auto expr = parseString(arg);
|
||||||
|
std::string fallbackName;
|
||||||
|
PosIdx fallbackPos;
|
||||||
|
DocComment fallbackDoc;
|
||||||
|
if (auto select = dynamic_cast<ExprSelect *>(expr)) {
|
||||||
|
Value vAttrs;
|
||||||
|
auto name = select->evalExceptFinalSelect(*state, *env, vAttrs);
|
||||||
|
fallbackName = state->symbols[name];
|
||||||
|
|
||||||
|
state->forceAttrs(vAttrs, noPos, "while evaluating an attribute set to look for documentation");
|
||||||
|
auto attrs = vAttrs.attrs();
|
||||||
|
assert(attrs);
|
||||||
|
auto attr = attrs->get(name);
|
||||||
|
if (!attr) {
|
||||||
|
// When missing, trigger the normal exception
|
||||||
|
// e.g. :doc builtins.foo
|
||||||
|
// behaves like
|
||||||
|
// nix-repl> builtins.foo
|
||||||
|
// error: attribute 'foo' missing
|
||||||
|
evalString(arg, v);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
if (attr->pos) {
|
||||||
|
fallbackPos = attr->pos;
|
||||||
|
fallbackDoc = state->getDocCommentForPos(fallbackPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
evalString(arg, v);
|
||||||
|
}
|
||||||
|
|
||||||
evalString(arg, v);
|
evalString(arg, v);
|
||||||
if (auto doc = state->getDoc(v)) {
|
if (auto doc = state->getDoc(v)) {
|
||||||
std::string markdown;
|
std::string markdown;
|
||||||
|
@ -633,6 +666,19 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||||
markdown += stripIndentation(doc->doc);
|
markdown += stripIndentation(doc->doc);
|
||||||
|
|
||||||
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||||
|
} else if (fallbackPos) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Attribute `" << fallbackName << "`\n\n";
|
||||||
|
ss << " … defined at " << state->positions[fallbackPos] << "\n\n";
|
||||||
|
if (fallbackDoc) {
|
||||||
|
ss << fallbackDoc.getInnerText(state->positions);
|
||||||
|
} else {
|
||||||
|
ss << "No documentation found.\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto markdown = ss.str();
|
||||||
|
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||||
|
|
||||||
} else
|
} else
|
||||||
throw Error("value does not have documentation");
|
throw Error("value does not have documentation");
|
||||||
}
|
}
|
||||||
|
@ -690,14 +736,14 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||||
if (flakeRefS.empty())
|
if (flakeRefS.empty())
|
||||||
throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
|
throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
|
||||||
|
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
|
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, absPath("."), true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||||
|
|
||||||
Value v;
|
Value v;
|
||||||
|
|
||||||
flake::callFlake(*state,
|
flake::callFlake(*state,
|
||||||
flake::lockFlake(*state, flakeRef,
|
flake::lockFlake(flakeSettings, *state, flakeRef,
|
||||||
flake::LockFlags {
|
flake::LockFlags {
|
||||||
.updateLockFile = false,
|
.updateLockFile = false,
|
||||||
.useRegistries = !evalSettings.pureEval,
|
.useRegistries = !evalSettings.pureEval,
|
||||||
|
|
|
@ -15,7 +15,7 @@ libexprc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \
|
||||||
$(INCLUDE_libstore) $(INCLUDE_libstorec) \
|
$(INCLUDE_libstore) $(INCLUDE_libstorec) \
|
||||||
$(INCLUDE_libexpr) $(INCLUDE_libexprc)
|
$(INCLUDE_libexpr) $(INCLUDE_libexprc)
|
||||||
|
|
||||||
libexprc_LIBS = libutil libutilc libstore libstorec libexpr
|
libexprc_LIBS = libutil libutilc libstore libstorec libfetchers libexpr
|
||||||
|
|
||||||
libexprc_LDFLAGS += $(THREAD_LDFLAGS)
|
libexprc_LDFLAGS += $(THREAD_LDFLAGS)
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "config.hh"
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "eval-gc.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "util.hh"
|
|
||||||
#include "eval-settings.hh"
|
#include "eval-settings.hh"
|
||||||
|
|
||||||
#include "nix_api_expr.h"
|
#include "nix_api_expr.h"
|
||||||
|
@ -112,12 +110,14 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
|
||||||
static_cast<std::align_val_t>(alignof(EvalState)));
|
static_cast<std::align_val_t>(alignof(EvalState)));
|
||||||
auto * p2 = static_cast<EvalState *>(p);
|
auto * p2 = static_cast<EvalState *>(p);
|
||||||
new (p) EvalState {
|
new (p) EvalState {
|
||||||
|
.fetchSettings = nix::fetchers::Settings{},
|
||||||
.settings = nix::EvalSettings{
|
.settings = nix::EvalSettings{
|
||||||
nix::settings.readOnlyMode,
|
nix::settings.readOnlyMode,
|
||||||
},
|
},
|
||||||
.state = nix::EvalState(
|
.state = nix::EvalState(
|
||||||
nix::LookupPath::parse(lookupPath),
|
nix::LookupPath::parse(lookupPath),
|
||||||
store->ptr,
|
store->ptr,
|
||||||
|
p2->fetchSettings,
|
||||||
p2->settings),
|
p2->settings),
|
||||||
};
|
};
|
||||||
loadConfFile(p2->settings);
|
loadConfFile(p2->settings);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef NIX_API_EXPR_INTERNAL_H
|
#ifndef NIX_API_EXPR_INTERNAL_H
|
||||||
#define NIX_API_EXPR_INTERNAL_H
|
#define NIX_API_EXPR_INTERNAL_H
|
||||||
|
|
||||||
|
#include "fetch-settings.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
#include "eval-settings.hh"
|
#include "eval-settings.hh"
|
||||||
#include "attr-set.hh"
|
#include "attr-set.hh"
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
|
|
||||||
struct EvalState
|
struct EvalState
|
||||||
{
|
{
|
||||||
|
nix::fetchers::Settings fetchSettings;
|
||||||
nix::EvalSettings settings;
|
nix::EvalSettings settings;
|
||||||
nix::EvalState state;
|
nix::EvalState state;
|
||||||
};
|
};
|
||||||
|
|
|
@ -115,7 +115,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* Compare to another value of the same type.
|
* Compare to another value of the same type.
|
||||||
*/
|
*/
|
||||||
virtual bool operator==(const ExternalValueBase & b) const override
|
virtual bool operator==(const ExternalValueBase & b) const noexcept override
|
||||||
{
|
{
|
||||||
if (!desc.equal) {
|
if (!desc.equal) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -383,7 +383,7 @@ nix_value * nix_get_attr_byidx(
|
||||||
try {
|
try {
|
||||||
auto & v = check_value_in(value);
|
auto & v = check_value_in(value);
|
||||||
const nix::Attr & a = (*v.attrs())[i];
|
const nix::Attr & a = (*v.attrs())[i];
|
||||||
*name = ((const std::string &) (state->state.symbols[a.name])).c_str();
|
*name = state->state.symbols[a.name].c_str();
|
||||||
nix_gc_incref(nullptr, a.value);
|
nix_gc_incref(nullptr, a.value);
|
||||||
state->state.forceValue(*a.value, nix::noPos);
|
state->state.forceValue(*a.value, nix::noPos);
|
||||||
return as_nix_value_ptr(a.value);
|
return as_nix_value_ptr(a.value);
|
||||||
|
@ -399,7 +399,7 @@ nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalSt
|
||||||
try {
|
try {
|
||||||
auto & v = check_value_in(value);
|
auto & v = check_value_in(value);
|
||||||
const nix::Attr & a = (*v.attrs())[i];
|
const nix::Attr & a = (*v.attrs())[i];
|
||||||
return ((const std::string &) (state->state.symbols[a.name])).c_str();
|
return state->state.symbols[a.name].c_str();
|
||||||
}
|
}
|
||||||
NIXC_CATCH_ERRS_NULL
|
NIXC_CATCH_ERRS_NULL
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
|
||||||
if (!a) {
|
if (!a) {
|
||||||
std::set<std::string> attrNames;
|
std::set<std::string> attrNames;
|
||||||
for (auto & attr : *v->attrs())
|
for (auto & attr : *v->attrs())
|
||||||
attrNames.insert(state.symbols[attr.name]);
|
attrNames.insert(std::string(state.symbols[attr.name]));
|
||||||
|
|
||||||
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
||||||
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -28,9 +27,9 @@ struct Attr
|
||||||
Attr(Symbol name, Value * value, PosIdx pos = noPos)
|
Attr(Symbol name, Value * value, PosIdx pos = noPos)
|
||||||
: name(name), pos(pos), value(value) { };
|
: name(name), pos(pos), value(value) { };
|
||||||
Attr() { };
|
Attr() { };
|
||||||
bool operator < (const Attr & a) const
|
auto operator <=> (const Attr & a) const
|
||||||
{
|
{
|
||||||
return name < a.name;
|
return name <=> a.name;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ struct AttrDb
|
||||||
(key.first)
|
(key.first)
|
||||||
(symbols[key.second])
|
(symbols[key.second])
|
||||||
(AttrType::ListOfStrings)
|
(AttrType::ListOfStrings)
|
||||||
(concatStringsSep("\t", l)).exec();
|
(dropEmptyInitThenConcatStringsSep("\t", l)).exec();
|
||||||
|
|
||||||
return state->db.getLastInsertedRowId();
|
return state->db.getLastInsertedRowId();
|
||||||
});
|
});
|
||||||
|
@ -441,12 +441,12 @@ std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
|
||||||
|
|
||||||
std::string AttrCursor::getAttrPathStr() const
|
std::string AttrCursor::getAttrPathStr() const
|
||||||
{
|
{
|
||||||
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
|
return dropEmptyInitThenConcatStringsSep(".", root->state.symbols.resolve(getAttrPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AttrCursor::getAttrPathStr(Symbol name) const
|
std::string AttrCursor::getAttrPathStr(Symbol name) const
|
||||||
{
|
{
|
||||||
return concatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
|
return dropEmptyInitThenConcatStringsSep(".", root->state.symbols.resolve(getAttrPath(name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Value & AttrCursor::forceValue()
|
Value & AttrCursor::forceValue()
|
||||||
|
@ -490,7 +490,7 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
|
||||||
auto attrNames = getAttrs();
|
auto attrNames = getAttrs();
|
||||||
std::set<std::string> strAttrNames;
|
std::set<std::string> strAttrNames;
|
||||||
for (auto & name : attrNames)
|
for (auto & name : attrNames)
|
||||||
strAttrNames.insert(root->state.symbols[name]);
|
strAttrNames.insert(std::string(root->state.symbols[name]));
|
||||||
|
|
||||||
return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]);
|
return Suggestions::bestMatches(strAttrNames, root->state.symbols[name]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "error.hh"
|
#include "error.hh"
|
||||||
#include "pos-idx.hh"
|
#include "pos-idx.hh"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "users.hh"
|
#include "users.hh"
|
||||||
#include "config-global.hh"
|
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "profiles.hh"
|
#include "profiles.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "eval-gc.hh"
|
||||||
#include "eval-settings.hh"
|
#include "eval-settings.hh"
|
||||||
#include "hash.hh"
|
|
||||||
#include "primops.hh"
|
#include "primops.hh"
|
||||||
#include "print-options.hh"
|
#include "print-options.hh"
|
||||||
#include "exit.hh"
|
#include "exit.hh"
|
||||||
|
@ -16,7 +16,6 @@
|
||||||
#include "print.hh"
|
#include "print.hh"
|
||||||
#include "filtering-source-accessor.hh"
|
#include "filtering-source-accessor.hh"
|
||||||
#include "memory-source-accessor.hh"
|
#include "memory-source-accessor.hh"
|
||||||
#include "signals.hh"
|
|
||||||
#include "gc-small-vector.hh"
|
#include "gc-small-vector.hh"
|
||||||
#include "url.hh"
|
#include "url.hh"
|
||||||
#include "fetch-to-store.hh"
|
#include "fetch-to-store.hh"
|
||||||
|
@ -24,7 +23,6 @@
|
||||||
#include "parser-tab.hh"
|
#include "parser-tab.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -51,6 +49,8 @@
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "strings-inline.hh"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -217,9 +217,11 @@ static constexpr size_t BASE_ENV_SIZE = 128;
|
||||||
EvalState::EvalState(
|
EvalState::EvalState(
|
||||||
const LookupPath & _lookupPath,
|
const LookupPath & _lookupPath,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
std::shared_ptr<Store> buildStore)
|
std::shared_ptr<Store> buildStore)
|
||||||
: settings{settings}
|
: fetchSettings{fetchSettings}
|
||||||
|
, settings{settings}
|
||||||
, sWith(symbols.create("<with>"))
|
, sWith(symbols.create("<with>"))
|
||||||
, sOutPath(symbols.create("outPath"))
|
, sOutPath(symbols.create("outPath"))
|
||||||
, sDrvPath(symbols.create("drvPath"))
|
, sDrvPath(symbols.create("drvPath"))
|
||||||
|
@ -557,6 +559,54 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||||
.doc = doc,
|
.doc = doc,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (v.isLambda()) {
|
||||||
|
auto exprLambda = v.payload.lambda.fun;
|
||||||
|
|
||||||
|
std::stringstream s(std::ios_base::out);
|
||||||
|
std::string name;
|
||||||
|
auto pos = positions[exprLambda->getPos()];
|
||||||
|
std::string docStr;
|
||||||
|
|
||||||
|
if (exprLambda->name) {
|
||||||
|
name = symbols[exprLambda->name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exprLambda->docComment) {
|
||||||
|
docStr = exprLambda->docComment.getInnerText(positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.empty()) {
|
||||||
|
s << "Function ";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s << "Function `" << name << "`";
|
||||||
|
if (pos)
|
||||||
|
s << "\\\n … " ;
|
||||||
|
else
|
||||||
|
s << "\\\n";
|
||||||
|
}
|
||||||
|
if (pos) {
|
||||||
|
s << "defined at " << pos;
|
||||||
|
}
|
||||||
|
if (!docStr.empty()) {
|
||||||
|
s << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
s << docStr;
|
||||||
|
|
||||||
|
s << '\0'; // for making a c string below
|
||||||
|
std::string ss = s.str();
|
||||||
|
|
||||||
|
return Doc {
|
||||||
|
.pos = pos,
|
||||||
|
.name = name,
|
||||||
|
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
|
||||||
|
.args = {},
|
||||||
|
.doc =
|
||||||
|
// FIXME: this leaks; make the field std::string?
|
||||||
|
strdup(ss.data()),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,11 +683,11 @@ void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const En
|
||||||
if (se.isWith && !env.values[0]->isThunk()) {
|
if (se.isWith && !env.values[0]->isThunk()) {
|
||||||
// add 'with' bindings.
|
// add 'with' bindings.
|
||||||
for (auto & j : *env.values[0]->attrs())
|
for (auto & j : *env.values[0]->attrs())
|
||||||
vm[st[j.name]] = j.value;
|
vm.insert_or_assign(std::string(st[j.name]), j.value);
|
||||||
} else {
|
} else {
|
||||||
// iterate through staticenv bindings and add them.
|
// iterate through staticenv bindings and add them.
|
||||||
for (auto & i : se.vars)
|
for (auto & i : se.vars)
|
||||||
vm[st[i.first]] = env.values[i.second];
|
vm.insert_or_assign(std::string(st[i.first]), env.values[i.second]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1011,7 +1061,7 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
|
||||||
if (!e)
|
if (!e)
|
||||||
e = parseExprFromFile(resolvedPath);
|
e = parseExprFromFile(resolvedPath);
|
||||||
|
|
||||||
fileParseCache[resolvedPath] = e;
|
fileParseCache.emplace(resolvedPath, e);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto dts = debugRepl
|
auto dts = debugRepl
|
||||||
|
@ -1034,8 +1084,8 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
fileEvalCache[resolvedPath] = v;
|
fileEvalCache.emplace(resolvedPath, v);
|
||||||
if (path != resolvedPath) fileEvalCache[path] = v;
|
if (path != resolvedPath) fileEvalCache.emplace(path, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1338,7 +1388,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
if (!(j = vAttrs->attrs()->get(name))) {
|
if (!(j = vAttrs->attrs()->get(name))) {
|
||||||
std::set<std::string> allAttrNames;
|
std::set<std::string> allAttrNames;
|
||||||
for (auto & attr : *vAttrs->attrs())
|
for (auto & attr : *vAttrs->attrs())
|
||||||
allAttrNames.insert(state.symbols[attr.name]);
|
allAttrNames.insert(std::string(state.symbols[attr.name]));
|
||||||
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
|
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
|
||||||
state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
|
state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
|
||||||
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
|
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
|
||||||
|
@ -1365,6 +1415,22 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
v = *vAttrs;
|
v = *vAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Symbol ExprSelect::evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs)
|
||||||
|
{
|
||||||
|
Value vTmp;
|
||||||
|
Symbol name = getName(attrPath[attrPath.size() - 1], state, env);
|
||||||
|
|
||||||
|
if (attrPath.size() == 1) {
|
||||||
|
e->eval(state, env, vTmp);
|
||||||
|
} else {
|
||||||
|
ExprSelect init(*this);
|
||||||
|
init.attrPath.pop_back();
|
||||||
|
init.eval(state, env, vTmp);
|
||||||
|
}
|
||||||
|
attrs = vTmp;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
|
@ -1496,7 +1562,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
if (!lambda.formals->has(i.name)) {
|
if (!lambda.formals->has(i.name)) {
|
||||||
std::set<std::string> formalNames;
|
std::set<std::string> formalNames;
|
||||||
for (auto & formal : lambda.formals->formals)
|
for (auto & formal : lambda.formals->formals)
|
||||||
formalNames.insert(symbols[formal.name]);
|
formalNames.insert(std::string(symbols[formal.name]));
|
||||||
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
|
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
|
||||||
error<TypeError>("function '%1%' called with unexpected argument '%2%'",
|
error<TypeError>("function '%1%' called with unexpected argument '%2%'",
|
||||||
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
||||||
|
@ -2826,13 +2892,37 @@ Expr * EvalState::parse(
|
||||||
const SourcePath & basePath,
|
const SourcePath & basePath,
|
||||||
std::shared_ptr<StaticEnv> & staticEnv)
|
std::shared_ptr<StaticEnv> & staticEnv)
|
||||||
{
|
{
|
||||||
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, rootFS, exprSymbols);
|
DocCommentMap tmpDocComments; // Only used when not origin is not a SourcePath
|
||||||
|
DocCommentMap *docComments = &tmpDocComments;
|
||||||
|
|
||||||
|
if (auto sourcePath = std::get_if<SourcePath>(&origin)) {
|
||||||
|
auto [it, _] = positionToDocComment.try_emplace(*sourcePath);
|
||||||
|
docComments = &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, *docComments, rootFS, exprSymbols);
|
||||||
|
|
||||||
result->bindVars(*this, staticEnv);
|
result->bindVars(*this, staticEnv);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DocComment EvalState::getDocCommentForPos(PosIdx pos)
|
||||||
|
{
|
||||||
|
auto pos2 = positions[pos];
|
||||||
|
auto path = pos2.getSourcePath();
|
||||||
|
if (!path)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto table = positionToDocComment.find(*path);
|
||||||
|
if (table == positionToDocComment.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto it = table->second.find(pos);
|
||||||
|
if (it == table->second.end())
|
||||||
|
return {};
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
||||||
{
|
{
|
||||||
|
@ -2842,7 +2932,7 @@ std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool ExternalValueBase::operator==(const ExternalValueBase & b) const
|
bool ExternalValueBase::operator==(const ExternalValueBase & b) const noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,21 @@
|
||||||
|
|
||||||
#include "attr-set.hh"
|
#include "attr-set.hh"
|
||||||
#include "eval-error.hh"
|
#include "eval-error.hh"
|
||||||
#include "eval-gc.hh"
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "value.hh"
|
#include "value.hh"
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
|
#include "position.hh"
|
||||||
|
#include "pos-table.hh"
|
||||||
#include "source-accessor.hh"
|
#include "source-accessor.hh"
|
||||||
#include "search-path.hh"
|
#include "search-path.hh"
|
||||||
#include "repl-exit-status.hh"
|
#include "repl-exit-status.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -30,6 +30,7 @@ namespace nix {
|
||||||
constexpr size_t maxPrimOpArity = 8;
|
constexpr size_t maxPrimOpArity = 8;
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
|
namespace fetchers { struct Settings; }
|
||||||
struct EvalSettings;
|
struct EvalSettings;
|
||||||
class EvalState;
|
class EvalState;
|
||||||
class StorePath;
|
class StorePath;
|
||||||
|
@ -43,7 +44,7 @@ namespace eval_cache {
|
||||||
/**
|
/**
|
||||||
* Function that implements a primop.
|
* Function that implements a primop.
|
||||||
*/
|
*/
|
||||||
typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
using PrimOpFun = void(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info about a primitive operation, and its implementation
|
* Info about a primitive operation, and its implementation
|
||||||
|
@ -84,7 +85,7 @@ struct PrimOp
|
||||||
/**
|
/**
|
||||||
* Implementation of the primop.
|
* Implementation of the primop.
|
||||||
*/
|
*/
|
||||||
std::function<std::remove_pointer<PrimOpFun>::type> fun;
|
std::function<PrimOpFun> fun;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional experimental for this to be gated on.
|
* Optional experimental for this to be gated on.
|
||||||
|
@ -129,6 +130,8 @@ struct Constant
|
||||||
typedef std::map<std::string, Value *> ValMap;
|
typedef std::map<std::string, Value *> ValMap;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef std::map<PosIdx, DocComment> DocCommentMap;
|
||||||
|
|
||||||
struct Env
|
struct Env
|
||||||
{
|
{
|
||||||
Env * up;
|
Env * up;
|
||||||
|
@ -162,6 +165,7 @@ struct DebugTrace {
|
||||||
class EvalState : public std::enable_shared_from_this<EvalState>
|
class EvalState : public std::enable_shared_from_this<EvalState>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
const fetchers::Settings & fetchSettings;
|
||||||
const EvalSettings & settings;
|
const EvalSettings & settings;
|
||||||
SymbolTable symbols;
|
SymbolTable symbols;
|
||||||
PosTable positions;
|
PosTable positions;
|
||||||
|
@ -305,15 +309,15 @@ private:
|
||||||
|
|
||||||
/* Cache for calls to addToStore(); maps source paths to the store
|
/* Cache for calls to addToStore(); maps source paths to the store
|
||||||
paths. */
|
paths. */
|
||||||
Sync<std::map<SourcePath, StorePath>> srcToStore;
|
Sync<std::unordered_map<SourcePath, StorePath>> srcToStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache from path names to parse trees.
|
* A cache from path names to parse trees.
|
||||||
*/
|
*/
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef std::map<SourcePath, Expr *, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache;
|
typedef std::unordered_map<SourcePath, Expr *, std::hash<SourcePath>, std::equal_to<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache;
|
||||||
#else
|
#else
|
||||||
typedef std::map<SourcePath, Expr *> FileParseCache;
|
typedef std::unordered_map<SourcePath, Expr *> FileParseCache;
|
||||||
#endif
|
#endif
|
||||||
FileParseCache fileParseCache;
|
FileParseCache fileParseCache;
|
||||||
|
|
||||||
|
@ -321,12 +325,18 @@ private:
|
||||||
* A cache from path names to values.
|
* A cache from path names to values.
|
||||||
*/
|
*/
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef std::map<SourcePath, Value, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache;
|
typedef std::unordered_map<SourcePath, Value, std::hash<SourcePath>, std::equal_to<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache;
|
||||||
#else
|
#else
|
||||||
typedef std::map<SourcePath, Value> FileEvalCache;
|
typedef std::unordered_map<SourcePath, Value> FileEvalCache;
|
||||||
#endif
|
#endif
|
||||||
FileEvalCache fileEvalCache;
|
FileEvalCache fileEvalCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate source positions of certain AST nodes with their preceding doc comment, if they have one.
|
||||||
|
* Grouped by file.
|
||||||
|
*/
|
||||||
|
std::map<SourcePath, DocCommentMap> positionToDocComment;
|
||||||
|
|
||||||
LookupPath lookupPath;
|
LookupPath lookupPath;
|
||||||
|
|
||||||
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
std::map<std::string, std::optional<std::string>> lookupPathResolved;
|
||||||
|
@ -353,6 +363,7 @@ public:
|
||||||
EvalState(
|
EvalState(
|
||||||
const LookupPath & _lookupPath,
|
const LookupPath & _lookupPath,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
std::shared_ptr<Store> buildStore = nullptr);
|
std::shared_ptr<Store> buildStore = nullptr);
|
||||||
~EvalState();
|
~EvalState();
|
||||||
|
@ -768,6 +779,8 @@ public:
|
||||||
std::string_view pathArg,
|
std::string_view pathArg,
|
||||||
PosIdx pos);
|
PosIdx pos);
|
||||||
|
|
||||||
|
DocComment getDocCommentForPos(PosIdx pos);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -342,9 +342,9 @@ std::optional<PackageInfo> getDerivation(EvalState & state, Value & v,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static std::string addToPath(const std::string & s1, const std::string & s2)
|
static std::string addToPath(const std::string & s1, std::string_view s2)
|
||||||
{
|
{
|
||||||
return s1.empty() ? s2 : s1 + "." + s2;
|
return s1.empty() ? std::string(s2) : s1 + "." + s2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
30
src/libexpr/lexer-helpers.cc
Normal file
30
src/libexpr/lexer-helpers.cc
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include "lexer-tab.hh"
|
||||||
|
#include "lexer-helpers.hh"
|
||||||
|
#include "parser-tab.hh"
|
||||||
|
|
||||||
|
void nix::lexer::internal::initLoc(YYLTYPE * loc)
|
||||||
|
{
|
||||||
|
loc->beginOffset = loc->endOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len)
|
||||||
|
{
|
||||||
|
loc->stash();
|
||||||
|
|
||||||
|
LexerState & lexerState = *yyget_extra(yyscanner);
|
||||||
|
|
||||||
|
if (lexerState.docCommentDistance == 1) {
|
||||||
|
// Preceding token was a doc comment.
|
||||||
|
ParserLocation doc;
|
||||||
|
doc.beginOffset = lexerState.lastDocCommentLoc.beginOffset;
|
||||||
|
ParserLocation docEnd;
|
||||||
|
docEnd.beginOffset = lexerState.lastDocCommentLoc.endOffset;
|
||||||
|
DocComment docComment{lexerState.at(doc), lexerState.at(docEnd)};
|
||||||
|
PosIdx locPos = lexerState.at(*loc);
|
||||||
|
lexerState.positionToDocComment.emplace(locPos, docComment);
|
||||||
|
}
|
||||||
|
lexerState.docCommentDistance++;
|
||||||
|
|
||||||
|
loc->beginOffset = loc->endOffset;
|
||||||
|
loc->endOffset += len;
|
||||||
|
}
|
9
src/libexpr/lexer-helpers.hh
Normal file
9
src/libexpr/lexer-helpers.hh
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nix::lexer::internal {
|
||||||
|
|
||||||
|
void initLoc(YYLTYPE * loc);
|
||||||
|
|
||||||
|
void adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len);
|
||||||
|
|
||||||
|
} // namespace nix::lexer
|
|
@ -5,7 +5,7 @@
|
||||||
%option stack
|
%option stack
|
||||||
%option nodefault
|
%option nodefault
|
||||||
%option nounput noyy_top_state
|
%option nounput noyy_top_state
|
||||||
|
%option extra-type="::nix::LexerState *"
|
||||||
|
|
||||||
%s DEFAULT
|
%s DEFAULT
|
||||||
%x STRING
|
%x STRING
|
||||||
|
@ -14,6 +14,10 @@
|
||||||
%x INPATH_SLASH
|
%x INPATH_SLASH
|
||||||
%x PATH_START
|
%x PATH_START
|
||||||
|
|
||||||
|
%top {
|
||||||
|
#include "parser-tab.hh" // YYSTYPE
|
||||||
|
#include "parser-state.hh"
|
||||||
|
}
|
||||||
|
|
||||||
%{
|
%{
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
|
@ -22,28 +26,19 @@
|
||||||
|
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
#include "parser-tab.hh"
|
#include "parser-tab.hh"
|
||||||
|
#include "lexer-helpers.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
struct LexerState;
|
||||||
|
}
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
using namespace nix::lexer::internal;
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
#define CUR_POS state->at(*yylloc)
|
#define CUR_POS state->at(*yylloc)
|
||||||
|
|
||||||
static void initLoc(YYLTYPE * loc)
|
|
||||||
{
|
|
||||||
loc->first_line = loc->last_line = 0;
|
|
||||||
loc->first_column = loc->last_column = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
|
||||||
{
|
|
||||||
loc->stash();
|
|
||||||
|
|
||||||
loc->first_column = loc->last_column;
|
|
||||||
loc->last_column += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// we make use of the fact that the parser receives a private copy of the input
|
// we make use of the fact that the parser receives a private copy of the input
|
||||||
// string and can munge around in it.
|
// string and can munge around in it.
|
||||||
static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
|
static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
|
||||||
|
@ -79,7 +74,7 @@ static StringToken unescapeStr(SymbolTable & symbols, char * s, size_t length)
|
||||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||||
|
|
||||||
#define YY_USER_INIT initLoc(yylloc)
|
#define YY_USER_INIT initLoc(yylloc)
|
||||||
#define YY_USER_ACTION adjustLoc(yylloc, yytext, yyleng);
|
#define YY_USER_ACTION adjustLoc(yyscanner, yylloc, yytext, yyleng);
|
||||||
|
|
||||||
#define PUSH_STATE(state) yy_push_state(state, yyscanner)
|
#define PUSH_STATE(state) yy_push_state(state, yyscanner)
|
||||||
#define POP_STATE() yy_pop_state(yyscanner)
|
#define POP_STATE() yy_pop_state(yyscanner)
|
||||||
|
@ -279,9 +274,32 @@ or { return OR_KW; }
|
||||||
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
||||||
{URI} { yylval->uri = {yytext, (size_t) yyleng}; return URI; }
|
{URI} { yylval->uri = {yytext, (size_t) yyleng}; return URI; }
|
||||||
|
|
||||||
[ \t\r\n]+ /* eat up whitespace */
|
%{
|
||||||
\#[^\r\n]* /* single-line comments */
|
// Doc comment rule
|
||||||
\/\*([^*]|\*+[^*/])*\*+\/ /* long comments */
|
//
|
||||||
|
// \/\*\* /**
|
||||||
|
// [^/*] reject /**/ (empty comment) and /***
|
||||||
|
// ([^*]|\*+[^*/])*\*+\/ same as the long comment rule
|
||||||
|
// ( )* zero or more non-ending sequences
|
||||||
|
// \* end(1)
|
||||||
|
// \/ end(2)
|
||||||
|
%}
|
||||||
|
\/\*\*[^/*]([^*]|\*+[^*/])*\*+\/ /* doc comments */ {
|
||||||
|
LexerState & lexerState = *yyget_extra(yyscanner);
|
||||||
|
lexerState.docCommentDistance = 0;
|
||||||
|
lexerState.lastDocCommentLoc.beginOffset = yylloc->beginOffset;
|
||||||
|
lexerState.lastDocCommentLoc.endOffset = yylloc->endOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
%{
|
||||||
|
// The following rules have docCommentDistance--
|
||||||
|
// This compensates for the docCommentDistance++ which happens by default to
|
||||||
|
// make all the other rules invalidate the doc comment.
|
||||||
|
%}
|
||||||
|
[ \t\r\n]+ /* eat up whitespace */ { yyget_extra(yyscanner)->docCommentDistance--; }
|
||||||
|
\#[^\r\n]* /* single-line comments */ { yyget_extra(yyscanner)->docCommentDistance--; }
|
||||||
|
\/\*([^*]|\*+[^*/])*\*+\/ /* long comments */ { yyget_extra(yyscanner)->docCommentDistance--; }
|
||||||
|
|
||||||
{ANY} {
|
{ANY} {
|
||||||
/* Don't return a negative number, as this will cause
|
/* Don't return a negative number, as this will cause
|
||||||
|
|
|
@ -139,6 +139,7 @@ sources = files(
|
||||||
'function-trace.cc',
|
'function-trace.cc',
|
||||||
'get-drvs.cc',
|
'get-drvs.cc',
|
||||||
'json-to-value.cc',
|
'json-to-value.cc',
|
||||||
|
'lexer-helpers.cc',
|
||||||
'nixexpr.cc',
|
'nixexpr.cc',
|
||||||
'paths.cc',
|
'paths.cc',
|
||||||
'primops.cc',
|
'primops.cc',
|
||||||
|
@ -165,6 +166,7 @@ headers = [config_h] + files(
|
||||||
'gc-small-vector.hh',
|
'gc-small-vector.hh',
|
||||||
'get-drvs.hh',
|
'get-drvs.hh',
|
||||||
'json-to-value.hh',
|
'json-to-value.hh',
|
||||||
|
# internal: 'lexer-helpers.hh',
|
||||||
'nixexpr.hh',
|
'nixexpr.hh',
|
||||||
'parser-state.hh',
|
'parser-state.hh',
|
||||||
'pos-idx.hh',
|
'pos-idx.hh',
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
#include "derivations.hh"
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
|
@ -8,6 +7,8 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "strings-inline.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
unsigned long Expr::nrExprs = 0;
|
unsigned long Expr::nrExprs = 0;
|
||||||
|
@ -582,6 +583,22 @@ std::string ExprLambda::showNamePos(const EvalState & state) const
|
||||||
return fmt("%1% at %2%", id, state.positions[pos]);
|
return fmt("%1% at %2%", id, state.positions[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExprLambda::setDocComment(DocComment docComment) {
|
||||||
|
// RFC 145 specifies that the innermost doc comment wins.
|
||||||
|
// See https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md#ambiguous-placement
|
||||||
|
if (!this->docComment) {
|
||||||
|
this->docComment = docComment;
|
||||||
|
|
||||||
|
// Curried functions are defined by putting a function directly
|
||||||
|
// in the body of another function. To render docs for those, we
|
||||||
|
// need to propagate the doc comment to the innermost function.
|
||||||
|
//
|
||||||
|
// If we have our own comment, we've already propagated it, so this
|
||||||
|
// belongs in the same conditional.
|
||||||
|
body->setDocComment(docComment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Position table. */
|
/* Position table. */
|
||||||
|
@ -626,4 +643,22 @@ size_t SymbolTable::totalSize() const
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string DocComment::getInnerText(const PosTable & positions) const {
|
||||||
|
auto beginPos = positions[begin];
|
||||||
|
auto endPos = positions[end];
|
||||||
|
auto docCommentStr = beginPos.getSnippetUpTo(endPos).value_or("");
|
||||||
|
|
||||||
|
// Strip "/**" and "*/"
|
||||||
|
constexpr size_t prefixLen = 3;
|
||||||
|
constexpr size_t suffixLen = 2;
|
||||||
|
std::string docStr = docCommentStr.substr(prefixLen, docCommentStr.size() - prefixLen - suffixLen);
|
||||||
|
if (docStr.empty())
|
||||||
|
return {};
|
||||||
|
// Turn the now missing "/**" into indentation
|
||||||
|
docStr = " " + docStr;
|
||||||
|
// Strip indentation (for the whole, potentially multi-line string)
|
||||||
|
docStr = stripIndentation(docStr);
|
||||||
|
return docStr;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,21 +6,58 @@
|
||||||
|
|
||||||
#include "value.hh"
|
#include "value.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "error.hh"
|
|
||||||
#include "position.hh"
|
|
||||||
#include "eval-error.hh"
|
#include "eval-error.hh"
|
||||||
#include "pos-idx.hh"
|
#include "pos-idx.hh"
|
||||||
#include "pos-table.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
struct Env;
|
|
||||||
struct Value;
|
|
||||||
class EvalState;
|
class EvalState;
|
||||||
|
class PosTable;
|
||||||
|
struct Env;
|
||||||
struct ExprWith;
|
struct ExprWith;
|
||||||
struct StaticEnv;
|
struct StaticEnv;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A documentation comment, in the sense of [RFC 145](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md)
|
||||||
|
*
|
||||||
|
* Note that this does not implement the following:
|
||||||
|
* - argument attribute names ("formals"): TBD
|
||||||
|
* - argument names: these are internal to the function and their names may not be optimal for documentation
|
||||||
|
* - function arity (degree of currying or number of ':'s):
|
||||||
|
* - Functions returning partially applied functions have a higher arity
|
||||||
|
* than can be determined locally and without evaluation.
|
||||||
|
* We do not want to present false data.
|
||||||
|
* - Some functions should be thought of as transformations of other
|
||||||
|
* functions. For instance `overlay -> overlay -> overlay` is the simplest
|
||||||
|
* way to understand `composeExtensions`, but its implementation looks like
|
||||||
|
* `f: g: final: prev: <...>`. The parameters `final` and `prev` are part
|
||||||
|
* of the overlay concept, while distracting from the function's purpose.
|
||||||
|
*/
|
||||||
|
struct DocComment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start of the comment, including the opening, ie `/` and `**`.
|
||||||
|
*/
|
||||||
|
PosIdx begin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position right after the final asterisk and `/` that terminate the comment.
|
||||||
|
*/
|
||||||
|
PosIdx end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the comment is set.
|
||||||
|
*
|
||||||
|
* A `DocComment` is small enough that it makes sense to pass by value, and
|
||||||
|
* therefore baking optionality into it is also useful, to avoiding the memory
|
||||||
|
* overhead of `std::optional`.
|
||||||
|
*/
|
||||||
|
operator bool() const { return static_cast<bool>(begin); }
|
||||||
|
|
||||||
|
std::string getInnerText(const PosTable & positions) const;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An attribute path is a sequence of attribute names.
|
* An attribute path is a sequence of attribute names.
|
||||||
|
@ -57,6 +94,7 @@ struct Expr
|
||||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||||
virtual void setName(Symbol name);
|
virtual void setName(Symbol name);
|
||||||
|
virtual void setDocComment(DocComment docComment) { };
|
||||||
virtual PosIdx getPos() const { return noPos; }
|
virtual PosIdx getPos() const { return noPos; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,6 +197,17 @@ struct ExprSelect : Expr
|
||||||
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
|
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
|
||||||
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
||||||
PosIdx getPos() const override { return pos; }
|
PosIdx getPos() const override { return pos; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
|
||||||
|
*
|
||||||
|
* @param[out] v The attribute set that should contain the last attribute name (if it exists).
|
||||||
|
* @return The last attribute name in `attrPath`
|
||||||
|
*
|
||||||
|
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist.
|
||||||
|
*/
|
||||||
|
Symbol evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs);
|
||||||
|
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -281,6 +330,8 @@ struct ExprLambda : Expr
|
||||||
Symbol arg;
|
Symbol arg;
|
||||||
Formals * formals;
|
Formals * formals;
|
||||||
Expr * body;
|
Expr * body;
|
||||||
|
DocComment docComment;
|
||||||
|
|
||||||
ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
|
ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
|
||||||
: pos(pos), arg(arg), formals(formals), body(body)
|
: pos(pos), arg(arg), formals(formals), body(body)
|
||||||
{
|
{
|
||||||
|
@ -293,6 +344,7 @@ struct ExprLambda : Expr
|
||||||
std::string showNamePos(const EvalState & state) const;
|
std::string showNamePos(const EvalState & state) const;
|
||||||
inline bool hasFormals() const { return formals != nullptr; }
|
inline bool hasFormals() const { return formals != nullptr; }
|
||||||
PosIdx getPos() const override { return pos; }
|
PosIdx getPos() const override { return pos; }
|
||||||
|
virtual void setDocComment(DocComment docComment) override;
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -20,25 +22,59 @@ struct StringToken
|
||||||
|
|
||||||
struct ParserLocation
|
struct ParserLocation
|
||||||
{
|
{
|
||||||
int first_line, first_column;
|
int beginOffset;
|
||||||
int last_line, last_column;
|
int endOffset;
|
||||||
|
|
||||||
// backup to recover from yyless(0)
|
// backup to recover from yyless(0)
|
||||||
int stashed_first_column, stashed_last_column;
|
int stashedBeginOffset, stashedEndOffset;
|
||||||
|
|
||||||
void stash() {
|
void stash() {
|
||||||
stashed_first_column = first_column;
|
stashedBeginOffset = beginOffset;
|
||||||
stashed_last_column = last_column;
|
stashedEndOffset = endOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void unstash() {
|
void unstash() {
|
||||||
first_column = stashed_first_column;
|
beginOffset = stashedBeginOffset;
|
||||||
last_column = stashed_last_column;
|
endOffset = stashedEndOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Latest doc comment position, or 0. */
|
||||||
|
int doc_comment_first_column, doc_comment_last_column;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LexerState
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tracks the distance to the last doc comment, in terms of lexer tokens.
|
||||||
|
*
|
||||||
|
* The lexer sets this to 0 when reading a doc comment, and increments it
|
||||||
|
* for every matched rule; see `lexer-helpers.cc`.
|
||||||
|
* Whitespace and comment rules decrement the distance, so that they result
|
||||||
|
* in a net 0 change in distance.
|
||||||
|
*/
|
||||||
|
int docCommentDistance = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The location of the last doc comment.
|
||||||
|
*
|
||||||
|
* (stashing fields are not used)
|
||||||
|
*/
|
||||||
|
ParserLocation lastDocCommentLoc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maps some positions to a DocComment, where the comment is relevant to the location.
|
||||||
|
*/
|
||||||
|
std::map<PosIdx, DocComment> & positionToDocComment;
|
||||||
|
|
||||||
|
PosTable & positions;
|
||||||
|
PosTable::Origin origin;
|
||||||
|
|
||||||
|
PosIdx at(const ParserLocation & loc);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ParserState
|
struct ParserState
|
||||||
{
|
{
|
||||||
|
const LexerState & lexerState;
|
||||||
SymbolTable & symbols;
|
SymbolTable & symbols;
|
||||||
PosTable & positions;
|
PosTable & positions;
|
||||||
Expr * result;
|
Expr * result;
|
||||||
|
@ -270,9 +306,14 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
|
||||||
return new ExprConcatStrings(pos, true, es2);
|
return new ExprConcatStrings(pos, true, es2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline PosIdx LexerState::at(const ParserLocation & loc)
|
||||||
|
{
|
||||||
|
return positions.add(origin, loc.beginOffset);
|
||||||
|
}
|
||||||
|
|
||||||
inline PosIdx ParserState::at(const ParserLocation & loc)
|
inline PosIdx ParserState::at(const ParserLocation & loc)
|
||||||
{
|
{
|
||||||
return positions.add(origin, loc.first_column);
|
return positions.add(origin, loc.beginOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,25 @@
|
||||||
#define YY_DECL int yylex \
|
#define YY_DECL int yylex \
|
||||||
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParserState * state)
|
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParserState * state)
|
||||||
|
|
||||||
|
// For efficiency, we only track offsets; not line,column coordinates
|
||||||
|
# define YYLLOC_DEFAULT(Current, Rhs, N) \
|
||||||
|
do \
|
||||||
|
if (N) \
|
||||||
|
{ \
|
||||||
|
(Current).beginOffset = YYRHSLOC (Rhs, 1).beginOffset; \
|
||||||
|
(Current).endOffset = YYRHSLOC (Rhs, N).endOffset; \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
(Current).beginOffset = (Current).endOffset = \
|
||||||
|
YYRHSLOC (Rhs, 0).endOffset; \
|
||||||
|
} \
|
||||||
|
while (0)
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
typedef std::map<PosIdx, DocComment> DocCommentMap;
|
||||||
|
|
||||||
Expr * parseExprFromBuf(
|
Expr * parseExprFromBuf(
|
||||||
char * text,
|
char * text,
|
||||||
size_t length,
|
size_t length,
|
||||||
|
@ -41,6 +58,7 @@ Expr * parseExprFromBuf(
|
||||||
SymbolTable & symbols,
|
SymbolTable & symbols,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
PosTable & positions,
|
PosTable & positions,
|
||||||
|
DocCommentMap & docComments,
|
||||||
const ref<SourceAccessor> rootFS,
|
const ref<SourceAccessor> rootFS,
|
||||||
const Expr::AstSymbols & astSymbols);
|
const Expr::AstSymbols & astSymbols);
|
||||||
|
|
||||||
|
@ -65,8 +83,7 @@ using namespace nix;
|
||||||
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
|
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
|
||||||
{
|
{
|
||||||
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
|
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
|
||||||
loc->first_column = loc->last_column;
|
loc->beginOffset = loc->endOffset;
|
||||||
loc->first_line = loc->last_line;
|
|
||||||
}
|
}
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = HintFmt(error),
|
.msg = HintFmt(error),
|
||||||
|
@ -74,6 +91,14 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char *
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SET_DOC_POS(lambda, pos) setDocPosition(state->lexerState, lambda, state->at(pos))
|
||||||
|
static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, PosIdx start) {
|
||||||
|
auto it = lexerState.positionToDocComment.find(start);
|
||||||
|
if (it != lexerState.positionToDocComment.end()) {
|
||||||
|
lambda->setDocComment(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
@ -119,6 +144,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char *
|
||||||
%token IND_STRING_OPEN IND_STRING_CLOSE
|
%token IND_STRING_OPEN IND_STRING_CLOSE
|
||||||
%token ELLIPSIS
|
%token ELLIPSIS
|
||||||
|
|
||||||
|
|
||||||
%right IMPL
|
%right IMPL
|
||||||
%left OR
|
%left OR
|
||||||
%left AND
|
%left AND
|
||||||
|
@ -140,18 +166,28 @@ expr: expr_function;
|
||||||
|
|
||||||
expr_function
|
expr_function
|
||||||
: ID ':' expr_function
|
: ID ':' expr_function
|
||||||
{ $$ = new ExprLambda(CUR_POS, state->symbols.create($1), 0, $3); }
|
{ auto me = new ExprLambda(CUR_POS, state->symbols.create($1), 0, $3);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
|
}
|
||||||
| '{' formals '}' ':' expr_function
|
| '{' formals '}' ':' expr_function
|
||||||
{ $$ = new ExprLambda(CUR_POS, state->validateFormals($2), $5); }
|
{ auto me = new ExprLambda(CUR_POS, state->validateFormals($2), $5);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
|
}
|
||||||
| '{' formals '}' '@' ID ':' expr_function
|
| '{' formals '}' '@' ID ':' expr_function
|
||||||
{
|
{
|
||||||
auto arg = state->symbols.create($5);
|
auto arg = state->symbols.create($5);
|
||||||
$$ = new ExprLambda(CUR_POS, arg, state->validateFormals($2, CUR_POS, arg), $7);
|
auto me = new ExprLambda(CUR_POS, arg, state->validateFormals($2, CUR_POS, arg), $7);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
}
|
}
|
||||||
| ID '@' '{' formals '}' ':' expr_function
|
| ID '@' '{' formals '}' ':' expr_function
|
||||||
{
|
{
|
||||||
auto arg = state->symbols.create($1);
|
auto arg = state->symbols.create($1);
|
||||||
$$ = new ExprLambda(CUR_POS, arg, state->validateFormals($4, CUR_POS, arg), $7);
|
auto me = new ExprLambda(CUR_POS, arg, state->validateFormals($4, CUR_POS, arg), $7);
|
||||||
|
$$ = me;
|
||||||
|
SET_DOC_POS(me, @1);
|
||||||
}
|
}
|
||||||
| ASSERT expr ';' expr_function
|
| ASSERT expr ';' expr_function
|
||||||
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
|
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
|
||||||
|
@ -312,7 +348,22 @@ ind_string_parts
|
||||||
;
|
;
|
||||||
|
|
||||||
binds
|
binds
|
||||||
: binds attrpath '=' expr ';' { $$ = $1; state->addAttr($$, std::move(*$2), $4, state->at(@2)); delete $2; }
|
: binds attrpath '=' expr ';' {
|
||||||
|
$$ = $1;
|
||||||
|
|
||||||
|
auto pos = state->at(@2);
|
||||||
|
auto exprPos = state->at(@4);
|
||||||
|
{
|
||||||
|
auto it = state->lexerState.positionToDocComment.find(pos);
|
||||||
|
if (it != state->lexerState.positionToDocComment.end()) {
|
||||||
|
$4->setDocComment(it->second);
|
||||||
|
state->lexerState.positionToDocComment.emplace(exprPos, it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state->addAttr($$, std::move(*$2), $4, pos);
|
||||||
|
delete $2;
|
||||||
|
}
|
||||||
| binds INHERIT attrs ';'
|
| binds INHERIT attrs ';'
|
||||||
{ $$ = $1;
|
{ $$ = $1;
|
||||||
for (auto & [i, iPos] : *$3) {
|
for (auto & [i, iPos] : *$3) {
|
||||||
|
@ -431,21 +482,28 @@ Expr * parseExprFromBuf(
|
||||||
SymbolTable & symbols,
|
SymbolTable & symbols,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
PosTable & positions,
|
PosTable & positions,
|
||||||
|
DocCommentMap & docComments,
|
||||||
const ref<SourceAccessor> rootFS,
|
const ref<SourceAccessor> rootFS,
|
||||||
const Expr::AstSymbols & astSymbols)
|
const Expr::AstSymbols & astSymbols)
|
||||||
{
|
{
|
||||||
yyscan_t scanner;
|
yyscan_t scanner;
|
||||||
|
LexerState lexerState {
|
||||||
|
.positionToDocComment = docComments,
|
||||||
|
.positions = positions,
|
||||||
|
.origin = positions.addOrigin(origin, length),
|
||||||
|
};
|
||||||
ParserState state {
|
ParserState state {
|
||||||
|
.lexerState = lexerState,
|
||||||
.symbols = symbols,
|
.symbols = symbols,
|
||||||
.positions = positions,
|
.positions = positions,
|
||||||
.basePath = basePath,
|
.basePath = basePath,
|
||||||
.origin = positions.addOrigin(origin, length),
|
.origin = lexerState.origin,
|
||||||
.rootFS = rootFS,
|
.rootFS = rootFS,
|
||||||
.s = astSymbols,
|
.s = astSymbols,
|
||||||
.settings = settings,
|
.settings = settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
yylex_init(&scanner);
|
yylex_init_extra(&lexerState, &scanner);
|
||||||
Finally _destroy([&] { yylex_destroy(scanner); });
|
Finally _destroy([&] { yylex_destroy(scanner); });
|
||||||
|
|
||||||
yy_scan_buffer(text, length, scanner);
|
yy_scan_buffer(text, length, scanner);
|
||||||
|
|
|
@ -28,20 +28,15 @@ public:
|
||||||
return id > 0;
|
return id > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(const PosIdx other) const
|
auto operator<=>(const PosIdx other) const
|
||||||
{
|
{
|
||||||
return id < other.id;
|
return id <=> other.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const PosIdx other) const
|
bool operator==(const PosIdx other) const
|
||||||
{
|
{
|
||||||
return id == other.id;
|
return id == other.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator!=(const PosIdx other) const
|
|
||||||
{
|
|
||||||
return id != other.id;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline PosIdx noPos = {};
|
inline PosIdx noPos = {};
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cstdint>
|
||||||
#include <numeric>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "chunked-vector.hh"
|
|
||||||
#include "pos-idx.hh"
|
#include "pos-idx.hh"
|
||||||
#include "position.hh"
|
#include "position.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#include "archive.hh"
|
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "downstream-placeholder.hh"
|
#include "downstream-placeholder.hh"
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
|
@ -1225,7 +1224,7 @@ static void derivationStrictInternal(
|
||||||
|
|
||||||
for (auto & i : attrs->lexicographicOrder(state.symbols)) {
|
for (auto & i : attrs->lexicographicOrder(state.symbols)) {
|
||||||
if (i->name == state.sIgnoreNulls) continue;
|
if (i->name == state.sIgnoreNulls) continue;
|
||||||
const std::string & key = state.symbols[i->name];
|
auto key = state.symbols[i->name];
|
||||||
vomit("processing attribute '%1%'", key);
|
vomit("processing attribute '%1%'", key);
|
||||||
|
|
||||||
auto handleHashMode = [&](const std::string_view s) {
|
auto handleHashMode = [&](const std::string_view s) {
|
||||||
|
@ -1309,7 +1308,7 @@ static void derivationStrictInternal(
|
||||||
|
|
||||||
if (i->name == state.sStructuredAttrs) continue;
|
if (i->name == state.sStructuredAttrs) continue;
|
||||||
|
|
||||||
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
|
jsonObject->emplace(key, printValueAsJSON(state, true, *i->value, pos, context));
|
||||||
|
|
||||||
if (i->name == state.sBuilder)
|
if (i->name == state.sBuilder)
|
||||||
drv.builder = state.forceString(*i->value, context, pos, context_below);
|
drv.builder = state.forceString(*i->value, context, pos, context_below);
|
||||||
|
|
|
@ -62,7 +62,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
attrs.insert_or_assign("name", std::string(name));
|
attrs.insert_or_assign("name", std::string(name));
|
||||||
if (ref) attrs.insert_or_assign("ref", *ref);
|
if (ref) attrs.insert_or_assign("ref", *ref);
|
||||||
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
|
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
|
||||||
auto input = fetchers::Input::fromAttrs(std::move(attrs));
|
auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||||
|
|
||||||
auto [storePath, input2] = input.fetchToStore(state.store);
|
auto [storePath, input2] = input.fetchToStore(state.store);
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ static void fetchTree(
|
||||||
Value & v,
|
Value & v,
|
||||||
const FetchTreeParams & params = FetchTreeParams{}
|
const FetchTreeParams & params = FetchTreeParams{}
|
||||||
) {
|
) {
|
||||||
fetchers::Input input;
|
fetchers::Input input { state.fetchSettings };
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
std::optional<std::string> type;
|
std::optional<std::string> type;
|
||||||
if (params.isFetchGit) type = "git";
|
if (params.isFetchGit) type = "git";
|
||||||
|
@ -148,7 +148,7 @@ static void fetchTree(
|
||||||
"attribute 'name' isn’t supported in call to 'fetchTree'"
|
"attribute 'name' isn’t supported in call to 'fetchTree'"
|
||||||
).atPos(pos).debugThrow();
|
).atPos(pos).debugThrow();
|
||||||
|
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||||
} else {
|
} else {
|
||||||
auto url = state.coerceToString(pos, *args[0], context,
|
auto url = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the first argument passed to the fetcher",
|
"while evaluating the first argument passed to the fetcher",
|
||||||
|
@ -161,9 +161,9 @@ static void fetchTree(
|
||||||
if (!attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
|
if (!attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
|
||||||
attrs.emplace("exportIgnore", Explicit<bool>{true});
|
attrs.emplace("exportIgnore", Explicit<bool>{true});
|
||||||
}
|
}
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||||
} else {
|
} else {
|
||||||
input = fetchers::Input::fromURL(url);
|
input = fetchers::Input::fromURL(state.fetchSettings, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ public:
|
||||||
return *s == s2;
|
return *s == s2;
|
||||||
}
|
}
|
||||||
|
|
||||||
operator const std::string & () const
|
const char * c_str() const
|
||||||
{
|
{
|
||||||
return *s;
|
return s->c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
operator const std::string_view () const
|
operator const std::string_view () const
|
||||||
|
@ -41,6 +41,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
|
friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol);
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return s->empty();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,9 +67,8 @@ public:
|
||||||
|
|
||||||
explicit operator bool() const { return id > 0; }
|
explicit operator bool() const { return id > 0; }
|
||||||
|
|
||||||
bool operator<(const Symbol other) const { return id < other.id; }
|
auto operator<=>(const Symbol other) const { return id <=> other.id; }
|
||||||
bool operator==(const Symbol other) const { return id == other.id; }
|
bool operator==(const Symbol other) const { return id == other.id; }
|
||||||
bool operator!=(const Symbol other) const { return id != other.id; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -58,7 +58,7 @@ json printValueAsJSON(EvalState & state, bool strict,
|
||||||
out = json::object();
|
out = json::object();
|
||||||
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
|
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
|
||||||
try {
|
try {
|
||||||
out[state.symbols[a->name]] = printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore);
|
out.emplace(state.symbols[a->name], printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(state.positions[a->pos],
|
e.addTrace(state.positions[a->pos],
|
||||||
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
|
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
static XMLAttrs singletonAttrs(const std::string & name, const std::string & value)
|
static XMLAttrs singletonAttrs(const std::string & name, std::string_view value)
|
||||||
{
|
{
|
||||||
XMLAttrs attrs;
|
XMLAttrs attrs;
|
||||||
attrs[name] = value;
|
attrs[name] = value;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <climits>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
|
@ -112,7 +111,7 @@ class ExternalValueBase
|
||||||
* Compare to another value of the same type. Defaults to uncomparable,
|
* Compare to another value of the same type. Defaults to uncomparable,
|
||||||
* i.e. always false.
|
* i.e. always false.
|
||||||
*/
|
*/
|
||||||
virtual bool operator ==(const ExternalValueBase & b) const;
|
virtual bool operator ==(const ExternalValueBase & b) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print the value as JSON. Defaults to unconvertable, i.e. throws an error
|
* Print the value as JSON. Defaults to unconvertable, i.e. throws an error
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
#include "fetch-settings.hh"
|
#include "fetch-settings.hh"
|
||||||
#include "config-global.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix::fetchers {
|
||||||
|
|
||||||
FetchSettings::FetchSettings()
|
Settings::Settings()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
FetchSettings fetchSettings;
|
|
||||||
|
|
||||||
static GlobalConfig::Register rFetchSettings(&fetchSettings);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix::fetchers {
|
||||||
|
|
||||||
struct FetchSettings : public Config
|
struct Settings : public Config
|
||||||
{
|
{
|
||||||
FetchSettings();
|
Settings();
|
||||||
|
|
||||||
Setting<StringMap> accessTokens{this, {}, "access-tokens",
|
Setting<StringMap> accessTokens{this, {}, "access-tokens",
|
||||||
R"(
|
R"(
|
||||||
|
@ -84,9 +84,14 @@ struct FetchSettings : public Config
|
||||||
`narHash` attribute is specified,
|
`narHash` attribute is specified,
|
||||||
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`.
|
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`.
|
||||||
)"};
|
)"};
|
||||||
|
|
||||||
|
Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry",
|
||||||
|
R"(
|
||||||
|
Path or URI of the global flake registry.
|
||||||
|
|
||||||
|
When empty, disables the global flake registry.
|
||||||
|
)",
|
||||||
|
{}, true};
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: don't use a global variable.
|
|
||||||
extern FetchSettings fetchSettings;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,11 @@ nlohmann::json dumpRegisterInputSchemeInfo() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Input Input::fromURL(const std::string & url, bool requireTree)
|
Input Input::fromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const std::string & url, bool requireTree)
|
||||||
{
|
{
|
||||||
return fromURL(parseURL(url), requireTree);
|
return fromURL(settings, parseURL(url), requireTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fixupInput(Input & input)
|
static void fixupInput(Input & input)
|
||||||
|
@ -49,10 +51,12 @@ static void fixupInput(Input & input)
|
||||||
input.getLastModified();
|
input.getLastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
Input Input::fromURL(const ParsedURL & url, bool requireTree)
|
Input Input::fromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree)
|
||||||
{
|
{
|
||||||
for (auto & [_, inputScheme] : *inputSchemes) {
|
for (auto & [_, inputScheme] : *inputSchemes) {
|
||||||
auto res = inputScheme->inputFromURL(url, requireTree);
|
auto res = inputScheme->inputFromURL(settings, url, requireTree);
|
||||||
if (res) {
|
if (res) {
|
||||||
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
|
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
|
||||||
res->scheme = inputScheme;
|
res->scheme = inputScheme;
|
||||||
|
@ -64,7 +68,7 @@ Input Input::fromURL(const ParsedURL & url, bool requireTree)
|
||||||
throw Error("input '%s' is unsupported", url.url);
|
throw Error("input '%s' is unsupported", url.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Input Input::fromAttrs(Attrs && attrs)
|
Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
|
||||||
{
|
{
|
||||||
auto schemeName = ({
|
auto schemeName = ({
|
||||||
auto schemeNameOpt = maybeGetStrAttr(attrs, "type");
|
auto schemeNameOpt = maybeGetStrAttr(attrs, "type");
|
||||||
|
@ -78,7 +82,7 @@ Input Input::fromAttrs(Attrs && attrs)
|
||||||
// but not all of them. Doing this is to support those other
|
// but not all of them. Doing this is to support those other
|
||||||
// operations which are supposed to be robust on
|
// operations which are supposed to be robust on
|
||||||
// unknown/uninterpretable inputs.
|
// unknown/uninterpretable inputs.
|
||||||
Input input;
|
Input input { settings };
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
fixupInput(input);
|
fixupInput(input);
|
||||||
return input;
|
return input;
|
||||||
|
@ -99,7 +103,7 @@ Input Input::fromAttrs(Attrs && attrs)
|
||||||
if (name != "type" && allowedAttrs.count(name) == 0)
|
if (name != "type" && allowedAttrs.count(name) == 0)
|
||||||
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
|
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
|
||||||
|
|
||||||
auto res = inputScheme->inputFromAttrs(attrs);
|
auto res = inputScheme->inputFromAttrs(settings, attrs);
|
||||||
if (!res) return raw();
|
if (!res) return raw();
|
||||||
res->scheme = inputScheme;
|
res->scheme = inputScheme;
|
||||||
fixupInput(*res);
|
fixupInput(*res);
|
||||||
|
@ -146,7 +150,7 @@ Attrs Input::toAttrs() const
|
||||||
return attrs;
|
return attrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Input::operator ==(const Input & other) const
|
bool Input::operator ==(const Input & other) const noexcept
|
||||||
{
|
{
|
||||||
return attrs == other.attrs;
|
return attrs == other.attrs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,16 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <nlohmann/json_fwd.hpp>
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
||||||
|
#include "ref.hh"
|
||||||
|
|
||||||
namespace nix { class Store; class StorePath; struct SourceAccessor; }
|
namespace nix { class Store; class StorePath; struct SourceAccessor; }
|
||||||
|
|
||||||
namespace nix::fetchers {
|
namespace nix::fetchers {
|
||||||
|
|
||||||
struct InputScheme;
|
struct InputScheme;
|
||||||
|
|
||||||
|
struct Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Input` object is generated by a specific fetcher, based on
|
* The `Input` object is generated by a specific fetcher, based on
|
||||||
* user-supplied information, and contains
|
* user-supplied information, and contains
|
||||||
|
@ -28,6 +32,12 @@ struct Input
|
||||||
{
|
{
|
||||||
friend struct InputScheme;
|
friend struct InputScheme;
|
||||||
|
|
||||||
|
const Settings * settings;
|
||||||
|
|
||||||
|
Input(const Settings & settings)
|
||||||
|
: settings{&settings}
|
||||||
|
{ }
|
||||||
|
|
||||||
std::shared_ptr<InputScheme> scheme; // note: can be null
|
std::shared_ptr<InputScheme> scheme; // note: can be null
|
||||||
Attrs attrs;
|
Attrs attrs;
|
||||||
|
|
||||||
|
@ -42,16 +52,22 @@ public:
|
||||||
*
|
*
|
||||||
* The URL indicate which sort of fetcher, and provides information to that fetcher.
|
* The URL indicate which sort of fetcher, and provides information to that fetcher.
|
||||||
*/
|
*/
|
||||||
static Input fromURL(const std::string & url, bool requireTree = true);
|
static Input fromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const std::string & url, bool requireTree = true);
|
||||||
|
|
||||||
static Input fromURL(const ParsedURL & url, bool requireTree = true);
|
static Input fromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an `Input` from a an `Attrs`.
|
* Create an `Input` from a an `Attrs`.
|
||||||
*
|
*
|
||||||
* The URL indicate which sort of fetcher, and provides information to that fetcher.
|
* The URL indicate which sort of fetcher, and provides information to that fetcher.
|
||||||
*/
|
*/
|
||||||
static Input fromAttrs(Attrs && attrs);
|
static Input fromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
Attrs && attrs);
|
||||||
|
|
||||||
ParsedURL toURL() const;
|
ParsedURL toURL() const;
|
||||||
|
|
||||||
|
@ -73,7 +89,7 @@ public:
|
||||||
*/
|
*/
|
||||||
bool isLocked() const;
|
bool isLocked() const;
|
||||||
|
|
||||||
bool operator ==(const Input & other) const;
|
bool operator ==(const Input & other) const noexcept;
|
||||||
|
|
||||||
bool contains(const Input & other) const;
|
bool contains(const Input & other) const;
|
||||||
|
|
||||||
|
@ -146,9 +162,13 @@ struct InputScheme
|
||||||
virtual ~InputScheme()
|
virtual ~InputScheme()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
virtual std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const = 0;
|
virtual std::optional<Input> inputFromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree) const = 0;
|
||||||
|
|
||||||
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;
|
virtual std::optional<Input> inputFromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
const Attrs & attrs) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What is the name of the scheme?
|
* What is the name of the scheme?
|
||||||
|
|
|
@ -115,10 +115,10 @@ git_oid hashToOID(const Hash & hash)
|
||||||
return oid;
|
return oid;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object lookupObject(git_repository * repo, const git_oid & oid)
|
Object lookupObject(git_repository * repo, const git_oid & oid, git_object_t type = GIT_OBJECT_ANY)
|
||||||
{
|
{
|
||||||
Object obj;
|
Object obj;
|
||||||
if (git_object_lookup(Setter(obj), repo, &oid, GIT_OBJECT_ANY)) {
|
if (git_object_lookup(Setter(obj), repo, &oid, type)) {
|
||||||
auto err = git_error_last();
|
auto err = git_error_last();
|
||||||
throw Error("getting Git object '%s': %s", oid, err->message);
|
throw Error("getting Git object '%s': %s", oid, err->message);
|
||||||
}
|
}
|
||||||
|
@ -909,6 +909,61 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
|
||||||
addToTree(*pathComponents.rbegin(), oid, GIT_FILEMODE_LINK);
|
addToTree(*pathComponents.rbegin(), oid, GIT_FILEMODE_LINK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void createHardlink(const CanonPath & path, const CanonPath & target) override
|
||||||
|
{
|
||||||
|
std::vector<std::string> pathComponents;
|
||||||
|
for (auto & c : path)
|
||||||
|
pathComponents.emplace_back(c);
|
||||||
|
|
||||||
|
if (!prepareDirs(pathComponents, false)) return;
|
||||||
|
|
||||||
|
// We can't just look up the path from the start of the root, since
|
||||||
|
// some parent directories may not have finished yet, so we compute
|
||||||
|
// a relative path that helps us find the right git_tree_builder or object.
|
||||||
|
auto relTarget = CanonPath(path).parent()->makeRelative(target);
|
||||||
|
|
||||||
|
auto dir = pendingDirs.rbegin();
|
||||||
|
|
||||||
|
// For each ../ component at the start, go up one directory.
|
||||||
|
// CanonPath::makeRelative() always puts all .. elements at the start,
|
||||||
|
// so they're all handled by this loop:
|
||||||
|
std::string_view relTargetLeft(relTarget);
|
||||||
|
while (hasPrefix(relTargetLeft, "../")) {
|
||||||
|
if (dir == pendingDirs.rend())
|
||||||
|
throw Error("invalid hard link target '%s' for path '%s'", target, path);
|
||||||
|
++dir;
|
||||||
|
relTargetLeft = relTargetLeft.substr(3);
|
||||||
|
}
|
||||||
|
if (dir == pendingDirs.rend())
|
||||||
|
throw Error("invalid hard link target '%s' for path '%s'", target, path);
|
||||||
|
|
||||||
|
// Look up the remainder of the target, starting at the
|
||||||
|
// top-most `git_treebuilder`.
|
||||||
|
std::variant<git_treebuilder *, git_oid> curDir{dir->builder.get()};
|
||||||
|
Object tree; // needed to keep `entry` alive
|
||||||
|
const git_tree_entry * entry = nullptr;
|
||||||
|
|
||||||
|
for (auto & c : CanonPath(relTargetLeft)) {
|
||||||
|
if (auto builder = std::get_if<git_treebuilder *>(&curDir)) {
|
||||||
|
assert(*builder);
|
||||||
|
if (!(entry = git_treebuilder_get(*builder, std::string(c).c_str())))
|
||||||
|
throw Error("cannot find hard link target '%s' for path '%s'", target, path);
|
||||||
|
curDir = *git_tree_entry_id(entry);
|
||||||
|
} else if (auto oid = std::get_if<git_oid>(&curDir)) {
|
||||||
|
tree = lookupObject(*repo, *oid, GIT_OBJECT_TREE);
|
||||||
|
if (!(entry = git_tree_entry_byname((const git_tree *) &*tree, std::string(c).c_str())))
|
||||||
|
throw Error("cannot find hard link target '%s' for path '%s'", target, path);
|
||||||
|
curDir = *git_tree_entry_id(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(entry);
|
||||||
|
|
||||||
|
addToTree(*pathComponents.rbegin(),
|
||||||
|
*git_tree_entry_id(entry),
|
||||||
|
git_tree_entry_filemode(entry));
|
||||||
|
}
|
||||||
|
|
||||||
Hash sync() override {
|
Hash sync() override {
|
||||||
updateBuilders({});
|
updateBuilders({});
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace nix {
|
||||||
|
|
||||||
namespace fetchers { struct PublicKey; }
|
namespace fetchers { struct PublicKey; }
|
||||||
|
|
||||||
struct GitFileSystemObjectSink : FileSystemObjectSink
|
struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Flush builder and return a final Git hash.
|
* Flush builder and return a final Git hash.
|
||||||
|
|
|
@ -164,7 +164,9 @@ static const Hash nullRev{HashAlgorithm::SHA1};
|
||||||
|
|
||||||
struct GitInputScheme : InputScheme
|
struct GitInputScheme : InputScheme
|
||||||
{
|
{
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
std::optional<Input> inputFromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree) const override
|
||||||
{
|
{
|
||||||
if (url.scheme != "git" &&
|
if (url.scheme != "git" &&
|
||||||
url.scheme != "git+http" &&
|
url.scheme != "git+http" &&
|
||||||
|
@ -190,7 +192,7 @@ struct GitInputScheme : InputScheme
|
||||||
|
|
||||||
attrs.emplace("url", url2.to_string());
|
attrs.emplace("url", url2.to_string());
|
||||||
|
|
||||||
return inputFromAttrs(attrs);
|
return inputFromAttrs(settings, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,7 +224,9 @@ struct GitInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
|
std::optional<Input> inputFromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
const Attrs & attrs) const override
|
||||||
{
|
{
|
||||||
for (auto & [name, _] : attrs)
|
for (auto & [name, _] : attrs)
|
||||||
if (name == "verifyCommit"
|
if (name == "verifyCommit"
|
||||||
|
@ -238,7 +242,7 @@ struct GitInputScheme : InputScheme
|
||||||
throw BadURL("invalid Git branch/tag name '%s'", *ref);
|
throw BadURL("invalid Git branch/tag name '%s'", *ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
auto url = fixGitURL(getStrAttr(attrs, "url"));
|
auto url = fixGitURL(getStrAttr(attrs, "url"));
|
||||||
parseURL(url);
|
parseURL(url);
|
||||||
|
@ -366,13 +370,13 @@ struct GitInputScheme : InputScheme
|
||||||
/* URL of the repo, or its path if isLocal. Never a `file` URL. */
|
/* URL of the repo, or its path if isLocal. Never a `file` URL. */
|
||||||
std::string url;
|
std::string url;
|
||||||
|
|
||||||
void warnDirty() const
|
void warnDirty(const Settings & settings) const
|
||||||
{
|
{
|
||||||
if (workdirInfo.isDirty) {
|
if (workdirInfo.isDirty) {
|
||||||
if (!fetchSettings.allowDirty)
|
if (!settings.allowDirty)
|
||||||
throw Error("Git tree '%s' is dirty", url);
|
throw Error("Git tree '%s' is dirty", url);
|
||||||
|
|
||||||
if (fetchSettings.warnDirty)
|
if (settings.warnDirty)
|
||||||
warn("Git tree '%s' is dirty", url);
|
warn("Git tree '%s' is dirty", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -653,7 +657,7 @@ struct GitInputScheme : InputScheme
|
||||||
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
|
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore });
|
||||||
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
|
attrs.insert_or_assign("submodules", Explicit<bool>{ true });
|
||||||
attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||||
auto submoduleInput = fetchers::Input::fromAttrs(std::move(attrs));
|
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||||
auto [submoduleAccessor, submoduleInput2] =
|
auto [submoduleAccessor, submoduleInput2] =
|
||||||
submoduleInput.getAccessor(store);
|
submoduleInput.getAccessor(store);
|
||||||
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
||||||
|
@ -711,7 +715,7 @@ struct GitInputScheme : InputScheme
|
||||||
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
|
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
|
||||||
// attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
// attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
|
||||||
|
|
||||||
auto submoduleInput = fetchers::Input::fromAttrs(std::move(attrs));
|
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
|
||||||
auto [submoduleAccessor, submoduleInput2] =
|
auto [submoduleAccessor, submoduleInput2] =
|
||||||
submoduleInput.getAccessor(store);
|
submoduleInput.getAccessor(store);
|
||||||
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
|
||||||
|
@ -743,7 +747,7 @@ struct GitInputScheme : InputScheme
|
||||||
|
|
||||||
verifyCommit(input, repo);
|
verifyCommit(input, repo);
|
||||||
} else {
|
} else {
|
||||||
repoInfo.warnDirty();
|
repoInfo.warnDirty(*input.settings);
|
||||||
|
|
||||||
if (repoInfo.workdirInfo.headRev) {
|
if (repoInfo.workdirInfo.headRev) {
|
||||||
input.attrs.insert_or_assign("dirtyRev",
|
input.attrs.insert_or_assign("dirtyRev",
|
||||||
|
|
|
@ -31,7 +31,9 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
{
|
{
|
||||||
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
|
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
|
||||||
|
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
std::optional<Input> inputFromURL(
|
||||||
|
const fetchers::Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree) const override
|
||||||
{
|
{
|
||||||
if (url.scheme != schemeName()) return {};
|
if (url.scheme != schemeName()) return {};
|
||||||
|
|
||||||
|
@ -90,7 +92,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
if (ref && rev)
|
if (ref && rev)
|
||||||
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev());
|
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev());
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs.insert_or_assign("type", std::string { schemeName() });
|
input.attrs.insert_or_assign("type", std::string { schemeName() });
|
||||||
input.attrs.insert_or_assign("owner", path[0]);
|
input.attrs.insert_or_assign("owner", path[0]);
|
||||||
input.attrs.insert_or_assign("repo", path[1]);
|
input.attrs.insert_or_assign("repo", path[1]);
|
||||||
|
@ -119,12 +121,14 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
|
std::optional<Input> inputFromAttrs(
|
||||||
|
const fetchers::Settings & settings,
|
||||||
|
const Attrs & attrs) const override
|
||||||
{
|
{
|
||||||
getStrAttr(attrs, "owner");
|
getStrAttr(attrs, "owner");
|
||||||
getStrAttr(attrs, "repo");
|
getStrAttr(attrs, "repo");
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
@ -168,18 +172,20 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> getAccessToken(const std::string & host) const
|
std::optional<std::string> getAccessToken(const fetchers::Settings & settings, const std::string & host) const
|
||||||
{
|
{
|
||||||
auto tokens = fetchSettings.accessTokens.get();
|
auto tokens = settings.accessTokens.get();
|
||||||
if (auto token = get(tokens, host))
|
if (auto token = get(tokens, host))
|
||||||
return *token;
|
return *token;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Headers makeHeadersWithAuthTokens(const std::string & host) const
|
Headers makeHeadersWithAuthTokens(
|
||||||
|
const fetchers::Settings & settings,
|
||||||
|
const std::string & host) const
|
||||||
{
|
{
|
||||||
Headers headers;
|
Headers headers;
|
||||||
auto accessToken = getAccessToken(host);
|
auto accessToken = getAccessToken(settings, host);
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
auto hdr = accessHeaderFromToken(*accessToken);
|
auto hdr = accessHeaderFromToken(*accessToken);
|
||||||
if (hdr)
|
if (hdr)
|
||||||
|
@ -295,7 +301,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
locking. FIXME: in the future, we may want to require a Git
|
locking. FIXME: in the future, we may want to require a Git
|
||||||
tree hash instead of a NAR hash. */
|
tree hash instead of a NAR hash. */
|
||||||
return input.getRev().has_value()
|
return input.getRev().has_value()
|
||||||
&& (fetchSettings.trustTarballsFromGitForges ||
|
&& (input.settings->trustTarballsFromGitForges ||
|
||||||
input.getNarHash().has_value());
|
input.getNarHash().has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +353,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||||
: "https://%s/api/v3/repos/%s/%s/commits/%s",
|
: "https://%s/api/v3/repos/%s/%s/commits/%s",
|
||||||
host, getOwner(input), getRepo(input), *input.getRef());
|
host, getOwner(input), getRepo(input), *input.getRef());
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(host);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
|
||||||
|
|
||||||
auto json = nlohmann::json::parse(
|
auto json = nlohmann::json::parse(
|
||||||
readFile(
|
readFile(
|
||||||
|
@ -364,7 +370,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||||
{
|
{
|
||||||
auto host = getHost(input);
|
auto host = getHost(input);
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(host);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
|
||||||
|
|
||||||
// If we have no auth headers then we default to the public archive
|
// If we have no auth headers then we default to the public archive
|
||||||
// urls so we do not run into rate limits.
|
// urls so we do not run into rate limits.
|
||||||
|
@ -384,7 +390,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||||
void clone(const Input & input, const Path & destDir) const override
|
void clone(const Input & input, const Path & destDir) const override
|
||||||
{
|
{
|
||||||
auto host = getHost(input);
|
auto host = getHost(input);
|
||||||
Input::fromURL(fmt("git+https://%s/%s/%s.git",
|
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git",
|
||||||
host, getOwner(input), getRepo(input)))
|
host, getOwner(input), getRepo(input)))
|
||||||
.applyOverrides(input.getRef(), input.getRev())
|
.applyOverrides(input.getRef(), input.getRev())
|
||||||
.clone(destDir);
|
.clone(destDir);
|
||||||
|
@ -421,7 +427,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||||
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/commits?ref_name=%s",
|
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/commits?ref_name=%s",
|
||||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(host);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
|
||||||
|
|
||||||
auto json = nlohmann::json::parse(
|
auto json = nlohmann::json::parse(
|
||||||
readFile(
|
readFile(
|
||||||
|
@ -451,7 +457,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||||
input.getRev()->to_string(HashFormat::Base16, false));
|
input.getRev()->to_string(HashFormat::Base16, false));
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(host);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
|
||||||
return DownloadUrl { url, headers };
|
return DownloadUrl { url, headers };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,7 +465,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||||
{
|
{
|
||||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||||
// FIXME: get username somewhere
|
// FIXME: get username somewhere
|
||||||
Input::fromURL(fmt("git+https://%s/%s/%s.git",
|
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git",
|
||||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||||
.applyOverrides(input.getRef(), input.getRev())
|
.applyOverrides(input.getRef(), input.getRev())
|
||||||
.clone(destDir);
|
.clone(destDir);
|
||||||
|
@ -491,7 +497,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
auto base_url = fmt("https://%s/%s/%s",
|
auto base_url = fmt("https://%s/%s/%s",
|
||||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"));
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"));
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(host);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
|
||||||
|
|
||||||
std::string refUri;
|
std::string refUri;
|
||||||
if (ref == "HEAD") {
|
if (ref == "HEAD") {
|
||||||
|
@ -538,14 +544,14 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||||
input.getRev()->to_string(HashFormat::Base16, false));
|
input.getRev()->to_string(HashFormat::Base16, false));
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(host);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host);
|
||||||
return DownloadUrl { url, headers };
|
return DownloadUrl { url, headers };
|
||||||
}
|
}
|
||||||
|
|
||||||
void clone(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");
|
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||||
Input::fromURL(fmt("git+https://%s/%s/%s",
|
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s",
|
||||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||||
.applyOverrides(input.getRef(), input.getRev())
|
.applyOverrides(input.getRef(), input.getRev())
|
||||||
.clone(destDir);
|
.clone(destDir);
|
||||||
|
|
|
@ -8,7 +8,9 @@ std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
|
||||||
|
|
||||||
struct IndirectInputScheme : InputScheme
|
struct IndirectInputScheme : InputScheme
|
||||||
{
|
{
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
std::optional<Input> inputFromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree) const override
|
||||||
{
|
{
|
||||||
if (url.scheme != "flake") return {};
|
if (url.scheme != "flake") return {};
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ struct IndirectInputScheme : InputScheme
|
||||||
|
|
||||||
// FIXME: forbid query params?
|
// FIXME: forbid query params?
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs.insert_or_assign("type", "indirect");
|
input.attrs.insert_or_assign("type", "indirect");
|
||||||
input.attrs.insert_or_assign("id", id);
|
input.attrs.insert_or_assign("id", id);
|
||||||
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
|
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
|
||||||
|
@ -65,13 +67,15 @@ struct IndirectInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
|
std::optional<Input> inputFromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
const Attrs & attrs) const override
|
||||||
{
|
{
|
||||||
auto id = getStrAttr(attrs, "id");
|
auto id = getStrAttr(attrs, "id");
|
||||||
if (!std::regex_match(id, flakeRegex))
|
if (!std::regex_match(id, flakeRegex))
|
||||||
throw BadURL("'%s' is not a valid flake ID", id);
|
throw BadURL("'%s' is not a valid flake ID", id);
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,9 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
|
||||||
|
|
||||||
struct MercurialInputScheme : InputScheme
|
struct MercurialInputScheme : InputScheme
|
||||||
{
|
{
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
std::optional<Input> inputFromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree) const override
|
||||||
{
|
{
|
||||||
if (url.scheme != "hg+http" &&
|
if (url.scheme != "hg+http" &&
|
||||||
url.scheme != "hg+https" &&
|
url.scheme != "hg+https" &&
|
||||||
|
@ -68,7 +70,7 @@ struct MercurialInputScheme : InputScheme
|
||||||
|
|
||||||
attrs.emplace("url", url2.to_string());
|
attrs.emplace("url", url2.to_string());
|
||||||
|
|
||||||
return inputFromAttrs(attrs);
|
return inputFromAttrs(settings, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view schemeName() const override
|
std::string_view schemeName() const override
|
||||||
|
@ -88,7 +90,9 @@ struct MercurialInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
|
std::optional<Input> inputFromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
const Attrs & attrs) const override
|
||||||
{
|
{
|
||||||
parseURL(getStrAttr(attrs, "url"));
|
parseURL(getStrAttr(attrs, "url"));
|
||||||
|
|
||||||
|
@ -97,7 +101,7 @@ struct MercurialInputScheme : InputScheme
|
||||||
throw BadURL("invalid Mercurial branch/tag name '%s'", *ref);
|
throw BadURL("invalid Mercurial branch/tag name '%s'", *ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
@ -182,10 +186,10 @@ struct MercurialInputScheme : InputScheme
|
||||||
/* This is an unclean working tree. So copy all tracked
|
/* This is an unclean working tree. So copy all tracked
|
||||||
files. */
|
files. */
|
||||||
|
|
||||||
if (!fetchSettings.allowDirty)
|
if (!input.settings->allowDirty)
|
||||||
throw Error("Mercurial tree '%s' is unclean", actualUrl);
|
throw Error("Mercurial tree '%s' is unclean", actualUrl);
|
||||||
|
|
||||||
if (fetchSettings.warnDirty)
|
if (input.settings->warnDirty)
|
||||||
warn("Mercurial tree '%s' is unclean", actualUrl);
|
warn("Mercurial tree '%s' is unclean", actualUrl);
|
||||||
|
|
||||||
input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl })));
|
input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl })));
|
||||||
|
|
|
@ -7,14 +7,16 @@ namespace nix::fetchers {
|
||||||
|
|
||||||
struct PathInputScheme : InputScheme
|
struct PathInputScheme : InputScheme
|
||||||
{
|
{
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
|
std::optional<Input> inputFromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & url, bool requireTree) const override
|
||||||
{
|
{
|
||||||
if (url.scheme != "path") return {};
|
if (url.scheme != "path") return {};
|
||||||
|
|
||||||
if (url.authority && *url.authority != "")
|
if (url.authority && *url.authority != "")
|
||||||
throw Error("path URL '%s' should not have an authority ('%s')", url.url, *url.authority);
|
throw Error("path URL '%s' should not have an authority ('%s')", url.url, *url.authority);
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs.insert_or_assign("type", "path");
|
input.attrs.insert_or_assign("type", "path");
|
||||||
input.attrs.insert_or_assign("path", url.path);
|
input.attrs.insert_or_assign("path", url.path);
|
||||||
|
|
||||||
|
@ -54,11 +56,13 @@ struct PathInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
|
std::optional<Input> inputFromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
const Attrs & attrs) const override
|
||||||
{
|
{
|
||||||
getStrAttr(attrs, "path");
|
getStrAttr(attrs, "path");
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
#include "fetch-settings.hh"
|
||||||
#include "registry.hh"
|
#include "registry.hh"
|
||||||
#include "tarball.hh"
|
#include "tarball.hh"
|
||||||
#include "users.hh"
|
#include "users.hh"
|
||||||
#include "config-global.hh"
|
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
|
@ -11,12 +11,13 @@
|
||||||
namespace nix::fetchers {
|
namespace nix::fetchers {
|
||||||
|
|
||||||
std::shared_ptr<Registry> Registry::read(
|
std::shared_ptr<Registry> Registry::read(
|
||||||
|
const Settings & settings,
|
||||||
const Path & path, RegistryType type)
|
const Path & path, RegistryType type)
|
||||||
{
|
{
|
||||||
auto registry = std::make_shared<Registry>(type);
|
auto registry = std::make_shared<Registry>(settings, type);
|
||||||
|
|
||||||
if (!pathExists(path))
|
if (!pathExists(path))
|
||||||
return std::make_shared<Registry>(type);
|
return std::make_shared<Registry>(settings, type);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -36,8 +37,8 @@ std::shared_ptr<Registry> Registry::read(
|
||||||
auto exact = i.find("exact");
|
auto exact = i.find("exact");
|
||||||
registry->entries.push_back(
|
registry->entries.push_back(
|
||||||
Entry {
|
Entry {
|
||||||
.from = Input::fromAttrs(jsonToAttrs(i["from"])),
|
.from = Input::fromAttrs(settings, jsonToAttrs(i["from"])),
|
||||||
.to = Input::fromAttrs(std::move(toAttrs)),
|
.to = Input::fromAttrs(settings, std::move(toAttrs)),
|
||||||
.extraAttrs = extraAttrs,
|
.extraAttrs = extraAttrs,
|
||||||
.exact = exact != i.end() && exact.value()
|
.exact = exact != i.end() && exact.value()
|
||||||
});
|
});
|
||||||
|
@ -106,10 +107,10 @@ static Path getSystemRegistryPath()
|
||||||
return settings.nixConfDir + "/registry.json";
|
return settings.nixConfDir + "/registry.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<Registry> getSystemRegistry()
|
static std::shared_ptr<Registry> getSystemRegistry(const Settings & settings)
|
||||||
{
|
{
|
||||||
static auto systemRegistry =
|
static auto systemRegistry =
|
||||||
Registry::read(getSystemRegistryPath(), Registry::System);
|
Registry::read(settings, getSystemRegistryPath(), Registry::System);
|
||||||
return systemRegistry;
|
return systemRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,25 +119,24 @@ Path getUserRegistryPath()
|
||||||
return getConfigDir() + "/nix/registry.json";
|
return getConfigDir() + "/nix/registry.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Registry> getUserRegistry()
|
std::shared_ptr<Registry> getUserRegistry(const Settings & settings)
|
||||||
{
|
{
|
||||||
static auto userRegistry =
|
static auto userRegistry =
|
||||||
Registry::read(getUserRegistryPath(), Registry::User);
|
Registry::read(settings, getUserRegistryPath(), Registry::User);
|
||||||
return userRegistry;
|
return userRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Registry> getCustomRegistry(const Path & p)
|
std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Path & p)
|
||||||
{
|
{
|
||||||
static auto customRegistry =
|
static auto customRegistry =
|
||||||
Registry::read(p, Registry::Custom);
|
Registry::read(settings, p, Registry::Custom);
|
||||||
return customRegistry;
|
return customRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<Registry> flagRegistry =
|
std::shared_ptr<Registry> getFlagRegistry(const Settings & settings)
|
||||||
std::make_shared<Registry>(Registry::Flag);
|
|
||||||
|
|
||||||
std::shared_ptr<Registry> getFlagRegistry()
|
|
||||||
{
|
{
|
||||||
|
static auto flagRegistry =
|
||||||
|
std::make_shared<Registry>(settings, Registry::Flag);
|
||||||
return flagRegistry;
|
return flagRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,30 +145,15 @@ void overrideRegistry(
|
||||||
const Input & to,
|
const Input & to,
|
||||||
const Attrs & extraAttrs)
|
const Attrs & extraAttrs)
|
||||||
{
|
{
|
||||||
flagRegistry->add(from, to, extraAttrs);
|
getFlagRegistry(*from.settings)->add(from, to, extraAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RegistrySettings : Config
|
static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, ref<Store> store)
|
||||||
{
|
|
||||||
Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry",
|
|
||||||
R"(
|
|
||||||
Path or URI of the global flake registry.
|
|
||||||
|
|
||||||
When empty, disables the global flake registry.
|
|
||||||
)",
|
|
||||||
{}, true};
|
|
||||||
};
|
|
||||||
|
|
||||||
RegistrySettings registrySettings;
|
|
||||||
|
|
||||||
static GlobalConfig::Register rRegistrySettings(®istrySettings);
|
|
||||||
|
|
||||||
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
|
||||||
{
|
{
|
||||||
static auto reg = [&]() {
|
static auto reg = [&]() {
|
||||||
auto path = registrySettings.flakeRegistry.get();
|
auto path = settings.flakeRegistry.get();
|
||||||
if (path == "") {
|
if (path == "") {
|
||||||
return std::make_shared<Registry>(Registry::Global); // empty registry
|
return std::make_shared<Registry>(settings, Registry::Global); // empty registry
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasPrefix(path, "/")) {
|
if (!hasPrefix(path, "/")) {
|
||||||
|
@ -178,19 +163,19 @@ static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
||||||
path = store->toRealPath(storePath);
|
path = store->toRealPath(storePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Registry::read(path, Registry::Global);
|
return Registry::read(settings, path, Registry::Global);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
return reg;
|
return reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Registries getRegistries(ref<Store> store)
|
Registries getRegistries(const Settings & settings, ref<Store> store)
|
||||||
{
|
{
|
||||||
Registries registries;
|
Registries registries;
|
||||||
registries.push_back(getFlagRegistry());
|
registries.push_back(getFlagRegistry(settings));
|
||||||
registries.push_back(getUserRegistry());
|
registries.push_back(getUserRegistry(settings));
|
||||||
registries.push_back(getSystemRegistry());
|
registries.push_back(getSystemRegistry(settings));
|
||||||
registries.push_back(getGlobalRegistry(store));
|
registries.push_back(getGlobalRegistry(settings, store));
|
||||||
return registries;
|
return registries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +192,7 @@ std::pair<Input, Attrs> lookupInRegistries(
|
||||||
n++;
|
n++;
|
||||||
if (n > 100) throw Error("cycle detected in flake registry for '%s'", input.to_string());
|
if (n > 100) throw Error("cycle detected in flake registry for '%s'", input.to_string());
|
||||||
|
|
||||||
for (auto & registry : getRegistries(store)) {
|
for (auto & registry : getRegistries(*input.settings, store)) {
|
||||||
// FIXME: O(n)
|
// FIXME: O(n)
|
||||||
for (auto & entry : registry->entries) {
|
for (auto & entry : registry->entries) {
|
||||||
if (entry.exact) {
|
if (entry.exact) {
|
||||||
|
|
|
@ -10,6 +10,8 @@ namespace nix::fetchers {
|
||||||
|
|
||||||
struct Registry
|
struct Registry
|
||||||
{
|
{
|
||||||
|
const Settings & settings;
|
||||||
|
|
||||||
enum RegistryType {
|
enum RegistryType {
|
||||||
Flag = 0,
|
Flag = 0,
|
||||||
User = 1,
|
User = 1,
|
||||||
|
@ -29,11 +31,13 @@ struct Registry
|
||||||
|
|
||||||
std::vector<Entry> entries;
|
std::vector<Entry> entries;
|
||||||
|
|
||||||
Registry(RegistryType type)
|
Registry(const Settings & settings, RegistryType type)
|
||||||
: type(type)
|
: settings{settings}
|
||||||
|
, type{type}
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
static std::shared_ptr<Registry> read(
|
static std::shared_ptr<Registry> read(
|
||||||
|
const Settings & settings,
|
||||||
const Path & path, RegistryType type);
|
const Path & path, RegistryType type);
|
||||||
|
|
||||||
void write(const Path & path);
|
void write(const Path & path);
|
||||||
|
@ -48,13 +52,13 @@ struct Registry
|
||||||
|
|
||||||
typedef std::vector<std::shared_ptr<Registry>> Registries;
|
typedef std::vector<std::shared_ptr<Registry>> Registries;
|
||||||
|
|
||||||
std::shared_ptr<Registry> getUserRegistry();
|
std::shared_ptr<Registry> getUserRegistry(const Settings & settings);
|
||||||
|
|
||||||
std::shared_ptr<Registry> getCustomRegistry(const Path & p);
|
std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Path & p);
|
||||||
|
|
||||||
Path getUserRegistryPath();
|
Path getUserRegistryPath();
|
||||||
|
|
||||||
Registries getRegistries(ref<Store> store);
|
Registries getRegistries(const Settings & settings, ref<Store> store);
|
||||||
|
|
||||||
void overrideRegistry(
|
void overrideRegistry(
|
||||||
const Input & from,
|
const Input & from,
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "cache.hh"
|
#include "cache.hh"
|
||||||
#include "filetransfer.hh"
|
#include "filetransfer.hh"
|
||||||
#include "globals.hh"
|
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "tarfile.hh"
|
#include "tarfile.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "split.hh"
|
|
||||||
#include "store-path-accessor.hh"
|
#include "store-path-accessor.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "git-utils.hh"
|
#include "git-utils.hh"
|
||||||
|
@ -214,12 +212,14 @@ struct CurlInputScheme : InputScheme
|
||||||
|
|
||||||
static const std::set<std::string> specialParams;
|
static const std::set<std::string> specialParams;
|
||||||
|
|
||||||
std::optional<Input> inputFromURL(const ParsedURL & _url, bool requireTree) const override
|
std::optional<Input> inputFromURL(
|
||||||
|
const Settings & settings,
|
||||||
|
const ParsedURL & _url, bool requireTree) const override
|
||||||
{
|
{
|
||||||
if (!isValidURL(_url, requireTree))
|
if (!isValidURL(_url, requireTree))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
Input input;
|
Input input{settings};
|
||||||
|
|
||||||
auto url = _url;
|
auto url = _url;
|
||||||
|
|
||||||
|
@ -267,9 +267,11 @@ struct CurlInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
|
std::optional<Input> inputFromAttrs(
|
||||||
|
const Settings & settings,
|
||||||
|
const Attrs & attrs) const override
|
||||||
{
|
{
|
||||||
Input input;
|
Input input{settings};
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
|
|
||||||
//input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
|
//input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
|
||||||
|
@ -349,7 +351,7 @@ struct TarballInputScheme : CurlInputScheme
|
||||||
result.accessor->setPathDisplay("«" + input.to_string() + "»");
|
result.accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||||
|
|
||||||
if (result.immutableUrl) {
|
if (result.immutableUrl) {
|
||||||
auto immutableInput = Input::fromURL(*result.immutableUrl);
|
auto immutableInput = Input::fromURL(*input.settings, *result.immutableUrl);
|
||||||
// FIXME: would be nice to support arbitrary flakerefs
|
// FIXME: would be nice to support arbitrary flakerefs
|
||||||
// here, e.g. git flakes.
|
// here, e.g. git flakes.
|
||||||
if (immutableInput.getType() != "tarball")
|
if (immutableInput.getType() != "tarball")
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "types.hh"
|
|
||||||
#include "path.hh"
|
|
||||||
#include "hash.hh"
|
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
#include "hash.hh"
|
||||||
|
#include "path.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
class Store;
|
class Store;
|
||||||
struct SourceAccessor;
|
struct SourceAccessor;
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
#include "flake-settings.hh"
|
|
||||||
#include "config-global.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
|
||||||
|
|
||||||
FlakeSettings::FlakeSettings() {}
|
|
||||||
|
|
||||||
FlakeSettings flakeSettings;
|
|
||||||
|
|
||||||
static GlobalConfig::Register rFlakeSettings(&flakeSettings);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "users.hh"
|
#include "users.hh"
|
||||||
#include "config-global.hh"
|
#include "config-global.hh"
|
||||||
#include "flake-settings.hh"
|
#include "flake/settings.hh"
|
||||||
#include "flake.hh"
|
#include "flake.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
@ -30,7 +30,7 @@ static void writeTrustedList(const TrustedList & trustedList)
|
||||||
writeFile(path, nlohmann::json(trustedList).dump());
|
writeFile(path, nlohmann::json(trustedList).dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigFile::apply()
|
void ConfigFile::apply(const Settings & flakeSettings)
|
||||||
{
|
{
|
||||||
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-prefix", "bash-prompt-suffix", "flake-registry", "commit-lock-file-summary", "commit-lockfile-summary"};
|
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-prefix", "bash-prompt-suffix", "flake-registry", "commit-lock-file-summary", "commit-lockfile-summary"};
|
||||||
|
|
||||||
|
@ -47,11 +47,11 @@ void ConfigFile::apply()
|
||||||
else if (auto* b = std::get_if<Explicit<bool>>(&value))
|
else if (auto* b = std::get_if<Explicit<bool>>(&value))
|
||||||
valueS = b->t ? "true" : "false";
|
valueS = b->t ? "true" : "false";
|
||||||
else if (auto ss = std::get_if<std::vector<std::string>>(&value))
|
else if (auto ss = std::get_if<std::vector<std::string>>(&value))
|
||||||
valueS = concatStringsSep(" ", *ss); // FIXME: evil
|
valueS = dropEmptyInitThenConcatStringsSep(" ", *ss); // FIXME: evil
|
||||||
else
|
else
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
||||||
if (!whitelist.count(baseName) && !nix::flakeSettings.acceptFlakeConfig) {
|
if (!whitelist.count(baseName) && !flakeSettings.acceptFlakeConfig) {
|
||||||
bool trusted = false;
|
bool trusted = false;
|
||||||
auto trustedList = readTrustedList();
|
auto trustedList = readTrustedList();
|
||||||
auto tlname = get(trustedList, name);
|
auto tlname = get(trustedList, name);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "fetch-settings.hh"
|
#include "fetch-settings.hh"
|
||||||
#include "flake-settings.hh"
|
#include "flake/settings.hh"
|
||||||
#include "value-to-json.hh"
|
#include "value-to-json.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
const std::optional<Path> & baseDir, InputPath lockRootPath);
|
const std::optional<Path> & baseDir, InputPath lockRootPath);
|
||||||
|
|
||||||
static FlakeInput parseFlakeInput(EvalState & state,
|
static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
const std::string & inputName, Value * value, const PosIdx pos,
|
std::string_view inputName, Value * value, const PosIdx pos,
|
||||||
const std::optional<Path> & baseDir, InputPath lockRootPath)
|
const std::optional<Path> & baseDir, InputPath lockRootPath)
|
||||||
{
|
{
|
||||||
expectType(state, nAttrs, *value, pos);
|
expectType(state, nAttrs, *value, pos);
|
||||||
|
@ -164,7 +164,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
|
|
||||||
if (attrs.count("type"))
|
if (attrs.count("type"))
|
||||||
try {
|
try {
|
||||||
input.ref = FlakeRef::fromAttrs(attrs);
|
input.ref = FlakeRef::fromAttrs(state.fetchSettings, attrs);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(state.positions[pos], HintFmt("while evaluating flake input"));
|
e.addTrace(state.positions[pos], HintFmt("while evaluating flake input"));
|
||||||
throw;
|
throw;
|
||||||
|
@ -174,11 +174,11 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
if (!attrs.empty())
|
if (!attrs.empty())
|
||||||
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]);
|
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]);
|
||||||
if (url)
|
if (url)
|
||||||
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
|
input.ref = parseFlakeRef(state.fetchSettings, *url, baseDir, true, input.isFlake);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input.follows && !input.ref)
|
if (!input.follows && !input.ref)
|
||||||
input.ref = FlakeRef::fromAttrs({{"type", "indirect"}, {"id", inputName}});
|
input.ref = FlakeRef::fromAttrs(state.fetchSettings, {{"type", "indirect"}, {"id", std::string(inputName)}});
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ Flake readFlake(
|
||||||
for (auto & formal : outputs->value->payload.lambda.fun->formals->formals) {
|
for (auto & formal : outputs->value->payload.lambda.fun->formals->formals) {
|
||||||
if (formal.name != state.sSelf)
|
if (formal.name != state.sSelf)
|
||||||
flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
|
flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
|
||||||
.ref = parseFlakeRef(state.symbols[formal.name])
|
.ref = parseFlakeRef(state.fetchSettings, std::string(state.symbols[formal.name]))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,33 +329,37 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
|
||||||
return getFlake(state, originalRef, allowLookup, flakeCache);
|
return getFlake(state, originalRef, allowLookup, flakeCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
static LockFile readLockFile(const SourcePath & lockFilePath)
|
static LockFile readLockFile(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const SourcePath & lockFilePath)
|
||||||
{
|
{
|
||||||
return lockFilePath.pathExists()
|
return lockFilePath.pathExists()
|
||||||
? LockFile(lockFilePath.readFile(), fmt("%s", lockFilePath))
|
? LockFile(fetchSettings, lockFilePath.readFile(), fmt("%s", lockFilePath))
|
||||||
: LockFile();
|
: LockFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
|
const Settings & settings,
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & topRef,
|
const FlakeRef & topRef,
|
||||||
const LockFlags & lockFlags,
|
const LockFlags & lockFlags,
|
||||||
Flake flake,
|
Flake flake,
|
||||||
FlakeCache & flakeCache)
|
FlakeCache & flakeCache)
|
||||||
{
|
{
|
||||||
auto useRegistries = lockFlags.useRegistries.value_or(flakeSettings.useRegistries);
|
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
|
||||||
|
|
||||||
if (lockFlags.applyNixConfig) {
|
if (lockFlags.applyNixConfig) {
|
||||||
flake.config.apply();
|
flake.config.apply(settings);
|
||||||
state.store->setOptions();
|
state.store->setOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!fetchSettings.allowDirty && lockFlags.referenceLockFilePath) {
|
if (!state.fetchSettings.allowDirty && lockFlags.referenceLockFilePath) {
|
||||||
throw Error("reference lock file was provided, but the `allow-dirty` setting is set to false");
|
throw Error("reference lock file was provided, but the `allow-dirty` setting is set to false");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto oldLockFile = readLockFile(
|
auto oldLockFile = readLockFile(
|
||||||
|
state.fetchSettings,
|
||||||
lockFlags.referenceLockFilePath.value_or(
|
lockFlags.referenceLockFilePath.value_or(
|
||||||
flake.lockFilePath()));
|
flake.lockFilePath()));
|
||||||
|
|
||||||
|
@ -591,7 +595,7 @@ LockedFlake lockFlake(
|
||||||
inputFlake.inputs, childNode, inputPath,
|
inputFlake.inputs, childNode, inputPath,
|
||||||
oldLock
|
oldLock
|
||||||
? std::dynamic_pointer_cast<const Node>(oldLock)
|
? std::dynamic_pointer_cast<const Node>(oldLock)
|
||||||
: readLockFile(inputFlake.lockFilePath()).root.get_ptr(),
|
: readLockFile(state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
|
||||||
oldLock ? lockRootPath : inputPath,
|
oldLock ? lockRootPath : inputPath,
|
||||||
localPath,
|
localPath,
|
||||||
false);
|
false);
|
||||||
|
@ -654,7 +658,7 @@ LockedFlake lockFlake(
|
||||||
if (lockFlags.writeLockFile) {
|
if (lockFlags.writeLockFile) {
|
||||||
if (sourcePath || lockFlags.outputLockFilePath) {
|
if (sourcePath || lockFlags.outputLockFilePath) {
|
||||||
if (auto unlockedInput = newLockFile.isUnlocked()) {
|
if (auto unlockedInput = newLockFile.isUnlocked()) {
|
||||||
if (fetchSettings.warnDirty)
|
if (state.fetchSettings.warnDirty)
|
||||||
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
|
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
|
||||||
} else {
|
} else {
|
||||||
if (!lockFlags.updateLockFile)
|
if (!lockFlags.updateLockFile)
|
||||||
|
@ -686,7 +690,7 @@ LockedFlake lockFlake(
|
||||||
if (lockFlags.commitLockFile) {
|
if (lockFlags.commitLockFile) {
|
||||||
std::string cm;
|
std::string cm;
|
||||||
|
|
||||||
cm = flakeSettings.commitLockFileSummary.get();
|
cm = settings.commitLockFileSummary.get();
|
||||||
|
|
||||||
if (cm == "") {
|
if (cm == "") {
|
||||||
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
|
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
|
||||||
|
@ -735,25 +739,27 @@ LockedFlake lockFlake(
|
||||||
}
|
}
|
||||||
|
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
|
const Settings & settings,
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & topRef,
|
const FlakeRef & topRef,
|
||||||
const LockFlags & lockFlags)
|
const LockFlags & lockFlags)
|
||||||
{
|
{
|
||||||
FlakeCache flakeCache;
|
FlakeCache flakeCache;
|
||||||
|
|
||||||
auto useRegistries = lockFlags.useRegistries.value_or(flakeSettings.useRegistries);
|
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
|
||||||
|
|
||||||
return lockFlake(state, topRef, lockFlags, getFlake(state, topRef, useRegistries, flakeCache), flakeCache);
|
return lockFlake(settings, state, topRef, lockFlags, getFlake(state, topRef, useRegistries, flakeCache), flakeCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
|
const Settings & settings,
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & topRef,
|
const FlakeRef & topRef,
|
||||||
const LockFlags & lockFlags,
|
const LockFlags & lockFlags,
|
||||||
Flake flake)
|
Flake flake)
|
||||||
{
|
{
|
||||||
FlakeCache flakeCache;
|
FlakeCache flakeCache;
|
||||||
return lockFlake(state, topRef, lockFlags, std::move(flake), flakeCache);
|
return lockFlake(settings, state, topRef, lockFlags, std::move(flake), flakeCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
void callFlake(EvalState & state,
|
void callFlake(EvalState & state,
|
||||||
|
@ -814,45 +820,48 @@ void callFlake(EvalState & state,
|
||||||
state.callFunction(*vTmp1, vOverrides, vRes, noPos);
|
state.callFunction(*vTmp1, vOverrides, vRes, noPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
void initLib(const Settings & settings)
|
||||||
{
|
{
|
||||||
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
auto prim_getFlake = [&settings](EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
{
|
||||||
if (state.settings.pureEval && !flakeRef.input.isLocked())
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
|
||||||
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
|
auto flakeRef = parseFlakeRef(state.fetchSettings, flakeRefS, {}, true);
|
||||||
|
if (state.settings.pureEval && !flakeRef.input.isLocked())
|
||||||
|
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);
|
||||||
|
|
||||||
callFlake(state,
|
callFlake(state,
|
||||||
lockFlake(state, flakeRef,
|
lockFlake(settings, state, flakeRef,
|
||||||
LockFlags {
|
LockFlags {
|
||||||
.updateLockFile = false,
|
.updateLockFile = false,
|
||||||
.writeLockFile = false,
|
.writeLockFile = false,
|
||||||
.useRegistries = !state.settings.pureEval && flakeSettings.useRegistries,
|
.useRegistries = !state.settings.pureEval && settings.useRegistries,
|
||||||
.allowUnlocked = !state.settings.pureEval,
|
.allowUnlocked = !state.settings.pureEval,
|
||||||
}),
|
}),
|
||||||
v);
|
v);
|
||||||
|
};
|
||||||
|
|
||||||
|
RegisterPrimOp::primOps->push_back({
|
||||||
|
.name = "__getFlake",
|
||||||
|
.args = {"args"},
|
||||||
|
.doc = R"(
|
||||||
|
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
Unless impure evaluation is allowed (`--impure`), the flake reference
|
||||||
|
must be "locked", e.g. contain a Git revision or content hash. An
|
||||||
|
example of an unlocked usage is:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(builtins.getFlake "github:edolstra/dwarffs").rev
|
||||||
|
```
|
||||||
|
)",
|
||||||
|
.fun = prim_getFlake,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r2({
|
|
||||||
.name = "__getFlake",
|
|
||||||
.args = {"args"},
|
|
||||||
.doc = R"(
|
|
||||||
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
Unless impure evaluation is allowed (`--impure`), the flake reference
|
|
||||||
must be "locked", e.g. contain a Git revision or content hash. An
|
|
||||||
example of an unlocked usage is:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
(builtins.getFlake "github:edolstra/dwarffs").rev
|
|
||||||
```
|
|
||||||
)",
|
|
||||||
.fun = prim_getFlake,
|
|
||||||
});
|
|
||||||
|
|
||||||
static void prim_parseFlakeRef(
|
static void prim_parseFlakeRef(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const PosIdx pos,
|
const PosIdx pos,
|
||||||
|
@ -861,7 +870,7 @@ static void prim_parseFlakeRef(
|
||||||
{
|
{
|
||||||
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos,
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos,
|
||||||
"while evaluating the argument passed to builtins.parseFlakeRef"));
|
"while evaluating the argument passed to builtins.parseFlakeRef"));
|
||||||
auto attrs = parseFlakeRef(flakeRefS, {}, true).toAttrs();
|
auto attrs = parseFlakeRef(state.fetchSettings, flakeRefS, {}, true).toAttrs();
|
||||||
auto binds = state.buildBindings(attrs.size());
|
auto binds = state.buildBindings(attrs.size());
|
||||||
for (const auto & [key, value] : attrs) {
|
for (const auto & [key, value] : attrs) {
|
||||||
auto s = state.symbols.create(key);
|
auto s = state.symbols.create(key);
|
||||||
|
@ -925,7 +934,7 @@ static void prim_flakeRefToString(
|
||||||
showType(*attr.value)).debugThrow();
|
showType(*attr.value)).debugThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto flakeRef = FlakeRef::fromAttrs(attrs);
|
auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs);
|
||||||
v.mkString(flakeRef.to_string());
|
v.mkString(flakeRef.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,16 @@ class EvalState;
|
||||||
|
|
||||||
namespace flake {
|
namespace flake {
|
||||||
|
|
||||||
|
struct Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize `libnixflake`
|
||||||
|
*
|
||||||
|
* So far, this registers the `builtins.getFlake` primop, which depends
|
||||||
|
* on the choice of `flake:Settings`.
|
||||||
|
*/
|
||||||
|
void initLib(const Settings & settings);
|
||||||
|
|
||||||
struct FlakeInput;
|
struct FlakeInput;
|
||||||
|
|
||||||
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
|
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
|
||||||
|
@ -57,7 +67,7 @@ struct ConfigFile
|
||||||
|
|
||||||
std::map<std::string, ConfigValue> settings;
|
std::map<std::string, ConfigValue> settings;
|
||||||
|
|
||||||
void apply();
|
void apply(const Settings & settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,11 +216,13 @@ Flake readFlake(
|
||||||
* and optionally write it to file, if the flake is writable.
|
* and optionally write it to file, if the flake is writable.
|
||||||
*/
|
*/
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
|
const Settings & settings,
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & flakeRef,
|
const FlakeRef & flakeRef,
|
||||||
const LockFlags & lockFlags);
|
const LockFlags & lockFlags);
|
||||||
|
|
||||||
LockedFlake lockFlake(
|
LockedFlake lockFlake(
|
||||||
|
const Settings & settings,
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const FlakeRef & topRef,
|
const FlakeRef & topRef,
|
||||||
const LockFlags & lockFlags,
|
const LockFlags & lockFlags,
|
||||||
|
|
|
@ -36,11 +36,6 @@ std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlakeRef::operator ==(const FlakeRef & other) const
|
|
||||||
{
|
|
||||||
return input == other.input && subdir == other.subdir;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlakeRef FlakeRef::resolve(ref<Store> store) const
|
FlakeRef FlakeRef::resolve(ref<Store> store) const
|
||||||
{
|
{
|
||||||
auto [input2, extraAttrs] = lookupInRegistries(store, input);
|
auto [input2, extraAttrs] = lookupInRegistries(store, input);
|
||||||
|
@ -48,28 +43,32 @@ FlakeRef FlakeRef::resolve(ref<Store> store) const
|
||||||
}
|
}
|
||||||
|
|
||||||
FlakeRef parseFlakeRef(
|
FlakeRef parseFlakeRef(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir,
|
const std::optional<Path> & baseDir,
|
||||||
bool allowMissing,
|
bool allowMissing,
|
||||||
bool isFlake)
|
bool isFlake)
|
||||||
{
|
{
|
||||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir, allowMissing, isFlake);
|
auto [flakeRef, fragment] = parseFlakeRefWithFragment(fetchSettings, url, baseDir, allowMissing, isFlake);
|
||||||
if (fragment != "")
|
if (fragment != "")
|
||||||
throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url);
|
throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url);
|
||||||
return flakeRef;
|
return flakeRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<FlakeRef> maybeParseFlakeRef(
|
std::optional<FlakeRef> maybeParseFlakeRef(
|
||||||
const std::string & url, const std::optional<Path> & baseDir)
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const std::string & url,
|
||||||
|
const std::optional<Path> & baseDir)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return parseFlakeRef(url, baseDir);
|
return parseFlakeRef(fetchSettings, url, baseDir);
|
||||||
} catch (Error &) {
|
} catch (Error &) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir,
|
const std::optional<Path> & baseDir,
|
||||||
bool allowMissing,
|
bool allowMissing,
|
||||||
|
@ -166,7 +165,7 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||||
parsedURL.query.insert_or_assign("shallow", "1");
|
parsedURL.query.insert_or_assign("shallow", "1");
|
||||||
|
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
FlakeRef(fetchers::Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")),
|
FlakeRef(fetchers::Input::fromURL(fetchSettings, parsedURL), getOr(parsedURL.query, "dir", "")),
|
||||||
fragment);
|
fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,13 +184,14 @@ std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
|
||||||
attrs.insert_or_assign("type", "path");
|
attrs.insert_or_assign("type", "path");
|
||||||
attrs.insert_or_assign("path", path);
|
attrs.insert_or_assign("path", path);
|
||||||
|
|
||||||
return std::make_pair(FlakeRef(fetchers::Input::fromAttrs(std::move(attrs)), ""), fragment);
|
return std::make_pair(FlakeRef(fetchers::Input::fromAttrs(fetchSettings, std::move(attrs)), ""), fragment);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
|
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
|
||||||
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
|
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
|
||||||
std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(
|
static std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
bool isFlake
|
bool isFlake
|
||||||
)
|
)
|
||||||
|
@ -213,7 +213,7 @@ std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(
|
||||||
};
|
};
|
||||||
|
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
FlakeRef(fetchers::Input::fromURL(parsedURL, isFlake), ""),
|
FlakeRef(fetchers::Input::fromURL(fetchSettings, parsedURL, isFlake), ""),
|
||||||
percentDecode(match.str(6)));
|
percentDecode(match.str(6)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +221,7 @@ std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
|
std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir,
|
const std::optional<Path> & baseDir,
|
||||||
bool isFlake
|
bool isFlake
|
||||||
|
@ -236,7 +237,7 @@ std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
|
||||||
std::string fragment;
|
std::string fragment;
|
||||||
std::swap(fragment, parsedURL.fragment);
|
std::swap(fragment, parsedURL.fragment);
|
||||||
|
|
||||||
auto input = fetchers::Input::fromURL(parsedURL, isFlake);
|
auto input = fetchers::Input::fromURL(fetchSettings, parsedURL, isFlake);
|
||||||
input.parent = baseDir;
|
input.parent = baseDir;
|
||||||
|
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
|
@ -245,6 +246,7 @@ std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir,
|
const std::optional<Path> & baseDir,
|
||||||
bool allowMissing,
|
bool allowMissing,
|
||||||
|
@ -254,31 +256,34 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
|
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
||||||
if (auto res = parseFlakeIdRef(url, isFlake)) {
|
if (auto res = parseFlakeIdRef(fetchSettings, url, isFlake)) {
|
||||||
return *res;
|
return *res;
|
||||||
} else if (auto res = parseURLFlakeRef(url, baseDir, isFlake)) {
|
} else if (auto res = parseURLFlakeRef(fetchSettings, url, baseDir, isFlake)) {
|
||||||
return *res;
|
return *res;
|
||||||
} else {
|
} else {
|
||||||
return parsePathFlakeRefWithFragment(url, baseDir, allowMissing, isFlake);
|
return parsePathFlakeRefWithFragment(fetchSettings, url, baseDir, allowMissing, isFlake);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url, const std::optional<Path> & baseDir)
|
const std::string & url, const std::optional<Path> & baseDir)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return parseFlakeRefWithFragment(url, baseDir);
|
return parseFlakeRefWithFragment(fetchSettings, url, baseDir);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FlakeRef FlakeRef::fromAttrs(const fetchers::Attrs & attrs)
|
FlakeRef FlakeRef::fromAttrs(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const fetchers::Attrs & attrs)
|
||||||
{
|
{
|
||||||
auto attrs2(attrs);
|
auto attrs2(attrs);
|
||||||
attrs2.erase("dir");
|
attrs2.erase("dir");
|
||||||
return FlakeRef(
|
return FlakeRef(
|
||||||
fetchers::Input::fromAttrs(std::move(attrs2)),
|
fetchers::Input::fromAttrs(fetchSettings, std::move(attrs2)),
|
||||||
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
|
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,13 +294,16 @@ std::pair<StorePath, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir,
|
const std::optional<Path> & baseDir,
|
||||||
bool allowMissing,
|
bool allowMissing,
|
||||||
bool isFlake)
|
bool isFlake)
|
||||||
{
|
{
|
||||||
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(url);
|
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(url);
|
||||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, baseDir, allowMissing, isFlake);
|
auto [flakeRef, fragment] = parseFlakeRefWithFragment(
|
||||||
|
fetchSettings,
|
||||||
|
std::string { prefix }, baseDir, allowMissing, isFlake);
|
||||||
return {std::move(flakeRef), fragment, std::move(extendedOutputsSpec)};
|
return {std::move(flakeRef), fragment, std::move(extendedOutputsSpec)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "hash.hh"
|
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "outputs-spec.hh"
|
#include "outputs-spec.hh"
|
||||||
|
|
||||||
#include <regex>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
|
@ -48,7 +46,7 @@ struct FlakeRef
|
||||||
*/
|
*/
|
||||||
Path subdir;
|
Path subdir;
|
||||||
|
|
||||||
bool operator==(const FlakeRef & other) const;
|
bool operator ==(const FlakeRef & other) const = default;
|
||||||
|
|
||||||
FlakeRef(fetchers::Input && input, const Path & subdir)
|
FlakeRef(fetchers::Input && input, const Path & subdir)
|
||||||
: input(std::move(input)), subdir(subdir)
|
: input(std::move(input)), subdir(subdir)
|
||||||
|
@ -61,7 +59,9 @@ struct FlakeRef
|
||||||
|
|
||||||
FlakeRef resolve(ref<Store> store) const;
|
FlakeRef resolve(ref<Store> store) const;
|
||||||
|
|
||||||
static FlakeRef fromAttrs(const fetchers::Attrs & attrs);
|
static FlakeRef fromAttrs(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const fetchers::Attrs & attrs);
|
||||||
|
|
||||||
std::pair<StorePath, FlakeRef> fetchTree(ref<Store> store) const;
|
std::pair<StorePath, FlakeRef> fetchTree(ref<Store> store) const;
|
||||||
};
|
};
|
||||||
|
@ -72,6 +72,7 @@ std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef);
|
||||||
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
||||||
*/
|
*/
|
||||||
FlakeRef parseFlakeRef(
|
FlakeRef parseFlakeRef(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir = {},
|
const std::optional<Path> & baseDir = {},
|
||||||
bool allowMissing = false,
|
bool allowMissing = false,
|
||||||
|
@ -81,12 +82,15 @@ FlakeRef parseFlakeRef(
|
||||||
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
||||||
*/
|
*/
|
||||||
std::optional<FlakeRef> maybeParseFlake(
|
std::optional<FlakeRef> maybeParseFlake(
|
||||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const std::string & url,
|
||||||
|
const std::optional<Path> & baseDir = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
||||||
*/
|
*/
|
||||||
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir = {},
|
const std::optional<Path> & baseDir = {},
|
||||||
bool allowMissing = false,
|
bool allowMissing = false,
|
||||||
|
@ -96,12 +100,15 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
||||||
*/
|
*/
|
||||||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const std::string & url,
|
||||||
|
const std::optional<Path> & baseDir = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
* @param baseDir Optional [base directory](https://nixos.org/manual/nix/unstable/glossary#gloss-base-directory)
|
||||||
*/
|
*/
|
||||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir = {},
|
const std::optional<Path> & baseDir = {},
|
||||||
bool allowMissing = false,
|
bool allowMissing = false,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "lockfile.hh"
|
#include "lockfile.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "url-parts.hh"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
@ -8,9 +9,12 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix::flake {
|
namespace nix::flake {
|
||||||
|
|
||||||
FlakeRef getFlakeRef(
|
static FlakeRef getFlakeRef(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
const nlohmann::json & json,
|
const nlohmann::json & json,
|
||||||
const char * attr,
|
const char * attr,
|
||||||
const char * info)
|
const char * info)
|
||||||
|
@ -26,15 +30,17 @@ FlakeRef getFlakeRef(
|
||||||
attrs.insert_or_assign(k.first, k.second);
|
attrs.insert_or_assign(k.first, k.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return FlakeRef::fromAttrs(attrs);
|
return FlakeRef::fromAttrs(fetchSettings, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Error("attribute '%s' missing in lock file", attr);
|
throw Error("attribute '%s' missing in lock file", attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
LockedNode::LockedNode(const nlohmann::json & json)
|
LockedNode::LockedNode(
|
||||||
: lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info"
|
const fetchers::Settings & fetchSettings,
|
||||||
, originalRef(getFlakeRef(json, "original", nullptr))
|
const nlohmann::json & json)
|
||||||
|
: lockedRef(getFlakeRef(fetchSettings, json, "locked", "info")) // FIXME: remove "info"
|
||||||
|
, originalRef(getFlakeRef(fetchSettings, json, "original", nullptr))
|
||||||
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
||||||
{
|
{
|
||||||
if (!lockedRef.input.isLocked())
|
if (!lockedRef.input.isLocked())
|
||||||
|
@ -84,7 +90,9 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
|
||||||
return doFind(root, path, visited);
|
return doFind(root, path, visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
LockFile::LockFile(std::string_view contents, std::string_view path)
|
LockFile::LockFile(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
|
std::string_view contents, std::string_view path)
|
||||||
{
|
{
|
||||||
auto json = nlohmann::json::parse(contents);
|
auto json = nlohmann::json::parse(contents);
|
||||||
|
|
||||||
|
@ -113,7 +121,7 @@ LockFile::LockFile(std::string_view contents, std::string_view path)
|
||||||
auto jsonNode2 = nodes.find(inputKey);
|
auto jsonNode2 = nodes.find(inputKey);
|
||||||
if (jsonNode2 == nodes.end())
|
if (jsonNode2 == nodes.end())
|
||||||
throw Error("lock file references missing node '%s'", inputKey);
|
throw Error("lock file references missing node '%s'", inputKey);
|
||||||
auto input = make_ref<LockedNode>(*jsonNode2);
|
auto input = make_ref<LockedNode>(fetchSettings, *jsonNode2);
|
||||||
k = nodeMap.insert_or_assign(inputKey, input).first;
|
k = nodeMap.insert_or_assign(inputKey, input).first;
|
||||||
getInputs(*input, *jsonNode2);
|
getInputs(*input, *jsonNode2);
|
||||||
}
|
}
|
||||||
|
@ -243,11 +251,6 @@ bool LockFile::operator ==(const LockFile & other) const
|
||||||
return toJSON().first == other.toJSON().first;
|
return toJSON().first == other.toJSON().first;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LockFile::operator !=(const LockFile & other) const
|
|
||||||
{
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
|
|
||||||
InputPath parseInputPath(std::string_view s)
|
InputPath parseInputPath(std::string_view s)
|
||||||
{
|
{
|
||||||
InputPath path;
|
InputPath path;
|
||||||
|
|
|
@ -45,7 +45,9 @@ struct LockedNode : Node
|
||||||
: lockedRef(lockedRef), originalRef(originalRef), isFlake(isFlake)
|
: lockedRef(lockedRef), originalRef(originalRef), isFlake(isFlake)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
LockedNode(const nlohmann::json & json);
|
LockedNode(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
|
const nlohmann::json & json);
|
||||||
|
|
||||||
StorePath computeStorePath(Store & store) const;
|
StorePath computeStorePath(Store & store) const;
|
||||||
};
|
};
|
||||||
|
@ -55,7 +57,9 @@ struct LockFile
|
||||||
ref<Node> root = make_ref<Node>();
|
ref<Node> root = make_ref<Node>();
|
||||||
|
|
||||||
LockFile() {};
|
LockFile() {};
|
||||||
LockFile(std::string_view contents, std::string_view path);
|
LockFile(
|
||||||
|
const fetchers::Settings & fetchSettings,
|
||||||
|
std::string_view contents, std::string_view path);
|
||||||
|
|
||||||
typedef std::map<ref<const Node>, std::string> KeyMap;
|
typedef std::map<ref<const Node>, std::string> KeyMap;
|
||||||
|
|
||||||
|
@ -70,9 +74,6 @@ struct LockFile
|
||||||
std::optional<FlakeRef> isUnlocked() const;
|
std::optional<FlakeRef> isUnlocked() const;
|
||||||
|
|
||||||
bool operator ==(const LockFile & other) const;
|
bool operator ==(const LockFile & other) const;
|
||||||
// Needed for old gcc versions that don't synthesize it (like gcc 8.2.2
|
|
||||||
// that is still the default on aarch64-linux)
|
|
||||||
bool operator !=(const LockFile & other) const;
|
|
||||||
|
|
||||||
std::shared_ptr<Node> findInput(const InputPath & path);
|
std::shared_ptr<Node> findInput(const InputPath & path);
|
||||||
|
|
||||||
|
|
7
src/libflake/flake/settings.cc
Normal file
7
src/libflake/flake/settings.cc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include "flake/settings.hh"
|
||||||
|
|
||||||
|
namespace nix::flake {
|
||||||
|
|
||||||
|
Settings::Settings() {}
|
||||||
|
|
||||||
|
}
|
|
@ -10,11 +10,11 @@
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix::flake {
|
||||||
|
|
||||||
struct FlakeSettings : public Config
|
struct Settings : public Config
|
||||||
{
|
{
|
||||||
FlakeSettings();
|
Settings();
|
||||||
|
|
||||||
Setting<bool> useRegistries{
|
Setting<bool> useRegistries{
|
||||||
this, true, "use-registries", "Whether to use flake registries to resolve flake references.", {}, true};
|
this, true, "use-registries", "Whether to use flake registries to resolve flake references.", {}, true};
|
||||||
|
@ -39,7 +39,4 @@ struct FlakeSettings : public Config
|
||||||
true};
|
true};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: don't use a global variable.
|
|
||||||
extern FlakeSettings flakeSettings;
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -42,21 +42,21 @@ add_project_arguments(
|
||||||
subdir('build-utils-meson/diagnostics')
|
subdir('build-utils-meson/diagnostics')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'flake-settings.cc',
|
|
||||||
'flake/config.cc',
|
'flake/config.cc',
|
||||||
'flake/flake.cc',
|
'flake/flake.cc',
|
||||||
'flake/flakeref.cc',
|
'flake/flakeref.cc',
|
||||||
'flake/url-name.cc',
|
|
||||||
'flake/lockfile.cc',
|
'flake/lockfile.cc',
|
||||||
|
'flake/settings.cc',
|
||||||
|
'flake/url-name.cc',
|
||||||
)
|
)
|
||||||
|
|
||||||
include_dirs = [include_directories('.')]
|
include_dirs = [include_directories('.')]
|
||||||
|
|
||||||
headers = files(
|
headers = files(
|
||||||
'flake-settings.hh',
|
|
||||||
'flake/flake.hh',
|
'flake/flake.hh',
|
||||||
'flake/flakeref.hh',
|
'flake/flakeref.hh',
|
||||||
'flake/lockfile.hh',
|
'flake/lockfile.hh',
|
||||||
|
'flake/settings.hh',
|
||||||
'flake/url-name.hh',
|
'flake/url-name.hh',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
@ -23,6 +22,8 @@
|
||||||
|
|
||||||
#include <openssl/crypto.h>
|
#include <openssl/crypto.h>
|
||||||
|
|
||||||
|
#include "exit.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,9 @@
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "exit.hh"
|
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#include <locale>
|
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
int handleExceptions(const std::string & programName, std::function<void()> fun);
|
int handleExceptions(const std::string & programName, std::function<void()> fun);
|
||||||
|
|
|
@ -2,17 +2,7 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
GENERATE_CMP_EXT(
|
bool BuildResult::operator==(const BuildResult &) const noexcept = default;
|
||||||
,
|
std::strong_ordering BuildResult::operator<=>(const BuildResult &) const noexcept = default;
|
||||||
BuildResult,
|
|
||||||
me->status,
|
|
||||||
me->errorMsg,
|
|
||||||
me->timesBuilt,
|
|
||||||
me->isNonDeterministic,
|
|
||||||
me->builtOutputs,
|
|
||||||
me->startTime,
|
|
||||||
me->stopTime,
|
|
||||||
me->cpuUser,
|
|
||||||
me->cpuSystem);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "comparator.hh"
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -101,7 +100,8 @@ struct BuildResult
|
||||||
*/
|
*/
|
||||||
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
|
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
|
||||||
|
|
||||||
DECLARE_CMP(BuildResult);
|
bool operator ==(const BuildResult &) const noexcept;
|
||||||
|
std::strong_ordering operator <=>(const BuildResult &) const noexcept;
|
||||||
|
|
||||||
bool success()
|
bool success()
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,8 +32,18 @@
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
Goal::Co DerivationGoal::init() {
|
||||||
|
if (useDerivation) {
|
||||||
|
co_return getDerivation();
|
||||||
|
} else {
|
||||||
|
co_return haveDerivation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||||
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||||
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
|
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
|
||||||
|
@ -42,7 +52,6 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||||
, wantedOutputs(wantedOutputs)
|
, wantedOutputs(wantedOutputs)
|
||||||
, buildMode(buildMode)
|
, buildMode(buildMode)
|
||||||
{
|
{
|
||||||
state = &DerivationGoal::getDerivation;
|
|
||||||
name = fmt(
|
name = fmt(
|
||||||
"building of '%s' from .drv file",
|
"building of '%s' from .drv file",
|
||||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
|
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
|
||||||
|
@ -63,7 +72,6 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
|
||||||
{
|
{
|
||||||
this->drv = std::make_unique<Derivation>(drv);
|
this->drv = std::make_unique<Derivation>(drv);
|
||||||
|
|
||||||
state = &DerivationGoal::haveDerivation;
|
|
||||||
name = fmt(
|
name = fmt(
|
||||||
"building of '%s' from in-memory derivation",
|
"building of '%s' from in-memory derivation",
|
||||||
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
|
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
|
||||||
|
@ -107,13 +115,9 @@ void DerivationGoal::killChild()
|
||||||
void DerivationGoal::timedOut(Error && ex)
|
void DerivationGoal::timedOut(Error && ex)
|
||||||
{
|
{
|
||||||
killChild();
|
killChild();
|
||||||
done(BuildResult::TimedOut, {}, std::move(ex));
|
// We're not inside a coroutine, hence we can't use co_return here.
|
||||||
}
|
// Thus we ignore the return value.
|
||||||
|
[[maybe_unused]] Done _ = done(BuildResult::TimedOut, {}, std::move(ex));
|
||||||
|
|
||||||
void DerivationGoal::work()
|
|
||||||
{
|
|
||||||
(this->*state)();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||||
|
@ -137,7 +141,7 @@ void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::getDerivation()
|
Goal::Co DerivationGoal::getDerivation()
|
||||||
{
|
{
|
||||||
trace("init");
|
trace("init");
|
||||||
|
|
||||||
|
@ -145,23 +149,22 @@ void DerivationGoal::getDerivation()
|
||||||
exists. If it doesn't, it may be created through a
|
exists. If it doesn't, it may be created through a
|
||||||
substitute. */
|
substitute. */
|
||||||
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
|
if (buildMode == bmNormal && worker.evalStore.isValidPath(drvPath)) {
|
||||||
loadDerivation();
|
co_return loadDerivation();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(drvPath)));
|
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(drvPath)));
|
||||||
|
|
||||||
state = &DerivationGoal::loadDerivation;
|
co_await Suspend{};
|
||||||
|
co_return loadDerivation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::loadDerivation()
|
Goal::Co DerivationGoal::loadDerivation()
|
||||||
{
|
{
|
||||||
trace("loading derivation");
|
trace("loading derivation");
|
||||||
|
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
co_return done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* `drvPath' should already be a root, but let's be on the safe
|
/* `drvPath' should already be a root, but let's be on the safe
|
||||||
|
@ -183,11 +186,11 @@ void DerivationGoal::loadDerivation()
|
||||||
}
|
}
|
||||||
assert(drv);
|
assert(drv);
|
||||||
|
|
||||||
haveDerivation();
|
co_return haveDerivation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::haveDerivation()
|
Goal::Co DerivationGoal::haveDerivation()
|
||||||
{
|
{
|
||||||
trace("have derivation");
|
trace("have derivation");
|
||||||
|
|
||||||
|
@ -215,8 +218,7 @@ void DerivationGoal::haveDerivation()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
gaveUpOnSubstitution();
|
co_return gaveUpOnSubstitution();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
||||||
|
@ -238,8 +240,7 @@ void DerivationGoal::haveDerivation()
|
||||||
|
|
||||||
/* If they are all valid, then we're done. */
|
/* If they are all valid, then we're done. */
|
||||||
if (allValid && buildMode == bmNormal) {
|
if (allValid && buildMode == bmNormal) {
|
||||||
done(BuildResult::AlreadyValid, std::move(validOutputs));
|
co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We are first going to try to create the invalid output paths
|
/* We are first going to try to create the invalid output paths
|
||||||
|
@ -266,24 +267,21 @@ void DerivationGoal::haveDerivation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
if (!waitees.empty()) co_await Suspend{}; /* to prevent hang (no wake-up event) */
|
||||||
outputsSubstitutionTried();
|
co_return outputsSubstitutionTried();
|
||||||
else
|
|
||||||
state = &DerivationGoal::outputsSubstitutionTried;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::outputsSubstitutionTried()
|
Goal::Co DerivationGoal::outputsSubstitutionTried()
|
||||||
{
|
{
|
||||||
trace("all outputs substituted (maybe)");
|
trace("all outputs substituted (maybe)");
|
||||||
|
|
||||||
assert(!drv->type().isImpure());
|
assert(!drv->type().isImpure());
|
||||||
|
|
||||||
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
||||||
done(BuildResult::TransientFailure, {},
|
co_return done(BuildResult::TransientFailure, {},
|
||||||
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
||||||
worker.store.printStorePath(drvPath)));
|
worker.store.printStorePath(drvPath)));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If the substitutes form an incomplete closure, then we should
|
/* If the substitutes form an incomplete closure, then we should
|
||||||
|
@ -317,32 +315,29 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
|
|
||||||
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
|
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
|
||||||
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
||||||
haveDerivation();
|
co_return haveDerivation();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [allValid, validOutputs] = checkPathValidity();
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
|
|
||||||
if (buildMode == bmNormal && allValid) {
|
if (buildMode == bmNormal && allValid) {
|
||||||
done(BuildResult::Substituted, std::move(validOutputs));
|
co_return done(BuildResult::Substituted, std::move(validOutputs));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (buildMode == bmRepair && allValid) {
|
if (buildMode == bmRepair && allValid) {
|
||||||
repairClosure();
|
co_return repairClosure();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (buildMode == bmCheck && !allValid)
|
if (buildMode == bmCheck && !allValid)
|
||||||
throw Error("some outputs of '%s' are not valid, so checking is not possible",
|
throw Error("some outputs of '%s' are not valid, so checking is not possible",
|
||||||
worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(drvPath));
|
||||||
|
|
||||||
/* Nothing to wait for; tail call */
|
/* Nothing to wait for; tail call */
|
||||||
gaveUpOnSubstitution();
|
co_return gaveUpOnSubstitution();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* At least one of the output paths could not be
|
/* At least one of the output paths could not be
|
||||||
produced using a substitute. So we have to build instead. */
|
produced using a substitute. So we have to build instead. */
|
||||||
void DerivationGoal::gaveUpOnSubstitution()
|
Goal::Co DerivationGoal::gaveUpOnSubstitution()
|
||||||
{
|
{
|
||||||
/* At this point we are building all outputs, so if more are wanted there
|
/* At this point we are building all outputs, so if more are wanted there
|
||||||
is no need to restart. */
|
is no need to restart. */
|
||||||
|
@ -403,14 +398,12 @@ void DerivationGoal::gaveUpOnSubstitution()
|
||||||
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i)));
|
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
if (!waitees.empty()) co_await Suspend{}; /* to prevent hang (no wake-up event) */
|
||||||
inputsRealised();
|
co_return inputsRealised();
|
||||||
else
|
|
||||||
state = &DerivationGoal::inputsRealised;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::repairClosure()
|
Goal::Co DerivationGoal::repairClosure()
|
||||||
{
|
{
|
||||||
assert(!drv->type().isImpure());
|
assert(!drv->type().isImpure());
|
||||||
|
|
||||||
|
@ -464,41 +457,39 @@ void DerivationGoal::repairClosure()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitees.empty()) {
|
if (waitees.empty()) {
|
||||||
done(BuildResult::AlreadyValid, assertPathValidity());
|
co_return done(BuildResult::AlreadyValid, assertPathValidity());
|
||||||
return;
|
} else {
|
||||||
|
co_await Suspend{};
|
||||||
|
co_return closureRepaired();
|
||||||
}
|
}
|
||||||
|
|
||||||
state = &DerivationGoal::closureRepaired;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::closureRepaired()
|
Goal::Co DerivationGoal::closureRepaired()
|
||||||
{
|
{
|
||||||
trace("closure repaired");
|
trace("closure repaired");
|
||||||
if (nrFailed > 0)
|
if (nrFailed > 0)
|
||||||
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
||||||
worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(drvPath));
|
||||||
done(BuildResult::AlreadyValid, assertPathValidity());
|
co_return done(BuildResult::AlreadyValid, assertPathValidity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::inputsRealised()
|
Goal::Co DerivationGoal::inputsRealised()
|
||||||
{
|
{
|
||||||
trace("all inputs realised");
|
trace("all inputs realised");
|
||||||
|
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
if (!useDerivation)
|
if (!useDerivation)
|
||||||
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
||||||
done(BuildResult::DependencyFailed, {}, Error(
|
co_return done(BuildResult::DependencyFailed, {}, Error(
|
||||||
"%s dependencies of derivation '%s' failed to build",
|
"%s dependencies of derivation '%s' failed to build",
|
||||||
nrFailed, worker.store.printStorePath(drvPath)));
|
nrFailed, worker.store.printStorePath(drvPath)));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrySubstitution == RetrySubstitution::YesNeed) {
|
if (retrySubstitution == RetrySubstitution::YesNeed) {
|
||||||
retrySubstitution = RetrySubstitution::AlreadyRetried;
|
retrySubstitution = RetrySubstitution::AlreadyRetried;
|
||||||
haveDerivation();
|
co_return haveDerivation();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gather information necessary for computing the closure and/or
|
/* Gather information necessary for computing the closure and/or
|
||||||
|
@ -564,8 +555,8 @@ void DerivationGoal::inputsRealised()
|
||||||
pathResolved, wantedOutputs, buildMode);
|
pathResolved, wantedOutputs, buildMode);
|
||||||
addWaitee(resolvedDrvGoal);
|
addWaitee(resolvedDrvGoal);
|
||||||
|
|
||||||
state = &DerivationGoal::resolvedFinished;
|
co_await Suspend{};
|
||||||
return;
|
co_return resolvedFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
|
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
|
||||||
|
@ -629,8 +620,9 @@ void DerivationGoal::inputsRealised()
|
||||||
/* Okay, try to build. Note that here we don't wait for a build
|
/* Okay, try to build. Note that here we don't wait for a build
|
||||||
slot to become available, since we don't need one if there is a
|
slot to become available, since we don't need one if there is a
|
||||||
build hook. */
|
build hook. */
|
||||||
state = &DerivationGoal::tryToBuild;
|
|
||||||
worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
|
co_await Suspend{};
|
||||||
|
co_return tryToBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::started()
|
void DerivationGoal::started()
|
||||||
|
@ -655,7 +647,7 @@ void DerivationGoal::started()
|
||||||
worker.updateProgress();
|
worker.updateProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::tryToBuild()
|
Goal::Co DerivationGoal::tryToBuild()
|
||||||
{
|
{
|
||||||
trace("trying to build");
|
trace("trying to build");
|
||||||
|
|
||||||
|
@ -691,7 +683,8 @@ void DerivationGoal::tryToBuild()
|
||||||
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
|
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
|
||||||
fmt("waiting for lock on %s", Magenta(showPaths(lockFiles))));
|
fmt("waiting for lock on %s", Magenta(showPaths(lockFiles))));
|
||||||
worker.waitForAWhile(shared_from_this());
|
worker.waitForAWhile(shared_from_this());
|
||||||
return;
|
co_await Suspend{};
|
||||||
|
co_return tryToBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
|
@ -708,8 +701,7 @@ void DerivationGoal::tryToBuild()
|
||||||
if (buildMode != bmCheck && allValid) {
|
if (buildMode != bmCheck && allValid) {
|
||||||
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
||||||
outputLocks.setDeletion(true);
|
outputLocks.setDeletion(true);
|
||||||
done(BuildResult::AlreadyValid, std::move(validOutputs));
|
co_return done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If any of the outputs already exist but are not valid, delete
|
/* If any of the outputs already exist but are not valid, delete
|
||||||
|
@ -735,9 +727,9 @@ void DerivationGoal::tryToBuild()
|
||||||
EOF from the hook. */
|
EOF from the hook. */
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
buildResult.startTime = time(0); // inexact
|
buildResult.startTime = time(0); // inexact
|
||||||
state = &DerivationGoal::buildDone;
|
|
||||||
started();
|
started();
|
||||||
return;
|
co_await Suspend{};
|
||||||
|
co_return buildDone();
|
||||||
case rpPostpone:
|
case rpPostpone:
|
||||||
/* Not now; wait until at least one child finishes or
|
/* Not now; wait until at least one child finishes or
|
||||||
the wake-up timeout expires. */
|
the wake-up timeout expires. */
|
||||||
|
@ -746,7 +738,8 @@ void DerivationGoal::tryToBuild()
|
||||||
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
|
||||||
worker.waitForAWhile(shared_from_this());
|
worker.waitForAWhile(shared_from_this());
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
return;
|
co_await Suspend{};
|
||||||
|
co_return tryToBuild();
|
||||||
case rpDecline:
|
case rpDecline:
|
||||||
/* We should do it ourselves. */
|
/* We should do it ourselves. */
|
||||||
break;
|
break;
|
||||||
|
@ -755,11 +748,12 @@ void DerivationGoal::tryToBuild()
|
||||||
|
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
|
|
||||||
state = &DerivationGoal::tryLocalBuild;
|
|
||||||
worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
|
co_await Suspend{};
|
||||||
|
co_return tryLocalBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::tryLocalBuild() {
|
Goal::Co DerivationGoal::tryLocalBuild() {
|
||||||
throw Error(
|
throw Error(
|
||||||
R"(
|
R"(
|
||||||
Unable to build with a primary store that isn't a local store;
|
Unable to build with a primary store that isn't a local store;
|
||||||
|
@ -936,7 +930,7 @@ void runPostBuildHook(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::buildDone()
|
Goal::Co DerivationGoal::buildDone()
|
||||||
{
|
{
|
||||||
trace("build done");
|
trace("build done");
|
||||||
|
|
||||||
|
@ -1031,7 +1025,7 @@ void DerivationGoal::buildDone()
|
||||||
outputLocks.setDeletion(true);
|
outputLocks.setDeletion(true);
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
done(BuildResult::Built, std::move(builtOutputs));
|
co_return done(BuildResult::Built, std::move(builtOutputs));
|
||||||
|
|
||||||
} catch (BuildError & e) {
|
} catch (BuildError & e) {
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
@ -1056,12 +1050,11 @@ void DerivationGoal::buildDone()
|
||||||
BuildResult::PermanentFailure;
|
BuildResult::PermanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
done(st, {}, std::move(e));
|
co_return done(st, {}, std::move(e));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::resolvedFinished()
|
Goal::Co DerivationGoal::resolvedFinished()
|
||||||
{
|
{
|
||||||
trace("resolved derivation finished");
|
trace("resolved derivation finished");
|
||||||
|
|
||||||
|
@ -1129,7 +1122,7 @@ void DerivationGoal::resolvedFinished()
|
||||||
if (status == BuildResult::AlreadyValid)
|
if (status == BuildResult::AlreadyValid)
|
||||||
status = BuildResult::ResolvesToAlreadyValid;
|
status = BuildResult::ResolvesToAlreadyValid;
|
||||||
|
|
||||||
done(status, std::move(builtOutputs));
|
co_return done(status, std::move(builtOutputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
HookReply DerivationGoal::tryBuildHook()
|
HookReply DerivationGoal::tryBuildHook()
|
||||||
|
@ -1323,7 +1316,9 @@ void DerivationGoal::handleChildOutput(Descriptor fd, std::string_view data)
|
||||||
logSize += data.size();
|
logSize += data.size();
|
||||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||||
killChild();
|
killChild();
|
||||||
done(
|
// We're not inside a coroutine, hence we can't use co_return here.
|
||||||
|
// Thus we ignore the return value.
|
||||||
|
[[maybe_unused]] Done _ = done(
|
||||||
BuildResult::LogLimitExceeded, {},
|
BuildResult::LogLimitExceeded, {},
|
||||||
Error("%s killed after writing more than %d bytes of log output",
|
Error("%s killed after writing more than %d bytes of log output",
|
||||||
getName(), settings.maxLogSize));
|
getName(), settings.maxLogSize));
|
||||||
|
@ -1529,7 +1524,7 @@ SingleDrvOutputs DerivationGoal::assertPathValidity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::done(
|
Goal::Done DerivationGoal::done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
SingleDrvOutputs builtOutputs,
|
SingleDrvOutputs builtOutputs,
|
||||||
std::optional<Error> ex)
|
std::optional<Error> ex)
|
||||||
|
@ -1566,7 +1561,7 @@ void DerivationGoal::done(
|
||||||
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
|
return amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -194,9 +194,6 @@ struct DerivationGoal : public Goal
|
||||||
*/
|
*/
|
||||||
std::optional<DerivationType> derivationType;
|
std::optional<DerivationType> derivationType;
|
||||||
|
|
||||||
typedef void (DerivationGoal::*GoalState)();
|
|
||||||
GoalState state;
|
|
||||||
|
|
||||||
BuildMode buildMode;
|
BuildMode buildMode;
|
||||||
|
|
||||||
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
|
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
|
||||||
|
@ -227,8 +224,6 @@ struct DerivationGoal : public Goal
|
||||||
|
|
||||||
std::string key() override;
|
std::string key() override;
|
||||||
|
|
||||||
void work() override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add wanted outputs to an already existing derivation goal.
|
* Add wanted outputs to an already existing derivation goal.
|
||||||
*/
|
*/
|
||||||
|
@ -237,18 +232,19 @@ struct DerivationGoal : public Goal
|
||||||
/**
|
/**
|
||||||
* The states.
|
* The states.
|
||||||
*/
|
*/
|
||||||
void getDerivation();
|
Co init() override;
|
||||||
void loadDerivation();
|
Co getDerivation();
|
||||||
void haveDerivation();
|
Co loadDerivation();
|
||||||
void outputsSubstitutionTried();
|
Co haveDerivation();
|
||||||
void gaveUpOnSubstitution();
|
Co outputsSubstitutionTried();
|
||||||
void closureRepaired();
|
Co gaveUpOnSubstitution();
|
||||||
void inputsRealised();
|
Co closureRepaired();
|
||||||
void tryToBuild();
|
Co inputsRealised();
|
||||||
virtual void tryLocalBuild();
|
Co tryToBuild();
|
||||||
void buildDone();
|
virtual Co tryLocalBuild();
|
||||||
|
Co buildDone();
|
||||||
|
|
||||||
void resolvedFinished();
|
Co resolvedFinished();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the build hook willing to perform the build?
|
* Is the build hook willing to perform the build?
|
||||||
|
@ -329,11 +325,11 @@ struct DerivationGoal : public Goal
|
||||||
*/
|
*/
|
||||||
virtual void killChild();
|
virtual void killChild();
|
||||||
|
|
||||||
void repairClosure();
|
Co repairClosure();
|
||||||
|
|
||||||
void started();
|
void started();
|
||||||
|
|
||||||
void done(
|
Done done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
SingleDrvOutputs builtOutputs = {},
|
SingleDrvOutputs builtOutputs = {},
|
||||||
std::optional<Error> ex = {});
|
std::optional<Error> ex = {});
|
||||||
|
|
|
@ -14,146 +14,135 @@ DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
||||||
: Goal(worker, DerivedPath::Opaque { StorePath::dummy })
|
: Goal(worker, DerivedPath::Opaque { StorePath::dummy })
|
||||||
, id(id)
|
, id(id)
|
||||||
{
|
{
|
||||||
state = &DrvOutputSubstitutionGoal::init;
|
|
||||||
name = fmt("substitution of '%s'", id.to_string());
|
name = fmt("substitution of '%s'", id.to_string());
|
||||||
trace("created");
|
trace("created");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::init()
|
Goal::Co DrvOutputSubstitutionGoal::init()
|
||||||
{
|
{
|
||||||
trace("init");
|
trace("init");
|
||||||
|
|
||||||
/* If the derivation already exists, we’re done */
|
/* If the derivation already exists, we’re done */
|
||||||
if (worker.store.queryRealisation(id)) {
|
if (worker.store.queryRealisation(id)) {
|
||||||
amDone(ecSuccess);
|
co_return amDone(ecSuccess);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||||
tryNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::tryNext()
|
bool substituterFailed = false;
|
||||||
{
|
|
||||||
trace("trying next substituter");
|
|
||||||
|
|
||||||
if (subs.size() == 0) {
|
for (auto sub : subs) {
|
||||||
/* None left. Terminate this goal and let someone else deal
|
trace("trying next substituter");
|
||||||
with it. */
|
|
||||||
debug("derivation output '%s' is required, but there is no substituter that can provide it", id.to_string());
|
|
||||||
|
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* The callback of the curl download below can outlive `this` (if
|
||||||
In that case the calling derivation should just do a
|
some other error occurs), so it must not touch `this`. So put
|
||||||
build. */
|
the shared state in a separate refcounted object. */
|
||||||
amDone(substituterFailed ? ecFailed : ecNoSubstituters);
|
auto outPipe = std::make_shared<MuxablePipe>();
|
||||||
|
#ifndef _WIN32
|
||||||
|
outPipe->create();
|
||||||
|
#else
|
||||||
|
outPipe->createAsyncPipe(worker.ioport.get());
|
||||||
|
#endif
|
||||||
|
|
||||||
if (substituterFailed) {
|
auto promise = std::make_shared<std::promise<std::shared_ptr<const Realisation>>>();
|
||||||
worker.failedSubstitutions++;
|
|
||||||
worker.updateProgress();
|
sub->queryRealisation(
|
||||||
|
id,
|
||||||
|
{ [outPipe(outPipe), promise(promise)](std::future<std::shared_ptr<const Realisation>> res) {
|
||||||
|
try {
|
||||||
|
Finally updateStats([&]() { outPipe->writeSide.close(); });
|
||||||
|
promise->set_value(res.get());
|
||||||
|
} catch (...) {
|
||||||
|
promise->set_exception(std::current_exception());
|
||||||
|
}
|
||||||
|
} });
|
||||||
|
|
||||||
|
worker.childStarted(shared_from_this(), {
|
||||||
|
#ifndef _WIN32
|
||||||
|
outPipe->readSide.get()
|
||||||
|
#else
|
||||||
|
&outPipe
|
||||||
|
#endif
|
||||||
|
}, true, false);
|
||||||
|
|
||||||
|
co_await Suspend{};
|
||||||
|
|
||||||
|
worker.childTerminated(this);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The realisation corresponding to the given output id.
|
||||||
|
* Will be filled once we can get it.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<const Realisation> outputInfo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
outputInfo = promise->get_future().get();
|
||||||
|
} catch (std::exception & e) {
|
||||||
|
printError(e.what());
|
||||||
|
substituterFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
if (!outputInfo) continue;
|
||||||
|
|
||||||
|
bool failed = false;
|
||||||
|
|
||||||
|
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
|
||||||
|
if (depId != id) {
|
||||||
|
if (auto localOutputInfo = worker.store.queryRealisation(depId);
|
||||||
|
localOutputInfo && localOutputInfo->outPath != depPath) {
|
||||||
|
warn(
|
||||||
|
"substituter '%s' has an incompatible realisation for '%s', ignoring.\n"
|
||||||
|
"Local: %s\n"
|
||||||
|
"Remote: %s",
|
||||||
|
sub->getUri(),
|
||||||
|
depId.to_string(),
|
||||||
|
worker.store.printStorePath(localOutputInfo->outPath),
|
||||||
|
worker.store.printStorePath(depPath)
|
||||||
|
);
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
addWaitee(worker.makeDrvOutputSubstitutionGoal(depId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed) continue;
|
||||||
|
|
||||||
|
co_return realisationFetched(outputInfo, sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub = subs.front();
|
/* None left. Terminate this goal and let someone else deal
|
||||||
subs.pop_front();
|
with it. */
|
||||||
|
debug("derivation output '%s' is required, but there is no substituter that can provide it", id.to_string());
|
||||||
|
|
||||||
// FIXME: Make async
|
if (substituterFailed) {
|
||||||
// outputInfo = sub->queryRealisation(id);
|
worker.failedSubstitutions++;
|
||||||
|
worker.updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
/* The callback of the curl download below can outlive `this` (if
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
some other error occurs), so it must not touch `this`. So put
|
In that case the calling derivation should just do a
|
||||||
the shared state in a separate refcounted object. */
|
build. */
|
||||||
downloadState = std::make_shared<DownloadState>();
|
co_return amDone(substituterFailed ? ecFailed : ecNoSubstituters);
|
||||||
#ifndef _WIN32
|
|
||||||
downloadState->outPipe.create();
|
|
||||||
#else
|
|
||||||
downloadState->outPipe.createAsyncPipe(worker.ioport.get());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
sub->queryRealisation(
|
|
||||||
id,
|
|
||||||
{ [downloadState(downloadState)](std::future<std::shared_ptr<const Realisation>> res) {
|
|
||||||
try {
|
|
||||||
Finally updateStats([&]() { downloadState->outPipe.writeSide.close(); });
|
|
||||||
downloadState->promise.set_value(res.get());
|
|
||||||
} catch (...) {
|
|
||||||
downloadState->promise.set_exception(std::current_exception());
|
|
||||||
}
|
|
||||||
} });
|
|
||||||
|
|
||||||
worker.childStarted(shared_from_this(), {
|
|
||||||
#ifndef _WIN32
|
|
||||||
downloadState->outPipe.readSide.get()
|
|
||||||
#else
|
|
||||||
&downloadState->outPipe
|
|
||||||
#endif
|
|
||||||
}, true, false);
|
|
||||||
|
|
||||||
state = &DrvOutputSubstitutionGoal::realisationFetched;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::realisationFetched()
|
Goal::Co DrvOutputSubstitutionGoal::realisationFetched(std::shared_ptr<const Realisation> outputInfo, nix::ref<nix::Store> sub) {
|
||||||
{
|
|
||||||
worker.childTerminated(this);
|
|
||||||
|
|
||||||
try {
|
|
||||||
outputInfo = downloadState->promise.get_future().get();
|
|
||||||
} catch (std::exception & e) {
|
|
||||||
printError(e.what());
|
|
||||||
substituterFailed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!outputInfo) {
|
|
||||||
return tryNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
|
|
||||||
if (depId != id) {
|
|
||||||
if (auto localOutputInfo = worker.store.queryRealisation(depId);
|
|
||||||
localOutputInfo && localOutputInfo->outPath != depPath) {
|
|
||||||
warn(
|
|
||||||
"substituter '%s' has an incompatible realisation for '%s', ignoring.\n"
|
|
||||||
"Local: %s\n"
|
|
||||||
"Remote: %s",
|
|
||||||
sub->getUri(),
|
|
||||||
depId.to_string(),
|
|
||||||
worker.store.printStorePath(localOutputInfo->outPath),
|
|
||||||
worker.store.printStorePath(depPath)
|
|
||||||
);
|
|
||||||
tryNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addWaitee(worker.makeDrvOutputSubstitutionGoal(depId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath));
|
addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath));
|
||||||
|
|
||||||
if (waitees.empty()) outPathValid();
|
if (!waitees.empty()) co_await Suspend{};
|
||||||
else state = &DrvOutputSubstitutionGoal::outPathValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::outPathValid()
|
|
||||||
{
|
|
||||||
assert(outputInfo);
|
|
||||||
trace("output path substituted");
|
trace("output path substituted");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
||||||
amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
co_return amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.store.registerDrvOutput(*outputInfo);
|
worker.store.registerDrvOutput(*outputInfo);
|
||||||
finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::finished()
|
|
||||||
{
|
|
||||||
trace("finished");
|
trace("finished");
|
||||||
amDone(ecSuccess);
|
co_return amDone(ecSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DrvOutputSubstitutionGoal::key()
|
std::string DrvOutputSubstitutionGoal::key()
|
||||||
|
@ -163,14 +152,9 @@ std::string DrvOutputSubstitutionGoal::key()
|
||||||
return "a$" + std::string(id.to_string());
|
return "a$" + std::string(id.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::work()
|
|
||||||
{
|
|
||||||
(this->*state)();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::handleEOF(Descriptor fd)
|
void DrvOutputSubstitutionGoal::handleEOF(Descriptor fd)
|
||||||
{
|
{
|
||||||
if (fd == downloadState->outPipe.readSide.get()) worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,52 +27,19 @@ class DrvOutputSubstitutionGoal : public Goal {
|
||||||
*/
|
*/
|
||||||
DrvOutput id;
|
DrvOutput id;
|
||||||
|
|
||||||
/**
|
|
||||||
* The realisation corresponding to the given output id.
|
|
||||||
* Will be filled once we can get it.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<const Realisation> outputInfo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The remaining substituters.
|
|
||||||
*/
|
|
||||||
std::list<ref<Store>> subs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current substituter.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<Store> sub;
|
|
||||||
|
|
||||||
struct DownloadState
|
|
||||||
{
|
|
||||||
MuxablePipe outPipe;
|
|
||||||
std::promise<std::shared_ptr<const Realisation>> promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<DownloadState> downloadState;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether a substituter failed.
|
|
||||||
*/
|
|
||||||
bool substituterFailed = false;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
|
|
||||||
typedef void (DrvOutputSubstitutionGoal::*GoalState)();
|
typedef void (DrvOutputSubstitutionGoal::*GoalState)();
|
||||||
GoalState state;
|
GoalState state;
|
||||||
|
|
||||||
void init();
|
Co init() override;
|
||||||
void tryNext();
|
Co realisationFetched(std::shared_ptr<const Realisation> outputInfo, nix::ref<nix::Store> sub);
|
||||||
void realisationFetched();
|
|
||||||
void outPathValid();
|
|
||||||
void finished();
|
|
||||||
|
|
||||||
void timedOut(Error && ex) override { abort(); };
|
void timedOut(Error && ex) override { abort(); };
|
||||||
|
|
||||||
std::string key() override;
|
std::string key() override;
|
||||||
|
|
||||||
void work() override;
|
|
||||||
void handleEOF(Descriptor fd) override;
|
void handleEOF(Descriptor fd) override;
|
||||||
|
|
||||||
JobCategory jobCategory() const override {
|
JobCategory jobCategory() const override {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# include "derivation-goal.hh"
|
# include "derivation-goal.hh"
|
||||||
#endif
|
#endif
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,97 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
using Co = nix::Goal::Co;
|
||||||
|
using promise_type = nix::Goal::promise_type;
|
||||||
|
using handle_type = nix::Goal::handle_type;
|
||||||
|
using Suspend = nix::Goal::Suspend;
|
||||||
|
|
||||||
|
Co::Co(Co&& rhs) {
|
||||||
|
this->handle = rhs.handle;
|
||||||
|
rhs.handle = nullptr;
|
||||||
|
}
|
||||||
|
void Co::operator=(Co&& rhs) {
|
||||||
|
this->handle = rhs.handle;
|
||||||
|
rhs.handle = nullptr;
|
||||||
|
}
|
||||||
|
Co::~Co() {
|
||||||
|
if (handle) {
|
||||||
|
handle.promise().alive = false;
|
||||||
|
handle.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Co promise_type::get_return_object() {
|
||||||
|
auto handle = handle_type::from_promise(*this);
|
||||||
|
return Co{handle};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::coroutine_handle<> promise_type::final_awaiter::await_suspend(handle_type h) noexcept {
|
||||||
|
auto& p = h.promise();
|
||||||
|
auto goal = p.goal;
|
||||||
|
assert(goal);
|
||||||
|
goal->trace("in final_awaiter");
|
||||||
|
auto c = std::move(p.continuation);
|
||||||
|
|
||||||
|
if (c) {
|
||||||
|
// We still have a continuation, i.e. work to do.
|
||||||
|
// We assert that the goal is still busy.
|
||||||
|
assert(goal->exitCode == ecBusy);
|
||||||
|
assert(goal->top_co); // Goal must have an active coroutine.
|
||||||
|
assert(goal->top_co->handle == h); // The active coroutine must be us.
|
||||||
|
assert(p.alive); // We must not have been destructed.
|
||||||
|
|
||||||
|
// we move continuation to the top,
|
||||||
|
// note: previous top_co is actually h, so by moving into it,
|
||||||
|
// we're calling the destructor on h, DON'T use h and p after this!
|
||||||
|
|
||||||
|
// We move our continuation into `top_co`, i.e. the marker for the active continuation.
|
||||||
|
// By doing this we destruct the old `top_co`, i.e. us, so `h` can't be used anymore.
|
||||||
|
// Be careful not to access freed memory!
|
||||||
|
goal->top_co = std::move(c);
|
||||||
|
|
||||||
|
// We resume `top_co`.
|
||||||
|
return goal->top_co->handle;
|
||||||
|
} else {
|
||||||
|
// We have no continuation, i.e. no more work to do,
|
||||||
|
// so the goal must not be busy anymore.
|
||||||
|
assert(goal->exitCode != ecBusy);
|
||||||
|
|
||||||
|
// We reset `top_co` for good measure.
|
||||||
|
p.goal->top_co = {};
|
||||||
|
|
||||||
|
// We jump to the noop coroutine, which doesn't do anything and immediately suspends.
|
||||||
|
// This passes control back to the caller of goal.work().
|
||||||
|
return std::noop_coroutine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void promise_type::return_value(Co&& next) {
|
||||||
|
goal->trace("return_value(Co&&)");
|
||||||
|
// Save old continuation.
|
||||||
|
auto old_continuation = std::move(continuation);
|
||||||
|
// We set next as our continuation.
|
||||||
|
continuation = std::move(next);
|
||||||
|
// We set next's goal, and thus it must not have one already.
|
||||||
|
assert(!continuation->handle.promise().goal);
|
||||||
|
continuation->handle.promise().goal = goal;
|
||||||
|
// Nor can next have a continuation, as we set it to our old one.
|
||||||
|
assert(!continuation->handle.promise().continuation);
|
||||||
|
continuation->handle.promise().continuation = std::move(old_continuation);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::coroutine_handle<> nix::Goal::Co::await_suspend(handle_type caller) {
|
||||||
|
assert(handle); // we must be a valid coroutine
|
||||||
|
auto& p = handle.promise();
|
||||||
|
assert(!p.continuation); // we must have no continuation
|
||||||
|
assert(!p.goal); // we must not have a goal yet
|
||||||
|
auto goal = caller.promise().goal;
|
||||||
|
assert(goal);
|
||||||
|
p.goal = goal;
|
||||||
|
p.continuation = std::move(goal->top_co); // we set our continuation to be top_co (i.e. caller)
|
||||||
|
goal->top_co = std::move(*this); // we set top_co to ourselves, don't use this anymore after this!
|
||||||
|
return p.goal->top_co->handle; // we execute ourselves
|
||||||
|
}
|
||||||
|
|
||||||
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
||||||
std::string s1 = a->key();
|
std::string s1 = a->key();
|
||||||
|
@ -75,10 +166,10 @@ void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Goal::Done Goal::amDone(ExitCode result, std::optional<Error> ex)
|
||||||
void Goal::amDone(ExitCode result, std::optional<Error> ex)
|
|
||||||
{
|
{
|
||||||
trace("done");
|
trace("done");
|
||||||
|
assert(top_co);
|
||||||
assert(exitCode == ecBusy);
|
assert(exitCode == ecBusy);
|
||||||
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
|
assert(result == ecSuccess || result == ecFailed || result == ecNoSubstituters || result == ecIncompleteClosure);
|
||||||
exitCode = result;
|
exitCode = result;
|
||||||
|
@ -98,6 +189,13 @@ void Goal::amDone(ExitCode result, std::optional<Error> ex)
|
||||||
worker.removeGoal(shared_from_this());
|
worker.removeGoal(shared_from_this());
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
|
// We drop the continuation.
|
||||||
|
// In `final_awaiter` this will signal that there is no more work to be done.
|
||||||
|
top_co->handle.promise().continuation = {};
|
||||||
|
|
||||||
|
// won't return to caller because of logic in final_awaiter
|
||||||
|
return Done{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,4 +204,16 @@ void Goal::trace(std::string_view s)
|
||||||
debug("%1%: %2%", name, s);
|
debug("%1%: %2%", name, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Goal::work()
|
||||||
|
{
|
||||||
|
assert(top_co);
|
||||||
|
assert(top_co->handle);
|
||||||
|
assert(top_co->handle.promise().alive);
|
||||||
|
top_co->handle.resume();
|
||||||
|
// We either should be in a state where we can be work()-ed again,
|
||||||
|
// or we should be done.
|
||||||
|
assert(top_co || exitCode != ecBusy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "types.hh"
|
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "build-result.hh"
|
#include "build-result.hh"
|
||||||
|
|
||||||
|
#include <coroutine>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,9 +104,263 @@ protected:
|
||||||
* Build result.
|
* Build result.
|
||||||
*/
|
*/
|
||||||
BuildResult buildResult;
|
BuildResult buildResult;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspend our goal and wait until we get @ref work()-ed again.
|
||||||
|
* `co_await`-able by @ref Co.
|
||||||
|
*/
|
||||||
|
struct Suspend {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return from the current coroutine and suspend our goal
|
||||||
|
* if we're not busy anymore, or jump to the next coroutine
|
||||||
|
* set to be executed/resumed.
|
||||||
|
*/
|
||||||
|
struct Return {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `co_return`-ing this will end the goal.
|
||||||
|
* If you're not inside a coroutine, you can safely discard this.
|
||||||
|
*/
|
||||||
|
struct [[nodiscard]] Done {
|
||||||
|
private:
|
||||||
|
Done(){}
|
||||||
|
|
||||||
|
friend Goal;
|
||||||
|
};
|
||||||
|
|
||||||
|
// forward declaration of promise_type, see below
|
||||||
|
struct promise_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle to coroutine using @ref Co and @ref promise_type.
|
||||||
|
*/
|
||||||
|
using handle_type = std::coroutine_handle<promise_type>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C++20 coroutine wrapper for use in goal logic.
|
||||||
|
* Coroutines are functions that use `co_await`/`co_return` (and `co_yield`, but not supported by @ref Co).
|
||||||
|
*
|
||||||
|
* @ref Co is meant to be used by methods of subclasses of @ref Goal.
|
||||||
|
* The main functionality provided by `Co` is
|
||||||
|
* - `co_await Suspend{}`: Suspends the goal.
|
||||||
|
* - `co_await f()`: Waits until `f()` finishes.
|
||||||
|
* - `co_return f()`: Tail-calls `f()`.
|
||||||
|
* - `co_return Return{}`: Ends coroutine.
|
||||||
|
*
|
||||||
|
* The idea is that you implement the goal logic using coroutines,
|
||||||
|
* and do the core thing a goal can do, suspension, when you have
|
||||||
|
* children you're waiting for.
|
||||||
|
* Coroutines allow you to resume the work cleanly.
|
||||||
|
*
|
||||||
|
* @note Brief explanation of C++20 coroutines:
|
||||||
|
* When you `Co f()`, a `std::coroutine_handle<promise_type>` is created,
|
||||||
|
* alongside its @ref promise_type.
|
||||||
|
* There are suspension points at the beginning of the coroutine,
|
||||||
|
* at every `co_await`, and at the final (possibly implicit) `co_return`.
|
||||||
|
* Once suspended, you can resume the `std::coroutine_handle` by doing `coroutine_handle.resume()`.
|
||||||
|
* Suspension points are implemented by passing a struct to the compiler
|
||||||
|
* that implements `await_sus`pend.
|
||||||
|
* `await_suspend` can either say "cancel suspension", in which case execution resumes,
|
||||||
|
* "suspend", in which case control is passed back to the caller of `coroutine_handle.resume()`
|
||||||
|
* or the place where the coroutine function is initially executed in the case of the initial
|
||||||
|
* suspension, or `await_suspend` can specify another coroutine to jump to, which is
|
||||||
|
* how tail calls are implemented.
|
||||||
|
*
|
||||||
|
* @note Resources:
|
||||||
|
* - https://lewissbaker.github.io/
|
||||||
|
* - https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/coroutines-c++20/
|
||||||
|
* - https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html
|
||||||
|
*
|
||||||
|
* @todo Allocate explicitly on stack since HALO thing doesn't really work,
|
||||||
|
* specifically, there's no way to uphold the requirements when trying to do
|
||||||
|
* tail-calls without using a trampoline AFAICT.
|
||||||
|
*
|
||||||
|
* @todo Support returning data natively
|
||||||
|
*/
|
||||||
|
struct [[nodiscard]] Co {
|
||||||
|
/**
|
||||||
|
* The underlying handle.
|
||||||
|
*/
|
||||||
|
handle_type handle;
|
||||||
|
|
||||||
|
explicit Co(handle_type handle) : handle(handle) {};
|
||||||
|
void operator=(Co&&);
|
||||||
|
Co(Co&& rhs);
|
||||||
|
~Co();
|
||||||
|
|
||||||
|
bool await_ready() { return false; };
|
||||||
|
/**
|
||||||
|
* When we `co_await` another @ref Co-returning coroutine,
|
||||||
|
* we tell the caller of `caller_coroutine.resume()` to switch to our coroutine (@ref handle).
|
||||||
|
* To make sure we return to the original coroutine, we set it as the continuation of our
|
||||||
|
* coroutine. In @ref promise_type::final_awaiter we check if it's set and if so we return to it.
|
||||||
|
*
|
||||||
|
* To explain in more understandable terms:
|
||||||
|
* When we `co_await Co_returning_function()`, this function is called on the resultant @ref Co of
|
||||||
|
* the _called_ function, and C++ automatically passes the caller in.
|
||||||
|
*
|
||||||
|
* `goal` field of @ref promise_type is also set here by copying it from the caller.
|
||||||
|
*/
|
||||||
|
std::coroutine_handle<> await_suspend(handle_type handle);
|
||||||
|
void await_resume() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used on initial suspend, does the same as @ref std::suspend_always,
|
||||||
|
* but asserts that everything has been set correctly.
|
||||||
|
*/
|
||||||
|
struct InitialSuspend {
|
||||||
|
/**
|
||||||
|
* Handle of coroutine that does the
|
||||||
|
* initial suspend
|
||||||
|
*/
|
||||||
|
handle_type handle;
|
||||||
|
|
||||||
|
bool await_ready() { return false; };
|
||||||
|
void await_suspend(handle_type handle_) {
|
||||||
|
handle = handle_;
|
||||||
|
}
|
||||||
|
void await_resume() {
|
||||||
|
assert(handle);
|
||||||
|
assert(handle.promise().goal); // goal must be set
|
||||||
|
assert(handle.promise().goal->top_co); // top_co of goal must be set
|
||||||
|
assert(handle.promise().goal->top_co->handle == handle); // top_co of goal must be us
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promise type for coroutines defined using @ref Co.
|
||||||
|
* Attached to coroutine handle.
|
||||||
|
*/
|
||||||
|
struct promise_type {
|
||||||
|
/**
|
||||||
|
* Either this is who called us, or it is who we will tail-call.
|
||||||
|
* It is what we "jump" to once we are done.
|
||||||
|
*/
|
||||||
|
std::optional<Co> continuation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The goal that we're a part of.
|
||||||
|
* Set either in @ref Co::await_suspend or in constructor of @ref Goal.
|
||||||
|
*/
|
||||||
|
Goal* goal = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is set to false when destructed to ensure we don't use a
|
||||||
|
* destructed coroutine by accident
|
||||||
|
*/
|
||||||
|
bool alive = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The awaiter used by @ref final_suspend.
|
||||||
|
*/
|
||||||
|
struct final_awaiter {
|
||||||
|
bool await_ready() noexcept { return false; };
|
||||||
|
/**
|
||||||
|
* Here we execute our continuation, by passing it back to the caller.
|
||||||
|
* C++ compiler will create code that takes that and executes it promptly.
|
||||||
|
* `h` is the handle for the coroutine that is finishing execution,
|
||||||
|
* thus it must be destroyed.
|
||||||
|
*/
|
||||||
|
std::coroutine_handle<> await_suspend(handle_type h) noexcept;
|
||||||
|
void await_resume() noexcept { assert(false); };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by compiler generated code to construct the @ref Co
|
||||||
|
* that is returned from a @ref Co-returning coroutine.
|
||||||
|
*/
|
||||||
|
Co get_return_object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by compiler generated code before body of coroutine.
|
||||||
|
* We use this opportunity to set the @ref goal field
|
||||||
|
* and `top_co` field of @ref Goal.
|
||||||
|
*/
|
||||||
|
InitialSuspend initial_suspend() { return {}; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on `co_return`. Creates @ref final_awaiter which
|
||||||
|
* either jumps to continuation or suspends goal.
|
||||||
|
*/
|
||||||
|
final_awaiter final_suspend() noexcept { return {}; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing, but provides an opportunity for
|
||||||
|
* @ref final_suspend to happen.
|
||||||
|
*/
|
||||||
|
void return_value(Return) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing, but provides an opportunity for
|
||||||
|
* @ref final_suspend to happen.
|
||||||
|
*/
|
||||||
|
void return_value(Done) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When "returning" another coroutine, what happens is that
|
||||||
|
* we set it as our own continuation, thus once the final suspend
|
||||||
|
* happens, we transfer control to it.
|
||||||
|
* The original continuation we had is set as the continuation
|
||||||
|
* of the coroutine passed in.
|
||||||
|
* @ref final_suspend is called after this, and @ref final_awaiter will
|
||||||
|
* pass control off to @ref continuation.
|
||||||
|
*
|
||||||
|
* If we already have a continuation, that continuation is set as
|
||||||
|
* the continuation of the new continuation. Thus, the continuation
|
||||||
|
* passed to @ref return_value must not have a continuation set.
|
||||||
|
*/
|
||||||
|
void return_value(Co&&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an exception is thrown inside a coroutine,
|
||||||
|
* we re-throw it in the context of the "resumer" of the continuation.
|
||||||
|
*/
|
||||||
|
void unhandled_exception() { throw; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows awaiting a @ref Co.
|
||||||
|
*/
|
||||||
|
Co&& await_transform(Co&& co) { return static_cast<Co&&>(co); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows awaiting a @ref Suspend.
|
||||||
|
* Always suspends.
|
||||||
|
*/
|
||||||
|
std::suspend_always await_transform(Suspend) { return {}; };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The coroutine being currently executed.
|
||||||
|
* MUST be updated when switching the coroutine being executed.
|
||||||
|
* This is used both for memory management and to resume the last
|
||||||
|
* coroutine executed.
|
||||||
|
* Destroying this should destroy all coroutines created for this goal.
|
||||||
|
*/
|
||||||
|
std::optional<Co> top_co;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entry point for the goal
|
||||||
|
*/
|
||||||
|
virtual Co init() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around @ref init since virtual functions
|
||||||
|
* can't be used in constructors.
|
||||||
|
*/
|
||||||
|
inline Co init_wrapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that the goal is done.
|
||||||
|
* `co_return` the result. If you're not inside a coroutine, you can ignore
|
||||||
|
* the return value safely.
|
||||||
|
*/
|
||||||
|
Done amDone(ExitCode result, std::optional<Error> ex = {});
|
||||||
|
|
||||||
|
virtual void cleanup() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project a `BuildResult` with just the information that pertains
|
* Project a `BuildResult` with just the information that pertains
|
||||||
* to the given request.
|
* to the given request.
|
||||||
|
@ -124,15 +379,20 @@ public:
|
||||||
std::optional<Error> ex;
|
std::optional<Error> ex;
|
||||||
|
|
||||||
Goal(Worker & worker, DerivedPath path)
|
Goal(Worker & worker, DerivedPath path)
|
||||||
: worker(worker)
|
: worker(worker), top_co(init_wrapper())
|
||||||
{ }
|
{
|
||||||
|
// top_co shouldn't have a goal already, should be nullptr.
|
||||||
|
assert(!top_co->handle.promise().goal);
|
||||||
|
// we set it such that top_co can pass it down to its subcoroutines.
|
||||||
|
top_co->handle.promise().goal = this;
|
||||||
|
}
|
||||||
|
|
||||||
virtual ~Goal()
|
virtual ~Goal()
|
||||||
{
|
{
|
||||||
trace("goal destroyed");
|
trace("goal destroyed");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void work() = 0;
|
void work();
|
||||||
|
|
||||||
void addWaitee(GoalPtr waitee);
|
void addWaitee(GoalPtr waitee);
|
||||||
|
|
||||||
|
@ -164,10 +424,6 @@ public:
|
||||||
|
|
||||||
virtual std::string key() = 0;
|
virtual std::string key() = 0;
|
||||||
|
|
||||||
void amDone(ExitCode result, std::optional<Error> ex = {});
|
|
||||||
|
|
||||||
virtual void cleanup() { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Hint for the scheduler, which concurrency limit applies.
|
* @brief Hint for the scheduler, which concurrency limit applies.
|
||||||
* @see JobCategory
|
* @see JobCategory
|
||||||
|
@ -178,3 +434,12 @@ public:
|
||||||
void addToWeakGoals(WeakGoals & goals, GoalPtr p);
|
void addToWeakGoals(WeakGoals & goals, GoalPtr p);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename... ArgTypes>
|
||||||
|
struct std::coroutine_traits<nix::Goal::Co, ArgTypes...> {
|
||||||
|
using promise_type = nix::Goal::promise_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
nix::Goal::Co nix::Goal::init_wrapper() {
|
||||||
|
co_return init();
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "nar-info.hh"
|
#include "nar-info.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
|
#include <coroutine>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -12,7 +13,6 @@ PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker &
|
||||||
, repair(repair)
|
, repair(repair)
|
||||||
, ca(ca)
|
, ca(ca)
|
||||||
{
|
{
|
||||||
state = &PathSubstitutionGoal::init;
|
|
||||||
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
|
||||||
trace("created");
|
trace("created");
|
||||||
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
|
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
|
||||||
|
@ -25,7 +25,7 @@ PathSubstitutionGoal::~PathSubstitutionGoal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::done(
|
Goal::Done PathSubstitutionGoal::done(
|
||||||
ExitCode result,
|
ExitCode result,
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
std::optional<std::string> errorMsg)
|
std::optional<std::string> errorMsg)
|
||||||
|
@ -35,17 +35,11 @@ void PathSubstitutionGoal::done(
|
||||||
debug(*errorMsg);
|
debug(*errorMsg);
|
||||||
buildResult.errorMsg = *errorMsg;
|
buildResult.errorMsg = *errorMsg;
|
||||||
}
|
}
|
||||||
amDone(result);
|
return amDone(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::work()
|
Goal::Co PathSubstitutionGoal::init()
|
||||||
{
|
|
||||||
(this->*state)();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::init()
|
|
||||||
{
|
{
|
||||||
trace("init");
|
trace("init");
|
||||||
|
|
||||||
|
@ -53,152 +47,135 @@ void PathSubstitutionGoal::init()
|
||||||
|
|
||||||
/* If the path already exists we're done. */
|
/* If the path already exists we're done. */
|
||||||
if (!repair && worker.store.isValidPath(storePath)) {
|
if (!repair && worker.store.isValidPath(storePath)) {
|
||||||
done(ecSuccess, BuildResult::AlreadyValid);
|
co_return done(ecSuccess, BuildResult::AlreadyValid);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.readOnlyMode)
|
if (settings.readOnlyMode)
|
||||||
throw Error("cannot substitute path '%s' - no write access to the Nix store", worker.store.printStorePath(storePath));
|
throw Error("cannot substitute path '%s' - no write access to the Nix store", worker.store.printStorePath(storePath));
|
||||||
|
|
||||||
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
auto subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
|
||||||
|
|
||||||
tryNext();
|
bool substituterFailed = false;
|
||||||
}
|
|
||||||
|
|
||||||
|
for (auto sub : subs) {
|
||||||
|
trace("trying next substituter");
|
||||||
|
|
||||||
void PathSubstitutionGoal::tryNext()
|
cleanup();
|
||||||
{
|
|
||||||
trace("trying next substituter");
|
|
||||||
|
|
||||||
cleanup();
|
/* The path the substituter refers to the path as. This will be
|
||||||
|
* different when the stores have different names. */
|
||||||
|
std::optional<StorePath> subPath;
|
||||||
|
|
||||||
if (subs.size() == 0) {
|
/* Path info returned by the substituter's query info operation. */
|
||||||
/* None left. Terminate this goal and let someone else deal
|
std::shared_ptr<const ValidPathInfo> info;
|
||||||
with it. */
|
|
||||||
|
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
if (ca) {
|
||||||
In that case the calling derivation should just do a
|
subPath = sub->makeFixedOutputPathFromCA(
|
||||||
build. */
|
std::string { storePath.name() },
|
||||||
done(
|
ContentAddressWithReferences::withoutRefs(*ca));
|
||||||
substituterFailed ? ecFailed : ecNoSubstituters,
|
if (sub->storeDir == worker.store.storeDir)
|
||||||
BuildResult::NoSubstituters,
|
assert(subPath == storePath);
|
||||||
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
|
} else if (sub->storeDir != worker.store.storeDir) {
|
||||||
|
continue;
|
||||||
if (substituterFailed) {
|
|
||||||
worker.failedSubstitutions++;
|
|
||||||
worker.updateProgress();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
try {
|
||||||
}
|
// FIXME: make async
|
||||||
|
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
||||||
sub = subs.front();
|
} catch (InvalidPath &) {
|
||||||
subs.pop_front();
|
continue;
|
||||||
|
} catch (SubstituterDisabled & e) {
|
||||||
if (ca) {
|
if (settings.tryFallback) continue;
|
||||||
subPath = sub->makeFixedOutputPathFromCA(
|
else throw e;
|
||||||
std::string { storePath.name() },
|
} catch (Error & e) {
|
||||||
ContentAddressWithReferences::withoutRefs(*ca));
|
if (settings.tryFallback) {
|
||||||
if (sub->storeDir == worker.store.storeDir)
|
logError(e.info());
|
||||||
assert(subPath == storePath);
|
continue;
|
||||||
} else if (sub->storeDir != worker.store.storeDir) {
|
} else throw e;
|
||||||
tryNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// FIXME: make async
|
|
||||||
info = sub->queryPathInfo(subPath ? *subPath : storePath);
|
|
||||||
} catch (InvalidPath &) {
|
|
||||||
tryNext();
|
|
||||||
return;
|
|
||||||
} catch (SubstituterDisabled &) {
|
|
||||||
if (settings.tryFallback) {
|
|
||||||
tryNext();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
throw;
|
|
||||||
} catch (Error & e) {
|
if (info->path != storePath) {
|
||||||
if (settings.tryFallback) {
|
if (info->isContentAddressed(*sub) && info->references.empty()) {
|
||||||
logError(e.info());
|
auto info2 = std::make_shared<ValidPathInfo>(*info);
|
||||||
tryNext();
|
info2->path = storePath;
|
||||||
return;
|
info = info2;
|
||||||
|
} else {
|
||||||
|
printError("asked '%s' for '%s' but got '%s'",
|
||||||
|
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw;
|
|
||||||
|
/* Update the total expected download size. */
|
||||||
|
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
||||||
|
|
||||||
|
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
|
||||||
|
|
||||||
|
maintainExpectedDownload =
|
||||||
|
narInfo && narInfo->fileSize
|
||||||
|
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
worker.updateProgress();
|
||||||
|
|
||||||
|
/* Bail out early if this substituter lacks a valid
|
||||||
|
signature. LocalStore::addToStore() also checks for this, but
|
||||||
|
only after we've downloaded the path. */
|
||||||
|
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
|
||||||
|
{
|
||||||
|
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
|
||||||
|
worker.store.printStorePath(storePath), sub->getUri());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To maintain the closure invariant, we first have to realise the
|
||||||
|
paths referenced by this one. */
|
||||||
|
for (auto & i : info->references)
|
||||||
|
if (i != storePath) /* ignore self-references */
|
||||||
|
addWaitee(worker.makePathSubstitutionGoal(i));
|
||||||
|
|
||||||
|
if (!waitees.empty()) co_await Suspend{};
|
||||||
|
|
||||||
|
// FIXME: consider returning boolean instead of passing in reference
|
||||||
|
bool out = false; // is mutated by tryToRun
|
||||||
|
co_await tryToRun(subPath ? *subPath : storePath, sub, info, out);
|
||||||
|
substituterFailed = substituterFailed || out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->path != storePath) {
|
/* None left. Terminate this goal and let someone else deal
|
||||||
if (info->isContentAddressed(*sub) && info->references.empty()) {
|
with it. */
|
||||||
auto info2 = std::make_shared<ValidPathInfo>(*info);
|
|
||||||
info2->path = storePath;
|
|
||||||
info = info2;
|
|
||||||
} else {
|
|
||||||
printError("asked '%s' for '%s' but got '%s'",
|
|
||||||
sub->getUri(), worker.store.printStorePath(storePath), sub->printStorePath(info->path));
|
|
||||||
tryNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update the total expected download size. */
|
|
||||||
auto narInfo = std::dynamic_pointer_cast<const NarInfo>(info);
|
|
||||||
|
|
||||||
maintainExpectedNar = std::make_unique<MaintainCount<uint64_t>>(worker.expectedNarSize, info->narSize);
|
|
||||||
|
|
||||||
maintainExpectedDownload =
|
|
||||||
narInfo && narInfo->fileSize
|
|
||||||
? std::make_unique<MaintainCount<uint64_t>>(worker.expectedDownloadSize, narInfo->fileSize)
|
|
||||||
: nullptr;
|
|
||||||
|
|
||||||
|
worker.failedSubstitutions++;
|
||||||
worker.updateProgress();
|
worker.updateProgress();
|
||||||
|
|
||||||
/* Bail out early if this substituter lacks a valid
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
signature. LocalStore::addToStore() also checks for this, but
|
In that case the calling derivation should just do a
|
||||||
only after we've downloaded the path. */
|
build. */
|
||||||
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
|
co_return done(
|
||||||
{
|
substituterFailed ? ecFailed : ecNoSubstituters,
|
||||||
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
|
BuildResult::NoSubstituters,
|
||||||
worker.store.printStorePath(storePath), sub->getUri());
|
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
|
||||||
tryNext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To maintain the closure invariant, we first have to realise the
|
|
||||||
paths referenced by this one. */
|
|
||||||
for (auto & i : info->references)
|
|
||||||
if (i != storePath) /* ignore self-references */
|
|
||||||
addWaitee(worker.makePathSubstitutionGoal(i));
|
|
||||||
|
|
||||||
if (waitees.empty()) /* to prevent hang (no wake-up event) */
|
|
||||||
referencesValid();
|
|
||||||
else
|
|
||||||
state = &PathSubstitutionGoal::referencesValid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::referencesValid()
|
Goal::Co PathSubstitutionGoal::tryToRun(StorePath subPath, nix::ref<Store> sub, std::shared_ptr<const ValidPathInfo> info, bool& substituterFailed)
|
||||||
{
|
{
|
||||||
trace("all references realised");
|
trace("all references realised");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
done(
|
co_return done(
|
||||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||||
BuildResult::DependencyFailed,
|
BuildResult::DependencyFailed,
|
||||||
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & i : info->references)
|
for (auto & i : info->references)
|
||||||
if (i != storePath) /* ignore self-references */
|
if (i != storePath) /* ignore self-references */
|
||||||
assert(worker.store.isValidPath(i));
|
assert(worker.store.isValidPath(i));
|
||||||
|
|
||||||
state = &PathSubstitutionGoal::tryToRun;
|
|
||||||
worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
}
|
co_await Suspend{};
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::tryToRun()
|
|
||||||
{
|
|
||||||
trace("trying to run");
|
trace("trying to run");
|
||||||
|
|
||||||
/* Make sure that we are allowed to start a substitution. Note that even
|
/* Make sure that we are allowed to start a substitution. Note that even
|
||||||
|
@ -206,10 +183,10 @@ void PathSubstitutionGoal::tryToRun()
|
||||||
prevents infinite waiting. */
|
prevents infinite waiting. */
|
||||||
if (worker.getNrSubstitutions() >= std::max(1U, (unsigned int) settings.maxSubstitutionJobs)) {
|
if (worker.getNrSubstitutions() >= std::max(1U, (unsigned int) settings.maxSubstitutionJobs)) {
|
||||||
worker.waitForBuildSlot(shared_from_this());
|
worker.waitForBuildSlot(shared_from_this());
|
||||||
return;
|
co_await Suspend{};
|
||||||
}
|
}
|
||||||
|
|
||||||
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
auto maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
|
||||||
worker.updateProgress();
|
worker.updateProgress();
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -218,9 +195,9 @@ void PathSubstitutionGoal::tryToRun()
|
||||||
outPipe.createAsyncPipe(worker.ioport.get());
|
outPipe.createAsyncPipe(worker.ioport.get());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
promise = std::promise<void>();
|
auto promise = std::promise<void>();
|
||||||
|
|
||||||
thr = std::thread([this]() {
|
thr = std::thread([this, &promise, &subPath, &sub]() {
|
||||||
try {
|
try {
|
||||||
ReceiveInterrupts receiveInterrupts;
|
ReceiveInterrupts receiveInterrupts;
|
||||||
|
|
||||||
|
@ -231,7 +208,7 @@ void PathSubstitutionGoal::tryToRun()
|
||||||
PushActivity pact(act.id);
|
PushActivity pact(act.id);
|
||||||
|
|
||||||
copyStorePath(*sub, worker.store,
|
copyStorePath(*sub, worker.store,
|
||||||
subPath ? *subPath : storePath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
|
subPath, repair, sub->isTrusted ? NoCheckSigs : CheckSigs);
|
||||||
|
|
||||||
promise.set_value();
|
promise.set_value();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@ -247,12 +224,8 @@ void PathSubstitutionGoal::tryToRun()
|
||||||
#endif
|
#endif
|
||||||
}, true, false);
|
}, true, false);
|
||||||
|
|
||||||
state = &PathSubstitutionGoal::finished;
|
co_await Suspend{};
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::finished()
|
|
||||||
{
|
|
||||||
trace("substitute finished");
|
trace("substitute finished");
|
||||||
|
|
||||||
thr.join();
|
thr.join();
|
||||||
|
@ -274,10 +247,7 @@ void PathSubstitutionGoal::finished()
|
||||||
substituterFailed = true;
|
substituterFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try the next substitute. */
|
co_return Return{};
|
||||||
state = &PathSubstitutionGoal::tryNext;
|
|
||||||
worker.wakeUp(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.markContentsGood(storePath);
|
worker.markContentsGood(storePath);
|
||||||
|
@ -295,23 +265,19 @@ void PathSubstitutionGoal::finished()
|
||||||
worker.doneDownloadSize += fileSize;
|
worker.doneDownloadSize += fileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(maintainExpectedNar);
|
||||||
worker.doneNarSize += maintainExpectedNar->delta;
|
worker.doneNarSize += maintainExpectedNar->delta;
|
||||||
maintainExpectedNar.reset();
|
maintainExpectedNar.reset();
|
||||||
|
|
||||||
worker.updateProgress();
|
worker.updateProgress();
|
||||||
|
|
||||||
done(ecSuccess, BuildResult::Substituted);
|
co_return done(ecSuccess, BuildResult::Substituted);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::handleChildOutput(Descriptor fd, std::string_view data)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::handleEOF(Descriptor fd)
|
void PathSubstitutionGoal::handleEOF(Descriptor fd)
|
||||||
{
|
{
|
||||||
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "worker.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "goal.hh"
|
#include "goal.hh"
|
||||||
#include "muxable-pipe.hh"
|
#include "muxable-pipe.hh"
|
||||||
|
#include <coroutine>
|
||||||
|
#include <future>
|
||||||
|
#include <source_location>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
class Worker;
|
|
||||||
|
|
||||||
struct PathSubstitutionGoal : public Goal
|
struct PathSubstitutionGoal : public Goal
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -17,30 +19,9 @@ struct PathSubstitutionGoal : public Goal
|
||||||
StorePath storePath;
|
StorePath storePath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path the substituter refers to the path as. This will be
|
* Whether to try to repair a valid path.
|
||||||
* different when the stores have different names.
|
|
||||||
*/
|
*/
|
||||||
std::optional<StorePath> subPath;
|
RepairFlag repair;
|
||||||
|
|
||||||
/**
|
|
||||||
* The remaining substituters.
|
|
||||||
*/
|
|
||||||
std::list<ref<Store>> subs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current substituter.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<Store> sub;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether a substituter failed.
|
|
||||||
*/
|
|
||||||
bool substituterFailed = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Path info returned by the substituter's query info operation.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<const ValidPathInfo> info;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pipe for the substituter's standard output.
|
* Pipe for the substituter's standard output.
|
||||||
|
@ -52,31 +33,15 @@ struct PathSubstitutionGoal : public Goal
|
||||||
*/
|
*/
|
||||||
std::thread thr;
|
std::thread thr;
|
||||||
|
|
||||||
std::promise<void> promise;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to try to repair a valid path.
|
|
||||||
*/
|
|
||||||
RepairFlag repair;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Location where we're downloading the substitute. Differs from
|
|
||||||
* storePath when doing a repair.
|
|
||||||
*/
|
|
||||||
Path destPath;
|
|
||||||
|
|
||||||
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
|
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
|
||||||
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
|
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
|
||||||
|
|
||||||
typedef void (PathSubstitutionGoal::*GoalState)();
|
|
||||||
GoalState state;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content address for recomputing store path
|
* Content address for recomputing store path
|
||||||
*/
|
*/
|
||||||
std::optional<ContentAddress> ca;
|
std::optional<ContentAddress> ca;
|
||||||
|
|
||||||
void done(
|
Done done(
|
||||||
ExitCode result,
|
ExitCode result,
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
std::optional<std::string> errorMsg = {});
|
std::optional<std::string> errorMsg = {});
|
||||||
|
@ -96,22 +61,18 @@ public:
|
||||||
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
return "a$" + std::string(storePath.name()) + "$" + worker.store.printStorePath(storePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void work() override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The states.
|
* The states.
|
||||||
*/
|
*/
|
||||||
void init();
|
Co init() override;
|
||||||
void tryNext();
|
Co gotInfo();
|
||||||
void gotInfo();
|
Co tryToRun(StorePath subPath, nix::ref<Store> sub, std::shared_ptr<const ValidPathInfo> info, bool& substituterFailed);
|
||||||
void referencesValid();
|
Co finished();
|
||||||
void tryToRun();
|
|
||||||
void finished();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback used by the worker to write to the log.
|
* Callback used by the worker to write to the log.
|
||||||
*/
|
*/
|
||||||
void handleChildOutput(Descriptor fd, std::string_view data) override;
|
void handleChildOutput(Descriptor fd, std::string_view data) override {};
|
||||||
void handleEOF(Descriptor fd) override;
|
void handleEOF(Descriptor fd) override;
|
||||||
|
|
||||||
/* Called by destructor, can't be overridden */
|
/* Called by destructor, can't be overridden */
|
||||||
|
|
|
@ -337,31 +337,27 @@ void Worker::run(const Goals & _topGoals)
|
||||||
/* Wait for input. */
|
/* Wait for input. */
|
||||||
if (!children.empty() || !waitingForAWhile.empty())
|
if (!children.empty() || !waitingForAWhile.empty())
|
||||||
waitForInput();
|
waitForInput();
|
||||||
else {
|
else if (awake.empty() && 0U == settings.maxBuildJobs) {
|
||||||
if (awake.empty() && 0U == settings.maxBuildJobs)
|
if (getMachines().empty())
|
||||||
{
|
throw Error(
|
||||||
if (getMachines().empty())
|
R"(
|
||||||
throw Error(
|
Unable to start any build;
|
||||||
R"(
|
either increase '--max-jobs' or enable remote builds.
|
||||||
Unable to start any build;
|
|
||||||
either increase '--max-jobs' or enable remote builds.
|
|
||||||
|
|
||||||
For more information run 'man nix.conf' and search for '/machines'.
|
For more information run 'man nix.conf' and search for '/machines'.
|
||||||
)"
|
)"
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
throw Error(
|
throw Error(
|
||||||
R"(
|
R"(
|
||||||
Unable to start any build;
|
Unable to start any build;
|
||||||
remote machines may not have all required system features.
|
remote machines may not have all required system features.
|
||||||
|
|
||||||
For more information run 'man nix.conf' and search for '/machines'.
|
For more information run 'man nix.conf' and search for '/machines'.
|
||||||
)"
|
)"
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
} else assert(!awake.empty());
|
||||||
assert(!awake.empty());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If --keep-going is not set, it's possible that the main goal
|
/* If --keep-going is not set, it's possible that the main goal
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
#include "ssh-store-config.hh"
|
#include "common-ssh-store-config.hh"
|
||||||
#include "ssh.hh"
|
#include "ssh.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
|
@ -73,6 +73,7 @@ struct ContentAddressMethod
|
||||||
|
|
||||||
Raw raw;
|
Raw raw;
|
||||||
|
|
||||||
|
bool operator ==(const ContentAddressMethod &) const = default;
|
||||||
auto operator <=>(const ContentAddressMethod &) const = default;
|
auto operator <=>(const ContentAddressMethod &) const = default;
|
||||||
|
|
||||||
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressMethod);
|
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressMethod);
|
||||||
|
@ -159,6 +160,7 @@ struct ContentAddress
|
||||||
*/
|
*/
|
||||||
Hash hash;
|
Hash hash;
|
||||||
|
|
||||||
|
bool operator ==(const ContentAddress &) const = default;
|
||||||
auto operator <=>(const ContentAddress &) const = default;
|
auto operator <=>(const ContentAddress &) const = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -218,7 +220,9 @@ struct StoreReferences
|
||||||
*/
|
*/
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
|
|
||||||
auto operator <=>(const StoreReferences &) const = default;
|
bool operator ==(const StoreReferences &) const = default;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
//auto operator <=>(const StoreReferences &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This matches the additional info that we need for makeTextPath
|
// This matches the additional info that we need for makeTextPath
|
||||||
|
@ -235,7 +239,9 @@ struct TextInfo
|
||||||
*/
|
*/
|
||||||
StorePathSet references;
|
StorePathSet references;
|
||||||
|
|
||||||
auto operator <=>(const TextInfo &) const = default;
|
bool operator ==(const TextInfo &) const = default;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
//auto operator <=>(const TextInfo &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FixedOutputInfo
|
struct FixedOutputInfo
|
||||||
|
@ -255,7 +261,9 @@ struct FixedOutputInfo
|
||||||
*/
|
*/
|
||||||
StoreReferences references;
|
StoreReferences references;
|
||||||
|
|
||||||
auto operator <=>(const FixedOutputInfo &) const = default;
|
bool operator ==(const FixedOutputInfo &) const = default;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
//auto operator <=>(const FixedOutputInfo &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -272,7 +280,9 @@ struct ContentAddressWithReferences
|
||||||
|
|
||||||
Raw raw;
|
Raw raw;
|
||||||
|
|
||||||
auto operator <=>(const ContentAddressWithReferences &) const = default;
|
bool operator ==(const ContentAddressWithReferences &) const = default;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
//auto operator <=>(const ContentAddressWithReferences &) const = default;
|
||||||
|
|
||||||
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressWithReferences);
|
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressWithReferences);
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "strings-inline.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
std::optional<StorePath> DerivationOutput::path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const
|
std::optional<StorePath> DerivationOutput::path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const
|
||||||
|
|
|
@ -8,13 +8,11 @@
|
||||||
#include "repair-flag.hh"
|
#include "repair-flag.hh"
|
||||||
#include "derived-path-map.hh"
|
#include "derived-path-map.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
#include "comparator.hh"
|
|
||||||
#include "variant-wrapper.hh"
|
#include "variant-wrapper.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
struct StoreDirConfig;
|
struct StoreDirConfig;
|
||||||
|
@ -33,7 +31,8 @@ struct DerivationOutput
|
||||||
{
|
{
|
||||||
StorePath path;
|
StorePath path;
|
||||||
|
|
||||||
GENERATE_CMP(InputAddressed, me->path);
|
bool operator == (const InputAddressed &) const = default;
|
||||||
|
auto operator <=> (const InputAddressed &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +56,8 @@ struct DerivationOutput
|
||||||
*/
|
*/
|
||||||
StorePath path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const;
|
StorePath path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const;
|
||||||
|
|
||||||
GENERATE_CMP(CAFixed, me->ca);
|
bool operator == (const CAFixed &) const = default;
|
||||||
|
auto operator <=> (const CAFixed &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +77,8 @@ struct DerivationOutput
|
||||||
*/
|
*/
|
||||||
HashAlgorithm hashAlgo;
|
HashAlgorithm hashAlgo;
|
||||||
|
|
||||||
GENERATE_CMP(CAFloating, me->method, me->hashAlgo);
|
bool operator == (const CAFloating &) const = default;
|
||||||
|
auto operator <=> (const CAFloating &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,7 +86,8 @@ struct DerivationOutput
|
||||||
* isn't known yet.
|
* isn't known yet.
|
||||||
*/
|
*/
|
||||||
struct Deferred {
|
struct Deferred {
|
||||||
GENERATE_CMP(Deferred);
|
bool operator == (const Deferred &) const = default;
|
||||||
|
auto operator <=> (const Deferred &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,7 +106,8 @@ struct DerivationOutput
|
||||||
*/
|
*/
|
||||||
HashAlgorithm hashAlgo;
|
HashAlgorithm hashAlgo;
|
||||||
|
|
||||||
GENERATE_CMP(Impure, me->method, me->hashAlgo);
|
bool operator == (const Impure &) const = default;
|
||||||
|
auto operator <=> (const Impure &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
|
@ -117,7 +120,8 @@ struct DerivationOutput
|
||||||
|
|
||||||
Raw raw;
|
Raw raw;
|
||||||
|
|
||||||
GENERATE_CMP(DerivationOutput, me->raw);
|
bool operator == (const DerivationOutput &) const = default;
|
||||||
|
auto operator <=> (const DerivationOutput &) const = default;
|
||||||
|
|
||||||
MAKE_WRAPPER_CONSTRUCTOR(DerivationOutput);
|
MAKE_WRAPPER_CONSTRUCTOR(DerivationOutput);
|
||||||
|
|
||||||
|
@ -178,7 +182,8 @@ struct DerivationType {
|
||||||
*/
|
*/
|
||||||
bool deferred;
|
bool deferred;
|
||||||
|
|
||||||
GENERATE_CMP(InputAddressed, me->deferred);
|
bool operator == (const InputAddressed &) const = default;
|
||||||
|
auto operator <=> (const InputAddressed &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,7 +207,8 @@ struct DerivationType {
|
||||||
*/
|
*/
|
||||||
bool fixed;
|
bool fixed;
|
||||||
|
|
||||||
GENERATE_CMP(ContentAddressed, me->sandboxed, me->fixed);
|
bool operator == (const ContentAddressed &) const = default;
|
||||||
|
auto operator <=> (const ContentAddressed &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -212,7 +218,8 @@ struct DerivationType {
|
||||||
* type, but has some restrictions on its usage.
|
* type, but has some restrictions on its usage.
|
||||||
*/
|
*/
|
||||||
struct Impure {
|
struct Impure {
|
||||||
GENERATE_CMP(Impure);
|
bool operator == (const Impure &) const = default;
|
||||||
|
auto operator <=> (const Impure &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
|
@ -223,7 +230,8 @@ struct DerivationType {
|
||||||
|
|
||||||
Raw raw;
|
Raw raw;
|
||||||
|
|
||||||
GENERATE_CMP(DerivationType, me->raw);
|
bool operator == (const DerivationType &) const = default;
|
||||||
|
auto operator <=> (const DerivationType &) const = default;
|
||||||
|
|
||||||
MAKE_WRAPPER_CONSTRUCTOR(DerivationType);
|
MAKE_WRAPPER_CONSTRUCTOR(DerivationType);
|
||||||
|
|
||||||
|
@ -313,14 +321,9 @@ struct BasicDerivation
|
||||||
|
|
||||||
static std::string_view nameFromPath(const StorePath & storePath);
|
static std::string_view nameFromPath(const StorePath & storePath);
|
||||||
|
|
||||||
GENERATE_CMP(BasicDerivation,
|
bool operator == (const BasicDerivation &) const = default;
|
||||||
me->outputs,
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
me->inputSrcs,
|
//auto operator <=> (const BasicDerivation &) const = default;
|
||||||
me->platform,
|
|
||||||
me->builder,
|
|
||||||
me->args,
|
|
||||||
me->env,
|
|
||||||
me->name);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
|
@ -378,9 +381,9 @@ struct Derivation : BasicDerivation
|
||||||
const nlohmann::json & json,
|
const nlohmann::json & json,
|
||||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||||
|
|
||||||
GENERATE_CMP(Derivation,
|
bool operator == (const Derivation &) const = default;
|
||||||
static_cast<const BasicDerivation &>(*me),
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
me->inputDrvs);
|
//auto operator <=> (const Derivation &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,17 +54,18 @@ typename DerivedPathMap<V>::ChildNode * DerivedPathMap<V>::findSlot(const Single
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
GENERATE_CMP_EXT(
|
template<>
|
||||||
template<>,
|
bool DerivedPathMap<std::set<std::string>>::ChildNode::operator == (
|
||||||
DerivedPathMap<std::set<std::string>>::ChildNode,
|
const DerivedPathMap<std::set<std::string>>::ChildNode &) const noexcept = default;
|
||||||
me->value,
|
|
||||||
me->childMap);
|
|
||||||
|
|
||||||
GENERATE_CMP_EXT(
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
template<>,
|
#if 0
|
||||||
DerivedPathMap<std::set<std::string>>,
|
template<>
|
||||||
me->map);
|
std::strong_ordering DerivedPathMap<std::set<std::string>>::ChildNode::operator <=> (
|
||||||
|
const DerivedPathMap<std::set<std::string>>::ChildNode &) const noexcept = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template struct DerivedPathMap<std::set<std::string>>::ChildNode;
|
||||||
template struct DerivedPathMap<std::set<std::string>>;
|
template struct DerivedPathMap<std::set<std::string>>;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,7 +47,11 @@ struct DerivedPathMap {
|
||||||
*/
|
*/
|
||||||
Map childMap;
|
Map childMap;
|
||||||
|
|
||||||
DECLARE_CMP(ChildNode);
|
bool operator == (const ChildNode &) const noexcept;
|
||||||
|
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
// decltype(std::declval<V>() <=> std::declval<V>())
|
||||||
|
// operator <=> (const ChildNode &) const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +64,10 @@ struct DerivedPathMap {
|
||||||
*/
|
*/
|
||||||
Map map;
|
Map map;
|
||||||
|
|
||||||
DECLARE_CMP(DerivedPathMap);
|
bool operator == (const DerivedPathMap &) const = default;
|
||||||
|
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
|
// auto operator <=> (const DerivedPathMap &) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the node for `k`, creating it if needed.
|
* Find the node for `k`, creating it if needed.
|
||||||
|
@ -83,14 +90,21 @@ struct DerivedPathMap {
|
||||||
ChildNode * findSlot(const SingleDerivedPath & k);
|
ChildNode * findSlot(const SingleDerivedPath & k);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
bool DerivedPathMap<std::set<std::string>>::ChildNode::operator == (
|
||||||
|
const DerivedPathMap<std::set<std::string>>::ChildNode &) const noexcept;
|
||||||
|
|
||||||
DECLARE_CMP_EXT(
|
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
|
||||||
template<>,
|
#if 0
|
||||||
DerivedPathMap<std::set<std::string>>::,
|
template<>
|
||||||
DerivedPathMap<std::set<std::string>>);
|
std::strong_ordering DerivedPathMap<std::set<std::string>>::ChildNode::operator <=> (
|
||||||
DECLARE_CMP_EXT(
|
const DerivedPathMap<std::set<std::string>>::ChildNode &) const noexcept;
|
||||||
template<>,
|
|
||||||
DerivedPathMap<std::set<std::string>>::ChildNode::,
|
template<>
|
||||||
DerivedPathMap<std::set<std::string>>::ChildNode);
|
inline auto DerivedPathMap<std::set<std::string>>::operator <=> (const DerivedPathMap<std::set<std::string>> &) const noexcept = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern template struct DerivedPathMap<std::set<std::string>>::ChildNode;
|
||||||
|
extern template struct DerivedPathMap<std::set<std::string>>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "comparator.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
@ -8,26 +9,32 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
#define CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, COMPARATOR) \
|
// Custom implementation to avoid `ref` ptr equality
|
||||||
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
|
GENERATE_CMP_EXT(
|
||||||
{ \
|
,
|
||||||
const MY_TYPE* me = this; \
|
std::strong_ordering,
|
||||||
auto fields1 = std::tie(*me->drvPath, me->FIELD); \
|
SingleDerivedPathBuilt,
|
||||||
me = &other; \
|
*me->drvPath,
|
||||||
auto fields2 = std::tie(*me->drvPath, me->FIELD); \
|
me->output);
|
||||||
return fields1 COMPARATOR fields2; \
|
|
||||||
}
|
|
||||||
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
|
|
||||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, ==) \
|
|
||||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
|
|
||||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
|
|
||||||
|
|
||||||
CMP(SingleDerivedPath, SingleDerivedPathBuilt, output)
|
// Custom implementation to avoid `ref` ptr equality
|
||||||
|
|
||||||
CMP(SingleDerivedPath, DerivedPathBuilt, outputs)
|
// TODO no `GENERATE_CMP_EXT` because no `std::set::operator<=>` on
|
||||||
|
// Darwin, per header.
|
||||||
#undef CMP
|
GENERATE_EQUAL(
|
||||||
#undef CMP_ONE
|
,
|
||||||
|
DerivedPathBuilt ::,
|
||||||
|
DerivedPathBuilt,
|
||||||
|
*me->drvPath,
|
||||||
|
me->outputs);
|
||||||
|
GENERATE_ONE_CMP(
|
||||||
|
,
|
||||||
|
bool,
|
||||||
|
DerivedPathBuilt ::,
|
||||||
|
<,
|
||||||
|
DerivedPathBuilt,
|
||||||
|
*me->drvPath,
|
||||||
|
me->outputs);
|
||||||
|
|
||||||
nlohmann::json DerivedPath::Opaque::toJSON(const StoreDirConfig & store) const
|
nlohmann::json DerivedPath::Opaque::toJSON(const StoreDirConfig & store) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "outputs-spec.hh"
|
#include "outputs-spec.hh"
|
||||||
#include "comparator.hh"
|
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ struct DerivedPathOpaque {
|
||||||
static DerivedPathOpaque parse(const StoreDirConfig & store, std::string_view);
|
static DerivedPathOpaque parse(const StoreDirConfig & store, std::string_view);
|
||||||
nlohmann::json toJSON(const StoreDirConfig & store) const;
|
nlohmann::json toJSON(const StoreDirConfig & store) const;
|
||||||
|
|
||||||
GENERATE_CMP(DerivedPathOpaque, me->path);
|
bool operator == (const DerivedPathOpaque &) const = default;
|
||||||
|
auto operator <=> (const DerivedPathOpaque &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SingleDerivedPath;
|
struct SingleDerivedPath;
|
||||||
|
@ -78,7 +79,8 @@ struct SingleDerivedPathBuilt {
|
||||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||||
nlohmann::json toJSON(Store & store) const;
|
nlohmann::json toJSON(Store & store) const;
|
||||||
|
|
||||||
DECLARE_CMP(SingleDerivedPathBuilt);
|
bool operator == (const SingleDerivedPathBuilt &) const noexcept;
|
||||||
|
std::strong_ordering operator <=> (const SingleDerivedPathBuilt &) const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
using _SingleDerivedPathRaw = std::variant<
|
using _SingleDerivedPathRaw = std::variant<
|
||||||
|
@ -108,6 +110,9 @@ struct SingleDerivedPath : _SingleDerivedPathRaw {
|
||||||
return static_cast<const Raw &>(*this);
|
return static_cast<const Raw &>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator == (const SingleDerivedPath &) const = default;
|
||||||
|
auto operator <=> (const SingleDerivedPath &) const = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the store path this is ultimately derived from (by realising
|
* Get the store path this is ultimately derived from (by realising
|
||||||
* and projecting outputs).
|
* and projecting outputs).
|
||||||
|
@ -201,7 +206,9 @@ struct DerivedPathBuilt {
|
||||||
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||||
nlohmann::json toJSON(Store & store) const;
|
nlohmann::json toJSON(Store & store) const;
|
||||||
|
|
||||||
DECLARE_CMP(DerivedPathBuilt);
|
bool operator == (const DerivedPathBuilt &) const noexcept;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::set::operator <=>`, can't do yet.
|
||||||
|
bool operator < (const DerivedPathBuilt &) const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
using _DerivedPathRaw = std::variant<
|
using _DerivedPathRaw = std::variant<
|
||||||
|
@ -230,6 +237,10 @@ struct DerivedPath : _DerivedPathRaw {
|
||||||
return static_cast<const Raw &>(*this);
|
return static_cast<const Raw &>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator == (const DerivedPath &) const = default;
|
||||||
|
// TODO libc++ 16 (used by darwin) missing `std::set::operator <=>`, can't do yet.
|
||||||
|
//auto operator <=> (const DerivedPath &) const = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the store path this is ultimately derived from (by realising
|
* Get the store path this is ultimately derived from (by realising
|
||||||
* and projecting outputs).
|
* and projecting outputs).
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "types.hh"
|
|
||||||
#include "hash.hh"
|
|
||||||
#include "config.hh"
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <future>
|
#include <future>
|
||||||
|
|
||||||
|
#include "logging.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
#include "ref.hh"
|
||||||
|
#include "config.hh"
|
||||||
|
#include "serialise.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
struct FileTransferSettings : Config
|
struct FileTransferSettings : Config
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "store-api.hh"
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "strings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ Settings::Settings()
|
||||||
Strings ss;
|
Strings ss;
|
||||||
for (auto & p : tokenizeString<Strings>(*s, ":"))
|
for (auto & p : tokenizeString<Strings>(*s, ":"))
|
||||||
ss.push_back("@" + p);
|
ss.push_back("@" + p);
|
||||||
builders = concatStringsSep(" ", ss);
|
builders = concatStringsSep("\n", ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__linux__) && defined(SANDBOX_SHELL)
|
#if defined(__linux__) && defined(SANDBOX_SHELL)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "legacy-ssh-store.hh"
|
#include "legacy-ssh-store.hh"
|
||||||
#include "ssh-store-config.hh"
|
#include "common-ssh-store-config.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
#include "pool.hh"
|
#include "pool.hh"
|
||||||
#include "remote-store.hh"
|
#include "remote-store.hh"
|
||||||
|
@ -15,6 +15,15 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
LegacySSHStoreConfig::LegacySSHStoreConfig(
|
||||||
|
std::string_view scheme,
|
||||||
|
std::string_view authority,
|
||||||
|
const Params & params)
|
||||||
|
: StoreConfig(params)
|
||||||
|
, CommonSSHStoreConfig(scheme, authority, params)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
std::string LegacySSHStoreConfig::doc()
|
std::string LegacySSHStoreConfig::doc()
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
@ -35,7 +44,7 @@ LegacySSHStore::LegacySSHStore(
|
||||||
const Params & params)
|
const Params & params)
|
||||||
: StoreConfig(params)
|
: StoreConfig(params)
|
||||||
, CommonSSHStoreConfig(scheme, host, params)
|
, CommonSSHStoreConfig(scheme, host, params)
|
||||||
, LegacySSHStoreConfig(params)
|
, LegacySSHStoreConfig(scheme, host, params)
|
||||||
, Store(params)
|
, Store(params)
|
||||||
, connections(make_ref<Pool<Connection>>(
|
, connections(make_ref<Pool<Connection>>(
|
||||||
std::max(1, (int) maxConnections),
|
std::max(1, (int) maxConnections),
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue