1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-07 01:51:47 +02:00

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

This commit is contained in:
John Ericson 2023-10-23 13:13:37 -04:00
commit 5c1cb0b696
941 changed files with 10981 additions and 4439 deletions

View file

@ -314,7 +314,7 @@ connected:
//
// 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations
if (!drv.inputDrvs.empty())
if (!drv.inputDrvs.map.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv);
auto & result = *optResult;
@ -322,7 +322,12 @@ connected:
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
} else {
copyClosure(*store, *sshStore, StorePathSet {*drvPath}, NoRepair, NoCheckSigs, substitute);
auto res = sshStore->buildPathsWithResults({ DerivedPath::Built { *drvPath, OutputsSpec::All {} } });
auto res = sshStore->buildPathsWithResults({
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All {},
}
});
// One path to build should produce exactly one build result
assert(res.size() == 1);
optResult = std::move(res[0]);

View file

@ -8,13 +8,39 @@
namespace nix {
nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
nlohmann::json res;
res["drvPath"] = store->printStorePath(drvPath);
for (const auto& [output, path] : outputs) {
res["outputs"][output] = store->printStorePath(path);
#define CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, COMPARATOR) \
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
{ \
const MY_TYPE* me = this; \
auto fields1 = std::make_tuple<const CHILD_TYPE &, const FIELD_TYPE &>(*me->drvPath, me->FIELD); \
me = &other; \
auto fields2 = std::make_tuple<const CHILD_TYPE &, const FIELD_TYPE &>(*me->drvPath, me->FIELD); \
return fields1 COMPARATOR fields2; \
}
return res;
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, ==) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
#define FIELD_TYPE std::pair<std::string, StorePath>
CMP(SingleBuiltPath, SingleBuiltPathBuilt, output)
#undef FIELD_TYPE
#define FIELD_TYPE std::map<std::string, StorePath>
CMP(SingleBuiltPath, BuiltPathBuilt, outputs)
#undef FIELD_TYPE
#undef CMP
#undef CMP_ONE
StorePath SingleBuiltPath::outPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) { return p.path; },
[](const SingleBuiltPath::Built & b) { return b.output.second; },
}, raw()
);
}
StorePathSet BuiltPath::outPaths() const
@ -32,6 +58,62 @@ StorePathSet BuiltPath::outPaths() const
);
}
SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
{
return SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
.output = output.first,
};
}
SingleDerivedPath SingleBuiltPath::discardOutputPath() const
{
return std::visit(
overloaded{
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath {
return p;
},
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath {
return b.discardOutputPath();
},
}, raw()
);
}
nlohmann::json BuiltPath::Built::toJSON(const Store & store) const
{
nlohmann::json res;
res["drvPath"] = drvPath->toJSON(store);
for (const auto & [outputName, outputPath] : outputs) {
res["outputs"][outputName] = store.printStorePath(outputPath);
}
return res;
}
nlohmann::json SingleBuiltPath::Built::toJSON(const Store & store) const
{
nlohmann::json res;
res["drvPath"] = drvPath->toJSON(store);
auto & [outputName, outputPath] = output;
res["output"] = outputName;
res["outputPath"] = store.printStorePath(outputPath);
return res;
}
nlohmann::json SingleBuiltPath::toJSON(const Store & store) const
{
return std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw());
}
nlohmann::json BuiltPath::toJSON(const Store & store) const
{
return std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw());
}
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
{
RealisedPath::Set res;
@ -40,7 +122,7 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
[&](const BuiltPath::Opaque & p) { res.insert(p.path); },
[&](const BuiltPath::Built & p) {
auto drvHashes =
staticOutputHashes(store, store.readDerivation(p.drvPath));
staticOutputHashes(store, store.readDerivation(p.drvPath->outPath()));
for (auto& [outputName, outputPath] : p.outputs) {
if (experimentalFeatureSettings.isEnabled(
Xp::CaDerivations)) {
@ -48,7 +130,7 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
if (!drvOutput)
throw Error(
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
store.printStorePath(p.drvPath), outputName);
store.printStorePath(p.drvPath->outPath()), outputName);
auto thisRealisation = store.queryRealisation(
DrvOutput{*drvOutput, outputName});
assert(thisRealisation); // Weve built it, so we must

View file

@ -3,19 +3,64 @@
namespace nix {
struct SingleBuiltPath;
struct SingleBuiltPathBuilt {
ref<SingleBuiltPath> drvPath;
std::pair<std::string, StorePath> output;
SingleDerivedPathBuilt discardOutputPath() const;
std::string to_string(const Store & store) const;
static SingleBuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
nlohmann::json toJSON(const Store & store) const;
DECLARE_CMP(SingleBuiltPathBuilt);
};
using _SingleBuiltPathRaw = std::variant<
DerivedPathOpaque,
SingleBuiltPathBuilt
>;
struct SingleBuiltPath : _SingleBuiltPathRaw {
using Raw = _SingleBuiltPathRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = SingleBuiltPathBuilt;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
StorePath outPath() const;
SingleDerivedPath discardOutputPath() const;
static SingleBuiltPath parse(const Store & store, std::string_view);
nlohmann::json toJSON(const Store & store) const;
};
static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath)
{
return make_ref<SingleBuiltPath>(SingleBuiltPath::Opaque { drvPath });
}
/**
* A built derived path with hints in the form of optional concrete output paths.
*
* See 'BuiltPath' for more an explanation.
*/
struct BuiltPathBuilt {
StorePath drvPath;
ref<SingleBuiltPath> drvPath;
std::map<std::string, StorePath> outputs;
nlohmann::json toJSON(ref<Store> store) const;
static BuiltPathBuilt parse(const Store & store, std::string_view);
std::string to_string(const Store & store) const;
static BuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
nlohmann::json toJSON(const Store & store) const;
GENERATE_CMP(BuiltPathBuilt, me->drvPath, me->outputs);
DECLARE_CMP(BuiltPathBuilt);
};
using _BuiltPathRaw = std::variant<
@ -41,6 +86,7 @@ struct BuiltPath : _BuiltPathRaw {
StorePathSet outPaths() const;
RealisedPath::Set toRealisedPaths(Store & store) const;
nlohmann::json toJSON(const Store & store) const;
};
typedef std::vector<BuiltPath> BuiltPaths;

View file

@ -98,7 +98,7 @@ EvalCommand::EvalCommand()
EvalCommand::~EvalCommand()
{
if (evalState)
evalState->printStats();
evalState->maybePrintStats();
}
ref<Store> EvalCommand::getEvalStore()

View file

@ -34,21 +34,28 @@ struct NixMultiCommand : virtual MultiCommand, virtual Command
// For the overloaded run methods
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
/* A command that requires a Nix store. */
/**
* A command that requires a \ref Store "Nix store".
*/
struct StoreCommand : virtual Command
{
StoreCommand();
void run() override;
ref<Store> getStore();
virtual ref<Store> createStore();
/**
* Main entry point, with a `Store` provided
*/
virtual void run(ref<Store>) = 0;
private:
std::shared_ptr<Store> _store;
};
/* A command that copies something between `--from` and `--to`
stores. */
/**
* A command that copies something between `--from` and `--to` \ref
* Store stores.
*/
struct CopyCommand : virtual StoreCommand
{
std::string srcUri, dstUri;
@ -60,6 +67,9 @@ struct CopyCommand : virtual StoreCommand
ref<Store> getDstStore();
};
/**
* A command that needs to evaluate Nix language expressions.
*/
struct EvalCommand : virtual StoreCommand, MixEvalArgs
{
bool startReplOnEvalErrors = false;
@ -79,20 +89,26 @@ private:
std::shared_ptr<EvalState> evalState;
};
/**
* A mixin class for commands that process flakes, adding a few standard
* flake-related options/flags.
*/
struct MixFlakeOptions : virtual Args, EvalCommand
{
flake::LockFlags lockFlags;
std::optional<std::string> needsFlakeInputCompletion = {};
MixFlakeOptions();
virtual std::vector<std::string> getFlakesForCompletion()
/**
* The completion for some of these flags depends on the flake(s) in
* question.
*
* This method should be implemented to gather all flakerefs the
* command is operating with (presumably specified via some other
* arguments) so that the completions for these flags can use them.
*/
virtual std::vector<FlakeRef> getFlakeRefsForCompletion()
{ return {}; }
void completeFlakeInput(std::string_view prefix);
void completionHook() override;
};
struct SourceExprCommand : virtual Args, MixFlakeOptions
@ -112,15 +128,35 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
virtual Strings getDefaultFlakeAttrPathPrefixes();
void completeInstallable(std::string_view prefix);
/**
* Complete an installable from the given prefix.
*/
void completeInstallable(AddCompletions & completions, std::string_view prefix);
/**
* Convenience wrapper around the underlying function to make setting the
* callback easier.
*/
CompleterClosure getCompleteInstallable();
};
/**
* A mixin class for commands that need a read-only flag.
*
* What exactly is "read-only" is unspecified, but it will usually be
* the \ref Store "Nix store".
*/
struct MixReadOnlyOption : virtual Args
{
MixReadOnlyOption();
};
/* Like InstallablesCommand but the installables are not loaded */
/**
* Like InstallablesCommand but the installables are not loaded.
*
* This is needed by `CmdRepl` which wants to load (and reload) the
* installables itself.
*/
struct RawInstallablesCommand : virtual Args, SourceExprCommand
{
RawInstallablesCommand();
@ -129,19 +165,22 @@ struct RawInstallablesCommand : virtual Args, SourceExprCommand
void run(ref<Store> store) override;
// FIXME make const after CmdRepl's override is fixed up
// FIXME make const after `CmdRepl`'s override is fixed up
virtual void applyDefaultInstallables(std::vector<std::string> & rawInstallables);
bool readFromStdIn = false;
std::vector<std::string> getFlakesForCompletion() override;
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
private:
std::vector<std::string> rawInstallables;
};
/* A command that operates on a list of "installables", which can be
store paths, attribute paths, Nix expressions, etc. */
/**
* A command that operates on a list of "installables", which can be
* store paths, attribute paths, Nix expressions, etc.
*/
struct InstallablesCommand : RawInstallablesCommand
{
virtual void run(ref<Store> store, Installables && installables) = 0;
@ -149,7 +188,9 @@ struct InstallablesCommand : RawInstallablesCommand
void run(ref<Store> store, std::vector<std::string> && rawInstallables) override;
};
/* A command that operates on exactly one "installable" */
/**
* A command that operates on exactly one "installable".
*/
struct InstallableCommand : virtual Args, SourceExprCommand
{
InstallableCommand();
@ -158,10 +199,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand
void run(ref<Store> store) override;
std::vector<std::string> getFlakesForCompletion() override
{
return {_installable};
}
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
private:
@ -175,7 +213,12 @@ struct MixOperateOnOptions : virtual Args
MixOperateOnOptions();
};
/* A command that operates on zero or more store paths. */
/**
* A command that operates on zero or more extant store paths.
*
* If the argument the user passes is a some sort of recipe for a path
* not yet built, it must be built first.
*/
struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions
{
private:
@ -207,7 +250,9 @@ struct StorePathsCommand : public BuiltPathsCommand
void run(ref<Store> store, BuiltPaths && paths) override;
};
/* A command that operates on exactly one store path. */
/**
* A command that operates on exactly one store path.
*/
struct StorePathCommand : public StorePathsCommand
{
virtual void run(ref<Store> store, const StorePath & storePath) = 0;
@ -215,7 +260,9 @@ struct StorePathCommand : public StorePathsCommand
void run(ref<Store> store, StorePaths && storePaths) override;
};
/* A helper class for registering commands globally. */
/**
* A helper class for registering \ref Command commands globally.
*/
struct RegisterCommand
{
typedef std::map<std::vector<std::string>, std::function<ref<Command>()>> Commands;
@ -271,13 +318,18 @@ struct MixEnvironment : virtual Args {
MixEnvironment();
/* Modify global environ based on ignoreEnvironment, keep, and unset. It's expected that exec will be called before this class goes out of scope, otherwise environ will become invalid. */
/***
* Modify global environ based on `ignoreEnvironment`, `keep`, and
* `unset`. It's expected that exec will be called before this class
* goes out of scope, otherwise `environ` will become invalid.
*/
void setEnviron();
};
void completeFlakeRef(ref<Store> store, std::string_view prefix);
void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::string_view prefix);
void completeFlakeRefWithFragment(
AddCompletions & completions,
ref<EvalState> evalState,
flake::LockFlags lockFlags,
Strings attrPathPrefixes,

View file

@ -9,6 +9,7 @@
#include "flake/flakeref.hh"
#include "store-api.hh"
#include "command.hh"
#include "tarball.hh"
namespace nix {
@ -132,8 +133,8 @@ MixEvalArgs::MixEvalArgs()
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
}},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(openStore(), prefix);
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeRef(completions, openStore(), prefix);
}}
});
@ -168,14 +169,14 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s)
{
if (EvalSettings::isPseudoUrl(s)) {
auto storePath = fetchers::downloadTarball(
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).tree.storePath;
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).storePath;
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
}
else if (hasPrefix(s, "flake:")) {
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
}

View file

@ -80,7 +80,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
return e;
},
}, extendedOutputsSpec.raw());
}, extendedOutputsSpec.raw);
auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
@ -92,10 +92,11 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
for (auto & [drvPath, outputs] : byDrvPath)
res.push_back({
.path = DerivedPath::Built {
.drvPath = drvPath,
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = outputs,
},
.info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value {
.extendedOutputsSpec = outputs,
/* FIXME: reconsider backwards compatibility above
so we can fill in this info. */
}),
@ -114,7 +115,7 @@ InstallableAttrPath InstallableAttrPath::parse(
return {
state, cmd, v,
prefix == "." ? "" : std::string { prefix },
extendedOutputsSpec
std::move(extendedOutputsSpec),
};
}

View file

@ -18,14 +18,7 @@ DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths()
std::optional<StorePath> InstallableDerivedPath::getStorePath()
{
return std::visit(overloaded {
[&](const DerivedPath::Built & bfd) {
return bfd.drvPath;
},
[&](const DerivedPath::Opaque & bo) {
return bo.path;
},
}, derivedPath.raw());
return derivedPath.getBaseStorePath();
}
InstallableDerivedPath InstallableDerivedPath::parse(
@ -42,7 +35,7 @@ InstallableDerivedPath InstallableDerivedPath::parse(
// Remove this prior to stabilizing the new CLI.
if (storePath.isDerivation()) {
auto oldDerivedPath = DerivedPath::Built {
.drvPath = storePath,
.drvPath = makeConstantStorePathRef(storePath),
.outputs = OutputsSpec::All { },
};
warn(
@ -55,12 +48,14 @@ InstallableDerivedPath InstallableDerivedPath::parse(
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = make_ref<SingleDerivedPath>(SingleDerivedPath::parse(*store, prefix));
drvRequireExperiment(*drv);
return DerivedPath::Built {
.drvPath = store->parseStorePath(prefix),
.drvPath = std::move(drv),
.outputs = outputSpec,
};
},
}, extendedOutputsSpec.raw());
}, extendedOutputsSpec.raw);
return InstallableDerivedPath {
store,
std::move(derivedPath),

View file

@ -28,6 +28,11 @@ namespace nix {
std::vector<std::string> InstallableFlake::getActualAttrPaths()
{
std::vector<std::string> res;
if (attrPaths.size() == 1 && attrPaths.front().starts_with(".")){
attrPaths.front().erase(0,1);
res.push_back(attrPaths.front());
return res;
}
for (auto & prefix : prefixes)
res.push_back(prefix + *attrPaths.begin());
@ -118,7 +123,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
return {{
.path = DerivedPath::Built {
.drvPath = std::move(drvPath),
.drvPath = makeConstantStorePathRef(std::move(drvPath)),
.outputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
@ -141,7 +146,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
return e;
},
}, extendedOutputsSpec.raw()),
}, extendedOutputsSpec.raw),
},
.info = make_ref<ExtraPathInfoFlake>(
ExtraPathInfoValue::Value {

View file

@ -55,7 +55,8 @@ std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths
else if (v.type() == nString) {
return {{
.path = state->coerceToDerivedPath(pos, v, errorCtx),
.path = DerivedPath::fromSingle(
state->coerceToSingleDerivedPath(pos, v, errorCtx)),
.info = make_ref<ExtraPathInfo>(),
}};
}

View file

@ -28,6 +28,20 @@
namespace nix {
static void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
const std::vector<FlakeRef> & flakeRefs,
std::string_view prefix)
{
for (auto & flakeRef : flakeRefs) {
auto flake = flake::getFlake(*evalState, flakeRef, true);
for (auto & input : flake.inputs)
if (hasPrefix(input.first, prefix))
completions.add(input.first);
}
}
MixFlakeOptions::MixFlakeOptions()
{
auto category = "Common flake-related options";
@ -79,8 +93,8 @@ MixFlakeOptions::MixFlakeOptions()
.handler = {[&](std::string s) {
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
}},
.completer = {[&](size_t, std::string_view prefix) {
needsFlakeInputCompletion = {std::string(prefix)};
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
}}
});
@ -95,11 +109,12 @@ MixFlakeOptions::MixFlakeOptions()
flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath("."), true));
}},
.completer = {[&](size_t n, std::string_view prefix) {
if (n == 0)
needsFlakeInputCompletion = {std::string(prefix)};
else if (n == 1)
completeFlakeRef(getEvalState()->store, prefix);
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
if (n == 0) {
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
} else if (n == 1) {
completeFlakeRef(completions, getEvalState()->store, prefix);
}
}}
});
@ -146,30 +161,12 @@ MixFlakeOptions::MixFlakeOptions()
}
}
}},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(getEvalState()->store, prefix);
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeRef(completions, getEvalState()->store, prefix);
}}
});
}
void MixFlakeOptions::completeFlakeInput(std::string_view prefix)
{
auto evalState = getEvalState();
for (auto & flakeRefS : getFlakesForCompletion()) {
auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first;
auto flake = flake::getFlake(*evalState, flakeRef, true);
for (auto & input : flake.inputs)
if (hasPrefix(input.first, prefix))
completions->add(input.first);
}
}
void MixFlakeOptions::completionHook()
{
if (auto & prefix = needsFlakeInputCompletion)
completeFlakeInput(*prefix);
}
SourceExprCommand::SourceExprCommand()
{
addFlag({
@ -226,11 +223,18 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
};
}
void SourceExprCommand::completeInstallable(std::string_view prefix)
Args::CompleterClosure SourceExprCommand::getCompleteInstallable()
{
return [this](AddCompletions & completions, size_t, std::string_view prefix) {
completeInstallable(completions, prefix);
};
}
void SourceExprCommand::completeInstallable(AddCompletions & completions, std::string_view prefix)
{
try {
if (file) {
completionType = ctAttrs;
completions.setType(AddCompletions::Type::Attrs);
evalSettings.pureEval = false;
auto state = getEvalState();
@ -265,14 +269,15 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
std::string name = state->symbols[i.name];
if (name.find(searchWord) == 0) {
if (prefix_ == "")
completions->add(name);
completions.add(name);
else
completions->add(prefix_ + "." + name);
completions.add(prefix_ + "." + name);
}
}
}
} else {
completeFlakeRefWithFragment(
completions,
getEvalState(),
lockFlags,
getDefaultFlakeAttrPathPrefixes(),
@ -285,6 +290,7 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
}
void completeFlakeRefWithFragment(
AddCompletions & completions,
ref<EvalState> evalState,
flake::LockFlags lockFlags,
Strings attrPathPrefixes,
@ -296,11 +302,16 @@ void completeFlakeRefWithFragment(
try {
auto hash = prefix.find('#');
if (hash == std::string::npos) {
completeFlakeRef(evalState->store, prefix);
completeFlakeRef(completions, evalState->store, prefix);
} else {
completionType = ctAttrs;
completions.setType(AddCompletions::Type::Attrs);
auto fragment = prefix.substr(hash + 1);
std::string prefixRoot = "";
if (fragment.starts_with(".")){
fragment = fragment.substr(1);
prefixRoot = ".";
}
auto flakeRefS = std::string(prefix.substr(0, hash));
auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
@ -309,6 +320,9 @@ void completeFlakeRefWithFragment(
auto root = evalCache->getRoot();
if (prefixRoot == "."){
attrPathPrefixes.clear();
}
/* Complete 'fragment' relative to all the
attrpath prefixes as well as the root of the
flake. */
@ -333,7 +347,7 @@ void completeFlakeRefWithFragment(
auto attrPath2 = (*attr)->getAttrPath(attr2);
/* Strip the attrpath prefix. */
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
completions->add(flakeRefS + "#" + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
}
}
}
@ -344,7 +358,7 @@ void completeFlakeRefWithFragment(
for (auto & attrPath : defaultFlakeAttrPaths) {
auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
if (!attr) continue;
completions->add(flakeRefS + "#");
completions.add(flakeRefS + "#" + prefixRoot);
}
}
}
@ -353,15 +367,15 @@ void completeFlakeRefWithFragment(
}
}
void completeFlakeRef(ref<Store> store, std::string_view prefix)
void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::string_view prefix)
{
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
return;
if (prefix == "")
completions->add(".");
completions.add(".");
completeDir(0, prefix);
Args::completeDir(completions, 0, prefix);
/* Look for registry entries that match the prefix. */
for (auto & registry : fetchers::getRegistries(store)) {
@ -370,10 +384,10 @@ void completeFlakeRef(ref<Store> store, std::string_view prefix)
if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
std::string from2(from, 6);
if (hasPrefix(from2, prefix))
completions->add(from2);
completions.add(from2);
} else {
if (hasPrefix(from, prefix))
completions->add(from);
completions.add(from);
}
}
}
@ -459,7 +473,7 @@ Installables SourceExprCommand::parseInstallables(
result.push_back(
make_ref<InstallableAttrPath>(
InstallableAttrPath::parse(
state, *this, vFile, prefix, extendedOutputsSpec)));
state, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
}
} else {
@ -475,7 +489,7 @@ Installables SourceExprCommand::parseInstallables(
if (prefix.find('/') != std::string::npos) {
try {
result.push_back(make_ref<InstallableDerivedPath>(
InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec)));
InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec.raw)));
continue;
} catch (BadStorePath &) {
} catch (...) {
@ -491,7 +505,7 @@ Installables SourceExprCommand::parseInstallables(
getEvalState(),
std::move(flakeRef),
fragment,
extendedOutputsSpec,
std::move(extendedOutputsSpec),
getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(),
lockFlags));
@ -515,6 +529,30 @@ ref<Installable> SourceExprCommand::parseInstallable(
return installables.front();
}
static SingleBuiltPath getBuiltPath(ref<Store> evalStore, ref<Store> store, const SingleDerivedPath & b)
{
return std::visit(
overloaded{
[&](const SingleDerivedPath::Opaque & bo) -> SingleBuiltPath {
return SingleBuiltPath::Opaque { bo.path };
},
[&](const SingleDerivedPath::Built & bfd) -> SingleBuiltPath {
auto drvPath = getBuiltPath(evalStore, store, *bfd.drvPath);
// Resolving this instead of `bfd` will yield the same result, but avoid duplicative work.
SingleDerivedPath::Built truncatedBfd {
.drvPath = makeConstantStorePathRef(drvPath.outPath()),
.output = bfd.output,
};
auto outputPath = resolveDerivedPath(*store, truncatedBfd, &*evalStore);
return SingleBuiltPath::Built {
.drvPath = make_ref<SingleBuiltPath>(std::move(drvPath)),
.output = { bfd.output, outputPath },
};
},
},
b.raw());
}
std::vector<BuiltPathWithResult> Installable::build(
ref<Store> evalStore,
ref<Store> store,
@ -568,7 +606,10 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
[&](const DerivedPath::Built & bfd) {
auto outputs = resolveDerivedPath(*store, bfd, &*evalStore);
res.push_back({aux.installable, {
.path = BuiltPath::Built { bfd.drvPath, outputs },
.path = BuiltPath::Built {
.drvPath = make_ref<SingleBuiltPath>(getBuiltPath(evalStore, store, *bfd.drvPath)),
.outputs = outputs,
},
.info = aux.info}});
},
[&](const DerivedPath::Opaque & bo) {
@ -597,7 +638,10 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
for (auto & [outputName, realisation] : buildResult.builtOutputs)
outputs.emplace(outputName, realisation.outPath);
res.push_back({aux.installable, {
.path = BuiltPath::Built { bfd.drvPath, outputs },
.path = BuiltPath::Built {
.drvPath = make_ref<SingleBuiltPath>(getBuiltPath(evalStore, store, *bfd.drvPath)),
.outputs = outputs,
},
.info = aux.info,
.result = buildResult}});
},
@ -691,7 +735,7 @@ StorePathSet Installable::toDerivations(
: throw Error("argument '%s' did not evaluate to a derivation", i->what()));
},
[&](const DerivedPath::Built & bfd) {
drvPaths.insert(bfd.drvPath);
drvPaths.insert(resolveDerivedPath(*store, *bfd.drvPath));
},
}, b.path.raw());
@ -709,9 +753,7 @@ RawInstallablesCommand::RawInstallablesCommand()
expectArgs({
.label = "installables",
.handler = {&rawInstallables},
.completer = {[&](size_t, std::string_view prefix) {
completeInstallable(prefix);
}}
.completer = getCompleteInstallable(),
});
}
@ -724,6 +766,17 @@ void RawInstallablesCommand::applyDefaultInstallables(std::vector<std::string> &
}
}
std::vector<FlakeRef> RawInstallablesCommand::getFlakeRefsForCompletion()
{
applyDefaultInstallables(rawInstallables);
std::vector<FlakeRef> res;
for (auto i : rawInstallables)
res.push_back(parseFlakeRefWithFragment(
expandTilde(i),
absPath(".")).first);
return res;
}
void RawInstallablesCommand::run(ref<Store> store)
{
if (readFromStdIn && !isatty(STDIN_FILENO)) {
@ -737,10 +790,13 @@ void RawInstallablesCommand::run(ref<Store> store)
run(store, std::move(rawInstallables));
}
std::vector<std::string> RawInstallablesCommand::getFlakesForCompletion()
std::vector<FlakeRef> InstallableCommand::getFlakeRefsForCompletion()
{
applyDefaultInstallables(rawInstallables);
return rawInstallables;
return {
parseFlakeRefWithFragment(
expandTilde(_installable),
absPath(".")).first
};
}
void InstallablesCommand::run(ref<Store> store, std::vector<std::string> && rawInstallables)
@ -756,9 +812,7 @@ InstallableCommand::InstallableCommand()
.label = "installable",
.optional = true,
.handler = {&_installable},
.completer = {[&](size_t, std::string_view prefix) {
completeInstallable(prefix);
}}
.completer = getCompleteInstallable(),
});
}

View file

@ -488,35 +488,40 @@ bool NixRepl::processLine(std::string line)
std::cout
<< "The following commands are available:\n"
<< "\n"
<< " <expr> Evaluate and print expression\n"
<< " <x> = <expr> Bind expression to variable\n"
<< " :a <expr> Add attributes from resulting set to scope\n"
<< " :b <expr> Build a derivation\n"
<< " :bl <expr> Build a derivation, creating GC roots in the working directory\n"
<< " :e <expr> Open package or function in $EDITOR\n"
<< " :i <expr> Build derivation, then install result into current profile\n"
<< " :l <path> Load Nix expression and add it to scope\n"
<< " :lf <ref> Load Nix flake and add it to scope\n"
<< " :p <expr> Evaluate and print expression recursively\n"
<< " :q Exit nix-repl\n"
<< " :r Reload all files\n"
<< " :sh <expr> Build dependencies of derivation, then start nix-shell\n"
<< " :t <expr> Describe result of evaluation\n"
<< " :u <expr> Build derivation, then start nix-shell\n"
<< " :doc <expr> Show documentation of a builtin function\n"
<< " :log <expr> Show logs for a derivation\n"
<< " :te [bool] Enable, disable or toggle showing traces for errors\n"
<< " <expr> Evaluate and print expression\n"
<< " <x> = <expr> Bind expression to variable\n"
<< " :a, :add <expr> Add attributes from resulting set to scope\n"
<< " :b <expr> Build a derivation\n"
<< " :bl <expr> Build a derivation, creating GC roots in the\n"
<< " working directory\n"
<< " :e, :edit <expr> Open package or function in $EDITOR\n"
<< " :i <expr> Build derivation, then install result into\n"
<< " current profile\n"
<< " :l, :load <path> Load Nix expression and add it to scope\n"
<< " :lf, :load-flake <ref> Load Nix flake and add it to scope\n"
<< " :p, :print <expr> Evaluate and print expression recursively\n"
<< " :q, :quit Exit nix-repl\n"
<< " :r, :reload Reload all files\n"
<< " :sh <expr> Build dependencies of derivation, then start\n"
<< " nix-shell\n"
<< " :t <expr> Describe result of evaluation\n"
<< " :u <expr> Build derivation, then start nix-shell\n"
<< " :doc <expr> Show documentation of a builtin function\n"
<< " :log <expr> Show logs for a derivation\n"
<< " :te, :trace-enable [bool] Enable, disable or toggle showing traces for\n"
<< " errors\n"
<< " :?, :help Brings up this help menu\n"
;
if (state->debugRepl) {
std::cout
<< "\n"
<< " Debug mode commands\n"
<< " :env Show env stack\n"
<< " :bt Show trace stack\n"
<< " :st Show current trace\n"
<< " :st <idx> Change to another trace in the stack\n"
<< " :c Go until end of program, exception, or builtins.break\n"
<< " :s Go one step\n"
<< " :env Show env stack\n"
<< " :bt, :backtrace Show trace stack\n"
<< " :st Show current trace\n"
<< " :st <idx> Change to another trace in the stack\n"
<< " :c, :continue Go until end of program, exception, or builtins.break\n"
<< " :s, :step Go one step\n"
;
}
@ -648,7 +653,7 @@ bool NixRepl::processLine(std::string line)
if (command == ":b" || command == ":bl") {
state->store->buildPaths({
DerivedPath::Built {
.drvPath = drvPath,
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All { },
},
});
@ -917,7 +922,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
case nString:
str << ANSI_WARNING;
printLiteralString(str, v.string.s);
printLiteralString(str, v.string_view());
str << ANSI_NORMAL;
break;

View file

@ -132,7 +132,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
if (colon == std::string::npos) fail();
std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {CanonPath(fn.substr(0, colon)), lineno};
return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno};
} catch (std::invalid_argument & e) {
fail();
abort();

View file

@ -50,7 +50,7 @@ struct AttrDb
Path cacheDir = getCacheDir() + "/nix/eval-cache-v5";
createDirs(cacheDir);
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
Path dbPath = cacheDir + "/" + fingerprint.to_string(HashFormat::Base16, false) + ".sqlite";
state->db = SQLite(dbPath);
state->db.isCache();
@ -440,8 +440,8 @@ Value & AttrCursor::forceValue()
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
cachedValue = {root->db->setString(getKey(), v.c_str(), v.context()),
string_t{v.c_str(), {}}};
else if (v.type() == nPath) {
auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
@ -582,7 +582,7 @@ std::string AttrCursor::getString()
if (v.type() != nString && v.type() != nPath)
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
return v.type() == nString ? v.string.s : v.path().to_string();
return v.type() == nString ? v.c_str() : v.path().to_string();
}
string_t AttrCursor::getStringWithContext()
@ -599,12 +599,12 @@ string_t AttrCursor::getStringWithContext()
return d.drvPath;
},
[&](const NixStringContextElem::Built & b) -> const StorePath & {
return b.drvPath;
return b.drvPath->getBaseStorePath();
},
[&](const NixStringContextElem::Opaque & o) -> const StorePath & {
return o.path;
},
}, c.raw());
}, c.raw);
if (!root->state.store->isValidPath(path)) {
valid = false;
break;
@ -624,7 +624,7 @@ string_t AttrCursor::getStringWithContext()
if (v.type() == nString) {
NixStringContext context;
copyContext(v, context);
return {v.string.s, std::move(context)};
return {v.c_str(), std::move(context)};
}
else if (v.type() == nPath)
return {v.path().to_string(), {}};

View file

@ -63,7 +63,7 @@ Strings EvalSettings::getDefaultNixPath()
};
if (!evalSettings.restrictEval && !evalSettings.pureEval) {
add(settings.useXDGBaseDirectories ? getStateDir() + "/nix/defexpr/channels" : getHome() + "/.nix-defexpr/channels");
add(getNixDefExpr() + "/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
}
@ -92,4 +92,11 @@ EvalSettings evalSettings;
static GlobalConfig::Register rEvalSettings(&evalSettings);
Path getNixDefExpr()
{
return settings.useXDGBaseDirectories
? getStateDir() + "/nix/defexpr"
: getHome() + "/.nix-defexpr";
}
}

