Merge remote-tracking branch 'upstream/master' into overlayfs-store

This commit is contained in:
John Ericson 2023-07-24 15:39:36 -04:00
commit 2cabf85b53
47 changed files with 537 additions and 460 deletions

View file

@ -10,12 +10,14 @@ let
type' = optionalString (type != null) " (${type})"; type' = optionalString (type != null) " (${type})";
impureNotice = optionalString impure-only '' impureNotice = optionalString impure-only ''
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval). > **Note**
>
> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
''; '';
in in
squash '' squash ''
<dt id="builtin-constants-${name}"> <dt id="builtins-${name}">
<a href="#builtin-constants-${name}"><code>${name}</code>${type'}</a> <a href="#builtins-${name}"><code>${name}</code></a>${type'}
</dt> </dt>
<dd> <dd>

View file

@ -109,6 +109,7 @@
- [C++ style guide](contributing/cxx.md) - [C++ style guide](contributing/cxx.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.17 (2023-07-24)](release-notes/rl-2.17.md)
- [Release 2.16 (2023-05-31)](release-notes/rl-2.16.md) - [Release 2.16 (2023-05-31)](release-notes/rl-2.16.md)
- [Release 2.15 (2023-04-11)](release-notes/rl-2.15.md) - [Release 2.15 (2023-04-11)](release-notes/rl-2.15.md)
- [Release 2.14 (2023-02-28)](release-notes/rl-2.14.md) - [Release 2.14 (2023-02-28)](release-notes/rl-2.14.md)

View file

@ -128,7 +128,7 @@ platform. Common solutions include [remote builders] and [binary format emulatio
(only supported on NixOS). (only supported on NixOS).
[remote builders]: ../advanced-topics/distributed-builds.md [remote builders]: ../advanced-topics/distributed-builds.md
[binfmt emulation]: https://nixos.org/manual/nixos/stable/options.html#opt-boot.binfmt.emulatedSystems [binary format emulation]: https://nixos.org/manual/nixos/stable/options.html#opt-boot.binfmt.emulatedSystems
Given such a setup, executing the build only requires selecting the respective attribute. Given such a setup, executing the build only requires selecting the respective attribute.
For example, to compile for `aarch64-linux`: For example, to compile for `aarch64-linux`:
@ -166,7 +166,7 @@ When Nix is built such that `./configure` is passed any of the `--host`, `--buil
<cpu>-<vendor>[-<kernel>]-<os> <cpu>-<vendor>[-<kernel>]-<os>
``` ```
For historic reasons and backward-compatibility, some CPU and OS identifiers are translated from the GNU Autotools naming convention in [`configure.ac`](https://github.com/nixos/nix/blob/master/config/config.sub) as follows: For historic reasons and backward-compatibility, some CPU and OS identifiers are translated from the GNU Autotools naming convention in [`configure.ac`](https://github.com/nixos/nix/blob/master/configure.ac) as follows:
| `config.guess` | Nix | | `config.guess` | Nix |
|----------------------------|---------------------| |----------------------------|---------------------|

View file

@ -0,0 +1,29 @@
# Release 2.17 (2023-07-24)
* [`nix-channel`](../command-ref/nix-channel.md) now supports a `--list-generations` subcommand.
* The function [`builtins.fetchClosure`](../language/builtins.md#builtins-fetchClosure) can now fetch input-addressed paths in [pure evaluation mode](../command-ref/conf-file.md#conf-pure-eval), as those are not impure.
* Nix now allows unprivileged/[`allowed-users`](../command-ref/conf-file.md#conf-allowed-users) to sign paths.
Previously, only [`trusted-users`](../command-ref/conf-file.md#conf-trusted-users) users could sign paths.
* Nested dynamic attributes are now merged correctly by the parser. For example:
```nix
{
nested = { foo = 1; };
nested = { ${"ba" + "r"} = 2; };
}
```
This used to silently discard `nested.bar`, but now behaves as one would expect and evaluates to:
```nix
{ nested = { bar = 2; foo = 1; }; }
```
Note that the feature of merging multiple attribute set declarations is of questionable value.
It allows writing expressions that are very hard to read, for instance when there are many lines of code between two declarations of the same attribute.
This has been around for a long time and is therefore supported for backwards compatibility, but should not be relied upon.
* Tarball flakes can now redirect to an "immutable" URL that will be recorded in lock files. This allows the use of "mutable" tarball URLs like `https://example.org/hello/latest.tar.gz` in flakes. See the [tarball fetcher](../protocols/tarball-fetcher.md) for details.

View file

@ -1,8 +1 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
- [`nix-channel`](../command-ref/nix-channel.md) now supports a `--list-generations` subcommand
* The function [`builtins.fetchClosure`](../language/builtins.md#builtins-fetchClosure) can now fetch input-addressed paths in [pure evaluation mode](../command-ref/conf-file.md#conf-pure-eval), as those are not impure.
- Nix now allows unprivileged/[`allowed-users`](../command-ref/conf-file.md#conf-allowed-users) to sign paths.
Previously, only [`trusted-users`](../command-ref/conf-file.md#conf-trusted-users) users could sign paths.

View file

@ -294,10 +294,8 @@ SV * makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
auto h = Hash::parseAny(hash, parseHashType(algo)); auto h = Hash::parseAny(hash, parseHashType(algo));
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat; auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
auto path = store()->makeFixedOutputPath(name, FixedOutputInfo { auto path = store()->makeFixedOutputPath(name, FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = h, .hash = h,
},
.references = {}, .references = {},
}); });
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0))); XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));

View file

@ -1,4 +1,5 @@
#include "derived-path.hh" #include "derived-path.hh"
#include "realisation.hh"
namespace nix { namespace nix {

View file

@ -806,7 +806,7 @@ struct EvalSettings : Config
List of directories to be searched for `<...>` file references List of directories to be searched for `<...>` file references
In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtin-constants-nixPath). [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath).
)"}; )"};
Setting<bool> restrictEval{ Setting<bool> restrictEval{

View file

@ -137,6 +137,7 @@ static void addAttr(ExprAttrs * attrs, AttrPath && attrPath,
dupAttr(state, ad.first, j2->second.pos, ad.second.pos); dupAttr(state, ad.first, j2->second.pos, ad.second.pos);
jAttrs->attrs.emplace(ad.first, ad.second); jAttrs->attrs.emplace(ad.first, ad.second);
} }
jAttrs->dynamicAttrs.insert(jAttrs->dynamicAttrs.end(), ae->dynamicAttrs.begin(), ae->dynamicAttrs.end());
} else { } else {
dupAttr(state, attrPath, pos, j->second.pos); dupAttr(state, attrPath, pos, j->second.pos);
} }

View file

@ -1300,9 +1300,10 @@ drvName, Bindings * attrs, Value & v)
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat); auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
DerivationOutput::CAFixed dof { DerivationOutput::CAFixed dof {
.ca = ContentAddress::fromParts( .ca = ContentAddress {
std::move(method), .method = std::move(method),
std::move(h)), .hash = std::move(h),
},
}; };
drv.env["out"] = state.store->printStorePath(dof.path(*state.store, drvName, "out")); drv.env["out"] = state.store->printStorePath(dof.path(*state.store, drvName, "out"));
@ -2164,10 +2165,8 @@ static void addPath(
std::optional<StorePath> expectedStorePath; std::optional<StorePath> expectedStorePath;
if (expectedHash) if (expectedHash)
expectedStorePath = state.store->makeFixedOutputPath(name, FixedOutputInfo { expectedStorePath = state.store->makeFixedOutputPath(name, FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = *expectedHash, .hash = *expectedHash,
},
.references = {}, .references = {},
}); });

View file

@ -254,10 +254,8 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
auto expectedPath = state.store->makeFixedOutputPath( auto expectedPath = state.store->makeFixedOutputPath(
name, name,
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat, .method = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat,
.hash = *expectedHash, .hash = *expectedHash,
},
.references = {} .references = {}
}); });

View file

@ -217,10 +217,8 @@ StorePath Input::computeStorePath(Store & store) const
if (!narHash) if (!narHash)
throw Error("cannot compute store path for unlocked input '%s'", to_string()); throw Error("cannot compute store path for unlocked input '%s'", to_string());
return store.makeFixedOutputPath(getName(), FixedOutputInfo { return store.makeFixedOutputPath(getName(), FixedOutputInfo {
.hash = {
.method = FileIngestionMethod::Recursive, .method = FileIngestionMethod::Recursive,
.hash = *narHash, .hash = *narHash,
},
.references = {}, .references = {},
}); });
} }

View file

