Allow depending on content-addressed derivations#3776
Allow depending on content-addressed derivations#3776thufschmitt wants to merge 19 commits intoNixOS:masterfrom
Conversation
|
Between the first and #3740, This does seem like a bunch code doubling down on the "real and fake outputs" approach, which I do worry will be a source of bugs. Now that derivation outputs are fully parsed, I'd like to exploit that, going from struct DerivationOutput
{
StorePath path;
std::optional<FixedOutputHash> hash; /* hash used for expected hash computation */
};to struct DerivationOutputLegacy {
// StorePath path;
// regnat pointed out that when we depend on a ca-derivation,
// we cannot calculate downstream paths until the upstream one is built.
std::optional<StorePath> path;
};
struct DerivationOutputCAFixed {
/* hash used for expected hash computation */
FixedOutputHash hash;
// no need to store output store path, as it can be computed on the fly.
};
struct DerivationOutputCAFloating {
/* hash used for ca path computation */
HashType hashType;
};
typedef std::variant<
DerivationOutputLegacy,
DerivationOutputCAFixed,
DerivationOutputCAFloating
> DerivationOutput;
/* accessors to make it easier to handle multiple cases at once. */
std::optional<StorePath> outputPath(DerivationOutput) { ... }
std::optional<HashType> outputHashAlgo(DerivationOutput) { ... }I really do think we can make that change, fix all the type errors "on autopilot', and be quite close to a working version. As a first, preparatory step, we can do: struct DerivationOutputLegacy {
StorePath path;
};
struct DerivationOutputCAFixed {
/* hash used for expected hash computation */
FixedOutputHash hash;
// no need to store output store path, as it can be computed on the fly.
};
typedef std::variant<
DerivationOutputLegacy,
DerivationOutputCAFixed
> DerivationOutput;
/* accessors to make it easier to handle multiple cases at once. */
StorePath outputPath(DerivationOutput) { ... }I think we'll also need to make derivations store their own name. which is exactly isomorphic to today, to get the churn out of the way, and then add new new variant in a separate PR. |
|
https://github.com/NixOS/nix/pull/3754/files should also help. Every time in that PR I do something like FixedOutputInfo {
{ .method = ..., .hash = ... },
{} // empty references
}the |
Move the logic from the `nix make-content-addressed` nix command to a new method in the `Store` class
This is required so that we can call LocalStore::makeContentAddressed on paths that haven't been registered
Very limited atm: - Only `nix-build` works properly (`nix-store`, `nix build`, etc.. will print and create a symlink to a non-existent output path) - It isn't possible to depend on a content-addressed derivation
d32357a to
75a06ee
Compare
Otherwise `resolveDerivation` changes the name of the derivation (and as a consequence its hash)
Otherwise substituted or short-circuited paths are not properly registered
75a06ee to
bab7345
Compare
src/libstore/build.cc
Outdated
| bool isModified = resolveDerivation(worker.store, *(dynamic_cast<Derivation *>(drv.get()))); | ||
| // Define the actual outputs of the build | ||
| for (auto & i: drv->outputs) | ||
| actualOutputs.emplace(i.first, i.second.path); |
There was a problem hiding this comment.
It's confusing to me that here actualOutputs is initialized to what appears to be the non-actual outputs.
There was a problem hiding this comment.
I think this is intentional, but this is the sort of foot-gun I want to avoid by making DerivationOutput use variants in my comment above.
src/libstore/build.cc
Outdated
| // Replace the output path by the new CA one | ||
| // FIXME: Why can't this be done in one step? | ||
| actualOutputs.erase(i.first); | ||
| actualOutputs.emplace(i.first, info.path); |
There was a problem hiding this comment.
This can be replaced by insert_or_assign().
| mcRunningBuilds.reset(); | ||
|
|
||
| if (result.success()) { | ||
| registerOutputMappings(); |
There was a problem hiding this comment.
Why is this done here rather than in registerOutputs()? To me done() is a function that is only intended to interact with Worker to set error states and trigger dependent goals, so it shouldn't have side-effects like registering stuff in the database.
There was a problem hiding this comment.
There might be a better place for this indeed.
I didn't put it in registerOutputs because we want it to be called even when no build took place (either because we substituted the output or because we've short-cutted the build after resolving the derivation).
| void LocalFSStore::narFromPath(const StorePath & path, Sink & sink) | ||
| { | ||
| if (!isValidPath(path)) | ||
| throw Error("path '%s' is not valid", printStorePath(path)); |
There was a problem hiding this comment.
I think this check should stay since narFromPath is supposed to apply to valid paths only. For arbitrary paths you should use dumpPath().
There was a problem hiding this comment.
If the issue is that build.cc is calling Store::makeContentAddressed() on an invalid path, then maybe that function should take a separate argument for the path (so it can use dumpPath()).
src/libstore/local-store.cc
Outdated
| } | ||
| } | ||
|
|
||
| void LocalStore::linkDeriverToPath(const StorePath deriver, const string outputName, const StorePath output) |
There was a problem hiding this comment.
This should be const StorePath & deriver etc. Otherwise we're passing by value which is less efficient.
There was a problem hiding this comment.
Oh forgot these, thanks
src/libstore/rewrite-derivation.hh
Outdated
| * (as given by `queryDerivationOutputMap`) | ||
| * **if this one differs from the one written in the derivation** | ||
| */ | ||
| bool resolveDerivation(Store & store, Derivation & drv); |
There was a problem hiding this comment.
What's the boolean return value?
There was a problem hiding this comment.
Oh my bad I forgot to document it.
It serves to indicate whether the resolving actually modified the derivation or not (to know whether to retry the substitution). But I'm not very happy with it, so I'd be happy if you have any suggestion on improving this
src/libstore/rewrite-derivation.cc
Outdated
| auto outputEnvVar = drv.env.find(i.first); | ||
| if (outputEnvVar != drv.env.end()) | ||
| outputEnvVar->second = store.printStorePath(outPath); | ||
| debug(format("Rewrote output %1% to %2%") |
There was a problem hiding this comment.
format is obsolete, please do debug("rewrite output '%s' to '%s'", arg1, arg2).
src/libstore/rewrite-derivation.cc
Outdated
| for (auto & i : drv.outputs) { | ||
| // XXX: There's certainly a better and less error-prone way | ||
| // of getting the name than to look it up in the drv environment | ||
| string name = ParsedDerivation(StorePath::dummy, drv).getStringAttr("name").value_or(""); |
There was a problem hiding this comment.
This can be lifted out of the for loop.
| const StringMap & extraRewrites | ||
| ) | ||
| { | ||
| bool hasSelfReference = info.references.find(info.path) != info.references.end(); |
There was a problem hiding this comment.
=> bool hasSelfReference = info.references.count(info.path).
src/libstore/store-api.hh
Outdated
| /* Create a new path similar to the one referred to by `info`, but that's | ||
| * content-addressed. | ||
| * | ||
| * This doesn't alter the original path |
There was a problem hiding this comment.
This should document whether info needs to denote a valid path.
src/libstore/store-api.hh
Outdated
| std::optional<StorePath> deriver; | ||
| std::optional<string> outputname; |
There was a problem hiding this comment.
Is the intent that we can have outputname whenever we have deriver, except for backwards compat with old infos?
There was a problem hiding this comment.
BTW it looks like this field is not actually used anywhere.
There was a problem hiding this comment.
Indeed, I've added it for an experiment and forgot to remove it. This field doesn't really make any sense (even the deriver field should probably be deprecated as a store path can now have several derivers)
There was a problem hiding this comment.
Yeah once we upload the trust mapping, we'll probably want to key it by the derivation not the output path, which also helps different signatures sign for different derivers.
src/libstore/store-api.hh
Outdated
| { | ||
| StorePath path; | ||
| std::optional<StorePath> deriver; | ||
| std::optional<string> outputname; |
There was a problem hiding this comment.
It seems incorrect to have an outputName field in ValidPathInfo. A CA store path can be produced by differently named output of different derivations (e.g. the "foo" output of derivation A could produce the same contents as the "bar" output of derivation B).
| state->stmtAddDerivationOutput.use() | ||
| (queryValidPathId(*state, deriver)) | ||
| (outputName) | ||
| (printStorePath(output)) |
There was a problem hiding this comment.
output is an "actual" output here, right? In that case, doesn't this cause confusion wrt the meaning of the DerivationOutputs table?
Replace it by a more straightforward `caOutputs` map that just contains the outputs that are actually overriden by the CA process
This reverts commit 3854aec.
narFromPath expects a valid path as input (and making it more general would be complicated as it's a general method of the `Store` class), so we just inline the bit of `makeContentAddressed` that we need (which is bigger than what I though. Maybe I'm trying to retro-fit functional idioms for which cpp isn't designed)
Builds on top of #3740, and extends it to allow depending on ca-derivations, with a proper cutoff in case two different derivations in the build chain produce the same output (see the added test)