Merge pull request #7887 from obsidiansystems/add-derivation

`nix derivation add`, `show-derivation` -> `derivation show`
This commit is contained in:
Robert Hensing 2023-04-07 15:02:35 +02:00 committed by GitHub
commit 54b3b6ebc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 429 additions and 94 deletions

View file

@ -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

View file

@ -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.

View file

@ -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);
}; };
/** /**

View file

@ -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;
}
} }

View file

@ -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);
}; };

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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
View 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
View 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
)""

View file

@ -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"});

View file

@ -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
View 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");

View file

@ -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"}},

View file

@ -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

View 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
View 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"

View file

@ -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)

View file

@ -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 \