diff --git a/src/libstore/unix/build/local-derivation-goal.cc b/src/libstore/unix/build/local-derivation-goal.cc index 06a2f85be84..9b19ab094cf 100644 --- a/src/libstore/unix/build/local-derivation-goal.cc +++ b/src/libstore/unix/build/local-derivation-goal.cc @@ -2927,8 +2927,12 @@ void LocalDerivationGoal::checkOutputs(const std::mappath); - else - throw BuildError("derivation contains an illegal reference specifier '%s'", i); + else { + std::string outputsListing = concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; }); + throw BuildError("derivation '%s' output check for '%s' contains an illegal reference specifier '%s'," + " expected store path or output name (one of [%s])", + worker.store.printStorePath(drvPath), outputName, i, outputsListing); + } } auto used = recursive diff --git a/src/libutil-tests/strings.cc b/src/libutil-tests/strings.cc index 206890bcf19..33a1fae9b23 100644 --- a/src/libutil-tests/strings.cc +++ b/src/libutil-tests/strings.cc @@ -80,6 +80,42 @@ TEST(concatStringsSep, buildSingleString) ASSERT_EQ(concatStringsSep(",", strings), "this"); } +TEST(concatMapStringsSep, empty) +{ + Strings strings; + + ASSERT_EQ(concatMapStringsSep(",", strings, [](const std::string & s) { return s; }), ""); +} + +TEST(concatMapStringsSep, justOne) +{ + Strings strings; + strings.push_back("this"); + + ASSERT_EQ(concatMapStringsSep(",", strings, [](const std::string & s) { return s; }), "this"); +} + +TEST(concatMapStringsSep, two) +{ + Strings strings; + strings.push_back("this"); + strings.push_back("that"); + + ASSERT_EQ(concatMapStringsSep(",", strings, [](const std::string & s) { return s; }), "this,that"); +} + +TEST(concatMapStringsSep, map) +{ + std::map strings; + strings["this"] = "that"; + strings["1"] = "one"; + + ASSERT_EQ( + concatMapStringsSep( + ", ", strings, [](const std::pair & s) { return s.first + " -> " + s.second; }), + "1 -> one, this -> that"); +} + /* ---------------------------------------------------------------------------- * dropEmptyInitThenConcatStringsSep * --------------------------------------------------------------------------*/ diff --git a/src/libutil/strings.cc b/src/libutil/strings.cc index 402b7ae98a3..b94bca61184 100644 --- a/src/libutil/strings.cc +++ b/src/libutil/strings.cc @@ -37,6 +37,7 @@ basicSplitString(std::basic_string_view s, std::basic_string_view &); template std::string concatStringsSep(std::string_view, const std::set &); template std::string concatStringsSep(std::string_view, const std::vector &); +template std::string concatStringsSep(std::string_view, const boost::container::small_vector &); typedef std::string_view strings_2[2]; template std::string concatStringsSep(std::string_view, const strings_2 &); diff --git a/src/libutil/strings.hh b/src/libutil/strings.hh index c4fd3daa194..521e3425f4a 100644 --- a/src/libutil/strings.hh +++ b/src/libutil/strings.hh @@ -6,6 +6,8 @@ #include #include +#include + namespace nix { /* @@ -54,6 +56,21 @@ std::string concatStringsSep(const std::string_view sep, const C & ss); extern template std::string concatStringsSep(std::string_view, const std::list &); extern template std::string concatStringsSep(std::string_view, const std::set &); extern template std::string concatStringsSep(std::string_view, const std::vector &); +extern template std::string concatStringsSep(std::string_view, const boost::container::small_vector &); + +/** + * Apply a function to the `iterable`'s items and concat them with `separator`. + */ +template +std::string concatMapStringsSep(std::string_view separator, const C & iterable, F fn) +{ + boost::container::small_vector strings; + strings.reserve(iterable.size()); + for (const auto & elem : iterable) { + strings.push_back(fn(elem)); + } + return concatStringsSep(separator, strings); +} /** * Ignore any empty strings at the start of the list, and then concatenate the diff --git a/tests/functional/check-refs.nix b/tests/functional/check-refs.nix index 89690e456c1..1a900830e47 100644 --- a/tests/functional/check-refs.nix +++ b/tests/functional/check-refs.nix @@ -74,4 +74,10 @@ rec { buildCommand = ''echo ${dep} > "''${outputs[out]}"''; }; + test12 = makeTest 12 { + builder = builtins.toFile "builder.sh" "mkdir $out $lib"; + outputs = ["out" "lib"]; + disallowedReferences = ["dev"]; + }; + } diff --git a/tests/functional/check-refs.sh b/tests/functional/check-refs.sh index 5c3ac915ecf..8eb93b48d3c 100755 --- a/tests/functional/check-refs.sh +++ b/tests/functional/check-refs.sh @@ -60,3 +60,7 @@ if ! isTestOnNixOS; then fi fi + +# test12 should fail (syntactically invalid). +expectStderr 1 nix-build -vvv -o "$RESULT" check-refs.nix -A test12 >"$TEST_ROOT/test12.stderr" +grepQuiet -F "output check for 'lib' contains an illegal reference specifier 'dev', expected store path or output name (one of [lib, out])" < "$TEST_ROOT/test12.stderr"