Skip to content

Commit 1fb4ff8

Browse files
authored
Merge pull request #14232 from roberth/dyndrv-messages
Better dyndrv messages
2 parents 959c244 + 1b96a70 commit 1fb4ff8

File tree

12 files changed

+154
-15
lines changed

12 files changed

+154
-15
lines changed

src/libexpr/primops.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,8 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName
14201420
.debugThrow();
14211421
}
14221422
if (ingestionMethod == ContentAddressMethod::Raw::Text)
1423-
experimentalFeatureSettings.require(Xp::DynamicDerivations);
1423+
experimentalFeatureSettings.require(
1424+
Xp::DynamicDerivations, fmt("text-hashed derivation '%s', outputHashMode = \"text\"", drvName));
14241425
if (ingestionMethod == ContentAddressMethod::Raw::Git)
14251426
experimentalFeatureSettings.require(Xp::GitHashing);
14261427
};

src/libstore/derivations.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ static DerivationOutput parseDerivationOutput(
290290
if (!hashAlgoStr.empty()) {
291291
ContentAddressMethod method = ContentAddressMethod::parsePrefix(hashAlgoStr);
292292
if (method == ContentAddressMethod::Raw::Text)
293-
xpSettings.require(Xp::DynamicDerivations);
293+
xpSettings.require(Xp::DynamicDerivations, "text-hashed derivation output");
294294
const auto hashAlgo = parseHashAlgo(hashAlgoStr);
295295
if (hashS == "impure"sv) {
296296
xpSettings.require(Xp::ImpureDerivations);
@@ -428,7 +428,9 @@ Derivation parseDerivation(
428428
if (*versionS == "xp-dyn-drv"sv) {
429429
// Only version we have so far
430430
version = DerivationATermVersion::DynamicDerivations;
431-
xpSettings.require(Xp::DynamicDerivations);
431+
xpSettings.require(Xp::DynamicDerivations, [&] {
432+
return fmt("derivation '%s', ATerm format version 'xp-dyn-drv'", name);
433+
});
432434
} else {
433435
throw FormatError("Unknown derivation ATerm format version '%s'", *versionS);
434436
}
@@ -1303,7 +1305,7 @@ DerivationOutput::fromJSON(const nlohmann::json & _json, const ExperimentalFeatu
13031305
auto methodAlgo = [&]() -> std::pair<ContentAddressMethod, HashAlgorithm> {
13041306
ContentAddressMethod method = ContentAddressMethod::parse(getString(valueAt(json, "method")));
13051307
if (method == ContentAddressMethod::Raw::Text)
1306-
xpSettings.require(Xp::DynamicDerivations);
1308+
xpSettings.require(Xp::DynamicDerivations, "text-hashed derivation output in JSON");
13071309

13081310
auto hashAlgo = parseHashAlgo(getString(valueAt(json, "hashAlgo")));
13091311
return {std::move(method), std::move(hashAlgo)};
@@ -1456,7 +1458,8 @@ Derivation Derivation::fromJSON(const nlohmann::json & _json, const Experimental
14561458
node.value = getStringSet(valueAt(json, "outputs"));
14571459
auto drvs = getObject(valueAt(json, "dynamicOutputs"));
14581460
for (auto & [outputId, childNode] : drvs) {
1459-
xpSettings.require(Xp::DynamicDerivations);
1461+
xpSettings.require(
1462+
Xp::DynamicDerivations, [&] { return fmt("dynamic output '%s' in JSON", outputId); });
14601463
node.childMap[outputId] = doInput(childNode);
14611464
}
14621465
return node;

src/libstore/derived-path.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ void drvRequireExperiment(const SingleDerivedPath & drv, const ExperimentalFeatu
8585
[&](const SingleDerivedPath::Opaque &) {
8686
// plain drv path; no experimental features required.
8787
},
88-
[&](const SingleDerivedPath::Built &) { xpSettings.require(Xp::DynamicDerivations); },
88+
[&](const SingleDerivedPath::Built & b) {
89+
xpSettings.require(Xp::DynamicDerivations, [&] {
90+
return fmt("building output '%s' of '%s'", b.output, b.drvPath->getBaseStorePath().to_string());
91+
});
92+
},
8993
},
9094
drv.raw());
9195
}

src/libstore/downstream-placeholder.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ DownstreamPlaceholder DownstreamPlaceholder::unknownDerivation(
2424
OutputNameView outputName,
2525
const ExperimentalFeatureSettings & xpSettings)
2626
{
27-
xpSettings.require(Xp::DynamicDerivations);
27+
xpSettings.require(
28+
Xp::DynamicDerivations, [&] { return fmt("placeholder for unknown derivation output '%s'", outputName); });
2829
auto compressed = compressHash(placeholder.hash, 20);
2930
auto clearText =
3031
"nix-computed-output:" + compressed.to_string(HashFormat::Nix32, false) + ":" + std::string{outputName};

src/libutil-tests/strings.cc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,4 +494,63 @@ TEST(shellSplitString, testUnbalancedQuotes)
494494
ASSERT_THROW(shellSplitString("foo\"bar\\\""), Error);
495495
}
496496

497+
/* ----------------------------------------------------------------------------
498+
* optionalBracket
499+
* --------------------------------------------------------------------------*/
500+
501+
TEST(optionalBracket, emptyContent)
502+
{
503+
ASSERT_EQ(optionalBracket(" (", "", ")"), "");
504+
}
505+
506+
TEST(optionalBracket, nonEmptyContent)
507+
{
508+
ASSERT_EQ(optionalBracket(" (", "foo", ")"), " (foo)");
509+
}
510+
511+
TEST(optionalBracket, emptyPrefixAndSuffix)
512+
{
513+
ASSERT_EQ(optionalBracket("", "foo", ""), "foo");
514+
}
515+
516+
TEST(optionalBracket, emptyContentEmptyBrackets)
517+
{
518+
ASSERT_EQ(optionalBracket("", "", ""), "");
519+
}
520+
521+
TEST(optionalBracket, complexBrackets)
522+
{
523+
ASSERT_EQ(optionalBracket(" [[[", "content", "]]]"), " [[[content]]]");
524+
}
525+
526+
TEST(optionalBracket, onlyPrefix)
527+
{
528+
ASSERT_EQ(optionalBracket("prefix", "content", ""), "prefixcontent");
529+
}
530+
531+
TEST(optionalBracket, onlySuffix)
532+
{
533+
ASSERT_EQ(optionalBracket("", "content", "suffix"), "contentsuffix");
534+
}
535+
536+
TEST(optionalBracket, optionalWithValue)
537+
{
538+
ASSERT_EQ(optionalBracket(" (", std::optional<std::string>("foo"), ")"), " (foo)");
539+
}
540+
541+
TEST(optionalBracket, optionalNullopt)
542+
{
543+
ASSERT_EQ(optionalBracket(" (", std::optional<std::string>(std::nullopt), ")"), "");
544+
}
545+
546+
TEST(optionalBracket, optionalEmptyString)
547+
{
548+
ASSERT_EQ(optionalBracket(" (", std::optional<std::string>(""), ")"), "");
549+
}
550+
551+
TEST(optionalBracket, optionalStringViewWithValue)
552+
{
553+
ASSERT_EQ(optionalBracket(" (", std::optional<std::string_view>("bar"), ")"), " (bar)");
554+
}
555+
497556
} // namespace nix

src/libutil/configuration.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,10 +500,10 @@ bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature)
500500
return std::find(f.begin(), f.end(), feature) != f.end();
501501
}
502502

503-
void ExperimentalFeatureSettings::require(const ExperimentalFeature & feature) const
503+
void ExperimentalFeatureSettings::require(const ExperimentalFeature & feature, std::string reason) const
504504
{
505505
if (!isEnabled(feature))
506-
throw MissingExperimentalFeature(feature);
506+
throw MissingExperimentalFeature(feature, std::move(reason));
507507
}
508508

509509
bool ExperimentalFeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const

src/libutil/experimental-features.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "nix/util/experimental-features.hh"
22
#include "nix/util/fmt.hh"
3+
#include "nix/util/strings.hh"
34
#include "nix/util/util.hh"
45

56
#include <nlohmann/json.hpp>
@@ -376,11 +377,13 @@ std::set<ExperimentalFeature> parseFeatures(const StringSet & rawFeatures)
376377
return res;
377378
}
378379

379-
MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature)
380+
MissingExperimentalFeature::MissingExperimentalFeature(ExperimentalFeature feature, std::string reason)
380381
: Error(
381-
"experimental Nix feature '%1%' is disabled; add '--extra-experimental-features %1%' to enable it",
382-
showExperimentalFeature(feature))
382+
"experimental Nix feature '%1%' is disabled%2%; add '--extra-experimental-features %1%' to enable it",
383+
showExperimentalFeature(feature),
384+
Uncolored(optionalBracket(" (", reason, ")")))
383385
, missingFeature(feature)
386+
, reason{reason}
384387
{
385388
}
386389