View file

@ -21,7 +21,7 @@ struct EvalSettings : Config
R"(
List of directories to be searched for `<...>` file references
In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of
In particular, outside of [pure evaluation mode](#conf-pure-eval), this determines the value of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath).
)"};
@ -29,10 +29,12 @@ struct EvalSettings : Config
this, false, "restrict-eval",
R"(
If set to `true`, the Nix evaluator will not allow access to any
files outside of the Nix search path (as set via the `NIX_PATH`
environment variable or the `-I` option), or to URIs outside of
[`allowed-uris`](../command-ref/conf-file.md#conf-allowed-uris).
The default is `false`.
files outside of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath),
or to URIs outside of
[`allowed-uris`](@docroot@/command-ref/conf-file.md#conf-allowed-uris).
Also the default value for [`nix-path`](#conf-nix-path) is ignored, such that only explicitly set search path entries are taken into account.
)"};
Setting<bool> pureEval{this, false, "pure-eval",
@ -40,18 +42,22 @@ struct EvalSettings : Config
Pure evaluation mode ensures that the result of Nix expressions is fully determined by explicitly declared inputs, and not influenced by external state:
- Restrict file system and network access to files specified by cryptographic hash
- Disable [`bultins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
- Disable impure constants:
- [`bultins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem)
- [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
- [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath)
)"
};
Setting<bool> enableImportFromDerivation{
this, true, "allow-import-from-derivation",
R"(
By default, Nix allows you to `import` from a derivation, allowing
building at evaluation time. With this option set to false, Nix will
throw an error when evaluating an expression that uses this feature,
allowing users to ensure their evaluation will not require any
builds to take place.
By default, Nix allows [Import from Derivation](@docroot@/language/import-from-derivation.md).
With this option set to `false`, Nix will throw an error when evaluating an expression that uses this feature,
even when the required store object is readily available.
This ensures that evaluation will not require any builds to take place,
regardless of the state of the store.
)"};
Setting<Strings> allowedUris{this, {}, "allowed-uris",
@ -95,4 +101,9 @@ struct EvalSettings : Config
extern EvalSettings evalSettings;
/**
* Conventionally part of the default nix path in impure mode.
*/
Path getNixDefExpr();
}

View file

@ -12,6 +12,8 @@
#include "function-trace.hh"
#include "profiles.hh"
#include "print.hh"
#include "fs-input-accessor.hh"
#include "memory-input-accessor.hh"
#include <algorithm>
#include <chrono>
@ -114,7 +116,7 @@ void Value::print(const SymbolTable &symbols, std::ostream &str,
printLiteralBool(str, boolean);
break;
case tString:
printLiteralString(str, string.s);
printLiteralString(str, string_view());
break;
case tPath:
str << path().to_string(); // !!! escaping?
@ -339,7 +341,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
Value nameValue;
name.expr->eval(state, env, nameValue);
state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name");
return state.symbols.create(nameValue.string.s);
return state.symbols.create(nameValue.string_view());
}
}
@ -503,7 +505,17 @@ EvalState::EvalState(
, sOutputSpecified(symbols.create("outputSpecified"))
, repair(NoRepair)
, emptyBindings(0)
, derivationInternal(rootPath(CanonPath("/builtin/derivation.nix")))
, rootFS(makeFSInputAccessor(CanonPath::root))
, corepkgsFS(makeMemoryInputAccessor())
, internalFS(makeMemoryInputAccessor())
, derivationInternal{corepkgsFS->addFile(
CanonPath("derivation-internal.nix"),
#include "primops/derivation.nix.gen.hh"
)}
, callFlakeInternal{internalFS->addFile(
CanonPath("call-flake.nix"),
#include "flake/call-flake.nix.gen.hh"
)}
, store(store)
, buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr)
@ -527,9 +539,9 @@ EvalState::EvalState(
/* Initialise the Nix expression search path. */
if (!evalSettings.pureEval) {
for (auto & i : _searchPath.elements)
addToSearchPath(SearchPath::Elem {i});
searchPath.elements.emplace_back(SearchPath::Elem {i});
for (auto & i : evalSettings.nixPath.get())
addToSearchPath(SearchPath::Elem::parse(i));
searchPath.elements.emplace_back(SearchPath::Elem::parse(i));
}
if (evalSettings.restrictEval || evalSettings.pureEval) {
@ -539,7 +551,7 @@ EvalState::EvalState(
auto r = resolveSearchPathPath(i.path);
if (!r) continue;
auto path = *std::move(r);
auto path = std::move(*r);
if (store->isInStore(path)) {
try {
@ -555,6 +567,11 @@ EvalState::EvalState(
}
}
corepkgsFS->addFile(
CanonPath("fetchurl.nix"),
#include "fetchurl.nix.gen.hh"
);
createBaseEnv();
}
@ -585,6 +602,9 @@ void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value &
SourcePath EvalState::checkSourcePath(const SourcePath & path_)
{
// Don't check non-rootFS accessors, they're in a different namespace.
if (path_.accessor != ref<InputAccessor>(rootFS)) return path_;
if (!allowedPaths) return path_;
auto i = resolvedPaths.find(path_.path.abs());
@ -599,8 +619,6 @@ SourcePath EvalState::checkSourcePath(const SourcePath & path_)
*/
Path abspath = canonPath(path_.path.abs());
if (hasPrefix(abspath, corepkgsPrefix)) return CanonPath(abspath);
for (auto & i : *allowedPaths) {
if (isDirOrInDir(abspath, i)) {
found = true;
@ -617,7 +635,7 @@ SourcePath EvalState::checkSourcePath(const SourcePath & path_)
/* Resolve symlinks. */
debug("checking access to '%s'", abspath);
SourcePath path = CanonPath(canonPath(abspath, true));
SourcePath path = rootPath(CanonPath(canonPath(abspath, true)));
for (auto & i : *allowedPaths) {
if (isDirOrInDir(path.path.abs(), i)) {
@ -649,12 +667,12 @@ void EvalState::checkURI(const std::string & uri)
/* If the URI is a path, then check it against allowedPaths as
well. */
if (hasPrefix(uri, "/")) {
checkSourcePath(CanonPath(uri));
checkSourcePath(rootPath(CanonPath(uri)));
return;
}
if (hasPrefix(uri, "file://")) {
checkSourcePath(CanonPath(std::string(uri, 7)));
checkSourcePath(rootPath(CanonPath(std::string(uri, 7))));
return;
}
@ -950,7 +968,7 @@ void Value::mkStringMove(const char * s, const NixStringContext & context)
void Value::mkPath(const SourcePath & path)
{
mkPath(makeImmutableString(path.path.abs()));
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
}
@ -1027,24 +1045,67 @@ void EvalState::mkStorePathString(const StorePath & p, Value & v)
}
std::string EvalState::mkOutputStringRaw(
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings)
{
/* In practice, this is testing for the case of CA derivations, or
dynamic derivations. */
return optStaticOutputPath
? store->printStorePath(std::move(*optStaticOutputPath))
/* Downstream we would substitute this for an actual path once
we build the floating CA derivation */
: DownstreamPlaceholder::fromSingleDerivedPathBuilt(b, xpSettings).render();
}
void EvalState::mkOutputString(
Value & value,
const StorePath & drvPath,
const std::string outputName,
std::optional<StorePath> optOutputPath,
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings)
{
value.mkString(
optOutputPath
? store->printStorePath(*std::move(optOutputPath))
/* Downstream we would substitute this for an actual path once
we build the floating CA derivation */
: DownstreamPlaceholder::unknownCaOutput(drvPath, outputName, xpSettings).render(),
mkOutputStringRaw(b, optStaticOutputPath, xpSettings),
NixStringContext { b });
}
std::string EvalState::mkSingleDerivedPathStringRaw(
const SingleDerivedPath & p)
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
return store->printStorePath(o.path);
},
[&](const SingleDerivedPath::Built & b) {
auto optStaticOutputPath = std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
auto drv = store->readDerivation(o.path);
auto i = drv.outputs.find(b.output);
if (i == drv.outputs.end())
throw Error("derivation '%s' does not have output '%s'", b.drvPath->to_string(*store), b.output);
return i->second.path(*store, drv.name, b.output);
},
[&](const SingleDerivedPath::Built & o) -> std::optional<StorePath> {
return std::nullopt;
},
}, b.drvPath->raw());
return mkOutputStringRaw(b, optStaticOutputPath);
}
}, p.raw());
}
void EvalState::mkSingleDerivedPathString(
const SingleDerivedPath & p,
Value & v)
{
v.mkString(
mkSingleDerivedPathStringRaw(p),
NixStringContext {
NixStringContextElem::Built {
.drvPath = drvPath,
.output = outputName,
}
std::visit([](auto && v) -> NixStringContextElem { return v; }, p),
});
}
@ -1122,24 +1183,6 @@ void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial
if (!e)
e = parseExprFromFile(checkSourcePath(resolvedPath));
cacheFile(path, resolvedPath, e, v, mustBeTrivial);
}
void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
}
void EvalState::cacheFile(
const SourcePath & path,
const SourcePath & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial)
{
fileParseCache[resolvedPath] = e;
try {
@ -1168,6 +1211,13 @@ void EvalState::cacheFile(
}
void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
}
void EvalState::eval(Expr * e, Value & v)
{
e->eval(*this, baseEnv, v);
@ -1300,7 +1350,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
if (nameVal.type() == nNull)
continue;
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
auto nameSym = state.symbols.create(nameVal.string.s);
auto nameSym = state.symbols.create(nameVal.string_view());
Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end())
state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
@ -1994,7 +2044,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) {
if (!context.empty())
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(CanonPath(canonPath(str())));
v.mkPath(state.rootPath(CanonPath(canonPath(str()))));
} else
v.mkStringMove(c_str(), context);
}
@ -2112,7 +2162,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
forceValue(v, pos);
if (v.type() != nString)
error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
return v.string.s;
return v.string_view();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@ -2139,8 +2189,8 @@ std::string_view EvalState::forceString(Value & v, NixStringContext & context, c
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
{
auto s = forceString(v, pos, errorCtx);
if (v.string.context) {
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
if (v.context()) {
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
}
return s;
}
@ -2153,7 +2203,7 @@ bool EvalState::isDerivation(Value & v)
if (i == v.attrs->end()) return false;
forceValue(*i->value, i->pos);
if (i->value->type() != nString) return false;
return strcmp(i->value->string.s, "derivation") == 0;
return i->value->string_view().compare("derivation") == 0;
}
@ -2185,7 +2235,7 @@ BackedStringView EvalState::coerceToString(
if (v.type() == nString) {
copyContext(v, context);
return std::string_view(v.string.s);
return v.string_view();
}
if (v.type() == nPath) {
@ -2193,7 +2243,7 @@ BackedStringView EvalState::coerceToString(
!canonicalizePath && !copyToStore
? // FIXME: hack to preserve path literals that end in a
// slash, as in /foo/${x}.
v._path
v._path.path
: copyToStore
? store->printStorePath(copyPathToStore(context, v.path()))
: std::string(v.path().path.abs());
@ -2247,7 +2297,7 @@ BackedStringView EvalState::coerceToString(
&& (!v2->isList() || v2->listSize() != 0))
result += " ";
}
return std::move(result);
return result;
}
}
@ -2267,7 +2317,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
auto dstPath = i != srcToStore.end()
? i->second
: [&]() {
auto dstPath = path.fetchToStore(store, path.baseName(), nullptr, repair);
auto dstPath = path.fetchToStore(store, path.baseName(), FileIngestionMethod::Recursive, nullptr, repair);
allowPath(dstPath);
srcToStore.insert_or_assign(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
@ -2283,10 +2333,34 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx)
{
try {
forceValue(v, pos);
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
/* Handle path values directly, without coercing to a string. */
if (v.type() == nPath)
return v.path();
/* Similarly, handle __toString where the result may be a path
value. */
if (v.type() == nAttrs) {
auto i = v.attrs->find(sToString);
if (i != v.attrs->end()) {
Value v1;
callFunction(*i->value, v, v1, pos);
return coerceToPath(pos, v1, context, errorCtx);
}
}
/* Any other value should be coercable to a string, interpreted
relative to the root filesystem. */
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return CanonPath(path);
return rootPath(CanonPath(path));
}
@ -2299,7 +2373,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon
}
std::pair<DerivedPath, std::string_view> EvalState::coerceToDerivedPathUnchecked(const PosIdx pos, Value & v, std::string_view errorCtx)
std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedPathUnchecked(const PosIdx pos, Value & v, std::string_view errorCtx)
{
NixStringContext context;
auto s = forceString(v, context, pos, errorCtx);
@ -2310,23 +2384,18 @@ std::pair<DerivedPath, std::string_view> EvalState::coerceToDerivedPathUnchecked
s, csize)
.withTrace(pos, errorCtx).debugThrow<EvalError>();
auto derivedPath = std::visit(overloaded {
[&](NixStringContextElem::Opaque && o) -> DerivedPath {
return DerivedPath::Opaque {
.path = std::move(o.path),
};
[&](NixStringContextElem::Opaque && o) -> SingleDerivedPath {
return std::move(o);
},
[&](NixStringContextElem::DrvDeep &&) -> DerivedPath {
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
error(
"string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time",
s).withTrace(pos, errorCtx).debugThrow<EvalError>();
},
[&](NixStringContextElem::Built && b) -> DerivedPath {
return DerivedPath::Built {
.drvPath = std::move(b.drvPath),
.outputs = OutputsSpec::Names { std::move(b.output) },
};
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
return std::move(b);
},
}, ((NixStringContextElem &&) *context.begin()).raw());
}, ((NixStringContextElem &&) *context.begin()).raw);
return {
std::move(derivedPath),
std::move(s),
@ -2334,41 +2403,29 @@ std::pair<DerivedPath, std::string_view> EvalState::coerceToDerivedPathUnchecked
}
DerivedPath EvalState::coerceToDerivedPath(const PosIdx pos, Value & v, std::string_view errorCtx)
SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value & v, std::string_view errorCtx)
{
auto [derivedPath, s_] = coerceToDerivedPathUnchecked(pos, v, errorCtx);
auto [derivedPath, s_] = coerceToSingleDerivedPathUnchecked(pos, v, errorCtx);
auto s = s_;
std::visit(overloaded {
[&](const DerivedPath::Opaque & o) {
auto sExpected = store->printStorePath(o.path);
if (s != sExpected)
auto sExpected = mkSingleDerivedPathStringRaw(derivedPath);
if (s != sExpected) {
/* `std::visit` is used here just to provide a more precise
error message. */
std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
error(
"path string '%s' has context with the different path '%s'",
s, sExpected)
.withTrace(pos, errorCtx).debugThrow<EvalError>();
},
[&](const DerivedPath::Built & b) {
// TODO need derived path with single output to make this
// total. Will add as part of RFC 92 work and then this is
// cleaned up.
auto output = *std::get<OutputsSpec::Names>(b.outputs).begin();
auto drv = store->readDerivation(b.drvPath);
auto i = drv.outputs.find(output);
if (i == drv.outputs.end())
throw Error("derivation '%s' does not have output '%s'", store->printStorePath(b.drvPath), output);
auto optOutputPath = i->second.path(*store, drv.name, output);
// This is testing for the case of CA derivations
auto sExpected = optOutputPath
? store->printStorePath(*optOutputPath)
: DownstreamPlaceholder::unknownCaOutput(b.drvPath, output).render();
if (s != sExpected)
},
[&](const SingleDerivedPath::Built & b) {
error(
"string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'",
s, output, store->printStorePath(b.drvPath), sExpected)
s, b.output, b.drvPath->to_string(*store), sExpected)
.withTrace(pos, errorCtx).debugThrow<EvalError>();
}
}, derivedPath.raw());
}
}, derivedPath.raw());
}
return derivedPath;
}
@ -2400,10 +2457,13 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return v1.boolean == v2.boolean;
case nString:
return strcmp(v1.string.s, v2.string.s) == 0;
return v1.string_view().compare(v2.string_view()) == 0;
case nPath:
return strcmp(v1._path, v2._path) == 0;
return
// FIXME: compare accessors by their fingerprint.
v1._path.accessor == v2._path.accessor
&& strcmp(v1._path.path, v2._path.path) == 0;
case nNull:
return true;
@ -2451,10 +2511,37 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
}
}
void EvalState::printStats()
bool EvalState::fullGC() {
#if HAVE_BOEHMGC
GC_gcollect();
// Check that it ran. We might replace this with a version that uses more
// of the boehm API to get this reliably, at a maintenance cost.
// We use a 1K margin because technically this has a race condtion, but we
// probably won't encounter it in practice, because the CLI isn't concurrent
// like that.
return GC_get_bytes_since_gc() < 1024;
#else
return false;
#endif
}
void EvalState::maybePrintStats()
{
bool showStats = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
if (showStats) {
// Make the final heap size more deterministic.
#if HAVE_BOEHMGC
if (!fullGC()) {
warn("failed to perform a full GC before reporting stats");
}
#endif
printStatistics();
}
}
void EvalState::printStatistics()
{
struct rusage buf;
getrusage(RUSAGE_SELF, &buf);
float cpuTime = buf.ru_utime.tv_sec + ((float) buf.ru_utime.tv_usec / 1000000);
@ -2468,105 +2555,105 @@ void EvalState::printStats()
GC_word heapSize, totalBytes;
GC_get_heap_usage_safe(&heapSize, 0, 0, 0, &totalBytes);
#endif
if (showStats) {
auto outPath = getEnv("NIX_SHOW_STATS_PATH").value_or("-");
std::fstream fs;
if (outPath != "-")
fs.open(outPath, std::fstream::out);
json topObj = json::object();
topObj["cpuTime"] = cpuTime;
topObj["envs"] = {
{"number", nrEnvs},
{"elements", nrValuesInEnvs},
{"bytes", bEnvs},
};
topObj["list"] = {
{"elements", nrListElems},
{"bytes", bLists},
{"concats", nrListConcats},
};
topObj["values"] = {
{"number", nrValues},
{"bytes", bValues},
};
topObj["symbols"] = {
{"number", symbols.size()},
{"bytes", symbols.totalSize()},
};
topObj["sets"] = {
{"number", nrAttrsets},
{"bytes", bAttrsets},
{"elements", nrAttrsInAttrsets},
};
topObj["sizes"] = {
{"Env", sizeof(Env)},
{"Value", sizeof(Value)},
{"Bindings", sizeof(Bindings)},
{"Attr", sizeof(Attr)},
};
topObj["nrOpUpdates"] = nrOpUpdates;
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
topObj["nrThunks"] = nrThunks;
topObj["nrAvoided"] = nrAvoided;
topObj["nrLookups"] = nrLookups;
topObj["nrPrimOpCalls"] = nrPrimOpCalls;
topObj["nrFunctionCalls"] = nrFunctionCalls;
auto outPath = getEnv("NIX_SHOW_STATS_PATH").value_or("-");
std::fstream fs;
if (outPath != "-")
fs.open(outPath, std::fstream::out);
json topObj = json::object();
topObj["cpuTime"] = cpuTime;
topObj["envs"] = {
{"number", nrEnvs},
{"elements", nrValuesInEnvs},
{"bytes", bEnvs},
};
topObj["nrExprs"] = Expr::nrExprs;
topObj["list"] = {
{"elements", nrListElems},
{"bytes", bLists},
{"concats", nrListConcats},
};
topObj["values"] = {
{"number", nrValues},
{"bytes", bValues},
};
topObj["symbols"] = {
{"number", symbols.size()},
{"bytes", symbols.totalSize()},
};
topObj["sets"] = {
{"number", nrAttrsets},
{"bytes", bAttrsets},
{"elements", nrAttrsInAttrsets},
};
topObj["sizes"] = {
{"Env", sizeof(Env)},
{"Value", sizeof(Value)},
{"Bindings", sizeof(Bindings)},
{"Attr", sizeof(Attr)},
};
topObj["nrOpUpdates"] = nrOpUpdates;
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
topObj["nrThunks"] = nrThunks;
topObj["nrAvoided"] = nrAvoided;
topObj["nrLookups"] = nrLookups;
topObj["nrPrimOpCalls"] = nrPrimOpCalls;
topObj["nrFunctionCalls"] = nrFunctionCalls;
#if HAVE_BOEHMGC
topObj["gc"] = {
{"heapSize", heapSize},
{"totalBytes", totalBytes},
};
topObj["gc"] = {
{"heapSize", heapSize},
{"totalBytes", totalBytes},
};
#endif
if (countCalls) {
topObj["primops"] = primOpCalls;
{
auto& list = topObj["functions"];
list = json::array();
for (auto & [fun, count] : functionCalls) {
json obj = json::object();
if (fun->name)
obj["name"] = (std::string_view) symbols[fun->name];
else
obj["name"] = nullptr;
if (auto pos = positions[fun->pos]) {
if (auto path = std::get_if<SourcePath>(&pos.origin))
obj["file"] = path->to_string();
obj["line"] = pos.line;
obj["column"] = pos.column;
}
obj["count"] = count;
list.push_back(obj);
}
}
{
auto list = topObj["attributes"];
list = json::array();
for (auto & i : attrSelects) {
json obj = json::object();
if (auto pos = positions[i.first]) {
if (auto path = std::get_if<SourcePath>(&pos.origin))
obj["file"] = path->to_string();
obj["line"] = pos.line;
obj["column"] = pos.column;
}
obj["count"] = i.second;
list.push_back(obj);
if (countCalls) {
topObj["primops"] = primOpCalls;
{
auto& list = topObj["functions"];
list = json::array();
for (auto & [fun, count] : functionCalls) {
json obj = json::object();
if (fun->name)
obj["name"] = (std::string_view) symbols[fun->name];
else
obj["name"] = nullptr;
if (auto pos = positions[fun->pos]) {
if (auto path = std::get_if<SourcePath>(&pos.origin))
obj["file"] = path->to_string();
obj["line"] = pos.line;
obj["column"] = pos.column;
}
obj["count"] = count;
list.push_back(obj);
}
}
{
auto list = topObj["attributes"];
list = json::array();
for (auto & i : attrSelects) {
json obj = json::object();
if (auto pos = positions[i.first]) {
if (auto path = std::get_if<SourcePath>(&pos.origin))
obj["file"] = path->to_string();
obj["line"] = pos.line;
obj["column"] = pos.column;
}
obj["count"] = i.second;
list.push_back(obj);
}
}
}
if (getEnv("NIX_SHOW_SYMBOLS").value_or("0") != "0") {
// XXX: overrides earlier assignment
topObj["symbols"] = json::array();
auto &list = topObj["symbols"];
symbols.dump([&](const std::string & s) { list.emplace_back(s); });
}
if (outPath == "-") {
std::cerr << topObj.dump(2) << std::endl;
} else {
fs << topObj.dump(2) << std::endl;
}
if (getEnv("NIX_SHOW_SYMBOLS").value_or("0") != "0") {
// XXX: overrides earlier assignment
topObj["symbols"] = json::array();
auto &list = topObj["symbols"];
symbols.dump([&](const std::string & s) { list.emplace_back(s); });
}
if (outPath == "-") {
std::cerr << topObj.dump(2) << std::endl;
} else {
fs << topObj.dump(2) << std::endl;
}
}

View file

@ -22,8 +22,10 @@ namespace nix {
class Store;
class EvalState;
class StorePath;
struct DerivedPath;
struct SingleDerivedPath;
enum RepairFlag : bool;
struct FSInputAccessor;
struct MemoryInputAccessor;
/**
@ -211,8 +213,26 @@ public:
Bindings emptyBindings;
/**
* The accessor for the root filesystem.
*/
const ref<FSInputAccessor> rootFS;
/**
* The in-memory filesystem for <nix/...> paths.
*/
const ref<MemoryInputAccessor> corepkgsFS;
/**
* In-memory filesystem for internal, non-user-callable Nix
* expressions like call-flake.nix.
*/
const ref<MemoryInputAccessor> internalFS;
const SourcePath derivationInternal;
const SourcePath callFlakeInternal;
/**
* Store used to materialise .drv files.
*/
@ -223,7 +243,6 @@ public:
*/
const ref<Store> buildStore;
RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr;
/**
@ -341,8 +360,6 @@ public:
std::shared_ptr<Store> buildStore = nullptr);
~EvalState();
void addToSearchPath(SearchPath::Elem && elem);
SearchPath getSearchPath() { return searchPath; }
/**
@ -407,16 +424,6 @@ public:
*/
void evalFile(const SourcePath & path, Value & v, bool mustBeTrivial = false);
/**
* Like `evalFile`, but with an already parsed expression.
*/
void cacheFile(
const SourcePath & path,
const SourcePath & resolvedPath,
Expr * e,
Value & v,
bool mustBeTrivial = false);
void resetFileCache();
/**
@ -426,7 +433,7 @@ public:
SourcePath findFile(const SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
/**
* Try to resolve a search path value (not the optinal key part)
* Try to resolve a search path value (not the optional key part)
*
* If the specified search path element is a URI, download it.
*
@ -532,12 +539,12 @@ public:
StorePath coerceToStorePath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx);
/**
* Part of `coerceToDerivedPath()` without any store IO which is exposed for unit testing only.
* Part of `coerceToSingleDerivedPath()` without any store IO which is exposed for unit testing only.
*/
std::pair<DerivedPath, std::string_view> coerceToDerivedPathUnchecked(const PosIdx pos, Value & v, std::string_view errorCtx);
std::pair<SingleDerivedPath, std::string_view> coerceToSingleDerivedPathUnchecked(const PosIdx pos, Value & v, std::string_view errorCtx);
/**
* Coerce to `DerivedPath`.
* Coerce to `SingleDerivedPath`.
*
* Must be a string which is either a literal store path or a
* "placeholder (see `DownstreamPlaceholder`).
@ -551,7 +558,7 @@ public:
* source of truth, and ultimately tells us what we want, and then
* we ensure the string corresponds to it.
*/
DerivedPath coerceToDerivedPath(const PosIdx pos, Value & v, std::string_view errorCtx);
SingleDerivedPath coerceToSingleDerivedPath(const PosIdx pos, Value & v, std::string_view errorCtx);
public:
@ -668,43 +675,68 @@ public:
/**
* Create a string representing a store path.
*
* The string is the printed store path with a context containing a single
* `NixStringContextElem::Opaque` element of that store path.
* The string is the printed store path with a context containing a
* single `NixStringContextElem::Opaque` element of that store path.
*/
void mkStorePathString(const StorePath & storePath, Value & v);
/**
* Create a string representing a `DerivedPath::Built`.
* Create a string representing a `SingleDerivedPath::Built`.
*
* The string is the printed store path with a context containing a single
* `NixStringContextElem::Built` element of the drv path and output name.
* The string is the printed store path with a context containing a
* single `NixStringContextElem::Built` element of the drv path and
* output name.
*
* @param value Value we are settings
*
* @param drvPath Path the drv whose output we are making a string for
* @param b the drv whose output we are making a string for, and the
* output
*
* @param outputName Name of the output
*
* @param optOutputPath Optional output path for that string. Must
* 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 used (see `DownstreamPlaceholder`).
* @param optStaticOutputPath Optional output path for that string.
* Must be passed if and only if output store object is
* input-addressed or fixed output. Will be printed to form string
* if passed, otherwise a placeholder will be used (see
* `DownstreamPlaceholder`).
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
void mkOutputString(
Value & value,
const StorePath & drvPath,
const std::string outputName,
std::optional<StorePath> optOutputPath,
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Create a string representing a `SingleDerivedPath`.
*
* A combination of `mkStorePathString` and `mkOutputString`.
*/
void mkSingleDerivedPathString(
const SingleDerivedPath & p,
Value & v);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
/**
* Print statistics.
* Print statistics, if enabled.
*
* Performs a full memory GC before printing the statistics, so that the
* GC statistics are more accurate.
*/
void printStats();
void maybePrintStats();
/**
* Print statistics, unconditionally, cheaply, without performing a GC first.
*/
void printStatistics();
/**
* Perform a full memory garbage collection - not incremental.
*
* @return true if Nix was built with GC and a GC was performed, false if not.
* The return value is currently not thread safe - just the return value.
*/
bool fullGC();
/**
* Realise the given context, and return a mapping from the placeholders
@ -714,6 +746,22 @@ public:
private:
/**
* Like `mkOutputString` but just creates a raw string, not an
* string Value, which would also have a string context.
*/
std::string mkOutputStringRaw(
const SingleDerivedPath::Built & b,
std::optional<StorePath> optStaticOutputPath,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Like `mkSingleDerivedPathStringRaw` but just creates a raw string
* Value, which would also have a string context.
*/
std::string mkSingleDerivedPathStringRaw(
const SingleDerivedPath & p);
unsigned long nrEnvs = 0;
unsigned long nrValuesInEnvs = 0;
unsigned long nrValues = 0;
@ -790,8 +838,6 @@ struct InvalidPathError : EvalError
#endif
};
static const std::string corepkgsPrefix{"/__corepkgs__/"};
template<class ErrorType>
void ErrorBuilder::debugThrow()
{

View file

@ -15,7 +15,7 @@ using namespace flake;
namespace flake {
typedef std::pair<fetchers::Tree, FlakeRef> FetchedFlake;
typedef std::pair<StorePath, FlakeRef> FetchedFlake;
typedef std::vector<std::pair<FlakeRef, FetchedFlake>> FlakeCache;
static std::optional<FetchedFlake> lookupInFlakeCache(
@ -34,7 +34,7 @@ static std::optional<FetchedFlake> lookupInFlakeCache(
return std::nullopt;
}
static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
static std::tuple<StorePath, FlakeRef, FlakeRef> fetchOrSubstituteTree(
EvalState & state,
const FlakeRef & originalRef,
bool allowLookup,
@ -61,16 +61,16 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
flakeCache.push_back({originalRef, *fetched});
}
auto [tree, lockedRef] = *fetched;
auto [storePath, lockedRef] = *fetched;
debug("got tree '%s' from '%s'",
state.store->printStorePath(tree.storePath), lockedRef);
state.store->printStorePath(storePath), lockedRef);
state.allowPath(tree.storePath);
state.allowPath(storePath);
assert(!originalRef.input.getNarHash() || tree.storePath == originalRef.input.computeStorePath(*state.store));
assert(!originalRef.input.getNarHash() || storePath == originalRef.input.computeStorePath(*state.store));
return {std::move(tree), resolvedRef, lockedRef};
return {std::move(storePath), resolvedRef, lockedRef};
}
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
@ -113,7 +113,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
try {
if (attr.name == sUrl) {
expectType(state, nString, *attr.value, attr.pos);
url = attr.value->string.s;
url = attr.value->string_view();
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
expectType(state, nBool, *attr.value, attr.pos);
@ -122,7 +122,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
} else if (attr.name == sFollows) {
expectType(state, nString, *attr.value, attr.pos);
auto follows(parseInputPath(attr.value->string.s));
auto follows(parseInputPath(attr.value->c_str()));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else {
@ -131,7 +131,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value->type()) {
case nString:
attrs.emplace(state.symbols[attr.name], attr.value->string.s);
attrs.emplace(state.symbols[attr.name], attr.value->c_str());
break;
case nBool:
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
@ -202,34 +202,34 @@ static Flake getFlake(
FlakeCache & flakeCache,
InputPath lockRootPath)
{
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache);
// Guard against symlink attacks.
auto flakeDir = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir, true);
auto flakeDir = canonPath(state.store->toRealPath(storePath) + "/" + lockedRef.subdir, true);
auto flakeFile = canonPath(flakeDir + "/flake.nix", true);
if (!isInDir(flakeFile, sourceInfo.actualPath))
if (!isInDir(flakeFile, state.store->toRealPath(storePath)))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
lockedRef, state.store->printStorePath(storePath));
Flake flake {
.originalRef = originalRef,
.resolvedRef = resolvedRef,
.lockedRef = lockedRef,
.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
.storePath = storePath,
};
if (!pathExists(flakeFile))
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo;
state.evalFile(CanonPath(flakeFile), vInfo, true); // FIXME: symlink attack
state.evalFile(state.rootPath(CanonPath(flakeFile)), vInfo, true); // FIXME: symlink attack
expectType(state, nAttrs, vInfo, state.positions.add({CanonPath(flakeFile)}, 1, 1));
expectType(state, nAttrs, vInfo, state.positions.add({state.rootPath(CanonPath(flakeFile))}, 1, 1));
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, nString, *description->value, description->pos);
flake.description = description->value->string.s;
flake.description = description->value->c_str();
}
auto sInputs = state.symbols.create("inputs");
@ -346,7 +346,7 @@ LockedFlake lockFlake(
// FIXME: symlink attack
auto oldLockFile = LockFile::read(
lockFlags.referenceLockFilePath.value_or(
flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock"));
state.store->toRealPath(flake.storePath) + "/" + flake.lockedRef.subdir + "/flake.lock"));
debug("old lock file: %s", oldLockFile);
@ -520,11 +520,6 @@ LockedFlake lockFlake(
}
}
auto localPath(parentPath);
// If this input is a path, recurse it down.
// This allows us to resolve path inputs relative to the current flake.
if ((*input.ref).input.getType() == "path")
localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
computeLocks(
mustRefetch
? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs
@ -579,7 +574,7 @@ LockedFlake lockFlake(
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root.get_ptr(),
state.store->toRealPath(inputFlake.storePath) + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root.get_ptr(),
oldLock ? lockRootPath : inputPath,
localPath,
false);
@ -603,7 +598,7 @@ LockedFlake lockFlake(
};
// Bring in the current ref for relative path resolution if we have it
auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
auto parentPath = canonPath(state.store->toRealPath(flake.storePath) + "/" + flake.lockedRef.subdir, true);
computeLocks(
flake.inputs,
@ -734,7 +729,7 @@ void callFlake(EvalState & state,
emitTreeAttrs(
state,
*lockedFlake.flake.sourceInfo,
lockedFlake.flake.storePath,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
false,
@ -742,14 +737,10 @@ void callFlake(EvalState & state,
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
if (!state.vCallFlake) {
state.vCallFlake = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root), **state.vCallFlake);
}
auto vCallFlake = state.allocValue();
state.evalFile(state.callFlakeInternal, *vCallFlake);
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
@ -855,7 +846,7 @@ static void prim_flakeRefToString(
Explicit<bool> { attr.value->boolean });
} else if (t == nString) {
attrs.emplace(state.symbols[attr.name],
std::string(attr.value->str()));
std::string(attr.value->string_view()));
} else {
state.error(
"flake reference attribute sets may only contain integers, Booleans, "
@ -898,7 +889,7 @@ Fingerprint LockedFlake::getFingerprint() const
// flake.sourceInfo.storePath for the fingerprint.
return hashString(htSHA256,
fmt("%s;%s;%d;%d;%s",
flake.sourceInfo->storePath.to_string(),
flake.storePath.to_string(),
flake.lockedRef.subdir,
flake.lockedRef.input.getRevCount().value_or(0),
flake.lockedRef.input.getLastModified().value_or(0),

View file

@ -10,8 +10,6 @@ namespace nix {
class EvalState;
namespace fetchers { struct Tree; }
namespace flake {
struct FlakeInput;
@ -84,7 +82,7 @@ struct Flake
*/
bool forceDirty = false;
std::optional<std::string> description;
std::shared_ptr<const fetchers::Tree> sourceInfo;
StorePath storePath;
FlakeInputs inputs;
/**
* 'nixConfig' attribute
@ -193,7 +191,7 @@ void callFlake(
void emitTreeAttrs(
EvalState & state,
const fetchers::Tree & tree,
const StorePath & storePath,
const fetchers::Input & input,
Value & v,
bool emptyRevFallback = false,

View file

@ -69,32 +69,130 @@ std::optional<FlakeRef> maybeParseFlakeRef(
}
}
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::pair<FlakeRef, std::string> parsePathFlakeRefWithFragment(
const std::string & url,
const std::optional<Path> & baseDir,
bool allowMissing,
bool isFlake)
{
using namespace fetchers;
std::string path = url;
std::string fragment = "";
std::map<std::string, std::string> query;
auto pathEnd = url.find_first_of("#?");
auto fragmentStart = pathEnd;
if (pathEnd != std::string::npos && url[pathEnd] == '?') {
fragmentStart = url.find("#");
}
if (pathEnd != std::string::npos) {
path = url.substr(0, pathEnd);
}
if (fragmentStart != std::string::npos) {
fragment = percentDecode(url.substr(fragmentStart+1));
}
if (pathEnd != std::string::npos && fragmentStart != std::string::npos) {
query = decodeQuery(url.substr(pathEnd+1, fragmentStart));
}
static std::string fnRegex = "[0-9a-zA-Z-._~!$&'\"()*+,;=]+";
if (baseDir) {
/* Check if 'url' is a path (either absolute or relative
to 'baseDir'). If so, search upward to the root of the
repo (i.e. the directory containing .git). */
static std::regex pathUrlRegex(
"(/?" + fnRegex + "(?:/" + fnRegex + ")*/?)"
+ "(?:\\?(" + queryRegex + "))?"
+ "(?:#(" + queryRegex + "))?",
std::regex::ECMAScript);
path = absPath(path, baseDir);
if (isFlake) {
if (!allowMissing && !pathExists(path + "/flake.nix")){
notice("path '%s' does not contain a 'flake.nix', searching up",path);
// Save device to detect filesystem boundary
dev_t device = lstat(path).st_dev;
bool found = false;
while (path != "/") {
if (pathExists(path + "/flake.nix")) {
found = true;
break;
} else if (pathExists(path + "/.git"))
throw Error("path '%s' is not part of a flake (neither it nor its parent directories contain a 'flake.nix' file)", path);
else {
if (lstat(path).st_dev != device)
throw Error("unable to find a flake before encountering filesystem boundary at '%s'", path);
}
path = dirOf(path);
}
if (!found)
throw BadURL("could not find a flake.nix file");
}
if (!S_ISDIR(lstat(path).st_mode))
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
if (!allowMissing && !pathExists(path + "/flake.nix"))
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
auto flakeRoot = path;
std::string subdir;
while (flakeRoot != "/") {
if (pathExists(flakeRoot + "/.git")) {
auto base = std::string("git+file://") + flakeRoot;
auto parsedURL = ParsedURL{
.url = base, // FIXME
.base = base,
.scheme = "git+file",
.authority = "",
.path = flakeRoot,
.query = query,
};
if (subdir != "") {
if (parsedURL.query.count("dir"))
throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
parsedURL.query.insert_or_assign("dir", subdir);
}
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
FlakeRef(fetchers::Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")),
fragment);
}
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
flakeRoot = dirOf(flakeRoot);
}
}
} else {
if (!hasPrefix(path, "/"))
throw BadURL("flake reference '%s' is not an absolute path", url);
path = canonPath(path + "/" + getOr(query, "dir", ""));
}
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "path");
attrs.insert_or_assign("path", path);
return std::make_pair(FlakeRef(fetchers::Input::fromAttrs(std::move(attrs)), ""), fragment);
};
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
std::optional<std::pair<FlakeRef, std::string>> parseFlakeIdRef(
const std::string & url,
bool isFlake
)
{
std::smatch match;
static std::regex flakeRegex(
"((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)"
+ "(?:#(" + queryRegex + "))?",
std::regex::ECMAScript);
std::smatch match;
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
if (std::regex_match(url, match, flakeRegex)) {
auto parsedURL = ParsedURL{
.url = url,
@ -105,111 +203,53 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
};
return std::make_pair(
FlakeRef(Input::fromURL(parsedURL, isFlake), ""),
FlakeRef(fetchers::Input::fromURL(parsedURL, isFlake), ""),
percentDecode(match.str(6)));
}
else if (std::regex_match(url, match, pathUrlRegex)) {
std::string path = match[1];
std::string fragment = percentDecode(match.str(3));
return {};
}
if (baseDir) {
/* Check if 'url' is a path (either absolute or relative
to 'baseDir'). If so, search upward to the root of the
repo (i.e. the directory containing .git). */
path = absPath(path, baseDir);
if (isFlake) {
if (!allowMissing && !pathExists(path + "/flake.nix")){
notice("path '%s' does not contain a 'flake.nix', searching up",path);
// Save device to detect filesystem boundary
dev_t device = lstat(path).st_dev;
bool found = false;
while (path != "/") {
if (pathExists(path + "/flake.nix")) {
found = true;
break;
} else if (pathExists(path + "/.git"))
throw Error("path '%s' is not part of a flake (neither it nor its parent directories contain a 'flake.nix' file)", path);
else {
if (lstat(path).st_dev != device)
throw Error("unable to find a flake before encountering filesystem boundary at '%s'", path);
}
path = dirOf(path);
}
if (!found)
throw BadURL("could not find a flake.nix file");
}
if (!S_ISDIR(lstat(path).st_mode))
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
if (!allowMissing && !pathExists(path + "/flake.nix"))
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
auto flakeRoot = path;
std::string subdir;
while (flakeRoot != "/") {
if (pathExists(flakeRoot + "/.git")) {
auto base = std::string("git+file://") + flakeRoot;
auto parsedURL = ParsedURL{
.url = base, // FIXME
.base = base,
.scheme = "git+file",
.authority = "",
.path = flakeRoot,
.query = decodeQuery(match[2]),
};
if (subdir != "") {
if (parsedURL.query.count("dir"))
throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
parsedURL.query.insert_or_assign("dir", subdir);
}
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
return std::make_pair(
FlakeRef(Input::fromURL(parsedURL, isFlake), getOr(parsedURL.query, "dir", "")),
fragment);
}
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
flakeRoot = dirOf(flakeRoot);
}
}
} else {
if (!hasPrefix(path, "/"))
throw BadURL("flake reference '%s' is not an absolute path", url);
auto query = decodeQuery(match[2]);
path = canonPath(path + "/" + getOr(query, "dir", ""));
}
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "path");
attrs.insert_or_assign("path", path);
return std::make_pair(FlakeRef(Input::fromAttrs(std::move(attrs)), ""), fragment);
std::optional<std::pair<FlakeRef, std::string>> parseURLFlakeRef(
const std::string & url,
const std::optional<Path> & baseDir,
bool isFlake
)
{
ParsedURL parsedURL;
try {
parsedURL = parseURL(url);
} catch (BadURL &) {
return std::nullopt;
}
else {
auto parsedURL = parseURL(url);
std::string fragment;
std::swap(fragment, parsedURL.fragment);
std::string fragment;
std::swap(fragment, parsedURL.fragment);
auto input = Input::fromURL(parsedURL, isFlake);
input.parent = baseDir;
auto input = fetchers::Input::fromURL(parsedURL, isFlake);
input.parent = baseDir;
return std::make_pair(
FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
fragment);
return std::make_pair(
FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
fragment);
}
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
const std::string & url,
const std::optional<Path> & baseDir,
bool allowMissing,
bool isFlake)
{
using namespace fetchers;
std::smatch match;
if (auto res = parseFlakeIdRef(url, isFlake)) {
return *res;
} else if (auto res = parseURLFlakeRef(url, baseDir, isFlake)) {
return *res;
} else {
return parsePathFlakeRefWithFragment(url, baseDir, allowMissing, isFlake);
}
}
@ -232,10 +272,10 @@ FlakeRef FlakeRef::fromAttrs(const fetchers::Attrs & attrs)
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
}
std::pair<fetchers::Tree, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
std::pair<StorePath, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
{
auto [tree, lockedInput] = input.fetch(store);
return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)};
auto [storePath, lockedInput] = input.fetch(store);
return {std::move(storePath), FlakeRef(std::move(lockedInput), subdir)};
}
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
@ -246,7 +286,9 @@ std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragment
{
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(url);
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, baseDir, allowMissing, isFlake);
return {std::move(flakeRef), fragment, extendedOutputsSpec};
return {std::move(flakeRef), fragment, std::move(extendedOutputsSpec)};
}
std::regex flakeIdRegex(flakeIdRegexS, std::regex::ECMAScript);
}

View file

@ -6,6 +6,7 @@
#include "fetchers.hh"
#include "outputs-spec.hh"
#include <regex>
#include <variant>
namespace nix {
@ -62,7 +63,7 @@ struct FlakeRef
static FlakeRef fromAttrs(const fetchers::Attrs & attrs);
std::pair<fetchers::Tree, FlakeRef> fetchTree(ref<Store> store) const;
std::pair<StorePath, FlakeRef> fetchTree(ref<Store> store) const;
};
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef);
@ -91,5 +92,7 @@ std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragment
bool allowMissing = false,
bool isFlake = true);
const static std::string flakeIdRegexS = "[a-zA-Z][a-zA-Z0-9_-]*";
extern std::regex flakeIdRegex;
}

View file

@ -2,8 +2,10 @@
#include "store-api.hh"
#include "url-parts.hh"
#include <algorithm>
#include <iomanip>
#include <iterator>
#include <nlohmann/json.hpp>
namespace nix::flake {
@ -45,16 +47,26 @@ StorePath LockedNode::computeStorePath(Store & store) const
return lockedRef.input.computeStorePath(store);
}
std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{
static std::shared_ptr<Node> doFind(const ref<Node>& root, const InputPath & path, std::vector<InputPath>& visited) {
auto pos = root;
auto found = std::find(visited.cbegin(), visited.cend(), path);
if(found != visited.end()) {
std::vector<std::string> cycle;
std::transform(found, visited.cend(), std::back_inserter(cycle), printInputPath);
cycle.push_back(printInputPath(path));
throw Error("follow cycle detected: [%s]", concatStringsSep(" -> ", cycle));
}
visited.push_back(path);
for (auto & elem : path) {
if (auto i = get(pos->inputs, elem)) {
if (auto node = std::get_if<0>(&*i))
pos = *node;
else if (auto follows = std::get_if<1>(&*i)) {
if (auto p = findInput(*follows))
if (auto p = doFind(root, *follows, visited))
pos = ref(p);
else
return {};
@ -66,6 +78,12 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
return pos;
}
std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{
std::vector<InputPath> visited;
return doFind(root, path, visited);
}
LockFile::LockFile(const nlohmann::json & json, const Path & path)
{
auto version = json.value("version", 0);
@ -345,7 +363,7 @@ void LockFile::check()
for (auto & [inputPath, input] : inputs) {
if (auto follows = std::get_if<1>(&input)) {
if (!follows->empty() && !get(inputs, *follows))
if (!follows->empty() && !findInput(*follows))
throw Error("input '%s' follows a non-existent input '%s'",
printInputPath(inputPath),
printInputPath(*follows));

View file

@ -156,7 +156,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
Outputs result;
for (auto elem : outTI->listItems()) {
if (elem->type() != nString) throw errMsg;
auto out = outputs.find(elem->string.s);
auto out = outputs.find(elem->c_str());
if (out == outputs.end()) throw errMsg;
result.insert(*out);
}
@ -230,7 +230,7 @@ std::string DrvInfo::queryMetaString(const std::string & name)
{
Value * v = queryMeta(name);
if (!v || v->type() != nString) return "";
return v->string.s;
return v->c_str();
}
@ -242,7 +242,7 @@ NixInt DrvInfo::queryMetaInt(const std::string & name, NixInt def)
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
if (auto n = string2Int<NixInt>(v->string.s))
if (auto n = string2Int<NixInt>(v->c_str()))
return *n;
}
return def;
@ -256,7 +256,7 @@ NixFloat DrvInfo::queryMetaFloat(const std::string & name, NixFloat def)
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
float meta fields. */
if (auto n = string2Float<NixFloat>(v->string.s))
if (auto n = string2Float<NixFloat>(v->c_str()))
return *n;
}
return def;
@ -271,8 +271,8 @@ bool DrvInfo::queryMetaBool(const std::string & name, bool def)
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
if (strcmp(v->string.s, "true") == 0) return true;
if (strcmp(v->string.s, "false") == 0) return false;
if (v->string_view() == "true") return true;
if (v->string_view() == "false") return false;
}
return def;
}

View file

@ -76,12 +76,12 @@ void Expr::show(const SymbolTable & symbols, std::ostream & str) const
void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
{
str << n;
str << v.integer;
}
void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
{
str << nf;
str << v.fpoint;
}
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const

View file

@ -20,7 +20,6 @@ MakeError(Abort, EvalError);
MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
/**
* Position objects.
@ -155,6 +154,10 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
struct Expr
{
static unsigned long nrExprs;
Expr() {
nrExprs++;
}
virtual ~Expr() { };
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
@ -171,18 +174,16 @@ struct Expr
struct ExprInt : Expr
{
NixInt n;
Value v;
ExprInt(NixInt n) : n(n) { v.mkInt(n); };
ExprInt(NixInt n) { v.mkInt(n); };
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};
struct ExprFloat : Expr
{
NixFloat nf;
Value v;
ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); };
ExprFloat(NixFloat nf) { v.mkFloat(nf); };
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};
@ -198,9 +199,13 @@ struct ExprString : Expr
struct ExprPath : Expr
{
ref<InputAccessor> accessor;
std::string s;
Value v;
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); };
ExprPath(ref<InputAccessor> accessor, std::string s) : accessor(accessor), s(std::move(s))
{
v.mkPath(&*accessor, this->s.c_str());
}
Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS
};
@ -238,7 +243,7 @@ struct ExprSelect : Expr
PosIdx pos;
Expr * e, * def;
AttrPath attrPath;
ExprSelect(const PosIdx & pos, Expr * e, const AttrPath && attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
PosIdx getPos() const override { return pos; }
COMMON_METHODS
@ -248,7 +253,7 @@ struct ExprOpHasAttr : Expr
{
Expr * e;
AttrPath attrPath;
ExprOpHasAttr(Expr * e, const AttrPath && attrPath) : e(e), attrPath(std::move(attrPath)) { };
ExprOpHasAttr(Expr * e, AttrPath attrPath) : e(e), attrPath(std::move(attrPath)) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS
};

View file

@ -520,7 +520,7 @@ path_start
/* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/";
$$ = new ExprPath(path);
$$ = new ExprPath(ref<InputAccessor>(data->state.rootFS), std::move(path));
}
| HPATH {
if (evalSettings.pureEval) {
@ -530,7 +530,7 @@ path_start
);
}
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(path);
$$ = new ExprPath(ref<InputAccessor>(data->state.rootFS), std::move(path));
}
;
@ -646,13 +646,16 @@ formal
#include "eval.hh"
#include "filetransfer.hh"
#include "fetchers.hh"
#include "tarball.hh"
#include "store-api.hh"
#include "flake/flake.hh"
#include "fs-input-accessor.hh"
#include "memory-input-accessor.hh"
namespace nix {
unsigned long Expr::nrExprs = 0;
Expr * EvalState::parse(
char * text,
@ -736,12 +739,6 @@ Expr * EvalState::parseStdin()
}
void EvalState::addToSearchPath(SearchPath::Elem && elem)
{
searchPath.elements.emplace_back(std::move(elem));
}
SourcePath EvalState::findFile(const std::string_view path)
{
return findFile(searchPath, path);
@ -761,11 +758,11 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
auto r = *rOpt;
Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
if (pathExists(res)) return CanonPath(canonPath(res));
if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
}
if (hasPrefix(path, "nix/"))
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
return {corepkgsFS, CanonPath(path.substr(3))};
debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval
@ -788,7 +785,7 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
if (EvalSettings::isPseudoUrl(value)) {
try {
auto storePath = fetchers::downloadTarball(
store, EvalSettings::resolvePseudoUrl(value), "source", false).tree.storePath;
store, EvalSettings::resolvePseudoUrl(value), "source", false).storePath;
res = { store->toRealPath(storePath) };
} catch (FileTransferError & e) {
logWarning({
@ -802,7 +799,7 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(value.substr(6), {}, true, false);
debug("fetching flake search path element '%s''", value);
auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;
auto storePath = flakeRef.resolve(store).fetchTree(store).first;
res = { store->toRealPath(storePath) };
}

View file

@ -1,10 +1,11 @@
#include "eval.hh"
#include "fs-input-accessor.hh"
namespace nix {
SourcePath EvalState::rootPath(CanonPath path)
{
return std::move(path);
return {rootFS, std::move(path)};
}
}

View file

@ -56,7 +56,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
.drvPath = b.drvPath,
.outputs = OutputsSpec::Names { b.output },
});
ensureValid(b.drvPath);
ensureValid(b.drvPath->getBaseStorePath());
},
[&](const NixStringContextElem::Opaque & o) {
auto ctxS = store->printStorePath(o.path);
@ -69,7 +69,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
res.insert_or_assign(ctxS, ctxS);
ensureValid(d.drvPath);
},
}, c.raw());
}, c.raw);
}
if (drvs.empty()) return {};
@ -77,7 +77,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
if (!evalSettings.enableImportFromDerivation)
debugThrowLastTrace(Error(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
store->printStorePath(drvs.begin()->drvPath)));
drvs.begin()->to_string(*store)));
/* Build/substitute the context. */
std::vector<DerivedPath> buildReqs;
@ -95,7 +95,11 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
/* Get all the output paths corresponding to the placeholders we had */
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
res.insert_or_assign(
DownstreamPlaceholder::unknownCaOutput(drv.drvPath, outputName).render(),
DownstreamPlaceholder::fromSingleDerivedPathBuilt(
SingleDerivedPath::Built {
.drvPath = drv.drvPath,
.output = outputName,
}).render(),
store->printStorePath(outputPath)
);
}
@ -117,13 +121,15 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, co
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
try {
StringMap rewrites = state.realiseContext(context);
auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
if (!context.empty()) {
auto rewrites = state.realiseContext(context);
auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context);
return {path.accessor, CanonPath(realPath)};
}
return flags.checkForPureEval
? state.checkSourcePath(realPath)
: realPath;
? state.checkSourcePath(path)
: path;
} catch (Error & e) {
e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
throw;
@ -152,8 +158,10 @@ static void mkOutputString(
{
state.mkOutputString(
attrs.alloc(o.first),
drvPath,
o.first,
SingleDerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath),
.output = o.first,
},
o.second.path(*state.store, Derivation::nameFromPath(drvPath), o.first));
}
@ -196,7 +204,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh"
, CanonPath::root), **state.vImportedDrvToDerivation);
, state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
}
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
@ -204,12 +212,6 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
}
else if (path2 == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh"
, CanonPath::root), v);
}
else {
if (!vScope)
state.evalFile(path, v);
@ -252,64 +254,71 @@ static RegisterPrimOp primop_import({
.args = {"path"},
// TODO turn "normal path values" into link below
.doc = R"(
Load, parse and return the Nix expression in the file *path*.
The value *path* can be a path, a string, or an attribute set with an
`__toString` attribute or a `outPath` attribute (as derivations or flake
inputs typically have).
If *path* is a directory, the file `default.nix` in that directory
is loaded.
Evaluation aborts if the file doesnt exist or contains
an incorrect Nix expression. `import` implements Nixs module
system: you can put any Nix expression (such as a set or a
function) in a separate file, and use it from Nix expressions in
other files.
Load, parse, and return the Nix expression in the file *path*.
> **Note**
>
> Unlike some languages, `import` is a regular function in Nix.
> Paths using the angle bracket syntax (e.g., `import` *\<foo\>*)
> are normal [path values](@docroot@/language/values.md#type-path).
A Nix expression loaded by `import` must not contain any *free
variables* (identifiers that are not defined in the Nix expression
itself and are not built-in). Therefore, it cannot refer to
variables that are in scope at the call site. For instance, if you
have a calling expression
The *path* argument must meet the same criteria as an [interpolated expression](@docroot@/language/string-interpolation.md#interpolated-expression).
```nix
rec {
x = 123;
y = import ./foo.nix;
}
```
If *path* is a directory, the file `default.nix` in that directory is used if it exists.
then the following `foo.nix` will give an error:
> **Example**
>
> ```console
> $ echo 123 > default.nix
> ```
>
> Import `default.nix` from the current directory.
>
> ```nix
> import ./.
> ```
>
> 123
```nix
x + 456
```
Evaluation aborts if the file doesnt exist or contains an invalid Nix expression.
since `x` is not in scope in `foo.nix`. If you want `x` to be
available in `foo.nix`, you should pass it as a function argument:
A Nix expression loaded by `import` must not contain any *free variables*, that is, identifiers that are not defined in the Nix expression itself and are not built-in.
Therefore, it cannot refer to variables that are in scope at the call site.
```nix
rec {
x = 123;
y = import ./foo.nix x;
}
```
and
```nix
x: x + 456
```
(The function argument doesnt have to be called `x` in `foo.nix`;
any name would work.)
> **Example**
>
> If you have a calling expression
>
> ```nix
> rec {
> x = 123;
> y = import ./foo.nix;
> }
> ```
>
> then the following `foo.nix` will give an error:
>
> ```nix
> # foo.nix
> x + 456
> ```
>
> since `x` is not in scope in `foo.nix`.
> If you want `x` to be available in `foo.nix`, pass it as a function argument:
>
> ```nix
> rec {
> x = 123;
> y = import ./foo.nix x;
> }
> ```
>
> and
>
> ```nix
> # foo.nix
> x: x + 456
> ```
>
> The function argument doesnt have to be called `x` in `foo.nix`; any name would work.
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
@ -584,9 +593,12 @@ struct CompareValues
case nFloat:
return v1->fpoint < v2->fpoint;
case nString:
return strcmp(v1->string.s, v2->string.s) < 0;
return v1->string_view().compare(v2->string_view()) < 0;
case nPath:
return strcmp(v1->_path, v2->_path) < 0;
// Note: we don't take the accessor into account
// since it's not obvious how to compare them in a
// reproducible way.
return strcmp(v1->_path.path, v2->_path.path) < 0;
case nList:
// Lexicographic comparison
for (size_t i = 0;; i++) {
@ -721,6 +733,14 @@ static RegisterPrimOp primop_genericClosure(PrimOp {
```
[ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ]
```
`key` can be one of the following types:
- [Number](@docroot@/language/values.md#type-number)
- [Boolean](@docroot@/language/values.md#type-boolean)
- [String](@docroot@/language/values.md#type-string)
- [Path](@docroot@/language/values.md#type-path)
- [List](@docroot@/language/values.md#list)
)",
.fun = prim_genericClosure,
});
@ -976,7 +996,7 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
{
state.forceValue(*args[0], pos);
if (args[0]->type() == nString)
printError("trace: %1%", args[0]->string.s);
printError("trace: %1%", args[0]->string_view());
else
printError("trace: %1%", printValue(state, *args[0]));
state.forceValue(*args[1], pos);
@ -1246,17 +1266,18 @@ drvName, Bindings * attrs, Value & v)
state.store->computeFSClosure(d.drvPath, refs);
for (auto & j : refs) {
drv.inputSrcs.insert(j);
if (j.isDerivation())
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
if (j.isDerivation()) {
drv.inputDrvs.map[j].value = state.store->readDerivation(j).outputNames();
}
}
},
[&](const NixStringContextElem::Built & b) {
drv.inputDrvs[b.drvPath].insert(b.output);
drv.inputDrvs.ensureSlot(*b.drvPath).value.insert(b.output);
},
[&](const NixStringContextElem::Opaque & o) {
drv.inputSrcs.insert(o.path);
},
}, c.raw());
}, c.raw);
}
/* Do we have all required attributes? */
@ -1325,13 +1346,13 @@ drvName, Bindings * attrs, Value & v)
if (isImpure)
drv.outputs.insert_or_assign(i,
DerivationOutput::Impure {
.method = method.raw,
.method = method,
.hashType = ht,
});
else
drv.outputs.insert_or_assign(i,
DerivationOutput::CAFloating {
.method = method.raw,
.method = method,
.hashType = ht,
});
}
@ -1364,7 +1385,7 @@ drvName, Bindings * attrs, Value & v)
drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign(
i,
DerivationOutputInputAddressed {
DerivationOutput::InputAddressed {
.path = std::move(outPath),
});
}
@ -1372,7 +1393,7 @@ drvName, Bindings * attrs, Value & v)
;
case DrvHash::Kind::Deferred:
for (auto & i : outputs) {
drv.outputs.insert_or_assign(i, DerivationOutputDeferred {});
drv.outputs.insert_or_assign(i, DerivationOutput::Deferred {});
}
}
}
@ -1471,7 +1492,7 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
}));
NixStringContext context;
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to 'builtins.storePath'")).path;
/* Resolve symlinks in path, unless path itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
@ -1511,15 +1532,27 @@ static RegisterPrimOp primop_storePath({
static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto & arg = *args[0];
/* We dont check the path right now, because we dont want to
throw if the path isnt allowed, but just return false (and we
cant just catch the exception here because we still want to
throw if something in the evaluation of `*args[0]` tries to
throw if something in the evaluation of `arg` tries to
access an unauthorized path). */
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
auto path = realisePath(state, pos, arg, { .checkForPureEval = false });
/* SourcePath doesn't know about trailing slash. */
auto mustBeDir = arg.type() == nString
&& (arg.string_view().ends_with("/")
|| arg.string_view().ends_with("/."));
try {
v.mkBool(state.checkSourcePath(path).pathExists());
auto checked = state.checkSourcePath(path);
auto exists = checked.pathExists();
if (exists && mustBeDir) {
exists = checked.lstat().type == InputAccessor::tDirectory;
}
v.mkBool(exists);
} catch (SysError & e) {
/* Don't give away info from errors while canonicalising
path in restricted mode. */
@ -1672,13 +1705,14 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
static RegisterPrimOp primop_findFile(PrimOp {
.name = "__findFile",
.args = {"search path", "lookup path"},
.args = {"search-path", "lookup-path"},
.doc = R"(
Look up the given path with the given search path.
Find *lookup-path* in *search-path*.
A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes, `prefix`, and `path`.
`prefix` is a relative path.
`path` denotes a file system location; the exact syntax depends on the command line interface.
A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes:
- `prefix` is a relative path.
- `path` denotes a file system location
The exact syntax depends on the command line interface.
Examples of search path attribute sets:
@ -1696,15 +1730,14 @@ static RegisterPrimOp primop_findFile(PrimOp {
}
```
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match.
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match:
This is the process for each entry:
If the lookup path matches `prefix`, then the remainder of the lookup path (the "suffix") is searched for within the directory denoted by `patch`.
Note that the `path` may need to be downloaded at this point to look inside.
If the suffix is found inside that directory, then the entry is a match;
the combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
- If *lookup-path* matches `prefix`, then the remainder of *lookup-path* (the "suffix") is searched for within the directory denoted by `path`.
Note that the `path` may need to be downloaded at this point to look inside.
- If the suffix is found inside that directory, then the entry is a match.
The combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
The syntax
[Lookup path](@docroot@/language/constructs/lookup-path.md) expressions can be [desugared](https://en.wikipedia.org/wiki/Syntactic_sugar) using this and [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath):
```nix
<nixpkgs>
@ -1732,7 +1765,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[1]);
v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
v.mkString(hashString(*ht, path.readFile()).to_string(HashFormat::Base16, false));
}
static RegisterPrimOp primop_hashFile({
@ -1829,6 +1862,45 @@ static RegisterPrimOp primop_readDir({
.fun = prim_readDir,
});
/* Extend single element string context with another output. */
static void prim_outputOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
SingleDerivedPath drvPath = state.coerceToSingleDerivedPath(pos, *args[0], "while evaluating the first argument to builtins.outputOf");
OutputNameView outputName = state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument to builtins.outputOf");
state.mkSingleDerivedPathString(
SingleDerivedPath::Built {
.drvPath = make_ref<SingleDerivedPath>(drvPath),
.output = std::string { outputName },
},
v);
}
static RegisterPrimOp primop_outputOf({
.name = "__outputOf",
.args = {"derivation-reference", "output-name"},
.doc = R"(
Return the output path of a derivation, literally or using a placeholder if needed.
If the derivation has a statically-known output path (i.e. the derivation output is input-addressed, or fixed content-addresed), the output path will just be returned.
But if the derivation is content-addressed or if the derivation is itself not-statically produced (i.e. is the output of another derivation), a placeholder will be returned instead.
*`derivation reference`* must be a string that may contain a regular store path to a derivation, or may be a placeholder reference. If the derivation is produced by a derivation, you must explicitly select `drv.outPath`.
This primop can be chained arbitrarily deeply.
For instance,
```nix
builtins.outputOf
(builtins.outputOf myDrv "out)
"out"
```
will return a placeholder for the output of the output of `myDrv`.
This primop corresponds to the `^` sigil for derivable paths, e.g. as part of installable syntax on the command line.
)",
.fun = prim_outputOf,
.experimentalFeature = Xp::DynamicDerivations,
});
/*************************************************************
* Creating files
@ -2006,7 +2078,7 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
StorePathSet refs;
for (auto c : context) {
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c))
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
refs.insert(p->path);
else
state.debugThrowLastTrace(EvalError({
@ -2138,7 +2210,7 @@ static void addPath(
path = evalSettings.pureEval && expectedHash
? path
: state.checkSourcePath(CanonPath(path)).path.abs();
: state.checkSourcePath(state.rootPath(CanonPath(path))).path.abs();
PathFilter filter = filterFun ? ([&](const Path & path) {
auto st = lstat(path);
@ -2171,9 +2243,7 @@ static void addPath(
});
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
StorePath dstPath = settings.readOnlyMode
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs);
auto dstPath = state.rootPath(CanonPath(path)).fetchToStore(state.store, name, method, &filter, state.repair);
if (expectedHash && expectedStorePath != dstPath)
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.allowAndSetStorePathString(dstPath, v);
@ -2190,7 +2260,7 @@ static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * arg
{
NixStringContext context;
auto path = state.coerceToPath(pos, *args[1], context,
"while evaluating the second argument (the path to filter) passed to builtins.filterSource");
"while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'");
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
}
@ -2344,7 +2414,7 @@ static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args,
(v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]);
std::sort(v.listElems(), v.listElems() + n,
[](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; });
[](Value * v1, Value * v2) { return v1->string_view().compare(v2->string_view()) < 0; });
}
static RegisterPrimOp primop_attrNames({
@ -2485,7 +2555,7 @@ static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args
names.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) {
state.forceStringNoCtx(*elem, pos, "while evaluating the values of the second argument passed to builtins.removeAttrs");
names.emplace_back(state.symbols.create(elem->string.s), nullptr);
names.emplace_back(state.symbols.create(elem->string_view()), nullptr);
}
std::sort(names.begin(), names.end());
@ -2935,7 +3005,7 @@ static RegisterPrimOp primop_tail({
.name = "__tail",
.args = {"list"},
.doc = R"(
Return the second to last elements of a list; abort evaluation if
Return the list without its first item; abort evaluation if
the argument isnt a list or is an empty list.
> **Warning**
@ -3695,7 +3765,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
NixStringContext context; // discarded
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
v.mkString(hashString(*ht, s).to_string(Base16, false));
v.mkString(hashString(*ht, s).to_string(HashFormat::Base16, false));
}
static RegisterPrimOp primop_hashString({
@ -3709,6 +3779,101 @@ static RegisterPrimOp primop_hashString({
.fun = prim_hashString,
});
static void prim_convertHash(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos, "while evaluating the first argument passed to builtins.convertHash");
auto &inputAttrs = args[0]->attrs;
Bindings::iterator iteratorHash = getAttr(state, state.symbols.create("hash"), inputAttrs, "while locating the attribute 'hash'");
auto hash = state.forceStringNoCtx(*iteratorHash->value, pos, "while evaluating the attribute 'hash'");
Bindings::iterator iteratorHashAlgo = inputAttrs->find(state.symbols.create("hashAlgo"));
std::optional<HashType> ht = std::nullopt;
if (iteratorHashAlgo != inputAttrs->end()) {
ht = parseHashType(state.forceStringNoCtx(*iteratorHashAlgo->value, pos, "while evaluating the attribute 'hashAlgo'"));
}
Bindings::iterator iteratorToHashFormat = getAttr(state, state.symbols.create("toHashFormat"), args[0]->attrs, "while locating the attribute 'toHashFormat'");
HashFormat hf = parseHashFormat(state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'"));
v.mkString(Hash::parseAny(hash, ht).to_string(hf, hf == HashFormat::SRI));
}
static RegisterPrimOp primop_convertHash({
.name = "__convertHash",
.args = {"args"},
.doc = R"(
Return the specified representation of a hash string, based on the attributes presented in *args*:
- `hash`
The hash to be converted.
The hash format is detected automatically.
- `hashAlgo`
The algorithm used to create the hash. Must be one of
- `"md5"`
- `"sha1"`
- `"sha256"`
- `"sha512"`
The attribute may be omitted when `hash` is an [SRI hash](https://www.w3.org/TR/SRI/#the-integrity-attribute) or when the hash is prefixed with the hash algorithm name followed by a colon.
That `<hashAlgo>:<hashBody>` syntax is supported for backwards compatibility with existing tooling.
- `toHashFormat`
The format of the resulting hash. Must be one of
- `"base16"`
- `"base32"`
- `"base64"`
- `"sri"`
The result hash is the *toHashFormat* representation of the hash *hash*.
> **Example**
>
> Convert a SHA256 hash in Base16 to SRI:
>
> ```nix
> builtins.convertHash {
> hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
> toHashFormat = "sri";
> hashAlgo = "sha256";
> }
> ```
>
> "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
> **Example**
>
> Convert a SHA256 hash in SRI to Base16:
>
> ```nix
> builtins.convertHash {
> hash = "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=";
> toHashFormat = "base16";
> }
> ```
>
> "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
> **Example**
>
> Convert a hash in the form `<hashAlgo>:<hashBody>` in Base16 to SRI:
>
> ```nix
> builtins.convertHash {
> hash = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
> toHashFormat = "sri";
> }
> ```
>
> "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
)",
.fun = prim_convertHash,
});
struct RegexCache
{
// TODO use C++20 transparent comparison when available
@ -4331,9 +4496,9 @@ void EvalState::createBaseEnv()
addConstant("__nixPath", v, {
.type = nList,
.doc = R"(
The search path used to resolve angle bracket path lookups.
List of search path entries used to resolve [lookup paths](@docroot@/language/constructs/lookup-path.md).
Angle bracket expressions can be
Lookup path expressions can be
[desugared](https://en.wikipedia.org/wiki/Syntactic_sugar)
using this and
[`builtins.findFile`](./builtins.html#builtins-findFile):
@ -4377,12 +4542,7 @@ void EvalState::createBaseEnv()
/* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */
char code[] =
#include "primops/derivation.nix.gen.hh"
// the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string.
"\0";
eval(parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation);
evalFile(derivationInternal, *vDerivation);
}

View file

@ -51,13 +51,13 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx p
NixStringContext context2;
for (auto && c : context) {
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c)) {
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c.raw)) {
context2.emplace(NixStringContextElem::Opaque {
.path = ptr->drvPath
});
} else {
/* Can reuse original item */
context2.emplace(std::move(c));
context2.emplace(std::move(c).raw);
}
}
@ -106,12 +106,15 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
contextInfos[std::move(d.drvPath)].allOutputs = true;
},
[&](NixStringContextElem::Built && b) {
contextInfos[std::move(b.drvPath)].outputs.emplace_back(std::move(b.output));
// FIXME should eventually show string context as is, no
// resolving here.
auto drvPath = resolveDerivedPath(*state.store, *b.drvPath);
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
},
[&](NixStringContextElem::Opaque && o) {
contextInfos[std::move(o.path)].path = true;
},
}, ((NixStringContextElem &&) i).raw());
}, ((NixStringContextElem &&) i).raw);
}
auto attrs = state.buildBindings(contextInfos.size());
@ -222,7 +225,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
context.emplace(NixStringContextElem::Built {
.drvPath = namePath,
.drvPath = makeConstantStorePathRef(namePath),
.output = std::string { outputName },
});
}

