Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nix derivation add, show-derivation -> derivation show #7887

Merged
merged 7 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/manual/src/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv`

See [`nix show-derivation`](./command-ref/new-cli/nix3-show-derivation.md) (experimental) for displaying the contents of store derivations.
See [`nix derivation show`](./command-ref/new-cli/nix3-derivation-show.md) (experimental) for displaying the contents of store derivations.

[store derivation]: #gloss-store-derivation

Expand Down
11 changes: 11 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,14 @@
This is useful for scripting interactions with (non-legacy-ssh) remote Nix stores.

`nix store ping` and `nix doctor` now display this information.

* A new command `nix derivation add` is created, to allow adding derivations to the store without involving the Nix language.
It exists to round out our collection of basic utility/plumbing commands, and allow for a low barrier-to-entry way of experimenting with alternative front-ends to the Nix Store.
It uses the same JSON layout as `nix show-derivation`, and is its inverse.

* `nix show-derivation` has been renamed to `nix derivation show`.
This matches `nix derivation add`, and avoids bloating the top-level namespace.
The old name is still kept as an alias for compatibility, however.

* The `nix derivation {add,show}` JSON format now includes the derivation name as a top-level field.
This is useful in general, but especially necessary for the `add` direction, as otherwise we would need to pass in the name out of band for certain cases.
5 changes: 5 additions & 0 deletions src/libstore/content-address.hh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <variant>
#include "hash.hh"
#include "comparator.hh"

namespace nix {

Expand Down Expand Up @@ -30,6 +31,8 @@ struct TextHash {
* Hash of the contents of the text/file.
*/
Hash hash;

GENERATE_CMP(TextHash, me->hash);
};

/**
Expand All @@ -46,6 +49,8 @@ struct FixedOutputHash {
Hash hash;

std::string printMethodAlgo() const;

GENERATE_CMP(FixedOutputHash, me->method, me->hash);
};

/**
Expand Down
169 changes: 169 additions & 0 deletions src/libstore/derivations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,67 @@ std::optional<BasicDerivation> Derivation::tryResolve(
return resolved;
}


void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
{
assert(drvPath.isDerivation());
std::string drvName(drvPath.name());
drvName = drvName.substr(0, drvName.size() - drvExtension.size());

if (drvName != name) {
throw Error("Derivation '%s' has name '%s' which does not match its path", store.printStorePath(drvPath), name);
}

auto envHasRightPath = [&](const StorePath & actual, const std::string & varName)
{
auto j = env.find(varName);
if (j == env.end() || store.parseStorePath(j->second) != actual)
throw Error("derivation '%s' has incorrect environment variable '%s', should be '%s'",
store.printStorePath(drvPath), varName, store.printStorePath(actual));
};


// Don't need the answer, but do this anyways to assert is proper
// combination. The code below is more general and naturally allows
// combinations that are currently prohibited.
type();

std::optional<DrvHash> hashesModulo;
for (auto & i : outputs) {
std::visit(overloaded {
[&](const DerivationOutput::InputAddressed & doia) {
if (!hashesModulo) {
// somewhat expensive so we do lazily
hashesModulo = hashDerivationModulo(store, *this, true);
}
auto currentOutputHash = get(hashesModulo->hashes, i.first);
if (!currentOutputHash)
throw Error("derivation '%s' has unexpected output '%s' (local-store / hashesModulo) named '%s'",
store.printStorePath(drvPath), store.printStorePath(doia.path), i.first);
StorePath recomputed = store.makeOutputPath(i.first, *currentOutputHash, drvName);
if (doia.path != recomputed)
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
store.printStorePath(drvPath), store.printStorePath(doia.path), store.printStorePath(recomputed));
envHasRightPath(doia.path, i.first);
},
[&](const DerivationOutput::CAFixed & dof) {
StorePath path = store.makeFixedOutputPath(dof.hash.method, dof.hash.hash, drvName);
envHasRightPath(path, i.first);
},
[&](const DerivationOutput::CAFloating &) {
/* Nothing to check */
},
[&](const DerivationOutput::Deferred &) {
/* Nothing to check */
},
[&](const DerivationOutput::Impure &) {
/* Nothing to check */
},
}, i.second.raw());
}
}


const Hash impureOutputHash = hashString(htSHA256, "impure");

nlohmann::json DerivationOutput::toJSON(
Expand Down Expand Up @@ -916,10 +977,79 @@ nlohmann::json DerivationOutput::toJSON(
return res;
}


DerivationOutput DerivationOutput::fromJSON(
const Store & store, std::string_view drvName, std::string_view outputName,
const nlohmann::json & _json)
{
std::set<std::string_view> keys;
auto json = (std::map<std::string, nlohmann::json>) _json;

for (const auto & [key, _] : json)
keys.insert(key);

auto methodAlgo = [&]() -> std::pair<FileIngestionMethod, HashType> {
std::string hashAlgo = json["hashAlgo"];
auto method = FileIngestionMethod::Flat;
if (hashAlgo.substr(0, 2) == "r:") {
method = FileIngestionMethod::Recursive;
hashAlgo = hashAlgo.substr(2);
}
auto hashType = parseHashType(hashAlgo);
return { method, hashType };
};

if (keys == (std::set<std::string_view> { "path" })) {
return DerivationOutput::InputAddressed {
.path = store.parseStorePath((std::string) json["path"]),
};
}

else if (keys == (std::set<std::string_view> { "path", "hashAlgo", "hash" })) {
auto [method, hashType] = methodAlgo();
auto dof = DerivationOutput::CAFixed {
.hash = {
.method = method,
.hash = Hash::parseNonSRIUnprefixed((std::string) json["hash"], hashType),
},
};
if (dof.path(store, drvName, outputName) != store.parseStorePath((std::string) json["path"]))
throw Error("Path doesn't match derivation output");
return dof;
}

else if (keys == (std::set<std::string_view> { "hashAlgo" })) {
auto [method, hashType] = methodAlgo();
return DerivationOutput::CAFloating {
.method = method,
.hashType = hashType,
};
}

else if (keys == (std::set<std::string_view> { })) {
return DerivationOutput::Deferred {};
}

else if (keys == (std::set<std::string_view> { "hashAlgo", "impure" })) {
auto [method, hashType] = methodAlgo();
return DerivationOutput::Impure {
.method = method,
.hashType = hashType,
};
}

else {
throw Error("invalid JSON for derivation output");
}
}


nlohmann::json Derivation::toJSON(const Store & store) const
{
nlohmann::json res = nlohmann::json::object();

res["name"] = name;

{
nlohmann::json & outputsObj = res["outputs"];
outputsObj = nlohmann::json::object();
Expand Down Expand Up @@ -950,4 +1080,43 @@ nlohmann::json Derivation::toJSON(const Store & store) const
return res;
}


Derivation Derivation::fromJSON(
const Store & store,
const nlohmann::json & json)
{
Derivation res;

res.name = json["name"];

{
auto & outputsObj = json["outputs"];
for (auto & [outputName, output] : outputsObj.items()) {
res.outputs.insert_or_assign(
outputName,
DerivationOutput::fromJSON(store, res.name, outputName, output));
}
}

{
auto & inputsList = json["inputSrcs"];
for (auto & input : inputsList)
res.inputSrcs.insert(store.parseStorePath(static_cast<const std::string &>(input)));
}

{
auto & inputDrvsObj = json["inputDrvs"];
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items())
res.inputDrvs[store.parseStorePath(inputDrvPath)] =
static_cast<const StringSet &>(inputOutputs);
}

res.platform = json["system"];
res.builder = json["builder"];
res.args = json["args"];
res.env = json["env"];

return res;
}

}
42 changes: 41 additions & 1 deletion src/libstore/derivations.hh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "content-address.hh"
#include "repair-flag.hh"
#include "sync.hh"
#include "comparator.hh"

#include <map>
#include <variant>
Expand All @@ -24,6 +25,8 @@ class Store;
struct DerivationOutputInputAddressed
{
StorePath path;

GENERATE_CMP(DerivationOutputInputAddressed, me->path);
};

/**
Expand All @@ -44,6 +47,8 @@ struct DerivationOutputCAFixed
* @param outputName The name of this output.
*/
StorePath path(const Store & store, std::string_view drvName, std::string_view outputName) const;

GENERATE_CMP(DerivationOutputCAFixed, me->hash);
};

/**
Expand All @@ -62,13 +67,17 @@ struct DerivationOutputCAFloating
* How the serialization will be hashed
*/
HashType hashType;

GENERATE_CMP(DerivationOutputCAFloating, me->method, me->hashType);
};

/**
* Input-addressed output which depends on a (CA) derivation whose hash
* isn't known yet.
*/
struct DerivationOutputDeferred {};
struct DerivationOutputDeferred {
GENERATE_CMP(DerivationOutputDeferred);
};

/**
* Impure output which is moved to a content-addressed location (like
Expand All @@ -85,6 +94,8 @@ struct DerivationOutputImpure
* How the serialization will be hashed
*/
HashType hashType;

GENERATE_CMP(DerivationOutputImpure, me->method, me->hashType);
};

typedef std::variant<
Expand Down Expand Up @@ -125,6 +136,11 @@ struct DerivationOutput : _DerivationOutputRaw
const Store & store,
std::string_view drvName,
std::string_view outputName) const;
static DerivationOutput fromJSON(
const Store & store,
std::string_view drvName,
std::string_view outputName,
const nlohmann::json & json);
};

typedef std::map<std::string, DerivationOutput> DerivationOutputs;
Expand Down Expand Up @@ -273,6 +289,15 @@ struct BasicDerivation
DerivationOutputsAndOptPaths outputsAndOptPaths(const Store & store) const;

static std::string_view nameFromPath(const StorePath & storePath);

GENERATE_CMP(BasicDerivation,
me->outputs,
me->inputSrcs,
me->platform,
me->builder,
me->args,
me->env,
me->name);
};

struct Derivation : BasicDerivation
Expand Down Expand Up @@ -308,11 +333,26 @@ struct Derivation : BasicDerivation
Store & store,
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const;

/* Check that the derivation is valid and does not present any
illegal states.

This is mainly a matter of checking the outputs, where our C++
representation supports all sorts of combinations we do not yet
allow. */
void checkInvariants(Store & store, const StorePath & drvPath) const;

Derivation() = default;
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }

nlohmann::json toJSON(const Store & store) const;
static Derivation fromJSON(
const Store & store,
const nlohmann::json & json);

GENERATE_CMP(Derivation,
static_cast<const BasicDerivation &>(*me),
me->inputDrvs);
};


Expand Down
Loading