src/libutil/include/nix/util/configuration.hh

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,20 @@ struct ExperimentalFeatureSettings : Config
463463
* Require an experimental feature be enabled, throwing an error if it is
464464
* not.
465465
*/
466-
void require(const ExperimentalFeature &) const;
466+
void require(const ExperimentalFeature &, std::string reason = "") const;
467+
468+
/**
469+
* Require an experimental feature be enabled, throwing an error if it is
470+
* not. The reason is lazily evaluated only if the feature is disabled.
471+
*/
472+
template<typename GetReason>
473+
requires std::invocable<GetReason> && std::convertible_to<std::invoke_result_t<GetReason>, std::string>
474+
void require(const ExperimentalFeature & feature, GetReason && getReason) const
475+
{
476+
if (isEnabled(feature))
477+
return;
478+
require(feature, getReason());
479+
}
467480

468481
/**
469482
* `std::nullopt` pointer means no feature, which means there is nothing that could be

src/libutil/include/nix/util/experimental-features.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ public:
8888
*/
8989
ExperimentalFeature missingFeature;
9090

91-
MissingExperimentalFeature(ExperimentalFeature missingFeature);
91+
std::string reason;
92+
93+
MissingExperimentalFeature(ExperimentalFeature missingFeature, std::string reason = "");
9294
};
9395