@ -77,10 +77,8 @@ DownloadFileResult downloadFile(
*store, *store,
name, name,
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = FileIngestionMethod::Flat, .method = FileIngestionMethod::Flat,
.hash = hash, .hash = hash,
},
.references = {}, .references = {},
}, },
hashString(htSHA256, sink.s), hashString(htSHA256, sink.s),

View file

@ -309,10 +309,8 @@ StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view n
*this, *this,
name, name,
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = nar.first, .hash = nar.first,
},
.references = { .references = {
.others = references, .others = references,
// caller is not capable of creating a self-reference, because this is content-addressed without modulus // caller is not capable of creating a self-reference, because this is content-addressed without modulus
@ -428,10 +426,8 @@ StorePath BinaryCacheStore::addToStore(
*this, *this,
name, name,
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = h, .hash = h,
},
.references = { .references = {
.others = references, .others = references,
// caller is not capable of creating a self-reference, because this is content-addressed without modulus // caller is not capable of creating a self-reference, because this is content-addressed without modulus
@ -465,8 +461,8 @@ StorePath BinaryCacheStore::addTextToStore(
*this, *this,
std::string { name }, std::string { name },
TextInfo { TextInfo {
{ .hash = textHash }, .hash = textHash,
references, .references = references,
}, },
nar.first, nar.first,
}; };

View file

@ -1,5 +1,5 @@
#include "local-derivation-goal.hh" #include "local-derivation-goal.hh"
#include "gc-store.hh" #include "indirect-root-store.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include "worker.hh" #include "worker.hh"
#include "builtins.hh" #include "builtins.hh"
@ -1200,7 +1200,7 @@ struct RestrictedStoreConfig : virtual LocalFSStoreConfig
/* A wrapper around LocalStore that only allows building/querying of /* A wrapper around LocalStore that only allows building/querying of
paths that are in the input closures of the build or were added via paths that are in the input closures of the build or were added via
recursive Nix calls. */ recursive Nix calls. */
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual LocalFSStore, public virtual GcStore struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual IndirectRootStore, public virtual GcStore
{ {
ref<LocalStore> next; ref<LocalStore> next;
@ -1251,11 +1251,13 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
void queryReferrers(const StorePath & path, StorePathSet & referrers) override void queryReferrers(const StorePath & path, StorePathSet & referrers) override
{ } { }
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(
const StorePath & path,
Store * evalStore = nullptr) override
{ {
if (!goal.isAllowed(path)) if (!goal.isAllowed(path))
throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path)); throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path));
return next->queryPartialDerivationOutputMap(path); return next->queryPartialDerivationOutputMap(path, evalStore);
} }
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
@ -2540,16 +2542,16 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
}, },
[&](const DerivationOutput::CAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
auto wanted = dof.ca.getHash(); auto & wanted = dof.ca.hash;
auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating { auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
.method = dof.ca.getMethod(), .method = dof.ca.method,
.hashType = wanted.type, .hashType = wanted.type,
}); });
/* Check wanted hash */ /* Check wanted hash */
assert(newInfo0.ca); assert(newInfo0.ca);
auto got = newInfo0.ca->getHash(); auto & got = newInfo0.ca->hash;
if (wanted != got) { if (wanted != got) {
/* Throw an error after registering the path as /* Throw an error after registering the path as
valid. */ valid. */

View file

@ -4,11 +4,6 @@
namespace nix { namespace nix {
std::string FixedOutputHash::printMethodAlgo() const
{
return makeFileIngestionPrefix(method) + printHashType(hash.type);
}
std::string makeFileIngestionPrefix(FileIngestionMethod m) std::string makeFileIngestionPrefix(FileIngestionMethod m)
{ {
switch (m) { switch (m) {
@ -42,21 +37,6 @@ ContentAddressMethod ContentAddressMethod::parsePrefix(std::string_view & m)
return method; return method;
} }
std::string ContentAddress::render() const
{
return std::visit(overloaded {
[](const TextHash & th) {
return "text:"
+ th.hash.to_string(Base32, true);
},
[](const FixedOutputHash & fsh) {
return "fixed:"
+ makeFileIngestionPrefix(fsh.method)
+ fsh.hash.to_string(Base32, true);
}
}, raw);
}
std::string ContentAddressMethod::render(HashType ht) const std::string ContentAddressMethod::render(HashType ht) const
{ {
return std::visit(overloaded { return std::visit(overloaded {
@ -69,6 +49,20 @@ std::string ContentAddressMethod::render(HashType ht) const
}, raw); }, raw);
} }
std::string ContentAddress::render() const
{
return std::visit(overloaded {
[](const TextIngestionMethod &) -> std::string {
return "text:";
},
[](const FileIngestionMethod & method) {
return "fixed:"
+ makeFileIngestionPrefix(method);
},
}, method.raw)
+ this->hash.to_string(Base32, true);
}
/** /**
* Parses content address strings up to the hash. * Parses content address strings up to the hash.
*/ */
@ -118,22 +112,12 @@ ContentAddress ContentAddress::parse(std::string_view rawCa)
{ {
auto rest = rawCa; auto rest = rawCa;
auto [caMethod, hashType_] = parseContentAddressMethodPrefix(rest); auto [caMethod, hashType] = parseContentAddressMethodPrefix(rest);
auto hashType = hashType_; // work around clang bug
return std::visit(overloaded { return ContentAddress {
[&](TextIngestionMethod &) { .method = std::move(caMethod).raw,
return ContentAddress(TextHash {
.hash = Hash::parseNonSRIUnprefixed(rest, hashType)
});
},
[&](FileIngestionMethod & fim) {
return ContentAddress(FixedOutputHash {
.method = fim,
.hash = Hash::parseNonSRIUnprefixed(rest, hashType), .hash = Hash::parseNonSRIUnprefixed(rest, hashType),
}); };
},
}, caMethod.raw);
} }
std::pair<ContentAddressMethod, HashType> ContentAddressMethod::parse(std::string_view caMethod) std::pair<ContentAddressMethod, HashType> ContentAddressMethod::parse(std::string_view caMethod)
@ -156,52 +140,10 @@ std::string renderContentAddress(std::optional<ContentAddress> ca)
return ca ? ca->render() : ""; return ca ? ca->render() : "";
} }
ContentAddress ContentAddress::fromParts(
ContentAddressMethod method, Hash hash) noexcept
{
return std::visit(overloaded {
[&](TextIngestionMethod _) -> ContentAddress {
return TextHash {
.hash = std::move(hash),
};
},
[&](FileIngestionMethod m2) -> ContentAddress {
return FixedOutputHash {
.method = std::move(m2),
.hash = std::move(hash),
};
},
}, method.raw);
}
ContentAddressMethod ContentAddress::getMethod() const
{
return std::visit(overloaded {
[](const TextHash & th) -> ContentAddressMethod {
return TextIngestionMethod {};
},
[](const FixedOutputHash & fsh) -> ContentAddressMethod {
return fsh.method;
},
}, raw);
}
const Hash & ContentAddress::getHash() const
{
return std::visit(overloaded {
[](const TextHash & th) -> auto & {
return th.hash;
},
[](const FixedOutputHash & fsh) -> auto & {
return fsh.hash;
},
}, raw);
}
std::string ContentAddress::printMethodAlgo() const std::string ContentAddress::printMethodAlgo() const
{ {
return getMethod().renderPrefix() return method.renderPrefix()
+ printHashType(getHash().type); + printHashType(hash.type);
} }
bool StoreReferences::empty() const bool StoreReferences::empty() const
@ -217,19 +159,20 @@ size_t StoreReferences::size() const
ContentAddressWithReferences ContentAddressWithReferences::withoutRefs(const ContentAddress & ca) noexcept ContentAddressWithReferences ContentAddressWithReferences::withoutRefs(const ContentAddress & ca) noexcept
{ {
return std::visit(overloaded { return std::visit(overloaded {
[&](const TextHash & h) -> ContentAddressWithReferences { [&](const TextIngestionMethod &) -> ContentAddressWithReferences {
return TextInfo { return TextInfo {
.hash = h, .hash = ca.hash,
.references = {}, .references = {},
}; };
}, },
[&](const FixedOutputHash & h) -> ContentAddressWithReferences { [&](const FileIngestionMethod & method) -> ContentAddressWithReferences {
return FixedOutputInfo { return FixedOutputInfo {
.hash = h, .method = method,
.hash = ca.hash,
.references = {}, .references = {},
}; };
}, },
}, ca.raw); }, ca.method.raw);
} }
std::optional<ContentAddressWithReferences> ContentAddressWithReferences::fromPartsOpt( std::optional<ContentAddressWithReferences> ContentAddressWithReferences::fromPartsOpt(
@ -241,7 +184,7 @@ std::optional<ContentAddressWithReferences> ContentAddressWithReferences::fromPa
return std::nullopt; return std::nullopt;
return ContentAddressWithReferences { return ContentAddressWithReferences {
TextInfo { TextInfo {
.hash = { .hash = std::move(hash) }, .hash = std::move(hash),
.references = std::move(refs.others), .references = std::move(refs.others),
} }
}; };
@ -249,10 +192,8 @@ std::optional<ContentAddressWithReferences> ContentAddressWithReferences::fromPa
[&](FileIngestionMethod m2) -> std::optional<ContentAddressWithReferences> { [&](FileIngestionMethod m2) -> std::optional<ContentAddressWithReferences> {
return ContentAddressWithReferences { return ContentAddressWithReferences {
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = m2, .method = m2,
.hash = std::move(hash), .hash = std::move(hash),
},
.references = std::move(refs), .references = std::move(refs),
} }
}; };
@ -267,7 +208,7 @@ ContentAddressMethod ContentAddressWithReferences::getMethod() const
return TextIngestionMethod {}; return TextIngestionMethod {};
}, },
[](const FixedOutputInfo & fsh) -> ContentAddressMethod { [](const FixedOutputInfo & fsh) -> ContentAddressMethod {
return fsh.hash.method; return fsh.method;
}, },
}, raw); }, raw);
} }
@ -276,10 +217,10 @@ Hash ContentAddressWithReferences::getHash() const
{ {
return std::visit(overloaded { return std::visit(overloaded {
[](const TextInfo & th) { [](const TextInfo & th) {
return th.hash.hash; return th.hash;
}, },
[](const FixedOutputInfo & fsh) { [](const FixedOutputInfo & fsh) {
return fsh.hash.hash; return fsh.hash;
}, },
}, raw); }, raw);
} }