View file

@ -133,7 +133,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
else if (attrName == "toPath") {
state.forceValue(*attr.value, attr.pos);
bool isEmptyString = attr.value->type() == nString && attr.value->string.s == std::string("");
bool isEmptyString = attr.value->type() == nString && attr.value->string_view() == "";
if (isEmptyString) {
toPath = StorePathOrGap {};
}

View file

@ -71,10 +71,10 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
auto input = fetchers::Input::fromAttrs(std::move(attrs));
// FIXME: use name
auto [tree, input2] = input.fetch(state.store);
auto [storePath, input2] = input.fetch(state.store);
auto attrs2 = state.buildBindings(8);
state.mkStorePathString(tree.storePath, attrs2.alloc(state.sOutPath));
state.mkStorePathString(storePath, attrs2.alloc(state.sOutPath));
if (input2.getRef())
attrs2.alloc("branch").mkString(*input2.getRef());
// Backward compatibility: set 'rev' to
@ -86,7 +86,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
attrs2.alloc("revCount").mkInt(*revCount);
v.mkAttrs(attrs2);
state.allowPath(tree.storePath);
state.allowPath(storePath);
}
static RegisterPrimOp r_fetchMercurial({

View file

@ -5,6 +5,7 @@
#include "fetchers.hh"
#include "filetransfer.hh"
#include "registry.hh"
#include "tarball.hh"
#include "url.hh"
#include <ctime>
@ -15,7 +16,7 @@ namespace nix {
void emitTreeAttrs(
EvalState & state,
const fetchers::Tree & tree,
const StorePath & storePath,
const fetchers::Input & input,
Value & v,
bool emptyRevFallback,
@ -25,14 +26,13 @@ void emitTreeAttrs(
auto attrs = state.buildBindings(10);
state.mkStorePathString(tree.storePath, attrs.alloc(state.sOutPath));
state.mkStorePathString(storePath, attrs.alloc(state.sOutPath));
// FIXME: support arbitrary input attributes.
auto narHash = input.getNarHash();
assert(narHash);
attrs.alloc("narHash").mkString(narHash->to_string(SRI, true));
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
if (input.getType() == "git")
attrs.alloc("submodules").mkBool(
@ -71,36 +71,10 @@ void emitTreeAttrs(
v.mkAttrs(attrs);
}
std::string fixURI(std::string uri, EvalState & state, const std::string & defaultScheme = "file")
{
state.checkURI(uri);
if (uri.find("://") == std::string::npos) {
const auto p = ParsedURL {
.scheme = defaultScheme,
.authority = "",
.path = uri
};
return p.to_string();
} else {
return uri;
}
}
std::string fixURIForGit(std::string uri, EvalState & state)
{
/* Detects scp-style uris (e.g. git@github.com:NixOS/nix) and fixes
* them by removing the `:` and assuming a scheme of `ssh://`
* */
static std::regex scp_uri("([^/]*)@(.*):(.*)");
if (uri[0] != '/' && std::regex_match(uri, scp_uri))
return fixURI(std::regex_replace(uri, scp_uri, "$1@$2/$3"), state, "ssh");
else
return fixURI(uri, state);
}
struct FetchTreeParams {
bool emptyRevFallback = false;
bool allowNameArgument = false;
bool isFetchGit = false;
};
static void fetchTree(
@ -108,11 +82,12 @@ static void fetchTree(
const PosIdx pos,
Value * * args,
Value & v,
std::optional<std::string> type,
const FetchTreeParams & params = FetchTreeParams{}
) {
fetchers::Input input;
NixStringContext context;
std::optional<std::string> type;
if (params.isFetchGit) type = "git";
state.forceValue(*args[0], pos);
@ -142,10 +117,8 @@ static void fetchTree(
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
attrs.emplace(state.symbols[attr.name],
state.symbols[attr.name] == "url"
? type == "git"
? fixURIForGit(s, state)
: fixURI(s, state)
params.isFetchGit && state.symbols[attr.name] == "url"
? fixGitURL(s)
: s);
}
else if (attr.value->type() == nBool)
@ -170,40 +143,86 @@ static void fetchTree(
"while evaluating the first argument passed to the fetcher",
false, false).toOwned();
if (type == "git") {
if (params.isFetchGit) {
fetchers::Attrs attrs;
attrs.emplace("type", "git");
attrs.emplace("url", fixURIForGit(url, state));
attrs.emplace("url", fixGitURL(url));
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
input = fetchers::Input::fromURL(fixURI(url, state));
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"),
.errPos = state.positions[pos]
}));
input = fetchers::Input::fromURL(url);
}
}
if (!evalSettings.pureEval && !input.isDirect())
if (!evalSettings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked())
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
auto [tree, input2] = input.fetch(state.store);
state.checkURI(input.toURLString());
state.allowPath(tree.storePath);
auto [storePath, input2] = input.fetch(state.store);
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
state.allowPath(storePath);
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
}
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
experimentalFeatureSettings.require(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
fetchTree(state, pos, args, v, { });
}
// FIXME: document
static RegisterPrimOp primop_fetchTree({
.name = "fetchTree",
.arity = 1,
.fun = prim_fetchTree
.args = {"input"},
.doc = R"(
Fetch a source tree or a plain file using one of the supported backends.
*input* must be a [flake reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references), either in attribute set representation or in the URL-like syntax.
The input should be "locked", that is, it should contain a commit hash or content hash unless impure evaluation (`--impure`) is enabled.
> **Note**
>
> The URL-like syntax requires the [`flakes` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-flakes) to be enabled.
Here are some examples of how to use `fetchTree`:
- Fetch a GitHub repository using the attribute set representation:
```nix
builtins.fetchTree {
type = "github";
owner = "NixOS";
repo = "nixpkgs";
rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
}
```
This evaluates to the following attribute set:
```
{
lastModified = 1686503798;
lastModifiedDate = "20230611171638";
narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=";
outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source";
rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
shortRev = "ae2e6b3";
}
```
- Fetch the same GitHub repository using the URL-like syntax:
```
builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
```
)",
.fun = prim_fetchTree,
});
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
@ -270,7 +289,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
// https://github.com/NixOS/nix/issues/4313
auto storePath =
unpack
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).tree.storePath
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).storePath
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
if (expectedHash) {
@ -279,7 +298,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
: hashFile(htSHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash)
state.debugThrowLastTrace(EvalError((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)));
*url, expectedHash->to_string(HashFormat::Base32, true), hash.to_string(HashFormat::Base32, true)));
}
state.allowAndSetStorePathString(storePath, v);
@ -353,7 +372,12 @@ static RegisterPrimOp primop_fetchTarball({
static void prim_fetchGit(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
fetchTree(state, pos, args, v,
FetchTreeParams {
.emptyRevFallback = true,
.allowNameArgument = true,
.isFetchGit = true
});
}
static RegisterPrimOp primop_fetchGit({

View file

@ -10,7 +10,7 @@ std::optional<std::string_view> SearchPath::Prefix::suffixIfPotentialMatch(
/* Non-empty prefix and suffix must be separated by a /, or the
prefix is not a valid path prefix. */
bool needSeparator = n > 0 && (path.size() - n) > 0;
bool needSeparator = n > 0 && n < path.size();
if (needSeparator && path[n] != '/') {
return std::nullopt;

View file

@ -18,15 +18,17 @@ TEST_F(DerivedPathExpressionTest, force_init)
{
}
#ifndef COVERAGE
RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest,
prop_opaque_path_round_trip,
(const DerivedPath::Opaque & o))
(const SingleDerivedPath::Opaque & o))
{
auto * v = state.allocValue();
state.mkStorePathString(o.path, *v);
auto d = state.coerceToDerivedPath(noPos, *v, "");
RC_ASSERT(DerivedPath { o } == d);
auto d = state.coerceToSingleDerivedPath(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { o } == d);
}
// TODO use DerivedPath::Built for parameter once it supports a single output
@ -34,8 +36,8 @@ RC_GTEST_FIXTURE_PROP(
RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest,
prop_built_path_placeholder_round_trip,
(const StorePath & drvPath, const StorePathName & outputName))
prop_derived_path_built_placeholder_round_trip,
(const SingleDerivedPath::Built & b))
{
/**
* We set these in tests rather than the regular globals so we don't have
@ -45,28 +47,22 @@ RC_GTEST_FIXTURE_PROP(
mockXpSettings.set("experimental-features", "ca-derivations");
auto * v = state.allocValue();
state.mkOutputString(*v, drvPath, outputName.name, std::nullopt, mockXpSettings);
auto [d, _] = state.coerceToDerivedPathUnchecked(noPos, *v, "");
DerivedPath::Built b {
.drvPath = drvPath,
.outputs = OutputsSpec::Names { outputName.name },
};
RC_ASSERT(DerivedPath { b } == d);
state.mkOutputString(*v, b, std::nullopt, mockXpSettings);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { b } == d);
}
RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest,
prop_built_path_out_path_round_trip,
(const StorePath & drvPath, const StorePathName & outputName, const StorePath & outPath))
prop_derived_path_built_out_path_round_trip,
(const SingleDerivedPath::Built & b, const StorePath & outPath))
{
auto * v = state.allocValue();
state.mkOutputString(*v, drvPath, outputName.name, outPath);
auto [d, _] = state.coerceToDerivedPathUnchecked(noPos, *v, "");
DerivedPath::Built b {
.drvPath = drvPath,
.outputs = OutputsSpec::Names { outputName.name },
};
RC_ASSERT(DerivedPath { b } == d);
state.mkOutputString(*v, b, outPath);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { b } == d);
}
#endif
} /* namespace nix */

View file

@ -310,7 +310,7 @@ namespace nix {
ASSERT_TRACE2("storePath true",
TypeError,
hintfmt("cannot coerce %s to a string", "a Boolean"),
hintfmt("while evaluating the first argument passed to builtins.storePath"));
hintfmt("while evaluating the first argument passed to 'builtins.storePath'"));
}
@ -378,12 +378,12 @@ namespace nix {
ASSERT_TRACE2("filterSource [] []",
TypeError,
hintfmt("cannot coerce %s to a string", "a list"),
hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
hintfmt("while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'"));
ASSERT_TRACE2("filterSource [] \"foo\"",
EvalError,
hintfmt("string '%s' doesn't represent an absolute path", "foo"),
hintfmt("while evaluating the second argument (the path to filter) passed to builtins.filterSource"));
hintfmt("while evaluating the second argument (the path to filter) passed to 'builtins.filterSource'"));
ASSERT_TRACE2("filterSource [] ./.",
TypeError,
@ -1084,7 +1084,7 @@ namespace nix {
ASSERT_TRACE1("hashString \"foo\" \"content\"",
UsageError,
hintfmt("unknown hash algorithm '%s'", "foo"));
hintfmt("unknown hash algorithm '%s', expect 'md5', 'sha1', 'sha256', or 'sha512'", "foo"));
ASSERT_TRACE2("hashString \"sha256\" {}",
TypeError,

View file

@ -0,0 +1,22 @@
#include <gtest/gtest.h>
#include "flake/flakeref.hh"
namespace nix {
/* ----------- tests for flake/flakeref.hh --------------------------------------------------*/
/* ----------------------------------------------------------------------------
* to_string
* --------------------------------------------------------------------------*/
TEST(to_string, doesntReencodeUrl) {
auto s = "http://localhost:8181/test/+3d.tar.gz";
auto flakeref = parseFlakeRef(s);
auto parsed = flakeref.to_string();
auto expected = "http://localhost:8181/test/%2B3d.tar.gz";
ASSERT_EQ(parsed, expected);
}
}

View file

@ -62,7 +62,7 @@ namespace nix {
// not supported by store 'dummy'" thrown in the test body.
TEST_F(JSONValueTest, DISABLED_Path) {
Value v;
v.mkPath("test");
v.mkPath(state.rootPath(CanonPath("/test")));
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
}
} /* namespace nix */

View file

@ -71,7 +71,7 @@ namespace nix {
if (arg.type() != nString) {
return false;
}
return std::string_view(arg.string.s) == s;
return std::string_view(arg.c_str()) == s;
}
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
@ -103,14 +103,17 @@ namespace nix {
}
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type();
return false;
} else if (std::string_view(arg.string.s) != p) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.string.s;
if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type();
return false;
} else {
auto path = arg.path();
if (path.path != CanonPath(p)) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << path.path;
return false;
}
return true;
}
return true;
}

View file

@ -711,14 +711,14 @@ namespace nix {
// FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\"");
ASSERT_EQ(v.type(), nString);
ASSERT_EQ(v.string.s, std::string_view("fabir"));
ASSERT_EQ(v.string_view(), "fabir");
}
TEST_F(PrimOpTest, concatStringsSep) {
// FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]");
ASSERT_EQ(v.type(), nString);
ASSERT_EQ(std::string_view(v.string.s), "foo%bar%baz");
ASSERT_EQ(v.string_view(), "foo%bar%baz");
}
TEST_F(PrimOpTest, split1) {

View file

@ -8,6 +8,8 @@
namespace nix {
// Test a few cases of invalid string context elements.
TEST(NixStringContextElemTest, empty_invalid) {
EXPECT_THROW(
NixStringContextElem::parse(""),
@ -38,46 +40,88 @@ TEST(NixStringContextElemTest, slash_invalid) {
BadStorePath);
}
/**
* Round trip (string <-> data structure) test for
* `NixStringContextElem::Opaque`.
*/
TEST(NixStringContextElemTest, opaque) {
std::string_view opaque = "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
auto elem = NixStringContextElem::parse(opaque);
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem);
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->path, StorePath { opaque });
ASSERT_EQ(elem.to_string(), opaque);
}
/**
* Round trip (string <-> data structure) test for
* `NixStringContextElem::DrvDeep`.
*/
TEST(NixStringContextElemTest, drvDeep) {
std::string_view drvDeep = "=g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(drvDeep);
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem);
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->drvPath, StorePath { drvDeep.substr(1) });
ASSERT_EQ(elem.to_string(), drvDeep);
}
TEST(NixStringContextElemTest, built) {
/**
* Round trip (string <-> data structure) test for a simpler
* `NixStringContextElem::Built`.
*/
TEST(NixStringContextElemTest, built_opaque) {
std::string_view built = "!foo!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(built);
auto * p = std::get_if<NixStringContextElem::Built>(&elem);
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->output, "foo");
ASSERT_EQ(p->drvPath, StorePath { built.substr(5) });
ASSERT_EQ(*p->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
.path = StorePath { built.substr(5) },
}));
ASSERT_EQ(elem.to_string(), built);
}
/**
* Round trip (string <-> data structure) test for a more complex,
* inductive `NixStringContextElem::Built`.
*/
TEST(NixStringContextElemTest, built_built) {
/**
* 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", "dynamic-derivations ca-derivations");
std::string_view built = "!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(built, mockXpSettings);
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
ASSERT_TRUE(p);
ASSERT_EQ(p->output, "foo");
auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath);
ASSERT_TRUE(drvPath);
ASSERT_EQ(drvPath->output, "bar");
ASSERT_EQ(*drvPath->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque {
.path = StorePath { built.substr(9) },
}));
ASSERT_EQ(elem.to_string(), built);
}
/**
* Without the right experimental features enabled, we cannot parse a
* complex inductive string context element.
*/
TEST(NixStringContextElemTest, built_built_xp) {
ASSERT_THROW(
NixStringContextElem::parse("!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"), MissingExperimentalFeature);
}
}
namespace rc {
using namespace nix;
Gen<NixStringContextElem::Opaque> Arbitrary<NixStringContextElem::Opaque>::arbitrary()
{
return gen::just(NixStringContextElem::Opaque {
.path = *gen::arbitrary<StorePath>(),
});
}
Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arbitrary()
{
return gen::just(NixStringContextElem::DrvDeep {
@ -85,14 +129,6 @@ Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arb
});
}
Gen<NixStringContextElem::Built> Arbitrary<NixStringContextElem::Built>::arbitrary()
{
return gen::just(NixStringContextElem::Built {
.drvPath = *gen::arbitrary<StorePath>(),
.output = (*gen::arbitrary<StorePathName>()).name,
});
}
Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
{
switch (*gen::inRange<uint8_t>(0, std::variant_size_v<NixStringContextElem::Raw>)) {
@ -111,6 +147,8 @@ Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
namespace nix {
#ifndef COVERAGE
RC_GTEST_PROP(
NixStringContextElemTest,
prop_round_rip,
@ -119,4 +157,6 @@ RC_GTEST_PROP(
RC_ASSERT(o == NixStringContextElem::parse(o.to_string()));
}
#endif
}

View file

@ -31,7 +31,7 @@ json printValueAsJSON(EvalState & state, bool strict,
case nString:
copyContext(v, context);
out = v.string.s;
out = v.c_str();
break;
case nPath:

View file

@ -74,7 +74,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
case nString:
/* !!! show the context? */
copyContext(v, context);
doc.writeEmptyElement("string", singletonAttrs("value", v.string.s));
doc.writeEmptyElement("string", singletonAttrs("value", v.c_str()));
break;
case nPath:
@ -96,14 +96,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
xmlAttrs["drvPath"] = drvPath = a->value->c_str();
}
a = v.attrs->find(state.sOutPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value, a->pos);
if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->string.s;
xmlAttrs["outPath"] = a->value->c_str();
}
XMLOpenElement _(doc, "derivation", xmlAttrs);

View file

@ -186,11 +186,15 @@ public:
* For canonicity, the store paths should be in sorted order.
*/
struct {
const char * s;
const char * c_str;
const char * * context; // must be in sorted order
} string;
const char * _path;
struct {
InputAccessor * accessor;
const char * path;
} _path;
Bindings * attrs;
struct {
size_t size;
@ -270,7 +274,7 @@ public:
inline void mkString(const char * s, const char * * context = 0)
{
internalType = tString;
string.s = s;
string.c_str = s;
string.context = context;
}
@ -287,11 +291,12 @@ public:
void mkPath(const SourcePath & path);
inline void mkPath(const char * path)
inline void mkPath(InputAccessor * accessor, const char * path)
{
clearValue();
internalType = tPath;
_path = path;
_path.accessor = accessor;
_path.path = path;
}
inline void mkNull()
@ -438,13 +443,27 @@ public:
SourcePath path() const
{
assert(internalType == tPath);
return SourcePath{CanonPath(_path)};
return SourcePath {
.accessor = ref(_path.accessor->shared_from_this()),
.path = CanonPath(CanonPath::unchecked_t(), _path.path)
};
}
std::string_view str() const
std::string_view string_view() const
{
assert(internalType == tString);
return std::string_view(string.s);
return std::string_view(string.c_str);
}
const char * const c_str() const
{
assert(internalType == tString);
return string.c_str;
}
const char * * context() const
{
return string.context;
}
};

View file

@ -4,29 +4,52 @@
namespace nix {
NixStringContextElem NixStringContextElem::parse(std::string_view s0)
NixStringContextElem NixStringContextElem::parse(
std::string_view s0,
const ExperimentalFeatureSettings & xpSettings)
{
std::string_view s = s0;
std::function<SingleDerivedPath()> parseRest;
parseRest = [&]() -> SingleDerivedPath {
// Case on whether there is a '!'
size_t index = s.find("!");
if (index == std::string_view::npos) {
return SingleDerivedPath::Opaque {
.path = StorePath { s },
};
} else {
std::string output { s.substr(0, index) };
// Advance string to parse after the '!'
s = s.substr(index + 1);
auto drv = make_ref<SingleDerivedPath>(parseRest());
drvRequireExperiment(*drv, xpSettings);
return SingleDerivedPath::Built {
.drvPath = std::move(drv),
.output = std::move(output),
};
}
};
if (s.size() == 0) {
throw BadNixStringContextElem(s0,
"String context element should never be an empty string");
}
switch (s.at(0)) {
case '!': {
s = s.substr(1); // advance string to parse after first !
size_t index = s.find("!");
// This makes index + 1 safe. Index can be the length (one after index
// of last character), so given any valid character index --- a
// successful find --- we can add one.
if (index == std::string_view::npos) {
// Advance string to parse after the '!'
s = s.substr(1);
// Find *second* '!'
if (s.find("!") == std::string_view::npos) {
throw BadNixStringContextElem(s0,
"String content element beginning with '!' should have a second '!'");
}
return NixStringContextElem::Built {
.drvPath = StorePath { s.substr(index + 1) },
.output = std::string(s.substr(0, index)),
};
return std::visit(
[&](auto x) -> NixStringContextElem { return std::move(x); },
parseRest());
}
case '=': {
return NixStringContextElem::DrvDeep {
@ -34,33 +57,51 @@ NixStringContextElem NixStringContextElem::parse(std::string_view s0)
};
}
default: {
return NixStringContextElem::Opaque {
.path = StorePath { s },
};
// Ensure no '!'
if (s.find("!") != std::string_view::npos) {
throw BadNixStringContextElem(s0,
"String content element not beginning with '!' should not have a second '!'");
}
return std::visit(
[&](auto x) -> NixStringContextElem { return std::move(x); },
parseRest());
}
}
}
std::string NixStringContextElem::to_string() const {
return std::visit(overloaded {
std::string NixStringContextElem::to_string() const
{
std::string res;
std::function<void(const SingleDerivedPath &)> toStringRest;
toStringRest = [&](auto & p) {
std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
res += o.path.to_string();
},
[&](const SingleDerivedPath::Built & o) {
res += o.output;
res += '!';
toStringRest(*o.drvPath);
},
}, p.raw());
};
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
std::string res;
res += '!';
res += b.output;
res += '!';
res += b.drvPath.to_string();
return res;
},
[&](const NixStringContextElem::DrvDeep & d) {
std::string res;
res += '=';
res += d.drvPath.to_string();
return res;
toStringRest(b);
},
[&](const NixStringContextElem::Opaque & o) {
return std::string { o.path.to_string() };
toStringRest(o);
},
}, raw());
[&](const NixStringContextElem::DrvDeep & d) {
res += '=';
res += d.drvPath.to_string();
},
}, raw);
return res;
}
}

