diff --git a/src/libstore-tests/derivation-advanced-attrs.cc b/src/libstore-tests/derivation-advanced-attrs.cc index 9c13bf048..02bc8fa24 100644 --- a/src/libstore-tests/derivation-advanced-attrs.cc +++ b/src/libstore-tests/derivation-advanced-attrs.cc @@ -14,7 +14,7 @@ namespace nix { -using nlohmann::json; +using namespace nlohmann; class DerivationAdvancedAttrsTest : public CharacterizationTest, public LibStoreTest { @@ -51,44 +51,44 @@ using BothFixtures = ::testing::TypesreadTest(NAME ".json", [&](const auto & encoded_) { \ - auto encoded = json::parse(encoded_); \ - /* Use DRV file instead of C++ literal as source of truth. */ \ - auto aterm = readFile(this->goldenMaster(NAME ".drv")); \ - auto expected = parseDerivation(*this->store, std::move(aterm), NAME, this->mockXpSettings); \ - Derivation got = Derivation::fromJSON(encoded, this->mockXpSettings); \ - EXPECT_EQ(got, expected); \ - }); \ - } \ - \ - TYPED_TEST(DerivationAdvancedAttrsBothTest, Derivation_##STEM##_to_json) \ - { \ - this->writeTest( \ - NAME ".json", \ - [&]() -> json { \ - /* Use DRV file instead of C++ literal as source of truth. */ \ - auto aterm = readFile(this->goldenMaster(NAME ".drv")); \ - return parseDerivation(*this->store, std::move(aterm), NAME, this->mockXpSettings).toJSON(); \ - }, \ - [](const auto & file) { return json::parse(readFile(file)); }, \ - [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ - } \ - \ - TYPED_TEST(DerivationAdvancedAttrsBothTest, Derivation_##STEM##_from_aterm) \ - { \ - this->readTest(NAME ".drv", [&](auto encoded) { \ - /* Use JSON file instead of C++ literal as source of truth. */ \ - auto json = json::parse(readFile(this->goldenMaster(NAME ".json"))); \ - auto expected = Derivation::fromJSON(json, this->mockXpSettings); \ - auto got = parseDerivation(*this->store, std::move(encoded), NAME, this->mockXpSettings); \ - EXPECT_EQ(got.toJSON(), expected.toJSON()); \ - EXPECT_EQ(got, expected); \ - }); \ - } \ - \ +#define TEST_ATERM_JSON(STEM, NAME) \ + TYPED_TEST(DerivationAdvancedAttrsBothTest, Derivation_##STEM##_from_json) \ + { \ + this->readTest(NAME ".json", [&](const auto & encoded_) { \ + auto encoded = json::parse(encoded_); \ + /* Use DRV file instead of C++ literal as source of truth. */ \ + auto aterm = readFile(this->goldenMaster(NAME ".drv")); \ + auto expected = parseDerivation(*this->store, std::move(aterm), NAME, this->mockXpSettings); \ + Derivation got = adl_serializer::from_json(encoded, this->mockXpSettings); \ + EXPECT_EQ(got, expected); \ + }); \ + } \ + \ + TYPED_TEST(DerivationAdvancedAttrsBothTest, Derivation_##STEM##_to_json) \ + { \ + this->writeTest( \ + NAME ".json", \ + [&]() -> json { \ + /* Use DRV file instead of C++ literal as source of truth. */ \ + auto aterm = readFile(this->goldenMaster(NAME ".drv")); \ + return parseDerivation(*this->store, std::move(aterm), NAME, this->mockXpSettings); \ + }, \ + [](const auto & file) { return json::parse(readFile(file)); }, \ + [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ + } \ + \ + TYPED_TEST(DerivationAdvancedAttrsBothTest, Derivation_##STEM##_from_aterm) \ + { \ + this->readTest(NAME ".drv", [&](auto encoded) { \ + /* Use JSON file instead of C++ literal as source of truth. */ \ + auto j = json::parse(readFile(this->goldenMaster(NAME ".json"))); \ + auto expected = adl_serializer::from_json(j, this->mockXpSettings); \ + auto got = parseDerivation(*this->store, std::move(encoded), NAME, this->mockXpSettings); \ + EXPECT_EQ(static_cast(got), static_cast(expected)); \ + EXPECT_EQ(got, expected); \ + }); \ + } \ + \ /* No corresponding write test, because we need to read the drv to write the json file */ TEST_ATERM_JSON(advancedAttributes, "advanced-attributes-defaults"); diff --git a/src/libstore-tests/derivation.cc b/src/libstore-tests/derivation.cc index 65a5d011d..6b33e5442 100644 --- a/src/libstore-tests/derivation.cc +++ b/src/libstore-tests/derivation.cc @@ -66,23 +66,17 @@ TEST_F(DynDerivationTest, BadATerm_oldVersionDynDeps) FormatError); } -#define MAKE_OUTPUT_JSON_TEST_P(FIXTURE) \ - TEST_P(FIXTURE, from_json) \ - { \ - const auto & [name, expected] = GetParam(); \ - /* Don't use readJsonTest because we want to check experimental \ - features. */ \ - readTest(Path{"output-"} + name + ".json", [&](const auto & encoded_) { \ - json j = json::parse(encoded_); \ - DerivationOutput got = DerivationOutput::fromJSON(j, mockXpSettings); \ - ASSERT_EQ(got, expected); \ - }); \ - } \ - \ - TEST_P(FIXTURE, to_json) \ - { \ - const auto & [name, value] = GetParam(); \ - writeJsonTest("output-" + name, value); \ +#define MAKE_OUTPUT_JSON_TEST_P(FIXTURE) \ + TEST_P(FIXTURE, from_json) \ + { \ + const auto & [name, expected] = GetParam(); \ + readJsonTest(Path{"output-"} + name, expected, mockXpSettings); \ + } \ + \ + TEST_P(FIXTURE, to_json) \ + { \ + const auto & [name, value] = GetParam(); \ + writeJsonTest("output-" + name, value); \ } struct DerivationOutputJsonTest : DerivationTest, @@ -193,13 +187,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(FIXTURE, from_json) \ { \ const auto & drv = GetParam(); \ - /* Don't use readJsonTest because we want to check experimental \ - features. */ \ - readTest(drv.name + ".json", [&](const auto & encoded_) { \ - auto encoded = json::parse(encoded_); \ - Derivation got = Derivation::fromJSON(encoded, mockXpSettings); \ - ASSERT_EQ(got, drv); \ - }); \ + readJsonTest(drv.name, drv, mockXpSettings); \ } \ \ TEST_P(FIXTURE, to_json) \ @@ -213,7 +201,8 @@ INSTANTIATE_TEST_SUITE_P( const auto & drv = GetParam(); \ readTest(drv.name + ".drv", [&](auto encoded) { \ auto got = parseDerivation(*store, std::move(encoded), drv.name, mockXpSettings); \ - ASSERT_EQ(got.toJSON(), drv.toJSON()); \ + using nlohmann::json; \ + ASSERT_EQ(static_cast(got), static_cast(drv)); \ ASSERT_EQ(got, drv); \ }); \ } \ diff --git a/src/libstore-tests/derived-path.cc b/src/libstore-tests/derived-path.cc index 6e7648f25..70e789c0c 100644 --- a/src/libstore-tests/derived-path.cc +++ b/src/libstore-tests/derived-path.cc @@ -3,13 +3,13 @@ #include #include -#include "nix/util/tests/characterization.hh" +#include "nix/util/tests/json-characterization.hh" #include "nix/store/tests/derived-path.hh" #include "nix/store/tests/libstore.hh" namespace nix { -class DerivedPathTest : public CharacterizationTest, public LibStoreTest +class DerivedPathTest : public virtual CharacterizationTest, public LibStoreTest { std::filesystem::path unitTestData = getUnitTestData() / "derived-path"; @@ -123,25 +123,51 @@ RC_GTEST_FIXTURE_PROP(DerivedPathTest, prop_round_rip, (const DerivedPath & o)) using nlohmann::json; -#define TEST_JSON(TYPE, NAME, VAL) \ - static const TYPE NAME = VAL; \ - \ - TEST_F(DerivedPathTest, NAME##_from_json) \ - { \ - readTest(#NAME ".json", [&](const auto & encoded_) { \ - auto encoded = json::parse(encoded_); \ - TYPE got = static_cast(encoded); \ - ASSERT_EQ(got, NAME); \ - }); \ - } \ - \ - TEST_F(DerivedPathTest, NAME##_to_json) \ - { \ - writeTest( \ - #NAME ".json", \ - [&]() -> json { return static_cast(NAME); }, \ - [](const auto & file) { return json::parse(readFile(file)); }, \ - [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \ +struct SingleDerivedPathJsonTest : DerivedPathTest, + JsonCharacterizationTest, + ::testing::WithParamInterface +{}; + +struct DerivedPathJsonTest : DerivedPathTest, + JsonCharacterizationTest, + ::testing::WithParamInterface +{}; + +#define TEST_JSON(TYPE, NAME, VAL) \ + static const TYPE NAME = VAL; \ + \ + TEST_F(TYPE##JsonTest, NAME##_from_json) \ + { \ + readJsonTest(#NAME, NAME); \ + } \ + \ + TEST_F(TYPE##JsonTest, NAME##_to_json) \ + { \ + writeJsonTest(#NAME, NAME); \ + } + +#define TEST_JSON_XP_DYN(TYPE, NAME, VAL) \ + static const TYPE NAME = VAL; \ + \ + TEST_F(TYPE##JsonTest, NAME##_from_json_throws_without_xp) \ + { \ + std::optional ret; \ + readTest(#NAME ".json", [&](const auto & encoded_) { ret = json::parse(encoded_); }); \ + if (ret) { \ + EXPECT_THROW(nlohmann::adl_serializer::from_json(*ret), MissingExperimentalFeature); \ + } \ + } \ + \ + TEST_F(TYPE##JsonTest, NAME##_from_json) \ + { \ + ExperimentalFeatureSettings xpSettings; \ + xpSettings.set("experimental-features", "dynamic-derivations"); \ + readJsonTest(#NAME, NAME, xpSettings); \ + } \ + \ + TEST_F(TYPE##JsonTest, NAME##_to_json) \ + { \ + writeJsonTest(#NAME, NAME); \ } TEST_JSON( @@ -156,7 +182,7 @@ TEST_JSON( .output = "bar", })); -TEST_JSON( +TEST_JSON_XP_DYN( SingleDerivedPath, single_built_built, (SingleDerivedPath::Built{ @@ -179,7 +205,7 @@ TEST_JSON( .outputs = OutputsSpec::Names{"bar", "baz"}, })); -TEST_JSON( +TEST_JSON_XP_DYN( DerivedPath, multi_built_built, (DerivedPath::Built{ @@ -191,7 +217,7 @@ TEST_JSON( .outputs = OutputsSpec::Names{"baz", "quux"}, })); -TEST_JSON( +TEST_JSON_XP_DYN( DerivedPath, multi_built_built_wildcard, (DerivedPath::Built{ diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index 24dd61807..f44bf3e70 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -1261,9 +1261,15 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const const Hash impureOutputHash = hashString(HashAlgorithm::SHA256, "impure"); -nlohmann::json DerivationOutput::toJSON() const +} // namespace nix + +namespace nlohmann { + +using namespace nix; + +void adl_serializer::to_json(json & res, const DerivationOutput & o) { - nlohmann::json res = nlohmann::json::object(); + res = nlohmann::json::object(); std::visit( overloaded{ [&](const DerivationOutput::InputAddressed & doi) { res["path"] = doi.path; }, @@ -1289,12 +1295,11 @@ nlohmann::json DerivationOutput::toJSON() const res["impure"] = true; }, }, - raw); - return res; + o.raw); } DerivationOutput -DerivationOutput::fromJSON(const nlohmann::json & _json, const ExperimentalFeatureSettings & xpSettings) +adl_serializer::from_json(const json & _json, const ExperimentalFeatureSettings & xpSettings) { std::set keys; auto & json = getObject(_json); @@ -1362,18 +1367,18 @@ DerivationOutput::fromJSON(const nlohmann::json & _json, const ExperimentalFeatu } } -nlohmann::json Derivation::toJSON() const +void adl_serializer::to_json(json & res, const Derivation & d) { - nlohmann::json res = nlohmann::json::object(); + res = nlohmann::json::object(); - res["name"] = name; + res["name"] = d.name; res["version"] = 3; { nlohmann::json & outputsObj = res["outputs"]; outputsObj = nlohmann::json::object(); - for (auto & [outputName, output] : outputs) { + for (auto & [outputName, output] : d.outputs) { outputsObj[outputName] = output; } } @@ -1381,7 +1386,7 @@ nlohmann::json Derivation::toJSON() const { auto & inputsList = res["inputSrcs"]; inputsList = nlohmann::json ::array(); - for (auto & input : inputSrcs) + for (auto & input : d.inputSrcs) inputsList.emplace_back(input); } @@ -1401,24 +1406,22 @@ nlohmann::json Derivation::toJSON() const { auto & inputDrvsObj = res["inputDrvs"]; inputDrvsObj = nlohmann::json::object(); - for (auto & [inputDrv, inputNode] : inputDrvs.map) { + for (auto & [inputDrv, inputNode] : d.inputDrvs.map) { inputDrvsObj[inputDrv.to_string()] = doInput(inputNode); } } } - res["system"] = platform; - res["builder"] = builder; - res["args"] = args; - res["env"] = env; + res["system"] = d.platform; + res["builder"] = d.builder; + res["args"] = d.args; + res["env"] = d.env; - if (structuredAttrs) - res["structuredAttrs"] = structuredAttrs->structuredAttrs; - - return res; + if (d.structuredAttrs) + res["structuredAttrs"] = d.structuredAttrs->structuredAttrs; } -Derivation Derivation::fromJSON(const nlohmann::json & _json, const ExperimentalFeatureSettings & xpSettings) +Derivation adl_serializer::from_json(const json & _json, const ExperimentalFeatureSettings & xpSettings) { using nlohmann::detail::value_t; @@ -1434,7 +1437,7 @@ Derivation Derivation::fromJSON(const nlohmann::json & _json, const Experimental try { auto outputs = getObject(valueAt(json, "outputs")); for (auto & [outputName, output] : outputs) { - res.outputs.insert_or_assign(outputName, DerivationOutput::fromJSON(output, xpSettings)); + res.outputs.insert_or_assign(outputName, adl_serializer::from_json(output, xpSettings)); } } catch (Error & e) { e.addTrace({}, "while reading key 'outputs'"); @@ -1490,30 +1493,4 @@ Derivation Derivation::fromJSON(const nlohmann::json & _json, const Experimental return res; } -} // namespace nix - -namespace nlohmann { - -using namespace nix; - -DerivationOutput adl_serializer::from_json(const json & json) -{ - return DerivationOutput::fromJSON(json); -} - -void adl_serializer::to_json(json & json, const DerivationOutput & c) -{ - json = c.toJSON(); -} - -Derivation adl_serializer::from_json(const json & json) -{ - return Derivation::fromJSON(json); -} - -void adl_serializer::to_json(json & json, const Derivation & c) -{ - json = c.toJSON(); -} - } // namespace nlohmann diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 8d606cb41..251e11251 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -252,20 +252,26 @@ void adl_serializer::to_json(json & json, const DerivedPath: }; } -SingleDerivedPath::Built adl_serializer::from_json(const json & json0) +SingleDerivedPath::Built +adl_serializer::from_json(const json & json0, const ExperimentalFeatureSettings & xpSettings) { auto & json = getObject(json0); + auto drvPath = make_ref(static_cast(valueAt(json, "drvPath"))); + drvRequireExperiment(*drvPath, xpSettings); return { - .drvPath = make_ref(static_cast(valueAt(json, "drvPath"))), + .drvPath = std::move(drvPath), .output = getString(valueAt(json, "output")), }; } -DerivedPath::Built adl_serializer::from_json(const json & json0) +DerivedPath::Built +adl_serializer::from_json(const json & json0, const ExperimentalFeatureSettings & xpSettings) { auto & json = getObject(json0); + auto drvPath = make_ref(static_cast(valueAt(json, "drvPath"))); + drvRequireExperiment(*drvPath, xpSettings); return { - .drvPath = make_ref(static_cast(valueAt(json, "drvPath"))), + .drvPath = std::move(drvPath), .outputs = adl_serializer::from_json(valueAt(json, "outputs")), }; } @@ -280,20 +286,21 @@ void adl_serializer::to_json(json & json, const DerivedPath & sdp) std::visit([&](const auto & buildable) { json = buildable; }, sdp.raw()); } -SingleDerivedPath adl_serializer::from_json(const json & json) +SingleDerivedPath +adl_serializer::from_json(const json & json, const ExperimentalFeatureSettings & xpSettings) { if (json.is_string()) return static_cast(json); else - return static_cast(json); + return adl_serializer::from_json(json, xpSettings); } -DerivedPath adl_serializer::from_json(const json & json) +DerivedPath adl_serializer::from_json(const json & json, const ExperimentalFeatureSettings & xpSettings) { if (json.is_string()) return static_cast(json); else - return static_cast(json); + return adl_serializer::from_json(json, xpSettings); } } // namespace nlohmann diff --git a/src/libstore/include/nix/store/derivations.hh b/src/libstore/include/nix/store/derivations.hh index 0dfb80347..4615d8acd 100644 --- a/src/libstore/include/nix/store/derivations.hh +++ b/src/libstore/include/nix/store/derivations.hh @@ -134,13 +134,6 @@ struct DerivationOutput */ std::optional path(const StoreDirConfig & store, std::string_view drvName, OutputNameView outputName) const; - - nlohmann::json toJSON() const; - /** - * @param xpSettings Stop-gap to avoid globals during unit tests. - */ - static DerivationOutput - fromJSON(const nlohmann::json & json, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); }; typedef std::map DerivationOutputs; @@ -390,10 +383,6 @@ struct Derivation : BasicDerivation { } - nlohmann::json toJSON() const; - static Derivation - fromJSON(const nlohmann::json & json, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); - bool operator==(const Derivation &) const = default; // TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet. // auto operator <=> (const Derivation &) const = default; @@ -537,5 +526,5 @@ std::string hashPlaceholder(const OutputNameView outputName); } // namespace nix -JSON_IMPL(nix::DerivationOutput) -JSON_IMPL(nix::Derivation) +JSON_IMPL_WITH_XP_FEATURES(nix::DerivationOutput) +JSON_IMPL_WITH_XP_FEATURES(nix::Derivation) diff --git a/src/libstore/include/nix/store/derived-path.hh b/src/libstore/include/nix/store/derived-path.hh index 47b29b2d6..70074ea40 100644 --- a/src/libstore/include/nix/store/derived-path.hh +++ b/src/libstore/include/nix/store/derived-path.hh @@ -299,7 +299,7 @@ void drvRequireExperiment( } // namespace nix JSON_IMPL(nix::SingleDerivedPath::Opaque) -JSON_IMPL(nix::SingleDerivedPath::Built) -JSON_IMPL(nix::SingleDerivedPath) -JSON_IMPL(nix::DerivedPath::Built) -JSON_IMPL(nix::DerivedPath) +JSON_IMPL_WITH_XP_FEATURES(nix::SingleDerivedPath::Built) +JSON_IMPL_WITH_XP_FEATURES(nix::SingleDerivedPath) +JSON_IMPL_WITH_XP_FEATURES(nix::DerivedPath::Built) +JSON_IMPL_WITH_XP_FEATURES(nix::DerivedPath) diff --git a/src/libutil-test-support/include/nix/util/tests/json-characterization.hh b/src/libutil-test-support/include/nix/util/tests/json-characterization.hh index 5a38b8e2c..d713c615b 100644 --- a/src/libutil-test-support/include/nix/util/tests/json-characterization.hh +++ b/src/libutil-test-support/include/nix/util/tests/json-characterization.hh @@ -24,12 +24,12 @@ struct JsonCharacterizationTest : virtual CharacterizationTest * @param test hook that takes the contents of the file and does the * actual work */ - void readJsonTest(PathView testStem, const T & expected) + void readJsonTest(PathView testStem, const T & expected, auto... args) { using namespace nlohmann; readTest(Path{testStem} + ".json", [&](const auto & encodedRaw) { auto encoded = json::parse(encodedRaw); - T decoded = adl_serializer::from_json(encoded); + T decoded = adl_serializer::from_json(encoded, args...); ASSERT_EQ(decoded, expected); }); } diff --git a/src/libutil/include/nix/util/json-impls.hh b/src/libutil/include/nix/util/json-impls.hh index 751fc410f..802c212e1 100644 --- a/src/libutil/include/nix/util/json-impls.hh +++ b/src/libutil/include/nix/util/json-impls.hh @@ -3,6 +3,8 @@ #include +#include "nix/util/experimental-features.hh" + // Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types #define JSON_IMPL(TYPE) \ namespace nlohmann { \ @@ -14,3 +16,15 @@ static void to_json(json & json, const TYPE & t); \ }; \ } + +#define JSON_IMPL_WITH_XP_FEATURES(TYPE) \ + namespace nlohmann { \ + using namespace nix; \ + template<> \ + struct adl_serializer \ + { \ + static TYPE \ + from_json(const json & json, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); \ + static void to_json(json & json, const TYPE & t); \ + }; \ + } diff --git a/src/nix/derivation-add.cc b/src/nix/derivation-add.cc index 2d13aba52..48e935092 100644 --- a/src/nix/derivation-add.cc +++ b/src/nix/derivation-add.cc @@ -33,7 +33,7 @@ struct CmdAddDerivation : MixDryRun, StoreCommand { auto json = nlohmann::json::parse(drainFD(STDIN_FILENO)); - auto drv = Derivation::fromJSON(json); + auto drv = static_cast(json); auto drvPath = writeDerivation(*store, drv, NoRepair, /* read only */ dryRun); diff --git a/src/nix/derivation-show.cc b/src/nix/derivation-show.cc index 20e54bba7..1528f5b51 100644 --- a/src/nix/derivation-show.cc +++ b/src/nix/derivation-show.cc @@ -58,7 +58,7 @@ struct CmdShowDerivation : InstallablesCommand, MixPrintJSON if (!drvPath.isDerivation()) continue; - jsonRoot[drvPath.to_string()] = store->readDerivation(drvPath).toJSON(); + jsonRoot[drvPath.to_string()] = store->readDerivation(drvPath); } printJSON(jsonRoot); }