mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-10 00:08:07 +02:00
Merge remote-tracking branch 'nixos/master'
This commit is contained in:
commit
cfac8cf277
94 changed files with 1885 additions and 781 deletions
2
.version
2
.version
|
@ -1 +1 @@
|
|||
2.7.0
|
||||
2.8.0
|
|
@ -72,6 +72,7 @@
|
|||
- [CLI guideline](contributing/cli-guideline.md)
|
||||
- [Release Notes](release-notes/release-notes.md)
|
||||
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
|
||||
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)
|
||||
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)
|
||||
- [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md)
|
||||
- [Release 2.4 (2021-11-01)](release-notes/rl-2.4.md)
|
||||
|
|
|
@ -110,7 +110,7 @@ default, set it to `-`.
|
|||
7. A comma-separated list of *mandatory features*. A machine will only
|
||||
be used to build a derivation if all of the machine’s mandatory
|
||||
features appear in the derivation’s `requiredSystemFeatures`
|
||||
attribute..
|
||||
attribute.
|
||||
|
||||
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
|
||||
will use its regular known-hosts file. Specifically, the field is calculated
|
||||
|
|
33
doc/manual/src/release-notes/rl-2.7.md
Normal file
33
doc/manual/src/release-notes/rl-2.7.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Release X.Y (2022-03-07)
|
||||
|
||||
* Nix will now make some helpful suggestions when you mistype
|
||||
something on the command line. For instance, if you type `nix build
|
||||
nixpkgs#thunderbrd`, it will suggest `thunderbird`.
|
||||
|
||||
* A number of "default" flake output attributes have been
|
||||
renamed. These are:
|
||||
|
||||
* `defaultPackage.<system>` → `packages.<system>.default`
|
||||
* `defaultApps.<system>` → `apps.<system>.default`
|
||||
* `defaultTemplate` → `templates.default`
|
||||
* `defaultBundler.<system>` → `bundlers.<system>.default`
|
||||
* `overlay` → `overlays.default`
|
||||
* `devShell.<system>` → `devShells.<system>.default`
|
||||
|
||||
The old flake output attributes still work, but `nix flake check`
|
||||
will warn about them.
|
||||
|
||||
* Breaking API change: `nix bundle` now supports bundlers of the form
|
||||
`bundler.<system>.<name>= derivation: another-derivation;`. This
|
||||
supports additional functionality to inspect evaluation information
|
||||
during bundling. A new
|
||||
[repository](https://github.com/NixOS/bundlers) has various bundlers
|
||||
implemented.
|
||||
|
||||
* `nix store ping` now reports the version of the remote Nix daemon.
|
||||
|
||||
* `nix flake {init,new}` now display information about which files have been
|
||||
created.
|
||||
|
||||
* Templates can now define a `welcomeText` attribute, which is printed out by
|
||||
`nix flake {init,new} --template <template>`.
|
|
@ -1,28 +1,3 @@
|
|||
# Release X.Y (202?-??-??)
|
||||
|
||||
* A number of "default" flake output attributes have been
|
||||
renamed. These are:
|
||||
|
||||
* `defaultPackage.<system>` → `packages.<system>.default`
|
||||
* `defaultApps.<system>` → `apps.<system>.default`
|
||||
* `defaultTemplate` → `templates.default`
|
||||
* `defaultBundler.<system>` → `bundlers.<system>.default`
|
||||
* `overlay` → `overlays.default`
|
||||
* `devShell.<system>` → `devShells.<system>.default`
|
||||
|
||||
The old flake output attributes still work, but `nix flake check`
|
||||
will warn about them.
|
||||
|
||||
* `nix bundle` breaking API change now supports bundlers of the form
|
||||
`bundler.<system>.<name>= derivation: another-derivation;`. This supports
|
||||
additional functionality to inspect evaluation information during bundling. A
|
||||
new [repository](https://github.com/NixOS/bundlers) has various bundlers
|
||||
implemented.
|
||||
|
||||
* `nix store ping` now reports the version of the remote Nix daemon.
|
||||
|
||||
* `nix flake {init,new}` now display information about which files have been
|
||||
created.
|
||||
|
||||
* Templates can now define a `welcomeText` attribute, which is printed out by
|
||||
`nix flake {init,new} --template <template>`.
|
||||
* Various nix commands can now read expressions from stdin with `--file -`.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[Unit]
|
||||
Description=Nix Daemon
|
||||
Documentation=man:nix-daemon https://nixos.org/manual
|
||||
RequiresMountsFor=@storedir@
|
||||
RequiresMountsFor=@localstatedir@
|
||||
ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket
|
||||
|
|
|
@ -300,7 +300,7 @@ connected:
|
|||
|
||||
std::set<Realisation> missingRealisations;
|
||||
StorePathSet missingPaths;
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
|
||||
for (auto & outputName : wantedOutputs) {
|
||||
auto thisOutputHash = outputHashes.at(outputName);
|
||||
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "eval-cache.hh"
|
||||
#include "url.hh"
|
||||
#include "registry.hh"
|
||||
#include "build-result.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
|
@ -146,7 +147,9 @@ SourceExprCommand::SourceExprCommand()
|
|||
addFlag({
|
||||
.longName = "file",
|
||||
.shortName = 'f',
|
||||
.description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.",
|
||||
.description =
|
||||
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
|
||||
"If *file* is the character -, then a Nix expression will be read from standard input.",
|
||||
.category = installablesCategory,
|
||||
.labels = {"file"},
|
||||
.handler = {&file},
|
||||
|
@ -342,9 +345,9 @@ void completeFlakeRefWithFragment(
|
|||
auto attr = root->findAlongAttrPath(attrPath);
|
||||
if (!attr) continue;
|
||||
|
||||
for (auto & attr2 : attr->getAttrs()) {
|
||||
for (auto & attr2 : (*attr)->getAttrs()) {
|
||||
if (hasPrefix(attr2, lastAttr)) {
|
||||
auto attrPath2 = attr->getAttrPath(attr2);
|
||||
auto attrPath2 = (*attr)->getAttrPath(attr2);
|
||||
/* Strip the attrpath prefix. */
|
||||
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
||||
if (isAttrPath)
|
||||
|
@ -662,15 +665,22 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> toDerivation
|
|||
auto cache = openEvalCache(*flake.state, lockedFlake);
|
||||
auto root = cache->getRoot();
|
||||
|
||||
Suggestions suggestions;
|
||||
|
||||
for (auto & attrPath : flake.getActualAttrPaths()) {
|
||||
debug("trying flake output attribute '%s'", attrPath);
|
||||
|
||||
auto attr = root->findAlongAttrPath(
|
||||
auto attrOrSuggestions = root->findAlongAttrPath(
|
||||
parseAttrPath(*flake.state, attrPath),
|
||||
true
|
||||
);
|
||||
|
||||
if (!attr) continue;
|
||||
if (!attrOrSuggestions) {
|
||||
suggestions += attrOrSuggestions.getSuggestions();
|
||||
continue;
|
||||
}
|
||||
|
||||
auto attr = *attrOrSuggestions;
|
||||
|
||||
if (!attr->isDerivation())
|
||||
throw Error("flake output attribute '%s' is not a derivation", attrPath);
|
||||
|
@ -685,7 +695,7 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> toDerivation
|
|||
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
|
||||
}
|
||||
|
||||
throw Error("flake '%s' does not provide attribute %s",
|
||||
throw Error(suggestions, "flake '%s' does not provide attribute %s",
|
||||
flake.flakeRef, showAttrPaths(flake.getActualAttrPaths()));
|
||||
}
|
||||
|
||||
|
@ -726,17 +736,24 @@ std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
|
|||
|
||||
auto emptyArgs = state.allocBindings(0);
|
||||
|
||||
Suggestions suggestions;
|
||||
|
||||
for (auto & attrPath : getActualAttrPaths()) {
|
||||
try {
|
||||
auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
|
||||
state.forceValue(*v, pos);
|
||||
return {v, pos};
|
||||
} catch (AttrPathNotFound & e) {
|
||||
suggestions += e.info().suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
throw Error("flake '%s' does not provide attribute %s",
|
||||
flakeRef, showAttrPaths(getActualAttrPaths()));
|
||||
throw Error(
|
||||
suggestions,
|
||||
"flake '%s' does not provide attribute %s",
|
||||
flakeRef,
|
||||
showAttrPaths(getActualAttrPaths())
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
||||
|
@ -751,7 +768,7 @@ InstallableFlake::getCursors(EvalState & state)
|
|||
|
||||
for (auto & attrPath : getActualAttrPaths()) {
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
|
||||
if (attr) res.push_back({attr, attrPath});
|
||||
if (attr) res.push_back({*attr, attrPath});
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -828,7 +845,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
|||
auto state = getEvalState();
|
||||
auto vFile = state->allocValue();
|
||||
|
||||
if (file)
|
||||
if (file == "-") {
|
||||
auto e = state->parseStdin();
|
||||
state->eval(e, *vFile);
|
||||
} else if (file)
|
||||
state->evalFile(lookupFileArg(*state, *file), *vFile);
|
||||
else {
|
||||
auto e = state->parseExprFromString(*expr, absPath("."));
|
||||
|
@ -1001,8 +1021,7 @@ BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPa
|
|||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
store->printStorePath(bfd.drvPath), output);
|
||||
if (settings.isExperimentalFeatureEnabled(
|
||||
Xp::CaDerivations)) {
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
auto outputId =
|
||||
DrvOutput{outputHashes.at(output), output};
|
||||
auto realisation =
|
||||
|
@ -1048,12 +1067,33 @@ BuiltPaths Installable::build(
|
|||
pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
|
||||
}
|
||||
|
||||
if (mode == Realise::Nothing || mode == Realise::Derivation)
|
||||
switch (mode) {
|
||||
case Realise::Nothing:
|
||||
case Realise::Derivation:
|
||||
printMissing(store, pathsToBuild, lvlError);
|
||||
else if (mode == Realise::Outputs)
|
||||
store->buildPaths(pathsToBuild, bMode, evalStore);
|
||||
|
||||
return getBuiltPaths(evalStore, store, pathsToBuild);
|
||||
return getBuiltPaths(evalStore, store, pathsToBuild);
|
||||
case Realise::Outputs: {
|
||||
BuiltPaths res;
|
||||
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
||||
if (!buildResult.success())
|
||||
buildResult.rethrow();
|
||||
std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
std::map<std::string, StorePath> outputs;
|
||||
for (auto & path : buildResult.builtOutputs)
|
||||
outputs.emplace(path.first.outputName, path.second.outPath);
|
||||
res.push_back(BuiltPath::Built { bfd.drvPath, outputs });
|
||||
},
|
||||
[&](const DerivedPath::Opaque & bo) {
|
||||
res.push_back(BuiltPath::Opaque { bo.path });
|
||||
},
|
||||
}, buildResult.path.raw());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
BuiltPaths Installable::toBuiltPaths(
|
||||
|
|
|
@ -74,8 +74,14 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
|
|||
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
||||
|
||||
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
||||
if (a == v->attrs->end())
|
||||
throw AttrPathNotFound("attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
||||
if (a == v->attrs->end()) {
|
||||
std::set<std::string> attrNames;
|
||||
for (auto & attr : *v->attrs)
|
||||
attrNames.insert(attr.name);
|
||||
|
||||
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
||||
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
||||
}
|
||||
v = &*a->value;
|
||||
pos = *a->pos;
|
||||
}
|
||||
|
|
|
@ -406,6 +406,16 @@ Value & AttrCursor::forceValue()
|
|||
return v;
|
||||
}
|
||||
|
||||
Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
|
||||
{
|
||||
auto attrNames = getAttrs();
|
||||
std::set<std::string> strAttrNames;
|
||||
for (auto & name : attrNames)
|
||||
strAttrNames.insert(std::string(name));
|
||||
|
||||
return Suggestions::bestMatches(strAttrNames, name);
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors)
|
||||
{
|
||||
if (root->db) {
|
||||
|
@ -446,6 +456,11 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
|||
return nullptr;
|
||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
|
||||
for (auto & attr : *v.attrs) {
|
||||
if (root->db)
|
||||
root->db->setPlaceholder({cachedValue->first, attr.name});
|
||||
}
|
||||
|
||||
auto attr = v.attrs->get(name);
|
||||
|
||||
if (!attr) {
|
||||
|
@ -464,7 +479,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
|||
cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()};
|
||||
}
|
||||
|
||||
return std::make_shared<AttrCursor>(
|
||||
return make_ref<AttrCursor>(
|
||||
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
|
||||
}
|
||||
|
||||
|
@ -473,27 +488,31 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name)
|
|||
return maybeGetAttr(root->state.symbols.create(name));
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors)
|
||||
ref<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors)
|
||||
{
|
||||
auto p = maybeGetAttr(name, forceErrors);
|
||||
if (!p)
|
||||
throw Error("attribute '%s' does not exist", getAttrPathStr(name));
|
||||
return p;
|
||||
return ref(p);
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
||||
ref<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
||||
{
|
||||
return getAttr(root->state.symbols.create(name));
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force)
|
||||
OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force)
|
||||
{
|
||||
auto res = shared_from_this();
|
||||
for (auto & attr : attrPath) {
|
||||
res = res->maybeGetAttr(attr, force);
|
||||
if (!res) return {};
|
||||
auto child = res->maybeGetAttr(attr, force);
|
||||
if (!child) {
|
||||
auto suggestions = res->getSuggestionsForAttr(attr);
|
||||
return OrSuggestions<ref<AttrCursor>>::failed(suggestions);
|
||||
}
|
||||
res = child;
|
||||
}
|
||||
return res;
|
||||
return ref(res);
|
||||
}
|
||||
|
||||
std::string AttrCursor::getString()
|
||||
|
|
|
@ -94,15 +94,17 @@ public:
|
|||
|
||||
std::string getAttrPathStr(Symbol name) const;
|
||||
|
||||
Suggestions getSuggestionsForAttr(Symbol name);
|
||||
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name, bool forceErrors = false);
|
||||
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
|
||||
|
||||
std::shared_ptr<AttrCursor> getAttr(Symbol name, bool forceErrors = false);
|
||||
ref<AttrCursor> getAttr(Symbol name, bool forceErrors = false);
|
||||
|
||||
std::shared_ptr<AttrCursor> getAttr(std::string_view name);
|
||||
ref<AttrCursor> getAttr(std::string_view name);
|
||||
|
||||
std::shared_ptr<AttrCursor> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
|
||||
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
|
||||
|
||||
std::string getString();
|
||||
|
||||
|
|
|
@ -24,6 +24,81 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
|
|||
}
|
||||
|
||||
|
||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||
[[gnu::always_inline]]
|
||||
inline void * allocBytes(size_t n)
|
||||
{
|
||||
void * p;
|
||||
#if HAVE_BOEHMGC
|
||||
p = GC_MALLOC(n);
|
||||
#else
|
||||
p = calloc(n, 1);
|
||||
#endif
|
||||
if (!p) throw std::bad_alloc();
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
[[gnu::always_inline]]
|
||||
Value * EvalState::allocValue()
|
||||
{
|
||||
#if HAVE_BOEHMGC
|
||||
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
||||
GC_malloc_many returns a linked list of objects of the given size, where the first word
|
||||
of each object is also the pointer to the next object in the list. This also means that we
|
||||
have to explicitly clear the first word of every object we take. */
|
||||
if (!*valueAllocCache) {
|
||||
*valueAllocCache = GC_malloc_many(sizeof(Value));
|
||||
if (!*valueAllocCache) throw std::bad_alloc();
|
||||
}
|
||||
|
||||
/* GC_NEXT is a convenience macro for accessing the first word of an object.
|
||||
Take the first list item, advance the list to the next item, and clear the next pointer. */
|
||||
void * p = *valueAllocCache;
|
||||
*valueAllocCache = GC_NEXT(p);
|
||||
GC_NEXT(p) = nullptr;
|
||||
#else
|
||||
void * p = allocBytes(sizeof(Value));
|
||||
#endif
|
||||
|
||||
nrValues++;
|
||||
return (Value *) p;
|
||||
}
|
||||
|
||||
|
||||
[[gnu::always_inline]]
|
||||
Env & EvalState::allocEnv(size_t size)
|
||||
{
|
||||
nrEnvs++;
|
||||
nrValuesInEnvs += size;
|
||||
|
||||
Env * env;
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
if (size == 1) {
|
||||
/* see allocValue for explanations. */
|
||||
if (!*env1AllocCache) {
|
||||
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
|
||||
if (!*env1AllocCache) throw std::bad_alloc();
|
||||
}
|
||||
|
||||
void * p = *env1AllocCache;
|
||||
*env1AllocCache = GC_NEXT(p);
|
||||
GC_NEXT(p) = nullptr;
|
||||
env = (Env *) p;
|
||||
} else
|
||||
#endif
|
||||
env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
|
||||
|
||||
env->type = Env::Plain;
|
||||
|
||||
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
|
||||
|
||||
return *env;
|
||||
}
|
||||
|
||||
|
||||
[[gnu::always_inline]]
|
||||
void EvalState::forceValue(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, [&]() { return pos; });
|
||||
|
@ -52,6 +127,7 @@ void EvalState::forceValue(Value & v, Callable getPos)
|
|||
}
|
||||
|
||||
|
||||
[[gnu::always_inline]]
|
||||
inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
||||
{
|
||||
forceAttrs(v, [&]() { return pos; });
|
||||
|
@ -59,6 +135,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
|||
|
||||
|
||||
template <typename Callable>
|
||||
[[gnu::always_inline]]
|
||||
inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
||||
{
|
||||
forceValue(v, getPos);
|
||||
|
@ -67,6 +144,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
|||
}
|
||||
|
||||
|
||||
[[gnu::always_inline]]
|
||||
inline void EvalState::forceList(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
|
@ -74,18 +152,5 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
|
|||
throwTypeError(pos, "value is %1% while a list was expected", v);
|
||||
}
|
||||
|
||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||
inline void * allocBytes(size_t n)
|
||||
{
|
||||
void * p;
|
||||
#if HAVE_BOEHMGC
|
||||
p = GC_MALLOC(n);
|
||||
#else
|
||||
p = calloc(n, 1);
|
||||
#endif
|
||||
if (!p) throw std::bad_alloc();
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -63,9 +63,15 @@ static char * dupString(const char * s)
|
|||
}
|
||||
|
||||
|
||||
static char * dupStringWithLen(const char * s, size_t size)
|
||||
// When there's no need to write to the string, we can optimize away empty
|
||||
// string allocations.
|
||||
// This function handles makeImmutableStringWithLen(null, 0) by returning the
|
||||
// empty string.
|
||||
static const char * makeImmutableStringWithLen(const char * s, size_t size)
|
||||
{
|
||||
char * t;
|
||||
if (size == 0)
|
||||
return "";
|
||||
#if HAVE_BOEHMGC
|
||||
t = GC_STRNDUP(s, size);
|
||||
#else
|
||||
|
@ -75,6 +81,10 @@ static char * dupStringWithLen(const char * s, size_t size)
|
|||
return t;
|
||||
}
|
||||
|
||||
static inline const char * makeImmutableString(std::string_view s) {
|
||||
return makeImmutableStringWithLen(s.data(), s.size());
|
||||
}
|
||||
|
||||
|
||||
RootValue allocRootValue(Value * v)
|
||||
{
|
||||
|
@ -439,8 +449,10 @@ EvalState::EvalState(
|
|||
, regexCache(makeRegexCache())
|
||||
#if HAVE_BOEHMGC
|
||||
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
#else
|
||||
, valueAllocCache(std::make_shared<void *>(nullptr))
|
||||
, env1AllocCache(std::make_shared<void *>(nullptr))
|
||||
#endif
|
||||
, baseEnv(allocEnv(128))
|
||||
, staticBaseEnv(false, 0)
|
||||
|
@ -717,9 +729,18 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2
|
|||
throw EvalError(s, s2);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2))
|
||||
{
|
||||
throw EvalError(ErrorInfo {
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = pos,
|
||||
.suggestions = suggestions,
|
||||
});
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2))
|
||||
{
|
||||
throw EvalError({
|
||||
throw EvalError(ErrorInfo {
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = pos
|
||||
});
|
||||
|
@ -763,6 +784,16 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
|
|||
});
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2))
|
||||
{
|
||||
throw TypeError(ErrorInfo {
|
||||
.msg = hintfmt(s, fun.showNamePos(), s2),
|
||||
.errPos = pos,
|
||||
.suggestions = suggestions,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
|
||||
{
|
||||
throw TypeError(s, showType(v));
|
||||
|
@ -805,7 +836,7 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con
|
|||
|
||||
void Value::mkString(std::string_view s)
|
||||
{
|
||||
mkString(dupStringWithLen(s.data(), s.size()));
|
||||
mkString(makeImmutableString(s));
|
||||
}
|
||||
|
||||
|
||||
|
@ -836,7 +867,7 @@ void Value::mkStringMove(const char * s, const PathSet & context)
|
|||
|
||||
void Value::mkPath(std::string_view s)
|
||||
{
|
||||
mkPath(dupStringWithLen(s.data(), s.size()));
|
||||
mkPath(makeImmutableString(s));
|
||||
}
|
||||
|
||||
|
||||
|
@ -866,42 +897,6 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
|||
}
|
||||
|
||||
|
||||
Value * EvalState::allocValue()
|
||||
{
|
||||
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
||||
GC_malloc_many returns a linked list of objects of the given size, where the first word
|
||||
of each object is also the pointer to the next object in the list. This also means that we
|
||||
have to explicitly clear the first word of every object we take. */
|
||||
if (!*valueAllocCache) {
|
||||
*valueAllocCache = GC_malloc_many(sizeof(Value));
|
||||
if (!*valueAllocCache) throw std::bad_alloc();
|
||||
}
|
||||
|
||||
/* GC_NEXT is a convenience macro for accessing the first word of an object.
|
||||
Take the first list item, advance the list to the next item, and clear the next pointer. */
|
||||
void * p = *valueAllocCache;
|
||||
GC_PTR_STORE_AND_DIRTY(&*valueAllocCache, GC_NEXT(p));
|
||||
GC_NEXT(p) = nullptr;
|
||||
|
||||
nrValues++;
|
||||
auto v = (Value *) p;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
Env & EvalState::allocEnv(size_t size)
|
||||
{
|
||||
nrEnvs++;
|
||||
nrValuesInEnvs += size;
|
||||
Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
|
||||
env->type = Env::Plain;
|
||||
|
||||
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
|
||||
|
||||
return *env;
|
||||
}
|
||||
|
||||
|
||||
void EvalState::mkList(Value & v, size_t size)
|
||||
{
|
||||
v.mkList(size);
|
||||
|
@ -1271,8 +1266,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
|||
}
|
||||
} else {
|
||||
state.forceAttrs(*vAttrs, pos);
|
||||
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
||||
throwEvalError(pos, "attribute '%1%' missing", name);
|
||||
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
|
||||
std::set<std::string> allAttrNames;
|
||||
for (auto & attr : *vAttrs->attrs)
|
||||
allAttrNames.insert(attr.name);
|
||||
throwEvalError(
|
||||
pos,
|
||||
Suggestions::bestMatches(allAttrNames, name),
|
||||
"attribute '%1%' missing", name);
|
||||
}
|
||||
}
|
||||
vAttrs = j->value;
|
||||
pos2 = j->pos;
|
||||
|
@ -1388,8 +1390,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
/* Nope, so show the first unexpected argument to the
|
||||
user. */
|
||||
for (auto & i : *args[0]->attrs)
|
||||
if (!lambda.formals->has(i.name))
|
||||
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
|
||||
if (!lambda.formals->has(i.name)) {
|
||||
std::set<std::string> formalNames;
|
||||
for (auto & formal : lambda.formals->formals)
|
||||
formalNames.insert(formal.name);
|
||||
throwTypeError(
|
||||
pos,
|
||||
Suggestions::bestMatches(formalNames, i.name),
|
||||
"%1% called with unexpected argument '%2%'",
|
||||
lambda,
|
||||
i.name);
|
||||
}
|
||||
abort(); // can't happen
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,9 +133,14 @@ private:
|
|||
/* Cache used by prim_match(). */
|
||||
std::shared_ptr<RegexCache> regexCache;
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
/* Allocation cache for GC'd Value objects. */
|
||||
std::shared_ptr<void *> valueAllocCache;
|
||||
|
||||
/* Allocation cache for size-1 Env objects. */
|
||||
std::shared_ptr<void *> env1AllocCache;
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
EvalState(
|
||||
|
@ -347,8 +352,8 @@ public:
|
|||
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
||||
|
||||
/* Allocation primitives. */
|
||||
Value * allocValue();
|
||||
Env & allocEnv(size_t size);
|
||||
inline Value * allocValue();
|
||||
inline Env & allocEnv(size_t size);
|
||||
|
||||
Value * allocAttr(Value & vAttrs, const Symbol & name);
|
||||
Value * allocAttr(Value & vAttrs, std::string_view name);
|
||||
|
@ -509,3 +514,5 @@ extern EvalSettings evalSettings;
|
|||
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
||||
|
||||
}
|
||||
|
||||
#include "eval-inline.hh"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "get-drvs.hh"
|
||||
#include "util.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
|
||||
|
@ -102,7 +103,7 @@ StorePath DrvInfo::queryOutPath() const
|
|||
}
|
||||
|
||||
|
||||
DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
|
||||
DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
|
||||
{
|
||||
if (outputs.empty()) {
|
||||
/* Get the ‘outputs’ list. */
|
||||
|
@ -112,20 +113,24 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
|
|||
|
||||
/* For each output... */
|
||||
for (auto elem : i->value->listItems()) {
|
||||
/* Evaluate the corresponding set. */
|
||||
std::string name(state->forceStringNoCtx(*elem, *i->pos));
|
||||
Bindings::iterator out = attrs->find(state->symbols.create(name));
|
||||
if (out == attrs->end()) continue; // FIXME: throw error?
|
||||
state->forceAttrs(*out->value, *i->pos);
|
||||
std::string output(state->forceStringNoCtx(*elem, *i->pos));
|
||||
|
||||
/* And evaluate its ‘outPath’ attribute. */
|
||||
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
||||
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
||||
PathSet context;
|
||||
outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
|
||||
if (withPaths) {
|
||||
/* Evaluate the corresponding set. */
|
||||
Bindings::iterator out = attrs->find(state->symbols.create(output));
|
||||
if (out == attrs->end()) continue; // FIXME: throw error?
|
||||
state->forceAttrs(*out->value, *i->pos);
|
||||
|
||||
/* And evaluate its ‘outPath’ attribute. */
|
||||
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
||||
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
||||
PathSet context;
|
||||
outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
|
||||
} else
|
||||
outputs.emplace(output, std::nullopt);
|
||||
}
|
||||
} else
|
||||
outputs.emplace("out", queryOutPath());
|
||||
outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
|
||||
}
|
||||
if (!onlyOutputsToInstall || !attrs)
|
||||
return outputs;
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace nix {
|
|||
struct DrvInfo
|
||||
{
|
||||
public:
|
||||
typedef std::map<std::string, StorePath> Outputs;
|
||||
typedef std::map<std::string, std::optional<StorePath>> Outputs;
|
||||
|
||||
private:
|
||||
EvalState * state;
|
||||
|
@ -46,8 +46,9 @@ public:
|
|||
StorePath requireDrvPath() const;
|
||||
StorePath queryOutPath() const;
|
||||
std::string queryOutputName() const;
|
||||
/** Return the list of outputs. The "outputs to install" are determined by `meta.outputsToInstall`. */
|
||||
Outputs queryOutputs(bool onlyOutputsToInstall = false);
|
||||
/** Return the unordered map of output names to (optional) output paths.
|
||||
* The "outputs to install" are determined by `meta.outputsToInstall`. */
|
||||
Outputs queryOutputs(bool withPaths = true, bool onlyOutputsToInstall = false);
|
||||
|
||||
StringSet queryMetaNames();
|
||||
Value * queryMeta(const std::string & name);
|
||||
|
|
|
@ -23,14 +23,13 @@ MakeError(RestrictedPathError, Error);
|
|||
|
||||
struct Pos
|
||||
{
|
||||
FileOrigin origin;
|
||||
Symbol file;
|
||||
unsigned int line, column;
|
||||
|
||||
Pos() : origin(foString), line(0), column(0) { }
|
||||
Pos(FileOrigin origin, const Symbol & file, unsigned int line, unsigned int column)
|
||||
: origin(origin), file(file), line(line), column(column) { }
|
||||
|
||||
uint32_t line;
|
||||
FileOrigin origin:2;
|
||||
uint32_t column:30;
|
||||
Pos() : line(0), origin(foString), column(0) { };
|
||||
Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column)
|
||||
: file(file), line(line), origin(origin), column(column) { };
|
||||
operator bool() const
|
||||
{
|
||||
return line != 0;
|
||||
|
|
|
@ -1163,26 +1163,24 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
|||
|
||||
auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName);
|
||||
drv.env["out"] = state.store->printStorePath(outPath);
|
||||
drv.outputs.insert_or_assign("out", DerivationOutput {
|
||||
.output = DerivationOutputCAFixed {
|
||||
.hash = FixedOutputHash {
|
||||
.method = ingestionMethod,
|
||||
.hash = std::move(h),
|
||||
},
|
||||
drv.outputs.insert_or_assign("out",
|
||||
DerivationOutput::CAFixed {
|
||||
.hash = FixedOutputHash {
|
||||
.method = ingestionMethod,
|
||||
.hash = std::move(h),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
else if (contentAddressed) {
|
||||
HashType ht = parseHashType(outputHashAlgo);
|
||||
for (auto & i : outputs) {
|
||||
drv.env[i] = hashPlaceholder(i);
|
||||
drv.outputs.insert_or_assign(i, DerivationOutput {
|
||||
.output = DerivationOutputCAFloating {
|
||||
drv.outputs.insert_or_assign(i,
|
||||
DerivationOutput::CAFloating {
|
||||
.method = ingestionMethod,
|
||||
.hashType = ht,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1196,43 +1194,36 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
|||
for (auto & i : outputs) {
|
||||
drv.env[i] = "";
|
||||
drv.outputs.insert_or_assign(i,
|
||||
DerivationOutput {
|
||||
.output = DerivationOutputInputAddressed {
|
||||
.path = StorePath::dummy,
|
||||
},
|
||||
});
|
||||
DerivationOutput::Deferred { });
|
||||
}
|
||||
|
||||
// Regular, non-CA derivation should always return a single hash and not
|
||||
// hash per output.
|
||||
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
|
||||
auto hashModulo = hashDerivationModulo(*state.store, drv, true);
|
||||
std::visit(overloaded {
|
||||
[&](Hash & h) {
|
||||
for (auto & i : outputs) {
|
||||
auto outPath = state.store->makeOutputPath(i, h, drvName);
|
||||
drv.env[i] = state.store->printStorePath(outPath);
|
||||
drv.outputs.insert_or_assign(i,
|
||||
DerivationOutput {
|
||||
.output = DerivationOutputInputAddressed {
|
||||
.path = std::move(outPath),
|
||||
},
|
||||
});
|
||||
[&](const DrvHash & drvHash) {
|
||||
auto & h = drvHash.hash;
|
||||
switch (drvHash.kind) {
|
||||
case DrvHash::Kind::Deferred:
|
||||
/* Outputs already deferred, nothing to do */
|
||||
break;
|
||||
case DrvHash::Kind::Regular:
|
||||
for (auto & [outputName, output] : drv.outputs) {
|
||||
auto outPath = state.store->makeOutputPath(outputName, h, drvName);
|
||||
drv.env[outputName] = state.store->printStorePath(outPath);
|
||||
output = DerivationOutput::InputAddressed {
|
||||
.path = std::move(outPath),
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
[&](CaOutputHashes &) {
|
||||
[&](const CaOutputHashes &) {
|
||||
// Shouldn't happen as the toplevel derivation is not CA.
|
||||
assert(false);
|
||||
},
|
||||
[&](DeferredHash &) {
|
||||
for (auto & i : outputs) {
|
||||
drv.outputs.insert_or_assign(i,
|
||||
DerivationOutput {
|
||||
.output = DerivationOutputDeferred{},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
hashModulo);
|
||||
hashModulo.raw());
|
||||
|
||||
}
|
||||
|
||||
|
@ -1244,12 +1235,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
|||
|
||||
/* Optimisation, but required in read-only mode! because in that
|
||||
case we don't actually write store derivations, so we can't
|
||||
read them later.
|
||||
|
||||
However, we don't bother doing this for floating CA derivations because
|
||||
their "hash modulo" is indeterminate until built. */
|
||||
if (drv.type() != DerivationType::CAFloating) {
|
||||
auto h = hashDerivationModulo(*state.store, Derivation(drv), false);
|
||||
read them later. */
|
||||
{
|
||||
auto h = hashDerivationModulo(*state.store, drv, false);
|
||||
drvHashes.lock()->insert_or_assign(drvPath, h);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
|
|
@ -29,7 +29,7 @@ struct FetchSettings : public Config
|
|||
|
||||
* Github: the token value is the OAUTH-TOKEN string obtained
|
||||
as the Personal Access Token from the Github server (see
|
||||
https://docs.github.com/en/developers/apps/authorizing-oath-apps).
|
||||
https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps).
|
||||
|
||||
* Gitlab: the token value is either the OAuth2 token or the
|
||||
Personal Access Token (these are different types tokens
|
||||
|
|
|
@ -222,22 +222,46 @@ struct GitInputScheme : InputScheme
|
|||
if (!input.getRef() && !input.getRev() && isLocal) {
|
||||
bool clean = false;
|
||||
|
||||
/* Check whether this repo has any commits. There are
|
||||
probably better ways to do this. */
|
||||
auto gitDir = actualUrl + "/.git";
|
||||
auto commonGitDir = chomp(runProgram(
|
||||
"git",
|
||||
true,
|
||||
{ "-C", actualUrl, "rev-parse", "--git-common-dir" }
|
||||
));
|
||||
if (commonGitDir != ".git")
|
||||
gitDir = commonGitDir;
|
||||
auto env = getEnv();
|
||||
// Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
|
||||
// that way unknown errors can lead to a failure instead of continuing through the wrong code path
|
||||
env["LC_ALL"] = "C";
|
||||
|
||||
bool haveCommits = !readDirectory(gitDir + "/refs/heads").empty();
|
||||
/* Check whether HEAD points to something that looks like a commit,
|
||||
since that is the refrence we want to use later on. */
|
||||
auto result = runProgram(RunOptions {
|
||||
.program = "git",
|
||||
.args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
|
||||
.environment = env,
|
||||
.mergeStderrToStdout = true
|
||||
});
|
||||
auto exitCode = WEXITSTATUS(result.first);
|
||||
auto errorMessage = result.second;
|
||||
|
||||
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
|
||||
throw Error("'%s' is not a Git repository", actualUrl);
|
||||
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
|
||||
// indicates that the repo does not have any commits
|
||||
// we want to proceed and will consider it dirty later
|
||||
} else if (exitCode != 0) {
|
||||
// any other errors should lead to a failure
|
||||
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage);
|
||||
}
|
||||
|
||||
bool hasHead = exitCode == 0;
|
||||
try {
|
||||
if (haveCommits) {
|
||||
runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" });
|
||||
if (hasHead) {
|
||||
// Using git diff is preferrable over lower-level operations here,
|
||||
// because its conceptually simpler and we only need the exit code anyways.
|
||||
auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"});
|
||||
if (!submodules) {
|
||||
// Changes in submodules should only make the tree dirty
|
||||
// when those submodules will be copied as well.
|
||||
gitDiffOpts.emplace_back("--ignore-submodules");
|
||||
}
|
||||
gitDiffOpts.emplace_back("--");
|
||||
runProgram("git", true, gitDiffOpts);
|
||||
|
||||
clean = true;
|
||||
}
|
||||
} catch (ExecError & e) {
|
||||
|
@ -282,7 +306,7 @@ struct GitInputScheme : InputScheme
|
|||
// modified dirty file?
|
||||
input.attrs.insert_or_assign(
|
||||
"lastModified",
|
||||
haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
|
||||
hasHead ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
|
||||
|
||||
return {std::move(storePath), input};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "fetchers.hh"
|
||||
#include "store-api.hh"
|
||||
#include "archive.hh"
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
|
@ -80,8 +81,9 @@ struct PathInputScheme : InputScheme
|
|||
// nothing to do
|
||||
}
|
||||
|
||||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
|
||||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||
{
|
||||
Input input(_input);
|
||||
std::string absPath;
|
||||
auto path = getStrAttr(input.attrs, "path");
|
||||
|
||||
|
@ -111,9 +113,15 @@ struct PathInputScheme : InputScheme
|
|||
if (storePath)
|
||||
store->addTempRoot(*storePath);
|
||||
|
||||
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath))
|
||||
time_t mtime = 0;
|
||||
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
|
||||
// FIXME: try to substitute storePath.
|
||||
storePath = store->addToStore("source", absPath);
|
||||
auto src = sinkToSource([&](Sink & sink) {
|
||||
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
|
||||
});
|
||||
storePath = store->addToStoreFromDump(*src, "source");
|
||||
}
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
||||
|
||||
return {std::move(*storePath), input};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "crypto.hh"
|
||||
#include "store-api.hh"
|
||||
#include "log-store.hh"
|
||||
|
||||
#include "pool.hh"
|
||||
|
||||
|
@ -28,7 +29,9 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
|
|||
"other than -1 which we reserve to indicate Nix defaults should be used"};
|
||||
};
|
||||
|
||||
class BinaryCacheStore : public virtual BinaryCacheStoreConfig, public virtual Store
|
||||
class BinaryCacheStore : public virtual BinaryCacheStoreConfig,
|
||||
public virtual Store,
|
||||
public virtual LogStore
|
||||
{
|
||||
|
||||
private:
|
||||
|
|
|
@ -28,6 +28,7 @@ struct BuildResult
|
|||
LogLimitExceeded,
|
||||
NotDeterministic,
|
||||
ResolvesToAlreadyValid,
|
||||
NoSubstituters,
|
||||
} status = MiscFailure;
|
||||
std::string errorMsg;
|
||||
|
||||
|
@ -63,15 +64,26 @@ struct BuildResult
|
|||
non-determinism.) */
|
||||
bool isNonDeterministic = false;
|
||||
|
||||
/* The derivation we built or the store path we substituted. */
|
||||
DerivedPath path;
|
||||
|
||||
/* For derivations, a mapping from the names of the wanted outputs
|
||||
to actual paths. */
|
||||
DrvOutputs builtOutputs;
|
||||
|
||||
/* The start/stop times of the build (or one of the rounds, if it
|
||||
was repeated). */
|
||||
time_t startTime = 0, stopTime = 0;
|
||||
|
||||
bool success() {
|
||||
bool success()
|
||||
{
|
||||
return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid;
|
||||
}
|
||||
|
||||
void rethrow()
|
||||
{
|
||||
throw Error("%s", errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ namespace nix {
|
|||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||
, useDerivation(true)
|
||||
, drvPath(drvPath)
|
||||
, wantedOutputs(wantedOutputs)
|
||||
|
@ -85,7 +85,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
|||
|
||||
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||
: Goal(worker)
|
||||
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||
, useDerivation(false)
|
||||
, drvPath(drvPath)
|
||||
, wantedOutputs(wantedOutputs)
|
||||
|
@ -135,7 +135,7 @@ void DerivationGoal::killChild()
|
|||
void DerivationGoal::timedOut(Error && ex)
|
||||
{
|
||||
killChild();
|
||||
done(BuildResult::TimedOut, ex);
|
||||
done(BuildResult::TimedOut, {}, ex);
|
||||
}
|
||||
|
||||
|
||||
|
@ -182,7 +182,7 @@ void DerivationGoal::loadDerivation()
|
|||
trace("loading derivation");
|
||||
|
||||
if (nrFailed != 0) {
|
||||
done(BuildResult::MiscFailure, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
||||
done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,7 @@ void DerivationGoal::haveDerivation()
|
|||
{
|
||||
trace("have derivation");
|
||||
|
||||
if (drv->type() == DerivationType::CAFloating)
|
||||
if (!drv->type().hasKnownOutputPaths())
|
||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||
|
||||
retrySubstitution = false;
|
||||
|
@ -215,28 +215,20 @@ void DerivationGoal::haveDerivation()
|
|||
|
||||
auto outputHashes = staticOutputHashes(worker.evalStore, *drv);
|
||||
for (auto & [outputName, outputHash] : outputHashes)
|
||||
initialOutputs.insert({
|
||||
initialOutputs.insert({
|
||||
outputName,
|
||||
InitialOutput{
|
||||
InitialOutput {
|
||||
.wanted = true, // Will be refined later
|
||||
.outputHash = outputHash
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Check what outputs paths are not already valid. */
|
||||
checkPathValidity();
|
||||
bool allValid = true;
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.wanted) continue;
|
||||
if (!status.known || !status.known->isValid()) {
|
||||
allValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto [allValid, validOutputs] = checkPathValidity();
|
||||
|
||||
/* If they are all valid, then we're done. */
|
||||
if (allValid && buildMode == bmNormal) {
|
||||
done(BuildResult::AlreadyValid);
|
||||
done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -277,7 +269,7 @@ void DerivationGoal::outputsSubstitutionTried()
|
|||
trace("all outputs substituted (maybe)");
|
||||
|
||||
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
||||
done(BuildResult::TransientFailure,
|
||||
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 ",
|
||||
worker.store.printStorePath(drvPath)));
|
||||
return;
|
||||
|
@ -301,23 +293,17 @@ void DerivationGoal::outputsSubstitutionTried()
|
|||
return;
|
||||
}
|
||||
|
||||
checkPathValidity();
|
||||
size_t nrInvalid = 0;
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.wanted) continue;
|
||||
if (!status.known || !status.known->isValid())
|
||||
nrInvalid++;
|
||||
}
|
||||
auto [allValid, validOutputs] = checkPathValidity();
|
||||
|
||||
if (buildMode == bmNormal && nrInvalid == 0) {
|
||||
done(BuildResult::Substituted);
|
||||
if (buildMode == bmNormal && allValid) {
|
||||
done(BuildResult::Substituted, std::move(validOutputs));
|
||||
return;
|
||||
}
|
||||
if (buildMode == bmRepair && nrInvalid == 0) {
|
||||
if (buildMode == bmRepair && allValid) {
|
||||
repairClosure();
|
||||
return;
|
||||
}
|
||||
if (buildMode == bmCheck && nrInvalid > 0)
|
||||
if (buildMode == bmCheck && !allValid)
|
||||
throw Error("some outputs of '%s' are not valid, so checking is not possible",
|
||||
worker.store.printStorePath(drvPath));
|
||||
|
||||
|
@ -409,7 +395,7 @@ void DerivationGoal::repairClosure()
|
|||
}
|
||||
|
||||
if (waitees.empty()) {
|
||||
done(BuildResult::AlreadyValid);
|
||||
done(BuildResult::AlreadyValid, assertPathValidity());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -423,7 +409,7 @@ void DerivationGoal::closureRepaired()
|
|||
if (nrFailed > 0)
|
||||
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
||||
worker.store.printStorePath(drvPath));
|
||||
done(BuildResult::AlreadyValid);
|
||||
done(BuildResult::AlreadyValid, assertPathValidity());
|
||||
}
|
||||
|
||||
|
||||
|
@ -434,7 +420,7 @@ void DerivationGoal::inputsRealised()
|
|||
if (nrFailed != 0) {
|
||||
if (!useDerivation)
|
||||
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
||||
done(BuildResult::DependencyFailed, Error(
|
||||
done(BuildResult::DependencyFailed, {}, Error(
|
||||
"%s dependencies of derivation '%s' failed to build",
|
||||
nrFailed, worker.store.printStorePath(drvPath)));
|
||||
return;
|
||||
|
@ -454,9 +440,28 @@ void DerivationGoal::inputsRealised()
|
|||
if (useDerivation) {
|
||||
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
|
||||
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) &&
|
||||
((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type()))
|
||||
|| fullDrv.type() == DerivationType::DeferredInputAddressed)) {
|
||||
auto drvType = fullDrv.type();
|
||||
bool resolveDrv = std::visit(overloaded {
|
||||
[&](const DerivationType::InputAddressed & ia) {
|
||||
/* must resolve if deferred. */
|
||||
return ia.deferred;
|
||||
},
|
||||
[&](const DerivationType::ContentAddressed & ca) {
|
||||
return !fullDrv.inputDrvs.empty() && (
|
||||
ca.fixed
|
||||
/* Can optionally resolve if fixed, which is good
|
||||
for avoiding unnecessary rebuilds. */
|
||||
? settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
|
||||
/* Must resolve if floating and there are any inputs
|
||||
drvs. */
|
||||
: true);
|
||||
},
|
||||
}, drvType.raw());
|
||||
|
||||
if (resolveDrv)
|
||||
{
|
||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||
|
||||
/* We are be able to resolve this derivation based on the
|
||||
now-known results of dependencies. If so, we become a stub goal
|
||||
aliasing that resolved derivation goal */
|
||||
|
@ -515,7 +520,7 @@ void DerivationGoal::inputsRealised()
|
|||
|
||||
/* Don't repeat fixed-output derivations since they're already
|
||||
verified by their output hash.*/
|
||||
nrRounds = derivationIsFixed(derivationType) ? 1 : settings.buildRepeat + 1;
|
||||
nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1;
|
||||
|
||||
/* 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
|
||||
|
@ -523,10 +528,11 @@ void DerivationGoal::inputsRealised()
|
|||
state = &DerivationGoal::tryToBuild;
|
||||
worker.wakeUp(shared_from_this());
|
||||
|
||||
result = BuildResult();
|
||||
buildResult = BuildResult { .path = buildResult.path };
|
||||
}
|
||||
|
||||
void DerivationGoal::started() {
|
||||
void DerivationGoal::started()
|
||||
{
|
||||
auto msg = fmt(
|
||||
buildMode == bmRepair ? "repairing outputs of '%s'" :
|
||||
buildMode == bmCheck ? "checking outputs of '%s'" :
|
||||
|
@ -588,19 +594,12 @@ void DerivationGoal::tryToBuild()
|
|||
omitted, but that would be less efficient.) Note that since we
|
||||
now hold the locks on the output paths, no other process can
|
||||
build this derivation, so no further checks are necessary. */
|
||||
checkPathValidity();
|
||||
bool allValid = true;
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.wanted) continue;
|
||||
if (!status.known || !status.known->isValid()) {
|
||||
allValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto [allValid, validOutputs] = checkPathValidity();
|
||||
|
||||
if (buildMode != bmCheck && allValid) {
|
||||
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
||||
outputLocks.setDeletion(true);
|
||||
done(BuildResult::AlreadyValid);
|
||||
done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -626,7 +625,7 @@ void DerivationGoal::tryToBuild()
|
|||
/* Yes, it has started doing so. Wait until we get
|
||||
EOF from the hook. */
|
||||
actLock.reset();
|
||||
result.startTime = time(0); // inexact
|
||||
buildResult.startTime = time(0); // inexact
|
||||
state = &DerivationGoal::buildDone;
|
||||
started();
|
||||
return;
|
||||
|
@ -830,8 +829,8 @@ void DerivationGoal::buildDone()
|
|||
|
||||
debug("builder process for '%s' finished", worker.store.printStorePath(drvPath));
|
||||
|
||||
result.timesBuilt++;
|
||||
result.stopTime = time(0);
|
||||
buildResult.timesBuilt++;
|
||||
buildResult.stopTime = time(0);
|
||||
|
||||
/* So the child is gone now. */
|
||||
worker.childTerminated(this);
|
||||
|
@ -876,11 +875,11 @@ void DerivationGoal::buildDone()
|
|||
|
||||
/* Compute the FS closure of the outputs and register them as
|
||||
being valid. */
|
||||
registerOutputs();
|
||||
auto builtOutputs = registerOutputs();
|
||||
|
||||
StorePathSet outputPaths;
|
||||
for (auto & [_, path] : finalOutputs)
|
||||
outputPaths.insert(path);
|
||||
for (auto & [_, output] : buildResult.builtOutputs)
|
||||
outputPaths.insert(output.outPath);
|
||||
runPostBuildHook(
|
||||
worker.store,
|
||||
*logger,
|
||||
|
@ -890,7 +889,7 @@ void DerivationGoal::buildDone()
|
|||
|
||||
if (buildMode == bmCheck) {
|
||||
cleanupPostOutputsRegisteredModeCheck();
|
||||
done(BuildResult::Built);
|
||||
done(BuildResult::Built, std::move(builtOutputs));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -911,6 +910,8 @@ void DerivationGoal::buildDone()
|
|||
outputLocks.setDeletion(true);
|
||||
outputLocks.unlock();
|
||||
|
||||
done(BuildResult::Built, std::move(builtOutputs));
|
||||
|
||||
} catch (BuildError & e) {
|
||||
outputLocks.unlock();
|
||||
|
||||
|
@ -926,18 +927,17 @@ void DerivationGoal::buildDone()
|
|||
st =
|
||||
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
|
||||
statusOk(status) ? BuildResult::OutputRejected :
|
||||
derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure :
|
||||
derivationType.isImpure() || diskFull ? BuildResult::TransientFailure :
|
||||
BuildResult::PermanentFailure;
|
||||
}
|
||||
|
||||
done(st, e);
|
||||
done(st, {}, e);
|
||||
return;
|
||||
}
|
||||
|
||||
done(BuildResult::Built);
|
||||
}
|
||||
|
||||
void DerivationGoal::resolvedFinished() {
|
||||
void DerivationGoal::resolvedFinished()
|
||||
{
|
||||
assert(resolvedDrvGoal);
|
||||
auto resolvedDrv = *resolvedDrvGoal->drv;
|
||||
|
||||
|
@ -950,11 +950,13 @@ void DerivationGoal::resolvedFinished() {
|
|||
if (realWantedOutputs.empty())
|
||||
realWantedOutputs = resolvedDrv.outputNames();
|
||||
|
||||
DrvOutputs builtOutputs;
|
||||
|
||||
for (auto & wantedOutput : realWantedOutputs) {
|
||||
assert(initialOutputs.count(wantedOutput) != 0);
|
||||
assert(resolvedHashes.count(wantedOutput) != 0);
|
||||
auto realisation = worker.store.queryRealisation(
|
||||
DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput}
|
||||
DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput}
|
||||
);
|
||||
// We've just built it, but maybe the build failed, in which case the
|
||||
// realisation won't be there
|
||||
|
@ -966,10 +968,11 @@ void DerivationGoal::resolvedFinished() {
|
|||
signRealisation(newRealisation);
|
||||
worker.store.registerDrvOutput(newRealisation);
|
||||
outputPaths.insert(realisation->outPath);
|
||||
builtOutputs.emplace(realisation->id, *realisation);
|
||||
} else {
|
||||
// If we don't have a realisation, then it must mean that something
|
||||
// failed when building the resolved drv
|
||||
assert(!result.success());
|
||||
assert(!buildResult.success());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -981,7 +984,7 @@ void DerivationGoal::resolvedFinished() {
|
|||
);
|
||||
|
||||
auto status = [&]() {
|
||||
auto resolvedResult = resolvedDrvGoal->getResult();
|
||||
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
||||
switch (resolvedResult.status) {
|
||||
case BuildResult::AlreadyValid:
|
||||
return BuildResult::ResolvesToAlreadyValid;
|
||||
|
@ -990,7 +993,7 @@ void DerivationGoal::resolvedFinished() {
|
|||
}
|
||||
}();
|
||||
|
||||
done(status);
|
||||
done(status, std::move(builtOutputs));
|
||||
}
|
||||
|
||||
HookReply DerivationGoal::tryBuildHook()
|
||||
|
@ -1100,7 +1103,7 @@ HookReply DerivationGoal::tryBuildHook()
|
|||
}
|
||||
|
||||
|
||||
void DerivationGoal::registerOutputs()
|
||||
DrvOutputs DerivationGoal::registerOutputs()
|
||||
{
|
||||
/* When using a build hook, the build hook can register the output
|
||||
as valid (by doing `nix-store --import'). If so we don't have
|
||||
|
@ -1109,21 +1112,7 @@ void DerivationGoal::registerOutputs()
|
|||
We can only early return when the outputs are known a priori. For
|
||||
floating content-addressed derivations this isn't the case.
|
||||
*/
|
||||
for (auto & [outputName, optOutputPath] : worker.store.queryPartialDerivationOutputMap(drvPath)) {
|
||||
if (!wantOutput(outputName, wantedOutputs))
|
||||
continue;
|
||||
if (!optOutputPath)
|
||||
throw BuildError(
|
||||
"output '%s' from derivation '%s' does not have a known output path",
|
||||
outputName, worker.store.printStorePath(drvPath));
|
||||
auto & outputPath = *optOutputPath;
|
||||
if (!worker.store.isValidPath(outputPath))
|
||||
throw BuildError(
|
||||
"output '%s' from derivation '%s' is supposed to be at '%s' but that path is not valid",
|
||||
outputName, worker.store.printStorePath(drvPath), worker.store.printStorePath(outputPath));
|
||||
|
||||
finalOutputs.insert_or_assign(outputName, outputPath);
|
||||
}
|
||||
return assertPathValidity();
|
||||
}
|
||||
|
||||
Path DerivationGoal::openLogFile()
|
||||
|
@ -1175,7 +1164,6 @@ bool DerivationGoal::isReadDesc(int fd)
|
|||
return fd == hook->builderOut.readSide.get();
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::handleChildOutput(int fd, std::string_view data)
|
||||
{
|
||||
// local & `ssh://`-builds are dealt with here.
|
||||
|
@ -1186,7 +1174,7 @@ void DerivationGoal::handleChildOutput(int fd, std::string_view data)
|
|||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||
killChild();
|
||||
done(
|
||||
BuildResult::LogLimitExceeded,
|
||||
BuildResult::LogLimitExceeded, {},
|
||||
Error("%s killed after writing more than %d bytes of log output",
|
||||
getName(), settings.maxLogSize));
|
||||
return;
|
||||
|
@ -1252,7 +1240,7 @@ void DerivationGoal::flushLine()
|
|||
|
||||
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
|
||||
{
|
||||
if (!useDerivation || drv->type() != DerivationType::CAFloating) {
|
||||
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
|
||||
std::map<std::string, std::optional<StorePath>> res;
|
||||
for (auto & [name, output] : drv->outputs)
|
||||
res.insert_or_assign(name, output.path(worker.store, drv->name, name));
|
||||
|
@ -1264,7 +1252,7 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
|
|||
|
||||
OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||
{
|
||||
if (!useDerivation || drv->type() != DerivationType::CAFloating) {
|
||||
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
|
||||
OutputPathMap res;
|
||||
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
|
||||
res.insert_or_assign(name, *output.second);
|
||||
|
@ -1275,10 +1263,12 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
|||
}
|
||||
|
||||
|
||||
void DerivationGoal::checkPathValidity()
|
||||
std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||
{
|
||||
bool checkHash = buildMode == bmRepair;
|
||||
auto wantedOutputsLeft = wantedOutputs;
|
||||
DrvOutputs validOutputs;
|
||||
|
||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||
InitialOutput & info = initialOutputs.at(i.first);
|
||||
info.wanted = wantOutput(i.first, wantedOutputs);
|
||||
|
@ -1295,26 +1285,28 @@ void DerivationGoal::checkPathValidity()
|
|||
: PathStatus::Corrupt,
|
||||
};
|
||||
}
|
||||
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first};
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first};
|
||||
if (auto real = worker.store.queryRealisation(drvOutput)) {
|
||||
info.known = {
|
||||
.path = real->outPath,
|
||||
.status = PathStatus::Valid,
|
||||
};
|
||||
} else if (info.known && info.known->status == PathStatus::Valid) {
|
||||
// We know the output because it' a static output of the
|
||||
} else if (info.known && info.known->isValid()) {
|
||||
// We know the output because it's a static output of the
|
||||
// derivation, and the output path is valid, but we don't have
|
||||
// its realisation stored (probably because it has been built
|
||||
// without the `ca-derivations` experimental flag)
|
||||
// without the `ca-derivations` experimental flag).
|
||||
worker.store.registerDrvOutput(
|
||||
Realisation{
|
||||
Realisation {
|
||||
drvOutput,
|
||||
info.known->path,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
if (info.wanted && info.known && info.known->isValid())
|
||||
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
|
||||
}
|
||||
// If we requested all the outputs via the empty set, we are always fine.
|
||||
// If we requested specific elements, the loop above removes all the valid
|
||||
|
@ -1323,24 +1315,50 @@ void DerivationGoal::checkPathValidity()
|
|||
throw Error("derivation '%s' does not have wanted outputs %s",
|
||||
worker.store.printStorePath(drvPath),
|
||||
concatStringsSep(", ", quoteStrings(wantedOutputsLeft)));
|
||||
|
||||
bool allValid = true;
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.wanted) continue;
|
||||
if (!status.known || !status.known->isValid()) {
|
||||
allValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { allValid, validOutputs };
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex)
|
||||
DrvOutputs DerivationGoal::assertPathValidity()
|
||||
{
|
||||
result.status = status;
|
||||
auto [allValid, validOutputs] = checkPathValidity();
|
||||
if (!allValid)
|
||||
throw Error("some outputs are unexpectedly invalid");
|
||||
return validOutputs;
|
||||
}
|
||||
|
||||
|
||||
void DerivationGoal::done(
|
||||
BuildResult::Status status,
|
||||
DrvOutputs builtOutputs,
|
||||
std::optional<Error> ex)
|
||||
{
|
||||
buildResult.status = status;
|
||||
if (ex)
|
||||
result.errorMsg = ex->what();
|
||||
amDone(result.success() ? ecSuccess : ecFailed, ex);
|
||||
if (result.status == BuildResult::TimedOut)
|
||||
// FIXME: strip: "error: "
|
||||
buildResult.errorMsg = ex->what();
|
||||
amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
|
||||
if (buildResult.status == BuildResult::TimedOut)
|
||||
worker.timedOut = true;
|
||||
if (result.status == BuildResult::PermanentFailure)
|
||||
if (buildResult.status == BuildResult::PermanentFailure)
|
||||
worker.permanentFailure = true;
|
||||
|
||||
mcExpectedBuilds.reset();
|
||||
mcRunningBuilds.reset();
|
||||
|
||||
if (result.success()) {
|
||||
if (buildResult.success()) {
|
||||
assert(!builtOutputs.empty());
|
||||
buildResult.builtOutputs = std::move(builtOutputs);
|
||||
if (status == BuildResult::Built)
|
||||
worker.doneBuilds++;
|
||||
} else {
|
||||
|
@ -1354,7 +1372,7 @@ void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex)
|
|||
if (traceBuiltOutputsFile != "") {
|
||||
std::fstream fs;
|
||||
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
||||
fs << worker.store.printStorePath(drvPath) << "\t" << result.toString() << std::endl;
|
||||
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "parsed-derivations.hh"
|
||||
#include "lock.hh"
|
||||
#include "build-result.hh"
|
||||
#include "store-api.hh"
|
||||
#include "pathlocks.hh"
|
||||
#include "goal.hh"
|
||||
|
@ -105,20 +104,8 @@ struct DerivationGoal : public Goal
|
|||
typedef void (DerivationGoal::*GoalState)();
|
||||
GoalState state;
|
||||
|
||||
/* The final output paths of the build.
|
||||
|
||||
- For input-addressed derivations, always the precomputed paths
|
||||
|
||||
- For content-addressed derivations, calcuated from whatever the hash
|
||||
ends up being. (Note that fixed outputs derivations that produce the
|
||||
"wrong" output still install that data under its true content-address.)
|
||||
*/
|
||||
OutputPathMap finalOutputs;
|
||||
|
||||
BuildMode buildMode;
|
||||
|
||||
BuildResult result;
|
||||
|
||||
/* The current round, if we're building multiple times. */
|
||||
size_t curRound = 1;
|
||||
|
||||
|
@ -153,8 +140,6 @@ struct DerivationGoal : public Goal
|
|||
/* Add wanted outputs to an already existing derivation goal. */
|
||||
void addWantedOutputs(const StringSet & outputs);
|
||||
|
||||
BuildResult getResult() { return result; }
|
||||
|
||||
/* The states. */
|
||||
void getDerivation();
|
||||
void loadDerivation();
|
||||
|
@ -176,7 +161,7 @@ struct DerivationGoal : public Goal
|
|||
|
||||
/* Check that the derivation outputs all exist and register them
|
||||
as valid. */
|
||||
virtual void registerOutputs();
|
||||
virtual DrvOutputs registerOutputs();
|
||||
|
||||
/* Open a log file and a pipe to it. */
|
||||
Path openLogFile();
|
||||
|
@ -211,8 +196,17 @@ struct DerivationGoal : public Goal
|
|||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
|
||||
OutputPathMap queryDerivationOutputMap();
|
||||
|
||||
/* Return the set of (in)valid paths. */
|
||||
void checkPathValidity();
|
||||
/* Update 'initialOutputs' to determine the current status of the
|
||||
outputs of the derivation. Also returns a Boolean denoting
|
||||
whether all outputs are valid and non-corrupt, and a
|
||||
'DrvOutputs' structure containing the valid and wanted
|
||||
outputs. */
|
||||
std::pair<bool, DrvOutputs> checkPathValidity();
|
||||
|
||||
/* Aborts if any output is not valid or corrupt, and otherwise
|
||||
returns a 'DrvOutputs' structure containing the wanted
|
||||
outputs. */
|
||||
DrvOutputs assertPathValidity();
|
||||
|
||||
/* Forcibly kill the child process, if any. */
|
||||
virtual void killChild();
|
||||
|
@ -223,6 +217,7 @@ struct DerivationGoal : public Goal
|
|||
|
||||
void done(
|
||||
BuildResult::Status status,
|
||||
DrvOutputs builtOutputs = {},
|
||||
std::optional<Error> ex = {});
|
||||
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
: Goal(worker)
|
||||
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
||||
const DrvOutput & id,
|
||||
Worker & worker,
|
||||
RepairFlag repair,
|
||||
std::optional<ContentAddress> ca)
|
||||
: Goal(worker, DerivedPath::Opaque { StorePath::dummy })
|
||||
, id(id)
|
||||
{
|
||||
state = &DrvOutputSubstitutionGoal::init;
|
||||
|
@ -32,7 +36,7 @@ void DrvOutputSubstitutionGoal::init()
|
|||
|
||||
void DrvOutputSubstitutionGoal::tryNext()
|
||||
{
|
||||
trace("Trying next substituter");
|
||||
trace("trying next substituter");
|
||||
|
||||
if (subs.size() == 0) {
|
||||
/* None left. Terminate this goal and let someone else deal
|
||||
|
@ -119,7 +123,7 @@ void DrvOutputSubstitutionGoal::realisationFetched()
|
|||
void DrvOutputSubstitutionGoal::outPathValid()
|
||||
{
|
||||
assert(outputInfo);
|
||||
trace("Output path substituted");
|
||||
trace("output path substituted");
|
||||
|
||||
if (nrFailed > 0) {
|
||||
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
||||
|
|
|
@ -47,43 +47,51 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<BuildResult> Store::buildPathsWithResults(
|
||||
const std::vector<DerivedPath> & reqs,
|
||||
BuildMode buildMode,
|
||||
std::shared_ptr<Store> evalStore)
|
||||
{
|
||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
||||
|
||||
Goals goals;
|
||||
for (const auto & br : reqs) {
|
||||
std::visit(overloaded {
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
goals.insert(worker.makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode));
|
||||
},
|
||||
[&](const DerivedPath::Opaque & bo) {
|
||||
goals.insert(worker.makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair));
|
||||
},
|
||||
}, br.raw());
|
||||
}
|
||||
|
||||
worker.run(goals);
|
||||
|
||||
std::vector<BuildResult> results;
|
||||
|
||||
for (auto & i : goals)
|
||||
results.push_back(i->buildResult);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode)
|
||||
{
|
||||
Worker worker(*this, *this);
|
||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
|
||||
|
||||
BuildResult result;
|
||||
|
||||
try {
|
||||
worker.run(Goals{goal});
|
||||
result = goal->getResult();
|
||||
return goal->buildResult;
|
||||
} catch (Error & e) {
|
||||
result.status = BuildResult::MiscFailure;
|
||||
result.errorMsg = e.msg();
|
||||
}
|
||||
// XXX: Should use `goal->queryPartialDerivationOutputMap()` once it's
|
||||
// extended to return the full realisation for each output
|
||||
auto staticDrvOutputs = drv.outputsAndOptPaths(*this);
|
||||
auto outputHashes = staticOutputHashes(*this, drv);
|
||||
for (auto & [outputName, staticOutput] : staticDrvOutputs) {
|
||||
auto outputId = DrvOutput{outputHashes.at(outputName), outputName};
|
||||
if (staticOutput.second)
|
||||
result.builtOutputs.insert_or_assign(
|
||||
outputId,
|
||||
Realisation{ outputId, *staticOutput.second}
|
||||
);
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
|
||||
auto realisation = this->queryRealisation(outputId);
|
||||
if (realisation)
|
||||
result.builtOutputs.insert_or_assign(
|
||||
outputId,
|
||||
*realisation
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return BuildResult {
|
||||
.status = BuildResult::MiscFailure,
|
||||
.errorMsg = e.msg(),
|
||||
.path = DerivedPath::Built { .drvPath = drvPath },
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "types.hh"
|
||||
#include "store-api.hh"
|
||||
#include "build-result.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -55,10 +56,15 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
|||
/* Whether the goal is finished. */
|
||||
ExitCode exitCode;
|
||||
|
||||
/* Build result. */
|
||||
BuildResult buildResult;
|
||||
|
||||
/* Exception containing an error message, if any. */
|
||||
std::optional<Error> ex;
|
||||
|
||||
Goal(Worker & worker) : worker(worker)
|
||||
Goal(Worker & worker, DerivedPath path)
|
||||
: worker(worker)
|
||||
, buildResult { .path = std::move(path) }
|
||||
{
|
||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
||||
exitCode = ecBusy;
|
||||
|
|
|
@ -194,7 +194,7 @@ void LocalDerivationGoal::tryLocalBuild() {
|
|||
outputLocks.unlock();
|
||||
buildUser.reset();
|
||||
worker.permanentFailure = true;
|
||||
done(BuildResult::InputRejected, e);
|
||||
done(BuildResult::InputRejected, {}, e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
else if (settings.sandboxMode == smDisabled)
|
||||
useChroot = false;
|
||||
else if (settings.sandboxMode == smRelaxed)
|
||||
useChroot = !(derivationIsImpure(derivationType)) && !noChroot;
|
||||
useChroot = !(derivationType.isImpure()) && !noChroot;
|
||||
}
|
||||
|
||||
auto & localStore = getLocalStore();
|
||||
|
@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
"nogroup:x:65534:\n", sandboxGid()));
|
||||
|
||||
/* Create /etc/hosts with localhost entry. */
|
||||
if (!(derivationIsImpure(derivationType)))
|
||||
if (!(derivationType.isImpure()))
|
||||
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
||||
|
||||
/* Make the closure of the inputs available in the chroot,
|
||||
|
@ -756,7 +756,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
if (tcsetattr(builderOut.writeSide.get(), TCSANOW, &term))
|
||||
throw SysError("putting pseudoterminal into raw mode");
|
||||
|
||||
result.startTime = time(0);
|
||||
buildResult.startTime = time(0);
|
||||
|
||||
/* Fork a child to build the package. */
|
||||
|
||||
|
@ -796,7 +796,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
us.
|
||||
*/
|
||||
|
||||
if (!(derivationIsImpure(derivationType)))
|
||||
if (!(derivationType.isImpure()))
|
||||
privateNetwork = true;
|
||||
|
||||
userNamespaceSync.create();
|
||||
|
@ -1049,7 +1049,7 @@ void LocalDerivationGoal::initEnv()
|
|||
derivation, tell the builder, so that for instance `fetchurl'
|
||||
can skip checking the output. On older Nixes, this environment
|
||||
variable won't be set, so `fetchurl' will do the check. */
|
||||
if (derivationIsFixed(derivationType)) env["NIX_OUTPUT_CHECKED"] = "1";
|
||||
if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
|
||||
|
||||
/* *Only* if this is a fixed-output derivation, propagate the
|
||||
values of the environment variables specified in the
|
||||
|
@ -1060,7 +1060,7 @@ void LocalDerivationGoal::initEnv()
|
|||
to the builder is generally impure, but the output of
|
||||
fixed-output derivations is by definition pure (since we
|
||||
already know the cryptographic hash of the output). */
|
||||
if (derivationIsImpure(derivationType)) {
|
||||
if (derivationType.isImpure()) {
|
||||
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
|
||||
env[i] = getEnv(i).value_or("");
|
||||
}
|
||||
|
@ -1260,6 +1260,16 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
}
|
||||
|
||||
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override
|
||||
{
|
||||
for (auto & result : buildPathsWithResults(paths, buildMode, evalStore))
|
||||
if (!result.success())
|
||||
result.rethrow();
|
||||
}
|
||||
|
||||
std::vector<BuildResult> buildPathsWithResults(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
BuildMode buildMode = bmNormal,
|
||||
std::shared_ptr<Store> evalStore = nullptr) override
|
||||
{
|
||||
assert(!evalStore);
|
||||
|
||||
|
@ -1273,26 +1283,13 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
throw InvalidPath("cannot build '%s' in recursive Nix because path is unknown", req.to_string(*next));
|
||||
}
|
||||
|
||||
next->buildPaths(paths, buildMode);
|
||||
auto results = next->buildPathsWithResults(paths, buildMode);
|
||||
|
||||
for (auto & path : paths) {
|
||||
auto p = std::get_if<DerivedPath::Built>(&path);
|
||||
if (!p) continue;
|
||||
auto & bfd = *p;
|
||||
auto drv = readDerivation(bfd.drvPath);
|
||||
auto drvHashes = staticOutputHashes(*this, drv);
|
||||
auto outputs = next->queryDerivationOutputMap(bfd.drvPath);
|
||||
for (auto & [outputName, outputPath] : outputs)
|
||||
if (wantOutput(outputName, bfd.outputs)) {
|
||||
newPaths.insert(outputPath);
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
auto thisRealisation = next->queryRealisation(
|
||||
DrvOutput{drvHashes.at(outputName), outputName}
|
||||
);
|
||||
assert(thisRealisation);
|
||||
newRealisations.insert(*thisRealisation);
|
||||
}
|
||||
}
|
||||
for (auto & result : results) {
|
||||
for (auto & [outputName, output] : result.builtOutputs) {
|
||||
newPaths.insert(output.outPath);
|
||||
newRealisations.insert(output);
|
||||
}
|
||||
}
|
||||
|
||||
StorePathSet closure;
|
||||
|
@ -1301,6 +1298,8 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
goal.addDependency(path);
|
||||
for (auto & real : Realisation::closure(*next, newRealisations))
|
||||
goal.addedDrvOutputs.insert(real.id);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
|
@ -1341,6 +1340,12 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
|||
next->queryMissing(allowed, willBuild, willSubstitute,
|
||||
unknown, downloadSize, narSize);
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getBuildLog(const StorePath & path) override
|
||||
{ return std::nullopt; }
|
||||
|
||||
virtual void addBuildLog(const StorePath & path, std::string_view log) override
|
||||
{ unsupported("addBuildLog"); }
|
||||
};
|
||||
|
||||
|
||||
|
@ -1669,7 +1674,7 @@ void LocalDerivationGoal::runChild()
|
|||
/* Fixed-output derivations typically need to access the
|
||||
network, so give them access to /etc/resolv.conf and so
|
||||
on. */
|
||||
if (derivationIsImpure(derivationType)) {
|
||||
if (derivationType.isImpure()) {
|
||||
// Only use nss functions to resolve hosts and
|
||||
// services. Don’t use it for anything else that may
|
||||
// be configured for this system. This limits the
|
||||
|
@ -1913,7 +1918,7 @@ void LocalDerivationGoal::runChild()
|
|||
|
||||
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
|
||||
|
||||
if (derivationIsImpure(derivationType))
|
||||
if (derivationType.isImpure())
|
||||
sandboxProfile += "(import \"sandbox-network.sb\")\n";
|
||||
|
||||
/* Add the output paths we'll use at build-time to the chroot */
|
||||
|
@ -2069,7 +2074,7 @@ void LocalDerivationGoal::runChild()
|
|||
}
|
||||
|
||||
|
||||
void LocalDerivationGoal::registerOutputs()
|
||||
DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||
{
|
||||
/* When using a build hook, the build hook can register the output
|
||||
as valid (by doing `nix-store --import'). If so we don't have
|
||||
|
@ -2078,10 +2083,8 @@ void LocalDerivationGoal::registerOutputs()
|
|||
We can only early return when the outputs are known a priori. For
|
||||
floating content-addressed derivations this isn't the case.
|
||||
*/
|
||||
if (hook) {
|
||||
DerivationGoal::registerOutputs();
|
||||
return;
|
||||
}
|
||||
if (hook)
|
||||
return DerivationGoal::registerOutputs();
|
||||
|
||||
std::map<std::string, ValidPathInfo> infos;
|
||||
|
||||
|
@ -2204,6 +2207,8 @@ void LocalDerivationGoal::registerOutputs()
|
|||
|
||||
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());
|
||||
|
||||
OutputPathMap finalOutputs;
|
||||
|
||||
for (auto & outputName : sortedOutputNames) {
|
||||
auto output = drv->outputs.at(outputName);
|
||||
auto & scratchPath = scratchOutputs.at(outputName);
|
||||
|
@ -2274,7 +2279,7 @@ void LocalDerivationGoal::registerOutputs()
|
|||
return res;
|
||||
};
|
||||
|
||||
auto newInfoFromCA = [&](const DerivationOutputCAFloating outputHash) -> ValidPathInfo {
|
||||
auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
|
||||
auto & st = outputStats.at(outputName);
|
||||
if (outputHash.method == FileIngestionMethod::Flat) {
|
||||
/* The output path should be a regular file without execute permission. */
|
||||
|
@ -2340,7 +2345,8 @@ void LocalDerivationGoal::registerOutputs()
|
|||
};
|
||||
|
||||
ValidPathInfo newInfo = std::visit(overloaded {
|
||||
[&](const DerivationOutputInputAddressed & output) {
|
||||
|
||||
[&](const DerivationOutput::InputAddressed & output) {
|
||||
/* input-addressed case */
|
||||
auto requiredFinalPath = output.path;
|
||||
/* Preemptively add rewrite rule for final hash, as that is
|
||||
|
@ -2359,8 +2365,9 @@ void LocalDerivationGoal::registerOutputs()
|
|||
newInfo0.references.insert(newInfo0.path);
|
||||
return newInfo0;
|
||||
},
|
||||
[&](const DerivationOutputCAFixed & dof) {
|
||||
auto newInfo0 = newInfoFromCA(DerivationOutputCAFloating {
|
||||
|
||||
[&](const DerivationOutput::CAFixed & dof) {
|
||||
auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
|
||||
.method = dof.hash.method,
|
||||
.hashType = dof.hash.hash.type,
|
||||
});
|
||||
|
@ -2381,15 +2388,18 @@ void LocalDerivationGoal::registerOutputs()
|
|||
}
|
||||
return newInfo0;
|
||||
},
|
||||
[&](DerivationOutputCAFloating dof) {
|
||||
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
return newInfoFromCA(dof);
|
||||
},
|
||||
[&](DerivationOutputDeferred) -> ValidPathInfo {
|
||||
|
||||
[&](const DerivationOutput::Deferred &) -> ValidPathInfo {
|
||||
// No derivation should reach that point without having been
|
||||
// rewritten first
|
||||
assert(false);
|
||||
},
|
||||
}, output.output);
|
||||
|
||||
}, output.raw());
|
||||
|
||||
/* FIXME: set proper permissions in restorePath() so
|
||||
we don't have to do another traversal. */
|
||||
|
@ -2499,11 +2509,12 @@ void LocalDerivationGoal::registerOutputs()
|
|||
}
|
||||
|
||||
if (buildMode == bmCheck) {
|
||||
// In case of FOD mismatches on `--check` an error must be thrown as this is also
|
||||
// a source for non-determinism.
|
||||
/* In case of fixed-output derivations, if there are
|
||||
mismatches on `--check` an error must be thrown as this is
|
||||
also a source for non-determinism. */
|
||||
if (delayedException)
|
||||
std::rethrow_exception(delayedException);
|
||||
return;
|
||||
return assertPathValidity();
|
||||
}
|
||||
|
||||
/* Apply output checks. */
|
||||
|
@ -2515,7 +2526,7 @@ void LocalDerivationGoal::registerOutputs()
|
|||
assert(prevInfos.size() == infos.size());
|
||||
for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
|
||||
if (!(*i == *j)) {
|
||||
result.isNonDeterministic = true;
|
||||
buildResult.isNonDeterministic = true;
|
||||
Path prev = worker.store.printStorePath(i->second.path) + checkSuffix;
|
||||
bool prevExists = keepPreviousRound && pathExists(prev);
|
||||
hintformat hint = prevExists
|
||||
|
@ -2553,7 +2564,7 @@ void LocalDerivationGoal::registerOutputs()
|
|||
|
||||
if (curRound < nrRounds) {
|
||||
prevInfos = std::move(infos);
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Remove the .check directories if we're done. FIXME: keep them
|
||||
|
@ -2588,17 +2599,24 @@ void LocalDerivationGoal::registerOutputs()
|
|||
means it's safe to link the derivation to the output hash. We must do
|
||||
that for floating CA derivations, which otherwise couldn't be cached,
|
||||
but it's fine to do in all cases. */
|
||||
DrvOutputs builtOutputs;
|
||||
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
for (auto& [outputName, newInfo] : infos) {
|
||||
auto thisRealisation = Realisation{
|
||||
.id = DrvOutput{initialOutputs.at(outputName).outputHash,
|
||||
outputName},
|
||||
.outPath = newInfo.path};
|
||||
for (auto & [outputName, newInfo] : infos) {
|
||||
auto thisRealisation = Realisation {
|
||||
.id = DrvOutput {
|
||||
initialOutputs.at(outputName).outputHash,
|
||||
outputName
|
||||
},
|
||||
.outPath = newInfo.path
|
||||
};
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
signRealisation(thisRealisation);
|
||||
worker.store.registerDrvOutput(thisRealisation);
|
||||
}
|
||||
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
||||
}
|
||||
|
||||
return builtOutputs;
|
||||
}
|
||||
|
||||
void LocalDerivationGoal::signRealisation(Realisation & realisation)
|
||||
|
@ -2607,7 +2625,7 @@ void LocalDerivationGoal::signRealisation(Realisation & realisation)
|
|||
}
|
||||
|
||||
|
||||
void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
|
||||
void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo> & outputs)
|
||||
{
|
||||
std::map<Path, const ValidPathInfo &> outputsByPath;
|
||||
for (auto & output : outputs)
|
||||
|
@ -2679,8 +2697,8 @@ void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & out
|
|||
for (auto & i : *value) {
|
||||
if (worker.store.isStorePath(i))
|
||||
spec.insert(worker.store.parseStorePath(i));
|
||||
else if (finalOutputs.count(i))
|
||||
spec.insert(finalOutputs.at(i));
|
||||
else if (outputs.count(i))
|
||||
spec.insert(outputs.at(i).path);
|
||||
else throw BuildError("derivation contains an illegal reference specifier '%s'", i);
|
||||
}
|
||||
|
||||
|
|
|
@ -169,7 +169,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
|||
|
||||
/* Check that the derivation outputs all exist and register them
|
||||
as valid. */
|
||||
void registerOutputs() override;
|
||||
DrvOutputs registerOutputs() override;
|
||||
|
||||
void signRealisation(Realisation &) override;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
namespace nix {
|
||||
|
||||
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||
: Goal(worker)
|
||||
: Goal(worker, DerivedPath::Opaque { storePath })
|
||||
, storePath(storePath)
|
||||
, repair(repair)
|
||||
, ca(ca)
|
||||
|
@ -24,6 +24,13 @@ PathSubstitutionGoal::~PathSubstitutionGoal()
|
|||
}
|
||||
|
||||
|
||||
void PathSubstitutionGoal::done(ExitCode result, BuildResult::Status status)
|
||||
{
|
||||
buildResult.status = status;
|
||||
amDone(result);
|
||||
}
|
||||
|
||||
|
||||
void PathSubstitutionGoal::work()
|
||||
{
|
||||
(this->*state)();
|
||||
|
@ -38,7 +45,7 @@ void PathSubstitutionGoal::init()
|
|||
|
||||
/* If the path already exists we're done. */
|
||||
if (!repair && worker.store.isValidPath(storePath)) {
|
||||
amDone(ecSuccess);
|
||||
done(ecSuccess, BuildResult::AlreadyValid);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -65,7 +72,7 @@ void PathSubstitutionGoal::tryNext()
|
|||
/* Hack: don't indicate failure if there were no substituters.
|
||||
In that case the calling derivation should just do a
|
||||
build. */
|
||||
amDone(substituterFailed ? ecFailed : ecNoSubstituters);
|
||||
done(substituterFailed ? ecFailed : ecNoSubstituters, BuildResult::NoSubstituters);
|
||||
|
||||
if (substituterFailed) {
|
||||
worker.failedSubstitutions++;
|
||||
|
@ -163,7 +170,9 @@ void PathSubstitutionGoal::referencesValid()
|
|||
|
||||
if (nrFailed > 0) {
|
||||
debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath));
|
||||
amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
||||
done(
|
||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||
BuildResult::DependencyFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -268,7 +277,7 @@ void PathSubstitutionGoal::finished()
|
|||
|
||||
worker.updateProgress();
|
||||
|
||||
amDone(ecSuccess);
|
||||
done(ecSuccess, BuildResult::Substituted);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -53,6 +53,8 @@ struct PathSubstitutionGoal : public Goal
|
|||
/* Content address for recomputing store path */
|
||||
std::optional<ContentAddress> ca;
|
||||
|
||||
void done(ExitCode result, BuildResult::Status status);
|
||||
|
||||
public:
|
||||
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||
~PathSubstitutionGoal();
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
#include "worker-protocol.hh"
|
||||
#include "build-result.hh"
|
||||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "log-store.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
#include "finally.hh"
|
||||
#include "archive.hh"
|
||||
|
@ -532,6 +534,25 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
break;
|
||||
}
|
||||
|
||||
case wopBuildPathsWithResults: {
|
||||
auto drvs = readDerivedPaths(*store, clientVersion, from);
|
||||
BuildMode mode = bmNormal;
|
||||
mode = (BuildMode) readInt(from);
|
||||
|
||||
/* Repairing is not atomic, so disallowed for "untrusted"
|
||||
clients. */
|
||||
if (mode == bmRepair && !trusted)
|
||||
throw Error("repairing is not allowed because you are not in 'trusted-users'");
|
||||
|
||||
logger->startWork();
|
||||
auto results = store->buildPathsWithResults(drvs, mode);
|
||||
logger->stopWork();
|
||||
|
||||
worker_proto::write(*store, to, results);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case wopBuildDerivation: {
|
||||
auto drvPath = store->parseStorePath(readString(from));
|
||||
BasicDerivation drv;
|
||||
|
@ -539,6 +560,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
BuildMode buildMode = (BuildMode) readInt(from);
|
||||
logger->startWork();
|
||||
|
||||
auto drvType = drv.type();
|
||||
|
||||
/* Content-addressed derivations are trustless because their output paths
|
||||
are verified by their content alone, so any derivation is free to
|
||||
try to produce such a path.
|
||||
|
@ -571,12 +594,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
derivations, we throw out the precomputed output paths and just
|
||||
store the hashes, so there aren't two competing sources of truth an
|
||||
attacker could exploit. */
|
||||
if (drv.type() == DerivationType::InputAddressed && !trusted)
|
||||
if (!(drvType.isCA() || trusted))
|
||||
throw Error("you are not privileged to build input-addressed derivations");
|
||||
|
||||
/* Make sure that the non-input-addressed derivations that got this far
|
||||
are in fact content-addressed if we don't trust them. */
|
||||
assert(derivationIsCA(drv.type()) || trusted);
|
||||
assert(drvType.isCA() || trusted);
|
||||
|
||||
/* Recompute the derivation path when we cannot trust the original. */
|
||||
if (!trusted) {
|
||||
|
@ -585,7 +608,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
original not-necessarily-resolved derivation to verify the drv
|
||||
derivation as adequate claim to the input-addressed output
|
||||
paths. */
|
||||
assert(derivationIsCA(drv.type()));
|
||||
assert(drvType.isCA());
|
||||
|
||||
Derivation drv2;
|
||||
static_cast<BasicDerivation &>(drv2) = drv;
|
||||
|
@ -626,7 +649,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
Path path = absPath(readString(from));
|
||||
|
||||
logger->startWork();
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
gcStore.addIndirectRoot(path);
|
||||
logger->stopWork();
|
||||
|
||||
|
@ -644,7 +667,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
|
||||
case wopFindRoots: {
|
||||
logger->startWork();
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
Roots roots = gcStore.findRoots(!trusted);
|
||||
logger->stopWork();
|
||||
|
||||
|
@ -676,7 +699,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
logger->startWork();
|
||||
if (options.ignoreLiveness)
|
||||
throw Error("you are not allowed to ignore liveness");
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
gcStore.collectGarbage(options, results);
|
||||
logger->stopWork();
|
||||
|
||||
|
@ -934,11 +957,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
logger->startWork();
|
||||
if (!trusted)
|
||||
throw Error("you are not privileged to add logs");
|
||||
auto & logStore = require<LogStore>(*store);
|
||||
{
|
||||
FramedSource source(from);
|
||||
StringSink sink;
|
||||
source.drainInto(sink);
|
||||
store->addBuildLog(path, sink.s);
|
||||
logStore.addBuildLog(path, sink.s);
|
||||
}
|
||||
logger->stopWork();
|
||||
to << 1;
|
||||
|
|
|
@ -11,72 +11,71 @@ namespace nix {
|
|||
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[](const DerivationOutputInputAddressed & doi) -> std::optional<StorePath> {
|
||||
[](const DerivationOutput::InputAddressed & doi) -> std::optional<StorePath> {
|
||||
return { doi.path };
|
||||
},
|
||||
[&](const DerivationOutputCAFixed & dof) -> std::optional<StorePath> {
|
||||
[&](const DerivationOutput::CAFixed & dof) -> std::optional<StorePath> {
|
||||
return {
|
||||
dof.path(store, drvName, outputName)
|
||||
};
|
||||
},
|
||||
[](const DerivationOutputCAFloating & dof) -> std::optional<StorePath> {
|
||||
[](const DerivationOutput::CAFloating & dof) -> std::optional<StorePath> {
|
||||
return std::nullopt;
|
||||
},
|
||||
[](const DerivationOutputDeferred &) -> std::optional<StorePath> {
|
||||
[](const DerivationOutput::Deferred &) -> std::optional<StorePath> {
|
||||
return std::nullopt;
|
||||
},
|
||||
}, output);
|
||||
}, raw());
|
||||
}
|
||||
|
||||
|
||||
StorePath DerivationOutputCAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const {
|
||||
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const {
|
||||
return store.makeFixedOutputPath(
|
||||
hash.method, hash.hash,
|
||||
outputPathName(drvName, outputName));
|
||||
}
|
||||
|
||||
|
||||
bool derivationIsCA(DerivationType dt) {
|
||||
switch (dt) {
|
||||
case DerivationType::InputAddressed: return false;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return true;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
// Since enums can have non-variant values, but making a `default:` would
|
||||
// disable exhaustiveness warnings.
|
||||
assert(false);
|
||||
bool DerivationType::isCA() const {
|
||||
/* Normally we do the full `std::visit` to make sure we have
|
||||
exhaustively handled all variants, but so long as there is a
|
||||
variant called `ContentAddressed`, it must be the only one for
|
||||
which `isCA` is true for this to make sense!. */
|
||||
return std::holds_alternative<ContentAddressed>(raw());
|
||||
}
|
||||
|
||||
bool derivationIsFixed(DerivationType dt) {
|
||||
switch (dt) {
|
||||
case DerivationType::InputAddressed: return false;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return false;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
assert(false);
|
||||
bool DerivationType::isFixed() const {
|
||||
return std::visit(overloaded {
|
||||
[](const InputAddressed & ia) {
|
||||
return false;
|
||||
},
|
||||
[](const ContentAddressed & ca) {
|
||||
return ca.fixed;
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
bool derivationHasKnownOutputPaths(DerivationType dt) {
|
||||
switch (dt) {
|
||||
case DerivationType::InputAddressed: return true;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return false;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
assert(false);
|
||||
bool DerivationType::hasKnownOutputPaths() const {
|
||||
return std::visit(overloaded {
|
||||
[](const InputAddressed & ia) {
|
||||
return !ia.deferred;
|
||||
},
|
||||
[](const ContentAddressed & ca) {
|
||||
return ca.fixed;
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
|
||||
bool derivationIsImpure(DerivationType dt) {
|
||||
switch (dt) {
|
||||
case DerivationType::InputAddressed: return false;
|
||||
case DerivationType::CAFixed: return true;
|
||||
case DerivationType::CAFloating: return false;
|
||||
case DerivationType::DeferredInputAddressed: return false;
|
||||
};
|
||||
assert(false);
|
||||
bool DerivationType::isImpure() const {
|
||||
return std::visit(overloaded {
|
||||
[](const InputAddressed & ia) {
|
||||
return false;
|
||||
},
|
||||
[](const ContentAddressed & ca) {
|
||||
return !ca.pure;
|
||||
},
|
||||
}, raw());
|
||||
}
|
||||
|
||||
|
||||
|
@ -179,35 +178,27 @@ static DerivationOutput parseDerivationOutput(const Store & store,
|
|||
const auto hashType = parseHashType(hashAlgo);
|
||||
if (hash != "") {
|
||||
validatePath(pathS);
|
||||
return DerivationOutput {
|
||||
.output = DerivationOutputCAFixed {
|
||||
.hash = FixedOutputHash {
|
||||
.method = std::move(method),
|
||||
.hash = Hash::parseNonSRIUnprefixed(hash, hashType),
|
||||
},
|
||||
return DerivationOutput::CAFixed {
|
||||
.hash = FixedOutputHash {
|
||||
.method = std::move(method),
|
||||
.hash = Hash::parseNonSRIUnprefixed(hash, hashType),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||
assert(pathS == "");
|
||||
return DerivationOutput {
|
||||
.output = DerivationOutputCAFloating {
|
||||
.method = std::move(method),
|
||||
.hashType = std::move(hashType),
|
||||
},
|
||||
return DerivationOutput::CAFloating {
|
||||
.method = std::move(method),
|
||||
.hashType = std::move(hashType),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (pathS == "") {
|
||||
return DerivationOutput {
|
||||
.output = DerivationOutputDeferred { }
|
||||
};
|
||||
return DerivationOutput::Deferred { };
|
||||
}
|
||||
validatePath(pathS);
|
||||
return DerivationOutput {
|
||||
.output = DerivationOutputInputAddressed {
|
||||
.path = store.parseStorePath(pathS),
|
||||
}
|
||||
return DerivationOutput::InputAddressed {
|
||||
.path = store.parseStorePath(pathS),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -335,27 +326,27 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
|
|||
if (first) first = false; else s += ',';
|
||||
s += '('; printUnquotedString(s, i.first);
|
||||
std::visit(overloaded {
|
||||
[&](const DerivationOutputInputAddressed & doi) {
|
||||
[&](const DerivationOutput::InputAddressed & doi) {
|
||||
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path));
|
||||
s += ','; printUnquotedString(s, "");
|
||||
s += ','; printUnquotedString(s, "");
|
||||
},
|
||||
[&](const DerivationOutputCAFixed & dof) {
|
||||
[&](const DerivationOutput::CAFixed & dof) {
|
||||
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
|
||||
s += ','; printUnquotedString(s, dof.hash.printMethodAlgo());
|
||||
s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false));
|
||||
},
|
||||
[&](const DerivationOutputCAFloating & dof) {
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
s += ','; printUnquotedString(s, "");
|
||||
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
||||
s += ','; printUnquotedString(s, "");
|
||||
},
|
||||
[&](const DerivationOutputDeferred &) {
|
||||
[&](const DerivationOutput::Deferred &) {
|
||||
s += ','; printUnquotedString(s, "");
|
||||
s += ','; printUnquotedString(s, "");
|
||||
s += ','; printUnquotedString(s, "");
|
||||
}
|
||||
}, i.second.output);
|
||||
}, i.second.raw());
|
||||
s += ')';
|
||||
}
|
||||
|
||||
|
@ -423,13 +414,13 @@ DerivationType BasicDerivation::type() const
|
|||
std::optional<HashType> floatingHashType;
|
||||
for (auto & i : outputs) {
|
||||
std::visit(overloaded {
|
||||
[&](const DerivationOutputInputAddressed &) {
|
||||
[&](const DerivationOutput::InputAddressed &) {
|
||||
inputAddressedOutputs.insert(i.first);
|
||||
},
|
||||
[&](const DerivationOutputCAFixed &) {
|
||||
[&](const DerivationOutput::CAFixed &) {
|
||||
fixedCAOutputs.insert(i.first);
|
||||
},
|
||||
[&](const DerivationOutputCAFloating & dof) {
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
floatingCAOutputs.insert(i.first);
|
||||
if (!floatingHashType) {
|
||||
floatingHashType = dof.hashType;
|
||||
|
@ -438,27 +429,37 @@ DerivationType BasicDerivation::type() const
|
|||
throw Error("All floating outputs must use the same hash type");
|
||||
}
|
||||
},
|
||||
[&](const DerivationOutputDeferred &) {
|
||||
[&](const DerivationOutput::Deferred &) {
|
||||
deferredIAOutputs.insert(i.first);
|
||||
},
|
||||
}, i.second.output);
|
||||
}, i.second.raw());
|
||||
}
|
||||
|
||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
throw Error("Must have at least one output");
|
||||
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
return DerivationType::InputAddressed;
|
||||
return DerivationType::InputAddressed {
|
||||
.deferred = false,
|
||||
};
|
||||
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
if (fixedCAOutputs.size() > 1)
|
||||
// FIXME: Experimental feature?
|
||||
throw Error("Only one fixed output is allowed for now");
|
||||
if (*fixedCAOutputs.begin() != "out")
|
||||
throw Error("Single fixed output must be named \"out\"");
|
||||
return DerivationType::CAFixed;
|
||||
return DerivationType::ContentAddressed {
|
||||
.pure = false,
|
||||
.fixed = true,
|
||||
};
|
||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
||||
return DerivationType::CAFloating;
|
||||
return DerivationType::ContentAddressed {
|
||||
.pure = true,
|
||||
.fixed = false,
|
||||
};
|
||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
|
||||
return DerivationType::DeferredInputAddressed;
|
||||
return DerivationType::InputAddressed {
|
||||
.deferred = true,
|
||||
};
|
||||
} else {
|
||||
throw Error("Can't mix derivation output types");
|
||||
}
|
||||
|
@ -510,13 +511,13 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath &
|
|||
*/
|
||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
|
||||
{
|
||||
bool isDeferred = false;
|
||||
auto type = drv.type();
|
||||
|
||||
/* Return a fixed hash for fixed-output derivations. */
|
||||
switch (drv.type()) {
|
||||
case DerivationType::CAFixed: {
|
||||
if (type.isFixed()) {
|
||||
std::map<std::string, Hash> outputHashes;
|
||||
for (const auto & i : drv.outputs) {
|
||||
auto & dof = std::get<DerivationOutputCAFixed>(i.second.output);
|
||||
auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw());
|
||||
auto hash = hashString(htSHA256, "fixed:out:"
|
||||
+ dof.hash.printMethodAlgo() + ":"
|
||||
+ dof.hash.hash.to_string(Base16, false) + ":"
|
||||
|
@ -525,33 +526,37 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
|||
}
|
||||
return outputHashes;
|
||||
}
|
||||
case DerivationType::CAFloating:
|
||||
isDeferred = true;
|
||||
break;
|
||||
case DerivationType::InputAddressed:
|
||||
break;
|
||||
case DerivationType::DeferredInputAddressed:
|
||||
break;
|
||||
}
|
||||
|
||||
auto kind = std::visit(overloaded {
|
||||
[](const DerivationType::InputAddressed & ia) {
|
||||
/* This might be a "pesimistically" deferred output, so we don't
|
||||
"taint" the kind yet. */
|
||||
return DrvHash::Kind::Regular;
|
||||
},
|
||||
[](const DerivationType::ContentAddressed & ca) {
|
||||
return ca.fixed
|
||||
? DrvHash::Kind::Regular
|
||||
: DrvHash::Kind::Deferred;
|
||||
},
|
||||
}, drv.type().raw());
|
||||
|
||||
/* For other derivations, replace the inputs paths with recursive
|
||||
calls to this function. */
|
||||
std::map<std::string, StringSet> inputs2;
|
||||
for (auto & i : drv.inputDrvs) {
|
||||
const auto & res = pathDerivationModulo(store, i.first);
|
||||
for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
|
||||
// Avoid lambda capture restriction with standard / Clang
|
||||
auto & inputOutputs = inputOutputs0;
|
||||
const auto & res = pathDerivationModulo(store, drvPath);
|
||||
std::visit(overloaded {
|
||||
// Regular non-CA derivation, replace derivation
|
||||
[&](const Hash & drvHash) {
|
||||
inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second);
|
||||
},
|
||||
[&](const DeferredHash & deferredHash) {
|
||||
isDeferred = true;
|
||||
inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
|
||||
[&](const DrvHash & drvHash) {
|
||||
kind |= drvHash.kind;
|
||||
inputs2.insert_or_assign(drvHash.hash.to_string(Base16, false), inputOutputs);
|
||||
},
|
||||
// CA derivation's output hashes
|
||||
[&](const CaOutputHashes & outputHashes) {
|
||||
std::set<std::string> justOut = { "out" };
|
||||
for (auto & output : i.second) {
|
||||
for (auto & output : inputOutputs) {
|
||||
/* Put each one in with a single "out" output.. */
|
||||
const auto h = outputHashes.at(output);
|
||||
inputs2.insert_or_assign(
|
||||
|
@ -559,15 +564,24 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
|||
justOut);
|
||||
}
|
||||
},
|
||||
}, res);
|
||||
}, res.raw());
|
||||
}
|
||||
|
||||
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
||||
|
||||
if (isDeferred)
|
||||
return DeferredHash { hash };
|
||||
else
|
||||
return hash;
|
||||
return DrvHash { .hash = hash, .kind = kind };
|
||||
}
|
||||
|
||||
|
||||
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept
|
||||
{
|
||||
switch (other) {
|
||||
case DrvHash::Kind::Regular:
|
||||
break;
|
||||
case DrvHash::Kind::Deferred:
|
||||
self = other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -575,20 +589,15 @@ std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation &
|
|||
{
|
||||
std::map<std::string, Hash> res;
|
||||
std::visit(overloaded {
|
||||
[&](const Hash & drvHash) {
|
||||
[&](const DrvHash & drvHash) {
|
||||
for (auto & outputName : drv.outputNames()) {
|
||||
res.insert({outputName, drvHash});
|
||||
}
|
||||
},
|
||||
[&](const DeferredHash & deferredHash) {
|
||||
for (auto & outputName : drv.outputNames()) {
|
||||
res.insert({outputName, deferredHash.hash});
|
||||
res.insert({outputName, drvHash.hash});
|
||||
}
|
||||
},
|
||||
[&](const CaOutputHashes & outputHashes) {
|
||||
res = outputHashes;
|
||||
},
|
||||
}, hashDerivationModulo(store, drv, true));
|
||||
}, hashDerivationModulo(store, drv, true).raw());
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -669,27 +678,27 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
|
|||
for (auto & i : drv.outputs) {
|
||||
out << i.first;
|
||||
std::visit(overloaded {
|
||||
[&](const DerivationOutputInputAddressed & doi) {
|
||||
[&](const DerivationOutput::InputAddressed & doi) {
|
||||
out << store.printStorePath(doi.path)
|
||||
<< ""
|
||||
<< "";
|
||||
},
|
||||
[&](const DerivationOutputCAFixed & dof) {
|
||||
[&](const DerivationOutput::CAFixed & dof) {
|
||||
out << store.printStorePath(dof.path(store, drv.name, i.first))
|
||||
<< dof.hash.printMethodAlgo()
|
||||
<< dof.hash.hash.to_string(Base16, false);
|
||||
},
|
||||
[&](const DerivationOutputCAFloating & dof) {
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
out << ""
|
||||
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
|
||||
<< "";
|
||||
},
|
||||
[&](const DerivationOutputDeferred &) {
|
||||
[&](const DerivationOutput::Deferred &) {
|
||||
out << ""
|
||||
<< ""
|
||||
<< "";
|
||||
},
|
||||
}, i.second.output);
|
||||
}, i.second.raw());
|
||||
}
|
||||
worker_proto::write(store, out, drv.inputSrcs);
|
||||
out << drv.platform << drv.builder << drv.args;
|
||||
|
@ -737,45 +746,63 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
|
|||
|
||||
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
|
||||
for (auto & [outputName, output] : drv.outputs) {
|
||||
if (std::holds_alternative<DerivationOutputDeferred>(output.output)) {
|
||||
Hash h = std::get<Hash>(hashModulo);
|
||||
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
|
||||
auto & h = hashModulo.requireNoFixedNonDeferred();
|
||||
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
||||
drv.env[outputName] = store.printStorePath(outPath);
|
||||
output = DerivationOutput {
|
||||
.output = DerivationOutputInputAddressed {
|
||||
.path = std::move(outPath),
|
||||
},
|
||||
output = DerivationOutput::InputAddressed {
|
||||
.path = std::move(outPath),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Hash & DrvHashModulo::requireNoFixedNonDeferred() const {
|
||||
auto * drvHashOpt = std::get_if<DrvHash>(&raw());
|
||||
assert(drvHashOpt);
|
||||
assert(drvHashOpt->kind == DrvHash::Kind::Regular);
|
||||
return drvHashOpt->hash;
|
||||
}
|
||||
|
||||
static bool tryResolveInput(
|
||||
Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
|
||||
const StorePath & inputDrv, const StringSet & inputOutputs)
|
||||
{
|
||||
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(inputDrv);
|
||||
|
||||
auto getOutput = [&](const std::string & outputName) {
|
||||
auto & actualPathOpt = inputDrvOutputs.at(outputName);
|
||||
if (!actualPathOpt)
|
||||
warn("output %s of input %s missing, aborting the resolving",
|
||||
outputName,
|
||||
store.printStorePath(inputDrv)
|
||||
);
|
||||
return actualPathOpt;
|
||||
};
|
||||
|
||||
for (auto & outputName : inputOutputs) {
|
||||
auto actualPathOpt = getOutput(outputName);
|
||||
if (!actualPathOpt) return false;
|
||||
auto actualPath = *actualPathOpt;
|
||||
inputRewrites.emplace(
|
||||
downstreamPlaceholder(store, inputDrv, outputName),
|
||||
store.printStorePath(actualPath));
|
||||
inputSrcs.insert(std::move(actualPath));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
||||
BasicDerivation resolved { *this };
|
||||
|
||||
// Input paths that we'll want to rewrite in the derivation
|
||||
StringMap inputRewrites;
|
||||
|
||||
for (auto & input : inputDrvs) {
|
||||
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(input.first);
|
||||
StringSet newOutputNames;
|
||||
for (auto & outputName : input.second) {
|
||||
auto actualPathOpt = inputDrvOutputs.at(outputName);
|
||||
if (!actualPathOpt) {
|
||||
warn("output %s of input %s missing, aborting the resolving",
|
||||
outputName,
|
||||
store.printStorePath(input.first)
|
||||
);
|
||||
return std::nullopt;
|
||||
}
|
||||
auto actualPath = *actualPathOpt;
|
||||
inputRewrites.emplace(
|
||||
downstreamPlaceholder(store, input.first, outputName),
|
||||
store.printStorePath(actualPath));
|
||||
resolved.inputSrcs.insert(std::move(actualPath));
|
||||
}
|
||||
}
|
||||
for (auto & [inputDrv, inputOutputs] : inputDrvs)
|
||||
if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites, inputDrv, inputOutputs))
|
||||
return std::nullopt;
|
||||
|
||||
rewriteDerivation(store, resolved, inputRewrites);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "content-address.hh"
|
||||
#include "repair-flag.hh"
|
||||
#include "sync.hh"
|
||||
|
||||
#include <map>
|
||||
|
@ -44,19 +45,31 @@ struct DerivationOutputCAFloating
|
|||
*/
|
||||
struct DerivationOutputDeferred {};
|
||||
|
||||
struct DerivationOutput
|
||||
typedef std::variant<
|
||||
DerivationOutputInputAddressed,
|
||||
DerivationOutputCAFixed,
|
||||
DerivationOutputCAFloating,
|
||||
DerivationOutputDeferred
|
||||
> _DerivationOutputRaw;
|
||||
|
||||
struct DerivationOutput : _DerivationOutputRaw
|
||||
{
|
||||
std::variant<
|
||||
DerivationOutputInputAddressed,
|
||||
DerivationOutputCAFixed,
|
||||
DerivationOutputCAFloating,
|
||||
DerivationOutputDeferred
|
||||
> output;
|
||||
using Raw = _DerivationOutputRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using InputAddressed = DerivationOutputInputAddressed;
|
||||
using CAFixed = DerivationOutputCAFixed;
|
||||
using CAFloating = DerivationOutputCAFloating;
|
||||
using Deferred = DerivationOutputDeferred;
|
||||
|
||||
/* Note, when you use this function you should make sure that you're passing
|
||||
the right derivation name. When in doubt, you should use the safer
|
||||
interface provided by BasicDerivation::outputsAndOptPaths */
|
||||
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
|
||||
|
@ -72,30 +85,50 @@ typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePat
|
|||
output IDs we are interested in. */
|
||||
typedef std::map<StorePath, StringSet> DerivationInputs;
|
||||
|
||||
enum struct DerivationType : uint8_t {
|
||||
InputAddressed,
|
||||
DeferredInputAddressed,
|
||||
CAFixed,
|
||||
CAFloating,
|
||||
struct DerivationType_InputAddressed {
|
||||
bool deferred;
|
||||
};
|
||||
|
||||
/* Do the outputs of the derivation have paths calculated from their content,
|
||||
or from the derivation itself? */
|
||||
bool derivationIsCA(DerivationType);
|
||||
struct DerivationType_ContentAddressed {
|
||||
bool pure;
|
||||
bool fixed;
|
||||
};
|
||||
|
||||
/* Is the content of the outputs fixed a-priori via a hash? Never true for
|
||||
non-CA derivations. */
|
||||
bool derivationIsFixed(DerivationType);
|
||||
typedef std::variant<
|
||||
DerivationType_InputAddressed,
|
||||
DerivationType_ContentAddressed
|
||||
> _DerivationTypeRaw;
|
||||
|
||||
/* Is the derivation impure and needs to access non-deterministic resources, or
|
||||
pure and can be sandboxed? Note that whether or not we actually sandbox the
|
||||
derivation is controlled separately. Never true for non-CA derivations. */
|
||||
bool derivationIsImpure(DerivationType);
|
||||
struct DerivationType : _DerivationTypeRaw {
|
||||
using Raw = _DerivationTypeRaw;
|
||||
using Raw::Raw;
|
||||
using InputAddressed = DerivationType_InputAddressed;
|
||||
using ContentAddressed = DerivationType_ContentAddressed;
|
||||
|
||||
/* Does the derivation knows its own output paths?
|
||||
* Only true when there's no floating-ca derivation involved in the closure.
|
||||
*/
|
||||
bool derivationHasKnownOutputPaths(DerivationType);
|
||||
|
||||
/* Do the outputs of the derivation have paths calculated from their content,
|
||||
or from the derivation itself? */
|
||||
bool isCA() const;
|
||||
|
||||
/* Is the content of the outputs fixed a-priori via a hash? Never true for
|
||||
non-CA derivations. */
|
||||
bool isFixed() const;
|
||||
|
||||
/* Is the derivation impure and needs to access non-deterministic resources, or
|
||||
pure and can be sandboxed? Note that whether or not we actually sandbox the
|
||||
derivation is controlled separately. Never true for non-CA derivations. */
|
||||
bool isImpure() const;
|
||||
|
||||
/* Does the derivation knows its own output paths?
|
||||
Only true when there's no floating-ca derivation involved in the
|
||||
closure, or if fixed output.
|
||||
*/
|
||||
bool hasKnownOutputPaths() const;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct BasicDerivation
|
||||
{
|
||||
|
@ -150,8 +183,6 @@ struct Derivation : BasicDerivation
|
|||
|
||||
class Store;
|
||||
|
||||
enum RepairFlag : bool { NoRepair = false, Repair = true };
|
||||
|
||||
/* Write a derivation to the Nix store, and return its path. */
|
||||
StorePath writeDerivation(Store & store,
|
||||
const Derivation & drv,
|
||||
|
@ -175,13 +206,43 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
|
|||
// whose output hashes are always known since they are fixed up-front.
|
||||
typedef std::map<std::string, Hash> CaOutputHashes;
|
||||
|
||||
struct DeferredHash { Hash hash; };
|
||||
struct DrvHash {
|
||||
Hash hash;
|
||||
|
||||
enum struct Kind: bool {
|
||||
// Statically determined derivations.
|
||||
// This hash will be directly used to compute the output paths
|
||||
Regular,
|
||||
// Floating-output derivations (and their reverse dependencies).
|
||||
Deferred,
|
||||
};
|
||||
|
||||
Kind kind;
|
||||
};
|
||||
|
||||
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept;
|
||||
|
||||
typedef std::variant<
|
||||
Hash, // regular DRV normalized hash
|
||||
CaOutputHashes, // Fixed-output derivation hashes
|
||||
DeferredHash // Deferred hashes for floating outputs drvs and their dependencies
|
||||
> DrvHashModulo;
|
||||
// Regular normalized derivation hash, and whether it was deferred (because
|
||||
// an ancestor derivation is a floating content addressed derivation).
|
||||
DrvHash,
|
||||
// Fixed-output derivation hashes
|
||||
CaOutputHashes
|
||||
> _DrvHashModuloRaw;
|
||||
|
||||
struct DrvHashModulo : _DrvHashModuloRaw {
|
||||
using Raw = _DrvHashModuloRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
/* Get hash, throwing if it is per-output CA hashes or a
|
||||
deferred Drv hash.
|
||||
*/
|
||||
const Hash & requireNoFixedNonDeferred() const;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/* Returns hashes with the details of fixed-output subderivations
|
||||
expunged.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "derived-path.hh"
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
@ -11,6 +12,21 @@ nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
|
|||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
|
||||
nlohmann::json res;
|
||||
res["drvPath"] = store->printStorePath(drvPath);
|
||||
// Fallback for the input-addressed derivation case: We expect to always be
|
||||
// able to print the output paths, so let’s do it
|
||||
auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
|
||||
for (const auto& output : outputs) {
|
||||
if (knownOutputs.at(output))
|
||||
res["outputs"][output] = store->printStorePath(knownOutputs.at(output).value());
|
||||
else
|
||||
res["outputs"][output] = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
|
||||
nlohmann::json res;
|
||||
res["drvPath"] = store->printStorePath(drvPath);
|
||||
|
@ -35,16 +51,22 @@ StorePathSet BuiltPath::outPaths() const
|
|||
);
|
||||
}
|
||||
|
||||
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store) {
|
||||
template<typename T>
|
||||
nlohmann::json stuffToJSON(const std::vector<T> & ts, ref<Store> store) {
|
||||
auto res = nlohmann::json::array();
|
||||
for (const BuiltPath & buildable : buildables) {
|
||||
std::visit([&res, store](const auto & buildable) {
|
||||
res.push_back(buildable.toJSON(store));
|
||||
}, buildable.raw());
|
||||
for (const T & t : ts) {
|
||||
std::visit([&res, store](const auto & t) {
|
||||
res.push_back(t.toJSON(store));
|
||||
}, t.raw());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store)
|
||||
{ return stuffToJSON<BuiltPath>(buildables, store); }
|
||||
nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref<Store> store)
|
||||
{ return stuffToJSON<DerivedPath>(paths, store); }
|
||||
|
||||
|
||||
std::string DerivedPath::Opaque::to_string(const Store & store) const {
|
||||
return store.printStorePath(path);
|
||||
|
|
|
@ -45,6 +45,7 @@ struct DerivedPathBuilt {
|
|||
|
||||
std::string to_string(const Store & store) const;
|
||||
static DerivedPathBuilt parse(const Store & store, std::string_view);
|
||||
nlohmann::json toJSON(ref<Store> store) const;
|
||||
};
|
||||
|
||||
using _DerivedPathRaw = std::variant<
|
||||
|
@ -119,5 +120,6 @@ typedef std::vector<DerivedPath> DerivedPaths;
|
|||
typedef std::vector<BuiltPath> BuiltPaths;
|
||||
|
||||
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store);
|
||||
nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref<Store> store);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
#include "gc-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
GcStore & requireGcStore(Store & store)
|
||||
{
|
||||
auto * gcStore = dynamic_cast<GcStore *>(&store);
|
||||
if (!gcStore)
|
||||
throw UsageError("Garbage collection not supported by this store");
|
||||
return *gcStore;
|
||||
}
|
||||
|
||||
}
|
|
@ -61,6 +61,8 @@ struct GCResults
|
|||
|
||||
struct GcStore : public virtual Store
|
||||
{
|
||||
inline static std::string operationName = "Garbage collection";
|
||||
|
||||
/* Add an indirect root, which is merely a symlink to `path' from
|
||||
/nix/var/nix/gcroots/auto/<hash of `path'>. `path' is supposed
|
||||
to be a symlink to a store path. The garbage collector will
|
||||
|
@ -79,6 +81,4 @@ struct GcStore : public virtual Store
|
|||
virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0;
|
||||
};
|
||||
|
||||
GcStore & requireGcStore(Store & store);
|
||||
|
||||
}
|
||||
|
|
|
@ -678,7 +678,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
alive.insert(start);
|
||||
try {
|
||||
StorePathSet closure;
|
||||
computeFSClosure(*path, closure);
|
||||
computeFSClosure(*path, closure,
|
||||
/* flipDirection */ false, gcKeepOutputs, gcKeepDerivations);
|
||||
for (auto & p : closure)
|
||||
alive.insert(p);
|
||||
} catch (InvalidPath &) { }
|
||||
|
@ -841,7 +842,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
|||
if (unlink(path.c_str()) == -1)
|
||||
throw SysError("deleting '%1%'", path);
|
||||
|
||||
results.bytesFreed += st.st_size;
|
||||
/* Do not accound for deleted file here. Rely on deletePath()
|
||||
accounting. */
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
|
|
|
@ -279,7 +279,7 @@ public:
|
|||
|
||||
conn->to.flush();
|
||||
|
||||
BuildResult status;
|
||||
BuildResult status { .path = DerivedPath::Built { .drvPath = drvPath } };
|
||||
status.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> status.errorMsg;
|
||||
|
||||
|
@ -317,7 +317,7 @@ public:
|
|||
|
||||
conn->to.flush();
|
||||
|
||||
BuildResult result;
|
||||
BuildResult result { .path = DerivedPath::Opaque { StorePath::dummy } };
|
||||
result.status = (BuildResult::Status) readInt(conn->from);
|
||||
|
||||
if (!result.success()) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "store-api.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "log-store.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -24,7 +25,10 @@ struct LocalFSStoreConfig : virtual StoreConfig
|
|||
"physical path to the Nix store"};
|
||||
};
|
||||
|
||||
class LocalFSStore : public virtual LocalFSStoreConfig, public virtual Store, virtual GcStore
|
||||
class LocalFSStore : public virtual LocalFSStoreConfig,
|
||||
public virtual Store,
|
||||
public virtual GcStore,
|
||||
public virtual LogStore
|
||||
{
|
||||
public:
|
||||
|
||||
|
|
|
@ -698,11 +698,11 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
|||
std::optional<Hash> h;
|
||||
for (auto & i : drv.outputs) {
|
||||
std::visit(overloaded {
|
||||
[&](const DerivationOutputInputAddressed & doia) {
|
||||
[&](const DerivationOutput::InputAddressed & doia) {
|
||||
if (!h) {
|
||||
// somewhat expensive so we do lazily
|
||||
auto temp = hashDerivationModulo(*this, drv, true);
|
||||
h = std::get<Hash>(temp);
|
||||
auto h0 = hashDerivationModulo(*this, drv, true);
|
||||
h = h0.requireNoFixedNonDeferred();
|
||||
}
|
||||
StorePath recomputed = makeOutputPath(i.first, *h, drvName);
|
||||
if (doia.path != recomputed)
|
||||
|
@ -710,16 +710,17 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
|||
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
||||
envHasRightPath(doia.path, i.first);
|
||||
},
|
||||
[&](const DerivationOutputCAFixed & dof) {
|
||||
[&](const DerivationOutput::CAFixed & dof) {
|
||||
StorePath path = makeFixedOutputPath(dof.hash.method, dof.hash.hash, drvName);
|
||||
envHasRightPath(path, i.first);
|
||||
},
|
||||
[&](const DerivationOutputCAFloating &) {
|
||||
[&](const DerivationOutput::CAFloating &) {
|
||||
/* Nothing to check */
|
||||
},
|
||||
[&](const DerivationOutputDeferred &) {
|
||||
[&](const DerivationOutput::Deferred &) {
|
||||
/* Nothing to check */
|
||||
},
|
||||
}, i.second.output);
|
||||
}, i.second.raw());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
21
src/libstore/log-store.hh
Normal file
21
src/libstore/log-store.hh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "store-api.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct LogStore : public virtual Store
|
||||
{
|
||||
inline static std::string operationName = "Build log storage and retrieval";
|
||||
|
||||
/* Return the build log of the specified store path, if available,
|
||||
or null otherwise. */
|
||||
virtual std::optional<std::string> getBuildLog(const StorePath & path) = 0;
|
||||
|
||||
virtual void addBuildLog(const StorePath & path, std::string_view log) = 0;
|
||||
|
||||
static LogStore & require(Store & store);
|
||||
};
|
||||
|
||||
}
|
|
@ -87,7 +87,7 @@ std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv)
|
|||
{
|
||||
auto out = drv.outputs.find("out");
|
||||
if (out != drv.outputs.end()) {
|
||||
if (auto v = std::get_if<DerivationOutputCAFixed>(&out->second.output))
|
||||
if (const auto * v = std::get_if<DerivationOutput::CAFixed>(&out->second.raw()))
|
||||
return v->hash;
|
||||
}
|
||||
return std::nullopt;
|
||||
|
|
|
@ -93,7 +93,7 @@ StringSet ParsedDerivation::getRequiredSystemFeatures() const
|
|||
StringSet res;
|
||||
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
|
||||
res.insert(i);
|
||||
if (!derivationHasKnownOutputPaths(drv.type()))
|
||||
if (!drv.type().hasKnownOutputPaths())
|
||||
res.insert("ca-derivations");
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
|
|
@ -91,6 +91,35 @@ void write(const Store & store, Sink & out, const DrvOutput & drvOutput)
|
|||
}
|
||||
|
||||
|
||||
BuildResult read(const Store & store, Source & from, Phantom<BuildResult> _)
|
||||
{
|
||||
auto path = worker_proto::read(store, from, Phantom<DerivedPath> {});
|
||||
BuildResult res { .path = path };
|
||||
res.status = (BuildResult::Status) readInt(from);
|
||||
from
|
||||
>> res.errorMsg
|
||||
>> res.timesBuilt
|
||||
>> res.isNonDeterministic
|
||||
>> res.startTime
|
||||
>> res.stopTime;
|
||||
res.builtOutputs = worker_proto::read(store, from, Phantom<DrvOutputs> {});
|
||||
return res;
|
||||
}
|
||||
|
||||
void write(const Store & store, Sink & to, const BuildResult & res)
|
||||
{
|
||||
worker_proto::write(store, to, res.path);
|
||||
to
|
||||
<< res.status
|
||||
<< res.errorMsg
|
||||
<< res.timesBuilt
|
||||
<< res.isNonDeterministic
|
||||
<< res.startTime
|
||||
<< res.stopTime;
|
||||
worker_proto::write(store, to, res.builtOutputs);
|
||||
}
|
||||
|
||||
|
||||
std::optional<StorePath> read(const Store & store, Source & from, Phantom<std::optional<StorePath>> _)
|
||||
{
|
||||
auto s = readString(from);
|
||||
|
@ -747,17 +776,24 @@ static void writeDerivedPaths(RemoteStore & store, ConnectionHandle & conn, cons
|
|||
}
|
||||
}
|
||||
|
||||
void RemoteStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
|
||||
void RemoteStore::copyDrvsFromEvalStore(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
std::shared_ptr<Store> evalStore)
|
||||
{
|
||||
if (evalStore && evalStore.get() != this) {
|
||||
/* The remote doesn't have a way to access evalStore, so copy
|
||||
the .drvs. */
|
||||
RealisedPath::Set drvPaths2;
|
||||
for (auto & i : drvPaths)
|
||||
for (auto & i : paths)
|
||||
if (auto p = std::get_if<DerivedPath::Built>(&i))
|
||||
drvPaths2.insert(p->drvPath);
|
||||
copyClosure(*evalStore, *this, drvPaths2);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode, std::shared_ptr<Store> evalStore)
|
||||
{
|
||||
copyDrvsFromEvalStore(drvPaths, evalStore);
|
||||
|
||||
auto conn(getConnection());
|
||||
conn->to << wopBuildPaths;
|
||||
|
@ -774,6 +810,91 @@ void RemoteStore::buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMod
|
|||
readInt(conn->from);
|
||||
}
|
||||
|
||||
std::vector<BuildResult> RemoteStore::buildPathsWithResults(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
BuildMode buildMode,
|
||||
std::shared_ptr<Store> evalStore)
|
||||
{
|
||||
copyDrvsFromEvalStore(paths, evalStore);
|
||||
|
||||
std::optional<ConnectionHandle> conn_(getConnection());
|
||||
auto & conn = *conn_;
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 34) {
|
||||
conn->to << wopBuildPathsWithResults;
|
||||
writeDerivedPaths(*this, conn, paths);
|
||||
conn->to << buildMode;
|
||||
conn.processStderr();
|
||||
return worker_proto::read(*this, conn->from, Phantom<std::vector<BuildResult>> {});
|
||||
} else {
|
||||
// Avoid deadlock.
|
||||
conn_.reset();
|
||||
|
||||
// Note: this throws an exception if a build/substitution
|
||||
// fails, but meh.
|
||||
buildPaths(paths, buildMode, evalStore);
|
||||
|
||||
std::vector<BuildResult> results;
|
||||
|
||||
for (auto & path : paths) {
|
||||
std::visit(
|
||||
overloaded {
|
||||
[&](const DerivedPath::Opaque & bo) {
|
||||
results.push_back(BuildResult {
|
||||
.status = BuildResult::Substituted,
|
||||
.path = bo,
|
||||
});
|
||||
},
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
BuildResult res {
|
||||
.status = BuildResult::Built,
|
||||
.path = bfd,
|
||||
};
|
||||
|
||||
OutputPathMap outputs;
|
||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
auto drvOutputs = drv.outputsAndOptPaths(*this);
|
||||
for (auto & output : bfd.outputs) {
|
||||
if (!outputHashes.count(output))
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
printStorePath(bfd.drvPath), output);
|
||||
auto outputId =
|
||||
DrvOutput{outputHashes.at(output), output};
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
auto realisation =
|
||||
queryRealisation(outputId);
|
||||
if (!realisation)
|
||||
throw Error(
|
||||
"cannot operate on an output of unbuilt "
|
||||
"content-addressed derivation '%s'",
|
||||
outputId.to_string());
|
||||
res.builtOutputs.emplace(realisation->id, *realisation);
|
||||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
assert(drvOutputs.count(output));
|
||||
assert(drvOutputs.at(output).second);
|
||||
res.builtOutputs.emplace(
|
||||
outputId,
|
||||
Realisation {
|
||||
.id = outputId,
|
||||
.outPath = *drvOutputs.at(output).second
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
results.push_back(res);
|
||||
}
|
||||
},
|
||||
path.raw());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode)
|
||||
|
@ -783,7 +904,7 @@ BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicD
|
|||
writeDerivation(conn->to, *this, drv);
|
||||
conn->to << buildMode;
|
||||
conn.processStderr();
|
||||
BuildResult res;
|
||||
BuildResult res { .path = DerivedPath::Built { .drvPath = drvPath } };
|
||||
res.status = (BuildResult::Status) readInt(conn->from);
|
||||
conn->from >> res.errorMsg;
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "store-api.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "log-store.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
@ -30,7 +31,10 @@ struct RemoteStoreConfig : virtual StoreConfig
|
|||
|
||||
/* FIXME: RemoteStore is a misnomer - should be something like
|
||||
DaemonStore. */
|
||||
class RemoteStore : public virtual RemoteStoreConfig, public virtual Store, public virtual GcStore
|
||||
class RemoteStore : public virtual RemoteStoreConfig,
|
||||
public virtual Store,
|
||||
public virtual GcStore,
|
||||
public virtual LogStore
|
||||
{
|
||||
public:
|
||||
|
||||
|
@ -97,6 +101,11 @@ public:
|
|||
|
||||
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override;
|
||||
|
||||
std::vector<BuildResult> buildPathsWithResults(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
BuildMode buildMode,
|
||||
std::shared_ptr<Store> evalStore) override;
|
||||
|
||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||
BuildMode buildMode) override;
|
||||
|
||||
|
@ -171,6 +180,9 @@ private:
|
|||
|
||||
std::atomic_bool failed{false};
|
||||
|
||||
void copyDrvsFromEvalStore(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
std::shared_ptr<Store> evalStore);
|
||||
};
|
||||
|
||||
|
||||
|
|
7
src/libstore/repair-flag.hh
Normal file
7
src/libstore/repair-flag.hh
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
namespace nix {
|
||||
|
||||
enum RepairFlag : bool { NoRepair = false, Repair = true };
|
||||
|
||||
}
|
|
@ -52,6 +52,10 @@ public:
|
|||
bool sameMachine() override
|
||||
{ return false; }
|
||||
|
||||
// FIXME extend daemon protocol, move implementation to RemoteStore
|
||||
std::optional<std::string> getBuildLog(const StorePath & path) override
|
||||
{ unsupported("getBuildLog"); }
|
||||
|
||||
private:
|
||||
|
||||
struct Connection : RemoteStore::Connection
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "crypto.hh"
|
||||
#include "fs-accessor.hh"
|
||||
#include "globals.hh"
|
||||
#include "derivations.hh"
|
||||
#include "store-api.hh"
|
||||
#include "util.hh"
|
||||
#include "nar-info-disk-cache.hh"
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#include "sync.hh"
|
||||
#include "globals.hh"
|
||||
#include "config.hh"
|
||||
#include "derivations.hh"
|
||||
#include "path-info.hh"
|
||||
#include "repair-flag.hh"
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
|
@ -62,6 +62,8 @@ MakeError(BadStorePath, Error);
|
|||
|
||||
MakeError(InvalidStoreURI, Error);
|
||||
|
||||
struct BasicDerivation;
|
||||
struct Derivation;
|
||||
class FSAccessor;
|
||||
class NarInfoDiskCache;
|
||||
class Store;
|
||||
|
@ -432,6 +434,16 @@ public:
|
|||
BuildMode buildMode = bmNormal,
|
||||
std::shared_ptr<Store> evalStore = nullptr);
|
||||
|
||||
/* Like `buildPaths()`, but return a vector of `BuildResult`s
|
||||
corresponding to each element in `paths`. Note that in case of
|
||||
a build/substitution error, this function won't throw an
|
||||
exception, but return a `BuildResult` containing an error
|
||||
message. */
|
||||
virtual std::vector<BuildResult> buildPathsWithResults(
|
||||
const std::vector<DerivedPath> & paths,
|
||||
BuildMode buildMode = bmNormal,
|
||||
std::shared_ptr<Store> evalStore = nullptr);
|
||||
|
||||
/* Build a single non-materialized derivation (i.e. not from an
|
||||
on-disk .drv file).
|
||||
|
||||
|
@ -595,14 +607,6 @@ public:
|
|||
*/
|
||||
StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths);
|
||||
|
||||
/* Return the build log of the specified store path, if available,
|
||||
or null otherwise. */
|
||||
virtual std::optional<std::string> getBuildLog(const StorePath & path)
|
||||
{ return std::nullopt; }
|
||||
|
||||
virtual void addBuildLog(const StorePath & path, std::string_view log)
|
||||
{ unsupported("addBuildLog"); }
|
||||
|
||||
/* Hack to allow long-running processes like hydra-queue-runner to
|
||||
occasionally flush their path info cache. */
|
||||
void clearPathInfoCache()
|
||||
|
|
16
src/libstore/store-cast.hh
Normal file
16
src/libstore/store-cast.hh
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
template<typename T>
|
||||
T & require(Store & store)
|
||||
{
|
||||
auto * castedStore = dynamic_cast<T *>(&store);
|
||||
if (!castedStore)
|
||||
throw UsageError("%s not supported by store '%s'", T::operationName, store.getUri());
|
||||
return *castedStore;
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@ namespace nix {
|
|||
#define WORKER_MAGIC_1 0x6e697863
|
||||
#define WORKER_MAGIC_2 0x6478696f
|
||||
|
||||
#define PROTOCOL_VERSION (1 << 8 | 33)
|
||||
#define PROTOCOL_VERSION (1 << 8 | 34)
|
||||
#define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
|
||||
#define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
|
||||
|
||||
|
@ -57,6 +57,7 @@ typedef enum {
|
|||
wopQueryRealisation = 43,
|
||||
wopAddMultipleToStore = 44,
|
||||
wopAddBuildLog = 45,
|
||||
wopBuildPathsWithResults = 46,
|
||||
} WorkerOp;
|
||||
|
||||
|
||||
|
@ -91,6 +92,7 @@ MAKE_WORKER_PROTO(, ContentAddress);
|
|||
MAKE_WORKER_PROTO(, DerivedPath);
|
||||
MAKE_WORKER_PROTO(, Realisation);
|
||||
MAKE_WORKER_PROTO(, DrvOutput);
|
||||
MAKE_WORKER_PROTO(, BuildResult);
|
||||
|
||||
MAKE_WORKER_PROTO(template<typename T>, std::vector<T>);
|
||||
MAKE_WORKER_PROTO(template<typename T>, std::set<T>);
|
||||
|
|
|
@ -64,11 +64,12 @@ static void dumpContents(const Path & path, off_t size,
|
|||
}
|
||||
|
||||
|
||||
static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
||||
static time_t dump(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
auto st = lstat(path);
|
||||
time_t result = st.st_mtime;
|
||||
|
||||
sink << "(";
|
||||
|
||||
|
@ -103,7 +104,10 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
|||
for (auto & i : unhacked)
|
||||
if (filter(path + "/" + i.first)) {
|
||||
sink << "entry" << "(" << "name" << i.first << "node";
|
||||
dump(path + "/" + i.second, sink, filter);
|
||||
auto tmp_mtime = dump(path + "/" + i.second, sink, filter);
|
||||
if (tmp_mtime > result) {
|
||||
result = tmp_mtime;
|
||||
}
|
||||
sink << ")";
|
||||
}
|
||||
}
|
||||
|
@ -114,13 +118,20 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
|
|||
else throw Error("file '%1%' has an unsupported type", path);
|
||||
|
||||
sink << ")";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
|
||||
time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
sink << narVersionMagic1;
|
||||
dump(path, sink, filter);
|
||||
return dump(path, sink, filter);
|
||||
}
|
||||
|
||||
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
|
||||
{
|
||||
dumpPathAndGetMtime(path, sink, filter);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,10 @@ namespace nix {
|
|||
void dumpPath(const Path & path, Sink & sink,
|
||||
PathFilter & filter = defaultPathFilter);
|
||||
|
||||
/* Same as `void dumpPath()`, but returns the last modified date of the path */
|
||||
time_t dumpPathAndGetMtime(const Path & path, Sink & sink,
|
||||
PathFilter & filter = defaultPathFilter);
|
||||
|
||||
void dumpString(std::string_view s, Sink & sink);
|
||||
|
||||
/* FIXME: fix this API, it sucks. */
|
||||
|
|
|
@ -327,8 +327,13 @@ MultiCommand::MultiCommand(const Commands & commands_)
|
|||
.handler = {[=](std::string s) {
|
||||
assert(!command);
|
||||
auto i = commands.find(s);
|
||||
if (i == commands.end())
|
||||
throw UsageError("'%s' is not a recognised command", s);
|
||||
if (i == commands.end()) {
|
||||
std::set<std::string> commandNames;
|
||||
for (auto & [name, _] : commands)
|
||||
commandNames.insert(name);
|
||||
auto suggestions = Suggestions::bestMatches(commandNames, s);
|
||||
throw UsageError(suggestions, "'%s' is not a recognised command", s);
|
||||
}
|
||||
command = {s, i->second()};
|
||||
command->second->parent = this;
|
||||
}},
|
||||
|
|
|
@ -282,6 +282,13 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
|
|||
}
|
||||
}
|
||||
|
||||
auto suggestions = einfo.suggestions.trim();
|
||||
if (! suggestions.suggestions.empty()){
|
||||
oss << "Did you mean " <<
|
||||
suggestions.trim() <<
|
||||
"?" << std::endl;
|
||||
}
|
||||
|
||||
// traces
|
||||
if (showTrace && !einfo.traces.empty()) {
|
||||
for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "suggestions.hh"
|
||||
#include "ref.hh"
|
||||
#include "types.hh"
|
||||
#include "fmt.hh"
|
||||
|
@ -53,6 +54,7 @@ typedef enum {
|
|||
lvlVomit
|
||||
} Verbosity;
|
||||
|
||||
/* adjust Pos::origin bit width when adding stuff here */
|
||||
typedef enum {
|
||||
foFile,
|
||||
foStdin,
|
||||
|
@ -112,6 +114,8 @@ struct ErrorInfo {
|
|||
std::optional<ErrPos> errPos;
|
||||
std::list<Trace> traces;
|
||||
|
||||
Suggestions suggestions;
|
||||
|
||||
static std::optional<std::string> programName;
|
||||
};
|
||||
|
||||
|
@ -141,6 +145,11 @@ public:
|
|||
: err { .level = lvlError, .msg = hintfmt(fs, args...) }
|
||||
{ }
|
||||
|
||||
template<typename... Args>
|
||||
BaseError(const Suggestions & sug, const Args & ... args)
|
||||
: err { .level = lvlError, .msg = hintfmt(args...), .suggestions = sug }
|
||||
{ }
|
||||
|
||||
BaseError(hintformat hint)
|
||||
: err { .level = lvlError, .msg = hint }
|
||||
{ }
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
/* A trivial class to run a function at the end of a scope. */
|
||||
template<typename Fn>
|
||||
class Finally
|
||||
{
|
||||
private:
|
||||
std::function<void()> fun;
|
||||
Fn fun;
|
||||
|
||||
public:
|
||||
Finally(std::function<void()> fun) : fun(fun) { }
|
||||
Finally(Fn fun) : fun(std::move(fun)) { }
|
||||
~Finally() { fun(); }
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "error.hh"
|
||||
#include "config.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
114
src/libutil/suggestions.cc
Normal file
114
src/libutil/suggestions.cc
Normal file
|
@ -0,0 +1,114 @@
|
|||
#include "suggestions.hh"
|
||||
#include "ansicolor.hh"
|
||||
#include "util.hh"
|
||||
#include <algorithm>
|
||||
|
||||
namespace nix {
|
||||
|
||||
int levenshteinDistance(std::string_view first, std::string_view second)
|
||||
{
|
||||
// Implementation borrowed from
|
||||
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
|
||||
|
||||
int m = first.size();
|
||||
int n = second.size();
|
||||
|
||||
auto v0 = std::vector<int>(n+1);
|
||||
auto v1 = std::vector<int>(n+1);
|
||||
|
||||
for (auto i = 0; i <= n; i++)
|
||||
v0[i] = i;
|
||||
|
||||
for (auto i = 0; i < m; i++) {
|
||||
v1[0] = i+1;
|
||||
|
||||
for (auto j = 0; j < n; j++) {
|
||||
auto deletionCost = v0[j+1] + 1;
|
||||
auto insertionCost = v1[j] + 1;
|
||||
auto substitutionCost = first[i] == second[j] ? v0[j] : v0[j] + 1;
|
||||
v1[j+1] = std::min({deletionCost, insertionCost, substitutionCost});
|
||||
}
|
||||
|
||||
std::swap(v0, v1);
|
||||
}
|
||||
|
||||
return v0[n];
|
||||
}
|
||||
|
||||
Suggestions Suggestions::bestMatches (
|
||||
std::set<std::string> allMatches,
|
||||
std::string query)
|
||||
{
|
||||
std::set<Suggestion> res;
|
||||
for (const auto & possibleMatch : allMatches) {
|
||||
res.insert(Suggestion {
|
||||
.distance = levenshteinDistance(query, possibleMatch),
|
||||
.suggestion = possibleMatch,
|
||||
});
|
||||
}
|
||||
return Suggestions { res };
|
||||
}
|
||||
|
||||
Suggestions Suggestions::trim(int limit, int maxDistance) const
|
||||
{
|
||||
std::set<Suggestion> res;
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (auto & elt : suggestions) {
|
||||
if (count >= limit || elt.distance > maxDistance)
|
||||
break;
|
||||
count++;
|
||||
res.insert(elt);
|
||||
}
|
||||
|
||||
return Suggestions{res};
|
||||
}
|
||||
|
||||
std::string Suggestion::to_string() const
|
||||
{
|
||||
return ANSI_WARNING + filterANSIEscapes(suggestion) + ANSI_NORMAL;
|
||||
}
|
||||
|
||||
std::string Suggestions::to_string() const
|
||||
{
|
||||
switch (suggestions.size()) {
|
||||
case 0:
|
||||
return "";
|
||||
case 1:
|
||||
return suggestions.begin()->to_string();
|
||||
default: {
|
||||
std::string res = "one of ";
|
||||
auto iter = suggestions.begin();
|
||||
res += iter->to_string(); // Iter can’t be end() because the container isn’t null
|
||||
iter++;
|
||||
auto last = suggestions.end(); last--;
|
||||
for ( ; iter != suggestions.end() ; iter++) {
|
||||
res += (iter == last) ? " or " : ", ";
|
||||
res += iter->to_string();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Suggestions & Suggestions::operator+=(const Suggestions & other)
|
||||
{
|
||||
suggestions.insert(
|
||||
other.suggestions.begin(),
|
||||
other.suggestions.end()
|
||||
);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::ostream & operator<<(std::ostream & str, const Suggestion & suggestion)
|
||||
{
|
||||
return str << suggestion.to_string();
|
||||
}
|
||||
|
||||
std::ostream & operator<<(std::ostream & str, const Suggestions & suggestions)
|
||||
{
|
||||
return str << suggestions.to_string();
|
||||
}
|
||||
|
||||
}
|
102
src/libutil/suggestions.hh
Normal file
102
src/libutil/suggestions.hh
Normal file
|
@ -0,0 +1,102 @@
|
|||
#pragma once
|
||||
|
||||
#include "comparator.hh"
|
||||
#include "types.hh"
|
||||
#include <set>
|
||||
|
||||
namespace nix {
|
||||
|
||||
int levenshteinDistance(std::string_view first, std::string_view second);
|
||||
|
||||
/**
|
||||
* A potential suggestion for the cli interface.
|
||||
*/
|
||||
class Suggestion {
|
||||
public:
|
||||
int distance; // The smaller the better
|
||||
std::string suggestion;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
GENERATE_CMP(Suggestion, me->distance, me->suggestion)
|
||||
};
|
||||
|
||||
class Suggestions {
|
||||
public:
|
||||
std::set<Suggestion> suggestions;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
Suggestions trim(
|
||||
int limit = 5,
|
||||
int maxDistance = 2
|
||||
) const;
|
||||
|
||||
static Suggestions bestMatches (
|
||||
std::set<std::string> allMatches,
|
||||
std::string query
|
||||
);
|
||||
|
||||
Suggestions& operator+=(const Suggestions & other);
|
||||
};
|
||||
|
||||
std::ostream & operator<<(std::ostream & str, const Suggestion &);
|
||||
std::ostream & operator<<(std::ostream & str, const Suggestions &);
|
||||
|
||||
// Either a value of type `T`, or some suggestions
|
||||
template<typename T>
|
||||
class OrSuggestions {
|
||||
public:
|
||||
using Raw = std::variant<T, Suggestions>;
|
||||
|
||||
Raw raw;
|
||||
|
||||
T* operator ->()
|
||||
{
|
||||
return &**this;
|
||||
}
|
||||
|
||||
T& operator *()
|
||||
{
|
||||
return std::get<T>(raw);
|
||||
}
|
||||
|
||||
operator bool() const noexcept
|
||||
{
|
||||
return std::holds_alternative<T>(raw);
|
||||
}
|
||||
|
||||
OrSuggestions(T t)
|
||||
: raw(t)
|
||||
{
|
||||
}
|
||||
|
||||
OrSuggestions()
|
||||
: raw(Suggestions{})
|
||||
{
|
||||
}
|
||||
|
||||
static OrSuggestions<T> failed(const Suggestions & s)
|
||||
{
|
||||
auto res = OrSuggestions<T>();
|
||||
res.raw = s;
|
||||
return res;
|
||||
}
|
||||
|
||||
static OrSuggestions<T> failed()
|
||||
{
|
||||
return OrSuggestions<T>::failed(Suggestions{});
|
||||
}
|
||||
|
||||
const Suggestions & getSuggestions()
|
||||
{
|
||||
static Suggestions noSuggestions;
|
||||
if (const auto & suggestions = std::get_if<Suggestions>(&raw))
|
||||
return *suggestions;
|
||||
else
|
||||
return noSuggestions;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
43
src/libutil/tests/suggestions.cc
Normal file
43
src/libutil/tests/suggestions.cc
Normal file
|
@ -0,0 +1,43 @@
|
|||
#include "suggestions.hh"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct LevenshteinDistanceParam {
|
||||
std::string s1, s2;
|
||||
int distance;
|
||||
};
|
||||
|
||||
class LevenshteinDistanceTest :
|
||||
public testing::TestWithParam<LevenshteinDistanceParam> {
|
||||
};
|
||||
|
||||
TEST_P(LevenshteinDistanceTest, CorrectlyComputed) {
|
||||
auto params = GetParam();
|
||||
|
||||
ASSERT_EQ(levenshteinDistance(params.s1, params.s2), params.distance);
|
||||
ASSERT_EQ(levenshteinDistance(params.s2, params.s1), params.distance);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(LevenshteinDistance, LevenshteinDistanceTest,
|
||||
testing::Values(
|
||||
LevenshteinDistanceParam{"foo", "foo", 0},
|
||||
LevenshteinDistanceParam{"foo", "", 3},
|
||||
LevenshteinDistanceParam{"", "", 0},
|
||||
LevenshteinDistanceParam{"foo", "fo", 1},
|
||||
LevenshteinDistanceParam{"foo", "oo", 1},
|
||||
LevenshteinDistanceParam{"foo", "fao", 1},
|
||||
LevenshteinDistanceParam{"foo", "abc", 3}
|
||||
)
|
||||
);
|
||||
|
||||
TEST(Suggestions, Trim) {
|
||||
auto suggestions = Suggestions::bestMatches({"foooo", "bar", "fo", "gao"}, "foo");
|
||||
auto onlyOne = suggestions.trim(1);
|
||||
ASSERT_EQ(onlyOne.suggestions.size(), 1);
|
||||
ASSERT_TRUE(onlyOne.suggestions.begin()->suggestion == "fo");
|
||||
|
||||
auto closest = suggestions.trim(999, 2);
|
||||
ASSERT_EQ(closest.suggestions.size(), 3);
|
||||
}
|
||||
}
|
|
@ -426,8 +426,29 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
|
|||
throw SysError("getting status of '%1%'", path);
|
||||
}
|
||||
|
||||
if (!S_ISDIR(st.st_mode) && st.st_nlink == 1)
|
||||
bytesFreed += st.st_size;
|
||||
if (!S_ISDIR(st.st_mode)) {
|
||||
/* We are about to delete a file. Will it likely free space? */
|
||||
|
||||
switch (st.st_nlink) {
|
||||
/* Yes: last link. */
|
||||
case 1:
|
||||
bytesFreed += st.st_size;
|
||||
break;
|
||||
/* Maybe: yes, if 'auto-optimise-store' or manual optimisation
|
||||
was performed. Instead of checking for real let's assume
|
||||
it's an optimised file and space will be freed.
|
||||
|
||||
In worst case we will double count on freed space for files
|
||||
with exactly two hardlinks for unoptimised packages.
|
||||
*/
|
||||
case 2:
|
||||
bytesFreed += st.st_size;
|
||||
break;
|
||||
/* No: 3+ links. */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
/* Make the directory accessible. */
|
||||
|
@ -702,7 +723,14 @@ std::string drainFD(int fd, bool block, const size_t reserveSize)
|
|||
|
||||
void drainFD(int fd, Sink & sink, bool block)
|
||||
{
|
||||
int saved;
|
||||
// silence GCC maybe-uninitialized warning in finally
|
||||
int saved = 0;
|
||||
|
||||
if (!block) {
|
||||
saved = fcntl(fd, F_GETFL);
|
||||
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
|
||||
throw SysError("making file descriptor non-blocking");
|
||||
}
|
||||
|
||||
Finally finally([&]() {
|
||||
if (!block) {
|
||||
|
@ -711,12 +739,6 @@ void drainFD(int fd, Sink & sink, bool block)
|
|||
}
|
||||
});
|
||||
|
||||
if (!block) {
|
||||
saved = fcntl(fd, F_GETFL);
|
||||
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
|
||||
throw SysError("making file descriptor non-blocking");
|
||||
}
|
||||
|
||||
std::vector<unsigned char> buf(64 * 1024);
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
|
|
|
@ -325,8 +325,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
|
||||
state->printStats();
|
||||
|
||||
auto buildPaths = [&](const std::vector<StorePathWithOutputs> & paths0) {
|
||||
auto paths = toDerivedPaths(paths0);
|
||||
auto buildPaths = [&](const std::vector<DerivedPath> & paths) {
|
||||
/* Note: we do this even when !printMissing to efficiently
|
||||
fetch binary cache data. */
|
||||
uint64_t downloadSize, narSize;
|
||||
|
@ -348,7 +347,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
auto & drvInfo = drvs.front();
|
||||
auto drv = evalStore->derivationFromPath(drvInfo.requireDrvPath());
|
||||
|
||||
std::vector<StorePathWithOutputs> pathsToBuild;
|
||||
std::vector<DerivedPath> pathsToBuild;
|
||||
RealisedPath::Set pathsToCopy;
|
||||
|
||||
/* Figure out what bash shell to use. If $NIX_BUILD_SHELL
|
||||
|
@ -370,7 +369,10 @@ static void main_nix_build(int argc, char * * argv)
|
|||
throw Error("the 'bashInteractive' attribute in <nixpkgs> did not evaluate to a derivation");
|
||||
|
||||
auto bashDrv = drv->requireDrvPath();
|
||||
pathsToBuild.push_back({bashDrv});
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = bashDrv,
|
||||
.outputs = {},
|
||||
});
|
||||
pathsToCopy.insert(bashDrv);
|
||||
shellDrv = bashDrv;
|
||||
|
||||
|
@ -382,17 +384,24 @@ static void main_nix_build(int argc, char * * argv)
|
|||
}
|
||||
|
||||
// Build or fetch all dependencies of the derivation.
|
||||
for (const auto & input : drv.inputDrvs)
|
||||
for (const auto & [inputDrv0, inputOutputs] : drv.inputDrvs) {
|
||||
// To get around lambda capturing restrictions in the
|
||||
// standard.
|
||||
const auto & inputDrv = inputDrv0;
|
||||
if (std::all_of(envExclude.cbegin(), envExclude.cend(),
|
||||
[&](const std::string & exclude) {
|
||||
return !std::regex_search(store->printStorePath(input.first), std::regex(exclude));
|
||||
return !std::regex_search(store->printStorePath(inputDrv), std::regex(exclude));
|
||||
}))
|
||||
{
|
||||
pathsToBuild.push_back({input.first, input.second});
|
||||
pathsToCopy.insert(input.first);
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = inputDrv,
|
||||
.outputs = inputOutputs
|
||||
});
|
||||
pathsToCopy.insert(inputDrv);
|
||||
}
|
||||
}
|
||||
for (const auto & src : drv.inputSrcs) {
|
||||
pathsToBuild.push_back({src});
|
||||
pathsToBuild.push_back(DerivedPath::Opaque{src});
|
||||
pathsToCopy.insert(src);
|
||||
}
|
||||
|
||||
|
@ -543,7 +552,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
|
||||
else {
|
||||
|
||||
std::vector<StorePathWithOutputs> pathsToBuild;
|
||||
std::vector<DerivedPath> pathsToBuild;
|
||||
std::vector<std::pair<StorePath, std::string>> pathsToBuildOrdered;
|
||||
RealisedPath::Set drvsToCopy;
|
||||
|
||||
|
@ -556,7 +565,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
if (outputName == "")
|
||||
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
|
||||
|
||||
pathsToBuild.push_back({drvPath, {outputName}});
|
||||
pathsToBuild.push_back(DerivedPath::Built{drvPath, {outputName}});
|
||||
pathsToBuildOrdered.push_back({drvPath, {outputName}});
|
||||
drvsToCopy.insert(drvPath);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "profiles.hh"
|
||||
#include "shared.hh"
|
||||
|
@ -81,7 +82,7 @@ static int main_nix_collect_garbage(int argc, char * * argv)
|
|||
// Run the actual garbage collector.
|
||||
if (!dryRun) {
|
||||
auto store = openStore();
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
options.action = GCOptions::gcDeleteDead;
|
||||
GCResults results;
|
||||
PrintFreed freed(true, results);
|
||||
|
|
|
@ -128,7 +128,12 @@ static void getAllExprs(EvalState & state,
|
|||
if (hasSuffix(attrName, ".nix"))
|
||||
attrName = std::string(attrName, 0, attrName.size() - 4);
|
||||
if (!seen.insert(attrName).second) {
|
||||
printError("warning: name collision in input Nix expressions, skipping '%1%'", path2);
|
||||
std::string suggestionMessage = "";
|
||||
if (path2.find("channels") != std::string::npos && path.find("channels") != std::string::npos) {
|
||||
suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName);
|
||||
}
|
||||
printError("warning: name collision in input Nix expressions, skipping '%1%'"
|
||||
"%2%", path2, suggestionMessage);
|
||||
continue;
|
||||
}
|
||||
/* Load the expression on demand. */
|
||||
|
@ -918,12 +923,17 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
|
|||
pkgObj.attr("pname", drvName.name);
|
||||
pkgObj.attr("version", drvName.version);
|
||||
pkgObj.attr("system", i.querySystem());
|
||||
pkgObj.attr("outputName", i.queryOutputName());
|
||||
|
||||
if (printOutPath) {
|
||||
DrvInfo::Outputs outputs = i.queryOutputs();
|
||||
{
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
|
||||
JSONObject outputObj = pkgObj.object("outputs");
|
||||
for (auto & j : outputs)
|
||||
outputObj.attr(j.first, globals.state->store->printStorePath(j.second));
|
||||
for (auto & j : outputs) {
|
||||
if (j.second)
|
||||
outputObj.attr(j.first, globals.state->store->printStorePath(*j.second));
|
||||
else
|
||||
outputObj.attr(j.first, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (printMeta) {
|
||||
|
@ -1052,6 +1062,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
|||
/* Print the desired columns, or XML output. */
|
||||
if (jsonOutput) {
|
||||
queryJSON(globals, elems, printOutPath, printMeta);
|
||||
cout << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1154,13 +1165,16 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
|||
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
|
||||
}
|
||||
|
||||
if (xmlOutput)
|
||||
attrs["outputName"] = i.queryOutputName();
|
||||
|
||||
if (printOutPath && !xmlOutput) {
|
||||
DrvInfo::Outputs outputs = i.queryOutputs();
|
||||
std::string s;
|
||||
for (auto & j : outputs) {
|
||||
if (!s.empty()) s += ';';
|
||||
if (j.first != "out") { s += j.first; s += "="; }
|
||||
s += store.printStorePath(j.second);
|
||||
s += store.printStorePath(*j.second);
|
||||
}
|
||||
columns.push_back(s);
|
||||
}
|
||||
|
@ -1174,71 +1188,67 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
|||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
if (printOutPath || printMeta) {
|
||||
XMLOpenElement item(xml, "item", attrs);
|
||||
if (printOutPath) {
|
||||
DrvInfo::Outputs outputs = i.queryOutputs();
|
||||
for (auto & j : outputs) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j.first;
|
||||
attrs2["path"] = store.printStorePath(j.second);
|
||||
xml.writeEmptyElement("output", attrs2);
|
||||
}
|
||||
}
|
||||
if (printMeta) {
|
||||
StringSet metaNames = i.queryMetaNames();
|
||||
for (auto & j : metaNames) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j;
|
||||
Value * v = i.queryMeta(j);
|
||||
if (!v)
|
||||
printError(
|
||||
"derivation '%s' has invalid meta attribute '%s'",
|
||||
i.queryName(), j);
|
||||
else {
|
||||
if (v->type() == nString) {
|
||||
attrs2["type"] = "string";
|
||||
attrs2["value"] = v->string.s;
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nInt) {
|
||||
attrs2["type"] = "int";
|
||||
attrs2["value"] = (format("%1%") % v->integer).str();
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nFloat) {
|
||||
attrs2["type"] = "float";
|
||||
attrs2["value"] = (format("%1%") % v->fpoint).str();
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nBool) {
|
||||
attrs2["type"] = "bool";
|
||||
attrs2["value"] = v->boolean ? "true" : "false";
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nList) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
for (auto elem : v->listItems()) {
|
||||
if (elem->type() != nString) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["value"] = elem->string.s;
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
} else if (v->type() == nAttrs) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
Bindings & attrs = *v->attrs;
|
||||
for (auto &i : attrs) {
|
||||
Attr & a(*attrs.find(i.name));
|
||||
if(a.value->type() != nString) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["type"] = i.name;
|
||||
attrs3["value"] = a.value->string.s;
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
XMLOpenElement item(xml, "item", attrs);
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
|
||||
for (auto & j : outputs) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j.first;
|
||||
if (j.second)
|
||||
attrs2["path"] = store.printStorePath(*j.second);
|
||||
xml.writeEmptyElement("output", attrs2);
|
||||
}
|
||||
if (printMeta) {
|
||||
StringSet metaNames = i.queryMetaNames();
|
||||
for (auto & j : metaNames) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j;
|
||||
Value * v = i.queryMeta(j);
|
||||
if (!v)
|
||||
printError(
|
||||
"derivation '%s' has invalid meta attribute '%s'",
|
||||
i.queryName(), j);
|
||||
else {
|
||||
if (v->type() == nString) {
|
||||
attrs2["type"] = "string";
|
||||
attrs2["value"] = v->string.s;
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nInt) {
|
||||
attrs2["type"] = "int";
|
||||
attrs2["value"] = (format("%1%") % v->integer).str();
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nFloat) {
|
||||
attrs2["type"] = "float";
|
||||
attrs2["value"] = (format("%1%") % v->fpoint).str();
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nBool) {
|
||||
attrs2["type"] = "bool";
|
||||
attrs2["value"] = v->boolean ? "true" : "false";
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nList) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
for (auto elem : v->listItems()) {
|
||||
if (elem->type() != nString) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["value"] = elem->string.s;
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
}
|
||||
} else if (v->type() == nAttrs) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
Bindings & attrs = *v->attrs;
|
||||
for (auto &i : attrs) {
|
||||
Attr & a(*attrs.find(i.name));
|
||||
if(a.value->type() != nString) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["type"] = i.name;
|
||||
attrs3["value"] = a.value->string.s;
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
xml.writeEmptyElement("item", attrs);
|
||||
}
|
||||
} else
|
||||
table.push_back(columns);
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
|||
output paths, and optionally the derivation path, as well
|
||||
as the meta attributes. */
|
||||
std::optional<StorePath> drvPath = keepDerivations ? i.queryDrvPath() : std::nullopt;
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(true);
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(true, true);
|
||||
StringSet metaNames = i.queryMetaNames();
|
||||
|
||||
auto attrs = state.buildBindings(7 + outputs.size());
|
||||
|
@ -76,15 +76,15 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
|||
for (const auto & [m, j] : enumerate(outputs)) {
|
||||
(vOutputs.listElems()[m] = state.allocValue())->mkString(j.first);
|
||||
auto outputAttrs = state.buildBindings(2);
|
||||
outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(j.second));
|
||||
outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(*j.second));
|
||||
attrs.alloc(j.first).mkAttrs(outputAttrs);
|
||||
|
||||
/* This is only necessary when installing store paths, e.g.,
|
||||
`nix-env -i /nix/store/abcd...-foo'. */
|
||||
state.store->addTempRoot(j.second);
|
||||
state.store->ensurePath(j.second);
|
||||
state.store->addTempRoot(*j.second);
|
||||
state.store->ensurePath(*j.second);
|
||||
|
||||
references.insert(j.second);
|
||||
references.insert(*j.second);
|
||||
}
|
||||
|
||||
// Copy the meta attributes.
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
#include "dotgraph.hh"
|
||||
#include "globals.hh"
|
||||
#include "build-result.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
#include "log-store.hh"
|
||||
#include "local-store.hh"
|
||||
#include "monitor-fd.hh"
|
||||
#include "serve-protocol.hh"
|
||||
|
@ -429,7 +431,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
|
|||
store->computeFSClosure(
|
||||
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations);
|
||||
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
Roots roots = gcStore.findRoots(false);
|
||||
for (auto & [target, links] : roots)
|
||||
if (referrers.find(target) != referrers.end())
|
||||
|
@ -474,13 +476,15 @@ static void opReadLog(Strings opFlags, Strings opArgs)
|
|||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
|
||||
auto & logStore = require<LogStore>(*store);
|
||||
|
||||
RunPager pager;
|
||||
|
||||
for (auto & i : opArgs) {
|
||||
auto path = store->followLinksToStorePath(i);
|
||||
auto log = store->getBuildLog(path);
|
||||
auto path = logStore.followLinksToStorePath(i);
|
||||
auto log = logStore.getBuildLog(path);
|
||||
if (!log)
|
||||
throw Error("build log of derivation '%s' is not available", store->printStorePath(path));
|
||||
throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path));
|
||||
std::cout << *log;
|
||||
}
|
||||
}
|
||||
|
@ -590,7 +594,7 @@ static void opGC(Strings opFlags, Strings opArgs)
|
|||
|
||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
|
||||
if (printRoots) {
|
||||
Roots roots = gcStore.findRoots(false);
|
||||
|
@ -629,7 +633,7 @@ static void opDelete(Strings opFlags, Strings opArgs)
|
|||
for (auto & i : opArgs)
|
||||
options.pathsToDelete.insert(store->followLinksToStorePath(i));
|
||||
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
|
||||
GCResults results;
|
||||
PrintFreed freed(true, results);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "eval-cache.hh"
|
||||
#include "names.hh"
|
||||
#include "command.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
|
@ -52,15 +52,26 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
|
|||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
if (dryRun) {
|
||||
std::vector<DerivedPath> pathsToBuild;
|
||||
|
||||
for (auto & i : installables) {
|
||||
auto b = i->toDerivedPaths();
|
||||
pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
|
||||
}
|
||||
printMissing(store, pathsToBuild, lvlError);
|
||||
if (json)
|
||||
logger->cout("%s", derivedPathsToJSON(pathsToBuild, store).dump());
|
||||
return;
|
||||
}
|
||||
|
||||
auto buildables = Installable::build(
|
||||
getEvalStore(), store,
|
||||
dryRun ? Realise::Derivation : Realise::Outputs,
|
||||
Realise::Outputs,
|
||||
installables, buildMode);
|
||||
|
||||
if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump());
|
||||
|
||||
if (dryRun) return;
|
||||
|
||||
if (outLink != "")
|
||||
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
|
||||
for (const auto & [_i, buildable] : enumerate(buildables)) {
|
||||
|
|
|
@ -196,21 +196,22 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
|
|||
drv.inputSrcs.insert(std::move(getEnvShPath));
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
for (auto & output : drv.outputs) {
|
||||
output.second = {
|
||||
.output = DerivationOutputDeferred{},
|
||||
};
|
||||
output.second = DerivationOutput::Deferred {},
|
||||
drv.env[output.first] = hashPlaceholder(output.first);
|
||||
}
|
||||
} else {
|
||||
for (auto & output : drv.outputs) {
|
||||
output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } };
|
||||
output.second = DerivationOutput::Deferred { };
|
||||
drv.env[output.first] = "";
|
||||
}
|
||||
Hash h = std::get<0>(hashDerivationModulo(*evalStore, drv, true));
|
||||
auto h0 = hashDerivationModulo(*evalStore, drv, true);
|
||||
const Hash & h = h0.requireNoFixedNonDeferred();
|
||||
|
||||
for (auto & output : drv.outputs) {
|
||||
auto outPath = store->makeOutputPath(output.first, h, drv.name);
|
||||
output.second = { .output = DerivationOutputInputAddressed { .path = outPath } };
|
||||
output.second = DerivationOutput::InputAddressed {
|
||||
.path = outPath,
|
||||
};
|
||||
drv.env[output.first] = store->printStorePath(outPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -704,9 +704,14 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
|
|||
|
||||
auto [cursor, attrPath] = installable.getCursor(*evalState);
|
||||
|
||||
auto templateDir = cursor->getAttr("path")->getString();
|
||||
auto templateDirAttr = cursor->getAttr("path");
|
||||
auto templateDir = templateDirAttr->getString();
|
||||
|
||||
assert(store->isInStore(templateDir));
|
||||
if (!store->isInStore(templateDir))
|
||||
throw TypeError(
|
||||
"'%s' was not found in the Nix store\n"
|
||||
"If you've set '%s' to a string, try using a path instead.",
|
||||
templateDir, templateDirAttr->getAttrPathStr());
|
||||
|
||||
std::vector<Path> files;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "common-args.hh"
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
#include "log-store.hh"
|
||||
#include "progress-bar.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
@ -34,17 +35,24 @@ struct CmdLog : InstallableCommand
|
|||
|
||||
RunPager pager;
|
||||
for (auto & sub : subs) {
|
||||
auto * logSubP = dynamic_cast<LogStore *>(&*sub);
|
||||
if (!logSubP) {
|
||||
printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
|
||||
continue;
|
||||
}
|
||||
auto & logSub = *logSubP;
|
||||
|
||||
auto log = std::visit(overloaded {
|
||||
[&](const DerivedPath::Opaque & bo) {
|
||||
return sub->getBuildLog(bo.path);
|
||||
return logSub.getBuildLog(bo.path);
|
||||
},
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
return sub->getBuildLog(bfd.drvPath);
|
||||
return logSub.getBuildLog(bfd.drvPath);
|
||||
},
|
||||
}, b.raw());
|
||||
if (!log) continue;
|
||||
stopProgressBar();
|
||||
printInfo("got build log for '%s' from '%s'", installable->what(), sub->getUri());
|
||||
printInfo("got build log for '%s' from '%s'", installable->what(), logSub.getUri());
|
||||
std::cout << *log;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ extern "C" {
|
|||
#include "eval-inline.hh"
|
||||
#include "attr-path.hh"
|
||||
#include "store-api.hh"
|
||||
#include "log-store.hh"
|
||||
#include "common-eval-args.hh"
|
||||
#include "get-drvs.hh"
|
||||
#include "derivations.hh"
|
||||
|
@ -526,9 +527,16 @@ bool NixRepl::processLine(std::string line)
|
|||
bool foundLog = false;
|
||||
RunPager pager;
|
||||
for (auto & sub : subs) {
|
||||
auto log = sub->getBuildLog(drvPath);
|
||||
auto * logSubP = dynamic_cast<LogStore *>(&*sub);
|
||||
if (!logSubP) {
|
||||
printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
|
||||
continue;
|
||||
}
|
||||
auto & logSub = *logSubP;
|
||||
|
||||
auto log = logSub.getBuildLog(drvPath);
|
||||
if (log) {
|
||||
printInfo("got build log for '%s' from '%s'", drvPathRaw, sub->getUri());
|
||||
printInfo("got build log for '%s' from '%s'", drvPathRaw, logSub.getUri());
|
||||
logger->writeToStdout(*log);
|
||||
foundLog = true;
|
||||
break;
|
||||
|
|
|
@ -65,19 +65,19 @@ struct CmdShowDerivation : InstallablesCommand
|
|||
auto & outputName = _outputName; // work around clang bug
|
||||
auto outputObj { outputsObj.object(outputName) };
|
||||
std::visit(overloaded {
|
||||
[&](const DerivationOutputInputAddressed & doi) {
|
||||
[&](const DerivationOutput::InputAddressed & doi) {
|
||||
outputObj.attr("path", store->printStorePath(doi.path));
|
||||
},
|
||||
[&](const DerivationOutputCAFixed & dof) {
|
||||
[&](const DerivationOutput::CAFixed & dof) {
|
||||
outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName)));
|
||||
outputObj.attr("hashAlgo", dof.hash.printMethodAlgo());
|
||||
outputObj.attr("hash", dof.hash.hash.to_string(Base16, false));
|
||||
},
|
||||
[&](const DerivationOutputCAFloating & dof) {
|
||||
[&](const DerivationOutput::CAFloating & dof) {
|
||||
outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
||||
},
|
||||
[&](const DerivationOutputDeferred &) {},
|
||||
}, output.output);
|
||||
[&](const DerivationOutput::Deferred &) {},
|
||||
}, output.raw());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "command.hh"
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "log-store.hh"
|
||||
#include "sync.hh"
|
||||
#include "thread-pool.hh"
|
||||
|
||||
|
@ -26,7 +28,10 @@ struct CmdCopyLog : virtual CopyCommand, virtual InstallablesCommand
|
|||
|
||||
void run(ref<Store> srcStore) override
|
||||
{
|
||||
auto & srcLogStore = require<LogStore>(*srcStore);
|
||||
|
||||
auto dstStore = getDstStore();
|
||||
auto & dstLogStore = require<LogStore>(*dstStore);
|
||||
|
||||
StorePathSet drvPaths;
|
||||
|
||||
|
@ -35,8 +40,8 @@ struct CmdCopyLog : virtual CopyCommand, virtual InstallablesCommand
|
|||
drvPaths.insert(drvPath);
|
||||
|
||||
for (auto & drvPath : drvPaths) {
|
||||
if (auto log = srcStore->getBuildLog(drvPath))
|
||||
dstStore->addBuildLog(drvPath, *log);
|
||||
if (auto log = srcLogStore.getBuildLog(drvPath))
|
||||
dstLogStore.addBuildLog(drvPath, *log);
|
||||
else
|
||||
throw Error("build log for '%s' is not available", srcStore->printStorePath(drvPath));
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "common-args.hh"
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
@ -33,7 +34,7 @@ struct CmdStoreDelete : StorePathsCommand
|
|||
|
||||
void run(ref<Store> store, std::vector<StorePath> && storePaths) override
|
||||
{
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
|
||||
for (auto & path : storePaths)
|
||||
options.pathsToDelete.insert(path);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "common-args.hh"
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
#include "store-cast.hh"
|
||||
#include "gc-store.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
@ -34,7 +35,7 @@ struct CmdStoreGC : StoreCommand, MixDryRun
|
|||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
auto & gcStore = requireGcStore(*store);
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
|
||||
options.action = dryRun ? GCOptions::gcReturnDead : GCOptions::gcDeleteDead;
|
||||
GCResults results;
|
||||
|
|
|
@ -50,3 +50,22 @@ nix build -f dependencies.nix -o $RESULT --dry-run
|
|||
nix build -f dependencies.nix -o $RESULT
|
||||
|
||||
[[ -h $RESULT ]]
|
||||
|
||||
###################################################
|
||||
# Check the JSON output
|
||||
clearStore
|
||||
clearCache
|
||||
|
||||
RES=$(nix build -f dependencies.nix --dry-run --json)
|
||||
|
||||
if [[ -z "$NIX_TESTS_CA_BY_DEFAULT" ]]; then
|
||||
echo "$RES" | jq '.[0] | [
|
||||
(.drvPath | test("'$NIX_STORE_DIR'.*\\.drv")),
|
||||
(.outputs.out | test("'$NIX_STORE_DIR'"))
|
||||
] | all'
|
||||
else
|
||||
echo "$RES" | jq '.[0] | [
|
||||
(.drvPath | test("'$NIX_STORE_DIR'.*\\.drv")),
|
||||
.outputs.out == null
|
||||
] | all'
|
||||
fi
|
||||
|
|
|
@ -17,7 +17,7 @@ let
|
|||
input1 = mkDerivation {
|
||||
shell = busybox;
|
||||
name = "build-remote-input-1";
|
||||
buildCommand = "echo hi; echo FOO > $out";
|
||||
buildCommand = "echo hi-input1; echo FOO > $out";
|
||||
requiredSystemFeatures = ["foo"];
|
||||
outputHash = "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=";
|
||||
};
|
||||
|
@ -34,7 +34,7 @@ let
|
|||
shell = busybox;
|
||||
name = "build-remote-input-3";
|
||||
buildCommand = ''
|
||||
echo hi
|
||||
echo hi-input3
|
||||
read x < ${input2}
|
||||
echo $x BAZ > $out
|
||||
'';
|
||||
|
|
|
@ -15,7 +15,7 @@ let
|
|||
input1 = mkDerivation {
|
||||
shell = busybox;
|
||||
name = "build-remote-input-1";
|
||||
buildCommand = "echo hi; echo FOO > $out";
|
||||
buildCommand = "echo hi-input1; echo FOO > $out";
|
||||
requiredSystemFeatures = ["foo"];
|
||||
};
|
||||
|
||||
|
@ -30,7 +30,7 @@ let
|
|||
shell = busybox;
|
||||
name = "build-remote-input-3";
|
||||
buildCommand = ''
|
||||
echo hi
|
||||
echo hi-input3
|
||||
read x < ${input2}
|
||||
echo $x BAZ > $out
|
||||
'';
|
||||
|
|
|
@ -55,10 +55,10 @@ nix path-info --store $TEST_ROOT/machine3 --all \
|
|||
| grep builder-build-remote-input-3.sh
|
||||
|
||||
|
||||
# Temporarily disabled because of https://github.com/NixOS/nix/issues/6209
|
||||
if [[ -z "$CONTENT_ADDRESSED" ]]; then
|
||||
for i in input1 input3; do
|
||||
drv="$(nix-instantiate $file -A passthru.$i --store $TEST_ROOT/machine0 --arg busybox $busybox)"
|
||||
nix log --store $TEST_ROOT/machine0 "$drv"
|
||||
nix log --store $TEST_ROOT/machine0 --file "$file" --arg busybox $busybox passthru."$i" | grep hi-$i
|
||||
done
|
||||
fi
|
||||
|
||||
|
|
6
tests/ca/build-dry.sh
Normal file
6
tests/ca/build-dry.sh
Normal file
|
@ -0,0 +1,6 @@
|
|||
source common.sh
|
||||
|
||||
export NIX_TESTS_CA_BY_DEFAULT=1
|
||||
|
||||
cd .. && source build-dry.sh
|
||||
|
5
tests/eval.nix
Normal file
5
tests/eval.nix
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
int = 123;
|
||||
str = "foo";
|
||||
attr.foo = "bar";
|
||||
}
|
29
tests/eval.sh
Normal file
29
tests/eval.sh
Normal file
|
@ -0,0 +1,29 @@
|
|||
source common.sh
|
||||
|
||||
clearStore
|
||||
|
||||
testStdinHeredoc=$(nix eval -f - <<EOF
|
||||
{
|
||||
bar = 3 + 1;
|
||||
foo = 2 + 2;
|
||||
}
|
||||
EOF
|
||||
)
|
||||
[[ $testStdinHeredoc == '{ bar = 4; foo = 4; }' ]]
|
||||
|
||||
nix eval --expr 'assert 1 + 2 == 3; true'
|
||||
|
||||
[[ $(nix eval int -f "./eval.nix") == 123 ]]
|
||||
[[ $(nix eval str -f "./eval.nix") == '"foo"' ]]
|
||||
[[ $(nix eval str --raw -f "./eval.nix") == 'foo' ]]
|
||||
[[ $(nix eval attr -f "./eval.nix") == '{ foo = "bar"; }' ]]
|
||||
[[ $(nix eval attr --json -f "./eval.nix") == '{"foo":"bar"}' ]]
|
||||
[[ $(nix eval int -f - < "./eval.nix") == 123 ]]
|
||||
|
||||
|
||||
nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
|
||||
[[ $(nix-instantiate -A int --eval "./eval.nix") == 123 ]]
|
||||
[[ $(nix-instantiate -A str --eval "./eval.nix") == '"foo"' ]]
|
||||
[[ $(nix-instantiate -A attr --eval "./eval.nix") == '{ foo = "bar"; }' ]]
|
||||
[[ $(nix-instantiate -A attr --eval --json "./eval.nix") == '{"foo":"bar"}' ]]
|
||||
[[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]]
|
|
@ -11,7 +11,7 @@ repo=$TEST_ROOT/git
|
|||
|
||||
export _NIX_FORCE_HTTP=1
|
||||
|
||||
rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/shallow
|
||||
rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/shallow $TEST_ROOT/minimal
|
||||
|
||||
git init $repo
|
||||
git -C $repo config user.email "foobar@example.com"
|
||||
|
@ -147,8 +147,13 @@ path3=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath")
|
|||
# (check dirty-tree handling was used)
|
||||
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = 0000000000000000000000000000000000000000 ]]
|
||||
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).shortRev") = 0000000 ]]
|
||||
# Making a dirty tree clean again and fetching it should
|
||||
# record correct revision information. See: #4140
|
||||
echo world > $repo/hello
|
||||
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = $rev2 ]]
|
||||
|
||||
# Committing shouldn't change store path, or switch to using 'master'
|
||||
echo dev > $repo/hello
|
||||
git -C $repo commit -m 'Bla5' -a
|
||||
path4=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath")
|
||||
[[ $(cat $path4/hello) = dev ]]
|
||||
|
@ -170,6 +175,14 @@ NIX=$(command -v nix)
|
|||
path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath")
|
||||
[[ $path3 = $path5 ]]
|
||||
|
||||
# Fetching from a repo with only a specific revision and no branches should
|
||||
# not fall back to copying files and record correct revision information. See: #5302
|
||||
mkdir $TEST_ROOT/minimal
|
||||
git -C $TEST_ROOT/minimal init
|
||||
git -C $TEST_ROOT/minimal fetch $repo $rev2
|
||||
git -C $TEST_ROOT/minimal checkout $rev2
|
||||
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit { url = $TEST_ROOT/minimal; }).rev") = $rev2 ]]
|
||||
|
||||
# Fetching a shallow repo shouldn't work by default, because we can't
|
||||
# return a revCount.
|
||||
git clone --depth 1 file://$repo $TEST_ROOT/shallow
|
||||
|
@ -193,3 +206,11 @@ rev4_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$
|
|||
# The name argument should be handled
|
||||
path9=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; name = \"foo\"; }).outPath")
|
||||
[[ $path9 =~ -foo$ ]]
|
||||
|
||||
# should fail if there is no repo
|
||||
rm -rf $repo/.git
|
||||
(! nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath")
|
||||
|
||||
# should succeed for a repo without commits
|
||||
git init $repo
|
||||
path10=$(nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath")
|
||||
|
|
6
tests/fetchPath.sh
Normal file
6
tests/fetchPath.sh
Normal file
|
@ -0,0 +1,6 @@
|
|||
source common.sh
|
||||
|
||||
touch foo -t 202211111111
|
||||
# We only check whether 2022-11-1* **:**:** is the last modified date since
|
||||
# `lastModified` is transformed into UTC in `builtins.fetchTarball`.
|
||||
[[ "$(nix eval --impure --raw --expr "(builtins.fetchTree \"path://$PWD/foo\").lastModifiedDate")" =~ 2022111.* ]]
|
|
@ -21,6 +21,7 @@ nix_tests = \
|
|||
tarball.sh \
|
||||
fetchGit.sh \
|
||||
fetchurl.sh \
|
||||
fetchPath.sh \
|
||||
simple.sh \
|
||||
referrers.sh \
|
||||
optimise-store.sh \
|
||||
|
@ -52,6 +53,7 @@ nix_tests = \
|
|||
build-remote-content-addressed-floating.sh \
|
||||
nar-access.sh \
|
||||
pure-eval.sh \
|
||||
eval.sh \
|
||||
ca/post-hook.sh \
|
||||
repl.sh \
|
||||
ca/repl.sh \
|
||||
|
@ -92,10 +94,11 @@ nix_tests = \
|
|||
bash-profile.sh \
|
||||
pass-as-file.sh \
|
||||
describe-stores.sh \
|
||||
store-ping.sh \
|
||||
flake-bundler.sh \
|
||||
flake-print-local-path-error.sh \
|
||||
nix-profile.sh
|
||||
nix-profile.sh \
|
||||
suggestions.sh \
|
||||
store-ping.sh
|
||||
|
||||
ifeq ($(HAVE_LIBCPUID), 1)
|
||||
nix_tests += compute-levels.sh
|
||||
|
|
44
tests/suggestions.sh
Normal file
44
tests/suggestions.sh
Normal file
|
@ -0,0 +1,44 @@
|
|||
source common.sh
|
||||
|
||||
clearStore
|
||||
|
||||
cd "$TEST_HOME"
|
||||
|
||||
cat <<EOF > flake.nix
|
||||
{
|
||||
outputs = a: {
|
||||
packages.$system = {
|
||||
foo = 1;
|
||||
fo1 = 1;
|
||||
fo2 = 1;
|
||||
fooo = 1;
|
||||
foooo = 1;
|
||||
fooooo = 1;
|
||||
fooooo1 = 1;
|
||||
fooooo2 = 1;
|
||||
fooooo3 = 1;
|
||||
fooooo4 = 1;
|
||||
fooooo5 = 1;
|
||||
fooooo6 = 1;
|
||||
};
|
||||
};
|
||||
}
|
||||
EOF
|
||||
|
||||
# Probable typo in the requested attribute path. Suggest some close possibilities
|
||||
NIX_BUILD_STDERR_WITH_SUGGESTIONS=$(! nix build .\#fob 2>&1 1>/dev/null)
|
||||
[[ "$NIX_BUILD_STDERR_WITH_SUGGESTIONS" =~ "Did you mean one of fo1, fo2, foo or fooo?" ]] || \
|
||||
fail "The nix build stderr should suggest the three closest possiblities"
|
||||
|
||||
# None of the possible attributes is close to `bar`, so shouldn’t suggest anything
|
||||
NIX_BUILD_STDERR_WITH_NO_CLOSE_SUGGESTION=$(! nix build .\#bar 2>&1 1>/dev/null)
|
||||
[[ ! "$NIX_BUILD_STDERR_WITH_NO_CLOSE_SUGGESTION" =~ "Did you mean" ]] || \
|
||||
fail "The nix build stderr shouldn’t suggest anything if there’s nothing relevant to suggest"
|
||||
|
||||
NIX_EVAL_STDERR_WITH_SUGGESTIONS=$(! nix build --impure --expr '(builtins.getFlake (builtins.toPath ./.)).packages.'$system'.fob' 2>&1 1>/dev/null)
|
||||
[[ "$NIX_EVAL_STDERR_WITH_SUGGESTIONS" =~ "Did you mean one of fo1, fo2, foo or fooo?" ]] || \
|
||||
fail "The evaluator should suggest the three closest possiblities"
|
||||
|
||||
NIX_EVAL_STDERR_WITH_SUGGESTIONS=$(! nix build --impure --expr '({ foo }: foo) { foo = 1; fob = 2; }' 2>&1 1>/dev/null)
|
||||
[[ "$NIX_EVAL_STDERR_WITH_SUGGESTIONS" =~ "Did you mean foo?" ]] || \
|
||||
fail "The evaluator should suggest the three closest possiblities"
|
|
@ -17,6 +17,16 @@ outPath10=$(nix-env -f ./user-envs.nix -qa --out-path --no-name '*' | grep foo-1
|
|||
drvPath10=$(nix-env -f ./user-envs.nix -qa --drv-path --no-name '*' | grep foo-1.0)
|
||||
[ -n "$outPath10" -a -n "$drvPath10" ]
|
||||
|
||||
# Query with json
|
||||
nix-env -f ./user-envs.nix -qa --json | jq -e '.[] | select(.name == "bar-0.1") | [
|
||||
.outputName == "out",
|
||||
.outputs.out == null
|
||||
] | all'
|
||||
nix-env -f ./user-envs.nix -qa --json --out-path | jq -e '.[] | select(.name == "bar-0.1") | [
|
||||
.outputName == "out",
|
||||
(.outputs.out | test("'$NIX_STORE_DIR'.*-0\\.1"))
|
||||
] | all'
|
||||
|
||||
# Query descriptions.
|
||||
nix-env -f ./user-envs.nix -qa '*' --description | grep -q silly
|
||||
rm -rf $HOME/.nix-defexpr
|
||||
|
|
Loading…
Reference in a new issue