Merge remote-tracking branch 'origin/master' into overlayfs-store

This commit is contained in:
Ben Radford 2023-08-08 13:39:18 +01:00
commit c0e6466a1e
No known key found for this signature in database
GPG Key ID: 9DF5D4640AB888D5
29 changed files with 191 additions and 97 deletions

View File

@ -320,16 +320,6 @@ Derivations can declare some infrequently used optional attributes.
``` ```
- [`unsafeDiscardReferences`]{#adv-attr-unsafeDiscardReferences}\ - [`unsafeDiscardReferences`]{#adv-attr-unsafeDiscardReferences}\
> **Warning**
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md).
>
> To use this attribute, you must enable the
> [`discard-references`](@docroot@/contributing/experimental-features.md#xp-feature-discard-references) experimental feature.
> For example, in [nix.conf](../command-ref/conf-file.md) you could add:
>
> ```
> extra-experimental-features = discard-references
> ```
When using [structured attributes](#adv-attr-structuredAttrs), the When using [structured attributes](#adv-attr-structuredAttrs), the
attribute `unsafeDiscardReferences` is an attribute set with a boolean value for each output name. attribute `unsafeDiscardReferences` is an attribute set with a boolean value for each output name.

View File

@ -8,3 +8,11 @@
These functions are useful for converting between flake references encoded as attribute sets and URLs. These functions are useful for converting between flake references encoded as attribute sets and URLs.
- [`builtins.toJSON`](@docroot@/language/builtins.md#builtins-parseFlakeRef) now prints [--show-trace](@docroot@/command-ref/conf-file.html#conf-show-trace) items for the path in which it finds an evaluation error. - [`builtins.toJSON`](@docroot@/language/builtins.md#builtins-parseFlakeRef) now prints [--show-trace](@docroot@/command-ref/conf-file.html#conf-show-trace) items for the path in which it finds an evaluation error.
- Error messages regarding malformed input to [`derivation add`](@docroot@/command-ref/new-cli/nix3-derivation-add.md) are now clearer and more detailed.
- The `discard-references` feature has been stabilized.
This means that the
[unsafeDiscardReferences](@docroot@/contributing/experimental-features.md#xp-feature-discard-references)
attribute is no longer guarded by an experimental flag and can be used
freely.

View File

@ -1031,14 +1031,15 @@ void EvalState::mkOutputString(
Value & value, Value & value,
const StorePath & drvPath, const StorePath & drvPath,
const std::string outputName, const std::string outputName,
std::optional<StorePath> optOutputPath) std::optional<StorePath> optOutputPath,
const ExperimentalFeatureSettings & xpSettings)
{ {
value.mkString( value.mkString(
optOutputPath optOutputPath
? store->printStorePath(*std::move(optOutputPath)) ? store->printStorePath(*std::move(optOutputPath))
/* Downstream we would substitute this for an actual path once /* Downstream we would substitute this for an actual path once
we build the floating CA derivation */ we build the floating CA derivation */
: DownstreamPlaceholder::unknownCaOutput(drvPath, outputName).render(), : DownstreamPlaceholder::unknownCaOutput(drvPath, outputName, xpSettings).render(),
NixStringContext { NixStringContext {
NixStringContextElem::Built { NixStringContextElem::Built {
.drvPath = drvPath, .drvPath = drvPath,

View File

@ -689,12 +689,15 @@ public:
* be passed if and only if output store object is input-addressed. * be passed if and only if output store object is input-addressed.
* Will be printed to form string if passed, otherwise a placeholder * Will be printed to form string if passed, otherwise a placeholder
* will be used (see `DownstreamPlaceholder`). * will be used (see `DownstreamPlaceholder`).
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/ */
void mkOutputString( void mkOutputString(
Value & value, Value & value,
const StorePath & drvPath, const StorePath & drvPath,
const std::string outputName, const std::string outputName,
std::optional<StorePath> optOutputPath); std::optional<StorePath> optOutputPath,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx); void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);

View File

@ -105,7 +105,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
}; };
return std::make_pair( return std::make_pair(
FlakeRef(Input::fromURL(parsedURL), ""), FlakeRef(Input::fromURL(parsedURL, isFlake), ""),
percentDecode(match.str(6))); percentDecode(match.str(6)));
} }
@ -176,7 +176,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
parsedURL.query.insert_or_assign("shallow", "1"); parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair( return std::make_pair(
FlakeRef(Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")), FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")),
fragment); fragment);
} }
@ -204,7 +204,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::string fragment; std::string fragment;
std::swap(fragment, parsedURL.fragment); std::swap(fragment, parsedURL.fragment);
auto input = Input::fromURL(parsedURL); auto input = Input::fromURL(parsedURL, isFlake);
input.parent = baseDir; input.parent = baseDir;
return std::make_pair( return std::make_pair(

View File

@ -84,23 +84,22 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d }); for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d });
store->buildPaths(buildReqs); store->buildPaths(buildReqs);
/* Get all the output paths corresponding to the placeholders we had */
for (auto & drv : drvs) { for (auto & drv : drvs) {
auto outputs = resolveDerivedPath(*store, drv); auto outputs = resolveDerivedPath(*store, drv);
for (auto & [outputName, outputPath] : outputs) { for (auto & [outputName, outputPath] : outputs) {
/* Add the output of this derivations to the allowed
paths. */
if (allowedPaths) {
allowPath(outputPath);
}
/* Get all the output paths corresponding to the placeholders we had */
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
res.insert_or_assign( res.insert_or_assign(
DownstreamPlaceholder::unknownCaOutput(drv.drvPath, outputName).render(), DownstreamPlaceholder::unknownCaOutput(drv.drvPath, outputName).render(),
store->printStorePath(outputPath) store->printStorePath(outputPath)
); );
} }
} }
/* Add the output of this derivations to the allowed
paths. */
if (allowedPaths) {
for (auto & [_placeholder, outputPath] : res) {
allowPath(store->toRealPath(outputPath));
}
} }
return res; return res;