View file

@ -3,9 +3,8 @@
#include "util.hh"
#include "comparator.hh"
#include "path.hh"
#include <variant>
#include "derived-path.hh"
#include "variant-wrapper.hh"
#include <nlohmann/json_fwd.hpp>
@ -26,75 +25,59 @@ public:
}
};
/**
* Plain opaque path to some store object.
*
* Encoded as just the path: <path>.
*/
struct NixStringContextElem_Opaque {
StorePath path;
struct NixStringContextElem {
/**
* Plain opaque path to some store object.
*
* Encoded as just the path: <path>.
*/
using Opaque = SingleDerivedPath::Opaque;
GENERATE_CMP(NixStringContextElem_Opaque, me->path);
};
/**
* Path to a derivation and its entire build closure.
*
* The path doesn't just refer to derivation itself and its closure, but
* also all outputs of all derivations in that closure (including the
* root derivation).
*
* Encoded in the form =<drvPath>.
*/
struct DrvDeep {
StorePath drvPath;
/**
* Path to a derivation and its entire build closure.
*
* The path doesn't just refer to derivation itself and its closure, but
* also all outputs of all derivations in that closure (including the
* root derivation).
*
* Encoded in the form =<drvPath>.
*/
struct NixStringContextElem_DrvDeep {
StorePath drvPath;
GENERATE_CMP(DrvDeep, me->drvPath);
};
GENERATE_CMP(NixStringContextElem_DrvDeep, me->drvPath);
};
/**
* Derivation output.
*
* Encoded in the form !<output>!<drvPath>.
*/
using Built = SingleDerivedPath::Built;
/**
* Derivation output.
*
* Encoded in the form !<output>!<drvPath>.
*/
struct NixStringContextElem_Built {
StorePath drvPath;
std::string output;
using Raw = std::variant<
Opaque,
DrvDeep,
Built
>;
GENERATE_CMP(NixStringContextElem_Built, me->drvPath, me->output);
};
Raw raw;
using _NixStringContextElem_Raw = std::variant<
NixStringContextElem_Opaque,
NixStringContextElem_DrvDeep,
NixStringContextElem_Built
>;
GENERATE_CMP(NixStringContextElem, me->raw);
struct NixStringContextElem : _NixStringContextElem_Raw {
using Raw = _NixStringContextElem_Raw;
using Raw::Raw;
using Opaque = NixStringContextElem_Opaque;
using DrvDeep = NixStringContextElem_DrvDeep;
using Built = NixStringContextElem_Built;
inline const Raw & raw() const & {
return static_cast<const Raw &>(*this);
}
inline Raw & raw() & {
return static_cast<Raw &>(*this);
}
inline Raw && raw() && {
return static_cast<Raw &&>(*this);
}
MAKE_WRAPPER_CONSTRUCTOR(NixStringContextElem);
/**
* Decode a context string, one of:
* - <path>
* - =<path>
* - !<name>!<path>
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static NixStringContextElem parse(std::string_view s);
static NixStringContextElem parse(
std::string_view s,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
std::string to_string() const;
};

View file

@ -13,6 +13,12 @@
namespace nix::fetchers {
typedef std::variant<std::string, uint64_t, Explicit<bool>> Attr;
/**
* An `Attrs` can be thought of a JSON object restricted or simplified
* to be "flat", not containing any subcontainers (arrays or objects)
* and also not containing any `null`s.
*/
typedef std::map<std::string, Attr> Attrs;
Attrs jsonToAttrs(const nlohmann::json & json);

View file

@ -36,6 +36,7 @@ Input Input::fromURL(const ParsedURL & url, bool requireTree)
for (auto & inputScheme : *inputSchemes) {
auto res = inputScheme->inputFromURL(url, requireTree);
if (res) {
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
res->scheme = inputScheme;
fixupInput(*res);
return std::move(*res);
@ -50,6 +51,7 @@ Input Input::fromAttrs(Attrs && attrs)
for (auto & inputScheme : *inputSchemes) {
auto res = inputScheme->inputFromAttrs(attrs);
if (res) {
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
res->scheme = inputScheme;
fixupInput(*res);
return std::move(*res);
@ -82,16 +84,16 @@ std::string Input::to_string() const
return toURL().to_string();
}
bool Input::isDirect() const
{
return !scheme || scheme->isDirect(*this);
}
Attrs Input::toAttrs() const
{
return attrs;
}
bool Input::hasAllInfo() const
{
return getNarHash() && scheme && scheme->hasAllInfo(*this);
}
bool Input::operator ==(const Input & other) const
{
return attrs == other.attrs;
@ -107,7 +109,7 @@ bool Input::contains(const Input & other) const
return false;
}
std::pair<Tree, Input> Input::fetch(ref<Store> store) const
std::pair<StorePath, Input> Input::fetch(ref<Store> store) const
{
if (!scheme)
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
@ -115,7 +117,7 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
/* The tree may already be in the Nix store, or it could be
substituted (which is often faster than fetching from the
original source). So check that. */
if (hasAllInfo()) {
if (getNarHash()) {
try {
auto storePath = computeStorePath(*store);
@ -124,7 +126,7 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
debug("using substituted/cached input '%s' in '%s'",
to_string(), store->printStorePath(storePath));
return {Tree { .actualPath = store->toRealPath(storePath), .storePath = std::move(storePath) }, *this};
return {std::move(storePath), *this};
} catch (Error & e) {
debug("substitution of input '%s' failed: %s", to_string(), e.what());
}
@ -139,18 +141,16 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
}
}();
Tree tree {
.actualPath = store->toRealPath(storePath),
.storePath = storePath,
};
auto narHash = store->queryPathInfo(tree.storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(SRI, true));
auto narHash = store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
if (auto prevNarHash = getNarHash()) {
if (narHash != *prevNarHash)
throw Error((unsigned int) 102, "NAR hash mismatch in input '%s' (%s), expected '%s', got '%s'",
to_string(), tree.actualPath, prevNarHash->to_string(SRI, true), narHash.to_string(SRI, true));
to_string(),
store->printStorePath(storePath),
prevNarHash->to_string(HashFormat::SRI, true),
narHash.to_string(HashFormat::SRI, true));
}
if (auto prevLastModified = getLastModified()) {
@ -173,9 +173,7 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
input.locked = true;
assert(input.hasAllInfo());
return {std::move(tree), input};
return {std::move(storePath), input};
}
Input Input::applyOverrides(
@ -254,7 +252,8 @@ std::optional<Hash> Input::getRev() const
try {
hash = Hash::parseAnyPrefixed(*s);
} catch (BadHash &e) {
// Default to sha1 for backwards compatibility with existing flakes
// Default to sha1 for backwards compatibility with existing
// usages (e.g. `builtins.fetchTree` calls or flake inputs).
hash = Hash::parseAny(*s, htSHA1);
}
}
@ -308,4 +307,9 @@ void InputScheme::clone(const Input & input, const Path & destDir) const
throw Error("do not know how to clone input '%s'", input.to_string());
}
std::optional<ExperimentalFeature> InputScheme::experimentalFeature()
{
return {};
}
}

View file

@ -13,21 +13,14 @@ namespace nix { class Store; }
namespace nix::fetchers {
struct Tree
{
Path actualPath;
StorePath storePath;
};
struct InputScheme;
/**
* The Input object is generated by a specific fetcher, based on the
* user-supplied input attribute in the flake.nix file, and contains
* The `Input` object is generated by a specific fetcher, based on
* user-supplied information, and contains
* the information that the specific fetcher needs to perform the
* actual fetch. The Input object is most commonly created via the
* "fromURL()" or "fromAttrs()" static functions which are provided
* the url or attrset specified in the flake file.
* `fromURL()` or `fromAttrs()` static functions.
*/
struct Input
{
@ -36,7 +29,6 @@ struct Input
std::shared_ptr<InputScheme> scheme; // note: can be null
Attrs attrs;
bool locked = false;
bool direct = true;
/**
* path of the parent of this input, used for relative path resolution
@ -44,10 +36,20 @@ struct Input
std::optional<Path> parent;
public:
/**
* Create an `Input` from a URL.
*
* The URL indicate which sort of fetcher, and provides information to that fetcher.
*/
static Input fromURL(const std::string & url, bool requireTree = true);
static Input fromURL(const ParsedURL & url, bool requireTree = true);
/**
* Create an `Input` from a an `Attrs`.
*
* The URL indicate which sort of fetcher, and provides information to that fetcher.
*/
static Input fromAttrs(Attrs && attrs);
ParsedURL toURL() const;
@ -62,7 +64,7 @@ public:
* Check whether this is a "direct" input, that is, not
* one that goes through a registry.
*/
bool isDirect() const { return direct; }
bool isDirect() const;
/**
* Check whether this is a "locked" input, that is,
@ -70,24 +72,15 @@ public:
*/
bool isLocked() const { return locked; }
/**
* Check whether the input carries all necessary info required
* for cache insertion and substitution.
* These fields are used to uniquely identify cached trees
* within the "tarball TTL" window without necessarily
* indicating that the input's origin is unchanged.
*/
bool hasAllInfo() const;
bool operator ==(const Input & other) const;
bool contains(const Input & other) const;
/**
* Fetch the input into the Nix store, returning the location in
* the Nix store and the locked input.
* Fetch the entire input into the Nix store, returning the
* location in the Nix store and the locked input.
*/
std::pair<Tree, Input> fetch(ref<Store> store) const;
std::pair<StorePath, Input> fetch(ref<Store> store) const;
Input applyOverrides(
std::optional<std::string> ref,
@ -116,13 +109,13 @@ public:
/**
* The InputScheme represents a type of fetcher. Each fetcher
* registers with nix at startup time. When processing an input for a
* flake, each scheme is given an opportunity to "recognize" that
* input from the url or attributes in the flake file's specification
* and return an Input object to represent the input if it is
* recognized. The Input object contains the information the fetcher
* needs to actually perform the "fetch()" when called.
* The `InputScheme` represents a type of fetcher. Each fetcher
* registers with nix at startup time. When processing an `Input`,
* each scheme is given an opportunity to "recognize" that
* input from the user-provided url or attributes
* and return an `Input` object to represent the input if it is
* recognized. The `Input` object contains the information the fetcher
* needs to actually perform the `fetch()` when called.
*/
struct InputScheme
{
@ -135,8 +128,6 @@ struct InputScheme
virtual ParsedURL toURL(const Input & input) const;
virtual bool hasAllInfo(const Input & input) const = 0;
virtual Input applyOverrides(
const Input & input,
std::optional<std::string> ref,
@ -149,37 +140,16 @@ struct InputScheme
virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg);
virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0;
/**
* Is this `InputScheme` part of an experimental feature?
*/
virtual std::optional<ExperimentalFeature> experimentalFeature();
virtual bool isDirect(const Input & input) const
{ return true; }
};
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
struct DownloadFileResult
{
StorePath storePath;
std::string etag;
std::string effectiveUrl;
std::optional<std::string> immutableUrl;
};
DownloadFileResult downloadFile(
ref<Store> store,
const std::string & url,
const std::string & name,
bool locked,
const Headers & headers = {});
struct DownloadTarballResult
{
Tree tree;
time_t lastModified;
std::optional<std::string> immutableUrl;
};
DownloadTarballResult downloadTarball(
ref<Store> store,
const std::string & url,
const std::string & name,
bool locked,
const Headers & headers = {});
}

View file

@ -0,0 +1,130 @@
#include "fs-input-accessor.hh"
#include "posix-source-accessor.hh"
#include "store-api.hh"
namespace nix {
struct FSInputAccessorImpl : FSInputAccessor, PosixSourceAccessor
{
CanonPath root;
std::optional<std::set<CanonPath>> allowedPaths;
MakeNotAllowedError makeNotAllowedError;
FSInputAccessorImpl(
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths,
MakeNotAllowedError && makeNotAllowedError)
: root(root)
, allowedPaths(std::move(allowedPaths))
, makeNotAllowedError(std::move(makeNotAllowedError))
{
}
void readFile(
const CanonPath & path,
Sink & sink,
std::function<void(uint64_t)> sizeCallback) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
PosixSourceAccessor::readFile(absPath, sink, sizeCallback);
}
bool pathExists(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
return isAllowed(absPath) && PosixSourceAccessor::pathExists(absPath);
}
Stat lstat(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
return PosixSourceAccessor::lstat(absPath);
}
DirEntries readDirectory(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
DirEntries res;
for (auto & entry : PosixSourceAccessor::readDirectory(absPath))
if (isAllowed(absPath + entry.first))
res.emplace(entry);
return res;
}
std::string readLink(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
return PosixSourceAccessor::readLink(absPath);
}
CanonPath makeAbsPath(const CanonPath & path)
{
return root + path;
}
void checkAllowed(const CanonPath & absPath) override
{
if (!isAllowed(absPath))
throw makeNotAllowedError
? makeNotAllowedError(absPath)
: RestrictedPathError("access to path '%s' is forbidden", absPath);
}
bool isAllowed(const CanonPath & absPath)
{
if (!absPath.isWithin(root))
return false;
if (allowedPaths) {
auto p = absPath.removePrefix(root);
if (!p.isAllowed(*allowedPaths))
return false;
}
return true;
}
void allowPath(CanonPath path) override
{
if (allowedPaths)
allowedPaths->insert(std::move(path));
}
bool hasAccessControl() override
{
return (bool) allowedPaths;
}
std::optional<CanonPath> getPhysicalPath(const CanonPath & path) override
{
return makeAbsPath(path);
}
};
ref<FSInputAccessor> makeFSInputAccessor(
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths,
MakeNotAllowedError && makeNotAllowedError)
{
return make_ref<FSInputAccessorImpl>(root, std::move(allowedPaths), std::move(makeNotAllowedError));
}
ref<FSInputAccessor> makeStorePathAccessor(
ref<Store> store,
const StorePath & storePath,
MakeNotAllowedError && makeNotAllowedError)
{
return makeFSInputAccessor(CanonPath(store->toRealPath(storePath)), {}, std::move(makeNotAllowedError));
}
SourcePath getUnfilteredRootPath(CanonPath path)
{
static auto rootFS = makeFSInputAccessor(CanonPath::root);
return {rootFS, path};
}
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "input-accessor.hh"
namespace nix {
class StorePath;
class Store;
struct FSInputAccessor : InputAccessor
{
virtual void checkAllowed(const CanonPath & absPath) = 0;
virtual void allowPath(CanonPath path) = 0;
virtual bool hasAccessControl() = 0;
};
typedef std::function<RestrictedPathError(const CanonPath & path)> MakeNotAllowedError;
ref<FSInputAccessor> makeFSInputAccessor(
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths = {},
MakeNotAllowedError && makeNotAllowedError = {});
ref<FSInputAccessor> makeStorePathAccessor(
ref<Store> store,
const StorePath & storePath,
MakeNotAllowedError && makeNotAllowedError = {});
SourcePath getUnfilteredRootPath(CanonPath path);
}

View file

@ -46,7 +46,7 @@ bool touchCacheFile(const Path & path, time_t touch_time)
Path getCachePath(std::string_view key)
{
return getCacheDir() + "/nix/gitv3/" +
hashString(htSHA256, key).to_string(Base32, false);
hashString(htSHA256, key).to_string(HashFormat::Base32, false);
}
// Returns the name of the HEAD branch.
@ -293,7 +293,6 @@ struct GitInputScheme : InputScheme
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name" && name != "dirtyRev" && name != "dirtyShortRev")
throw Error("unsupported Git input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url"));
maybeGetBoolAttr(attrs, "shallow");
maybeGetBoolAttr(attrs, "submodules");
maybeGetBoolAttr(attrs, "allRefs");
@ -305,6 +304,9 @@ struct GitInputScheme : InputScheme
Input input;
input.attrs = attrs;
auto url = fixGitURL(getStrAttr(attrs, "url"));
parseURL(url);
input.attrs["url"] = url;
return input;
}
@ -319,15 +321,6 @@ struct GitInputScheme : InputScheme
return url;
}
bool hasAllInfo(const Input & input) const override
{
bool maybeDirty = !input.getRef();
bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false);
return
maybeGetIntAttr(input.attrs, "lastModified")
&& (shallow || maybeDirty || maybeGetIntAttr(input.attrs, "revCount"));
}
Input applyOverrides(
const Input & input,
std::optional<std::string> ref,
@ -415,7 +408,7 @@ struct GitInputScheme : InputScheme
auto checkHashType = [&](const std::optional<Hash> & hash)
{
if (hash.has_value() && !(hash->type == htSHA1 || hash->type == htSHA256))
throw Error("Hash '%s' is not supported by Git. Supported types are sha1 and sha256.", hash->to_string(Base16, true));
throw Error("Hash '%s' is not supported by Git. Supported types are sha1 and sha256.", hash->to_string(HashFormat::Base16, true));
};
auto getLockedAttrs = [&]()

View file

@ -7,6 +7,7 @@
#include "git.hh"
#include "fetchers.hh"
#include "fetch-settings.hh"
#include "tarball.hh"
#include <optional>
#include <nlohmann/json.hpp>
@ -125,18 +126,13 @@ struct GitArchiveInputScheme : InputScheme
auto path = owner + "/" + repo;
assert(!(ref && rev));
if (ref) path += "/" + *ref;
if (rev) path += "/" + rev->to_string(Base16, false);
if (rev) path += "/" + rev->to_string(HashFormat::Base16, false);
return ParsedURL {
.scheme = type(),
.path = path,
};
}
bool hasAllInfo(const Input & input) const override
{
return input.getRev() && maybeGetIntAttr(input.attrs, "lastModified");
}
Input applyOverrides(
const Input & _input,
std::optional<std::string> ref,
@ -218,10 +214,15 @@ struct GitArchiveInputScheme : InputScheme
{"rev", rev->gitRev()},
{"lastModified", uint64_t(result.lastModified)}
},
result.tree.storePath,
result.storePath,
true);
return {result.tree.storePath, input};
return {result.storePath, input};
}
std::optional<ExperimentalFeature> experimentalFeature() override
{
return Xp::Flakes;
}
};
@ -291,7 +292,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
: "https://api.%s/repos/%s/%s/tarball/%s";
const auto url = fmt(urlFmt, host, getOwner(input), getRepo(input),
input.getRev()->to_string(Base16, false));
input.getRev()->to_string(HashFormat::Base16, false));
return DownloadUrl { url, headers };
}
@ -357,7 +358,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/archive.tar.gz?sha=%s",
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
input.getRev()->to_string(Base16, false));
input.getRev()->to_string(HashFormat::Base16, false));
Headers headers = makeHeadersWithAuthTokens(host);
return DownloadUrl { url, headers };
@ -444,7 +445,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
auto url = fmt("https://%s/%s/%s/archive/%s.tar.gz",
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
input.getRev()->to_string(Base16, false));
input.getRev()->to_string(HashFormat::Base16, false));
Headers headers = makeHeadersWithAuthTokens(host);
return DownloadUrl { url, headers };