9496
/**

src/libutil/include/nix/util/strings.hh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "nix/util/types.hh"
44

55
#include <list>
6+
#include <optional>
67
#include <set>
78
#include <string_view>
89
#include <string>
@@ -93,6 +94,44 @@ extern template std::string dropEmptyInitThenConcatStringsSep(std::string_view,
9394
*/
9495
std::list<std::string> shellSplitString(std::string_view s);
9596

97+
/**
98+
* Conditionally wrap a string with prefix and suffix brackets.
99+
*
100+
* If `content` is empty, returns an empty string.
101+
* Otherwise, returns `prefix + content + suffix`.
102+
*
103+
* Example:
104+
* optionalBracket(" (", "foo", ")") == " (foo)"
105+
* optionalBracket(" (", "", ")") == ""
106+
*
107+
* Design note: this would have been called `optionalParentheses`, except this
108+
* function is more general and more explicit. Parentheses typically *also* need
109+
* to be prefixed with a space in order to fit nicely in a piece of natural
110+
* language.
111+
*/
112+
std::string optionalBracket(std::string_view prefix, std::string_view content, std::string_view suffix);
113+
114+
/**
115+
* Overload for optional content.
116+
*
117+
* If `content` is nullopt or contains an empty string, returns an empty string.
118+
* Otherwise, returns `prefix + *content + suffix`.
119+
*
120+
* Example:
121+
* optionalBracket(" (", std::optional<std::string>("foo"), ")") == " (foo)"
122+
* optionalBracket(" (", std::nullopt, ")") == ""
123+
* optionalBracket(" (", std::optional<std::string>(""), ")") == ""
124+
*/
125+
template<typename T>
126+
requires std::convertible_to<T, std::string_view>
127+
std::string optionalBracket(std::string_view prefix, const std::optional<T> & content, std::string_view suffix)
128+
{
129+
if (!content || std::string_view(*content).empty()) {
130+
return "";
131+
}
132+
return optionalBracket(prefix, std::string_view(*content), suffix);
133+
}
134+
96135
/**
97136
* Hash implementation that can be used for zero-copy heterogenous lookup from
98137
* P1690R1[1] in unordered containers.

0 commit comments

Comments
 (0)