View file

@ -113,37 +113,6 @@ struct ContentAddressMethod
* Mini content address * Mini content address
*/ */
/**
* Somewhat obscure, used by \ref Derivation derivations and
* `builtins.toFile` currently.
*/
struct TextHash {
/**
* Hash of the contents of the text/file.
*/
Hash hash;
GENERATE_CMP(TextHash, me->hash);
};
/**
* Used by most store objects that are content-addressed.
*/
struct FixedOutputHash {
/**
* How the file system objects are serialized
*/
FileIngestionMethod method;
/**
* Hash of that serialization
*/
Hash hash;
std::string printMethodAlgo() const;
GENERATE_CMP(FixedOutputHash, me->method, me->hash);
};
/** /**
* We've accumulated several types of content-addressed paths over the * We've accumulated several types of content-addressed paths over the
* years; fixed-output derivations support multiple hash algorithms and * years; fixed-output derivations support multiple hash algorithms and
@ -158,19 +127,17 @@ struct FixedOutputHash {
*/ */
struct ContentAddress struct ContentAddress
{ {
typedef std::variant< /**
TextHash, * How the file system objects are serialized
FixedOutputHash */
> Raw; ContentAddressMethod method;
Raw raw; /**
* Hash of that serialization
*/
Hash hash;
GENERATE_CMP(ContentAddress, me->raw); GENERATE_CMP(ContentAddress, me->method, me->hash);
/* The moral equivalent of `using Raw::Raw;` */
ContentAddress(auto &&... arg)
: raw(std::forward<decltype(arg)>(arg)...)
{ }
/** /**
* Compute the content-addressability assertion * Compute the content-addressability assertion
@ -183,20 +150,6 @@ struct ContentAddress
static std::optional<ContentAddress> parseOpt(std::string_view rawCaOpt); static std::optional<ContentAddress> parseOpt(std::string_view rawCaOpt);
/**
* Create a `ContentAddress` from 2 parts:
*
* @param method Way ingesting the file system data.
*
* @param hash Hash of ingested file system data.
*/
static ContentAddress fromParts(
ContentAddressMethod method, Hash hash) noexcept;
ContentAddressMethod getMethod() const;
const Hash & getHash() const;
std::string printMethodAlgo() const; std::string printMethodAlgo() const;
}; };
@ -219,7 +172,8 @@ std::string renderContentAddress(std::optional<ContentAddress> ca);
* References to other store objects are tracked with store paths, self * References to other store objects are tracked with store paths, self
* references however are tracked with a boolean. * references however are tracked with a boolean.
*/ */
struct StoreReferences { struct StoreReferences
{
/** /**
* References to other store objects * References to other store objects
*/ */
@ -246,8 +200,13 @@ struct StoreReferences {
}; };
// This matches the additional info that we need for makeTextPath // This matches the additional info that we need for makeTextPath
struct TextInfo { struct TextInfo
TextHash hash; {
/**
* Hash of the contents of the text/file.
*/
Hash hash;
/** /**
* References to other store objects only; self references * References to other store objects only; self references
* disallowed * disallowed
@ -257,8 +216,18 @@ struct TextInfo {
GENERATE_CMP(TextInfo, me->hash, me->references); GENERATE_CMP(TextInfo, me->hash, me->references);
}; };
struct FixedOutputInfo { struct FixedOutputInfo
FixedOutputHash hash; {
/**
* How the file system objects are serialized
*/
FileIngestionMethod method;
/**
* Hash of that serialization
*/
Hash hash;
/** /**
* References to other store objects or this one. * References to other store objects or this one.
*/ */

View file

@ -7,6 +7,7 @@
#include "store-cast.hh" #include "store-cast.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "log-store.hh" #include "log-store.hh"
#include "indirect-root-store.hh"
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
#include "finally.hh" #include "finally.hh"
#include "archive.hh" #include "archive.hh"
@ -675,8 +676,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
Path path = absPath(readString(from)); Path path = absPath(readString(from));
logger->startWork(); logger->startWork();
auto & gcStore = require<GcStore>(*store); auto & indirectRootStore = require<IndirectRootStore>(*store);
gcStore.addIndirectRoot(path); indirectRootStore.addIndirectRoot(path);
logger->stopWork(); logger->stopWork();
to << 1; to << 1;

View file

@ -232,9 +232,10 @@ static DerivationOutput parseDerivationOutput(const Store & store,
validatePath(pathS); validatePath(pathS);
auto hash = Hash::parseNonSRIUnprefixed(hashS, hashType); auto hash = Hash::parseNonSRIUnprefixed(hashS, hashType);
return DerivationOutput::CAFixed { return DerivationOutput::CAFixed {
.ca = ContentAddress::fromParts( .ca = ContentAddress {
std::move(method), .method = std::move(method),
std::move(hash)), .hash = std::move(hash),
},
}; };
} else { } else {
experimentalFeatureSettings.require(Xp::CaDerivations); experimentalFeatureSettings.require(Xp::CaDerivations);
@ -395,7 +396,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
[&](const DerivationOutput::CAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first))); s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
s += ','; printUnquotedString(s, dof.ca.printMethodAlgo()); s += ','; printUnquotedString(s, dof.ca.printMethodAlgo());
s += ','; printUnquotedString(s, dof.ca.getHash().to_string(Base16, false)); s += ','; printUnquotedString(s, dof.ca.hash.to_string(Base16, false));
}, },
[&](const DerivationOutput::CAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
@ -628,7 +629,7 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw()); auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw());
auto hash = hashString(htSHA256, "fixed:out:" auto hash = hashString(htSHA256, "fixed:out:"
+ dof.ca.printMethodAlgo() + ":" + dof.ca.printMethodAlgo() + ":"
+ dof.ca.getHash().to_string(Base16, false) + ":" + dof.ca.hash.to_string(Base16, false) + ":"
+ store.printStorePath(dof.path(store, drv.name, i.first))); + store.printStorePath(dof.path(store, drv.name, i.first)));
outputHashes.insert_or_assign(i.first, std::move(hash)); outputHashes.insert_or_assign(i.first, std::move(hash));
} }
@ -780,7 +781,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
[&](const DerivationOutput::CAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
out << store.printStorePath(dof.path(store, drv.name, i.first)) out << store.printStorePath(dof.path(store, drv.name, i.first))
<< dof.ca.printMethodAlgo() << dof.ca.printMethodAlgo()
<< dof.ca.getHash().to_string(Base16, false); << dof.ca.hash.to_string(Base16, false);
}, },
[&](const DerivationOutput::CAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
out << "" out << ""
@ -970,7 +971,7 @@ nlohmann::json DerivationOutput::toJSON(
[&](const DerivationOutput::CAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
res["path"] = store.printStorePath(dof.path(store, drvName, outputName)); res["path"] = store.printStorePath(dof.path(store, drvName, outputName));
res["hashAlgo"] = dof.ca.printMethodAlgo(); res["hashAlgo"] = dof.ca.printMethodAlgo();
res["hash"] = dof.ca.getHash().to_string(Base16, false); res["hash"] = dof.ca.hash.to_string(Base16, false);
// FIXME print refs? // FIXME print refs?
}, },
[&](const DerivationOutput::CAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
@ -1017,9 +1018,10 @@ DerivationOutput DerivationOutput::fromJSON(
else if (keys == (std::set<std::string_view> { "path", "hashAlgo", "hash" })) { else if (keys == (std::set<std::string_view> { "path", "hashAlgo", "hash" })) {
auto [method, hashType] = methodAlgo(); auto [method, hashType] = methodAlgo();
auto dof = DerivationOutput::CAFixed { auto dof = DerivationOutput::CAFixed {
.ca = ContentAddress::fromParts( .ca = ContentAddress {
std::move(method), .method = std::move(method),
Hash::parseNonSRIUnprefixed((std::string) json["hash"], hashType)), .hash = Hash::parseNonSRIUnprefixed((std::string) json["hash"], hashType),
},
}; };
if (dof.path(store, drvName, outputName) != store.parseStorePath((std::string) json["path"])) if (dof.path(store, drvName, outputName) != store.parseStorePath((std::string) json["path"]))
throw Error("Path doesn't match derivation output"); throw Error("Path doesn't match derivation output");

View file

@ -3,7 +3,6 @@
#include "util.hh" #include "util.hh"
#include "path.hh" #include "path.hh"
#include "realisation.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "comparator.hh" #include "comparator.hh"

View file

@ -71,19 +71,36 @@ struct GCResults
}; };
/**
* Mix-in class for \ref Store "stores" which expose a notion of garbage
* collection.
*
* Garbage collection will allow deleting paths which are not
* transitively "rooted".
*
* The notion of GC roots actually not part of this class.
*
* - The base `Store` class has `Store::addTempRoot()` because for a store
* that doesn't support garbage collection at all, a temporary GC root is
* safely implementable as no-op.
*
* @todo actually this is not so good because stores are *views*.
* Some views have only a no-op temp roots even though others to the
* same store allow triggering GC. For instance one can't add a root
* over ssh, but that doesn't prevent someone from gc-ing that store
* accesed via SSH locally).
*
* - The derived `LocalFSStore` class has `LocalFSStore::addPermRoot`,
* which is not part of this class because it relies on the notion of
* an ambient file system. There are stores (`ssh-ng://`, for one),
* that *do* support garbage collection but *don't* expose any file
* system, and `LocalFSStore::addPermRoot` thus does not make sense
* for them.
*/
struct GcStore : public virtual Store struct GcStore : public virtual Store
{ {
inline static std::string operationName = "Garbage collection"; 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
* automatically remove the indirect root when it finds that
* `path` has disappeared.
*/
virtual void addIndirectRoot(const Path & path) = 0;
/** /**
* Find the roots of the garbage collector. Each root is a pair * Find the roots of the garbage collector. Each root is a pair
* `(link, storepath)` where `link` is the path of the symlink * `(link, storepath)` where `link` is the path of the symlink

View file

@ -1,7 +1,6 @@
#include "derivations.hh" #include "derivations.hh"
#include "globals.hh" #include "globals.hh"
#include "local-store.hh" #include "local-store.hh"
#include "local-fs-store.hh"
#include "finally.hh" #include "finally.hh"
#include <functional> #include <functional>
@ -50,7 +49,7 @@ void LocalStore::addIndirectRoot(const Path & path)
} }
Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot) Path IndirectRootStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot)
{ {
Path gcRoot(canonPath(_gcRoot)); Path gcRoot(canonPath(_gcRoot));

View file

@ -0,0 +1,48 @@
#pragma once
///@file
#include "local-fs-store.hh"
namespace nix {
/**
* Mix-in class for implementing permanent roots as a pair of a direct
* (strong) reference and indirect weak reference to the first
* reference.
*
* See methods for details on the operations it represents.
*/
struct IndirectRootStore : public virtual LocalFSStore
{
inline static std::string operationName = "Indirect GC roots registration";
/**
* Implementation of `LocalFSStore::addPermRoot` where the permanent
* root is a pair of
*
* - The user-facing symlink which all implementations must create
*
* - An additional weak reference known as the "indirect root" that
* points to that symlink.
*
* The garbage collector will automatically remove the indirect root
* when it finds that the symlink has disappeared.
*
* The implementation of this method is concrete, but it delegates
* to `addIndirectRoot()` which is abstract.
*/
Path addPermRoot(const StorePath & storePath, const Path & gcRoot) override final;
/**
* Add an indirect root, which is a weak reference to the
* user-facing symlink created by `addPermRoot()`.
*
* @param path user-facing and user-controlled symlink to a store
* path.
*
* The form this weak-reference takes is implementation-specific.
*/
virtual void addIndirectRoot(const Path & path) = 0;
};
}

View file

@ -40,6 +40,7 @@ class LocalFSStore : public virtual LocalFSStoreConfig,
public virtual LogStore public virtual LogStore
{ {
public: public:
inline static std::string operationName = "Local Filesystem Store";
const static std::string drvsLogDir; const static std::string drvsLogDir;
@ -49,9 +50,20 @@ public:
ref<FSAccessor> getFSAccessor() override; ref<FSAccessor> getFSAccessor() override;
/** /**
* Register a permanent GC root. * Creates symlink from the `gcRoot` to the `storePath` and
* registers the `gcRoot` as a permanent GC root. The `gcRoot`
* symlink lives outside the store and is created and owned by the
* user.
*
* @param gcRoot The location of the symlink.
*
* @param storePath The store object being rooted. The symlink will
* point to `toRealPath(store.printStorePath(storePath))`.
*
* How the permanent GC root corresponding to this symlink is
* managed is implementation-specific.
*/ */
Path addPermRoot(const StorePath & storePath, const Path & gcRoot); virtual Path addPermRoot(const StorePath & storePath, const Path & gcRoot) = 0;
virtual Path getRealStoreDir() { return realStoreDir; } virtual Path getRealStoreDir() { return realStoreDir; }

View file

@ -1028,10 +1028,9 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
std::map<std::string, std::optional<StorePath>> std::map<std::string, std::optional<StorePath>>
LocalStore::queryPartialDerivationOutputMap(const StorePath & path_) LocalStore::queryStaticPartialDerivationOutputMap(const StorePath & path)
{ {
auto path = path_; return retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
auto outputs = retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
auto state(_state.lock()); auto state(_state.lock());
std::map<std::string, std::optional<StorePath>> outputs; std::map<std::string, std::optional<StorePath>> outputs;
uint64_t drvId; uint64_t drvId;
@ -1043,21 +1042,6 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
return outputs; return outputs;
}); });
if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
return outputs;
auto drv = readInvalidDerivation(path);
auto drvHashes = staticOutputHashes(*this, drv);
for (auto& [outputName, hash] : drvHashes) {
auto realisation = queryRealisation(DrvOutput{hash, outputName});
if (realisation)
outputs.insert_or_assign(outputName, realisation->outPath);
else
outputs.insert({outputName, std::nullopt});
}
return outputs;
} }
std::optional<StorePath> LocalStore::queryPathFromHashPart(const std::string & hashPart) std::optional<StorePath> LocalStore::queryPathFromHashPart(const std::string & hashPart)
@ -1255,27 +1239,17 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
printStorePath(info.path), info.narSize, hashResult.second); printStorePath(info.path), info.narSize, hashResult.second);
if (info.ca) { if (info.ca) {
if (auto foHash = std::get_if<FixedOutputHash>(&info.ca->raw)) { auto & specified = *info.ca;
auto actualFoHash = hashCAPath( auto actualHash = hashCAPath(
foHash->method, specified.method,
foHash->hash.type, specified.hash.type,
info.path info.path
); );
if (foHash->hash != actualFoHash.hash) { if (specified.hash != actualHash.hash) {
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s", throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
printStorePath(info.path), printStorePath(info.path),
foHash->hash.to_string(Base32, true), specified.hash.to_string(Base32, true),
actualFoHash.hash.to_string(Base32, true)); actualHash.hash.to_string(Base32, true));
}
}
if (auto textHash = std::get_if<TextHash>(&info.ca->raw)) {
auto actualTextHash = hashString(htSHA256, readFile(realPath));
if (textHash->hash != actualTextHash) {
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
printStorePath(info.path),
textHash->hash.to_string(Base32, true),
actualTextHash.to_string(Base32, true));
}
} }
} }
@ -1355,10 +1329,8 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
auto [hash, size] = hashSink->finish(); auto [hash, size] = hashSink->finish();
ContentAddressWithReferences desc = FixedOutputInfo { ContentAddressWithReferences desc = FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = hash, .hash = hash,
},
.references = { .references = {
.others = references, .others = references,
// caller is not capable of creating a self-reference, because this is content-addressed without modulus // caller is not capable of creating a self-reference, because this is content-addressed without modulus
@ -1434,8 +1406,8 @@ StorePath LocalStore::addTextToStore(
{ {
auto hash = hashString(htSHA256, s); auto hash = hashString(htSHA256, s);
auto dstPath = makeTextPath(name, TextInfo { auto dstPath = makeTextPath(name, TextInfo {
{ .hash = hash }, .hash = hash,
references, .references = references,
}); });
addTempRoot(dstPath); addTempRoot(dstPath);
@ -1465,7 +1437,10 @@ StorePath LocalStore::addTextToStore(
ValidPathInfo info { dstPath, narHash }; ValidPathInfo info { dstPath, narHash };
info.narSize = sink.s.size(); info.narSize = sink.s.size();
info.references = references; info.references = references;
info.ca = TextHash { .hash = hash }; info.ca = {
.method = TextIngestionMethod {},
.hash = hash,
};
registerValidPath(info); registerValidPath(info);
} }
@ -1862,22 +1837,27 @@ void LocalStore::queryRealisationUncached(const DrvOutput & id,
} }
} }
FixedOutputHash LocalStore::hashCAPath( ContentAddress LocalStore::hashCAPath(
const FileIngestionMethod & method, const HashType & hashType, const ContentAddressMethod & method, const HashType & hashType,
const StorePath & path) const StorePath & path)
{ {
return hashCAPath(method, hashType, Store::toRealPath(path), path.hashPart()); return hashCAPath(method, hashType, Store::toRealPath(path), path.hashPart());
} }
FixedOutputHash LocalStore::hashCAPath( ContentAddress LocalStore::hashCAPath(
const FileIngestionMethod & method, const ContentAddressMethod & method,
const HashType & hashType, const HashType & hashType,
const Path & path, const Path & path,
const std::string_view pathHash const std::string_view pathHash
) )
{ {
HashModuloSink caSink ( hashType, std::string(pathHash) ); HashModuloSink caSink ( hashType, std::string(pathHash) );
switch (method) { std::visit(overloaded {
[&](const TextIngestionMethod &) {
readFile(path, caSink);
},
[&](const FileIngestionMethod & m2) {
switch (m2) {
case FileIngestionMethod::Recursive: case FileIngestionMethod::Recursive:
dumpPath(path, caSink); dumpPath(path, caSink);
break; break;
@ -1885,10 +1865,11 @@ FixedOutputHash LocalStore::hashCAPath(
readFile(path, caSink); readFile(path, caSink);
break; break;
} }
auto hash = caSink.finish().first; },
return FixedOutputHash{ }, method.raw);
return ContentAddress {
.method = method, .method = method,
.hash = hash, .hash = caSink.finish().first,
}; };
} }

View file

@ -5,8 +5,7 @@
#include "pathlocks.hh" #include "pathlocks.hh"
#include "store-api.hh" #include "store-api.hh"
#include "local-fs-store.hh" #include "indirect-root-store.hh"
#include "gc-store.hh"
#include "sync.hh" #include "sync.hh"
#include "util.hh" #include "util.hh"
@ -68,7 +67,9 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
std::string doc() override; std::string doc() override;
}; };
class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore, public virtual GcStore class LocalStore : public virtual LocalStoreConfig
, public virtual IndirectRootStore
, public virtual GcStore
{ {
private: private:
@ -165,7 +166,7 @@ public:
StorePathSet queryValidDerivers(const StorePath & path) override; StorePathSet queryValidDerivers(const StorePath & path) override;
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override; std::map<std::string, std::optional<StorePath>> queryStaticPartialDerivationOutputMap(const StorePath & path) override;
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override; std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
@ -209,6 +210,12 @@ private:
public: public:
/**
* Implementation of IndirectRootStore::addIndirectRoot().
*
* The weak reference merely is a symlink to `path' from
* /nix/var/nix/gcroots/auto/<hash of `path'>.
*/
void addIndirectRoot(const Path & path) override; void addIndirectRoot(const Path & path) override;
private: private:
@ -352,13 +359,13 @@ private:
void signRealisation(Realisation &); void signRealisation(Realisation &);
// XXX: Make a generic `Store` method // XXX: Make a generic `Store` method
FixedOutputHash hashCAPath( ContentAddress hashCAPath(
const FileIngestionMethod & method, const ContentAddressMethod & method,
const HashType & hashType, const HashType & hashType,
const StorePath & path); const StorePath & path);
FixedOutputHash hashCAPath( ContentAddress hashCAPath(
const FileIngestionMethod & method, const ContentAddressMethod & method,
const HashType & hashType, const HashType & hashType,
const Path & path, const Path & path,
const std::string_view pathHash const std::string_view pathHash

View file

@ -52,10 +52,8 @@ std::map<StorePath, StorePath> makeContentAddressed(
dstStore, dstStore,
path.name(), path.name(),
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = FileIngestionMethod::Recursive, .method = FileIngestionMethod::Recursive,
.hash = narModuloHash, .hash = narModuloHash,
},
.references = std::move(refs), .references = std::move(refs),
}, },
Hash::dummy, Hash::dummy,

View file

@ -310,43 +310,34 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_) OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
{ {
auto & evalStore = evalStore_ ? *evalStore_ : store; auto outputsOpt_ = store.queryPartialDerivationOutputMap(bfd.drvPath, evalStore_);
OutputPathMap outputs; auto outputsOpt = std::visit(overloaded {
auto drv = evalStore.readDerivation(bfd.drvPath);
auto outputHashes = staticOutputHashes(store, drv);
auto drvOutputs = drv.outputsAndOptPaths(store);
auto outputNames = std::visit(overloaded {
[&](const OutputsSpec::All &) { [&](const OutputsSpec::All &) {
StringSet names; // Keep all outputs
for (auto & [outputName, _] : drv.outputs) return std::move(outputsOpt_);
names.insert(outputName);
return names;
}, },
[&](const OutputsSpec::Names & names) { [&](const OutputsSpec::Names & names) {
return static_cast<std::set<std::string>>(names); // Get just those mentioned by name
}, std::map<std::string, std::optional<StorePath>> outputsOpt;
}, bfd.outputs.raw()); for (auto & output : names) {
for (auto & output : outputNames) { auto * pOutputPathOpt = get(outputsOpt_, output);
auto outputHash = get(outputHashes, output); if (!pOutputPathOpt)
if (!outputHash)
throw Error( throw Error(
"the derivation '%s' doesn't have an output named '%s'", "the derivation '%s' doesn't have an output named '%s'",
store.printStorePath(bfd.drvPath), output); store.printStorePath(bfd.drvPath), output);
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) { outputsOpt.insert_or_assign(output, std::move(*pOutputPathOpt));
DrvOutput outputId { *outputHash, output };
auto realisation = store.queryRealisation(outputId);
if (!realisation)
throw MissingRealisation(outputId);
outputs.insert_or_assign(output, realisation->outPath);
} else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
auto drvOutput = get(drvOutputs, output);
assert(drvOutput);
assert(drvOutput->second);
outputs.insert_or_assign(output, *drvOutput->second);
} }
return outputsOpt;
},
}, bfd.outputs.raw());
OutputPathMap outputs;
for (auto & [outputName, outputPathOpt] : outputsOpt) {
if (!outputPathOpt)
throw MissingRealisation(store.printStorePath(bfd.drvPath), outputName);
auto & outputPath = *outputPathOpt;
outputs.insert_or_assign(outputName, outputPath);
} }
return outputs; return outputs;
} }