View file

@ -41,7 +41,6 @@ struct IndirectInputScheme : InputScheme
// FIXME: forbid query params?
Input input;
input.direct = false;
input.attrs.insert_or_assign("type", "indirect");
input.attrs.insert_or_assign("id", id);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
@ -63,7 +62,6 @@ struct IndirectInputScheme : InputScheme
throw BadURL("'%s' is not a valid flake ID", id);
Input input;
input.direct = false;
input.attrs = attrs;
return input;
}
@ -78,11 +76,6 @@ struct IndirectInputScheme : InputScheme
return url;
}
bool hasAllInfo(const Input & input) const override
{
return false;
}
Input applyOverrides(
const Input & _input,
std::optional<std::string> ref,
@ -98,6 +91,14 @@ struct IndirectInputScheme : InputScheme
{
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
}
std::optional<ExperimentalFeature> experimentalFeature() override
{
return Xp::Flakes;
}
bool isDirect(const Input & input) const override
{ return false; }
};
static auto rIndirectInputScheme = OnStartup([] { registerInputScheme(std::make_unique<IndirectInputScheme>()); });

View file

@ -3,12 +3,52 @@
namespace nix {
StorePath InputAccessor::fetchToStore(
ref<Store> store,
const CanonPath & path,
std::string_view name,
FileIngestionMethod method,
PathFilter * filter,
RepairFlag repair)
{
Activity act(*logger, lvlChatty, actUnknown, fmt("copying '%s' to the store", showPath(path)));
auto source = sinkToSource([&](Sink & sink) {
if (method == FileIngestionMethod::Recursive)
dumpPath(path, sink, filter ? *filter : defaultPathFilter);
else
readFile(path, sink);
});
auto storePath =
settings.readOnlyMode
? store->computeStorePathFromDump(*source, name, method, htSHA256).first
: store->addToStoreFromDump(*source, name, method, htSHA256, repair);
return storePath;
}
SourcePath InputAccessor::root()
{
return {ref(shared_from_this()), CanonPath::root};
}
std::ostream & operator << (std::ostream & str, const SourcePath & path)
{
str << path.to_string();
return str;
}
StorePath SourcePath::fetchToStore(
ref<Store> store,
std::string_view name,
FileIngestionMethod method,
PathFilter * filter,
RepairFlag repair) const
{
return accessor->fetchToStore(store, path, name, method, filter, repair);
}
std::string_view SourcePath::baseName() const
{
return path.baseName().value_or("source");
@ -18,60 +58,12 @@ SourcePath SourcePath::parent() const
{
auto p = path.parent();
assert(p);
return std::move(*p);
}
InputAccessor::Stat SourcePath::lstat() const
{
auto st = nix::lstat(path.abs());
return InputAccessor::Stat {
.type =
S_ISREG(st.st_mode) ? InputAccessor::tRegular :
S_ISDIR(st.st_mode) ? InputAccessor::tDirectory :
S_ISLNK(st.st_mode) ? InputAccessor::tSymlink :
InputAccessor::tMisc,
.isExecutable = S_ISREG(st.st_mode) && st.st_mode & S_IXUSR
};
}
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
{
// FIXME: merge these into one operation.
if (!pathExists())
return {};
return lstat();
}
InputAccessor::DirEntries SourcePath::readDirectory() const
{
InputAccessor::DirEntries res;
for (auto & entry : nix::readDirectory(path.abs())) {
std::optional<InputAccessor::Type> type;
switch (entry.type) {
case DT_REG: type = InputAccessor::Type::tRegular; break;
case DT_LNK: type = InputAccessor::Type::tSymlink; break;
case DT_DIR: type = InputAccessor::Type::tDirectory; break;
}
res.emplace(entry.name, type);
}
return res;
}
StorePath SourcePath::fetchToStore(
ref<Store> store,
std::string_view name,
PathFilter * filter,
RepairFlag repair) const
{
return
settings.readOnlyMode
? store->computeStorePathForPath(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter).first
: store->addToStore(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter, repair);
return {accessor, std::move(*p)};
}
SourcePath SourcePath::resolveSymlinks() const
{
SourcePath res(CanonPath::root);
auto res = accessor->root();
int linksAllowed = 1024;

View file

@ -1,40 +1,39 @@
#pragma once
#include "source-accessor.hh"
#include "ref.hh"
#include "types.hh"
#include "archive.hh"
#include "canon-path.hh"
#include "repair-flag.hh"
#include "content-address.hh"
namespace nix {
MakeError(RestrictedPathError, Error);
struct SourcePath;
class StorePath;
class Store;
struct InputAccessor
struct InputAccessor : SourceAccessor, std::enable_shared_from_this<InputAccessor>
{
enum Type {
tRegular, tSymlink, tDirectory,
/**
Any other node types that may be encountered on the file system, such as device nodes, sockets, named pipe, and possibly even more exotic things.
Responsible for `"unknown"` from `builtins.readFileType "/dev/null"`.
Unlike `DT_UNKNOWN`, this must not be used for deferring the lookup of types.
*/
tMisc
};
struct Stat
/**
* Return the maximum last-modified time of the files in this
* tree, if available.
*/
virtual std::optional<time_t> getLastModified()
{
Type type = tMisc;
//uint64_t fileSize = 0; // regular files only
bool isExecutable = false; // regular files only
};
return std::nullopt;
}
typedef std::optional<Type> DirEntry;
StorePath fetchToStore(
ref<Store> store,
const CanonPath & path,
std::string_view name = "source",
FileIngestionMethod method = FileIngestionMethod::Recursive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair);
typedef std::map<std::string, DirEntry> DirEntries;
SourcePath root();
};
/**
@ -45,12 +44,9 @@ struct InputAccessor
*/
struct SourcePath
{
ref<InputAccessor> accessor;
CanonPath path;
SourcePath(CanonPath path)
: path(std::move(path))
{ }
std::string_view baseName() const;
/**
@ -64,39 +60,42 @@ struct SourcePath
* return its contents; otherwise throw an error.
*/
std::string readFile() const
{ return nix::readFile(path.abs()); }
{ return accessor->readFile(path); }
/**
* Return whether this `SourcePath` denotes a file (of any type)
* that exists
*/
bool pathExists() const
{ return nix::pathExists(path.abs()); }
{ return accessor->pathExists(path); }
/**
* Return stats about this `SourcePath`, or throw an exception if
* it doesn't exist.
*/
InputAccessor::Stat lstat() const;
InputAccessor::Stat lstat() const
{ return accessor->lstat(path); }
/**
* Return stats about this `SourcePath`, or std::nullopt if it
* doesn't exist.
*/
std::optional<InputAccessor::Stat> maybeLstat() const;
std::optional<InputAccessor::Stat> maybeLstat() const
{ return accessor->maybeLstat(path); }
/**
* If this `SourcePath` denotes a directory (not a symlink),
* return its directory entries; otherwise throw an error.
*/
InputAccessor::DirEntries readDirectory() const;
InputAccessor::DirEntries readDirectory() const
{ return accessor->readDirectory(path); }
/**
* If this `SourcePath` denotes a symlink, return its target;
* otherwise throw an error.
*/
std::string readLink() const
{ return nix::readLink(path.abs()); }
{ return accessor->readLink(path); }
/**
* Dump this `SourcePath` to `sink` as a NAR archive.
@ -104,7 +103,7 @@ struct SourcePath
void dumpPath(
Sink & sink,
PathFilter & filter = defaultPathFilter) const
{ return nix::dumpPath(path.abs(), sink, filter); }
{ return accessor->dumpPath(path, sink, filter); }
/**
* Copy this `SourcePath` to the Nix store.
@ -112,6 +111,7 @@ struct SourcePath
StorePath fetchToStore(
ref<Store> store,
std::string_view name = "source",
FileIngestionMethod method = FileIngestionMethod::Recursive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair) const;
@ -120,7 +120,7 @@ struct SourcePath
* it has a physical location.
*/
std::optional<CanonPath> getPhysicalPath() const
{ return path; }
{ return accessor->getPhysicalPath(path); }
std::string to_string() const
{ return path.abs(); }
@ -129,7 +129,7 @@ struct SourcePath
* Append a `CanonPath` to this path.
*/
SourcePath operator + (const CanonPath & x) const
{ return {path + x}; }
{ return {accessor, path + x}; }
/**
* Append a single component `c` to this path. `c` must not
@ -137,21 +137,21 @@ struct SourcePath
* and `c`.
*/
SourcePath operator + (std::string_view c) const
{ return {path + c}; }
{ return {accessor, path + c}; }
bool operator == (const SourcePath & x) const
{
return path == x.path;
return std::tie(accessor, path) == std::tie(x.accessor, x.path);
}
bool operator != (const SourcePath & x) const
{
return path != x.path;
return std::tie(accessor, path) != std::tie(x.accessor, x.path);
}
bool operator < (const SourcePath & x) const
{
return path < x.path;
return std::tie(accessor, path) < std::tie(x.accessor, x.path);
}
/**

View file

@ -0,0 +1,54 @@
#include "memory-input-accessor.hh"
namespace nix {
struct MemoryInputAccessorImpl : MemoryInputAccessor
{
std::map<CanonPath, std::string> files;
std::string readFile(const CanonPath & path) override
{
auto i = files.find(path);
if (i == files.end())
throw Error("file '%s' does not exist", path);
return i->second;
}
bool pathExists(const CanonPath & path) override
{
auto i = files.find(path);
return i != files.end();
}
Stat lstat(const CanonPath & path) override
{
auto i = files.find(path);
if (i != files.end())
return Stat { .type = tRegular, .isExecutable = false };
throw Error("file '%s' does not exist", path);
}
DirEntries readDirectory(const CanonPath & path) override
{
return {};
}
std::string readLink(const CanonPath & path) override
{
throw UnimplementedError("MemoryInputAccessor::readLink");
}
SourcePath addFile(CanonPath path, std::string && contents) override
{
files.emplace(path, std::move(contents));
return {ref(shared_from_this()), std::move(path)};
}
};
ref<MemoryInputAccessor> makeMemoryInputAccessor()
{
return make_ref<MemoryInputAccessorImpl>();
}
}

View file

@ -0,0 +1,15 @@
#include "input-accessor.hh"
namespace nix {
/**
* An input accessor for an in-memory file system.
*/
struct MemoryInputAccessor : InputAccessor
{
virtual SourcePath addFile(CanonPath path, std::string && contents) = 0;
};
ref<MemoryInputAccessor> makeMemoryInputAccessor();
}

View file

@ -98,13 +98,6 @@ struct MercurialInputScheme : InputScheme
return url;
}
bool hasAllInfo(const Input & input) const override
{
// FIXME: ugly, need to distinguish between dirty and clean
// default trees.
return input.getRef() == "default" || maybeGetIntAttr(input.attrs, "revCount");
}
Input applyOverrides(
const Input & input,
std::optional<std::string> ref,
@ -206,7 +199,7 @@ struct MercurialInputScheme : InputScheme
auto checkHashType = [&](const std::optional<Hash> & hash)
{
if (hash.has_value() && hash->type != htSHA1)
throw Error("Hash '%s' is not supported by Mercurial. Only sha1 is supported.", hash->to_string(Base16, true));
throw Error("Hash '%s' is not supported by Mercurial. Only sha1 is supported.", hash->to_string(HashFormat::Base16, true));
};
@ -252,7 +245,7 @@ struct MercurialInputScheme : InputScheme
}
}
Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(htSHA256, actualUrl).to_string(Base32, false));
Path cacheDir = fmt("%s/nix/hg/%s", getCacheDir(), hashString(htSHA256, actualUrl).to_string(HashFormat::Base32, false));
/* If this is a commit hash that we already have, we don't
have to pull again. */

View file

@ -66,11 +66,6 @@ struct PathInputScheme : InputScheme
};
}
bool hasAllInfo(const Input & input) const override
{
return true;
}
std::optional<Path> getSourcePath(const Input & input) override
{
return getStrAttr(input.attrs, "path");
@ -125,6 +120,11 @@ struct PathInputScheme : InputScheme
return {std::move(*storePath), input};
}
std::optional<ExperimentalFeature> experimentalFeature() override
{
return Xp::Flakes;
}
};
static auto rPathInputScheme = OnStartup([] { registerInputScheme(std::make_unique<PathInputScheme>()); });

View file

@ -1,5 +1,5 @@
#include "registry.hh"
#include "fetchers.hh"
#include "tarball.hh"
#include "util.hh"
#include "globals.hh"
#include "store-api.hh"

View file

@ -1,3 +1,4 @@
#include "tarball.hh"
#include "fetchers.hh"
#include "cache.hh"
#include "filetransfer.hh"
@ -133,7 +134,7 @@ DownloadTarballResult downloadTarball(
if (cached && !cached->expired)
return {
.tree = Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
.storePath = std::move(cached->storePath),
.lastModified = (time_t) getIntAttr(cached->infoAttrs, "lastModified"),
.immutableUrl = maybeGetStrAttr(cached->infoAttrs, "immutableUrl"),
};
@ -174,7 +175,7 @@ DownloadTarballResult downloadTarball(
locked);
return {
.tree = Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
.storePath = std::move(*unpackedStorePath),
.lastModified = lastModified,
.immutableUrl = res.immutableUrl,
};
@ -232,7 +233,7 @@ struct CurlInputScheme : InputScheme
if (type != inputType()) return {};
// FIXME: some of these only apply to TarballInputScheme.
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount"};
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount", "lastModified"};
for (auto & [name, value] : attrs)
if (!allowedNames.count(name))
throw Error("unsupported %s input attribute '%s'", *type, name);
@ -250,15 +251,9 @@ struct CurlInputScheme : InputScheme
// NAR hashes are preferred over file hashes since tar/zip
// files don't have a canonical representation.
if (auto narHash = input.getNarHash())
url.query.insert_or_assign("narHash", narHash->to_string(SRI, true));
url.query.insert_or_assign("narHash", narHash->to_string(HashFormat::SRI, true));
return url;
}
bool hasAllInfo(const Input & input) const override
{
return true;
}
};
struct FileInputScheme : CurlInputScheme
@ -310,7 +305,10 @@ struct TarballInputScheme : CurlInputScheme
input = immutableInput;
}
return {result.tree.storePath, std::move(input)};
if (result.lastModified && !input.attrs.contains("lastModified"))
input.attrs.insert_or_assign("lastModified", uint64_t(result.lastModified));
return {result.storePath, std::move(input)};
}
};

View file

@ -0,0 +1,43 @@
#pragma once
#include "types.hh"
#include "path.hh"
#include <optional>
namespace nix {
class Store;
}
namespace nix::fetchers {
struct DownloadFileResult
{
StorePath storePath;
std::string etag;
std::string effectiveUrl;
std::optional<std::string> immutableUrl;
};
DownloadFileResult downloadFile(
ref<Store> store,
const std::string & url,
const std::string & name,
bool locked,
const Headers & headers = {});
struct DownloadTarballResult
{
StorePath storePath;
time_t lastModified;
std::optional<std::string> immutableUrl;
};
DownloadTarballResult downloadTarball(
ref<Store> store,
const std::string & url,
const std::string & name,
bool locked,
const Headers & headers = {});
}

View file