View File

@ -37,8 +37,15 @@ RC_GTEST_FIXTURE_PROP(
prop_built_path_placeholder_round_trip, prop_built_path_placeholder_round_trip,
(const StorePath & drvPath, const StorePathName & outputName)) (const StorePath & drvPath, const StorePathName & outputName))
{ {
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "ca-derivations");
auto * v = state.allocValue(); auto * v = state.allocValue();
state.mkOutputString(*v, drvPath, outputName.name, std::nullopt); state.mkOutputString(*v, drvPath, outputName.name, std::nullopt, mockXpSettings);
auto [d, _] = state.coerceToDerivedPathUnchecked(noPos, *v, ""); auto [d, _] = state.coerceToDerivedPathUnchecked(noPos, *v, "");
DerivedPath::Built b { DerivedPath::Built b {
.drvPath = drvPath, .drvPath = drvPath,

View File

@ -13,9 +13,9 @@ void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
inputSchemes->push_back(std::move(inputScheme)); inputSchemes->push_back(std::move(inputScheme));
} }
Input Input::fromURL(const std::string & url) Input Input::fromURL(const std::string & url, bool requireTree)
{ {
return fromURL(parseURL(url)); return fromURL(parseURL(url), requireTree);
} }
static void fixupInput(Input & input) static void fixupInput(Input & input)
@ -31,10 +31,10 @@ static void fixupInput(Input & input)
input.locked = true; input.locked = true;
} }
Input Input::fromURL(const ParsedURL & url) Input Input::fromURL(const ParsedURL & url, bool requireTree)
{ {
for (auto & inputScheme : *inputSchemes) { for (auto & inputScheme : *inputSchemes) {
auto res = inputScheme->inputFromURL(url); auto res = inputScheme->inputFromURL(url, requireTree);
if (res) { if (res) {
res->scheme = inputScheme; res->scheme = inputScheme;
fixupInput(*res); fixupInput(*res);

View File

@ -44,9 +44,9 @@ struct Input
std::optional<Path> parent; std::optional<Path> parent;
public: public:
static Input fromURL(const std::string & url); static Input fromURL(const std::string & url, bool requireTree = true);
static Input fromURL(const ParsedURL & url); static Input fromURL(const ParsedURL & url, bool requireTree = true);
static Input fromAttrs(Attrs && attrs); static Input fromAttrs(Attrs && attrs);
@ -129,7 +129,7 @@ struct InputScheme
virtual ~InputScheme() virtual ~InputScheme()
{ } { }
virtual std::optional<Input> inputFromURL(const ParsedURL & url) const = 0; virtual std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const = 0;
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0; virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;

View File

@ -256,7 +256,7 @@ std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, co
struct GitInputScheme : InputScheme struct GitInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "git" && if (url.scheme != "git" &&
url.scheme != "git+http" && url.scheme != "git+http" &&

View File

@ -30,7 +30,7 @@ struct GitArchiveInputScheme : InputScheme
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0; virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != type()) return {}; if (url.scheme != type()) return {};

View File

@ -7,7 +7,7 @@ std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
struct IndirectInputScheme : InputScheme struct IndirectInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "flake") return {}; if (url.scheme != "flake") return {};

View File

@ -43,7 +43,7 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
struct MercurialInputScheme : InputScheme struct MercurialInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "hg+http" && if (url.scheme != "hg+http" &&
url.scheme != "hg+https" && url.scheme != "hg+https" &&

View File

@ -6,7 +6,7 @@ namespace nix::fetchers {
struct PathInputScheme : InputScheme struct PathInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "path") return {}; if (url.scheme != "path") return {};

View File

@ -194,11 +194,11 @@ struct CurlInputScheme : InputScheme
|| hasSuffix(path, ".tar.zst"); || hasSuffix(path, ".tar.zst");
} }
virtual bool isValidURL(const ParsedURL & url) const = 0; virtual bool isValidURL(const ParsedURL & url, bool requireTree) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & _url) const override std::optional<Input> inputFromURL(const ParsedURL & _url, bool requireTree) const override
{ {
if (!isValidURL(_url)) if (!isValidURL(_url, requireTree))
return std::nullopt; return std::nullopt;
Input input; Input input;
@ -265,13 +265,13 @@ struct FileInputScheme : CurlInputScheme
{ {
const std::string inputType() const override { return "file"; } const std::string inputType() const override { return "file"; }
bool isValidURL(const ParsedURL & url) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType() ? parsedUrlScheme.application.value() == inputType()
: !hasTarballExtension(url.path)); : (!requireTree && !hasTarballExtension(url.path)));
} }
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
@ -285,14 +285,14 @@ struct TarballInputScheme : CurlInputScheme
{ {
const std::string inputType() const override { return "tarball"; } const std::string inputType() const override { return "tarball"; }
bool isValidURL(const ParsedURL & url) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType() ? parsedUrlScheme.application.value() == inputType()
: hasTarballExtension(url.path)); : (requireTree || hasTarballExtension(url.path)));
} }
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override

View File

@ -2308,7 +2308,6 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
bool discardReferences = false; bool discardReferences = false;
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) { if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) { if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) {
experimentalFeatureSettings.require(Xp::DiscardReferences);
if (auto output = get(*udr, outputName)) { if (auto output = get(*udr, outputName)) {
if (!output->is_boolean()) if (!output->is_boolean())
throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string()); throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string());

View File

@ -879,9 +879,11 @@ std::optional<BasicDerivation> Derivation::tryResolve(
for (auto & [inputDrv, inputOutputs] : inputDrvs) { for (auto & [inputDrv, inputOutputs] : inputDrvs) {
for (auto & outputName : inputOutputs) { for (auto & outputName : inputOutputs) {
if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) { if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
inputRewrites.emplace( inputRewrites.emplace(
DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName).render(), DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName).render(),
store.printStorePath(*actualPath)); store.printStorePath(*actualPath));
}
resolved.inputSrcs.insert(*actualPath); resolved.inputSrcs.insert(*actualPath);
} else { } else {
warn("output '%s' of input '%s' missing, aborting the resolving", warn("output '%s' of input '%s' missing, aborting the resolving",
@ -993,6 +995,7 @@ DerivationOutput DerivationOutput::fromJSON(
const ExperimentalFeatureSettings & xpSettings) const ExperimentalFeatureSettings & xpSettings)
{ {
std::set<std::string_view> keys; std::set<std::string_view> keys;
ensureType(_json, nlohmann::detail::value_t::object);
auto json = (std::map<std::string, nlohmann::json>) _json; auto json = (std::map<std::string, nlohmann::json>) _json;
for (const auto & [key, _] : json) for (const auto & [key, _] : json)
@ -1097,36 +1100,51 @@ Derivation Derivation::fromJSON(
const Store & store, const Store & store,
const nlohmann::json & json) const nlohmann::json & json)
{ {
using nlohmann::detail::value_t;
Derivation res; Derivation res;
res.name = json["name"]; ensureType(json, value_t::object);
{ res.name = ensureType(valueAt(json, "name"), value_t::string);
auto & outputsObj = json["outputs"];
try {
auto & outputsObj = ensureType(valueAt(json, "outputs"), value_t::object);
for (auto & [outputName, output] : outputsObj.items()) { for (auto & [outputName, output] : outputsObj.items()) {
res.outputs.insert_or_assign( res.outputs.insert_or_assign(
outputName, outputName,
DerivationOutput::fromJSON(store, res.name, outputName, output)); DerivationOutput::fromJSON(store, res.name, outputName, output));
} }
} catch (Error & e) {
e.addTrace({}, "while reading key 'outputs'");
throw;
} }
{ try {
auto & inputsList = json["inputSrcs"]; auto & inputsList = ensureType(valueAt(json, "inputSrcs"), value_t::array);
for (auto & input : inputsList) for (auto & input : inputsList)
res.inputSrcs.insert(store.parseStorePath(static_cast<const std::string &>(input))); res.inputSrcs.insert(store.parseStorePath(static_cast<const std::string &>(input)));
} catch (Error & e) {
e.addTrace({}, "while reading key 'inputSrcs'");
throw;
} }
{ try {
auto & inputDrvsObj = json["inputDrvs"]; auto & inputDrvsObj = ensureType(valueAt(json, "inputDrvs"), value_t::object);
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items()) for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items()) {
ensureType(inputOutputs, value_t::array);
res.inputDrvs[store.parseStorePath(inputDrvPath)] = res.inputDrvs[store.parseStorePath(inputDrvPath)] =
static_cast<const StringSet &>(inputOutputs); static_cast<const StringSet &>(inputOutputs);
} }
} catch (Error & e) {
e.addTrace({}, "while reading key 'inputDrvs'");
throw;
}
res.platform = json["system"]; res.platform = ensureType(valueAt(json, "system"), value_t::string);
res.builder = json["builder"]; res.builder = ensureType(valueAt(json, "builder"), value_t::string);
res.args = json["args"]; res.args = ensureType(valueAt(json, "args"), value_t::array);
res.env = json["env"]; res.env = ensureType(valueAt(json, "env"), value_t::object);
return res; return res;
} }

View File

@ -11,8 +11,10 @@ std::string DownstreamPlaceholder::render() const
DownstreamPlaceholder DownstreamPlaceholder::unknownCaOutput( DownstreamPlaceholder DownstreamPlaceholder::unknownCaOutput(
const StorePath & drvPath, const StorePath & drvPath,
std::string_view outputName) std::string_view outputName,
const ExperimentalFeatureSettings & xpSettings)
{ {
xpSettings.require(Xp::CaDerivations);
auto drvNameWithExtension = drvPath.name(); auto drvNameWithExtension = drvPath.name();
auto drvName = drvNameWithExtension.substr(0, drvNameWithExtension.size() - 4); auto drvName = drvNameWithExtension.substr(0, drvNameWithExtension.size() - 4);
auto clearText = "nix-upstream-output:" + std::string { drvPath.hashPart() } + ":" + outputPathName(drvName, outputName); auto clearText = "nix-upstream-output:" + std::string { drvPath.hashPart() } + ":" + outputPathName(drvName, outputName);

View File

@ -52,10 +52,13 @@ public:
* *
* The derivation itself is known (we have a store path for it), but * The derivation itself is known (we have a store path for it), but
* the output doesn't yet have a known store path. * the output doesn't yet have a known store path.
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/ */
static DownstreamPlaceholder unknownCaOutput( static DownstreamPlaceholder unknownCaOutput(
const StorePath & drvPath, const StorePath & drvPath,
std::string_view outputName); std::string_view outputName,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/** /**
* Create a placehold for the output of an unknown derivation. * Create a placehold for the output of an unknown derivation.

View File

@ -5,17 +5,24 @@
namespace nix { namespace nix {
TEST(DownstreamPlaceholder, unknownCaOutput) { TEST(DownstreamPlaceholder, unknownCaOutput) {
/**
* We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently.
*/
ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "ca-derivations");
ASSERT_EQ( ASSERT_EQ(
DownstreamPlaceholder::unknownCaOutput( DownstreamPlaceholder::unknownCaOutput(
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv" }, StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv" },
"out").render(), "out",
mockXpSettings).render(),
"/0c6rn30q4frawknapgwq386zq358m8r6msvywcvc89n6m5p2dgbz"); "/0c6rn30q4frawknapgwq386zq358m8r6msvywcvc89n6m5p2dgbz");
} }
TEST(DownstreamPlaceholder, unknownDerivation) { TEST(DownstreamPlaceholder, unknownDerivation) {
/** /**
* We set these in tests rather than the regular globals so we don't have * Same reason as above
* to worry about race conditions if the tests run concurrently.
*/ */
ExperimentalFeatureSettings mockXpSettings; ExperimentalFeatureSettings mockXpSettings;
mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations"); mockXpSettings.set("experimental-features", "dynamic-derivations ca-derivations");
@ -24,7 +31,8 @@ TEST(DownstreamPlaceholder, unknownDerivation) {
DownstreamPlaceholder::unknownDerivation( DownstreamPlaceholder::unknownDerivation(
DownstreamPlaceholder::unknownCaOutput( DownstreamPlaceholder::unknownCaOutput(
StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv.drv" }, StorePath { "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv.drv" },
"out"), "out",
mockXpSettings),
"out", "out",
mockXpSettings).render(), mockXpSettings).render(),
"/0gn6agqxjyyalf0dpihgyf49xq5hqxgw100f0wydnj6yqrhqsb3w"); "/0gn6agqxjyyalf0dpihgyf49xq5hqxgw100f0wydnj6yqrhqsb3w");

View File

@ -12,7 +12,7 @@ struct ExperimentalFeatureDetails
std::string_view description; std::string_view description;
}; };
constexpr std::array<ExperimentalFeatureDetails, 16> xpFeatureDetails = {{ constexpr std::array<ExperimentalFeatureDetails, 15> xpFeatureDetails = {{
{ {
.tag = Xp::CaDerivations, .tag = Xp::CaDerivations,
.name = "ca-derivations", .name = "ca-derivations",
@ -182,15 +182,6 @@ constexpr std::array<ExperimentalFeatureDetails, 16> xpFeatureDetails = {{
the [`use-cgroups`](#conf-use-cgroups) setting for details. the [`use-cgroups`](#conf-use-cgroups) setting for details.
)", )",
}, },
{
.tag = Xp::DiscardReferences,
.name = "discard-references",
.description = R"(
Allow the use of the [`unsafeDiscardReferences`](@docroot@/language/advanced-attributes.html#adv-attr-unsafeDiscardReferences) attribute in derivations
that use [structured attributes](@docroot@/language/advanced-attributes.html#adv-attr-structuredAttrs). This disables scanning of outputs for
runtime dependencies.
)",
},
{ {
.tag = Xp::DaemonTrustOverride, .tag = Xp::DaemonTrustOverride,
.name = "daemon-trust-override", .name = "daemon-trust-override",

View File

@ -27,7 +27,6 @@ enum struct ExperimentalFeature
ReplFlake, ReplFlake,
AutoAllocateUids, AutoAllocateUids,
Cgroups, Cgroups,
DiscardReferences,
DaemonTrustOverride, DaemonTrustOverride,
DynamicDerivations, DynamicDerivations,
ParseTomlTimestamps, ParseTomlTimestamps,

View File

@ -1,4 +1,5 @@
#include "json-utils.hh" #include "json-utils.hh"
#include "error.hh"
namespace nix { namespace nix {
@ -16,4 +17,27 @@ nlohmann::json * get(nlohmann::json & map, const std::string & key)
return &*i; return &*i;
} }
const nlohmann::json & valueAt(
const nlohmann::json & map,
const std::string & key)
{
if (!map.contains(key))
throw Error("Expected JSON object to contain key '%s' but it doesn't", key);
return map[key];
}
const nlohmann::json & ensureType(
const nlohmann::json & value,
nlohmann::json::value_type expectedType
)
{
if (value.type() != expectedType)
throw Error(
"Expected JSON value to be of type '%s' but it is of type '%s'",
nlohmann::json(expectedType).type_name(),
value.type_name());
return value;
}
} }

View File

@ -10,6 +10,28 @@ const nlohmann::json * get(const nlohmann::json & map, const std::string & key);
nlohmann::json * get(nlohmann::json & map, const std::string & key); nlohmann::json * get(nlohmann::json & map, const std::string & key);
/**
* Get the value of a json object at a key safely, failing
* with a Nix Error if the key does not exist.
*
* Use instead of nlohmann::json::at() to avoid ugly exceptions.
*
* _Does not check whether `map` is an object_, use `ensureType` for that.
*/
const nlohmann::json & valueAt(
const nlohmann::json & map,
const std::string & key);
/**
* Ensure the type of a json object is what you expect, failing
* with a Nix Error if it isn't.
*
* Use before type conversions and element access to avoid ugly exceptions.
*/
const nlohmann::json & ensureType(
const nlohmann::json & value,
nlohmann::json::value_type expectedType);
/** /**
* For `adl_serializer<std::optional<T>>` below, we need to track what * For `adl_serializer<std::optional<T>>` below, we need to track what
* types are not already using `null`. Only for them can we use `null` * types are not already using `null`. Only for them can we use `null`

View File

@ -22,6 +22,7 @@ StringPairs resolveRewrites(
StringPairs res; StringPairs res;
for (auto & dep : dependencies) for (auto & dep : dependencies)
if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path)) if (auto drvDep = std::get_if<BuiltPathBuilt>(&dep.path))
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
for (auto & [ outputName, outputPath ] : drvDep->outputs) for (auto & [ outputName, outputPath ] : drvDep->outputs)
res.emplace( res.emplace(
DownstreamPlaceholder::unknownCaOutput(drvDep->drvPath, outputName).render(), DownstreamPlaceholder::unknownCaOutput(drvDep->drvPath, outputName).render(),

View File

@ -239,7 +239,7 @@ struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
if (pos != std::string::npos) { if (pos != std::string::npos) {
size_t margin = 32; size_t margin = 32;
auto pos2 = pos >= margin ? pos - margin : 0; auto pos2 = pos >= margin ? pos - margin : 0;
hits[hash].emplace_back(fmt("%s: …%s…\n", hits[hash].emplace_back(fmt("%s: …%s…",
p2, p2,
hilite(filterPrintable( hilite(filterPrintable(
std::string(contents, pos2, pos - pos2 + hash.size() + margin)), std::string(contents, pos2, pos - pos2 + hash.size() + margin)),
@ -255,7 +255,7 @@ struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
for (auto & hash : hashes) { for (auto & hash : hashes) {
auto pos = target.find(hash); auto pos = target.find(hash);
if (pos != std::string::npos) if (pos != std::string::npos)
hits[hash].emplace_back(fmt("%s -> %s\n", p2, hits[hash].emplace_back(fmt("%s -> %s", p2,
hilite(target, pos, StorePath::HashLen, getColour(hash)))); hilite(target, pos, StorePath::HashLen, getColour(hash))));
} }
} }
@ -272,9 +272,9 @@ struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions
for (auto & hit : hits[hash]) { for (auto & hit : hits[hash]) {
bool first = hit == *hits[hash].begin(); bool first = hit == *hits[hash].begin();
std::cout << tailPad logger->cout("%s%s%s", tailPad,
<< (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine)) (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine)),
<< hit; hit);
if (!all) break; if (!all) break;
} }

View File

@ -42,8 +42,10 @@ nix-build -o $RESULT check-refs.nix -A test7
nix-build -o $RESULT check-refs.nix -A test10 nix-build -o $RESULT check-refs.nix -A test10
if isDaemonNewer 2.12pre20230103; then if isDaemonNewer 2.12pre20230103; then
if ! isDaemonNewer 2.16.0; then
enableFeatures discard-references enableFeatures discard-references
restartDaemon restartDaemon
fi
# test11 should succeed. # test11 should succeed.
test11=$(nix-build -o $RESULT check-refs.nix -A test11) test11=$(nix-build -o $RESULT check-refs.nix -A test11)

View File

@ -27,6 +27,7 @@ test_file_flake_input () {
mkdir inputs mkdir inputs
echo foo > inputs/test_input_file echo foo > inputs/test_input_file
echo '{ outputs = { self }: { }; }' > inputs/flake.nix
tar cfa test_input.tar.gz inputs tar cfa test_input.tar.gz inputs
cp test_input.tar.gz test_input_no_ext cp test_input.tar.gz test_input_no_ext
input_tarball_hash="$(nix hash path test_input.tar.gz)" input_tarball_hash="$(nix hash path test_input.tar.gz)"
@ -50,6 +51,9 @@ test_file_flake_input () {
url = "file+file://$PWD/test_input.tar.gz"; url = "file+file://$PWD/test_input.tar.gz";
flake = false; flake = false;
}; };
inputs.flake_no_ext = {
url = "file://$PWD/test_input_no_ext";
};
outputs = { ... }: {}; outputs = { ... }: {};
} }
EOF EOF
@ -58,7 +62,7 @@ EOF
nix eval --file - <<EOF nix eval --file - <<EOF
with (builtins.fromJSON (builtins.readFile ./flake.lock)); with (builtins.fromJSON (builtins.readFile ./flake.lock));
# Url inputs whose extension doesnt match a known archive format should # Non-flake inputs whose extension doesnt match a known archive format should
# not be unpacked by default # not be unpacked by default
assert (nodes.no_ext_default_no_unpack.locked.type == "file"); assert (nodes.no_ext_default_no_unpack.locked.type == "file");
assert (nodes.no_ext_default_no_unpack.locked.unpack or false == false); assert (nodes.no_ext_default_no_unpack.locked.unpack or false == false);
@ -75,8 +79,16 @@ EOF
# Explicitely passing the unpack parameter should enforce the desired behavior # Explicitely passing the unpack parameter should enforce the desired behavior
assert (nodes.no_ext_explicit_unpack.locked.narHash == nodes.tarball_default_unpack.locked.narHash); assert (nodes.no_ext_explicit_unpack.locked.narHash == nodes.tarball_default_unpack.locked.narHash);
assert (nodes.tarball_explicit_no_unpack.locked.narHash == nodes.no_ext_default_no_unpack.locked.narHash); assert (nodes.tarball_explicit_no_unpack.locked.narHash == nodes.no_ext_default_no_unpack.locked.narHash);
# Flake inputs should always be tarballs
assert (nodes.flake_no_ext.locked.type == "tarball");
true true
EOF EOF
# Test tarball URLs on the command line.
[[ $(nix flake metadata --json file://$PWD/test_input_no_ext | jq -r .resolved.type) = tarball ]]
popd popd
[[ -z "${NIX_DAEMON_PACKAGE-}" ]] && return 0 [[ -z "${NIX_DAEMON_PACKAGE-}" ]] && return 0

View File

@ -22,3 +22,8 @@ echo "$PRECISE_WHY_DEPENDS_OUTPUT" | grepQuiet input-2
# But only the “precise” one should refer to `reference-to-input-2` # But only the “precise” one should refer to `reference-to-input-2`
echo "$FAST_WHY_DEPENDS_OUTPUT" | grepQuietInverse reference-to-input-2 echo "$FAST_WHY_DEPENDS_OUTPUT" | grepQuietInverse reference-to-input-2
echo "$PRECISE_WHY_DEPENDS_OUTPUT" | grepQuiet reference-to-input-2 echo "$PRECISE_WHY_DEPENDS_OUTPUT" | grepQuiet reference-to-input-2
<<<"$PRECISE_WHY_DEPENDS_OUTPUT" sed -n '2p' | grepQuiet "└───reference-to-input-2 -> "
<<<"$PRECISE_WHY_DEPENDS_OUTPUT" sed -n '3p' | grep " →" | grepQuiet "dependencies-input-2"
<<<"$PRECISE_WHY_DEPENDS_OUTPUT" sed -n '4p' | grepQuiet " └───input0: …" # in input-2, file input0
<<<"$PRECISE_WHY_DEPENDS_OUTPUT" sed -n '5p' | grep " →" | grepQuiet "dependencies-input-0" # is dependencies-input-0 referenced