Skip to content

Commit

Permalink
Merge pull request #7887 from obsidiansystems/add-derivation
Browse files Browse the repository at this point in the history
`nix derivation add`, `show-derivation` -> `derivation show`
  • Loading branch information
roberth authored Apr 7, 2023
2 parents 81dfc2b + 9d11058 commit 54b3b6e
Show file tree
Hide file tree
Showing 20 changed files with 429 additions and 94 deletions.
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

0 comments on commit 54b3b6e

Please sign in to comment.