View file

@ -29,14 +29,14 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef
return std::nullopt; return std::nullopt;
return std::visit(overloaded { return std::visit(overloaded {
[&](const TextHash & th) -> ContentAddressWithReferences { [&](const TextIngestionMethod &) -> ContentAddressWithReferences {
assert(references.count(path) == 0); assert(references.count(path) == 0);
return TextInfo { return TextInfo {
.hash = th, .hash = ca->hash,
.references = references, .references = references,
}; };
}, },
[&](const FixedOutputHash & foh) -> ContentAddressWithReferences { [&](const FileIngestionMethod & m2) -> ContentAddressWithReferences {
auto refs = references; auto refs = references;
bool hasSelfReference = false; bool hasSelfReference = false;
if (refs.count(path)) { if (refs.count(path)) {
@ -44,14 +44,15 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef
refs.erase(path); refs.erase(path);
} }
return FixedOutputInfo { return FixedOutputInfo {
.hash = foh, .method = m2,
.hash = ca->hash,
.references = { .references = {
.others = std::move(refs), .others = std::move(refs),
.self = hasSelfReference, .self = hasSelfReference,
}, },
}; };
}, },
}, ca->raw); }, ca->method.raw);
} }
bool ValidPathInfo::isContentAddressed(const Store & store) const bool ValidPathInfo::isContentAddressed(const Store & store) const
@ -110,13 +111,19 @@ ValidPathInfo::ValidPathInfo(
std::visit(overloaded { std::visit(overloaded {
[this](TextInfo && ti) { [this](TextInfo && ti) {
this->references = std::move(ti.references); this->references = std::move(ti.references);
this->ca = std::move((TextHash &&) ti); this->ca = ContentAddress {
.method = TextIngestionMethod {},
.hash = std::move(ti.hash),
};
}, },
[this](FixedOutputInfo && foi) { [this](FixedOutputInfo && foi) {
this->references = std::move(foi.references.others); this->references = std::move(foi.references.others);
if (foi.references.self) if (foi.references.self)
this->references.insert(path); this->references.insert(path);
this->ca = std::move((FixedOutputHash &&) foi); this->ca = ContentAddress {
.method = std::move(foi.method),
.hash = std::move(foi.hash),
};
}, },
}, std::move(ca).raw); }, std::move(ca).raw);
} }

