mirror of
https://github.com/NixOS/nix.git
synced 2024-11-22 14:52:55 +00:00
Merge remote-tracking branch 'origin/master' into overlayfs-store
This commit is contained in:
commit
c0e6466a1e
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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" &&
|
||||||
|
@ -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 {};
|
||||||
|
|
||||||
|
@ -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 {};
|
||||||
|
|
||||||
|
@ -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" &&
|
||||||
|
@ -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 {};
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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");
|
||||||
|
@ -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",
|
||||||
|
@ -27,7 +27,6 @@ enum struct ExperimentalFeature
|
|||||||
ReplFlake,
|
ReplFlake,
|
||||||
AutoAllocateUids,
|
AutoAllocateUids,
|
||||||
Cgroups,
|
Cgroups,
|
||||||
DiscardReferences,
|
|
||||||
DaemonTrustOverride,
|
DaemonTrustOverride,
|
||||||
DynamicDerivations,
|
DynamicDerivations,
|
||||||
ParseTomlTimestamps,
|
ParseTomlTimestamps,
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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`
|
||||||
|
@ -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(),
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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 doesn’t match a known archive format should
|
# Non-flake inputs whose extension doesn’t 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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user