mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-30 09:36:15 +02:00
Merge pull request #7887 from obsidiansystems/add-derivation
`nix derivation add`, `show-derivation` -> `derivation show`
This commit is contained in:
commit
54b3b6ebc6
20 changed files with 429 additions and 94 deletions
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv`
|
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
|
[store derivation]: #gloss-store-derivation
|
||||||
|
|
||||||
|
|
|
@ -45,3 +45,14 @@
|
||||||
This is useful for scripting interactions with (non-legacy-ssh) remote Nix stores.
|
This is useful for scripting interactions with (non-legacy-ssh) remote Nix stores.
|
||||||
|
|
||||||
`nix store ping` and `nix doctor` now display this information.
|
`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.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
|
#include "comparator.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ struct TextHash {
|
||||||
* Hash of the contents of the text/file.
|
* Hash of the contents of the text/file.
|
||||||
*/
|
*/
|
||||||
Hash hash;
|
Hash hash;
|
||||||
|
|
||||||
|
GENERATE_CMP(TextHash, me->hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,6 +49,8 @@ struct FixedOutputHash {
|
||||||
Hash hash;
|
Hash hash;
|
||||||
|
|
||||||
std::string printMethodAlgo() const;
|
std::string printMethodAlgo() const;
|
||||||
|
|
||||||
|
GENERATE_CMP(FixedOutputHash, me->method, me->hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -889,6 +889,67 @@ std::optional<BasicDerivation> Derivation::tryResolve(
|
||||||
return resolved;
|
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");
|
const Hash impureOutputHash = hashString(htSHA256, "impure");
|
||||||
|
|
||||||
nlohmann::json DerivationOutput::toJSON(
|
nlohmann::json DerivationOutput::toJSON(
|
||||||
|
@ -916,10 +977,79 @@ nlohmann::json DerivationOutput::toJSON(
|
||||||
return res;
|
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 Derivation::toJSON(const Store & store) const
|
||||||
{
|
{
|
||||||
nlohmann::json res = nlohmann::json::object();
|
nlohmann::json res = nlohmann::json::object();
|
||||||
|
|
||||||
|
res["name"] = name;
|
||||||
|
|
||||||
{
|
{
|
||||||
nlohmann::json & outputsObj = res["outputs"];
|
nlohmann::json & outputsObj = res["outputs"];
|
||||||
outputsObj = nlohmann::json::object();
|
outputsObj = nlohmann::json::object();
|
||||||
|
@ -950,4 +1080,43 @@ nlohmann::json Derivation::toJSON(const Store & store) const
|
||||||
return res;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "content-address.hh"
|
#include "content-address.hh"
|
||||||
#include "repair-flag.hh"
|
#include "repair-flag.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
|
#include "comparator.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
@ -24,6 +25,8 @@ class Store;
|
||||||
struct DerivationOutputInputAddressed
|
struct DerivationOutputInputAddressed
|
||||||
{
|
{
|
||||||
StorePath path;
|
StorePath path;
|
||||||
|
|
||||||
|
GENERATE_CMP(DerivationOutputInputAddressed, me->path);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +47,8 @@ struct DerivationOutputCAFixed
|
||||||
* @param outputName The name of this output.
|
* @param outputName The name of this output.
|
||||||
*/
|
*/
|
||||||
StorePath path(const Store & store, std::string_view drvName, std::string_view outputName) const;
|
StorePath path(const Store & store, std::string_view drvName, std::string_view outputName) const;
|
||||||
|
|
||||||
|
GENERATE_CMP(DerivationOutputCAFixed, me->hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,13 +67,17 @@ struct DerivationOutputCAFloating
|
||||||
* How the serialization will be hashed
|
* How the serialization will be hashed
|
||||||
*/
|
*/
|
||||||
HashType hashType;
|
HashType hashType;
|
||||||
|
|
||||||
|
GENERATE_CMP(DerivationOutputCAFloating, me->method, me->hashType);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input-addressed output which depends on a (CA) derivation whose hash
|
* Input-addressed output which depends on a (CA) derivation whose hash
|
||||||
* isn't known yet.
|
* isn't known yet.
|
||||||
*/
|
*/
|
||||||
struct DerivationOutputDeferred {};
|
struct DerivationOutputDeferred {
|
||||||
|
GENERATE_CMP(DerivationOutputDeferred);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Impure output which is moved to a content-addressed location (like
|
* Impure output which is moved to a content-addressed location (like
|
||||||
|
@ -85,6 +94,8 @@ struct DerivationOutputImpure
|
||||||
* How the serialization will be hashed
|
* How the serialization will be hashed
|
||||||
*/
|
*/
|
||||||
HashType hashType;
|
HashType hashType;
|
||||||
|
|
||||||
|
GENERATE_CMP(DerivationOutputImpure, me->method, me->hashType);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
|
@ -125,6 +136,11 @@ struct DerivationOutput : _DerivationOutputRaw
|
||||||
const Store & store,
|
const Store & store,
|
||||||
std::string_view drvName,
|
std::string_view drvName,
|
||||||
std::string_view outputName) const;
|
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;
|
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
|
||||||
|
@ -273,6 +289,15 @@ struct BasicDerivation
|
||||||
DerivationOutputsAndOptPaths outputsAndOptPaths(const Store & store) const;
|
DerivationOutputsAndOptPaths outputsAndOptPaths(const Store & store) const;
|
||||||
|
|
||||||
static std::string_view nameFromPath(const StorePath & storePath);
|
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
|
struct Derivation : BasicDerivation
|
||||||
|
@ -308,11 +333,26 @@ struct Derivation : BasicDerivation
|
||||||
Store & store,
|
Store & store,
|
||||||
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const;
|
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() = default;
|
||||||
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
||||||
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
|
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
|
||||||
|
|
||||||
nlohmann::json toJSON(const Store & store) const;
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -710,62 +710,6 @@ void canonicalisePathMetaData(const Path & path,
|
||||||
canonicalisePathMetaData(path, uidRange, inodesSeen);
|
canonicalisePathMetaData(path, uidRange, inodesSeen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivation & drv)
|
|
||||||
{
|
|
||||||
assert(drvPath.isDerivation());
|
|
||||||
std::string drvName(drvPath.name());
|
|
||||||
drvName = drvName.substr(0, drvName.size() - drvExtension.size());
|
|
||||||
|
|
||||||
auto envHasRightPath = [&](const StorePath & actual, const std::string & varName)
|
|
||||||
{
|
|
||||||
auto j = drv.env.find(varName);
|
|
||||||
if (j == drv.env.end() || parseStorePath(j->second) != actual)
|
|
||||||
throw Error("derivation '%s' has incorrect environment variable '%s', should be '%s'",
|
|
||||||
printStorePath(drvPath), varName, 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.
|
|
||||||
drv.type();
|
|
||||||
|
|
||||||
std::optional<DrvHash> hashesModulo;
|
|
||||||
for (auto & i : drv.outputs) {
|
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const DerivationOutput::InputAddressed & doia) {
|
|
||||||
if (!hashesModulo) {
|
|
||||||
// somewhat expensive so we do lazily
|
|
||||||
hashesModulo = hashDerivationModulo(*this, drv, true);
|
|
||||||
}
|
|
||||||
auto currentOutputHash = get(hashesModulo->hashes, i.first);
|
|
||||||
if (!currentOutputHash)
|
|
||||||
throw Error("derivation '%s' has unexpected output '%s' (local-store / hashesModulo) named '%s'",
|
|
||||||
printStorePath(drvPath), printStorePath(doia.path), i.first);
|
|
||||||
StorePath recomputed = makeOutputPath(i.first, *currentOutputHash, drvName);
|
|
||||||
if (doia.path != recomputed)
|
|
||||||
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
|
|
||||||
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
|
||||||
envHasRightPath(doia.path, i.first);
|
|
||||||
},
|
|
||||||
[&](const DerivationOutput::CAFixed & dof) {
|
|
||||||
StorePath path = 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
|
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
|
||||||
{
|
{
|
||||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||||
|
@ -876,7 +820,7 @@ uint64_t LocalStore::addValidPath(State & state,
|
||||||
derivations). Note that if this throws an error, then the
|
derivations). Note that if this throws an error, then the
|
||||||
DB transaction is rolled back, so the path validity
|
DB transaction is rolled back, so the path validity
|
||||||
registration above is undone. */
|
registration above is undone. */
|
||||||
if (checkOutputs) checkDerivationOutputs(info.path, drv);
|
if (checkOutputs) drv.checkInvariants(*this, info.path);
|
||||||
|
|
||||||
for (auto & i : drv.outputsAndOptPaths(*this)) {
|
for (auto & i : drv.outputsAndOptPaths(*this)) {
|
||||||
/* Floating CA derivations have indeterminate output paths until
|
/* Floating CA derivations have indeterminate output paths until
|
||||||
|
@ -1175,8 +1119,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
|
||||||
for (auto & [_, i] : infos)
|
for (auto & [_, i] : infos)
|
||||||
if (i.path.isDerivation()) {
|
if (i.path.isDerivation()) {
|
||||||
// FIXME: inefficient; we already loaded the derivation in addValidPath().
|
// FIXME: inefficient; we already loaded the derivation in addValidPath().
|
||||||
checkDerivationOutputs(i.path,
|
readInvalidDerivation(i.path).checkInvariants(*this, i.path);
|
||||||
readInvalidDerivation(i.path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do a topological sort of the paths. This will throw an
|
/* Do a topological sort of the paths. This will throw an
|
||||||
|
|
|
@ -270,8 +270,6 @@ private:
|
||||||
|
|
||||||
std::pair<Path, AutoCloseFD> createTempDirInStore();
|
std::pair<Path, AutoCloseFD> createTempDirInStore();
|
||||||
|
|
||||||
void checkDerivationOutputs(const StorePath & drvPath, const Derivation & drv);
|
|
||||||
|
|
||||||
typedef std::unordered_set<ino_t> InodeHash;
|
typedef std::unordered_set<ino_t> InodeHash;
|
||||||
|
|
||||||
InodeHash loadInodeHash();
|
InodeHash loadInodeHash();
|
||||||
|
|
|
@ -11,15 +11,29 @@ class DerivationTest : public LibStoreTest
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TEST_JSON(TYPE, NAME, STR, VAL, ...) \
|
#define TEST_JSON(NAME, STR, VAL, DRV_NAME, OUTPUT_NAME) \
|
||||||
TEST_F(DerivationTest, TYPE ## _ ## NAME ## _to_json) { \
|
TEST_F(DerivationTest, DerivationOutput_ ## NAME ## _to_json) { \
|
||||||
using nlohmann::literals::operator "" _json; \
|
using nlohmann::literals::operator "" _json; \
|
||||||
ASSERT_EQ( \
|
ASSERT_EQ( \
|
||||||
STR ## _json, \
|
STR ## _json, \
|
||||||
(TYPE { VAL }).toJSON(*store __VA_OPT__(,) __VA_ARGS__)); \
|
(DerivationOutput { VAL }).toJSON( \
|
||||||
|
*store, \
|
||||||
|
DRV_NAME, \
|
||||||
|
OUTPUT_NAME)); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
TEST_F(DerivationTest, DerivationOutput_ ## NAME ## _from_json) { \
|
||||||
|
using nlohmann::literals::operator "" _json; \
|
||||||
|
ASSERT_EQ( \
|
||||||
|
DerivationOutput { VAL }, \
|
||||||
|
DerivationOutput::fromJSON( \
|
||||||
|
*store, \
|
||||||
|
DRV_NAME, \
|
||||||
|
OUTPUT_NAME, \
|
||||||
|
STR ## _json)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_JSON(DerivationOutput, inputAddressed,
|
TEST_JSON(inputAddressed,
|
||||||
R"({
|
R"({
|
||||||
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
|
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
|
||||||
})",
|
})",
|
||||||
|
@ -28,7 +42,7 @@ TEST_JSON(DerivationOutput, inputAddressed,
|
||||||
}),
|
}),
|
||||||
"drv-name", "output-name")
|
"drv-name", "output-name")
|
||||||
|
|
||||||
TEST_JSON(DerivationOutput, caFixed,
|
TEST_JSON(caFixed,
|
||||||
R"({
|
R"({
|
||||||
"hashAlgo": "r:sha256",
|
"hashAlgo": "r:sha256",
|
||||||
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
|
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
|
||||||
|
@ -42,7 +56,7 @@ TEST_JSON(DerivationOutput, caFixed,
|
||||||
}),
|
}),
|
||||||
"drv-name", "output-name")
|
"drv-name", "output-name")
|
||||||
|
|
||||||
TEST_JSON(DerivationOutput, caFloating,
|
TEST_JSON(caFloating,
|
||||||
R"({
|
R"({
|
||||||
"hashAlgo": "r:sha256"
|
"hashAlgo": "r:sha256"
|
||||||
})",
|
})",
|
||||||
|
@ -52,12 +66,12 @@ TEST_JSON(DerivationOutput, caFloating,
|
||||||
}),
|
}),
|
||||||
"drv-name", "output-name")
|
"drv-name", "output-name")
|
||||||
|
|
||||||
TEST_JSON(DerivationOutput, deferred,
|
TEST_JSON(deferred,
|
||||||
R"({ })",
|
R"({ })",
|
||||||
DerivationOutput::Deferred { },
|
DerivationOutput::Deferred { },
|
||||||
"drv-name", "output-name")
|
"drv-name", "output-name")
|
||||||
|
|
||||||
TEST_JSON(DerivationOutput, impure,
|
TEST_JSON(impure,
|
||||||
R"({
|
R"({
|
||||||
"hashAlgo": "r:sha256",
|
"hashAlgo": "r:sha256",
|
||||||
"impure": true
|
"impure": true
|
||||||
|
@ -68,8 +82,28 @@ TEST_JSON(DerivationOutput, impure,
|
||||||
}),
|
}),
|
||||||
"drv-name", "output-name")
|
"drv-name", "output-name")
|
||||||
|
|
||||||
TEST_JSON(Derivation, impure,
|
#undef TEST_JSON
|
||||||
|
|
||||||
|
#define TEST_JSON(NAME, STR, VAL, DRV_NAME) \
|
||||||
|
TEST_F(DerivationTest, Derivation_ ## NAME ## _to_json) { \
|
||||||
|
using nlohmann::literals::operator "" _json; \
|
||||||
|
ASSERT_EQ( \
|
||||||
|
STR ## _json, \
|
||||||
|
(Derivation { VAL }).toJSON(*store)); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
TEST_F(DerivationTest, Derivation_ ## NAME ## _from_json) { \
|
||||||
|
using nlohmann::literals::operator "" _json; \
|
||||||
|
ASSERT_EQ( \
|
||||||
|
Derivation { VAL }, \
|
||||||
|
Derivation::fromJSON( \
|
||||||
|
*store, \
|
||||||
|
STR ## _json)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_JSON(simple,
|
||||||
R"({
|
R"({
|
||||||
|
"name": "my-derivation",
|
||||||
"inputSrcs": [
|
"inputSrcs": [
|
||||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
||||||
],
|
],
|
||||||
|
@ -92,6 +126,7 @@ TEST_JSON(Derivation, impure,
|
||||||
})",
|
})",
|
||||||
({
|
({
|
||||||
Derivation drv;
|
Derivation drv;
|
||||||
|
drv.name = "my-derivation";
|
||||||
drv.inputSrcs = {
|
drv.inputSrcs = {
|
||||||
store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
|
store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
|
||||||
};
|
};
|
||||||
|
@ -117,7 +152,8 @@ TEST_JSON(Derivation, impure,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
drv;
|
drv;
|
||||||
}))
|
}),
|
||||||
|
"drv-name")
|
||||||
|
|
||||||
#undef TEST_JSON
|
#undef TEST_JSON
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
#define GENERATE_ONE_CMP(COMPARATOR, MY_TYPE, FIELDS...) \
|
#define GENERATE_ONE_CMP(COMPARATOR, MY_TYPE, ...) \
|
||||||
bool operator COMPARATOR(const MY_TYPE& other) const { \
|
bool operator COMPARATOR(const MY_TYPE& other) const { \
|
||||||
const MY_TYPE* me = this; \
|
__VA_OPT__(const MY_TYPE* me = this;) \
|
||||||
auto fields1 = std::make_tuple( FIELDS ); \
|
auto fields1 = std::make_tuple( __VA_ARGS__ ); \
|
||||||
me = &other; \
|
__VA_OPT__(me = &other;) \
|
||||||
auto fields2 = std::make_tuple( FIELDS ); \
|
auto fields2 = std::make_tuple( __VA_ARGS__ ); \
|
||||||
return fields1 COMPARATOR fields2; \
|
return fields1 COMPARATOR fields2; \
|
||||||
}
|
}
|
||||||
#define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args)
|
#define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args)
|
||||||
|
|
45
src/nix/derivation-add.cc
Normal file
45
src/nix/derivation-add.cc
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// FIXME: rename to 'nix plan add' or 'nix derivation add'?
|
||||||
|
|
||||||
|
#include "command.hh"
|
||||||
|
#include "common-args.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
#include "archive.hh"
|
||||||
|
#include "derivations.hh"
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
struct CmdAddDerivation : MixDryRun, StoreCommand
|
||||||
|
{
|
||||||
|
std::string description() override
|
||||||
|
{
|
||||||
|
return "Add a store derivation";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string doc() override
|
||||||
|
{
|
||||||
|
return
|
||||||
|
#include "derivation-add.md"
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
|
void run(ref<Store> store) override
|
||||||
|
{
|
||||||
|
auto json = nlohmann::json::parse(drainFD(STDIN_FILENO));
|
||||||
|
|
||||||
|
auto drv = Derivation::fromJSON(*store, json);
|
||||||
|
|
||||||
|
auto drvPath = writeDerivation(*store, drv, NoRepair, /* read only */ dryRun);
|
||||||
|
|
||||||
|
drv.checkInvariants(*store, drvPath);
|
||||||
|
|
||||||
|
writeDerivation(*store, drv, NoRepair, dryRun);
|
||||||
|
|
||||||
|
logger->cout("%s", store->printStorePath(drvPath));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto rCmdAddDerivation = registerCommand2<CmdAddDerivation>({"derivation", "add"});
|
18
src/nix/derivation-add.md
Normal file
18
src/nix/derivation-add.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
R""(
|
||||||
|
|
||||||
|
# Description
|
||||||
|
|
||||||
|
This command reads from standard input a JSON representation of a
|
||||||
|
[store derivation] to which an [*installable*](./nix.md#installables) evaluates.
|
||||||
|
|
||||||
|
Store derivations are used internally by Nix. They are store paths with
|
||||||
|
extension `.drv` that represent the build-time dependency graph to which
|
||||||
|
a Nix expression evaluates.
|
||||||
|
|
||||||
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
|
The JSON format is documented under the [`derivation show`] command.
|
||||||
|
|
||||||
|
[`derivation show`]: ./nix3-derivation-show.md
|
||||||
|
|
||||||
|
)""
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME: integrate this with nix path-info?
|
// FIXME: integrate this with nix path-info?
|
||||||
// FIXME: rename to 'nix store show-derivation' or 'nix debug show-derivation'?
|
// FIXME: rename to 'nix store derivation show' or 'nix debug derivation show'?
|
||||||
|
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
|
@ -33,7 +33,7 @@ struct CmdShowDerivation : InstallablesCommand
|
||||||
std::string doc() override
|
std::string doc() override
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
#include "show-derivation.md"
|
#include "derivation-show.md"
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,4 +61,4 @@ struct CmdShowDerivation : InstallablesCommand
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto rCmdShowDerivation = registerCommand<CmdShowDerivation>("show-derivation");
|
static auto rCmdShowDerivation = registerCommand2<CmdShowDerivation>({"derivation", "show"});
|
|
@ -8,7 +8,7 @@ R""(
|
||||||
[store derivation]: ../../glossary.md#gloss-store-derivation
|
[store derivation]: ../../glossary.md#gloss-store-derivation
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix show-derivation nixpkgs#hello
|
# nix derivation show nixpkgs#hello
|
||||||
{
|
{
|
||||||
"/nix/store/s6rn4jz1sin56rf4qj5b5v8jxjm32hlk-hello-2.10.drv": {
|
"/nix/store/s6rn4jz1sin56rf4qj5b5v8jxjm32hlk-hello-2.10.drv": {
|
||||||
…
|
…
|
||||||
|
@ -20,14 +20,14 @@ R""(
|
||||||
NixOS system:
|
NixOS system:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix show-derivation -r /run/current-system
|
# nix derivation show -r /run/current-system
|
||||||
```
|
```
|
||||||
|
|
||||||
* Print all files fetched using `fetchurl` by Firefox's dependency
|
* Print all files fetched using `fetchurl` by Firefox's dependency
|
||||||
graph:
|
graph:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix show-derivation -r nixpkgs#firefox \
|
# nix derivation show -r nixpkgs#firefox \
|
||||||
| jq -r '.[] | select(.outputs.out.hash and .env.urls) | .env.urls' \
|
| jq -r '.[] | select(.outputs.out.hash and .env.urls) | .env.urls' \
|
||||||
| uniq | sort
|
| uniq | sort
|
||||||
```
|
```
|
||||||
|
@ -39,10 +39,11 @@ R""(
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
This command prints on standard output a JSON representation of the
|
This command prints on standard output a JSON representation of the
|
||||||
[store derivation]s to which [*installables*](./nix.md#installables) evaluate. Store derivations
|
[store derivation]s to which [*installables*](./nix.md#installables) evaluate.
|
||||||
are used internally by Nix. They are store paths with extension `.drv`
|
|
||||||
that represent the build-time dependency graph to which a Nix
|
Store derivations are used internally by Nix. They are store paths with
|
||||||
expression evaluates.
|
extension `.drv` that represent the build-time dependency graph to which
|
||||||
|
a Nix expression evaluates.
|
||||||
|
|
||||||
By default, this command only shows top-level derivations, but with
|
By default, this command only shows top-level derivations, but with
|
||||||
`--recursive`, it also shows their dependencies.
|
`--recursive`, it also shows their dependencies.
|
||||||
|
@ -51,6 +52,9 @@ The JSON output is a JSON object whose keys are the store paths of the
|
||||||
derivations, and whose values are a JSON object with the following
|
derivations, and whose values are a JSON object with the following
|
||||||
fields:
|
fields:
|
||||||
|
|
||||||
|
* `name`: The name of the derivation. This is used when calculating the
|
||||||
|
store paths of the derivation's outputs.
|
||||||
|
|
||||||
* `outputs`: Information about the output paths of the
|
* `outputs`: Information about the output paths of the
|
||||||
derivation. This is a JSON object with one member per output, where
|
derivation. This is a JSON object with one member per output, where
|
||||||
the key is the output name and the value is a JSON object with these
|
the key is the output name and the value is a JSON object with these
|
25
src/nix/derivation.cc
Normal file
25
src/nix/derivation.cc
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#include "command.hh"
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
struct CmdDerivation : virtual NixMultiCommand
|
||||||
|
{
|
||||||
|
CmdDerivation() : MultiCommand(RegisterCommand::getCommandsFor({"derivation"}))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
std::string description() override
|
||||||
|
{
|
||||||
|
return "Work with derivations, Nix's notion of a build plan.";
|
||||||
|
}
|
||||||
|
|
||||||
|
Category category() override { return catUtility; }
|
||||||
|
|
||||||
|
void run() override
|
||||||
|
{
|
||||||
|
if (!command)
|
||||||
|
throw UsageError("'nix derivation' requires a sub-command.");
|
||||||
|
command->second->run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto rCmdDerivation = registerCommand<CmdDerivation>("derivation");
|
|
@ -127,6 +127,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
|
||||||
{"optimise-store", {"store", "optimise"}},
|
{"optimise-store", {"store", "optimise"}},
|
||||||
{"ping-store", {"store", "ping"}},
|
{"ping-store", {"store", "ping"}},
|
||||||
{"sign-paths", {"store", "sign"}},
|
{"sign-paths", {"store", "sign"}},
|
||||||
|
{"show-derivation", {"derivation", "show"}},
|
||||||
{"to-base16", {"hash", "to-base16"}},
|
{"to-base16", {"hash", "to-base16"}},
|
||||||
{"to-base32", {"hash", "to-base32"}},
|
{"to-base32", {"hash", "to-base32"}},
|
||||||
{"to-base64", {"hash", "to-base64"}},
|
{"to-base64", {"hash", "to-base64"}},
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
source common.sh
|
source common.sh
|
||||||
|
|
||||||
drv=$(nix-instantiate ./content-addressed.nix -A rootCA --arg seed 1)
|
drv=$(nix-instantiate ./content-addressed.nix -A rootCA --arg seed 1)
|
||||||
nix show-derivation "$drv" --arg seed 1
|
nix derivation show "$drv" --arg seed 1
|
||||||
|
|
||||||
buildAttr () {
|
buildAttr () {
|
||||||
local derivationPath=$1
|
local derivationPath=$1
|
||||||
|
|
26
tests/ca/derivation-json.sh
Normal file
26
tests/ca/derivation-json.sh
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
source common.sh
|
||||||
|
|
||||||
|
export NIX_TESTS_CA_BY_DEFAULT=1
|
||||||
|
|
||||||
|
drvPath=$(nix-instantiate ../simple.nix)
|
||||||
|
|
||||||
|
nix derivation show $drvPath | jq .[] > $TEST_HOME/simple.json
|
||||||
|
|
||||||
|
drvPath2=$(nix derivation add < $TEST_HOME/simple.json)
|
||||||
|
|
||||||
|
[[ "$drvPath" = "$drvPath2" ]]
|
||||||
|
|
||||||
|
# Content-addressed derivations can be renamed.
|
||||||
|
jq '.name = "foo"' < $TEST_HOME/simple.json > $TEST_HOME/foo.json
|
||||||
|
drvPath3=$(nix derivation add --dry-run < $TEST_HOME/foo.json)
|
||||||
|
# With --dry-run nothing is actually written
|
||||||
|
[[ ! -e "$drvPath3" ]]
|
||||||
|
|
||||||
|
# Without --dry-run it is actually written
|
||||||
|
drvPath4=$(nix derivation add < $TEST_HOME/foo.json)
|
||||||
|
[[ "$drvPath4" = "$drvPath3" ]]
|
||||||
|
[[ -e "$drvPath3" ]]
|
||||||
|
|
||||||
|
# The modified derivation read back as JSON matches
|
||||||
|
nix derivation show $drvPath3 | jq .[] > $TEST_HOME/foo-read.json
|
||||||
|
diff $TEST_HOME/foo.json $TEST_HOME/foo-read.json
|
12
tests/derivation-json.sh
Normal file
12
tests/derivation-json.sh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
source common.sh
|
||||||
|
|
||||||
|
drvPath=$(nix-instantiate simple.nix)
|
||||||
|
|
||||||
|
nix derivation show $drvPath | jq .[] > $TEST_HOME/simple.json
|
||||||
|
|
||||||
|
drvPath2=$(nix derivation add < $TEST_HOME/simple.json)
|
||||||
|
|
||||||
|
[[ "$drvPath" = "$drvPath2" ]]
|
||||||
|
|
||||||
|
# Input addressed derivations cannot be renamed.
|
||||||
|
jq '.name = "foo"' < $TEST_HOME/simple.json | expectStderr 1 nix derivation add | grepQuiet "has incorrect output"
|
|
@ -37,8 +37,8 @@ path4=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnIm
|
||||||
(! nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>&1) | grep 'depends on impure derivation'
|
(! nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>&1) | grep 'depends on impure derivation'
|
||||||
|
|
||||||
drvPath=$(nix eval --json --file ./impure-derivations.nix impure.drvPath | jq -r .)
|
drvPath=$(nix eval --json --file ./impure-derivations.nix impure.drvPath | jq -r .)
|
||||||
[[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.out.impure") = true ]]
|
[[ $(nix derivation show $drvPath | jq ".[\"$drvPath\"].outputs.out.impure") = true ]]
|
||||||
[[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.stuff.impure") = true ]]
|
[[ $(nix derivation show $drvPath | jq ".[\"$drvPath\"].outputs.stuff.impure") = true ]]
|
||||||
|
|
||||||
# Fixed-output derivations *can* depend on impure derivations.
|
# Fixed-output derivations *can* depend on impure derivations.
|
||||||
path5=$(nix build -L --no-link --json --file ./impure-derivations.nix contentAddressed | jq -r .[].outputs.out)
|
path5=$(nix build -L --no-link --json --file ./impure-derivations.nix contentAddressed | jq -r .[].outputs.out)
|
||||||
|
|
|
@ -102,6 +102,8 @@ nix_tests = \
|
||||||
eval-store.sh \
|
eval-store.sh \
|
||||||
why-depends.sh \
|
why-depends.sh \
|
||||||
ca/why-depends.sh \
|
ca/why-depends.sh \
|
||||||
|
derivation-json.sh \
|
||||||
|
ca/derivation-json.sh \
|
||||||
import-derivation.sh \
|
import-derivation.sh \
|
||||||
ca/import-derivation.sh \
|
ca/import-derivation.sh \
|
||||||
nix_path.sh \
|
nix_path.sh \
|
||||||
|
|
Loading…
Reference in a new issue