View file

@ -5,6 +5,7 @@
#include "hash.hh" #include "hash.hh"
#include "path.hh" #include "path.hh"
#include "derived-path.hh"
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#include "comparator.hh" #include "comparator.hh"
#include "crypto.hh" #include "crypto.hh"
@ -143,9 +144,13 @@ class MissingRealisation : public Error
{ {
public: public:
MissingRealisation(DrvOutput & outputId) MissingRealisation(DrvOutput & outputId)
: Error( "cannot operate on an output of the " : MissingRealisation(outputId.outputName, outputId.strHash())
{}
MissingRealisation(std::string_view drv, std::string outputName)
: Error( "cannot operate on output '%s' of the "
"unbuilt derivation '%s'", "unbuilt derivation '%s'",
outputId.to_string()) outputName,
drv)
{} {}
}; };

View file

@ -1,5 +1,6 @@
#include "remote-store.hh" #include "remote-store.hh"
#include "worker-protocol.hh" #include "worker-protocol.hh"
#include "pool.hh"
namespace nix { namespace nix {
@ -94,4 +95,34 @@ struct RemoteStore::Connection
std::exception_ptr processStderr(Sink * sink = 0, Source * source = 0, bool flush = true); std::exception_ptr processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);
}; };
/**
* A wrapper around Pool<RemoteStore::Connection>::Handle that marks
* the connection as bad (causing it to be closed) if a non-daemon
* exception is thrown before the handle is closed. Such an exception
* causes a deviation from the expected protocol and therefore a
* desynchronization between the client and daemon.
*/
struct RemoteStore::ConnectionHandle
{
Pool<RemoteStore::Connection>::Handle handle;
bool daemonException = false;
ConnectionHandle(Pool<RemoteStore::Connection>::Handle && handle)
: handle(std::move(handle))
{ }
ConnectionHandle(ConnectionHandle && h)
: handle(std::move(h.handle))
{ }
~ConnectionHandle();
RemoteStore::Connection & operator * () { return *handle; }
RemoteStore::Connection * operator -> () { return &*handle; }
void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true);
void withFramedSink(std::function<void(Sink & sink)> fun);
};
} }