@ -1,4 +1,5 @@
#include "common-args.hh"
#include "args/root.hh"
#include "globals.hh"
#include "loggers.hh"
@ -34,21 +35,21 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
.description = "Set the Nix configuration setting *name* to *value* (overriding `nix.conf`).",
.category = miscCategory,
.labels = {"name", "value"},
.handler = {[](std::string name, std::string value) {
.handler = {[this](std::string name, std::string value) {
try {
globalConfig.set(name, value);
} catch (UsageError & e) {
if (!completions)
if (!getRoot().completions)
warn(e.what());
}
}},
.completer = [](size_t index, std::string_view prefix) {
.completer = [](AddCompletions & completions, size_t index, std::string_view prefix) {
if (index == 0) {
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
for (auto & s : settings)
if (hasPrefix(s.first, prefix))
completions->add(s.first, fmt("Set the `%s` setting.", s.first));
completions.add(s.first, fmt("Set the `%s` setting.", s.first));
}
}
});

View file

@ -108,7 +108,8 @@ public:
stop();
}
void stop() override
/* Called by destructor, can't be overridden */
void stop() override final
{
{
auto state(state_.lock());

View file

@ -379,9 +379,9 @@ RunPager::RunPager()
});
pid.setKillSignal(SIGINT);
stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0);
std_out = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 0);
if (dup2(toPager.writeSide.get(), STDOUT_FILENO) == -1)
throw SysError("dupping stdout");
throw SysError("dupping standard output");
}
@ -390,7 +390,7 @@ RunPager::~RunPager()
try {
if (pid != -1) {
std::cout.flush();
dup2(stdout, STDOUT_FILENO);
dup2(std_out, STDOUT_FILENO);
pid.wait();
}
} catch (...) {

View file

@ -3,6 +3,7 @@
#include "util.hh"
#include "args.hh"
#include "args/root.hh"
#include "common-args.hh"
#include "path.hh"
#include "derived-path.hh"
@ -66,7 +67,7 @@ template<class N> N getIntArg(const std::string & opt,
}
struct LegacyArgs : public MixCommonArgs
struct LegacyArgs : public MixCommonArgs, public RootArgs
{
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg;
@ -85,8 +86,9 @@ struct LegacyArgs : public MixCommonArgs
void showManPage(const std::string & name);
/**
* The constructor of this class starts a pager if stdout is a
* terminal and $PAGER is set. Stdout is redirected to the pager.
* The constructor of this class starts a pager if standard output is a
* terminal and $PAGER is set. Standard output is redirected to the
* pager.
*/
class RunPager
{
@ -96,7 +98,7 @@ public:
private:
Pid pid;
int stdout;
int std_out;
};
extern volatile ::sig_atomic_t blockInt;

View file

@ -164,7 +164,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
auto [fileHash, fileSize] = fileHashSink.finish();
narInfo->fileHash = fileHash;
narInfo->fileSize = fileSize;
narInfo->url = "nar/" + narInfo->fileHash->to_string(Base32, false) + ".nar"
narInfo->url = "nar/" + narInfo->fileHash->to_string(HashFormat::Base32, false) + ".nar"
+ (compression == "xz" ? ".xz" :
compression == "bzip2" ? ".bz2" :
compression == "zstd" ? ".zst" :

View file

@ -0,0 +1,18 @@
#include "build-result.hh"
namespace nix {
GENERATE_CMP_EXT(
,
BuildResult,
me->status,
me->errorMsg,
me->timesBuilt,
me->isNonDeterministic,
me->builtOutputs,
me->startTime,
me->stopTime,
me->cpuUser,
me->cpuSystem);
}

View file

@ -3,6 +3,7 @@
#include "realisation.hh"
#include "derived-path.hh"
#include "comparator.hh"
#include <string>
#include <chrono>
@ -100,6 +101,8 @@ struct BuildResult
*/
std::optional<std::chrono::microseconds> cpuUser, cpuSystem;
DECLARE_CMP(BuildResult);
bool success()
{
return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid;

View file

@ -8,8 +8,8 @@
#include "util.hh"
#include "archive.hh"
#include "compression.hh"
#include "worker-protocol.hh"
#include "worker-protocol-impl.hh"
#include "common-protocol.hh"
#include "common-protocol-impl.hh"
#include "topo-sort.hh"
#include "callback.hh"
#include "local-store.hh" // TODO remove, along with remaining downcasts
@ -65,7 +65,7 @@ namespace nix {
DerivationGoal::DerivationGoal(const StorePath & drvPath,
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
, useDerivation(true)
, drvPath(drvPath)
, wantedOutputs(wantedOutputs)
@ -74,7 +74,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
state = &DerivationGoal::getDerivation;
name = fmt(
"building of '%s' from .drv file",
DerivedPath::Built { drvPath, wantedOutputs }.to_string(worker.store));
DerivedPath::Built { makeConstantStorePathRef(drvPath), wantedOutputs }.to_string(worker.store));
trace("created");
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
@ -84,7 +84,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
: Goal(worker, DerivedPath::Built { .drvPath = makeConstantStorePathRef(drvPath), .outputs = wantedOutputs })
, useDerivation(false)
, drvPath(drvPath)
, wantedOutputs(wantedOutputs)
@ -95,7 +95,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation
state = &DerivationGoal::haveDerivation;
name = fmt(
"building of '%s' from in-memory derivation",
DerivedPath::Built { drvPath, drv.outputNames() }.to_string(worker.store));
DerivedPath::Built { makeConstantStorePathRef(drvPath), drv.outputNames() }.to_string(worker.store));
trace("created");
mcExpectedBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.expectedBuilds);
@ -368,20 +368,37 @@ void DerivationGoal::gaveUpOnSubstitution()
/* The inputs must be built before we can build this goal. */
inputDrvOutputs.clear();
if (useDerivation)
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
if (useDerivation) {
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> addWaiteeDerivedPath;
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
addWaitee(worker.makeGoal(
DerivedPath::Built {
.drvPath = inputDrv,
.outputs = inputNode.value,
},
buildMode == bmRepair ? bmRepair : bmNormal));
for (const auto & [outputName, childNode] : inputNode.childMap)
addWaiteeDerivedPath(
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
childNode);
};
for (const auto & [inputDrvPath, inputNode] : dynamic_cast<Derivation *>(drv.get())->inputDrvs.map) {
/* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = worker.evalStore.readDerivation(i.first);
auto inputDrv = worker.evalStore.readDerivation(inputDrvPath);
if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'",
worker.store.printStorePath(drvPath),
worker.store.printStorePath(i.first));
worker.store.printStorePath(inputDrvPath));
}
addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
addWaiteeDerivedPath(makeConstantStorePathRef(inputDrvPath), inputNode);
}
}
/* Copy the input sources from the eval store to the build
store. */
@ -452,7 +469,12 @@ void DerivationGoal::repairClosure()
if (drvPath2 == outputsToDrv.end())
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
else
addWaitee(worker.makeDerivationGoal(drvPath2->second, OutputsSpec::All(), bmRepair));
addWaitee(worker.makeGoal(
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath2->second),
.outputs = OutputsSpec::All { },
},
bmRepair));
}
if (waitees.empty()) {
@ -509,7 +531,7 @@ void DerivationGoal::inputsRealised()
return ia.deferred;
},
[&](const DerivationType::ContentAddressed & ca) {
return !fullDrv.inputDrvs.empty() && (
return !fullDrv.inputDrvs.map.empty() && (
ca.fixed
/* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */
@ -521,9 +543,9 @@ void DerivationGoal::inputsRealised()
[&](const DerivationType::Impure &) {
return true;
}
}, drvType.raw());
}, drvType.raw);
if (resolveDrv && !fullDrv.inputDrvs.empty()) {
if (resolveDrv && !fullDrv.inputDrvs.map.empty()) {
experimentalFeatureSettings.require(Xp::CaDerivations);
/* We are be able to resolve this derivation based on the
@ -539,7 +561,7 @@ void DerivationGoal::inputsRealised()
attempt = fullDrv.tryResolve(worker.store);
}
assert(attempt);
Derivation drvResolved { *std::move(attempt) };
Derivation drvResolved { std::move(*attempt) };
auto pathResolved = writeDerivation(worker.store, drvResolved);
@ -560,11 +582,13 @@ void DerivationGoal::inputsRealised()
return;
}
for (auto & [depDrvPath, wantedDepOutputs] : fullDrv.inputDrvs) {
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
accumInputPaths = [&](const StorePath & depDrvPath, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
/* Add the relevant output closures of the input derivation
`i' as input paths. Only add the closures of output paths
that are specified as inputs. */
for (auto & j : wantedDepOutputs) {
auto getOutput = [&](const std::string & outputName) {
/* TODO (impure derivations-induced tech debt):
Tracking input derivation outputs statefully through the
goals is error prone and has led to bugs.
@ -576,21 +600,30 @@ void DerivationGoal::inputsRealised()
a representation in the store, which is a usability problem
in itself. When implementing this logic entirely with lookups
make sure that they're cached. */
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
worker.store.computeFSClosure(*outPath, inputPaths);
if (auto outPath = get(inputDrvOutputs, { depDrvPath, outputName })) {
return *outPath;
}
else {
auto outMap = worker.evalStore.queryDerivationOutputMap(depDrvPath);
auto outMapPath = outMap.find(j);
auto outMapPath = outMap.find(outputName);
if (outMapPath == outMap.end()) {
throw Error(
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
worker.store.printStorePath(drvPath), outputName, worker.store.printStorePath(depDrvPath));
}
worker.store.computeFSClosure(outMapPath->second, inputPaths);
return outMapPath->second;
}
}
}
};
for (auto & outputName : inputNode.value)
worker.store.computeFSClosure(getOutput(outputName), inputPaths);
for (auto & [outputName, childNode] : inputNode.childMap)
accumInputPaths(getOutput(outputName), childNode);
};
for (auto & [depDrvPath, depNode] : fullDrv.inputDrvs.map)
accumInputPaths(depDrvPath, depNode);
}
/* Second, the input sources. */
@ -996,10 +1029,11 @@ void DerivationGoal::buildDone()
}
else {
assert(derivationType);
st =
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
statusOk(status) ? BuildResult::OutputRejected :
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure :
!derivationType->isSandboxed() || diskFull ? BuildResult::TransientFailure :
BuildResult::PermanentFailure;
}
@ -1151,11 +1185,11 @@ HookReply DerivationGoal::tryBuildHook()
throw;
}
WorkerProto::WriteConn conn { hook->sink };
CommonProto::WriteConn conn { hook->sink };
/* Tell the hook all the inputs that have to be copied to the
remote system. */
WorkerProto::write(worker.store, conn, inputPaths);
CommonProto::write(worker.store, conn, inputPaths);
/* Tell the hooks the missing outputs that have to be copied back
from the remote system. */
@ -1166,7 +1200,7 @@ HookReply DerivationGoal::tryBuildHook()
if (buildMode != bmCheck && status.known && status.known->isValid()) continue;
missingOutputs.insert(outputName);
}
WorkerProto::write(worker.store, conn, missingOutputs);
CommonProto::write(worker.store, conn, missingOutputs);
}
hook->sink = FdSink();
@ -1358,7 +1392,7 @@ std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity()
[&](const OutputsSpec::Names & names) {
return static_cast<StringSet>(names);
},
}, wantedOutputs.raw());
}, wantedOutputs.raw);
SingleDrvOutputs validOutputs;
for (auto & i : queryPartialDerivationOutputMap()) {
@ -1485,12 +1519,13 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
if (!dg) return;
auto outputs = fullDrv.inputDrvs.find(dg->drvPath);
if (outputs == fullDrv.inputDrvs.end()) return;
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
if (!nodeP) return;
auto & outputs = nodeP->value;
for (auto & outputName : outputs->second) {
for (auto & outputName : outputs) {
auto buildResult = dg->getBuildResult(DerivedPath::Built {
.drvPath = dg->drvPath,
.drvPath = makeConstantStorePathRef(dg->drvPath),
.outputs = OutputsSpec::Names { outputName },
});
if (buildResult.success()) {

View file

@ -50,6 +50,9 @@ struct InitialOutput {
std::optional<InitialOutputStatus> known;
};
/**
* A goal for building some or all of the outputs of a derivation.
*/
struct DerivationGoal : public Goal
{
/**
@ -66,8 +69,7 @@ struct DerivationGoal : public Goal
std::shared_ptr<DerivationGoal> resolvedDrvGoal;
/**
* The specific outputs that we need to build. Empty means all of
* them.
* The specific outputs that we need to build.
*/
OutputsSpec wantedOutputs;
@ -184,7 +186,7 @@ struct DerivationGoal : public Goal
/**
* The sort of derivation we are building.
*/
DerivationType derivationType;
std::optional<DerivationType> derivationType;
typedef void (DerivationGoal::*GoalState)();
GoalState state;
@ -334,7 +336,9 @@ struct DerivationGoal : public Goal
StorePathSet exportReferences(const StorePathSet & storePaths);
JobCategory jobCategory() override { return JobCategory::Build; };
JobCategory jobCategory() const override {
return JobCategory::Build;
};
};
MakeError(NotDeterministic, BuildError);

View file

@ -73,7 +73,9 @@ public:
void work() override;
void handleEOF(int fd) override;
JobCategory jobCategory() override { return JobCategory::Substitution; };
JobCategory jobCategory() const override {
return JobCategory::Substitution;
};
};
}

View file

@ -25,8 +25,10 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
ex = std::move(i->ex);
}
if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get()))
failed.insert(i2->drvPath);
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get()))
failed.insert(i2->storePath);
}
}
@ -77,7 +79,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
try {
worker.run(Goals{goal});
return goal->getBuildResult(DerivedPath::Built {
.drvPath = drvPath,
.drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All {},
});
} catch (Error & e) {
@ -124,8 +126,11 @@ void Store::repairPath(const StorePath & path)
auto info = queryPathInfo(path);
if (info->deriver && isValidPath(*info->deriver)) {
goals.clear();
// FIXME: Should just build the specific output we need.
goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
goals.insert(worker.makeGoal(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*info->deriver),
// FIXME: Should just build the specific output we need.
.outputs = OutputsSpec::All { },
}, bmRepair));
worker.run(goals);
} else
throw Error(worker.failingExitStatus(), "cannot repair path '%s'", printStorePath(path));

View file

@ -11,7 +11,7 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
}
BuildResult Goal::getBuildResult(const DerivedPath & req) {
BuildResult Goal::getBuildResult(const DerivedPath & req) const {
BuildResult res { buildResult };
if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {

View file

@ -41,7 +41,13 @@ typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
* of each category in parallel.
*/
enum struct JobCategory {
/**
* A build of a derivation; it will use CPU and disk resources.
*/
Build,
/**
* A substitution an arbitrary store object; it will use network resources.
*/
Substitution,
};
@ -110,7 +116,7 @@ public:
* sake of both privacy and determinism, and this "safe accessor"
* ensures we don't.
*/
BuildResult getBuildResult(const DerivedPath &);
BuildResult getBuildResult(const DerivedPath &) const;
/**
* Exception containing an error message, if any.
@ -144,7 +150,7 @@ public:
void trace(std::string_view s);
std::string getName()
std::string getName() const
{
return name;
}
@ -162,7 +168,11 @@ public:
virtual void cleanup() { }
virtual JobCategory jobCategory() = 0;
/**
* @brief Hint for the scheduler, which concurrency limit applies.
* @see JobCategory
*/
virtual JobCategory jobCategory() const = 0;
};
void addToWeakGoals(WeakGoals & goals, GoalPtr p);

View file

@ -178,6 +178,8 @@ void LocalDerivationGoal::tryLocalBuild()
return;
}
assert(derivationType);
/* Are we doing a chroot build? */
{
auto noChroot = parsedDrv->getBoolAttr("__noChroot");
@ -195,7 +197,7 @@ void LocalDerivationGoal::tryLocalBuild()
else if (settings.sandboxMode == smDisabled)
useChroot = false;
else if (settings.sandboxMode == smRelaxed)
useChroot = derivationType.isSandboxed() && !noChroot;
useChroot = derivationType->isSandboxed() && !noChroot;
}
auto & localStore = getLocalStore();
@ -690,7 +692,7 @@ void LocalDerivationGoal::startBuilder()
"nogroup:x:65534:\n", sandboxGid()));
/* Create /etc/hosts with localhost entry. */
if (derivationType.isSandboxed())
if (derivationType->isSandboxed())
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
/* Make the closure of the inputs available in the chroot,
@ -894,7 +896,7 @@ void LocalDerivationGoal::startBuilder()
us.
*/
if (derivationType.isSandboxed())
if (derivationType->isSandboxed())
privateNetwork = true;
userNamespaceSync.create();
@ -1065,7 +1067,7 @@ void LocalDerivationGoal::initTmpDir() {
env[i.first] = i.second;
} else {
auto hash = hashString(htSHA256, i.first);
std::string fn = ".attr-" + hash.to_string(Base32, false);
std::string fn = ".attr-" + hash.to_string(HashFormat::Base32, false);
Path p = tmpDir + "/" + fn;
writeFile(p, rewriteStrings(i.second, inputRewrites));
chownToBuilder(p);
@ -1122,7 +1124,7 @@ void LocalDerivationGoal::initEnv()
derivation, tell the builder, so that for instance `fetchurl'
can skip checking the output. On older Nixes, this environment
variable won't be set, so `fetchurl' will do the check. */
if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
if (derivationType->isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
/* *Only* if this is a fixed-output derivation, propagate the
values of the environment variables specified in the
@ -1133,9 +1135,19 @@ void LocalDerivationGoal::initEnv()
to the builder is generally impure, but the output of
fixed-output derivations is by definition pure (since we
already know the cryptographic hash of the output). */
if (!derivationType.isSandboxed()) {
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
env[i] = getEnv(i).value_or("");
if (!derivationType->isSandboxed()) {
auto & impureEnv = settings.impureEnv.get();
if (!impureEnv.empty())
experimentalFeatureSettings.require(Xp::ConfigurableImpureEnv);
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) {
auto envVar = impureEnv.find(i);
if (envVar != impureEnv.end()) {
env[i] = envVar->second;
} else {
env[i] = getEnv(i).value_or("");
}
}
}
/* Currently structured log messages piggyback on stderr, but we
@ -1173,6 +1185,19 @@ void LocalDerivationGoal::writeStructuredAttrs()
}
static StorePath pathPartOfReq(const SingleDerivedPath & req)
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & bo) {
return bo.path;
},
[&](const SingleDerivedPath::Built & bfd) {
return pathPartOfReq(*bfd.drvPath);
},
}, req.raw());
}
static StorePath pathPartOfReq(const DerivedPath & req)
{
return std::visit(overloaded {
@ -1180,7 +1205,7 @@ static StorePath pathPartOfReq(const DerivedPath & req)
return bo.path;
},
[&](const DerivedPath::Built & bfd) {
return bfd.drvPath;
return pathPartOfReq(*bfd.drvPath);
},
}, req.raw());
}
@ -1785,7 +1810,7 @@ void LocalDerivationGoal::runChild()
/* Fixed-output derivations typically need to access the
network, so give them access to /etc/resolv.conf and so
on. */
if (!derivationType.isSandboxed()) {
if (!derivationType->isSandboxed()) {
// Only use nss functions to resolve hosts and
// services. Dont use it for anything else that may
// be configured for this system. This limits the
@ -2036,7 +2061,7 @@ void LocalDerivationGoal::runChild()
#include "sandbox-defaults.sb"
;
if (!derivationType.isSandboxed())
if (!derivationType->isSandboxed())
sandboxProfile +=
#include "sandbox-network.sb"
;
@ -2497,7 +2522,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
ValidPathInfo newInfo0 {
worker.store,
outputPathName(drv->name, outputName),
*std::move(optCA),
std::move(*optCA),
Hash::dummy,
};
if (*scratchPath != newInfo0.path) {
@ -2559,8 +2584,8 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
delayedException = std::make_exception_ptr(
BuildError("hash mismatch in fixed-output derivation '%s':\n specified: %s\n got: %s",
worker.store.printStorePath(drvPath),
wanted.to_string(SRI, true),
got.to_string(SRI, true)));
wanted.to_string(HashFormat::SRI, true),
got.to_string(HashFormat::SRI, true)));
}
if (!newInfo0.references.empty())
delayedException = std::make_exception_ptr(
@ -2587,7 +2612,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
});
},
}, output->raw());
}, output->raw);
/* FIXME: set proper permissions in restorePath() so
we don't have to do another traversal. */
@ -2941,7 +2966,7 @@ bool LocalDerivationGoal::isReadDesc(int fd)
}
StorePath LocalDerivationGoal::makeFallbackPath(std::string_view outputName)
StorePath LocalDerivationGoal::makeFallbackPath(OutputNameView outputName)
{
return worker.store.makeStorePath(
"rewrite:" + std::string(drvPath.to_string()) + ":name:" + std::string(outputName),

View file

@ -120,14 +120,6 @@ struct LocalDerivationGoal : public DerivationGoal
*/
OutputPathMap scratchOutputs;
/**
* Path registration info from the previous round, if we're
* building multiple times. Since this contains the hash, it
* allows us to compare whether two rounds produced the same
* result.
*/
std::map<Path, ValidPathInfo> prevInfos;
uid_t sandboxUid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); }
gid_t sandboxGid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID(); }
@ -272,8 +264,10 @@ struct LocalDerivationGoal : public DerivationGoal
/**
* Forcibly kill the child process, if any.
*
* Called by destructor, can't be overridden
*/
void killChild() override;
void killChild() override final;
/**
* Kill any processes running under the build user UID or in the
@ -295,7 +289,7 @@ struct LocalDerivationGoal : public DerivationGoal
* @todo Add option to randomize, so we can audit whether our
* rewrites caught everything
*/
StorePath makeFallbackPath(std::string_view outputName);
StorePath makeFallbackPath(OutputNameView outputName);
};
}

View file

@ -114,9 +114,12 @@ public:
void handleChildOutput(int fd, std::string_view data) override;
void handleEOF(int fd) override;
void cleanup() override;
/* Called by destructor, can't be overridden */
void cleanup() override final;
JobCategory jobCategory() override { return JobCategory::Substitution; };
JobCategory jobCategory() const override {
return JobCategory::Substitution;
};
};
}

View file

@ -111,7 +111,10 @@ GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
{
return std::visit(overloaded {
[&](const DerivedPath::Built & bfd) -> GoalPtr {
return makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode);
if (auto bop = std::get_if<DerivedPath::Opaque>(&*bfd.drvPath))
return makeDerivationGoal(bop->path, bfd.outputs, buildMode);
else
throw UnimplementedError("Building dynamic derivations in one shot is not yet implemented.");
},
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
@ -265,7 +268,10 @@ void Worker::run(const Goals & _topGoals)
for (auto & i : _topGoals) {
topGoals.insert(i);
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Built{goal->drvPath, goal->wantedOutputs});
topPaths.push_back(DerivedPath::Built {
.drvPath = makeConstantStorePathRef(goal->drvPath),
.outputs = goal->wantedOutputs,
});
} else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Opaque{goal->storePath});
}
@ -516,10 +522,13 @@ void Worker::markContentsGood(const StorePath & path)
}
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal) {
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal)
{
return subGoal;
}
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal) {
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal)
{
return subGoal;
}

View file

@ -34,7 +34,6 @@ GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal);
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
/**
* A mapping used to remember for each child process to what goal it
* belongs, and file descriptors for receiving log data and output

View file

@ -65,7 +65,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
if (!hasSuffix(hashedMirror, "/")) hashedMirror += '/';
std::optional<HashType> ht = parseHashTypeOpt(getAttr("outputHashAlgo"));
Hash h = newHashAllowEmpty(getAttr("outputHash"), ht);
fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(Base16, false));
fetch(hashedMirror + printHashType(h.type) + "/" + h.to_string(HashFormat::Base16, false));
return;
} catch (Error & e) {
debug(e.what());

View file

@ -0,0 +1,41 @@
#pragma once
/**
* @file
*
* Template implementations (as opposed to mere declarations).
*
* This file is an exmample of the "impl.hh" pattern. See the
* contributing guide.
*/
#include "common-protocol.hh"
#include "length-prefixed-protocol-helper.hh"
namespace nix {
/* protocol-agnostic templates */
#define COMMON_USE_LENGTH_PREFIX_SERIALISER(TEMPLATE, T) \
TEMPLATE T CommonProto::Serialise< T >::read(const Store & store, CommonProto::ReadConn conn) \
{ \
return LengthPrefixedProtoHelper<CommonProto, T >::read(store, conn); \
} \
TEMPLATE void CommonProto::Serialise< T >::write(const Store & store, CommonProto::WriteConn conn, const T & t) \
{ \
LengthPrefixedProtoHelper<CommonProto, T >::write(store, conn, t); \
}
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::vector<T>)
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename T>, std::set<T>)
COMMON_USE_LENGTH_PREFIX_SERIALISER(template<typename... Ts>, std::tuple<Ts...>)
#define COMMA_ ,
COMMON_USE_LENGTH_PREFIX_SERIALISER(
template<typename K COMMA_ typename V>,
std::map<K COMMA_ V>)
#undef COMMA_
/* protocol-specific templates */
}

View file

@ -0,0 +1,98 @@
#include "serialise.hh"
#include "util.hh"
#include "path-with-outputs.hh"
#include "store-api.hh"
#include "build-result.hh"
#include "common-protocol.hh"
#include "common-protocol-impl.hh"
#include "archive.hh"
#include "derivations.hh"
#include <nlohmann/json.hpp>
namespace nix {
/* protocol-agnostic definitions */
std::string CommonProto::Serialise<std::string>::read(const Store & store, CommonProto::ReadConn conn)
{
return readString(conn.from);
}
void CommonProto::Serialise<std::string>::write(const Store & store, CommonProto::WriteConn conn, const std::string & str)
{
conn.to << str;
}
StorePath CommonProto::Serialise<StorePath>::read(const Store & store, CommonProto::ReadConn conn)
{
return store.parseStorePath(readString(conn.from));
}
void CommonProto::Serialise<StorePath>::write(const Store & store, CommonProto::WriteConn conn, const StorePath & storePath)
{
conn.to << store.printStorePath(storePath);
}
ContentAddress CommonProto::Serialise<ContentAddress>::read(const Store & store, CommonProto::ReadConn conn)
{
return ContentAddress::parse(readString(conn.from));
}
void CommonProto::Serialise<ContentAddress>::write(const Store & store, CommonProto::WriteConn conn, const ContentAddress & ca)
{
conn.to << renderContentAddress(ca);
}
Realisation CommonProto::Serialise<Realisation>::read(const Store & store, CommonProto::ReadConn conn)
{
std::string rawInput = readString(conn.from);
return Realisation::fromJSON(
nlohmann::json::parse(rawInput),
"remote-protocol"
);
}
void CommonProto::Serialise<Realisation>::write(const Store & store, CommonProto::WriteConn conn, const Realisation & realisation)
{
conn.to << realisation.toJSON().dump();
}
DrvOutput CommonProto::Serialise<DrvOutput>::read(const Store & store, CommonProto::ReadConn conn)
{
return DrvOutput::parse(readString(conn.from));
}
void CommonProto::Serialise<DrvOutput>::write(const Store & store, CommonProto::WriteConn conn, const DrvOutput & drvOutput)
{
conn.to << drvOutput.to_string();
}
std::optional<StorePath> CommonProto::Serialise<std::optional<StorePath>>::read(const Store & store, CommonProto::ReadConn conn)
{
auto s = readString(conn.from);
return s == "" ? std::optional<StorePath> {} : store.parseStorePath(s);
}
void CommonProto::Serialise<std::optional<StorePath>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<StorePath> & storePathOpt)
{
conn.to << (storePathOpt ? store.printStorePath(*storePathOpt) : "");
}
std::optional<ContentAddress> CommonProto::Serialise<std::optional<ContentAddress>>::read(const Store & store, CommonProto::ReadConn conn)
{
return ContentAddress::parseOpt(readString(conn.from));
}
void CommonProto::Serialise<std::optional<ContentAddress>>::write(const Store & store, CommonProto::WriteConn conn, const std::optional<ContentAddress> & caOpt)
{
conn.to << (caOpt ? renderContentAddress(*caOpt) : "");
}
}

View file

@ -0,0 +1,106 @@
#pragma once
///@file
#include "serialise.hh"
namespace nix {
class Store;
struct Source;
// items being serialized
class StorePath;
struct ContentAddress;
struct DrvOutput;
struct Realisation;
/**
* Shared serializers between the worker protocol, serve protocol, and a
* few others.
*
* This `struct` is basically just a `namespace`; We use a type rather
* than a namespace just so we can use it as a template argument.
*/
struct CommonProto
{
/**
* A unidirectional read connection, to be used by the read half of the
* canonical serializers below.
*/
struct ReadConn {
Source & from;
};
/**
* A unidirectional write connection, to be used by the write half of the
* canonical serializers below.
*/
struct WriteConn {
Sink & to;
};
template<typename T>
struct Serialise;
/**
* Wrapper function around `CommonProto::Serialise<T>::write` that allows us to
* infer the type instead of having to write it down explicitly.
*/
template<typename T>
static void write(const Store & store, WriteConn conn, const T & t)
{
CommonProto::Serialise<T>::write(store, conn, t);
}
};
#define DECLARE_COMMON_SERIALISER(T) \
struct CommonProto::Serialise< T > \
{ \
static T read(const Store & store, CommonProto::ReadConn conn); \
static void write(const Store & store, CommonProto::WriteConn conn, const T & str); \
}
template<>
DECLARE_COMMON_SERIALISER(std::string);
template<>
DECLARE_COMMON_SERIALISER(StorePath);
template<>
DECLARE_COMMON_SERIALISER(ContentAddress);
template<>
DECLARE_COMMON_SERIALISER(DrvOutput);
template<>
DECLARE_COMMON_SERIALISER(Realisation);
template<typename T>
DECLARE_COMMON_SERIALISER(std::vector<T>);
template<typename T>
DECLARE_COMMON_SERIALISER(std::set<T>);
template<typename... Ts>
DECLARE_COMMON_SERIALISER(std::tuple<Ts...>);
#define COMMA_ ,
template<typename K, typename V>
DECLARE_COMMON_SERIALISER(std::map<K COMMA_ V>);
#undef COMMA_
/**
* These use the empty string for the null case, relying on the fact
* that the underlying types never serialize to the empty string.
*
* We do this instead of a generic std::optional<T> instance because
* ordinal tags (0 or 1, here) are a bit of a compatability hazard. For
* the same reason, we don't have a std::variant<T..> instances (ordinal
* tags 0...n).
*
* We could the generic instances and then these as specializations for
* compatability, but that's proven a bit finnicky, and also makes the
* worker protocol harder to implement in other languages where such
* specializations may not be allowed.
*/
template<>
DECLARE_COMMON_SERIALISER(std::optional<StorePath>);
template<>
DECLARE_COMMON_SERIALISER(std::optional<ContentAddress>);
}

View file