View file

@ -159,49 +159,25 @@ void RemoteStore::setOptions(Connection & conn)
} }
/* A wrapper around Pool<RemoteStore::Connection>::Handle that marks RemoteStore::ConnectionHandle::~ConnectionHandle()
the connection as bad (causing it to be closed) if a non-daemon
exception is thrown before the handle is closed. Such an exception
causes a deviation from the expected protocol and therefore a
desynchronization between the client and daemon. */
struct ConnectionHandle
{ {
Pool<RemoteStore::Connection>::Handle handle;
bool daemonException = false;
ConnectionHandle(Pool<RemoteStore::Connection>::Handle && handle)
: handle(std::move(handle))
{ }
ConnectionHandle(ConnectionHandle && h)
: handle(std::move(h.handle))
{ }
~ConnectionHandle()
{
if (!daemonException && std::uncaught_exceptions()) { if (!daemonException && std::uncaught_exceptions()) {
handle.markBad(); handle.markBad();
debug("closing daemon connection because of an exception"); debug("closing daemon connection because of an exception");
} }
} }
RemoteStore::Connection * operator -> () { return &*handle; } void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source, bool flush)
RemoteStore::Connection & operator * () { return *handle; } {
void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true)
{
auto ex = handle->processStderr(sink, source, flush); auto ex = handle->processStderr(sink, source, flush);
if (ex) { if (ex) {
daemonException = true; daemonException = true;
std::rethrow_exception(ex); std::rethrow_exception(ex);
} }
} }
void withFramedSink(std::function<void(Sink & sink)> fun);
};
ConnectionHandle RemoteStore::getConnection() RemoteStore::ConnectionHandle RemoteStore::getConnection()
{ {
return ConnectionHandle(connections->get()); return ConnectionHandle(connections->get());
} }
@ -378,27 +354,36 @@ StorePathSet RemoteStore::queryDerivationOutputs(const StorePath & path)
} }
std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivationOutputMap(const StorePath & path) std::map<std::string, std::optional<StorePath>> RemoteStore::queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore_)
{ {
if (GET_PROTOCOL_MINOR(getProtocol()) >= 0x16) { if (GET_PROTOCOL_MINOR(getProtocol()) >= 0x16) {
if (!evalStore_) {
auto conn(getConnection()); auto conn(getConnection());
conn->to << WorkerProto::Op::QueryDerivationOutputMap << printStorePath(path); conn->to << WorkerProto::Op::QueryDerivationOutputMap << printStorePath(path);
conn.processStderr(); conn.processStderr();
return WorkerProto::Serialise<std::map<std::string, std::optional<StorePath>>>::read(*this, *conn); return WorkerProto::Serialise<std::map<std::string, std::optional<StorePath>>>::read(*this, *conn);
} else { } else {
auto & evalStore = *evalStore_;
auto outputs = evalStore.queryStaticPartialDerivationOutputMap(path);
// union with the first branch overriding the statically-known ones
// when non-`std::nullopt`.
for (auto && [outputName, optPath] : queryPartialDerivationOutputMap(path, nullptr)) {
if (optPath)
outputs.insert_or_assign(std::move(outputName), std::move(optPath));
else
outputs.insert({std::move(outputName), std::nullopt});
}
return outputs;
}
} else {
auto & evalStore = evalStore_ ? *evalStore_ : *this;
// Fallback for old daemon versions. // Fallback for old daemon versions.
// For floating-CA derivations (and their co-dependencies) this is an // For floating-CA derivations (and their co-dependencies) this is an
// under-approximation as it only returns the paths that can be inferred // under-approximation as it only returns the paths that can be inferred
// from the derivation itself (and not the ones that are known because // from the derivation itself (and not the ones that are known because
// the have been built), but as old stores don't handle floating-CA // the have been built), but as old stores don't handle floating-CA
// derivations this shouldn't matter // derivations this shouldn't matter
auto derivation = readDerivation(path); return evalStore.queryStaticPartialDerivationOutputMap(path);
auto outputsWithOptPaths = derivation.outputsAndOptPaths(*this);
std::map<std::string, std::optional<StorePath>> ret;
for (auto & [outputName, outputAndPath] : outputsWithOptPaths) {
ret.emplace(outputName, outputAndPath.second);
}
return ret;
} }
} }
@ -837,15 +822,6 @@ void RemoteStore::addTempRoot(const StorePath & path)
} }
void RemoteStore::addIndirectRoot(const Path & path)
{
auto conn(getConnection());
conn->to << WorkerProto::Op::AddIndirectRoot << path;
conn.processStderr();
readInt(conn->from);
}
Roots RemoteStore::findRoots(bool censor) Roots RemoteStore::findRoots(bool censor)
{ {
auto conn(getConnection()); auto conn(getConnection());
@ -1090,7 +1066,7 @@ std::exception_ptr RemoteStore::Connection::processStderr(Sink * sink, Source *
return nullptr; return nullptr;
} }
void ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun) void RemoteStore::ConnectionHandle::withFramedSink(std::function<void(Sink & sink)> fun)
{ {
(*this)->to.flush(); (*this)->to.flush();

View file

@ -17,7 +17,6 @@ class Pid;
struct FdSink; struct FdSink;
struct FdSource; struct FdSource;
template<typename T> class Pool; template<typename T> class Pool;
struct ConnectionHandle;
struct RemoteStoreConfig : virtual StoreConfig struct RemoteStoreConfig : virtual StoreConfig
{ {
@ -63,7 +62,7 @@ public:
StorePathSet queryDerivationOutputs(const StorePath & path) override; StorePathSet queryDerivationOutputs(const StorePath & path) override;
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path) override; std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore = nullptr) override;
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override; std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
StorePathSet querySubstitutablePaths(const StorePathSet & paths) override; StorePathSet querySubstitutablePaths(const StorePathSet & paths) override;
@ -127,8 +126,6 @@ public:
void addTempRoot(const StorePath & path) override; void addTempRoot(const StorePath & path) override;
void addIndirectRoot(const Path & path) override;
Roots findRoots(bool censor) override; Roots findRoots(bool censor) override;
void collectGarbage(const GCOptions & options, GCResults & results) override; void collectGarbage(const GCOptions & options, GCResults & results) override;
@ -182,6 +179,8 @@ protected:
void setOptions() override; void setOptions() override;
struct ConnectionHandle;
ConnectionHandle getConnection(); ConnectionHandle getConnection();
friend struct ConnectionHandle; friend struct ConnectionHandle;
@ -199,5 +198,4 @@ private:
std::shared_ptr<Store> evalStore); std::shared_ptr<Store> evalStore);
}; };
} }