@ -60,7 +60,7 @@ std::string ContentAddress::render() const
+ makeFileIngestionPrefix(method);
},
}, method.raw)
+ this->hash.to_string(Base32, true);
+ this->hash.to_string(HashFormat::Base32, true);
}
/**
@ -83,7 +83,7 @@ static std::pair<ContentAddressMethod, HashType> parseContentAddressMethodPrefix
if (!hashTypeRaw)
throw UsageError("content address hash must be in form '<algo>:<hash>', but found: %s", wholeInput);
HashType hashType = parseHashType(*hashTypeRaw);
return std::move(hashType);
return hashType;
};
// Switch on prefix
@ -115,7 +115,7 @@ ContentAddress ContentAddress::parse(std::string_view rawCa)
auto [caMethod, hashType] = parseContentAddressMethodPrefix(rest);
return ContentAddress {
.method = std::move(caMethod).raw,
.method = std::move(caMethod),
.hash = Hash::parseNonSRIUnprefixed(rest, hashType),
};
}

View file

@ -5,6 +5,7 @@
#include "hash.hh"
#include "path.hh"
#include "comparator.hh"
#include "variant-wrapper.hh"
namespace nix {
@ -71,11 +72,7 @@ struct ContentAddressMethod
GENERATE_CMP(ContentAddressMethod, me->raw);
/* The moral equivalent of `using Raw::Raw;` */
ContentAddressMethod(auto &&... arg)
: raw(std::forward<decltype(arg)>(arg)...)
{ }
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressMethod);
/**
* Parse the prefix tag which indicates how the files
@ -252,10 +249,7 @@ struct ContentAddressWithReferences
GENERATE_CMP(ContentAddressWithReferences, me->raw);
/* The moral equivalent of `using Raw::Raw;` */
ContentAddressWithReferences(auto &&... arg)
: raw(std::forward<decltype(arg)>(arg)...)
{ }
MAKE_WRAPPER_CONSTRUCTOR(ContentAddressWithReferences);
/**
* Create a `ContentAddressWithReferences` from a mere

View file

@ -45,9 +45,9 @@ struct TunnelLogger : public Logger
Sync<State> state_;
unsigned int clientVersion;
WorkerProto::Version clientVersion;
TunnelLogger(FdSink & to, unsigned int clientVersion)
TunnelLogger(FdSink & to, WorkerProto::Version clientVersion)
: to(to), clientVersion(clientVersion) { }
void enqueueMsg(const std::string & s)
@ -261,24 +261,18 @@ struct ClientSettings
}
};
static std::vector<DerivedPath> readDerivedPaths(Store & store, unsigned int clientVersion, WorkerProto::ReadConn conn)
{
std::vector<DerivedPath> reqs;
if (GET_PROTOCOL_MINOR(clientVersion) >= 30) {
reqs = WorkerProto::Serialise<std::vector<DerivedPath>>::read(store, conn);
} else {
for (auto & s : readStrings<Strings>(conn.from))
reqs.push_back(parsePathWithOutputs(store, s).toDerivedPath());
}
return reqs;
}
static void performOp(TunnelLogger * logger, ref<Store> store,
TrustedFlag trusted, RecursiveFlag recursive, unsigned int clientVersion,
TrustedFlag trusted, RecursiveFlag recursive, WorkerProto::Version clientVersion,
Source & from, BufferedSink & to, WorkerProto::Op op)
{
WorkerProto::ReadConn rconn { .from = from };
WorkerProto::WriteConn wconn { .to = to };
WorkerProto::ReadConn rconn {
.from = from,
.version = clientVersion,
};
WorkerProto::WriteConn wconn {
.to = to,
.version = clientVersion,
};
switch (op) {
@ -334,7 +328,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork();
auto hash = store->queryPathInfo(path)->narHash;
logger->stopWork();
to << hash.to_string(Base16, false);
to << hash.to_string(HashFormat::Base16, false);
break;
}
@ -428,7 +422,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}();
logger->stopWork();
pathInfo->write(to, *store, GET_PROTOCOL_MINOR(clientVersion));
WorkerProto::Serialise<ValidPathInfo>::write(*store, wconn, *pathInfo);
} else {
HashType hashAlgo;
std::string baseName;
@ -532,7 +526,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}
case WorkerProto::Op::BuildPaths: {
auto drvs = readDerivedPaths(*store, clientVersion, rconn);
auto drvs = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
BuildMode mode = bmNormal;
if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
mode = (BuildMode) readInt(from);
@ -557,7 +551,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}
case WorkerProto::Op::BuildPathsWithResults: {
auto drvs = readDerivedPaths(*store, clientVersion, rconn);
auto drvs = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
BuildMode mode = bmNormal;
mode = (BuildMode) readInt(from);
@ -641,16 +635,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
auto res = store->buildDerivation(drvPath, drv, buildMode);
logger->stopWork();
to << res.status << res.errorMsg;
if (GET_PROTOCOL_MINOR(clientVersion) >= 29) {
to << res.timesBuilt << res.isNonDeterministic << res.startTime << res.stopTime;
}
if (GET_PROTOCOL_MINOR(clientVersion) >= 28) {
DrvOutputs builtOutputs;
for (auto & [output, realisation] : res.builtOutputs)
builtOutputs.insert_or_assign(realisation.id, realisation);
WorkerProto::write(*store, wconn, builtOutputs);
}
WorkerProto::write(*store, wconn, res);
break;
}
@ -834,7 +819,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
if (info) {
if (GET_PROTOCOL_MINOR(clientVersion) >= 17)
to << 1;
info->write(to, *store, GET_PROTOCOL_MINOR(clientVersion), false);
WorkerProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
} else {
assert(GET_PROTOCOL_MINOR(clientVersion) >= 17);
to << 0;
@ -932,7 +917,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}
case WorkerProto::Op::QueryMissing: {
auto targets = readDerivedPaths(*store, clientVersion, rconn);
auto targets = WorkerProto::Serialise<DerivedPaths>::read(*store, rconn);
logger->startWork();
StorePathSet willBuild, willSubstitute, unknown;
uint64_t downloadSize, narSize;
@ -1017,7 +1002,7 @@ void processConnection(
if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch");
to << WORKER_MAGIC_2 << PROTOCOL_VERSION;
to.flush();
unsigned int clientVersion = readInt(from);
WorkerProto::Version clientVersion = readInt(from);
if (clientVersion < 0x10a)
throw Error("the Nix client version is too old");
@ -1052,7 +1037,10 @@ void processConnection(
auto temp = trusted
? store->isTrustedClient()
: std::optional { NotTrusted };
WorkerProto::WriteConn wconn { .to = to };
WorkerProto::WriteConn wconn {
.to = to,
.version = clientVersion,
};
WorkerProto::write(*store, wconn, temp);
}

View file

@ -4,15 +4,15 @@
#include "globals.hh"
#include "util.hh"
#include "split.hh"
#include "worker-protocol.hh"
#include "worker-protocol-impl.hh"
#include "common-protocol.hh"
#include "common-protocol-impl.hh"
#include "fs-accessor.hh"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
namespace nix {
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, OutputNameView outputName) const
{
return std::visit(overloaded {
[](const DerivationOutput::InputAddressed & doi) -> std::optional<StorePath> {
@ -32,11 +32,11 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
[](const DerivationOutput::Impure &) -> std::optional<StorePath> {
return std::nullopt;
},
}, raw());
}, raw);
}
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, OutputNameView outputName) const
{
return store.makeFixedOutputPathFromCA(
outputPathName(drvName, outputName),
@ -60,7 +60,7 @@ bool DerivationType::isCA() const
[](const Impure &) {
return true;
},
}, raw());
}, raw);
}
bool DerivationType::isFixed() const
@ -75,7 +75,7 @@ bool DerivationType::isFixed() const
[](const Impure &) {
return false;
},
}, raw());
}, raw);
}
bool DerivationType::hasKnownOutputPaths() const
@ -90,7 +90,7 @@ bool DerivationType::hasKnownOutputPaths() const
[](const Impure &) {
return false;
},
}, raw());
}, raw);
}
@ -106,7 +106,7 @@ bool DerivationType::isSandboxed() const
[](const Impure &) {
return false;
},
}, raw());
}, raw);
}
@ -122,7 +122,7 @@ bool DerivationType::isPure() const
[](const Impure &) {
return false;
},
}, raw());
}, raw);
}
@ -136,7 +136,7 @@ StorePath writeDerivation(Store & store,
const Derivation & drv, RepairFlag repair, bool readOnly)
{
auto references = drv.inputSrcs;
for (auto & i : drv.inputDrvs)
for (auto & i : drv.inputDrvs.map)
references.insert(i.first);
/* Note that the outputs of a derivation are *not* references
(that can be missing (of course) and should not necessarily be
@ -154,8 +154,9 @@ static void expect(std::istream & str, std::string_view s)
{
char s2[s.size()];
str.read(s2, s.size());
if (std::string(s2, s.size()) != s)
throw FormatError("expected string '%1%'", s);
std::string_view s2View { s2, s.size() };
if (s2View != s)
throw FormatError("expected string '%s', got '%s'", s, s2View);
}
@ -207,23 +208,27 @@ static bool endOfList(std::istream & str)
static StringSet parseStrings(std::istream & str, bool arePaths)
{
StringSet res;
expect(str, "[");
while (!endOfList(str))
res.insert(arePaths ? parsePath(str) : parseString(str));
return res;
}
static DerivationOutput parseDerivationOutput(const Store & store,
std::string_view pathS, std::string_view hashAlgo, std::string_view hashS)
static DerivationOutput parseDerivationOutput(
const Store & store,
std::string_view pathS, std::string_view hashAlgo, std::string_view hashS,
const ExperimentalFeatureSettings & xpSettings)
{
if (hashAlgo != "") {
ContentAddressMethod method = ContentAddressMethod::parsePrefix(hashAlgo);
if (method == TextIngestionMethod {})
experimentalFeatureSettings.require(Xp::DynamicDerivations);
xpSettings.require(Xp::DynamicDerivations);
const auto hashType = parseHashType(hashAlgo);
if (hashS == "impure") {
experimentalFeatureSettings.require(Xp::ImpureDerivations);
assert(pathS == "");
xpSettings.require(Xp::ImpureDerivations);
if (pathS != "")
throw FormatError("impure derivation output should not specify output path");
return DerivationOutput::Impure {
.method = std::move(method),
.hashType = std::move(hashType),
@ -238,8 +243,9 @@ static DerivationOutput parseDerivationOutput(const Store & store,
},
};
} else {
experimentalFeatureSettings.require(Xp::CaDerivations);
assert(pathS == "");
xpSettings.require(Xp::CaDerivations);
if (pathS != "")
throw FormatError("content-addressed derivation output should not specify output path");
return DerivationOutput::CAFloating {
.method = std::move(method),
.hashType = std::move(hashType),
@ -256,29 +262,116 @@ static DerivationOutput parseDerivationOutput(const Store & store,
}
}
static DerivationOutput parseDerivationOutput(const Store & store, std::istringstream & str)
static DerivationOutput parseDerivationOutput(
const Store & store, std::istringstream & str,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings)
{
expect(str, ","); const auto pathS = parseString(str);
expect(str, ","); const auto hashAlgo = parseString(str);
expect(str, ","); const auto hash = parseString(str);
expect(str, ")");
return parseDerivationOutput(store, pathS, hashAlgo, hash);
return parseDerivationOutput(store, pathS, hashAlgo, hash, xpSettings);
}
/**
* All ATerm Derivation format versions currently known.
*
* Unknown versions are rejected at the parsing stage.
*/
enum struct DerivationATermVersion {
/**
* Older unversioned form
*/
Traditional,
/**
* Newer versioned form; only this version so far.
*/
DynamicDerivations,
};
static DerivedPathMap<StringSet>::ChildNode parseDerivedPathMapNode(
const Store & store,
std::istringstream & str,
DerivationATermVersion version)
{
DerivedPathMap<StringSet>::ChildNode node;
auto parseNonDynamic = [&]() {
node.value = parseStrings(str, false);
};
// Older derivation should never use new form, but newer
// derivaiton can use old form.
switch (version) {
case DerivationATermVersion::Traditional:
parseNonDynamic();
break;
case DerivationATermVersion::DynamicDerivations:
switch (str.peek()) {
case '[':
parseNonDynamic();
break;
case '(':
expect(str, "(");
node.value = parseStrings(str, false);
expect(str, ",[");
while (!endOfList(str)) {
expect(str, "(");
auto outputName = parseString(str);
expect(str, ",");
node.childMap.insert_or_assign(outputName, parseDerivedPathMapNode(store, str, version));
expect(str, ")");
}
expect(str, ")");
break;
default:
throw FormatError("invalid inputDrvs entry in derivation");
}
break;
default:
// invalid format, not a parse error but internal error
assert(false);
}
return node;
}
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name)
Derivation parseDerivation(
const Store & store, std::string && s, std::string_view name,
const ExperimentalFeatureSettings & xpSettings)
{
Derivation drv;
drv.name = name;
std::istringstream str(std::move(s));
expect(str, "Derive([");
expect(str, "D");
DerivationATermVersion version;
switch (str.peek()) {
case 'e':
expect(str, "erive(");
version = DerivationATermVersion::Traditional;
break;
case 'r':
expect(str, "rvWithVersion(");
auto versionS = parseString(str);
if (versionS == "xp-dyn-drv") {
// Only verison we have so far
version = DerivationATermVersion::DynamicDerivations;
xpSettings.require(Xp::DynamicDerivations);
} else {
throw FormatError("Unknown derivation ATerm format version '%s'", versionS);
}
expect(str, ",");
break;
}
/* Parse the list of outputs. */
expect(str, "[");
while (!endOfList(str)) {
expect(str, "("); std::string id = parseString(str);
auto output = parseDerivationOutput(store, str);
auto output = parseDerivationOutput(store, str, xpSettings);
drv.outputs.emplace(std::move(id), std::move(output));
}
@ -287,12 +380,12 @@ Derivation parseDerivation(const Store & store, std::string && s, std::string_vi
while (!endOfList(str)) {
expect(str, "(");
Path drvPath = parsePath(str);
expect(str, ",[");
drv.inputDrvs.insert_or_assign(store.parseStorePath(drvPath), parseStrings(str, false));
expect(str, ",");
drv.inputDrvs.map.insert_or_assign(store.parseStorePath(drvPath), parseDerivedPathMapNode(store, str, version));
expect(str, ")");
}
expect(str, ",["); drv.inputSrcs = store.parseStorePathSet(parseStrings(str, true));
expect(str, ","); drv.inputSrcs = store.parseStorePathSet(parseStrings(str, true));
expect(str, ","); drv.platform = parseString(str);
expect(str, ","); drv.builder = parseString(str);
@ -376,14 +469,67 @@ static void printUnquotedStrings(std::string & res, ForwardIterator i, ForwardIt
}
static void unparseDerivedPathMapNode(const Store & store, std::string & s, const DerivedPathMap<StringSet>::ChildNode & node)
{
s += ',';
if (node.childMap.empty()) {
printUnquotedStrings(s, node.value.begin(), node.value.end());
} else {
s += "(";
printUnquotedStrings(s, node.value.begin(), node.value.end());
s += ",[";
bool first = true;
for (auto & [outputName, childNode] : node.childMap) {
if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, outputName);
unparseDerivedPathMapNode(store, s, childNode);
s += ')';
}
s += "])";
}
}
/**
* Does the derivation have a dependency on the output of a dynamic
* derivation?
*
* In other words, does it on the output of derivation that is itself an
* ouput of a derivation? This corresponds to a dependency that is an
* inductive derived path with more than one layer of
* `DerivedPath::Built`.
*/
static bool hasDynamicDrvDep(const Derivation & drv)
{
return
std::find_if(
drv.inputDrvs.map.begin(),
drv.inputDrvs.map.end(),
[](auto & kv) { return !kv.second.childMap.empty(); })
!= drv.inputDrvs.map.end();
}
std::string Derivation::unparse(const Store & store, bool maskOutputs,
std::map<std::string, StringSet> * actualInputs) const
DerivedPathMap<StringSet>::ChildNode::Map * actualInputs) const
{
std::string s;
s.reserve(65536);
s += "Derive([";
/* Use older unversioned form if possible, for wider compat. Use
newer form only if we need it, which we do for
`Xp::DynamicDerivations`. */
if (hasDynamicDrvDep(*this)) {
s += "DrvWithVersion(";
// Only version we have so far
printUnquotedString(s, "xp-dyn-drv");
s += ",";
} else {
s += "Derive(";
}
bool first = true;
s += "[";
for (auto & i : outputs) {
if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, i.first);
@ -396,7 +542,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
[&](const DerivationOutput::CAFixed & dof) {
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
s += ','; printUnquotedString(s, dof.ca.printMethodAlgo());
s += ','; printUnquotedString(s, dof.ca.hash.to_string(Base16, false));
s += ','; printUnquotedString(s, dof.ca.hash.to_string(HashFormat::Base16, false));
},
[&](const DerivationOutput::CAFloating & dof) {
s += ','; printUnquotedString(s, "");
@ -408,30 +554,30 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, "");
},
[&](const DerivationOutputImpure & doi) {
[&](const DerivationOutput::Impure & doi) {
// FIXME
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, doi.method.renderPrefix() + printHashType(doi.hashType));
s += ','; printUnquotedString(s, "impure");
}
}, i.second.raw());
}, i.second.raw);
s += ')';
}
s += "],[";
first = true;
if (actualInputs) {
for (auto & i : *actualInputs) {
for (auto & [drvHashModulo, childMap] : *actualInputs) {
if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, i.first);
s += ','; printUnquotedStrings(s, i.second.begin(), i.second.end());
s += '('; printUnquotedString(s, drvHashModulo);
unparseDerivedPathMapNode(store, s, childMap);
s += ')';
}
} else {
for (auto & i : inputDrvs) {
for (auto & [drvPath, childMap] : inputDrvs.map) {
if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, store.printStorePath(i.first));
s += ','; printUnquotedStrings(s, i.second.begin(), i.second.end());
s += '('; printUnquotedString(s, store.printStorePath(drvPath));
unparseDerivedPathMapNode(store, s, childMap);
s += ')';
}
}
@ -466,7 +612,7 @@ bool isDerivation(std::string_view fileName)
}
std::string outputPathName(std::string_view drvName, std::string_view outputName) {
std::string outputPathName(std::string_view drvName, OutputNameView outputName) {
std::string res { drvName };
if (outputName != "out") {
res += "-";
@ -509,7 +655,7 @@ DerivationType BasicDerivation::type() const
[&](const DerivationOutput::Impure &) {
impureOutputs.insert(i.first);
},
}, i.second.raw());
}, i.second.raw);
}
if (inputAddressedOutputs.empty()
@ -626,10 +772,10 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
if (type.isFixed()) {
std::map<std::string, Hash> outputHashes;
for (const auto & i : drv.outputs) {
auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw());
auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw);
auto hash = hashString(htSHA256, "fixed:out:"
+ dof.ca.printMethodAlgo() + ":"
+ dof.ca.hash.to_string(Base16, false) + ":"
+ dof.ca.hash.to_string(HashFormat::Base16, false) + ":"
+ store.printStorePath(dof.path(store, drv.name, i.first)));
outputHashes.insert_or_assign(i.first, std::move(hash));
}
@ -663,20 +809,18 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
[](const DerivationType::Impure &) -> DrvHash::Kind {
assert(false);
}
}, drv.type().raw());
}, drv.type().raw);
std::map<std::string, StringSet> inputs2;
for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
// Avoid lambda capture restriction with standard / Clang
auto & inputOutputs = inputOutputs0;
DerivedPathMap<StringSet>::ChildNode::Map inputs2;
for (auto & [drvPath, node] : drv.inputDrvs.map) {
const auto & res = pathDerivationModulo(store, drvPath);
if (res.kind == DrvHash::Kind::Deferred)
kind = DrvHash::Kind::Deferred;
for (auto & outputName : inputOutputs) {
for (auto & outputName : node.value) {
const auto h = get(res.hashes, outputName);
if (!h)
throw Error("no hash for output '%s' of derivation '%s'", outputName, drv.name);
inputs2[h->to_string(Base16, false)].insert(outputName);
inputs2[h->to_string(HashFormat::Base16, false)].value.insert(outputName);
}
}
@ -706,7 +850,7 @@ static DerivationOutput readDerivationOutput(Source & in, const Store & store)
const auto hashAlgo = readString(in);
const auto hash = readString(in);
return parseDerivationOutput(store, pathS, hashAlgo, hash);
return parseDerivationOutput(store, pathS, hashAlgo, hash, experimentalFeatureSettings);
}
StringSet BasicDerivation::outputNames() const
@ -720,10 +864,10 @@ StringSet BasicDerivation::outputNames() const
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const
{
DerivationOutputsAndOptPaths outsAndOptPaths;
for (auto output : outputs)
for (auto & [outputName, output] : outputs)
outsAndOptPaths.insert(std::make_pair(
output.first,
std::make_pair(output.second, output.second.path(store, name, output.first))
outputName,
std::make_pair(output, output.path(store, name, outputName))
)
);
return outsAndOptPaths;
@ -751,8 +895,8 @@ Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv,
drv.outputs.emplace(std::move(name), std::move(output));
}
drv.inputSrcs = WorkerProto::Serialise<StorePathSet>::read(store,
WorkerProto::ReadConn { .from = in });
drv.inputSrcs = CommonProto::Serialise<StorePathSet>::read(store,
CommonProto::ReadConn { .from = in });
in >> drv.platform >> drv.builder;
drv.args = readStrings<Strings>(in);
@ -781,7 +925,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
[&](const DerivationOutput::CAFixed & dof) {
out << store.printStorePath(dof.path(store, drv.name, i.first))
<< dof.ca.printMethodAlgo()
<< dof.ca.hash.to_string(Base16, false);
<< dof.ca.hash.to_string(HashFormat::Base16, false);
},
[&](const DerivationOutput::CAFloating & dof) {
out << ""
@ -798,10 +942,10 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
<< (doi.method.renderPrefix() + printHashType(doi.hashType))
<< "impure";
},
}, i.second.raw());
}, i.second.raw);
}
WorkerProto::write(store,
WorkerProto::WriteConn { .to = out },
CommonProto::write(store,
CommonProto::WriteConn { .to = out },
drv.inputSrcs);
out << drv.platform << drv.builder << drv.args;
out << drv.env.size();
@ -810,10 +954,10 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
}
std::string hashPlaceholder(const std::string_view outputName)
std::string hashPlaceholder(const OutputNameView outputName)
{
// FIXME: memoize?
return "/" + hashString(htSHA256, concatStrings("nix-output:", outputName)).to_string(Base32, false);
return "/" + hashString(htSHA256, concatStrings("nix-output:", outputName)).to_string(HashFormat::Base32, false);
}
@ -821,6 +965,8 @@ std::string hashPlaceholder(const std::string_view outputName)
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites)
{
debug("Rewriting the derivation");
for (auto & rewrite : rewrites) {
debug("rewriting %s as %s", rewrite.first, rewrite.second);
}
@ -840,7 +986,7 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
for (auto & [outputName, output] : drv.outputs) {
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw)) {
auto h = get(hashModulo.hashes, outputName);
if (!h)
throw Error("derivation '%s' output '%s' has no hash (derivations.cc/rewriteDerivation)",
@ -859,14 +1005,70 @@ std::optional<BasicDerivation> Derivation::tryResolve(Store & store) const
{
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
for (auto & input : inputDrvs)
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(input.first))
if (outputPath)
inputDrvOutputs.insert_or_assign({input.first, outputName}, *outputPath);
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accum;
accum = [&](auto & inputDrv, auto & node) {
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(inputDrv)) {
if (outputPath) {
inputDrvOutputs.insert_or_assign({inputDrv, outputName}, *outputPath);
if (auto p = get(node.childMap, outputName))
accum(*outputPath, *p);
}
}
};
for (auto & [inputDrv, node] : inputDrvs.map)
accum(inputDrv, node);
return tryResolve(store, inputDrvOutputs);
}
static bool tryResolveInput(
Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
const DownstreamPlaceholder * placeholderOpt,
const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode,
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs)
{
auto getOutput = [&](const std::string & outputName) {
auto * actualPathOpt = get(inputDrvOutputs, { inputDrv, outputName });
if (!actualPathOpt)
warn("output %s of input %s missing, aborting the resolving",
outputName,
store.printStorePath(inputDrv)
);
return actualPathOpt;
};
auto getPlaceholder = [&](const std::string & outputName) {
return placeholderOpt
? DownstreamPlaceholder::unknownDerivation(*placeholderOpt, outputName)
: DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName);
};
for (auto & outputName : inputNode.value) {
auto actualPathOpt = getOutput(outputName);
if (!actualPathOpt) return false;
auto actualPath = *actualPathOpt;
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
inputRewrites.emplace(
getPlaceholder(outputName).render(),
store.printStorePath(actualPath));
}
inputSrcs.insert(std::move(actualPath));
}
for (auto & [outputName, childNode] : inputNode.childMap) {
auto actualPathOpt = getOutput(outputName);
if (!actualPathOpt) return false;
auto actualPath = *actualPathOpt;
auto nextPlaceholder = getPlaceholder(outputName);
if (!tryResolveInput(store, inputSrcs, inputRewrites,
&nextPlaceholder, actualPath, childNode,
inputDrvOutputs))
return false;
}
return true;
}
std::optional<BasicDerivation> Derivation::tryResolve(
Store & store,
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const
@ -876,23 +1078,10 @@ std::optional<BasicDerivation> Derivation::tryResolve(
// Input paths that we'll want to rewrite in the derivation
StringMap inputRewrites;
for (auto & [inputDrv, inputOutputs] : inputDrvs) {
for (auto & outputName : inputOutputs) {
if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
inputRewrites.emplace(
DownstreamPlaceholder::unknownCaOutput(inputDrv, outputName).render(),
store.printStorePath(*actualPath));
}
resolved.inputSrcs.insert(*actualPath);
} else {
warn("output '%s' of input '%s' missing, aborting the resolving",
outputName,
store.printStorePath(inputDrv));
return {};
}
}
}
for (auto & [inputDrv, inputNode] : inputDrvs.map)
if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites,
nullptr, inputDrv, inputNode, inputDrvOutputs))
return std::nullopt;
rewriteDerivation(store, resolved, inputRewrites);
@ -955,7 +1144,7 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
[&](const DerivationOutput::Impure &) {
/* Nothing to check */
},
}, i.second.raw());
}, i.second.raw);
}
}
@ -963,7 +1152,7 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
const Hash impureOutputHash = hashString(htSHA256, "impure");
nlohmann::json DerivationOutput::toJSON(
const Store & store, std::string_view drvName, std::string_view outputName) const
const Store & store, std::string_view drvName, OutputNameView outputName) const
{
nlohmann::json res = nlohmann::json::object();
std::visit(overloaded {
@ -973,7 +1162,7 @@ nlohmann::json DerivationOutput::toJSON(
[&](const DerivationOutput::CAFixed & dof) {
res["path"] = store.printStorePath(dof.path(store, drvName, outputName));
res["hashAlgo"] = dof.ca.printMethodAlgo();
res["hash"] = dof.ca.hash.to_string(Base16, false);
res["hash"] = dof.ca.hash.to_string(HashFormat::Base16, false);
// FIXME print refs?
},
[&](const DerivationOutput::CAFloating & dof) {
@ -984,13 +1173,13 @@ nlohmann::json DerivationOutput::toJSON(
res["hashAlgo"] = doi.method.renderPrefix() + printHashType(doi.hashType);
res["impure"] = true;
},
}, raw());
}, raw);
return res;
}
DerivationOutput DerivationOutput::fromJSON(
const Store & store, std::string_view drvName, std::string_view outputName,
const Store & store, std::string_view drvName, OutputNameView outputName,
const nlohmann::json & _json,
const ExperimentalFeatureSettings & xpSettings)
{
@ -1081,10 +1270,25 @@ nlohmann::json Derivation::toJSON(const Store & store) const
}
{
auto& inputDrvsObj = res["inputDrvs"];
inputDrvsObj = nlohmann::json ::object();
for (auto & input : inputDrvs)
inputDrvsObj[store.printStorePath(input.first)] = input.second;
std::function<nlohmann::json(const DerivedPathMap<StringSet>::ChildNode &)> doInput;
doInput = [&](const auto & inputNode) {
auto value = nlohmann::json::object();
value["outputs"] = inputNode.value;
{
auto next = nlohmann::json::object();
for (auto & [outputId, childNode] : inputNode.childMap)
next[outputId] = doInput(childNode);
value["dynamicOutputs"] = std::move(next);
}
return value;
};
{
auto& inputDrvsObj = res["inputDrvs"];
inputDrvsObj = nlohmann::json::object();
for (auto & [inputDrv, inputNode] : inputDrvs.map) {
inputDrvsObj[store.printStorePath(inputDrv)] = doInput(inputNode);
}
}
}
res["system"] = platform;
@ -1098,7 +1302,8 @@ nlohmann::json Derivation::toJSON(const Store & store) const
Derivation Derivation::fromJSON(
const Store & store,
const nlohmann::json & json)
const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings)
{
using nlohmann::detail::value_t;
@ -1130,12 +1335,21 @@ Derivation Derivation::fromJSON(
}
try {
std::function<DerivedPathMap<StringSet>::ChildNode(const nlohmann::json &)> doInput;
doInput = [&](const auto & json) {
DerivedPathMap<StringSet>::ChildNode node;
node.value = static_cast<const StringSet &>(
ensureType(valueAt(json, "outputs"), value_t::array));
for (auto & [outputId, childNode] : ensureType(valueAt(json, "dynamicOutputs"), value_t::object).items()) {
xpSettings.require(Xp::DynamicDerivations);
node.childMap[outputId] = doInput(childNode);
}
return node;
};
auto & inputDrvsObj = ensureType(valueAt(json, "inputDrvs"), value_t::object);
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items()) {
ensureType(inputOutputs, value_t::array);
res.inputDrvs[store.parseStorePath(inputDrvPath)] =
static_cast<const StringSet &>(inputOutputs);
}
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items())
res.inputDrvs.map[store.parseStorePath(inputDrvPath)] =
doInput(inputOutputs);
} catch (Error & e) {
e.addTrace({}, "while reading key 'inputDrvs'");
throw;

View file

@ -6,9 +6,10 @@
#include "hash.hh"
#include "content-address.hh"
#include "repair-flag.hh"
#include "derived-path.hh"
#include "derived-path-map.hh"
#include "sync.hh"
#include "comparator.hh"
#include "variant-wrapper.hh"
#include <map>
#include <variant>
@ -20,108 +21,110 @@ class Store;
/* Abstract syntax of derivations. */
/**
* The traditional non-fixed-output derivation type.
*/
struct DerivationOutputInputAddressed
{
StorePath path;
GENERATE_CMP(DerivationOutputInputAddressed, me->path);
};
/**
* Fixed-output derivations, whose output paths are content
* addressed according to that fixed output.
*/
struct DerivationOutputCAFixed
{
/**
* Method and hash used for expected hash computation.
*
* References are not allowed by fiat.
*/
ContentAddress ca;
/**
* Return the \ref StorePath "store path" corresponding to this output
*
* @param drvName The name of the derivation this is an output of, without the `.drv`.
* @param outputName The name of this output.
*/
StorePath path(const Store & store, std::string_view drvName, std::string_view outputName) const;
GENERATE_CMP(DerivationOutputCAFixed, me->ca);
};
/**
* Floating-output derivations, whose output paths are content
* addressed, but not fixed, and so are dynamically calculated from
* whatever the output ends up being.
* */
struct DerivationOutputCAFloating
{
/**
* How the file system objects will be serialized for hashing
*/
ContentAddressMethod method;
/**
* How the serialization will be hashed
*/
HashType hashType;
GENERATE_CMP(DerivationOutputCAFloating, me->method, me->hashType);
};
/**
* Input-addressed output which depends on a (CA) derivation whose hash
* isn't known yet.
*/
struct DerivationOutputDeferred {
GENERATE_CMP(DerivationOutputDeferred);
};
/**
* Impure output which is moved to a content-addressed location (like
* CAFloating) but isn't registered as a realization.
*/
struct DerivationOutputImpure
{
/**
* How the file system objects will be serialized for hashing
*/
ContentAddressMethod method;
/**
* How the serialization will be hashed
*/
HashType hashType;
GENERATE_CMP(DerivationOutputImpure, me->method, me->hashType);
};
typedef std::variant<
DerivationOutputInputAddressed,
DerivationOutputCAFixed,
DerivationOutputCAFloating,
DerivationOutputDeferred,
DerivationOutputImpure
> _DerivationOutputRaw;
/**
* A single output of a BasicDerivation (and Derivation).
*/
struct DerivationOutput : _DerivationOutputRaw
struct DerivationOutput
{
using Raw = _DerivationOutputRaw;
using Raw::Raw;
/**
* The traditional non-fixed-output derivation type.
*/
struct InputAddressed
{
StorePath path;
using InputAddressed = DerivationOutputInputAddressed;
using CAFixed = DerivationOutputCAFixed;
using CAFloating = DerivationOutputCAFloating;
using Deferred = DerivationOutputDeferred;
using Impure = DerivationOutputImpure;
GENERATE_CMP(InputAddressed, me->path);
};
/**
* Fixed-output derivations, whose output paths are content
* addressed according to that fixed output.
*/
struct CAFixed
{
/**
* Method and hash used for expected hash computation.
*
* References are not allowed by fiat.
*/
ContentAddress ca;
/**
* Return the \ref StorePath "store path" corresponding to this output
*
* @param drvName The name of the derivation this is an output of, without the `.drv`.
* @param outputName The name of this output.
*/
StorePath path(const Store & store, std::string_view drvName, OutputNameView outputName) const;
GENERATE_CMP(CAFixed, me->ca);
};
/**
* Floating-output derivations, whose output paths are content
* addressed, but not fixed, and so are dynamically calculated from
* whatever the output ends up being.
* */
struct CAFloating
{
/**
* How the file system objects will be serialized for hashing
*/
ContentAddressMethod method;
/**
* How the serialization will be hashed
*/
HashType hashType;
GENERATE_CMP(CAFloating, me->method, me->hashType);
};
/**
* Input-addressed output which depends on a (CA) derivation whose hash
* isn't known yet.
*/
struct Deferred {
GENERATE_CMP(Deferred);
};
/**
* Impure output which is moved to a content-addressed location (like
* CAFloating) but isn't registered as a realization.
*/
struct Impure
{
/**
* How the file system objects will be serialized for hashing
*/
ContentAddressMethod method;
/**
* How the serialization will be hashed
*/
HashType hashType;
GENERATE_CMP(Impure, me->method, me->hashType);
};
typedef std::variant<
InputAddressed,
CAFixed,
CAFloating,
Deferred,
Impure
> Raw;
Raw raw;
GENERATE_CMP(DerivationOutput, me->raw);
MAKE_WRAPPER_CONSTRUCTOR(DerivationOutput);
/**
* Force choosing a variant
*/
DerivationOutput() = delete;
/**
* \note when you use this function you should make sure that you're
@ -129,23 +132,19 @@ struct DerivationOutput : _DerivationOutputRaw
* the safer interface provided by
* BasicDerivation::outputsAndOptPaths
*/
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
std::optional<StorePath> path(const Store & store, std::string_view drvName, OutputNameView outputName) const;
nlohmann::json toJSON(
const Store & store,
std::string_view drvName,
std::string_view outputName) const;
OutputNameView outputName) const;
/**
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivationOutput fromJSON(
const Store & store,
std::string_view drvName,
std::string_view outputName,
OutputNameView outputName,
const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
};
@ -167,61 +166,71 @@ typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePat
*/
typedef std::map<StorePath, StringSet> DerivationInputs;
/**
* Input-addressed derivation types
*/
struct DerivationType_InputAddressed {
struct DerivationType {
/**
* True iff the derivation type can't be determined statically,
* for instance because it (transitively) depends on a content-addressed
* derivation.
*/
bool deferred;
};
/**
* Content-addressed derivation types
*/
struct DerivationType_ContentAddressed {
/**
* Whether the derivation should be built safely inside a sandbox.
* Input-addressed derivation types
*/
bool sandboxed;
struct InputAddressed {
/**
* True iff the derivation type can't be determined statically,
* for instance because it (transitively) depends on a content-addressed
* derivation.
*/
bool deferred;
GENERATE_CMP(InputAddressed, me->deferred);
};
/**
* Whether the derivation's outputs' content-addresses are "fixed"
* or "floating.
*
* - Fixed: content-addresses are written down as part of the
* derivation itself. If the outputs don't end up matching the
* build fails.
*
* - Floating: content-addresses are not written down, we do not
* know them until we perform the build.
* Content-addressed derivation types
*/
bool fixed;
};
struct ContentAddressed {
/**
* Whether the derivation should be built safely inside a sandbox.
*/
bool sandboxed;
/**
* Whether the derivation's outputs' content-addresses are "fixed"
* or "floating".
*
* - Fixed: content-addresses are written down as part of the
* derivation itself. If the outputs don't end up matching the
* build fails.
*
* - Floating: content-addresses are not written down, we do not
* know them until we perform the build.
*/
bool fixed;
/**
* Impure derivation type
*
* This is similar at buil-time to the content addressed, not standboxed, not fixed
* type, but has some restrictions on its usage.
*/
struct DerivationType_Impure {
};
GENERATE_CMP(ContentAddressed, me->sandboxed, me->fixed);
};
typedef std::variant<
DerivationType_InputAddressed,
DerivationType_ContentAddressed,
DerivationType_Impure
> _DerivationTypeRaw;
/**
* Impure derivation type
*
* This is similar at buil-time to the content addressed, not standboxed, not fixed
* type, but has some restrictions on its usage.
*/
struct Impure {
GENERATE_CMP(Impure);
};
struct DerivationType : _DerivationTypeRaw {
using Raw = _DerivationTypeRaw;
using Raw::Raw;
using InputAddressed = DerivationType_InputAddressed;
using ContentAddressed = DerivationType_ContentAddressed;
using Impure = DerivationType_Impure;
typedef std::variant<
InputAddressed,
ContentAddressed,
Impure
> Raw;
Raw raw;
GENERATE_CMP(DerivationType, me->raw);
MAKE_WRAPPER_CONSTRUCTOR(DerivationType);
/**
* Force choosing a variant
*/
DerivationType() = delete;
/**
* Do the outputs of the derivation have paths calculated from their
@ -257,10 +266,6 @@ struct DerivationType : _DerivationTypeRaw {
* closure, or if fixed output.
*/
bool hasKnownOutputPaths() const;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
};
struct BasicDerivation
@ -318,13 +323,13 @@ struct Derivation : BasicDerivation
/**
* inputs that are sub-derivations
*/
DerivationInputs inputDrvs;
DerivedPathMap<std::set<OutputName>> inputDrvs;
/**
* Print a derivation.
*/
std::string unparse(const Store & store, bool maskOutputs,
std::map<std::string, StringSet> * actualInputs = nullptr) const;
DerivedPathMap<StringSet>::ChildNode::Map * actualInputs = nullptr) const;
/**
* Return the underlying basic derivation but with these changes:
@ -363,7 +368,8 @@ struct Derivation : BasicDerivation
nlohmann::json toJSON(const Store & store) const;
static Derivation fromJSON(
const Store & store,
const nlohmann::json & json);
const nlohmann::json & json,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
GENERATE_CMP(Derivation,
static_cast<const BasicDerivation &>(*me),
@ -384,7 +390,11 @@ StorePath writeDerivation(Store & store,
/**
* Read a derivation from a file.
*/
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name);
Derivation parseDerivation(
const Store & store,
std::string && s,
std::string_view name,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* \todo Remove.
@ -400,7 +410,7 @@ bool isDerivation(std::string_view fileName);
* This is usually <drv-name>-<output-name>, but is just <drv-name> when
* the output name is "out".
*/
std::string outputPathName(std::string_view drvName, std::string_view outputName);
std::string outputPathName(std::string_view drvName, OutputNameView outputName);
/**
@ -494,7 +504,7 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
* own outputs without needing to use the hash of a derivation in
* itself, making the hash near-impossible to calculate.
*/
std::string hashPlaceholder(const std::string_view outputName);
std::string hashPlaceholder(const OutputNameView outputName);
extern const Hash impureOutputHash;

View file

@ -0,0 +1,69 @@
#include "derived-path-map.hh"
namespace nix {
template<typename V>
typename DerivedPathMap<V>::ChildNode & DerivedPathMap<V>::ensureSlot(const SingleDerivedPath & k)
{
std::function<ChildNode &(const SingleDerivedPath & )> initIter;
initIter = [&](const auto & k) -> auto & {
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & bo) -> auto & {
// will not overwrite if already there
return map[bo.path];
},
[&](const SingleDerivedPath::Built & bfd) -> auto & {
auto & n = initIter(*bfd.drvPath);
return n.childMap[bfd.output];
},
}, k.raw());
};
return initIter(k);
}
template<typename V>
typename DerivedPathMap<V>::ChildNode * DerivedPathMap<V>::findSlot(const SingleDerivedPath & k)
{
std::function<ChildNode *(const SingleDerivedPath & )> initIter;
initIter = [&](const auto & k) {
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & bo) {
auto it = map.find(bo.path);
return it != map.end()
? &it->second
: nullptr;
},
[&](const SingleDerivedPath::Built & bfd) {
auto * n = initIter(*bfd.drvPath);
if (!n) return (ChildNode *)nullptr;
auto it = n->childMap.find(bfd.output);
return it != n->childMap.end()
? &it->second
: nullptr;
},
}, k.raw());
};
return initIter(k);
}
}
// instantiations
namespace nix {
GENERATE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>::ChildNode,
me->value,
me->childMap);
GENERATE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>,
me->map);
template struct DerivedPathMap<std::set<std::string>>;
};

View file

@ -0,0 +1,95 @@
#pragma once
#include "types.hh"
#include "derived-path.hh"
namespace nix {
/**
* A simple Trie, of sorts. Conceptually a map of `SingleDerivedPath` to
* values.
*
* Concretely, an n-ary tree, as described below. A
* `SingleDerivedPath::Opaque` maps to the value of an immediate child
* of the root node. A `SingleDerivedPath::Built` maps to a deeper child
* node: the `SingleDerivedPath::Built::drvPath` is first mapped to a a
* child node (inductively), and then the
* `SingleDerivedPath::Built::output` is used to look up that child's
* child via its map. In this manner, every `SingleDerivedPath` is
* mapped to a child node.
*
* @param V A type to instantiate for each output. It should probably
* should be an "optional" type so not every interior node has to have a
* value. `* const Something` or `std::optional<Something>` would be
* good choices for "optional" types.
*/
template<typename V>
struct DerivedPathMap {
/**
* A child node (non-root node).
*/
struct ChildNode {
/**
* Value of this child node.
*
* @see DerivedPathMap for what `V` should be.
*/
V value;
/**
* The map type for the root node.
*/
using Map = std::map<OutputName, ChildNode>;
/**
* The map of the root node.
*/
Map childMap;
DECLARE_CMP(ChildNode);
};
/**
* The map type for the root node.
*/
using Map = std::map<StorePath, ChildNode>;
/**
* The map of root node.
*/
Map map;
DECLARE_CMP(DerivedPathMap);
/**
* Find the node for `k`, creating it if needed.
*
* The node is referred to as a "slot" on the assumption that `V` is
* some sort of optional type, so the given key can be set or unset
* by changing this node.
*/
ChildNode & ensureSlot(const SingleDerivedPath & k);
/**
* Like `ensureSlot` but does not create the slot if it doesn't exist.
*
* Read the entire description of `ensureSlot` to understand an
* important caveat here that "have slot" does *not* imply "key is
* set in map". To ensure a key is set one would need to get the
* child node (with `findSlot` or `ensureSlot`) *and* check the
* `ChildNode::value`.
*/
ChildNode * findSlot(const SingleDerivedPath & k);
};
DECLARE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>::,
DerivedPathMap<std::set<std::string>>);
DECLARE_CMP_EXT(
template<>,
DerivedPathMap<std::set<std::string>>::ChildNode::,
DerivedPathMap<std::set<std::string>>::ChildNode);
}

View file

@ -7,52 +7,133 @@
namespace nix {
nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
#define CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, COMPARATOR) \
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
{ \
const MY_TYPE* me = this; \
auto fields1 = std::make_tuple<const CHILD_TYPE &, const FIELD_TYPE &>(*me->drvPath, me->FIELD); \
me = &other; \
auto fields2 = std::make_tuple<const CHILD_TYPE &, const FIELD_TYPE &>(*me->drvPath, me->FIELD); \
return fields1 COMPARATOR fields2; \
}
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, ==) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
#define FIELD_TYPE std::string
CMP(SingleDerivedPath, SingleDerivedPathBuilt, output)
#undef FIELD_TYPE
#define FIELD_TYPE OutputsSpec
CMP(SingleDerivedPath, DerivedPathBuilt, outputs)
#undef FIELD_TYPE
#undef CMP
#undef CMP_ONE
nlohmann::json DerivedPath::Opaque::toJSON(const Store & store) const
{
return store.printStorePath(path);
}
nlohmann::json SingleDerivedPath::Built::toJSON(Store & store) const {
nlohmann::json res;
res["path"] = store->printStorePath(path);
res["drvPath"] = drvPath->toJSON(store);
// Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so lets do it
// FIXME try-resolve on drvPath
const auto outputMap = store.queryPartialDerivationOutputMap(resolveDerivedPath(store, *drvPath));
res["output"] = output;
auto outputPathIter = outputMap.find(output);
if (outputPathIter == outputMap.end())
res["outputPath"] = nullptr;
else if (std::optional p = outputPathIter->second)
res["outputPath"] = store.printStorePath(*p);
else
res["outputPath"] = nullptr;
return res;
}
nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
nlohmann::json DerivedPath::Built::toJSON(Store & store) const {
nlohmann::json res;
res["drvPath"] = store->printStorePath(drvPath);
res["drvPath"] = drvPath->toJSON(store);
// Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so lets do it
const auto outputMap = store->queryPartialDerivationOutputMap(drvPath);
// FIXME try-resolve on drvPath
const auto outputMap = store.queryPartialDerivationOutputMap(resolveDerivedPath(store, *drvPath));
for (const auto & [output, outputPathOpt] : outputMap) {
if (!outputs.contains(output)) continue;
if (outputPathOpt)
res["outputs"][output] = store->printStorePath(*outputPathOpt);
res["outputs"][output] = store.printStorePath(*outputPathOpt);
else
res["outputs"][output] = nullptr;
}
return res;
}
nlohmann::json SingleDerivedPath::toJSON(Store & store) const
{
return std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw());
}
nlohmann::json DerivedPath::toJSON(Store & store) const
{
return std::visit([&](const auto & buildable) {
return buildable.toJSON(store);
}, raw());
}
std::string DerivedPath::Opaque::to_string(const Store & store) const
{
return store.printStorePath(path);
}
std::string SingleDerivedPath::Built::to_string(const Store & store) const
{
return drvPath->to_string(store) + "^" + output;
}
std::string SingleDerivedPath::Built::to_string_legacy(const Store & store) const
{
return drvPath->to_string(store) + "!" + output;
}
std::string DerivedPath::Built::to_string(const Store & store) const
{
return store.printStorePath(drvPath)
return drvPath->to_string(store)
+ '^'
+ outputs.to_string();
}
std::string DerivedPath::Built::to_string_legacy(const Store & store) const
{
return store.printStorePath(drvPath)
+ '!'
return drvPath->to_string_legacy(store)
+ "!"
+ outputs.to_string();
}
std::string SingleDerivedPath::to_string(const Store & store) const
{
return std::visit(
[&](const auto & req) { return req.to_string(store); },
raw());
}
std::string DerivedPath::to_string(const Store & store) const
{
return std::visit(
[&](const auto & req) { return req.to_string(store); },
raw());
}
std::string SingleDerivedPath::to_string_legacy(const Store & store) const
{
return std::visit(overloaded {
[&](const DerivedPath::Built & req) { return req.to_string(store); },
[&](const DerivedPath::Opaque & req) { return req.to_string(store); },
[&](const SingleDerivedPath::Built & req) { return req.to_string_legacy(store); },
[&](const SingleDerivedPath::Opaque & req) { return req.to_string(store); },
}, this->raw());
}
@ -70,30 +151,156 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_
return {store.parseStorePath(s)};
}
DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS)
void drvRequireExperiment(
const SingleDerivedPath & drv,
const ExperimentalFeatureSettings & xpSettings)
{
std::visit(overloaded {
[&](const SingleDerivedPath::Opaque &) {
// plain drv path; no experimental features required.
},
[&](const SingleDerivedPath::Built &) {
xpSettings.require(Xp::DynamicDerivations);
},
}, drv.raw());
}
SingleDerivedPath::Built SingleDerivedPath::Built::parse(
const Store & store, ref<SingleDerivedPath> drv,
OutputNameView output,
const ExperimentalFeatureSettings & xpSettings)
{
drvRequireExperiment(*drv, xpSettings);
return {
.drvPath = store.parseStorePath(drvS),
.drvPath = drv,
.output = std::string { output },
};
}
DerivedPath::Built DerivedPath::Built::parse(
const Store & store, ref<SingleDerivedPath> drv,
OutputNameView outputsS,
const ExperimentalFeatureSettings & xpSettings)
{
drvRequireExperiment(*drv, xpSettings);
return {
.drvPath = drv,
.outputs = OutputsSpec::parse(outputsS),
};
}
static inline DerivedPath parseWith(const Store & store, std::string_view s, std::string_view separator)
static SingleDerivedPath parseWithSingle(
const Store & store, std::string_view s, std::string_view separator,
const ExperimentalFeatureSettings & xpSettings)
{
size_t n = s.find(separator);
size_t n = s.rfind(separator);
return n == s.npos
? (SingleDerivedPath) SingleDerivedPath::Opaque::parse(store, s)
: (SingleDerivedPath) SingleDerivedPath::Built::parse(store,
make_ref<SingleDerivedPath>(parseWithSingle(
store,
s.substr(0, n),
separator,
xpSettings)),
s.substr(n + 1),
xpSettings);
}
SingleDerivedPath SingleDerivedPath::parse(
const Store & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
return parseWithSingle(store, s, "^", xpSettings);
}
SingleDerivedPath SingleDerivedPath::parseLegacy(
const Store & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
return parseWithSingle(store, s, "!", xpSettings);
}
static DerivedPath parseWith(
const Store & store, std::string_view s, std::string_view separator,
const ExperimentalFeatureSettings & xpSettings)
{
size_t n = s.rfind(separator);
return n == s.npos
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
: (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1));
: (DerivedPath) DerivedPath::Built::parse(store,
make_ref<SingleDerivedPath>(parseWithSingle(
store,
s.substr(0, n),
separator,
xpSettings)),
s.substr(n + 1),
xpSettings);
}
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
DerivedPath DerivedPath::parse(
const Store & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
return parseWith(store, s, "^");
return parseWith(store, s, "^", xpSettings);
}
DerivedPath DerivedPath::parseLegacy(const Store & store, std::string_view s)
DerivedPath DerivedPath::parseLegacy(
const Store & store,
std::string_view s,
const ExperimentalFeatureSettings & xpSettings)
{
return parseWith(store, s, "!");
return parseWith(store, s, "!", xpSettings);
}
DerivedPath DerivedPath::fromSingle(const SingleDerivedPath & req)
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) -> DerivedPath {
return o;
},
[&](const SingleDerivedPath::Built & b) -> DerivedPath {
return DerivedPath::Built {
.drvPath = b.drvPath,
.outputs = OutputsSpec::Names { b.output },
};
},
}, req.raw());
}
const StorePath & SingleDerivedPath::Built::getBaseStorePath() const
{
return drvPath->getBaseStorePath();
}
const StorePath & DerivedPath::Built::getBaseStorePath() const
{
return drvPath->getBaseStorePath();
}
template<typename DP>
static inline const StorePath & getBaseStorePath_(const DP & derivedPath)
{
return std::visit(overloaded {
[&](const typename DP::Built & bfd) -> auto & {
return bfd.drvPath->getBaseStorePath();
},
[&](const typename DP::Opaque & bo) -> auto & {
return bo.path;
},
}, derivedPath.raw());
}
const StorePath & SingleDerivedPath::getBaseStorePath() const
{
return getBaseStorePath_(*this);
}
const StorePath & DerivedPath::getBaseStorePath() const
{
return getBaseStorePath_(*this);
}
}

View file

@ -24,28 +24,37 @@ class Store;
struct DerivedPathOpaque {
StorePath path;
nlohmann::json toJSON(ref<Store> store) const;
std::string to_string(const Store & store) const;
static DerivedPathOpaque parse(const Store & store, std::string_view);
nlohmann::json toJSON(const Store & store) const;
GENERATE_CMP(DerivedPathOpaque, me->path);
};
struct SingleDerivedPath;
/**
* A derived path that is built from a derivation
* A single derived path that is built from a derivation
*
* Built derived paths are pair of a derivation and some output names.
* They are evaluated by building the derivation, and then replacing the
* output names with the resulting outputs.
*
* Note that does mean a derived store paths evaluates to multiple
* opaque paths, which is sort of icky as expressions are supposed to
* evaluate to single values. Perhaps this should have just a single
* output name.
* Built derived paths are pair of a derivation and an output name. They are
* evaluated by building the derivation, and then taking the resulting output
* path of the given output name.
*/
struct DerivedPathBuilt {
StorePath drvPath;
OutputsSpec outputs;
struct SingleDerivedPathBuilt {
ref<SingleDerivedPath> drvPath;
OutputName output;
/**
* Get the store path this is ultimately derived from (by realising
* and projecting outputs).
*
* Note that this is *not* a property of the store object being
* referred to, but just of this path --- how we happened to be
* referring to that store object. In other words, this means this
* function breaks "referential transparency". It should therefore
* be used only with great care.
*/
const StorePath & getBaseStorePath() const;
/**
* Uses `^` as the separator
@ -57,11 +66,139 @@ struct DerivedPathBuilt {
std::string to_string_legacy(const Store & store) const;
/**
* The caller splits on the separator, so it works for both variants.
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPathBuilt parse(const Store & store, std::string_view drvPath, std::string_view outputs);
nlohmann::json toJSON(ref<Store> store) const;
static SingleDerivedPathBuilt parse(
const Store & store, ref<SingleDerivedPath> drvPath,
OutputNameView outputs,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
nlohmann::json toJSON(Store & store) const;
GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
DECLARE_CMP(SingleDerivedPathBuilt);
};
using _SingleDerivedPathRaw = std::variant<
DerivedPathOpaque,
SingleDerivedPathBuilt
>;
/**
* A "derived path" is a very simple sort of expression (not a Nix
* language expression! But an expression in a the general sense) that
* evaluates to (concrete) store path. It is either:
*
* - opaque, in which case it is just a concrete store path with
* possibly no known derivation
*
* - built, in which case it is a pair of a derivation path and an
* output name.
*/
struct SingleDerivedPath : _SingleDerivedPathRaw {
using Raw = _SingleDerivedPathRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = SingleDerivedPathBuilt;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
/**
* Get the store path this is ultimately derived from (by realising
* and projecting outputs).
*
* Note that this is *not* a property of the store object being
* referred to, but just of this path --- how we happened to be
* referring to that store object. In other words, this means this
* function breaks "referential transparency". It should therefore
* be used only with great care.
*/
const StorePath & getBaseStorePath() const;
/**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
/**
* Uses `!` as the separator
*/
std::string to_string_legacy(const Store & store) const;
/**
* Uses `^` as the separator
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static SingleDerivedPath parse(
const Store & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Uses `!` as the separator
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static SingleDerivedPath parseLegacy(
const Store & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
nlohmann::json toJSON(Store & store) const;
};
static inline ref<SingleDerivedPath> makeConstantStorePathRef(StorePath drvPath)
{
return make_ref<SingleDerivedPath>(SingleDerivedPath::Opaque { drvPath });
}
/**
* A set of derived paths that are built from a derivation
*
* Built derived paths are pair of a derivation and some output names.
* They are evaluated by building the derivation, and then replacing the
* output names with the resulting outputs.
*
* Note that does mean a derived store paths evaluates to multiple
* opaque paths, which is sort of icky as expressions are supposed to
* evaluate to single values. Perhaps this should have just a single
* output name.
*/
struct DerivedPathBuilt {
ref<SingleDerivedPath> drvPath;
OutputsSpec outputs;
/**
* Get the store path this is ultimately derived from (by realising
* and projecting outputs).
*
* Note that this is *not* a property of the store object being
* referred to, but just of this path --- how we happened to be
* referring to that store object. In other words, this means this
* function breaks "referential transparency". It should therefore
* be used only with great care.
*/
const StorePath & getBaseStorePath() const;
/**
* Uses `^` as the separator
*/
std::string to_string(const Store & store) const;
/**
* Uses `!` as the separator
*/
std::string to_string_legacy(const Store & store) const;
/**
* The caller splits on the separator, so it works for both variants.
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPathBuilt parse(
const Store & store, ref<SingleDerivedPath>,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
nlohmann::json toJSON(Store & store) const;
DECLARE_CMP(DerivedPathBuilt);
};
using _DerivedPathRaw = std::variant<
@ -71,13 +208,13 @@ using _DerivedPathRaw = std::variant<
/**
* A "derived path" is a very simple sort of expression that evaluates
* to (concrete) store path. It is either:
* to one or more (concrete) store paths. It is either:
*
* - opaque, in which case it is just a concrete store path with
* - opaque, in which case it is just a single concrete store path with
* possibly no known derivation
*
* - built, in which case it is a pair of a derivation path and an
* output name.
* - built, in which case it is a pair of a derivation path and some
* output names.
*/
struct DerivedPath : _DerivedPathRaw {
using Raw = _DerivedPathRaw;
@ -90,6 +227,18 @@ struct DerivedPath : _DerivedPathRaw {
return static_cast<const Raw &>(*this);
}
/**
* Get the store path this is ultimately derived from (by realising
* and projecting outputs).
*
* Note that this is *not* a property of the store object being
* referred to, but just of this path --- how we happened to be
* referring to that store object. In other words, this means this
* function breaks "referential transparency". It should therefore
* be used only with great care.
*/
const StorePath & getBaseStorePath() const;
/**
* Uses `^` as the separator
*/
@ -100,14 +249,43 @@ struct DerivedPath : _DerivedPathRaw {
std::string to_string_legacy(const Store & store) const;
/**
* Uses `^` as the separator
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPath parse(const Store & store, std::string_view);
static DerivedPath parse(
const Store & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Uses `!` as the separator
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
static DerivedPath parseLegacy(const Store & store, std::string_view);
static DerivedPath parseLegacy(
const Store & store,
std::string_view,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Convert a `SingleDerivedPath` to a `DerivedPath`.
*/
static DerivedPath fromSingle(const SingleDerivedPath &);
nlohmann::json toJSON(Store & store) const;
};
typedef std::vector<DerivedPath> DerivedPaths;
/**
* Used by various parser functions to require experimental features as
* needed.
*
* Somewhat unfortunate this cannot just be an implementation detail for
* this module.
*
* @param xpSettings Stop-gap to avoid globals during unit tests.
*/
void drvRequireExperiment(
const SingleDerivedPath & drv,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
}

View file

@ -5,13 +5,13 @@ namespace nix {
std::string DownstreamPlaceholder::render() const
{
return "/" + hash.to_string(Base32, false);
return "/" + hash.to_string(HashFormat::Base32, false);
}
DownstreamPlaceholder DownstreamPlaceholder::unknownCaOutput(
const StorePath & drvPath,
std::string_view outputName,
OutputNameView outputName,
const ExperimentalFeatureSettings & xpSettings)
{
xpSettings.require(Xp::CaDerivations);
@ -25,17 +25,34 @@ DownstreamPlaceholder DownstreamPlaceholder::unknownCaOutput(
DownstreamPlaceholder DownstreamPlaceholder::unknownDerivation(
const DownstreamPlaceholder & placeholder,
std::string_view outputName,
OutputNameView outputName,
const ExperimentalFeatureSettings & xpSettings)
{
xpSettings.require(Xp::DynamicDerivations);
auto compressed = compressHash(placeholder.hash, 20);
auto clearText = "nix-computed-output:"
+ compressed.to_string(Base32, false)
+ compressed.to_string(HashFormat::Base32, false)
+ ":" + std::string { outputName };
return DownstreamPlaceholder {
hashString(htSHA256, clearText)
};
}
DownstreamPlaceholder DownstreamPlaceholder::fromSingleDerivedPathBuilt(
const SingleDerivedPath::Built & b,
const ExperimentalFeatureSettings & xpSettings)
{
return std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
return DownstreamPlaceholder::unknownCaOutput(o.path, b.output, xpSettings);
},
[&](const SingleDerivedPath::Built & b2) {
return DownstreamPlaceholder::unknownDerivation(
DownstreamPlaceholder::fromSingleDerivedPathBuilt(b2, xpSettings),
b.output,
xpSettings);
},
}, b.drvPath->raw());
}
}

View file

@ -3,6 +3,7 @@
#include "hash.hh"
#include "path.hh"
#include "derived-path.hh"
namespace nix {
@ -57,7 +58,7 @@ public:
*/
static DownstreamPlaceholder unknownCaOutput(
const StorePath & drvPath,
std::string_view outputName,
OutputNameView outputName,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
@ -71,7 +72,19 @@ public:
*/
static DownstreamPlaceholder unknownDerivation(
const DownstreamPlaceholder & drvPlaceholder,
std::string_view outputName,
OutputNameView outputName,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Convenience constructor that handles both cases (unknown
* content-addressed output and unknown derivation), delegating as
* needed to `unknownCaOutput` and `unknownDerivation`.
*
* Recursively builds up a placeholder from a
* `SingleDerivedPath::Built.drvPath` chain.
*/
static DownstreamPlaceholder fromSingleDerivedPathBuilt(
const SingleDerivedPath::Built & built,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
};

View file

@ -1,8 +1,8 @@
#include "serialise.hh"
#include "store-api.hh"
#include "archive.hh"
#include "worker-protocol.hh"
#include "worker-protocol-impl.hh"
#include "common-protocol.hh"
#include "common-protocol-impl.hh"
#include <algorithm>
@ -41,13 +41,13 @@ void Store::exportPath(const StorePath & path, Sink & sink)
Hash hash = hashSink.currentHash().first;
if (hash != info->narHash && info->narHash != Hash(info->narHash.type))
throw Error("hash of path '%s' has changed from '%s' to '%s'!",
printStorePath(path), info->narHash.to_string(Base32, true), hash.to_string(Base32, true));
printStorePath(path), info->narHash.to_string(HashFormat::Base32, true), hash.to_string(HashFormat::Base32, true));
teeSink
<< exportMagic
<< printStorePath(path);
WorkerProto::write(*this,
WorkerProto::WriteConn { .to = teeSink },
CommonProto::write(*this,
CommonProto::WriteConn { .to = teeSink },
info->references);
teeSink
<< (info->deriver ? printStorePath(*info->deriver) : "")
@ -76,8 +76,8 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
//Activity act(*logger, lvlInfo, "importing path '%s'", info.path);
auto references = WorkerProto::Serialise<StorePathSet>::read(*this,
WorkerProto::ReadConn { .from = source });
auto references = CommonProto::Serialise<StorePathSet>::read(*this,
CommonProto::ReadConn { .from = source });
auto deriver = readString(source);
auto narHash = hashString(htSHA256, saved.s);

View file

@ -863,6 +863,8 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
}
chunk = std::move(state->data);
/* Reset state->data after the move, since we check data.empty() */
state->data = "";
state->request.notify_one();
}

View file

@ -43,7 +43,7 @@ static void makeSymlink(const Path & link, const Path & target)
void LocalStore::addIndirectRoot(const Path & path)
{
std::string hash = hashString(htSHA1, path).to_string(Base32, false);
std::string hash = hashString(htSHA1, path).to_string(HashFormat::Base32, false);
Path realRoot = canonPath(fmt("%1%/%2%/auto/%3%", stateDir, gcRootsDir, hash));
makeSymlink(realRoot, path);
}
@ -777,7 +777,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
}
};
/* Synchronisation point for testing, see tests/gc-concurrent.sh. */
/* Synchronisation point for testing, see tests/functional/gc-concurrent.sh. */
if (auto p = getEnv("_NIX_TEST_GC_SYNC"))
readFile(*p);

View file

@ -24,6 +24,9 @@
#include "config-impl.hh"
#ifdef __APPLE__
#include <sys/sysctl.h>
#endif
namespace nix {
@ -154,6 +157,29 @@ unsigned int Settings::getDefaultCores()
return concurrency;
}
#if __APPLE__
static bool hasVirt() {
int hasVMM;
int hvSupport;
size_t size;
size = sizeof(hasVMM);
if (sysctlbyname("kern.hv_vmm_present", &hasVMM, &size, NULL, 0) == 0) {
if (hasVMM)
return false;
}
// whether the kernel and hardware supports virt
size = sizeof(hvSupport);
if (sysctlbyname("kern.hv_support", &hvSupport, &size, NULL, 0) == 0) {
return hvSupport == 1;
} else {
return false;
}
}
#endif
StringSet Settings::getDefaultSystemFeatures()
{
/* For backwards compatibility, accept some "features" that are
@ -170,6 +196,11 @@ StringSet Settings::getDefaultSystemFeatures()
features.insert("kvm");
#endif
#if __APPLE__
if (hasVirt())
features.insert("apple-virt");
#endif
return features;
}

Some files were not shown because too many files have changed in this diff Show more