View file

@ -1,5 +1,6 @@
#include "ssh-store-config.hh" #include "ssh-store-config.hh"
#include "store-api.hh" #include "store-api.hh"
#include "local-fs-store.hh"
#include "remote-store.hh" #include "remote-store.hh"
#include "remote-store-connection.hh" #include "remote-store-connection.hh"
#include "remote-fs-accessor.hh" #include "remote-fs-accessor.hh"
@ -61,7 +62,7 @@ public:
std::optional<std::string> getBuildLogExact(const StorePath & path) override std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ unsupported("getBuildLogExact"); } { unsupported("getBuildLogExact"); }
private: protected:
struct Connection : RemoteStore::Connection struct Connection : RemoteStore::Connection
{ {
@ -93,9 +94,12 @@ private:
ref<RemoteStore::Connection> SSHStore::openConnection() ref<RemoteStore::Connection> SSHStore::openConnection()
{ {
auto conn = make_ref<Connection>(); auto conn = make_ref<Connection>();
conn->sshConn = master.startCommand(
fmt("%s --stdio", remoteProgram) std::string command = remoteProgram + " --stdio";
+ (remoteStore.get() == "" ? "" : " --store " + shellEscape(remoteStore.get()))); if (remoteStore.get() != "")
command += " --store " + shellEscape(remoteStore.get());
conn->sshConn = master.startCommand(command);
conn->to = FdSink(conn->sshConn->in.get()); conn->to = FdSink(conn->sshConn->in.get());
conn->from = FdSource(conn->sshConn->out.get()); conn->from = FdSource(conn->sshConn->out.get());
return conn; return conn;

View file

@ -185,15 +185,15 @@ static std::string makeType(
StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const
{ {
if (info.hash.hash.type == htSHA256 && info.hash.method == FileIngestionMethod::Recursive) { if (info.hash.type == htSHA256 && info.method == FileIngestionMethod::Recursive) {
return makeStorePath(makeType(*this, "source", info.references), info.hash.hash, name); return makeStorePath(makeType(*this, "source", info.references), info.hash, name);
} else { } else {
assert(info.references.size() == 0); assert(info.references.size() == 0);
return makeStorePath("output:out", return makeStorePath("output:out",
hashString(htSHA256, hashString(htSHA256,
"fixed:out:" "fixed:out:"
+ makeFileIngestionPrefix(info.hash.method) + makeFileIngestionPrefix(info.method)
+ info.hash.hash.to_string(Base16, true) + ":"), + info.hash.to_string(Base16, true) + ":"),
name); name);
} }
} }
@ -201,13 +201,13 @@ StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInf
StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) const StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) const
{ {
assert(info.hash.hash.type == htSHA256); assert(info.hash.type == htSHA256);
return makeStorePath( return makeStorePath(
makeType(*this, "text", StoreReferences { makeType(*this, "text", StoreReferences {
.others = info.references, .others = info.references,
.self = false, .self = false,
}), }),
info.hash.hash, info.hash,
name); name);
} }
@ -233,10 +233,8 @@ std::pair<StorePath, Hash> Store::computeStorePathForPath(std::string_view name,
? hashPath(hashAlgo, srcPath, filter).first ? hashPath(hashAlgo, srcPath, filter).first
: hashFile(hashAlgo, srcPath); : hashFile(hashAlgo, srcPath);
FixedOutputInfo caInfo { FixedOutputInfo caInfo {
.hash = {
.method = method, .method = method,
.hash = h, .hash = h,
},
.references = {}, .references = {},
}; };
return std::make_pair(makeFixedOutputPath(name, caInfo), h); return std::make_pair(makeFixedOutputPath(name, caInfo), h);
@ -249,8 +247,8 @@ StorePath Store::computeStorePathForText(
const StorePathSet & references) const const StorePathSet & references) const
{ {
return makeTextPath(name, TextInfo { return makeTextPath(name, TextInfo {
{ .hash = hashString(htSHA256, s) }, .hash = hashString(htSHA256, s),
references, .references = references,
}); });
} }
@ -442,10 +440,8 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
*this, *this,
name, name,
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = hash, .hash = hash,
},
.references = {}, .references = {},
}, },
narHash, narHash,
@ -497,22 +493,50 @@ bool Store::PathInfoCacheValue::isKnownNow()
return std::chrono::steady_clock::now() < time_point + ttl; return std::chrono::steady_clock::now() < time_point + ttl;
} }
std::map<std::string, std::optional<StorePath>> Store::queryPartialDerivationOutputMap(const StorePath & path) std::map<std::string, std::optional<StorePath>> Store::queryStaticPartialDerivationOutputMap(const StorePath & path)
{ {
std::map<std::string, std::optional<StorePath>> outputs; std::map<std::string, std::optional<StorePath>> outputs;
auto drv = readInvalidDerivation(path); auto drv = readInvalidDerivation(path);
for (auto& [outputName, output] : drv.outputsAndOptPaths(*this)) { for (auto & [outputName, output] : drv.outputsAndOptPaths(*this)) {
outputs.emplace(outputName, output.second); outputs.emplace(outputName, output.second);
} }
return outputs; return outputs;
} }
std::map<std::string, std::optional<StorePath>> Store::queryPartialDerivationOutputMap(
const StorePath & path,
Store * evalStore_)
{
auto & evalStore = evalStore_ ? *evalStore_ : *this;
auto outputs = evalStore.queryStaticPartialDerivationOutputMap(path);
if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
return outputs;
auto drv = evalStore.readInvalidDerivation(path);
auto drvHashes = staticOutputHashes(*this, drv);
for (auto & [outputName, hash] : drvHashes) {
auto realisation = queryRealisation(DrvOutput{hash, outputName});
if (realisation) {
outputs.insert_or_assign(outputName, realisation->outPath);
} else {
// queryStaticPartialDerivationOutputMap is not guaranteed
// to return std::nullopt for outputs which are not
// statically known.
outputs.insert({outputName, std::nullopt});
}
}
return outputs;
}
OutputPathMap Store::queryDerivationOutputMap(const StorePath & path) { OutputPathMap Store::queryDerivationOutputMap(const StorePath & path) {
auto resp = queryPartialDerivationOutputMap(path); auto resp = queryPartialDerivationOutputMap(path);
OutputPathMap result; OutputPathMap result;
for (auto & [outName, optOutPath] : resp) { for (auto & [outName, optOutPath] : resp) {
if (!optOutPath) if (!optOutPath)
throw Error("output '%s' of derivation '%s' has no store path mapped to it", outName, printStorePath(path)); throw MissingRealisation(printStorePath(path), outName);
result.insert_or_assign(outName, *optOutPath); result.insert_or_assign(outName, *optOutPath);
} }
return result; return result;

View file

@ -99,6 +99,8 @@ typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
struct StoreConfig : public Config struct StoreConfig : public Config
{ {
typedef std::map<std::string, std::string> Params;
using Config::Config; using Config::Config;
StoreConfig() = delete; StoreConfig() = delete;
@ -153,10 +155,6 @@ struct StoreConfig : public Config
class Store : public std::enable_shared_from_this<Store>, public virtual StoreConfig class Store : public std::enable_shared_from_this<Store>, public virtual StoreConfig
{ {
public:
typedef std::map<std::string, std::string> Params;
protected: protected:
struct PathInfoCacheValue { struct PathInfoCacheValue {
@ -425,7 +423,20 @@ public:
* derivation. All outputs are mentioned so ones mising the mapping * derivation. All outputs are mentioned so ones mising the mapping
* are mapped to `std::nullopt`. * are mapped to `std::nullopt`.
*/ */
virtual std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(const StorePath & path); virtual std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap(
const StorePath & path,
Store * evalStore = nullptr);
/**
* Like `queryPartialDerivationOutputMap` but only considers
* statically known output paths (i.e. those that can be gotten from
* the derivation itself.
*
* Just a helper function for implementing
* `queryPartialDerivationOutputMap`.
*/
virtual std::map<std::string, std::optional<StorePath>> queryStaticPartialDerivationOutputMap(
const StorePath & path);
/** /**
* Query the mapping outputName=>outputPath for the given derivation. * Query the mapping outputName=>outputPath for the given derivation.

View file

@ -81,7 +81,7 @@ TEST_JSON(DerivationTest, caFixedFlat,
"path": "/nix/store/rhcg9h16sqvlbpsa6dqm57sbr2al6nzg-drv-name-output-name" "path": "/nix/store/rhcg9h16sqvlbpsa6dqm57sbr2al6nzg-drv-name-output-name"
})", })",
(DerivationOutput::CAFixed { (DerivationOutput::CAFixed {
.ca = FixedOutputHash { .ca = {
.method = FileIngestionMethod::Flat, .method = FileIngestionMethod::Flat,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="), .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
}, },
@ -95,7 +95,7 @@ TEST_JSON(DerivationTest, caFixedNAR,
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name" "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
})", })",
(DerivationOutput::CAFixed { (DerivationOutput::CAFixed {
.ca = FixedOutputHash { .ca = {
.method = FileIngestionMethod::Recursive, .method = FileIngestionMethod::Recursive,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="), .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
}, },
@ -109,7 +109,7 @@ TEST_JSON(DynDerivationTest, caFixedText,
"path": "/nix/store/6s1zwabh956jvhv4w9xcdb5jiyanyxg1-drv-name-output-name" "path": "/nix/store/6s1zwabh956jvhv4w9xcdb5jiyanyxg1-drv-name-output-name"
})", })",
(DerivationOutput::CAFixed { (DerivationOutput::CAFixed {
.ca = TextHash { .ca = {
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="), .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
}, },
}), }),

View file

@ -1,4 +1,5 @@
#include "uds-remote-store.hh" #include "uds-remote-store.hh"
#include "worker-protocol.hh"
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -77,6 +78,15 @@ ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
} }
void UDSRemoteStore::addIndirectRoot(const Path & path)
{
auto conn(getConnection());
conn->to << WorkerProto::Op::AddIndirectRoot << path;
conn.processStderr();
readInt(conn->from);
}
static RegisterStoreImplementation<UDSRemoteStore, UDSRemoteStoreConfig> regUDSRemoteStore; static RegisterStoreImplementation<UDSRemoteStore, UDSRemoteStoreConfig> regUDSRemoteStore;
} }

View file

@ -3,13 +3,13 @@
#include "remote-store.hh" #include "remote-store.hh"
#include "remote-store-connection.hh" #include "remote-store-connection.hh"
#include "local-fs-store.hh" #include "indirect-root-store.hh"
namespace nix { namespace nix {
struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreConfig struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreConfig
{ {
UDSRemoteStoreConfig(const Store::Params & params) UDSRemoteStoreConfig(const Params & params)
: StoreConfig(params) : StoreConfig(params)
, LocalFSStoreConfig(params) , LocalFSStoreConfig(params)
, RemoteStoreConfig(params) , RemoteStoreConfig(params)
@ -21,7 +21,9 @@ struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreCon
std::string doc() override; std::string doc() override;
}; };
class UDSRemoteStore : public virtual UDSRemoteStoreConfig, public virtual LocalFSStore, public virtual RemoteStore class UDSRemoteStore : public virtual UDSRemoteStoreConfig
, public virtual IndirectRootStore
, public virtual RemoteStore
{ {
public: public:
@ -39,6 +41,16 @@ public:
void narFromPath(const StorePath & path, Sink & sink) override void narFromPath(const StorePath & path, Sink & sink) override
{ LocalFSStore::narFromPath(path, sink); } { LocalFSStore::narFromPath(path, sink); }
/**
* Implementation of `IndirectRootStore::addIndirectRoot()` which
* delegates to the remote store.
*
* The idea is that the client makes the direct symlink, so it is
* owned managed by the client's user account, and the server makes
* the indirect symlink.
*/
void addIndirectRoot(const Path & path) override;
private: private:
struct Connection : RemoteStore::Connection struct Connection : RemoteStore::Connection

View file

@ -220,10 +220,8 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs)
std::string name = *i++; std::string name = *i++;
cout << fmt("%s\n", store->printStorePath(store->makeFixedOutputPath(name, FixedOutputInfo { cout << fmt("%s\n", store->printStorePath(store->makeFixedOutputPath(name, FixedOutputInfo {
.hash = {
.method = method, .method = method,
.hash = Hash::parseAny(hash, hashAlgo), .hash = Hash::parseAny(hash, hashAlgo),
},
.references = {}, .references = {},
}))); })));
} }

View file

@ -45,10 +45,8 @@ struct CmdAddToStore : MixDryRun, StoreCommand
*store, *store,
std::move(*namePart), std::move(*namePart),
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = std::move(ingestionMethod), .method = std::move(ingestionMethod),
.hash = std::move(hash), .hash = std::move(hash),
},
.references = {}, .references = {},
}, },
narHash, narHash,

View file

@ -71,10 +71,8 @@ std::tuple<StorePath, Hash> prefetchFile(
if (expectedHash) { if (expectedHash) {
hashType = expectedHash->type; hashType = expectedHash->type;
storePath = store->makeFixedOutputPath(*name, FixedOutputInfo { storePath = store->makeFixedOutputPath(*name, FixedOutputInfo {
.hash = {
.method = ingestionMethod, .method = ingestionMethod,
.hash = *expectedHash, .hash = *expectedHash,
},
.references = {}, .references = {},
}); });
if (store->isValidPath(*storePath)) if (store->isValidPath(*storePath))
@ -127,7 +125,7 @@ std::tuple<StorePath, Hash> prefetchFile(
auto info = store->addToStoreSlow(*name, tmpFile, ingestionMethod, hashType, expectedHash); auto info = store->addToStoreSlow(*name, tmpFile, ingestionMethod, hashType, expectedHash);
storePath = info.path; storePath = info.path;
assert(info.ca); assert(info.ca);
hash = info.ca->getHash(); hash = info.ca->hash;
} }
return {storePath.value(), hash.value()}; return {storePath.value(), hash.value()};

View file

@ -222,10 +222,8 @@ struct ProfileManifest
*store, *store,
"profile", "profile",
FixedOutputInfo { FixedOutputInfo {
.hash = {
.method = FileIngestionMethod::Recursive, .method = FileIngestionMethod::Recursive,
.hash = narHash, .hash = narHash,
},
.references = { .references = {
.others = std::move(references), .others = std::move(references),
// profiles never refer to themselves // profiles never refer to themselves

View file

@ -0,0 +1,8 @@
error: dynamic attribute 'b' already defined at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:2:11
at /pwd/lang/eval-fail-dup-dynamic-attrs.nix:3:11:
2| set = { "${"" + "b"}" = 1; };
3| set = { "${"b" + ""}" = 2; };
| ^
4| }

View file

@ -0,0 +1,4 @@
{
set = { "${"" + "b"}" = 1; };
set = { "${"b" + ""}" = 2; };
}

View file

@ -0,0 +1 @@
{ set1 = { a = 1; b = 2; }; set2 = { a = 1; b = 2; }; set3 = { a = 1; b = 2; }; set4 = { a = 1; b = 2; }; }

View file

@ -0,0 +1,13 @@
{
set1 = { a = 1; };
set1 = { "${"b" + ""}" = 2; };
set2 = { "${"b" + ""}" = 2; };
set2 = { a = 1; };
set3.a = 1;
set3."${"b" + ""}" = 2;
set4."${"b" + ""}" = 2;
set4.a = 1;
}