mirror of
https://github.com/NixOS/nix
synced 2025-07-06 21:41:48 +02:00
WIP: broken merge but need a git checkpoint
This commit is contained in:
commit
eb460a9529
241 changed files with 7587 additions and 2694 deletions
|
@ -86,6 +86,11 @@ ref<Store> CopyCommand::getDstStore()
|
|||
|
||||
EvalCommand::EvalCommand()
|
||||
{
|
||||
addFlag({
|
||||
.longName = "debugger",
|
||||
.description = "start an interactive environment if evaluation fails",
|
||||
.handler = {&startReplOnEvalErrors, true},
|
||||
});
|
||||
}
|
||||
|
||||
EvalCommand::~EvalCommand()
|
||||
|
@ -103,7 +108,7 @@ ref<Store> EvalCommand::getEvalStore()
|
|||
|
||||
ref<EvalState> EvalCommand::getEvalState()
|
||||
{
|
||||
if (!evalState)
|
||||
if (!evalState) {
|
||||
evalState =
|
||||
#if HAVE_BOEHMGC
|
||||
std::allocate_shared<EvalState>(traceable_allocator<EvalState>(),
|
||||
|
@ -113,6 +118,11 @@ ref<EvalState> EvalCommand::getEvalState()
|
|||
searchPath, getEvalStore(), getStore())
|
||||
#endif
|
||||
;
|
||||
|
||||
if (startReplOnEvalErrors) {
|
||||
evalState->debugRepl = &runRepl;
|
||||
};
|
||||
}
|
||||
return ref<EvalState>(evalState);
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,9 @@ struct CopyCommand : virtual StoreCommand
|
|||
|
||||
struct EvalCommand : virtual StoreCommand, MixEvalArgs
|
||||
{
|
||||
bool startReplOnEvalErrors = false;
|
||||
bool ignoreExceptionsDuringTry = false;
|
||||
|
||||
EvalCommand();
|
||||
|
||||
~EvalCommand();
|
||||
|
@ -75,10 +78,16 @@ struct MixFlakeOptions : virtual Args, EvalCommand
|
|||
{
|
||||
flake::LockFlags lockFlags;
|
||||
|
||||
std::optional<std::string> needsFlakeInputCompletion = {};
|
||||
|
||||
MixFlakeOptions();
|
||||
|
||||
virtual std::optional<FlakeRef> getFlakeRefForCompletion()
|
||||
virtual std::vector<std::string> getFlakesForCompletion()
|
||||
{ return {}; }
|
||||
|
||||
void completeFlakeInput(std::string_view prefix);
|
||||
|
||||
void completionHook() override;
|
||||
};
|
||||
|
||||
struct SourceExprCommand : virtual Args, MixFlakeOptions
|
||||
|
@ -114,12 +123,13 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
|
|||
InstallablesCommand();
|
||||
|
||||
void prepare() override;
|
||||
Installables load();
|
||||
|
||||
virtual bool useDefaultInstallables() { return true; }
|
||||
|
||||
std::optional<FlakeRef> getFlakeRefForCompletion() override;
|
||||
std::vector<std::string> getFlakesForCompletion() override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
std::vector<std::string> _installables;
|
||||
};
|
||||
|
@ -133,9 +143,9 @@ struct InstallableCommand : virtual Args, SourceExprCommand
|
|||
|
||||
void prepare() override;
|
||||
|
||||
std::optional<FlakeRef> getFlakeRefForCompletion() override
|
||||
std::vector<std::string> getFlakesForCompletion() override
|
||||
{
|
||||
return parseFlakeRefWithFragment(_installable, absPath(".")).first;
|
||||
return {_installable};
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -270,4 +280,8 @@ void printClosureDiff(
|
|||
const StorePath & afterPath,
|
||||
std::string_view indent);
|
||||
|
||||
|
||||
void runRepl(
|
||||
ref<EvalState> evalState,
|
||||
const ValMap & extraEnv);
|
||||
}
|
||||
|
|
|
@ -23,17 +23,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
void completeFlakeInputPath(
|
||||
ref<EvalState> evalState,
|
||||
const FlakeRef & flakeRef,
|
||||
std::string_view prefix)
|
||||
{
|
||||
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";
|
||||
|
@ -86,8 +75,7 @@ MixFlakeOptions::MixFlakeOptions()
|
|||
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
|
||||
}},
|
||||
.completer = {[&](size_t, std::string_view prefix) {
|
||||
if (auto flakeRef = getFlakeRefForCompletion())
|
||||
completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
|
||||
needsFlakeInputCompletion = {std::string(prefix)};
|
||||
}}
|
||||
});
|
||||
|
||||
|
@ -103,12 +91,10 @@ MixFlakeOptions::MixFlakeOptions()
|
|||
parseFlakeRef(flakeRef, absPath("."), true));
|
||||
}},
|
||||
.completer = {[&](size_t n, std::string_view prefix) {
|
||||
if (n == 0) {
|
||||
if (auto flakeRef = getFlakeRefForCompletion())
|
||||
completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
|
||||
} else if (n == 1) {
|
||||
if (n == 0)
|
||||
needsFlakeInputCompletion = {std::string(prefix)};
|
||||
else if (n == 1)
|
||||
completeFlakeRef(getEvalState()->store, prefix);
|
||||
}
|
||||
}}
|
||||
});
|
||||
|
||||
|
@ -139,6 +125,24 @@ MixFlakeOptions::MixFlakeOptions()
|
|||
});
|
||||
}
|
||||
|
||||
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(bool supportReadOnlyMode)
|
||||
{
|
||||
addFlag({
|
||||
|
@ -146,7 +150,8 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
|
|||
.shortName = 'f',
|
||||
.description =
|
||||
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
|
||||
"If *file* is the character -, then a Nix expression will be read from standard input.",
|
||||
"If *file* is the character -, then a Nix expression will be read from standard input. "
|
||||
"Implies `--impure`.",
|
||||
.category = installablesCategory,
|
||||
.labels = {"file"},
|
||||
.handler = {&file},
|
||||
|
@ -440,10 +445,8 @@ DerivedPaths InstallableValue::toDerivedPaths()
|
|||
|
||||
// Group by derivation, helps with .all in particular
|
||||
for (auto & drv : toDerivations()) {
|
||||
auto outputName = drv.outputName;
|
||||
if (outputName == "")
|
||||
throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
|
||||
drvsToOutputs[drv.drvPath].insert(outputName);
|
||||
for (auto & outputName : drv.outputsToInstall)
|
||||
drvsToOutputs[drv.drvPath].insert(outputName);
|
||||
drvsToCopy.insert(drv.drvPath);
|
||||
}
|
||||
|
||||
|
@ -466,9 +469,19 @@ struct InstallableAttrPath : InstallableValue
|
|||
SourceExprCommand & cmd;
|
||||
RootValue v;
|
||||
std::string attrPath;
|
||||
OutputsSpec outputsSpec;
|
||||
|
||||
InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
|
||||
: InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
|
||||
InstallableAttrPath(
|
||||
ref<EvalState> state,
|
||||
SourceExprCommand & cmd,
|
||||
Value * v,
|
||||
const std::string & attrPath,
|
||||
OutputsSpec outputsSpec)
|
||||
: InstallableValue(state)
|
||||
, cmd(cmd)
|
||||
, v(allocRootValue(v))
|
||||
, attrPath(attrPath)
|
||||
, outputsSpec(std::move(outputsSpec))
|
||||
{ }
|
||||
|
||||
std::string what() const override { return attrPath; }
|
||||
|
@ -497,7 +510,19 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
|
|||
auto drvPath = drvInfo.queryDrvPath();
|
||||
if (!drvPath)
|
||||
throw Error("'%s' is not a derivation", what());
|
||||
res.push_back({ *drvPath, drvInfo.queryOutputName() });
|
||||
|
||||
std::set<std::string> outputsToInstall;
|
||||
|
||||
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
outputsToInstall = *outputNames;
|
||||
else
|
||||
for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
|
||||
outputsToInstall.insert(output.first);
|
||||
|
||||
res.push_back(DerivationInfo {
|
||||
.drvPath = *drvPath,
|
||||
.outputsToInstall = std::move(outputsToInstall)
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -574,6 +599,7 @@ InstallableFlake::InstallableFlake(
|
|||
ref<EvalState> state,
|
||||
FlakeRef && flakeRef,
|
||||
std::string_view fragment,
|
||||
OutputsSpec outputsSpec,
|
||||
Strings attrPaths,
|
||||
Strings prefixes,
|
||||
const flake::LockFlags & lockFlags)
|
||||
|
@ -581,6 +607,7 @@ InstallableFlake::InstallableFlake(
|
|||
flakeRef(flakeRef),
|
||||
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
|
||||
prefixes(fragment == "" ? Strings{} : prefixes),
|
||||
outputsSpec(std::move(outputsSpec)),
|
||||
lockFlags(lockFlags)
|
||||
{
|
||||
if (cmd && cmd->getAutoArgs(*state)->size())
|
||||
|
@ -589,6 +616,8 @@ InstallableFlake::InstallableFlake(
|
|||
|
||||
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
|
||||
{
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
|
||||
|
||||
auto attr = getCursor(*state);
|
||||
|
||||
auto attrPath = attr->getAttrPathStr();
|
||||
|
@ -598,9 +627,41 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
|
|||
|
||||
auto drvPath = attr->forceDerivation();
|
||||
|
||||
std::set<std::string> outputsToInstall;
|
||||
std::optional<NixInt> priority;
|
||||
|
||||
if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
|
||||
if (aOutputSpecified->getBool()) {
|
||||
if (auto aOutputName = attr->maybeGetAttr("outputName"))
|
||||
outputsToInstall = { aOutputName->getString() };
|
||||
}
|
||||
}
|
||||
|
||||
else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
|
||||
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
|
||||
for (auto & s : aOutputsToInstall->getListOfStrings())
|
||||
outputsToInstall.insert(s);
|
||||
if (auto aPriority = aMeta->maybeGetAttr("priority"))
|
||||
priority = aPriority->getInt();
|
||||
}
|
||||
|
||||
if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
|
||||
outputsToInstall.clear();
|
||||
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
|
||||
for (auto & s : aOutputs->getListOfStrings())
|
||||
outputsToInstall.insert(s);
|
||||
}
|
||||
|
||||
if (outputsToInstall.empty())
|
||||
outputsToInstall.insert("out");
|
||||
|
||||
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
outputsToInstall = *outputNames;
|
||||
|
||||
auto drvInfo = DerivationInfo {
|
||||
std::move(drvPath),
|
||||
attr->getAttr(state->sOutputName)->getString()
|
||||
.drvPath = std::move(drvPath),
|
||||
.outputsToInstall = std::move(outputsToInstall),
|
||||
.priority = priority,
|
||||
};
|
||||
|
||||
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
|
||||
|
@ -723,8 +784,14 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
|||
state->eval(e, *vFile);
|
||||
}
|
||||
|
||||
for (auto & s : ss)
|
||||
result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s));
|
||||
for (auto & s : ss) {
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec(s);
|
||||
result.push_back(
|
||||
std::make_shared<InstallableAttrPath>(
|
||||
state, *this, vFile,
|
||||
prefix == "." ? "" : prefix,
|
||||
outputsSpec));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -743,12 +810,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
|||
}
|
||||
|
||||
try {
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
|
||||
auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath("."));
|
||||
result.push_back(std::make_shared<InstallableFlake>(
|
||||
this,
|
||||
getEvalState(),
|
||||
std::move(flakeRef),
|
||||
fragment,
|
||||
outputsSpec,
|
||||
getDefaultFlakeAttrPaths(),
|
||||
getDefaultFlakeAttrPathPrefixes(),
|
||||
lockFlags));
|
||||
|
@ -822,12 +890,13 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
|
|||
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
auto drvOutputs = drv.outputsAndOptPaths(*store);
|
||||
for (auto & output : bfd.outputs) {
|
||||
if (!outputHashes.count(output))
|
||||
auto outputHash = get(outputHashes, output);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
store->printStorePath(bfd.drvPath), output);
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
DrvOutput outputId { outputHashes.at(output), output };
|
||||
DrvOutput outputId { *outputHash, output };
|
||||
auto realisation = store->queryRealisation(outputId);
|
||||
if (!realisation)
|
||||
throw Error(
|
||||
|
@ -838,10 +907,11 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
|
|||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
assert(drvOutputs.count(output));
|
||||
assert(drvOutputs.at(output).second);
|
||||
auto drvOutput = get(drvOutputs, output);
|
||||
assert(drvOutput);
|
||||
assert(drvOutput->second);
|
||||
outputs.insert_or_assign(
|
||||
output, *drvOutputs.at(output).second);
|
||||
output, *drvOutput->second);
|
||||
}
|
||||
}
|
||||
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
|
||||
|
@ -856,6 +926,9 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::bui
|
|||
break;
|
||||
|
||||
case Realise::Outputs: {
|
||||
if (settings.printMissing)
|
||||
printMissing(store, pathsToBuild, lvlInfo);
|
||||
|
||||
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
||||
if (!buildResult.success())
|
||||
buildResult.rethrow();
|
||||
|
@ -969,21 +1042,26 @@ InstallablesCommand::InstallablesCommand()
|
|||
|
||||
void InstallablesCommand::prepare()
|
||||
{
|
||||
installables = load();
|
||||
}
|
||||
|
||||
Installables InstallablesCommand::load() {
|
||||
Installables installables;
|
||||
if (_installables.empty() && useDefaultInstallables())
|
||||
// FIXME: commands like "nix profile install" should not have a
|
||||
// default, probably.
|
||||
_installables.push_back(".");
|
||||
installables = parseInstallables(getStore(), _installables);
|
||||
return parseInstallables(getStore(), _installables);
|
||||
}
|
||||
|
||||
std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
|
||||
std::vector<std::string> InstallablesCommand::getFlakesForCompletion()
|
||||
{
|
||||
if (_installables.empty()) {
|
||||
if (useDefaultInstallables())
|
||||
return parseFlakeRefWithFragment(".", absPath(".")).first;
|
||||
return {"."};
|
||||
return {};
|
||||
}
|
||||
return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
|
||||
return _installables;
|
||||
}
|
||||
|
||||
InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
|
||||
|
|
|
@ -132,6 +132,8 @@ struct Installable
|
|||
const std::vector<std::shared_ptr<Installable>> & installables);
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<Installable>> Installables;
|
||||
|
||||
struct InstallableValue : Installable
|
||||
{
|
||||
ref<EvalState> state;
|
||||
|
@ -141,7 +143,8 @@ struct InstallableValue : Installable
|
|||
struct DerivationInfo
|
||||
{
|
||||
StorePath drvPath;
|
||||
std::string outputName;
|
||||
std::set<std::string> outputsToInstall;
|
||||
std::optional<NixInt> priority;
|
||||
};
|
||||
|
||||
virtual std::vector<DerivationInfo> toDerivations() = 0;
|
||||
|
@ -156,6 +159,7 @@ struct InstallableFlake : InstallableValue
|
|||
FlakeRef flakeRef;
|
||||
Strings attrPaths;
|
||||
Strings prefixes;
|
||||
OutputsSpec outputsSpec;
|
||||
const flake::LockFlags & lockFlags;
|
||||
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
|
||||
|
||||
|
@ -164,6 +168,7 @@ struct InstallableFlake : InstallableValue
|
|||
ref<EvalState> state,
|
||||
FlakeRef && flakeRef,
|
||||
std::string_view fragment,
|
||||
OutputsSpec outputsSpec,
|
||||
Strings attrPaths,
|
||||
Strings prefixes,
|
||||
const flake::LockFlags & lockFlags);
|
||||
|
|
|
@ -6,9 +6,9 @@ libcmd_DIR := $(d)
|
|||
|
||||
libcmd_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers
|
||||
libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix
|
||||
|
||||
libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread
|
||||
libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread
|
||||
|
||||
libcmd_LIBS = libstore libutil libexpr libmain libfetchers
|
||||
|
||||
|
|
|
@ -9,14 +9,16 @@ namespace nix {
|
|||
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown)
|
||||
{
|
||||
int windowWidth = getWindowSize().second;
|
||||
|
||||
struct lowdown_opts opts {
|
||||
.type = LOWDOWN_TERM,
|
||||
.maxdepth = 20,
|
||||
.cols = std::max(getWindowSize().second, (unsigned short) 80),
|
||||
.cols = (size_t) std::max(windowWidth - 5, 60),
|
||||
.hmargin = 0,
|
||||
.vmargin = 0,
|
||||
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
|
||||
.oflags = 0,
|
||||
.oflags = LOWDOWN_TERM_NOLINK,
|
||||
};
|
||||
|
||||
auto doc = lowdown_doc_new(&opts);
|
||||
|
|
|
@ -22,6 +22,7 @@ extern "C" {
|
|||
#include "ansicolor.hh"
|
||||
#include "shared.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-cache.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "attr-path.hh"
|
||||
#include "store-api.hh"
|
||||
|
@ -34,6 +35,7 @@ extern "C" {
|
|||
#include "finally.hh"
|
||||
#include "markdown.hh"
|
||||
#include "local-fs-store.hh"
|
||||
#include "progress-bar.hh"
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#define GC_INCLUDE_NEW
|
||||
|
@ -48,38 +50,46 @@ struct NixRepl
|
|||
#endif
|
||||
{
|
||||
std::string curDir;
|
||||
std::unique_ptr<EvalState> state;
|
||||
ref<EvalState> state;
|
||||
Bindings * autoArgs;
|
||||
|
||||
size_t debugTraceIndex;
|
||||
|
||||
Strings loadedFiles;
|
||||
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
|
||||
std::function<AnnotatedValues()> getValues;
|
||||
|
||||
const static int envSize = 32768;
|
||||
StaticEnv staticEnv;
|
||||
std::shared_ptr<StaticEnv> staticEnv;
|
||||
Env * env;
|
||||
int displ;
|
||||
StringSet varNames;
|
||||
|
||||
const Path historyFile;
|
||||
|
||||
NixRepl(const Strings & searchPath, nix::ref<Store> store);
|
||||
NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
|
||||
std::function<AnnotatedValues()> getValues);
|
||||
~NixRepl();
|
||||
void mainLoop(const std::vector<std::string> & files);
|
||||
void mainLoop();
|
||||
StringSet completePrefix(const std::string & prefix);
|
||||
bool getLine(std::string & input, const std::string &prompt);
|
||||
bool getLine(std::string & input, const std::string & prompt);
|
||||
StorePath getDerivationPath(Value & v);
|
||||
bool processLine(std::string line);
|
||||
|
||||
void loadFile(const Path & path);
|
||||
void loadFlake(const std::string & flakeRef);
|
||||
void initEnv();
|
||||
void loadFiles();
|
||||
void reloadFiles();
|
||||
void addAttrsToScope(Value & attrs);
|
||||
void addVarToScope(const Symbol name, Value & v);
|
||||
Expr * parseString(std::string s);
|
||||
void evalString(std::string s, Value & v);
|
||||
void loadDebugTraceEnv(DebugTrace & dt);
|
||||
|
||||
typedef std::set<Value *> ValuesSeen;
|
||||
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
|
||||
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
|
||||
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
|
||||
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
|
||||
};
|
||||
|
||||
|
||||
|
@ -92,9 +102,12 @@ std::string removeWhitespace(std::string s)
|
|||
}
|
||||
|
||||
|
||||
NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store)
|
||||
: state(std::make_unique<EvalState>(searchPath, store))
|
||||
, staticEnv(false, &state->staticBaseEnv)
|
||||
NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
|
||||
std::function<NixRepl::AnnotatedValues()> getValues)
|
||||
: state(state)
|
||||
, debugTraceIndex(0)
|
||||
, getValues(getValues)
|
||||
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
|
||||
, historyFile(getDataDir() + "/nix/repl-history")
|
||||
{
|
||||
curDir = absPath(".");
|
||||
|
@ -106,23 +119,20 @@ NixRepl::~NixRepl()
|
|||
write_history(historyFile.c_str());
|
||||
}
|
||||
|
||||
std::string runNix(Path program, const Strings & args,
|
||||
void runNix(Path program, const Strings & args,
|
||||
const std::optional<std::string> & input = {})
|
||||
{
|
||||
auto subprocessEnv = getEnv();
|
||||
subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
|
||||
|
||||
auto res = runProgram(RunOptions {
|
||||
runProgram2(RunOptions {
|
||||
.program = settings.nixBinDir+ "/" + program,
|
||||
.args = args,
|
||||
.environment = subprocessEnv,
|
||||
.input = input,
|
||||
});
|
||||
|
||||
if (!statusOk(res.first))
|
||||
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
|
||||
|
||||
return res.second;
|
||||
return;
|
||||
}
|
||||
|
||||
static NixRepl * curRepl; // ugly
|
||||
|
@ -198,16 +208,37 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
void NixRepl::mainLoop(const std::vector<std::string> & files)
|
||||
static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
|
||||
{
|
||||
if (dt.isError)
|
||||
out << ANSI_RED "error: " << ANSI_NORMAL;
|
||||
out << dt.hint.str() << "\n";
|
||||
|
||||
// prefer direct pos, but if noPos then try the expr.
|
||||
auto pos = *dt.pos
|
||||
? *dt.pos
|
||||
: positions[dt.expr.getPos() ? dt.expr.getPos() : noPos];
|
||||
|
||||
if (pos) {
|
||||
printAtPos(pos, out);
|
||||
|
||||
auto loc = getCodeLines(pos);
|
||||
if (loc.has_value()) {
|
||||
out << "\n";
|
||||
printCodeLines(out, "", pos, *loc);
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void NixRepl::mainLoop()
|
||||
{
|
||||
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
||||
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
|
||||
|
||||
for (auto & i : files)
|
||||
loadedFiles.push_back(i);
|
||||
|
||||
reloadFiles();
|
||||
if (!loadedFiles.empty()) notice("");
|
||||
loadFiles();
|
||||
|
||||
// Allow nix-repl specific settings in .inputrc
|
||||
rl_readline_name = "nix-repl";
|
||||
|
@ -222,14 +253,21 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
|
|||
rl_set_list_possib_func(listPossibleCallback);
|
||||
#endif
|
||||
|
||||
/* Stop the progress bar because it interferes with the display of
|
||||
the repl. */
|
||||
stopProgressBar();
|
||||
|
||||
std::string input;
|
||||
|
||||
while (true) {
|
||||
// When continuing input from previous lines, don't print a prompt, just align to the same
|
||||
// number of chars as the prompt.
|
||||
if (!getLine(input, input.empty() ? "nix-repl> " : " "))
|
||||
if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
|
||||
// ctrl-D should exit the debugger.
|
||||
state->debugStop = false;
|
||||
state->debugQuit = true;
|
||||
break;
|
||||
|
||||
}
|
||||
try {
|
||||
if (!removeWhitespace(input).empty() && !processLine(input)) return;
|
||||
} catch (ParseError & e) {
|
||||
|
@ -240,6 +278,14 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
|
|||
} else {
|
||||
printMsg(lvlError, e.msg());
|
||||
}
|
||||
} catch (EvalError & e) {
|
||||
// in debugger mode, an EvalError should trigger another repl session.
|
||||
// when that session returns the exception will land here. No need to show it again;
|
||||
// show the error for this repl session instead.
|
||||
if (state->debugRepl && !state->debugTraces.empty())
|
||||
showDebugTrace(std::cout, state->positions, state->debugTraces.front());
|
||||
else
|
||||
printMsg(lvlError, e.msg());
|
||||
} catch (Error & e) {
|
||||
printMsg(lvlError, e.msg());
|
||||
} catch (Interrupted & e) {
|
||||
|
@ -394,6 +440,19 @@ StorePath NixRepl::getDerivationPath(Value & v) {
|
|||
return *drvPath;
|
||||
}
|
||||
|
||||
void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
|
||||
{
|
||||
initEnv();
|
||||
|
||||
auto se = state->getStaticEnv(dt.expr);
|
||||
if (se) {
|
||||
auto vm = mapStaticEnvBindings(state->symbols, *se.get(), dt.env);
|
||||
|
||||
// add staticenv vars.
|
||||
for (auto & [name, value] : *(vm.get()))
|
||||
addVarToScope(state->symbols.create(name), *value);
|
||||
}
|
||||
}
|
||||
|
||||
bool NixRepl::processLine(std::string line)
|
||||
{
|
||||
|
@ -429,12 +488,72 @@ bool NixRepl::processLine(std::string line)
|
|||
<< " :p <expr> Evaluate and print expression recursively\n"
|
||||
<< " :q Exit nix-repl\n"
|
||||
<< " :r Reload all files\n"
|
||||
<< " :s <expr> Build dependencies of derivation, then start nix-shell\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"
|
||||
<< " :st [bool] Enable, disable or toggle showing traces for errors\n";
|
||||
<< " :te [bool] Enable, disable or toggle showing traces for errors\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"
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else if (state->debugRepl && (command == ":bt" || command == ":backtrace")) {
|
||||
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
|
||||
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
|
||||
showDebugTrace(std::cout, state->positions, i);
|
||||
}
|
||||
}
|
||||
|
||||
else if (state->debugRepl && (command == ":env")) {
|
||||
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
|
||||
if (idx == debugTraceIndex) {
|
||||
printEnvBindings(*state, i.expr, i.env);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (state->debugRepl && (command == ":st")) {
|
||||
try {
|
||||
// change the DebugTrace index.
|
||||
debugTraceIndex = stoi(arg);
|
||||
} catch (...) { }
|
||||
|
||||
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
|
||||
if (idx == debugTraceIndex) {
|
||||
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
|
||||
showDebugTrace(std::cout, state->positions, i);
|
||||
std::cout << std::endl;
|
||||
printEnvBindings(*state, i.expr, i.env);
|
||||
loadDebugTraceEnv(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (state->debugRepl && (command == ":s" || command == ":step")) {
|
||||
// set flag to stop at next DebugTrace; exit repl.
|
||||
state->debugStop = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
else if (state->debugRepl && (command == ":c" || command == ":continue")) {
|
||||
// set flag to run to next breakpoint or end of program; exit repl.
|
||||
state->debugStop = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
else if (command == ":a" || command == ":add") {
|
||||
|
@ -506,7 +625,7 @@ bool NixRepl::processLine(std::string line)
|
|||
runNix("nix-shell", {state->store->printStorePath(drvPath)});
|
||||
}
|
||||
|
||||
else if (command == ":b" || command == ":bl" || command == ":i" || command == ":s" || command == ":log") {
|
||||
else if (command == ":b" || command == ":bl" || command == ":i" || command == ":sh" || command == ":log") {
|
||||
Value v;
|
||||
evalString(arg, v);
|
||||
StorePath drvPath = getDerivationPath(v);
|
||||
|
@ -567,8 +686,11 @@ bool NixRepl::processLine(std::string line)
|
|||
printValue(std::cout, v, 1000000000) << std::endl;
|
||||
}
|
||||
|
||||
else if (command == ":q" || command == ":quit")
|
||||
else if (command == ":q" || command == ":quit") {
|
||||
state->debugStop = false;
|
||||
state->debugQuit = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
else if (command == ":doc") {
|
||||
Value v;
|
||||
|
@ -593,7 +715,7 @@ bool NixRepl::processLine(std::string line)
|
|||
throw Error("value does not have documentation");
|
||||
}
|
||||
|
||||
else if (command == ":st" || command == ":show-trace") {
|
||||
else if (command == ":te" || command == ":trace-enable") {
|
||||
if (arg == "false" || (arg == "" && loggerSettings.showTrace)) {
|
||||
std::cout << "not showing error traces\n";
|
||||
loggerSettings.showTrace = false;
|
||||
|
@ -630,7 +752,6 @@ bool NixRepl::processLine(std::string line)
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
void NixRepl::loadFile(const Path & path)
|
||||
{
|
||||
loadedFiles.remove(path);
|
||||
|
@ -669,10 +790,10 @@ void NixRepl::initEnv()
|
|||
env = &state->allocEnv(envSize);
|
||||
env->up = &state->baseEnv;
|
||||
displ = 0;
|
||||
staticEnv.vars.clear();
|
||||
staticEnv->vars.clear();
|
||||
|
||||
varNames.clear();
|
||||
for (auto & i : state->staticBaseEnv.vars)
|
||||
for (auto & i : state->staticBaseEnv->vars)
|
||||
varNames.emplace(state->symbols[i.first]);
|
||||
}
|
||||
|
||||
|
@ -681,16 +802,24 @@ void NixRepl::reloadFiles()
|
|||
{
|
||||
initEnv();
|
||||
|
||||
loadFiles();
|
||||
}
|
||||
|
||||
|
||||
void NixRepl::loadFiles()
|
||||
{
|
||||
Strings old = loadedFiles;
|
||||
loadedFiles.clear();
|
||||
|
||||
bool first = true;
|
||||
for (auto & i : old) {
|
||||
if (!first) notice("");
|
||||
first = false;
|
||||
notice("Loading '%1%'...", i);
|
||||
loadFile(i);
|
||||
}
|
||||
|
||||
for (auto & [i, what] : getValues()) {
|
||||
notice("Loading installable '%1%'...", what);
|
||||
addAttrsToScope(*i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -701,12 +830,12 @@ void NixRepl::addAttrsToScope(Value & attrs)
|
|||
throw Error("environment full; cannot add more variables");
|
||||
|
||||
for (auto & i : *attrs.attrs) {
|
||||
staticEnv.vars.emplace_back(i.name, displ);
|
||||
staticEnv->vars.emplace_back(i.name, displ);
|
||||
env->values[displ++] = i.value;
|
||||
varNames.emplace(state->symbols[i.name]);
|
||||
}
|
||||
staticEnv.sort();
|
||||
staticEnv.deduplicate();
|
||||
staticEnv->sort();
|
||||
staticEnv->deduplicate();
|
||||
notice("Added %1% variables.", attrs.attrs->size());
|
||||
}
|
||||
|
||||
|
@ -715,10 +844,10 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
|
|||
{
|
||||
if (displ >= envSize)
|
||||
throw Error("environment full; cannot add more variables");
|
||||
if (auto oldVar = staticEnv.find(name); oldVar != staticEnv.vars.end())
|
||||
staticEnv.vars.erase(oldVar);
|
||||
staticEnv.vars.emplace_back(name, displ);
|
||||
staticEnv.sort();
|
||||
if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end())
|
||||
staticEnv->vars.erase(oldVar);
|
||||
staticEnv->vars.emplace_back(name, displ);
|
||||
staticEnv->sort();
|
||||
env->values[displ++] = &v;
|
||||
varNames.emplace(state->symbols[name]);
|
||||
}
|
||||
|
@ -886,17 +1015,66 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
|||
return str;
|
||||
}
|
||||
|
||||
struct CmdRepl : StoreCommand, MixEvalArgs
|
||||
void runRepl(
|
||||
ref<EvalState>evalState,
|
||||
const ValMap & extraEnv)
|
||||
{
|
||||
auto getValues = [&]()->NixRepl::AnnotatedValues{
|
||||
NixRepl::AnnotatedValues values;
|
||||
return values;
|
||||
};
|
||||
const Strings & searchPath = {};
|
||||
auto repl = std::make_unique<NixRepl>(
|
||||
searchPath,
|
||||
openStore(),
|
||||
evalState,
|
||||
getValues
|
||||
);
|
||||
|
||||
repl->initEnv();
|
||||
|
||||
// add 'extra' vars.
|
||||
for (auto & [name, value] : extraEnv)
|
||||
repl->addVarToScope(repl->state->symbols.create(name), *value);
|
||||
|
||||
repl->mainLoop();
|
||||
}
|
||||
|
||||
struct CmdRepl : InstallablesCommand
|
||||
{
|
||||
CmdRepl() {
|
||||
evalSettings.pureEval = false;
|
||||
}
|
||||
|
||||
void prepare()
|
||||
{
|
||||
if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
|
||||
warn("future versions of Nix will require using `--file` to load a file");
|
||||
if (this->_installables.size() > 1)
|
||||
warn("more than one input file is not currently supported");
|
||||
auto filePath = this->_installables[0].data();
|
||||
file = std::optional(filePath);
|
||||
_installables.front() = _installables.back();
|
||||
_installables.pop_back();
|
||||
}
|
||||
installables = InstallablesCommand::load();
|
||||
}
|
||||
|
||||
std::vector<std::string> files;
|
||||
|
||||
CmdRepl()
|
||||
Strings getDefaultFlakeAttrPaths() override
|
||||
{
|
||||
expectArgs({
|
||||
.label = "files",
|
||||
.handler = {&files},
|
||||
.completer = completePath
|
||||
});
|
||||
return {""};
|
||||
}
|
||||
|
||||
bool useDefaultInstallables() override
|
||||
{
|
||||
return file.has_value() or expr.has_value();
|
||||
}
|
||||
|
||||
bool forceImpureByDefault() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string description() override
|
||||
|
@ -913,10 +1091,37 @@ struct CmdRepl : StoreCommand, MixEvalArgs
|
|||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
evalSettings.pureEval = false;
|
||||
auto repl = std::make_unique<NixRepl>(searchPath, openStore());
|
||||
auto state = getEvalState();
|
||||
auto getValues = [&]()->NixRepl::AnnotatedValues{
|
||||
auto installables = load();
|
||||
NixRepl::AnnotatedValues values;
|
||||
for (auto & installable: installables){
|
||||
auto what = installable->what();
|
||||
if (file){
|
||||
auto [val, pos] = installable->toValue(*state);
|
||||
auto what = installable->what();
|
||||
state->forceValue(*val, pos);
|
||||
auto autoArgs = getAutoArgs(*state);
|
||||
auto valPost = state->allocValue();
|
||||
state->autoCallFunction(*autoArgs, *val, *valPost);
|
||||
state->forceValue(*valPost, pos);
|
||||
values.push_back( {valPost, what });
|
||||
} else {
|
||||
auto [val, pos] = installable->toValue(*state);
|
||||
values.push_back( {val, what} );
|
||||
}
|
||||
}
|
||||
return values;
|
||||
};
|
||||
auto repl = std::make_unique<NixRepl>(
|
||||
searchPath,
|
||||
openStore(),
|
||||
state,
|
||||
getValues
|
||||
);
|
||||
repl->autoArgs = getAutoArgs(*repl->state);
|
||||
repl->mainLoop(files);
|
||||
repl->initEnv();
|
||||
repl->mainLoop();
|
||||
}
|
||||
};
|
||||
|
|
@ -47,7 +47,7 @@ struct AttrDb
|
|||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
Path cacheDir = getCacheDir() + "/nix/eval-cache-v2";
|
||||
Path cacheDir = getCacheDir() + "/nix/eval-cache-v4";
|
||||
createDirs(cacheDir);
|
||||
|
||||
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
|
||||
|
@ -175,6 +175,42 @@ struct AttrDb
|
|||
});
|
||||
}
|
||||
|
||||
AttrId setInt(
|
||||
AttrKey key,
|
||||
int n)
|
||||
{
|
||||
return doSQLite([&]()
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(AttrType::Int)
|
||||
(n).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
});
|
||||
}
|
||||
|
||||
AttrId setListOfStrings(
|
||||
AttrKey key,
|
||||
const std::vector<std::string> & l)
|
||||
{
|
||||
return doSQLite([&]()
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(symbols[key.second])
|
||||
(AttrType::ListOfStrings)
|
||||
(concatStringsSep("\t", l)).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
});
|
||||
}
|
||||
|
||||
AttrId setPlaceholder(AttrKey key)
|
||||
{
|
||||
return doSQLite([&]()
|
||||
|
@ -246,7 +282,7 @@ struct AttrDb
|
|||
auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second]));
|
||||
if (!queryAttribute.next()) return {};
|
||||
|
||||
auto rowId = (AttrType) queryAttribute.getInt(0);
|
||||
auto rowId = (AttrId) queryAttribute.getInt(0);
|
||||
auto type = (AttrType) queryAttribute.getInt(1);
|
||||
|
||||
switch (type) {
|
||||
|
@ -269,6 +305,10 @@ struct AttrDb
|
|||
}
|
||||
case AttrType::Bool:
|
||||
return {{rowId, queryAttribute.getInt(2) != 0}};
|
||||
case AttrType::Int:
|
||||
return {{rowId, int_t{queryAttribute.getInt(2)}}};
|
||||
case AttrType::ListOfStrings:
|
||||
return {{rowId, tokenizeString<std::vector<std::string>>(queryAttribute.getStr(2), "\t")}};
|
||||
case AttrType::Missing:
|
||||
return {{rowId, missing_t()}};
|
||||
case AttrType::Misc:
|
||||
|
@ -385,7 +425,7 @@ std::string AttrCursor::getAttrPathStr(Symbol name) const
|
|||
|
||||
Value & AttrCursor::forceValue()
|
||||
{
|
||||
debug("evaluating uncached attribute %s", getAttrPathStr());
|
||||
debug("evaluating uncached attribute '%s'", getAttrPathStr());
|
||||
|
||||
auto & v = getValue();
|
||||
|
||||
|
@ -406,6 +446,8 @@ Value & AttrCursor::forceValue()
|
|||
cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}};
|
||||
else if (v.type() == nBool)
|
||||
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
|
||||
else if (v.type() == nInt)
|
||||
cachedValue = {root->db->setInt(getKey(), v.integer), int_t{v.integer}};
|
||||
else if (v.type() == nAttrs)
|
||||
; // FIXME: do something?
|
||||
else
|
||||
|
@ -444,7 +486,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
|||
return nullptr;
|
||||
else if (std::get_if<failed_t>(&attr->second)) {
|
||||
if (forceErrors)
|
||||
debug("reevaluating failed cached attribute '%s'");
|
||||
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
|
||||
else
|
||||
throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name));
|
||||
} else
|
||||
|
@ -465,11 +507,6 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
|||
return nullptr;
|
||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
|
||||
for (auto & attr : *v.attrs) {
|
||||
if (root->db)
|
||||
root->db->setPlaceholder({cachedValue->first, attr.name});
|
||||
}
|
||||
|
||||
auto attr = v.attrs->get(name);
|
||||
|
||||
if (!attr) {
|
||||
|
@ -534,14 +571,14 @@ std::string AttrCursor::getString()
|
|||
debug("using cached string attribute '%s'", getAttrPathStr());
|
||||
return s->first;
|
||||
} else
|
||||
throw TypeError("'%s' is not a string", getAttrPathStr());
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type() != nString && v.type() != nPath)
|
||||
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
|
||||
|
||||
return v.type() == nString ? v.string.s : v.path;
|
||||
}
|
||||
|
@ -565,7 +602,7 @@ string_t AttrCursor::getStringWithContext()
|
|||
return *s;
|
||||
}
|
||||
} else
|
||||
throw TypeError("'%s' is not a string", getAttrPathStr());
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,7 +613,7 @@ string_t AttrCursor::getStringWithContext()
|
|||
else if (v.type() == nPath)
|
||||
return {v.path, {}};
|
||||
else
|
||||
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
|
||||
}
|
||||
|
||||
bool AttrCursor::getBool()
|
||||
|
@ -589,18 +626,73 @@ bool AttrCursor::getBool()
|
|||
debug("using cached Boolean attribute '%s'", getAttrPathStr());
|
||||
return *b;
|
||||
} else
|
||||
throw TypeError("'%s' is not a Boolean", getAttrPathStr());
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type() != nBool)
|
||||
throw TypeError("'%s' is not a Boolean", getAttrPathStr());
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
|
||||
|
||||
return v.boolean;
|
||||
}
|
||||
|
||||
NixInt AttrCursor::getInt()
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey());
|
||||
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
if (auto i = std::get_if<int_t>(&cachedValue->second)) {
|
||||
debug("using cached Integer attribute '%s'", getAttrPathStr());
|
||||
return i->x;
|
||||
} else
|
||||
throw TypeError("'%s' is not an Integer", getAttrPathStr());
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type() != nInt)
|
||||
throw TypeError("'%s' is not an Integer", getAttrPathStr());
|
||||
|
||||
return v.integer;
|
||||
}
|
||||
|
||||
std::vector<std::string> AttrCursor::getListOfStrings()
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey());
|
||||
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
if (auto l = std::get_if<std::vector<std::string>>(&cachedValue->second)) {
|
||||
debug("using cached list of strings attribute '%s'", getAttrPathStr());
|
||||
return *l;
|
||||
} else
|
||||
throw TypeError("'%s' is not a list of strings", getAttrPathStr());
|
||||
}
|
||||
}
|
||||
|
||||
debug("evaluating uncached attribute '%s'", getAttrPathStr());
|
||||
|
||||
auto & v = getValue();
|
||||
root->state.forceValue(v, noPos);
|
||||
|
||||
if (v.type() != nList)
|
||||
throw TypeError("'%s' is not a list", getAttrPathStr());
|
||||
|
||||
std::vector<std::string> res;
|
||||
|
||||
for (auto & elem : v.listItems())
|
||||
res.push_back(std::string(root->state.forceStringNoCtx(*elem)));
|
||||
|
||||
if (root->db)
|
||||
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrs()
|
||||
{
|
||||
if (root->db) {
|
||||
|
@ -611,14 +703,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
|
|||
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
||||
return *attrs;
|
||||
} else
|
||||
throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type() != nAttrs)
|
||||
throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
|
||||
|
||||
std::vector<Symbol> attrs;
|
||||
for (auto & attr : *getValue().attrs)
|
||||
|
|
|
@ -44,12 +44,15 @@ enum AttrType {
|
|||
Misc = 4,
|
||||
Failed = 5,
|
||||
Bool = 6,
|
||||
ListOfStrings = 7,
|
||||
Int = 8,
|
||||
};
|
||||
|
||||
struct placeholder_t {};
|
||||
struct missing_t {};
|
||||
struct misc_t {};
|
||||
struct failed_t {};
|
||||
struct int_t { NixInt x; };
|
||||
typedef uint64_t AttrId;
|
||||
typedef std::pair<AttrId, Symbol> AttrKey;
|
||||
typedef std::pair<std::string, NixStringContext> string_t;
|
||||
|
@ -61,7 +64,9 @@ typedef std::variant<
|
|||
missing_t,
|
||||
misc_t,
|
||||
failed_t,
|
||||
bool
|
||||
bool,
|
||||
int_t,
|
||||
std::vector<std::string>
|
||||
> AttrValue;
|
||||
|
||||
class AttrCursor : public std::enable_shared_from_this<AttrCursor>
|
||||
|
@ -114,6 +119,10 @@ public:
|
|||
|
||||
bool getBool();
|
||||
|
||||
NixInt getInt();
|
||||
|
||||
std::vector<std::string> getListOfStrings();
|
||||
|
||||
std::vector<Symbol> getAttrs();
|
||||
|
||||
bool isDerivation();
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||
[[gnu::always_inline]]
|
||||
inline void * allocBytes(size_t n)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <sys/resource.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
|
||||
#include <sys/resource.h>
|
||||
|
||||
|
@ -36,7 +37,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
|
||||
static char * allocString(size_t size)
|
||||
{
|
||||
char * t;
|
||||
|
@ -147,7 +147,10 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
|
|||
else {
|
||||
str << "[ ";
|
||||
for (auto v2 : listItems()) {
|
||||
v2->print(symbols, str, seen);
|
||||
if (v2)
|
||||
v2->print(symbols, str, seen);
|
||||
else
|
||||
str << "(nullptr)";
|
||||
str << " ";
|
||||
}
|
||||
str << "]";
|
||||
|
@ -184,6 +187,11 @@ void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepe
|
|||
print(symbols, str, showRepeated ? nullptr : &seen);
|
||||
}
|
||||
|
||||
// Pretty print types for assertion errors
|
||||
std::ostream & operator << (std::ostream & os, const ValueType t) {
|
||||
os << showType(t);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string printValue(const EvalState & state, const Value & v)
|
||||
{
|
||||
|
@ -451,10 +459,15 @@ EvalState::EvalState(
|
|||
, sKey(symbols.create("key"))
|
||||
, sPath(symbols.create("path"))
|
||||
, sPrefix(symbols.create("prefix"))
|
||||
, sOutputSpecified(symbols.create("outputSpecified"))
|
||||
, repair(NoRepair)
|
||||
, emptyBindings(0)
|
||||
, store(store)
|
||||
, buildStore(buildStore ? buildStore : store)
|
||||
, debugRepl(nullptr)
|
||||
, debugStop(false)
|
||||
, debugQuit(false)
|
||||
, trylevel(0)
|
||||
, regexCache(makeRegexCache())
|
||||
#if HAVE_BOEHMGC
|
||||
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||
|
@ -464,7 +477,7 @@ EvalState::EvalState(
|
|||
, env1AllocCache(std::make_shared<void *>(nullptr))
|
||||
#endif
|
||||
, baseEnv(allocEnv(128))
|
||||
, staticBaseEnv(false, 0)
|
||||
, staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)}
|
||||
{
|
||||
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
|
||||
|
||||
|
@ -522,7 +535,7 @@ void EvalState::allowPath(const StorePath & storePath)
|
|||
allowedPaths->insert(store->toRealPath(storePath));
|
||||
}
|
||||
|
||||
void EvalState::allowAndSetStorePathString(const StorePath &storePath, Value & v)
|
||||
void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value & v)
|
||||
{
|
||||
allowPath(storePath);
|
||||
|
||||
|
@ -630,7 +643,7 @@ Value * EvalState::addConstant(const std::string & name, Value & v)
|
|||
|
||||
void EvalState::addConstant(const std::string & name, Value * v)
|
||||
{
|
||||
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
|
||||
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
|
||||
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
|
||||
|
@ -663,7 +676,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
|||
|
||||
Value * v = allocValue();
|
||||
v->mkPrimOp(new PrimOp(primOp));
|
||||
staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
|
||||
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
|
||||
baseEnv.values[baseEnvDispl++] = v;
|
||||
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v));
|
||||
return v;
|
||||
|
@ -693,11 +706,137 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
|||
}
|
||||
|
||||
|
||||
// just for the current level of StaticEnv, not the whole chain.
|
||||
void printStaticEnvBindings(const SymbolTable & st, const StaticEnv & se)
|
||||
{
|
||||
std::cout << ANSI_MAGENTA;
|
||||
for (auto & i : se.vars)
|
||||
std::cout << st[i.first] << " ";
|
||||
std::cout << ANSI_NORMAL;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// just for the current level of Env, not the whole chain.
|
||||
void printWithBindings(const SymbolTable & st, const Env & env)
|
||||
{
|
||||
if (env.type == Env::HasWithAttrs) {
|
||||
std::cout << "with: ";
|
||||
std::cout << ANSI_MAGENTA;
|
||||
Bindings::iterator j = env.values[0]->attrs->begin();
|
||||
while (j != env.values[0]->attrs->end()) {
|
||||
std::cout << st[j->name] << " ";
|
||||
++j;
|
||||
}
|
||||
std::cout << ANSI_NORMAL;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl)
|
||||
{
|
||||
std::cout << "Env level " << lvl << std::endl;
|
||||
|
||||
if (se.up && env.up) {
|
||||
std::cout << "static: ";
|
||||
printStaticEnvBindings(st, se);
|
||||
printWithBindings(st, env);
|
||||
std::cout << std::endl;
|
||||
printEnvBindings(st, *se.up, *env.up, ++lvl);
|
||||
} else {
|
||||
std::cout << ANSI_MAGENTA;
|
||||
// for the top level, don't print the double underscore ones;
|
||||
// they are in builtins.
|
||||
for (auto & i : se.vars)
|
||||
if (!hasPrefix(st[i.first], "__"))
|
||||
std::cout << st[i.first] << " ";
|
||||
std::cout << ANSI_NORMAL;
|
||||
std::cout << std::endl;
|
||||
printWithBindings(st, env); // probably nothing there for the top level.
|
||||
std::cout << std::endl;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env)
|
||||
{
|
||||
// just print the names for now
|
||||
auto se = es.getStaticEnv(expr);
|
||||
if (se)
|
||||
printEnvBindings(es.symbols, *se, env, 0);
|
||||
}
|
||||
|
||||
void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, ValMap & vm)
|
||||
{
|
||||
// add bindings for the next level up first, so that the bindings for this level
|
||||
// override the higher levels.
|
||||
// The top level bindings (builtins) are skipped since they are added for us by initEnv()
|
||||
if (env.up && se.up) {
|
||||
mapStaticEnvBindings(st, *se.up, *env.up, vm);
|
||||
|
||||
if (env.type == Env::HasWithAttrs) {
|
||||
// add 'with' bindings.
|
||||
Bindings::iterator j = env.values[0]->attrs->begin();
|
||||
while (j != env.values[0]->attrs->end()) {
|
||||
vm[st[j->name]] = j->value;
|
||||
++j;
|
||||
}
|
||||
} else {
|
||||
// iterate through staticenv bindings and add them.
|
||||
for (auto & i : se.vars)
|
||||
vm[st[i.first]] = env.values[i.second];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env)
|
||||
{
|
||||
auto vm = std::make_unique<ValMap>();
|
||||
mapStaticEnvBindings(st, se, env, *vm);
|
||||
return vm;
|
||||
}
|
||||
|
||||
void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & expr)
|
||||
{
|
||||
// double check we've got the debugRepl function pointer.
|
||||
if (!debugRepl)
|
||||
return;
|
||||
|
||||
auto dts =
|
||||
error && expr.getPos()
|
||||
? std::make_unique<DebugTraceStacker>(
|
||||
*this,
|
||||
DebugTrace {
|
||||
.pos = error->info().errPos ? *error->info().errPos : positions[expr.getPos()],
|
||||
.expr = expr,
|
||||
.env = env,
|
||||
.hint = error->info().msg,
|
||||
.isError = true
|
||||
})
|
||||
: nullptr;
|
||||
|
||||
if (error)
|
||||
{
|
||||
printError("%s\n\n", error->what());
|
||||
|
||||
if (trylevel > 0 && error->info().level != lvlInfo)
|
||||
printError("This exception occurred in a 'tryEval' call. Use " ANSI_GREEN "--ignore-try" ANSI_NORMAL " to skip these.\n");
|
||||
|
||||
printError(ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL);
|
||||
}
|
||||
|
||||
auto se = getStaticEnv(expr);
|
||||
if (se) {
|
||||
auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
|
||||
(debugRepl)(ref<EvalState>(shared_from_this()), *vm);
|
||||
}
|
||||
}
|
||||
|
||||
/* Every "format" object (even temporary) takes up a few hundred bytes
|
||||
of stack space, which is a real killer in the recursive
|
||||
evaluator. So here are some helper functions for throwing
|
||||
exceptions. */
|
||||
|
||||
// *WithTrace
|
||||
void EvalState::throwTypeErrorWithTrace(
|
||||
const PosIdx pos,
|
||||
const char * s,
|
||||
|
@ -759,6 +898,17 @@ void EvalState::throwEvalErrorWithTrace(const char * s, const std::string_view s
|
|||
throw e;
|
||||
}
|
||||
|
||||
|
||||
// *WithoutTrace coerce-strings
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
|
||||
{
|
||||
debugThrow(EvalError({
|
||||
.msg = hintfmt(s),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const
|
||||
{
|
||||
throw EvalError(ErrorInfo {
|
||||
|
@ -848,6 +998,167 @@ void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, cons
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
// master
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
|
||||
{
|
||||
debugThrow(EvalError({
|
||||
.msg = hintfmt(s),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s)
|
||||
{
|
||||
debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt(s),
|
||||
.errPos = positions[pos]
|
||||
}));
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const char * s, const std::string & s2)
|
||||
{
|
||||
debugThrowLastTrace(EvalError(s, s2));
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
||||
const std::string & s2, Env & env, Expr & expr)
|
||||
{
|
||||
debugThrow(EvalError(ErrorInfo{
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = positions[pos],
|
||||
.suggestions = suggestions,
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2)
|
||||
{
|
||||
debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = positions[pos]
|
||||
}));
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr)
|
||||
{
|
||||
debugThrow(EvalError({
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const char * s, const std::string & s2,
|
||||
const std::string & s3)
|
||||
{
|
||||
debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = positions[noPos]
|
||||
}));
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
||||
const std::string & s3)
|
||||
{
|
||||
debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = positions[pos]
|
||||
}));
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
||||
const std::string & s3, Env & env, Expr & expr)
|
||||
{
|
||||
debugThrow(EvalError({
|
||||
.msg = hintfmt(s, s2),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr)
|
||||
{
|
||||
// p1 is where the error occurred; p2 is a position mentioned in the message.
|
||||
debugThrow(EvalError({
|
||||
.msg = hintfmt(s, symbols[sym], positions[p2]),
|
||||
.errPos = positions[p1]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v)
|
||||
{
|
||||
debugThrowLastTrace(TypeError({
|
||||
.msg = hintfmt(s, showType(v)),
|
||||
.errPos = positions[pos]
|
||||
}));
|
||||
}
|
||||
|
||||
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr)
|
||||
{
|
||||
debugThrow(TypeError({
|
||||
.msg = hintfmt(s, showType(v)),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwTypeError(const PosIdx pos, const char * s)
|
||||
{
|
||||
debugThrowLastTrace(TypeError({
|
||||
.msg = hintfmt(s),
|
||||
.errPos = positions[pos]
|
||||
}));
|
||||
}
|
||||
|
||||
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
|
||||
const Symbol s2, Env & env, Expr &expr)
|
||||
{
|
||||
debugThrow(TypeError({
|
||||
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
||||
const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr)
|
||||
{
|
||||
debugThrow(TypeError(ErrorInfo {
|
||||
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
|
||||
.errPos = positions[pos],
|
||||
.suggestions = suggestions,
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr)
|
||||
{
|
||||
debugThrow(TypeError({
|
||||
.msg = hintfmt(s, showType(v)),
|
||||
.errPos = positions[expr.getPos()],
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
|
||||
{
|
||||
debugThrow(AssertionError({
|
||||
.msg = hintfmt(s, s1),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
|
||||
{
|
||||
debugThrow(UndefinedVarError({
|
||||
.msg = hintfmt(s, s1),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
|
||||
{
|
||||
debugThrow(MissingArgumentError({
|
||||
.msg = hintfmt(s, s1),
|
||||
.errPos = positions[pos]
|
||||
}), env, expr);
|
||||
}
|
||||
|
||||
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
|
||||
{
|
||||
e.addTrace(std::nullopt, s, s2);
|
||||
|
@ -858,6 +1169,32 @@ void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const
|
|||
e.addTrace(positions[pos], s, s2);
|
||||
}
|
||||
|
||||
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
|
||||
EvalState & state,
|
||||
Expr & expr,
|
||||
Env & env,
|
||||
std::optional<ErrPos> pos,
|
||||
const char * s,
|
||||
const std::string & s2)
|
||||
{
|
||||
return std::make_unique<DebugTraceStacker>(state,
|
||||
DebugTrace {
|
||||
.pos = pos,
|
||||
.expr = expr,
|
||||
.env = env,
|
||||
.hint = hintfmt(s, s2),
|
||||
.isError = false
|
||||
});
|
||||
}
|
||||
|
||||
DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
|
||||
: evalState(evalState)
|
||||
, trace(std::move(t))
|
||||
{
|
||||
evalState.debugTraces.push_front(trace);
|
||||
if (evalState.debugStop && evalState.debugRepl)
|
||||
evalState.runDebugRepl(nullptr, trace.env, trace.expr);
|
||||
}
|
||||
|
||||
void Value::mkString(std::string_view s)
|
||||
{
|
||||
|
@ -916,12 +1253,11 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
|||
return j->value;
|
||||
}
|
||||
if (!env->prevWith)
|
||||
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name]);
|
||||
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast<ExprVar&>(var));
|
||||
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EvalState::mkList(Value & v, size_t size)
|
||||
{
|
||||
v.mkList(size);
|
||||
|
@ -1054,8 +1390,19 @@ void EvalState::cacheFile(
|
|||
fileParseCache[resolvedPath] = e;
|
||||
|
||||
try {
|
||||
// Enforce that 'flake.nix' is a direct attrset, not a computation.
|
||||
if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e)))
|
||||
auto dts = debugRepl
|
||||
? makeDebugTraceStacker(
|
||||
*this,
|
||||
*e,
|
||||
this->baseEnv,
|
||||
e->getPos() ? std::optional(ErrPos(positions[e->getPos()])) : std::nullopt,
|
||||
"while evaluating the file '%1%':", resolvedPath)
|
||||
: nullptr;
|
||||
|
||||
// Enforce that 'flake.nix' is a direct attrset, not a
|
||||
// computation.
|
||||
if (mustBeTrivial &&
|
||||
!(dynamic_cast<ExprAttrs *>(e)))
|
||||
throwEvalError("file '%s' must be an attribute set", path);
|
||||
eval(e, v);
|
||||
} catch (Error & e) {
|
||||
|
@ -1094,7 +1441,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po
|
|||
try {
|
||||
e->eval(*this, env, v);
|
||||
if (v.type() != nAttrs)
|
||||
throwTypeError("value is %1% while a set was expected", v);
|
||||
throwTypeError("value is %1% while a set was expected", v, env, *e);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(positions[pos], errorCtx);
|
||||
throw;
|
||||
|
@ -1203,7 +1550,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
|||
auto nameSym = state.symbols.create(nameVal.string.s);
|
||||
Bindings::iterator j = v.attrs->find(nameSym);
|
||||
if (j != v.attrs->end())
|
||||
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos);
|
||||
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this);
|
||||
|
||||
i.valueExpr->setName(nameSym);
|
||||
/* Keep sorted order so find can catch duplicates */
|
||||
|
@ -1277,6 +1624,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
|||
e->eval(state, env, vTmp);
|
||||
|
||||
try {
|
||||
auto dts = state.debugRepl
|
||||
? makeDebugTraceStacker(
|
||||
state,
|
||||
*this,
|
||||
env,
|
||||
state.positions[pos2],
|
||||
"while evaluating the attribute '%1%'",
|
||||
showAttrPath(state, env, attrPath))
|
||||
: nullptr;
|
||||
|
||||
for (auto & i : attrPath) {
|
||||
state.nrLookups++;
|
||||
|
@ -1299,7 +1655,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
|||
state.throwEvalError(
|
||||
pos,
|
||||
Suggestions::bestMatches(allAttrNames, state.symbols[name]),
|
||||
"attribute '%1%' missing", state.symbols[name]);
|
||||
"attribute '%1%' missing", state.symbols[name], env, *this);
|
||||
}
|
||||
}
|
||||
vAttrs = j->value;
|
||||
|
@ -1351,6 +1707,7 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
|
|||
v.mkLambda(&env, this);
|
||||
}
|
||||
|
||||
|
||||
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
||||
{
|
||||
auto trace = evalSettings.traceFunctionCalls
|
||||
|
@ -1389,7 +1746,6 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
|
||||
if (!lambda.hasFormals())
|
||||
env2.values[displ++] = args[0];
|
||||
|
||||
else {
|
||||
try {
|
||||
forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
|
||||
|
@ -1412,7 +1768,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
throwTypeErrorWithTrace(lambda.pos,
|
||||
"function '%1%' called without required argument '%2%'",
|
||||
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
|
||||
i.name, pos, "from call site");
|
||||
i.name, pos, "from call site", *fun.lambda.env, lambda);
|
||||
}
|
||||
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
||||
} else {
|
||||
|
@ -1446,6 +1802,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
|||
|
||||
/* Evaluate the body. */
|
||||
try {
|
||||
auto dts = debugRepl
|
||||
? makeDebugTraceStacker(
|
||||
*this, *lambda.body, env2, positions[lambda.pos],
|
||||
"while evaluating %s",
|
||||
lambda.name
|
||||
? concatStrings("'", symbols[lambda.name], "'")
|
||||
: "anonymous lambda")
|
||||
: nullptr;
|
||||
|
||||
lambda.body->eval(*this, env2, vCur);
|
||||
} catch (Error & e) {
|
||||
if (loggerSettings.showTrace.get()) {
|
||||
|
@ -1617,8 +1982,8 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
|
|||
Nix attempted to evaluate a function as a top level expression; in
|
||||
this case it must have its arguments supplied either by default
|
||||
values, or passed explicitly with '--arg' or '--argstr'. See
|
||||
https://nixos.org/manual/nix/stable/#ss-functions.)", symbols[i.name]);
|
||||
|
||||
https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name],
|
||||
*fun.lambda.env, *fun.lambda.fun);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1641,7 +2006,7 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
|
|||
|
||||
void ExprIf::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
// We cheat in the parser, an pass the position of the condition as the position of the if itself.
|
||||
// We cheat in the parser, and pass the position of the condition as the position of the if itself.
|
||||
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
|
||||
}
|
||||
|
||||
|
@ -1651,7 +2016,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
|||
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
|
||||
std::ostringstream out;
|
||||
cond->show(state.symbols, out);
|
||||
state.throwAssertionError(pos, "assertion '%1%' failed", out.str());
|
||||
state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this);
|
||||
}
|
||||
body->eval(state, env, v);
|
||||
}
|
||||
|
@ -1828,14 +2193,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
nf = n;
|
||||
nf += vTmp.fpoint;
|
||||
} else
|
||||
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp));
|
||||
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this);
|
||||
} else if (firstType == nFloat) {
|
||||
if (vTmp.type() == nInt) {
|
||||
nf += vTmp.integer;
|
||||
} else if (vTmp.type() == nFloat) {
|
||||
nf += vTmp.fpoint;
|
||||
} else
|
||||
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
|
||||
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this);
|
||||
} else {
|
||||
if (s.empty()) s.reserve(es->size());
|
||||
/* skip canonization of first path, which would only be not
|
||||
|
@ -1855,7 +2220,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
|||
v.mkFloat(nf);
|
||||
else if (firstType == nPath) {
|
||||
if (!context.empty())
|
||||
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
|
||||
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this);
|
||||
v.mkPath(canonPath(str()));
|
||||
} else
|
||||
v.mkStringMove(c_str(), context);
|
||||
|
@ -1882,6 +2247,12 @@ void EvalState::forceValueDeep(Value & v)
|
|||
if (v.type() == nAttrs) {
|
||||
for (auto & i : *v.attrs)
|
||||
try {
|
||||
// If the value is a thunk, we're evaling. Otherwise no trace necessary.
|
||||
auto dts = debugRepl && i.value->isThunk()
|
||||
? makeDebugTraceStacker(*this, *i.value->thunk.expr, *i.value->thunk.env, positions[i.pos],
|
||||
"while evaluating the attribute '%1%'", symbols[i.name])
|
||||
: nullptr;
|
||||
|
||||
recurse(*i.value);
|
||||
} catch (Error & e) {
|
||||
addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]);
|
||||
|
@ -2092,7 +2463,7 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
return std::move(*maybeString);
|
||||
auto i = v.attrs->find(sOutPath);
|
||||
if (i == v.attrs->end())
|
||||
throwTypeErrorWithTrace("cannot coerce %1% to a string", showType(v), pos, errorCtx);
|
||||
throwTypeErrorWithTrace("cannot coerce a set to a string", pos, errorCtx);
|
||||
return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
|
||||
}
|
||||
|
||||
|
@ -2100,7 +2471,6 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
|
|||
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
|
||||
|
||||
if (coerceMore) {
|
||||
|
||||
/* Note that `false' is represented as an empty string for
|
||||
shell scripting convenience, just like `null'. */
|
||||
if (v.type() == nBool && v.boolean) return "1";
|
||||
|
@ -2334,18 +2704,18 @@ void EvalState::printStats()
|
|||
}
|
||||
{
|
||||
auto list = topObj.list("functions");
|
||||
for (auto & i : functionCalls) {
|
||||
for (auto & [fun, count] : functionCalls) {
|
||||
auto obj = list.object();
|
||||
if (i.first->name)
|
||||
obj.attr("name", (const std::string &) i.first->name);
|
||||
if (fun->name)
|
||||
obj.attr("name", (std::string_view) symbols[fun->name]);
|
||||
else
|
||||
obj.attr("name", nullptr);
|
||||
if (auto pos = positions[i.first->pos]) {
|
||||
obj.attr("file", (const std::string &) pos.file);
|
||||
if (auto pos = positions[fun->pos]) {
|
||||
obj.attr("file", (std::string_view) pos.file);
|
||||
obj.attr("line", pos.line);
|
||||
obj.attr("column", pos.column);
|
||||
}
|
||||
obj.attr("count", i.second);
|
||||
obj.attr("count", count);
|
||||
}
|
||||
}
|
||||
{
|
||||
|
@ -2372,7 +2742,7 @@ void EvalState::printStats()
|
|||
|
||||
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
|
||||
{
|
||||
auto e = TypeError(ErrorInfo {
|
||||
auto e = TypeError({
|
||||
.msg = hintfmt("cannot coerce %1% to a string", showType())
|
||||
});
|
||||
e.addTrace(pos, errorCtx);
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
|
@ -25,7 +24,6 @@ enum RepairFlag : bool;
|
|||
|
||||
typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||
|
||||
|
||||
struct PrimOp
|
||||
{
|
||||
PrimOpFun fun;
|
||||
|
@ -35,6 +33,11 @@ struct PrimOp
|
|||
const char * doc = nullptr;
|
||||
};
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *> > > ValMap;
|
||||
#else
|
||||
typedef std::map<std::string, Value *> ValMap;
|
||||
#endif
|
||||
|
||||
struct Env
|
||||
{
|
||||
|
@ -44,6 +47,10 @@ struct Env
|
|||
Value * values[0];
|
||||
};
|
||||
|
||||
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env);
|
||||
void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl = 0);
|
||||
|
||||
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env);
|
||||
|
||||
void copyContext(const Value & v, PathSet & context);
|
||||
|
||||
|
@ -55,6 +62,7 @@ typedef std::map<Path, StorePath> SrcToStore;
|
|||
|
||||
std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
|
||||
std::string printValue(const EvalState & state, const Value & v);
|
||||
std::ostream & operator << (std::ostream & os, const ValueType t);
|
||||
|
||||
|
||||
typedef std::pair<std::string, std::string> SearchPathElem;
|
||||
|
@ -69,8 +77,17 @@ struct RegexCache;
|
|||
|
||||
std::shared_ptr<RegexCache> makeRegexCache();
|
||||
|
||||
struct DebugTrace {
|
||||
std::optional<ErrPos> pos;
|
||||
const Expr & expr;
|
||||
const Env & env;
|
||||
hintformat hint;
|
||||
bool isError;
|
||||
};
|
||||
|
||||
class EvalState
|
||||
void debugError(Error * e, Env & env, Expr & expr);
|
||||
|
||||
class EvalState : public std::enable_shared_from_this<EvalState>
|
||||
{
|
||||
public:
|
||||
SymbolTable symbols;
|
||||
|
@ -86,7 +103,8 @@ public:
|
|||
sOutputHash, sOutputHashAlgo, sOutputHashMode,
|
||||
sRecurseForDerivations,
|
||||
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
|
||||
sPrefix;
|
||||
sPrefix,
|
||||
sOutputSpecified;
|
||||
Symbol sDerivationNix;
|
||||
|
||||
/* If set, force copying files to the Nix store even if they
|
||||
|
@ -108,12 +126,56 @@ public:
|
|||
RootValue vCallFlake = nullptr;
|
||||
RootValue vImportedDrvToDerivation = nullptr;
|
||||
|
||||
/* Debugger */
|
||||
void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
|
||||
bool debugStop;
|
||||
bool debugQuit;
|
||||
int trylevel;
|
||||
std::list<DebugTrace> debugTraces;
|
||||
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
|
||||
const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
|
||||
{
|
||||
auto i = exprEnvs.find(&expr);
|
||||
if (i != exprEnvs.end())
|
||||
return i->second;
|
||||
else
|
||||
return std::shared_ptr<const StaticEnv>();;
|
||||
}
|
||||
|
||||
void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
|
||||
|
||||
template<class E>
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void debugThrow(E && error, const Env & env, const Expr & expr)
|
||||
{
|
||||
if (debugRepl)
|
||||
runDebugRepl(&error, env, expr);
|
||||
|
||||
throw std::move(error);
|
||||
}
|
||||
|
||||
template<class E>
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void debugThrowLastTrace(E && e)
|
||||
{
|
||||
// Call this in the situation where Expr and Env are inaccessible.
|
||||
// The debugger will start in the last context that's in the
|
||||
// DebugTrace stack.
|
||||
if (debugRepl && !debugTraces.empty()) {
|
||||
const DebugTrace & last = debugTraces.front();
|
||||
runDebugRepl(&e, last.env, last.expr);
|
||||
}
|
||||
|
||||
throw std::move(e);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
SrcToStore srcToStore;
|
||||
|
||||
/* A cache from path names to parse trees. */
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *> > > FileParseCache;
|
||||
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *>>> FileParseCache;
|
||||
#else
|
||||
typedef std::map<Path, Expr *> FileParseCache;
|
||||
#endif
|
||||
|
@ -121,7 +183,7 @@ private:
|
|||
|
||||
/* A cache from path names to values. */
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value> > > FileEvalCache;
|
||||
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value>>> FileEvalCache;
|
||||
#else
|
||||
typedef std::map<Path, Value> FileEvalCache;
|
||||
#endif
|
||||
|
@ -184,10 +246,10 @@ public:
|
|||
|
||||
/* Parse a Nix expression from the specified file. */
|
||||
Expr * parseExprFromFile(const Path & path);
|
||||
Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv);
|
||||
Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv);
|
||||
|
||||
/* Parse a Nix expression from the specified string. */
|
||||
Expr * parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv);
|
||||
Expr * parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
|
||||
Expr * parseExprFromString(std::string s, const Path & basePath);
|
||||
|
||||
Expr * parseStdin();
|
||||
|
@ -197,7 +259,7 @@ public:
|
|||
trivial (i.e. doesn't require arbitrary computation). */
|
||||
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
|
||||
|
||||
/* Like `cacheFile`, but with an already parsed expression. */
|
||||
/* Like `evalFile`, but with an already parsed expression. */
|
||||
void cacheFile(
|
||||
const Path & path,
|
||||
const Path & resolvedPath,
|
||||
|
@ -253,32 +315,123 @@ public:
|
|||
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
|
||||
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const std::string_view s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const std::string_view s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const std::string_view s2, const std::string_view s3) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalError(const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const char * s) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeError(const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const PosIdx, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const PosIdx, const nix::Suggestions&, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const char*, std::string_view, const Pos &, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwTypeErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwEvalErrorWithTrace(const char*, std::string_view, std::string_view, nix::PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||
[[gnu::noinline, gnu::noreturn]] void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||
// coerce-strings
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const std::string_view s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string_view s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const std::string_view s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const std::string_view s2, const std::string_view s3) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const char * s, const Value & v) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeErrorWithTrace(const PosIdx, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeErrorWithTrace(const PosIdx, const nix::Suggestions&, const char*, std::string_view, const nix::Symbol&, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeErrorWithTrace(const char*, std::string_view, const Pos &, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalErrorWithTrace(const char*, std::string_view, const PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalErrorWithTrace(const char*, std::string_view, std::string_view, nix::PosIdx, std::string_view) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||
|
||||
[[gnu::noinline]] void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
|
||||
[[gnu::noinline]] void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const;
|
||||
// origin/master
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const std::string & s2);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const std::string & s2,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const std::string & s2, const std::string & s3,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const char * s, const std::string & s2, const std::string & s3);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2,
|
||||
Env & env, Expr & expr);
|
||||
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s, const Value & v);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s, const Value & v,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2,
|
||||
Env & env, Expr & expr);
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwTypeError(const char * s, const Value & v,
|
||||
Env & env, Expr & expr);
|
||||
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1,
|
||||
Env & env, Expr & expr);
|
||||
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1,
|
||||
Env & env, Expr & expr);
|
||||
|
||||
[[gnu::noinline, gnu::noreturn]]
|
||||
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1,
|
||||
Env & env, Expr & expr);
|
||||
|
||||
[[gnu::noinline]]
|
||||
void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
|
||||
[[gnu::noinline]]
|
||||
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const;
|
||||
|
||||
public:
|
||||
/* Return true iff the value `v' denotes a derivation (i.e. a
|
||||
|
@ -314,7 +467,7 @@ public:
|
|||
Env & baseEnv;
|
||||
|
||||
/* The same, but used during parsing to resolve variables. */
|
||||
StaticEnv staticBaseEnv; // !!! should be private
|
||||
std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
|
||||
|
||||
private:
|
||||
|
||||
|
@ -355,7 +508,7 @@ private:
|
|||
friend struct ExprLet;
|
||||
|
||||
Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
|
||||
const PathView basePath, StaticEnv & staticEnv);
|
||||
const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv);
|
||||
|
||||
public:
|
||||
|
||||
|
@ -450,6 +603,16 @@ private:
|
|||
friend struct Value;
|
||||
};
|
||||
|
||||
struct DebugTraceStacker {
|
||||
DebugTraceStacker(EvalState & evalState, DebugTrace t);
|
||||
~DebugTraceStacker()
|
||||
{
|
||||
// assert(evalState.debugTraces.front() == trace);
|
||||
evalState.debugTraces.pop_front();
|
||||
}
|
||||
EvalState & evalState;
|
||||
DebugTrace trace;
|
||||
};
|
||||
|
||||
/* Return a string representing the type of the value `v'. */
|
||||
std::string_view showType(ValueType type);
|
||||
|
@ -534,6 +697,15 @@ struct EvalSettings : Config
|
|||
|
||||
Setting<bool> useEvalCache{this, true, "eval-cache",
|
||||
"Whether to use the flake evaluation cache."};
|
||||
|
||||
Setting<bool> ignoreExceptionsDuringTry{this, false, "ignore-try",
|
||||
R"(
|
||||
If set to true, ignore exceptions inside 'tryEval' calls when evaluating nix expressions in
|
||||
debug mode (using the --debugger flag). By default the debugger will pause on all exceptions.
|
||||
)"};
|
||||
|
||||
Setting<bool> traceVerbose{this, false, "trace-verbose",
|
||||
"Whether `builtins.traceVerbose` should trace its first argument when evaluated."};
|
||||
};
|
||||
|
||||
extern EvalSettings evalSettings;
|
||||
|
|
|
@ -31,7 +31,7 @@ static void writeTrustedList(const TrustedList & trustedList)
|
|||
|
||||
void ConfigFile::apply()
|
||||
{
|
||||
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix", "flake-registry"};
|
||||
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-prefix", "bash-prompt-suffix", "flake-registry"};
|
||||
|
||||
for (auto & [name, value] : settings) {
|
||||
|
||||
|
@ -50,13 +50,11 @@ void ConfigFile::apply()
|
|||
else
|
||||
assert(false);
|
||||
|
||||
if (!whitelist.count(baseName)) {
|
||||
auto trustedList = readTrustedList();
|
||||
|
||||
if (!whitelist.count(baseName) && !nix::fetchSettings.acceptFlakeConfig) {
|
||||
bool trusted = false;
|
||||
if (nix::fetchSettings.acceptFlakeConfig){
|
||||
trusted = true;
|
||||
} else if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
|
||||
auto trustedList = readTrustedList();
|
||||
auto tlname = get(trustedList, name);
|
||||
if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
|
||||
trusted = *saved;
|
||||
warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS);
|
||||
} else {
|
||||
|
@ -69,7 +67,6 @@ void ConfigFile::apply()
|
|||
writeTrustedList(trustedList);
|
||||
}
|
||||
}
|
||||
|
||||
if (!trusted) {
|
||||
warn("ignoring untrusted flake configuration setting '%s'", name);
|
||||
continue;
|
||||
|
|
|
@ -341,7 +341,6 @@ LockedFlake lockFlake(
|
|||
|
||||
debug("old lock file: %s", oldLockFile);
|
||||
|
||||
// FIXME: check whether all overrides are used.
|
||||
std::map<InputPath, FlakeInput> overrides;
|
||||
std::set<InputPath> overridesUsed, updatesUsed;
|
||||
|
||||
|
@ -384,6 +383,18 @@ LockedFlake lockFlake(
|
|||
}
|
||||
}
|
||||
|
||||
/* Check whether this input has overrides for a
|
||||
non-existent input. */
|
||||
for (auto [inputPath, inputOverride] : overrides) {
|
||||
auto inputPath2(inputPath);
|
||||
auto follow = inputPath2.back();
|
||||
inputPath2.pop_back();
|
||||
if (inputPath2 == inputPathPrefix && !flakeInputs.count(follow))
|
||||
warn(
|
||||
"input '%s' has an override for a non-existent input '%s'",
|
||||
printInputPath(inputPathPrefix), follow);
|
||||
}
|
||||
|
||||
/* Go over the flake inputs, resolve/fetch them if
|
||||
necessary (i.e. if they're new or the flakeref changed
|
||||
from what's in the lock file). */
|
||||
|
@ -513,6 +524,15 @@ LockedFlake lockFlake(
|
|||
if (!lockFlags.allowMutable && !input.ref->input.isLocked())
|
||||
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
||||
|
||||
/* Note: in case of an --override-input, we use
|
||||
the *original* ref (input2.ref) for the
|
||||
"original" field, rather than the
|
||||
override. This ensures that the override isn't
|
||||
nuked the next time we update the lock
|
||||
file. That is, overrides are sticky unless you
|
||||
use --no-write-lock-file. */
|
||||
auto ref = input2.ref ? *input2.ref : *input.ref;
|
||||
|
||||
if (input.isFlake) {
|
||||
Path localPath = parentPath;
|
||||
FlakeRef localRef = *input.ref;
|
||||
|
@ -524,15 +544,7 @@ LockedFlake lockFlake(
|
|||
|
||||
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
|
||||
|
||||
/* Note: in case of an --override-input, we use
|
||||
the *original* ref (input2.ref) for the
|
||||
"original" field, rather than the
|
||||
override. This ensures that the override isn't
|
||||
nuked the next time we update the lock
|
||||
file. That is, overrides are sticky unless you
|
||||
use --no-write-lock-file. */
|
||||
auto childNode = std::make_shared<LockedNode>(
|
||||
inputFlake.lockedRef, input2.ref ? *input2.ref : *input.ref);
|
||||
auto childNode = std::make_shared<LockedNode>(inputFlake.lockedRef, ref);
|
||||
|
||||
node->inputs.insert_or_assign(id, childNode);
|
||||
|
||||
|
@ -560,7 +572,7 @@ LockedFlake lockFlake(
|
|||
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
state, *input.ref, useRegistries, flakeCache);
|
||||
node->inputs.insert_or_assign(id,
|
||||
std::make_shared<LockedNode>(lockedRef, *input.ref, false));
|
||||
std::make_shared<LockedNode>(lockedRef, ref, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -723,6 +735,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
lockFlake(state, flakeRef,
|
||||
LockFlags {
|
||||
.updateLockFile = false,
|
||||
.writeLockFile = false,
|
||||
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
|
||||
.allowMutable = !evalSettings.pureEval,
|
||||
}),
|
||||
|
|
|
@ -176,7 +176,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
parsedURL.query.insert_or_assign("shallow", "1");
|
||||
|
||||
return std::make_pair(
|
||||
FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
|
||||
FlakeRef(Input::fromURL(parsedURL), getOr(parsedURL.query, "dir", "")),
|
||||
fragment);
|
||||
}
|
||||
|
||||
|
@ -189,7 +189,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
if (!hasPrefix(path, "/"))
|
||||
throw BadURL("flake reference '%s' is not an absolute path", url);
|
||||
auto query = decodeQuery(match[2]);
|
||||
path = canonPath(path + "/" + get(query, "dir").value_or(""));
|
||||
path = canonPath(path + "/" + getOr(query, "dir", ""));
|
||||
}
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
|
@ -208,7 +208,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
input.parent = baseDir;
|
||||
|
||||
return std::make_pair(
|
||||
FlakeRef(std::move(input), get(parsedURL.query, "dir").value_or("")),
|
||||
FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
|
||||
fragment);
|
||||
}
|
||||
}
|
||||
|
@ -238,4 +238,15 @@ std::pair<fetchers::Tree, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
|
|||
return {std::move(tree), FlakeRef(std::move(lockedInput), subdir)};
|
||||
}
|
||||
|
||||
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
|
||||
const std::string & url,
|
||||
const std::optional<Path> & baseDir,
|
||||
bool allowMissing,
|
||||
bool isFlake)
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec(url);
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake);
|
||||
return {std::move(flakeRef), fragment, outputsSpec};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "path-with-outputs.hh"
|
||||
|
||||
#include <variant>
|
||||
|
||||
|
@ -27,7 +28,7 @@ typedef std::string FlakeId;
|
|||
* object that fetcher generates (usually via
|
||||
* FlakeRef::fromAttrs(attrs) or parseFlakeRef(url) calls).
|
||||
*
|
||||
* The actual fetch not have been performed yet (i.e. a FlakeRef may
|
||||
* The actual fetch may not have been performed yet (i.e. a FlakeRef may
|
||||
* be lazy), but the fetcher can be invoked at any time via the
|
||||
* FlakeRef to ensure the store is populated with this input.
|
||||
*/
|
||||
|
@ -79,4 +80,11 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
||||
|
||||
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec(
|
||||
const std::string & url,
|
||||
const std::optional<Path> & baseDir = {},
|
||||
bool allowMissing = false,
|
||||
bool isFlake = true);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPat
|
|||
|
||||
outputName =
|
||||
selectedOutputs.empty()
|
||||
? get(drv.env, "outputName").value_or("out")
|
||||
? getOr(drv.env, "outputName", "out")
|
||||
: *selectedOutputs.begin();
|
||||
|
||||
auto i = drv.outputs.find(outputName);
|
||||
|
@ -132,23 +132,36 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
|
|||
} else
|
||||
outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
|
||||
}
|
||||
|
||||
if (!onlyOutputsToInstall || !attrs)
|
||||
return outputs;
|
||||
|
||||
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
|
||||
const Value * outTI = queryMeta("outputsToInstall");
|
||||
if (!outTI) return outputs;
|
||||
const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
|
||||
/* ^ this shows during `nix-env -i` right under the bad derivation */
|
||||
if (!outTI->isList()) throw errMsg;
|
||||
Outputs result;
|
||||
for (auto elem : outTI->listItems()) {
|
||||
if (elem->type() != nString) throw errMsg;
|
||||
auto out = outputs.find(elem->string.s);
|
||||
if (out == outputs.end()) throw errMsg;
|
||||
Bindings::iterator i;
|
||||
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) {
|
||||
Outputs result;
|
||||
auto out = outputs.find(queryOutputName());
|
||||
if (out == outputs.end())
|
||||
throw Error("derivation does not have output '%s'", queryOutputName());
|
||||
result.insert(*out);
|
||||
return result;
|
||||
}
|
||||
|
||||
else {
|
||||
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
|
||||
const Value * outTI = queryMeta("outputsToInstall");
|
||||
if (!outTI) return outputs;
|
||||
const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
|
||||
/* ^ this shows during `nix-env -i` right under the bad derivation */
|
||||
if (!outTI->isList()) throw errMsg;
|
||||
Outputs result;
|
||||
for (auto elem : outTI->listItems()) {
|
||||
if (elem->type() != nString) throw errMsg;
|
||||
auto out = outputs.find(elem->string.s);
|
||||
if (out == outputs.end()) throw errMsg;
|
||||
result.insert(*out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ public:
|
|||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
|
||||
typedef std::list<DrvInfo, traceable_allocator<DrvInfo>> DrvInfos;
|
||||
#else
|
||||
typedef std::list<DrvInfo> DrvInfos;
|
||||
#endif
|
||||
|
|
|
@ -198,7 +198,7 @@ or { return OR_KW; }
|
|||
(...|\$[^\{\"\\]|\\.|\$\\.)+ would have triggered.
|
||||
This is technically invalid, but we leave the problem to the
|
||||
parser who fails with exact location. */
|
||||
return STR;
|
||||
return EOF;
|
||||
}
|
||||
|
||||
\'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Displaying abstract syntax trees. */
|
||||
|
||||
static void showString(std::ostream & str, std::string_view s)
|
||||
|
@ -294,35 +292,46 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
|
|||
|
||||
/* Computing levels/displacements for variables. */
|
||||
|
||||
void Expr::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
void ExprInt::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
}
|
||||
|
||||
void ExprFloat::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprFloat::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
}
|
||||
|
||||
void ExprString::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprString::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
}
|
||||
|
||||
void ExprPath::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprPath::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
}
|
||||
|
||||
void ExprVar::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
/* Check whether the variable appears in the environment. If so,
|
||||
set its level and displacement. */
|
||||
const StaticEnv * curEnv;
|
||||
Level level;
|
||||
int withLevel = -1;
|
||||
for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) {
|
||||
for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up, level++) {
|
||||
if (curEnv->isWith) {
|
||||
if (withLevel == -1) withLevel = level;
|
||||
} else {
|
||||
|
@ -348,8 +357,11 @@ void ExprVar::bindVars(const EvalState & es, const StaticEnv & env)
|
|||
this->level = withLevel;
|
||||
}
|
||||
|
||||
void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
e->bindVars(es, env);
|
||||
if (def) def->bindVars(es, env);
|
||||
for (auto & i : attrPath)
|
||||
|
@ -357,64 +369,78 @@ void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env)
|
|||
i.expr->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprOpHasAttr::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
e->bindVars(es, env);
|
||||
for (auto & i : attrPath)
|
||||
if (!i.symbol)
|
||||
i.expr->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprAttrs::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
const StaticEnv * dynamicEnv = &env;
|
||||
StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
if (recursive) {
|
||||
dynamicEnv = &newEnv;
|
||||
auto newEnv = std::make_shared<StaticEnv>(false, env.get(), recursive ? attrs.size() : 0);
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs)
|
||||
newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
|
||||
// No need to sort newEnv since attrs is in sorted order.
|
||||
|
||||
for (auto & i : attrs)
|
||||
i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
|
||||
}
|
||||
|
||||
else
|
||||
for (auto & i : dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, newEnv);
|
||||
i.valueExpr->bindVars(es, newEnv);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (auto & i : attrs)
|
||||
i.second.e->bindVars(es, env);
|
||||
|
||||
for (auto & i : dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, *dynamicEnv);
|
||||
i.valueExpr->bindVars(es, *dynamicEnv);
|
||||
for (auto & i : dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, env);
|
||||
i.valueExpr->bindVars(es, env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExprList::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
for (auto & i : elems)
|
||||
i->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
StaticEnv newEnv(
|
||||
false, &env,
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
auto newEnv = std::make_shared<StaticEnv>(
|
||||
false, env.get(),
|
||||
(hasFormals() ? formals->formals.size() : 0) +
|
||||
(!arg ? 0 : 1));
|
||||
|
||||
Displacement displ = 0;
|
||||
|
||||
if (arg) newEnv.vars.emplace_back(arg, displ++);
|
||||
if (arg) newEnv->vars.emplace_back(arg, displ++);
|
||||
|
||||
if (hasFormals()) {
|
||||
for (auto & i : formals->formals)
|
||||
newEnv.vars.emplace_back(i.name, displ++);
|
||||
newEnv->vars.emplace_back(i.name, displ++);
|
||||
|
||||
newEnv.sort();
|
||||
newEnv->sort();
|
||||
|
||||
for (auto & i : formals->formals)
|
||||
if (i.def) i.def->bindVars(es, newEnv);
|
||||
|
@ -423,20 +449,26 @@ void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env)
|
|||
body->bindVars(es, newEnv);
|
||||
}
|
||||
|
||||
void ExprCall::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
fun->bindVars(es, env);
|
||||
for (auto e : args)
|
||||
e->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprLet::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
StaticEnv newEnv(false, &env, attrs->attrs.size());
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
auto newEnv = std::make_shared<StaticEnv>(false, env.get(), attrs->attrs.size());
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs->attrs)
|
||||
newEnv.vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
|
||||
// No need to sort newEnv since attrs->attrs is in sorted order.
|
||||
|
||||
|
@ -446,51 +478,71 @@ void ExprLet::bindVars(const EvalState & es, const StaticEnv & env)
|
|||
body->bindVars(es, newEnv);
|
||||
}
|
||||
|
||||
void ExprWith::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
/* Does this `with' have an enclosing `with'? If so, record its
|
||||
level so that `lookupVar' can look up variables in the previous
|
||||
`with' if this one doesn't contain the desired attribute. */
|
||||
const StaticEnv * curEnv;
|
||||
Level level;
|
||||
prevWith = 0;
|
||||
for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++)
|
||||
for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up, level++)
|
||||
if (curEnv->isWith) {
|
||||
prevWith = level;
|
||||
break;
|
||||
}
|
||||
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
attrs->bindVars(es, env);
|
||||
StaticEnv newEnv(true, &env);
|
||||
auto newEnv = std::make_shared<StaticEnv>(true, env.get());
|
||||
body->bindVars(es, newEnv);
|
||||
}
|
||||
|
||||
void ExprIf::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
cond->bindVars(es, env);
|
||||
then->bindVars(es, env);
|
||||
else_->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprAssert::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
cond->bindVars(es, env);
|
||||
body->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprOpNot::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprOpNot::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
e->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprConcatStrings::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
for (auto & i : *this->es)
|
||||
i.second->bindVars(es, env);
|
||||
}
|
||||
|
||||
void ExprPos::bindVars(const EvalState & es, const StaticEnv & env)
|
||||
void ExprPos::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ MakeError(UndefinedVarError, Error);
|
|||
MakeError(MissingArgumentError, EvalError);
|
||||
MakeError(RestrictedPathError, Error);
|
||||
|
||||
|
||||
/* Position objects. */
|
||||
|
||||
struct Pos
|
||||
|
@ -142,24 +141,25 @@ struct Expr
|
|||
{
|
||||
virtual ~Expr() { };
|
||||
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
|
||||
virtual void bindVars(const EvalState & es, const StaticEnv & env);
|
||||
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
|
||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||
virtual void setName(Symbol name);
|
||||
virtual PosIdx getPos() const { return noPos; }
|
||||
};
|
||||
|
||||
#define COMMON_METHODS \
|
||||
void show(const SymbolTable & symbols, std::ostream & str) const; \
|
||||
void eval(EvalState & state, Env & env, Value & v); \
|
||||
void bindVars(const EvalState & es, const StaticEnv & env);
|
||||
void show(const SymbolTable & symbols, std::ostream & str) const override; \
|
||||
void eval(EvalState & state, Env & env, Value & v) override; \
|
||||
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override;
|
||||
|
||||
struct ExprInt : Expr
|
||||
{
|
||||
NixInt n;
|
||||
Value v;
|
||||
ExprInt(NixInt n) : n(n) { v.mkInt(n); };
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
};
|
||||
|
||||
struct ExprFloat : Expr
|
||||
|
@ -167,8 +167,8 @@ struct ExprFloat : Expr
|
|||
NixFloat nf;
|
||||
Value v;
|
||||
ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); };
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
};
|
||||
|
||||
struct ExprString : Expr
|
||||
|
@ -176,8 +176,8 @@ struct ExprString : Expr
|
|||
std::string s;
|
||||
Value v;
|
||||
ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
};
|
||||
|
||||
struct ExprPath : Expr
|
||||
|
@ -185,8 +185,8 @@ struct ExprPath : Expr
|
|||
std::string s;
|
||||
Value v;
|
||||
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); };
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
};
|
||||
|
||||
typedef uint32_t Level;
|
||||
|
@ -212,8 +212,9 @@ struct ExprVar : Expr
|
|||
|
||||
ExprVar(Symbol name) : name(name) { };
|
||||
ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { };
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
};
|
||||
|
||||
struct ExprSelect : Expr
|
||||
|
@ -223,6 +224,7 @@ struct ExprSelect : Expr
|
|||
AttrPath attrPath;
|
||||
ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(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
|
||||
};
|
||||
|
||||
|
@ -231,6 +233,7 @@ struct ExprOpHasAttr : Expr
|
|||
Expr * e;
|
||||
AttrPath attrPath;
|
||||
ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { };
|
||||
PosIdx getPos() const override { return e->getPos(); }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -259,6 +262,7 @@ struct ExprAttrs : Expr
|
|||
DynamicAttrDefs dynamicAttrs;
|
||||
ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
|
||||
ExprAttrs() : recursive(false) { };
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -267,6 +271,11 @@ struct ExprList : Expr
|
|||
std::vector<Expr *> elems;
|
||||
ExprList() { };
|
||||
COMMON_METHODS
|
||||
|
||||
PosIdx getPos() const override
|
||||
{
|
||||
return elems.empty() ? noPos : elems.front()->getPos();
|
||||
}
|
||||
};
|
||||
|
||||
struct Formal
|
||||
|
@ -316,9 +325,10 @@ struct ExprLambda : Expr
|
|||
: pos(pos), formals(formals), body(body)
|
||||
{
|
||||
}
|
||||
void setName(Symbol name);
|
||||
void setName(Symbol name) override;
|
||||
std::string showNamePos(const EvalState & state) const;
|
||||
inline bool hasFormals() const { return formals != nullptr; }
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -330,6 +340,7 @@ struct ExprCall : Expr
|
|||
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
|
||||
: fun(fun), args(args), pos(pos)
|
||||
{ }
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -347,6 +358,7 @@ struct ExprWith : Expr
|
|||
Expr * attrs, * body;
|
||||
size_t prevWith;
|
||||
ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -355,6 +367,7 @@ struct ExprIf : Expr
|
|||
PosIdx pos;
|
||||
Expr * cond, * then, * else_;
|
||||
ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -363,6 +376,7 @@ struct ExprAssert : Expr
|
|||
PosIdx pos;
|
||||
Expr * cond, * body;
|
||||
ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -380,15 +394,16 @@ struct ExprOpNot : Expr
|
|||
Expr * e1, * e2; \
|
||||
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
|
||||
name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
|
||||
void show(const SymbolTable & symbols, std::ostream & str) const \
|
||||
void show(const SymbolTable & symbols, std::ostream & str) const override \
|
||||
{ \
|
||||
str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \
|
||||
} \
|
||||
void bindVars(const EvalState & es, const StaticEnv & env) \
|
||||
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
|
||||
{ \
|
||||
e1->bindVars(es, env); e2->bindVars(es, env); \
|
||||
} \
|
||||
void eval(EvalState & state, Env & env, Value & v); \
|
||||
void eval(EvalState & state, Env & env, Value & v) override; \
|
||||
PosIdx getPos() const override { return pos; } \
|
||||
};
|
||||
|
||||
MakeBinOp(ExprOpEq, "==")
|
||||
|
@ -403,9 +418,10 @@ struct ExprConcatStrings : Expr
|
|||
{
|
||||
PosIdx pos;
|
||||
bool forceString;
|
||||
std::vector<std::pair<PosIdx, Expr *> > * es;
|
||||
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *> > * es)
|
||||
std::vector<std::pair<PosIdx, Expr *>> * es;
|
||||
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es)
|
||||
: pos(pos), forceString(forceString), es(es) { };
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
@ -413,6 +429,7 @@ struct ExprPos : Expr
|
|||
{
|
||||
PosIdx pos;
|
||||
ExprPos(const PosIdx & pos) : pos(pos) { };
|
||||
PosIdx getPos() const override { return pos; }
|
||||
COMMON_METHODS
|
||||
};
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
|
|||
|
||||
|
||||
static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
|
||||
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > > & es)
|
||||
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> & es)
|
||||
{
|
||||
if (es.empty()) return new ExprString("");
|
||||
|
||||
|
@ -233,7 +233,7 @@ static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
|
|||
}
|
||||
|
||||
/* Strip spaces from each line. */
|
||||
auto * es2 = new std::vector<std::pair<PosIdx, Expr *> >;
|
||||
auto * es2 = new std::vector<std::pair<PosIdx, Expr *>>;
|
||||
atStartOfLine = true;
|
||||
size_t curDropped = 0;
|
||||
size_t n = es.size();
|
||||
|
@ -320,8 +320,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
|
|||
StringToken uri;
|
||||
StringToken str;
|
||||
std::vector<nix::AttrName> * attrNames;
|
||||
std::vector<std::pair<nix::PosIdx, nix::Expr *> > * string_parts;
|
||||
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, StringToken> > > * ind_string_parts;
|
||||
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
|
||||
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, StringToken>>> * ind_string_parts;
|
||||
}
|
||||
|
||||
%type <e> start expr expr_function expr_if expr_op
|
||||
|
@ -503,9 +503,9 @@ string_parts_interpolated
|
|||
: string_parts_interpolated STR
|
||||
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::string($2))); }
|
||||
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
|
||||
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
|
||||
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(makeCurPos(@1, data), $2); }
|
||||
| STR DOLLAR_CURLY expr '}' {
|
||||
$$ = new std::vector<std::pair<PosIdx, Expr *> >;
|
||||
$$ = new std::vector<std::pair<PosIdx, Expr *>>;
|
||||
$$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1)));
|
||||
$$->emplace_back(makeCurPos(@2, data), $3);
|
||||
}
|
||||
|
@ -520,6 +520,12 @@ path_start
|
|||
$$ = new ExprPath(path);
|
||||
}
|
||||
| HPATH {
|
||||
if (evalSettings.pureEval) {
|
||||
throw Error(
|
||||
"the path '%s' can not be resolved in pure mode",
|
||||
std::string_view($1.p, $1.l)
|
||||
);
|
||||
}
|
||||
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
|
||||
$$ = new ExprPath(path);
|
||||
}
|
||||
|
@ -528,7 +534,7 @@ path_start
|
|||
ind_string_parts
|
||||
: ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); }
|
||||
| ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
|
||||
| { $$ = new std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > >; }
|
||||
| { $$ = new std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>>; }
|
||||
;
|
||||
|
||||
binds
|
||||
|
@ -643,7 +649,7 @@ namespace nix {
|
|||
|
||||
|
||||
Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
|
||||
const PathView path, const PathView basePath, StaticEnv & staticEnv)
|
||||
const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv)
|
||||
{
|
||||
yyscan_t scanner;
|
||||
std::string file;
|
||||
|
@ -706,7 +712,7 @@ Expr * EvalState::parseExprFromFile(const Path & path)
|
|||
}
|
||||
|
||||
|
||||
Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv)
|
||||
Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv)
|
||||
{
|
||||
auto buffer = readFile(path);
|
||||
// readFile should have left some extra space for terminators
|
||||
|
@ -715,7 +721,7 @@ Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv)
|
|||
}
|
||||
|
||||
|
||||
Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv)
|
||||
Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
|
||||
{
|
||||
s.append("\0\0", 2);
|
||||
return parse(s.data(), s.size(), foString, "", basePath, staticEnv);
|
||||
|
@ -782,13 +788,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
|
|||
if (hasPrefix(path, "nix/"))
|
||||
return concatStrings(corepkgsPrefix, path.substr(4));
|
||||
|
||||
throw ThrownError({
|
||||
debugThrowLastTrace(ThrownError({
|
||||
.msg = hintfmt(evalSettings.pureEval
|
||||
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
||||
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
||||
path),
|
||||
.errPos = positions[pos]
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
auto [ctx, outputName] = decodeContext(*store, i);
|
||||
auto ctxS = store->printStorePath(ctx);
|
||||
if (!store->isValidPath(ctx))
|
||||
throw InvalidPathError(store->printStorePath(ctx));
|
||||
debugThrowLastTrace(InvalidPathError(store->printStorePath(ctx)));
|
||||
if (!outputName.empty() && ctx.isDerivation()) {
|
||||
drvs.push_back({ctx, {outputName}});
|
||||
} else {
|
||||
|
@ -57,9 +57,9 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
if (drvs.empty()) return {};
|
||||
|
||||
if (!evalSettings.enableImportFromDerivation)
|
||||
throw Error(
|
||||
debugThrowLastTrace(Error(
|
||||
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
|
||||
store->printStorePath(drvs.begin()->drvPath));
|
||||
store->printStorePath(drvs.begin()->drvPath)));
|
||||
|
||||
/* Build/substitute the context. */
|
||||
std::vector<DerivedPath> buildReqs;
|
||||
|
@ -68,14 +68,15 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
|||
|
||||
/* Get all the output paths corresponding to the placeholders we had */
|
||||
for (auto & [drvPath, outputs] : drvs) {
|
||||
auto outputPaths = store->queryDerivationOutputMap(drvPath);
|
||||
const auto outputPaths = store->queryDerivationOutputMap(drvPath);
|
||||
for (auto & outputName : outputs) {
|
||||
if (outputPaths.count(outputName) == 0)
|
||||
throw Error("derivation '%s' does not have an output named '%s'",
|
||||
store->printStorePath(drvPath), outputName);
|
||||
auto outputPath = get(outputPaths, outputName);
|
||||
if (!outputPath)
|
||||
debugThrowLastTrace(Error("derivation '%s' does not have an output named '%s'",
|
||||
store->printStorePath(drvPath), outputName));
|
||||
res.insert_or_assign(
|
||||
downstreamPlaceholder(*store, drvPath, outputName),
|
||||
store->printStorePath(outputPaths.at(outputName))
|
||||
store->printStorePath(*outputPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -207,11 +208,11 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
|
|||
Env * env = &state.allocEnv(vScope->attrs->size());
|
||||
env->up = &state.baseEnv;
|
||||
|
||||
StaticEnv staticEnv(false, &state.staticBaseEnv, vScope->attrs->size());
|
||||
auto staticEnv = std::make_shared<StaticEnv>(false, state.staticBaseEnv.get(), vScope->attrs->size());
|
||||
|
||||
unsigned int displ = 0;
|
||||
for (auto & attr : *vScope->attrs) {
|
||||
staticEnv.vars.emplace_back(attr.name, displ);
|
||||
staticEnv->vars.emplace_back(attr.name, displ);
|
||||
env->values[displ++] = attr.value;
|
||||
}
|
||||
|
||||
|
@ -310,17 +311,16 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
|
|||
|
||||
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!handle)
|
||||
throw EvalError("could not open '%1%': %2%", path, dlerror());
|
||||
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
|
||||
|
||||
dlerror();
|
||||
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
||||
if(!func) {
|
||||
char *message = dlerror();
|
||||
if (message)
|
||||
throw EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message);
|
||||
state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message));
|
||||
else
|
||||
throw EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected",
|
||||
sym, path);
|
||||
state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path));
|
||||
}
|
||||
|
||||
(func)(state, v);
|
||||
|
@ -335,12 +335,11 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.exec");
|
||||
auto elems = args[0]->listElems();
|
||||
auto count = args[0]->listSize();
|
||||
if (count == 0) {
|
||||
throw EvalError({
|
||||
if (count == 0)
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("at least one argument to 'exec' required"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
}));
|
||||
PathSet context;
|
||||
auto program = state.coerceToString(pos, *elems[0], context, false, false,
|
||||
"while evaluating the first element of the argument passed to builtins.exec").toOwned();
|
||||
|
@ -352,11 +351,11 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
try {
|
||||
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
|
||||
program, e.path),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
auto output = runProgram(program, true, commandArgs);
|
||||
|
@ -561,10 +560,10 @@ struct CompareValues
|
|||
if (v1->type() == nInt && v2->type() == nFloat)
|
||||
return v1->integer < v2->fpoint;
|
||||
if (v1->type() != v2->type())
|
||||
throw EvalError({
|
||||
.msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)),
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
."cannot compare %1% with %2%", showType(*v1), showType(*v2)));
|
||||
.errPos = std::nullopt,
|
||||
});
|
||||
}));
|
||||
switch (v1->type()) {
|
||||
case nInt:
|
||||
return v1->integer < v2->integer;
|
||||
|
@ -586,10 +585,10 @@ struct CompareValues
|
|||
}
|
||||
}
|
||||
default:
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("%scannot compare %s with %s", errorCtx, showType(*v1), showType(*v2)),
|
||||
.errPos = std::nullopt,
|
||||
});
|
||||
}));
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addTrace(std::nullopt, errorCtx);
|
||||
|
@ -600,7 +599,7 @@ struct CompareValues
|
|||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::list<Value *, gc_allocator<Value *> > ValueList;
|
||||
typedef std::list<Value *, gc_allocator<Value *>> ValueList;
|
||||
#else
|
||||
typedef std::list<Value *> ValueList;
|
||||
#endif
|
||||
|
@ -618,6 +617,12 @@ static Bindings::iterator getAttr(
|
|||
.msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], errorCtx),
|
||||
.errPos = state.positions[attrSet->pos],
|
||||
});
|
||||
// TODO XXX
|
||||
// Adding another trace for the function name to make it clear
|
||||
// which call received wrong arguments.
|
||||
//e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
|
||||
//state.debugThrowLastTrace(e);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -714,6 +719,41 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
|
|||
.fun = prim_genericClosure,
|
||||
});
|
||||
|
||||
|
||||
static RegisterPrimOp primop_break({
|
||||
.name = "break",
|
||||
.args = {"v"},
|
||||
.doc = R"(
|
||||
In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
|
||||
Otherwise, return the argument `v`.
|
||||
)",
|
||||
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
if (state.debugRepl && !state.debugTraces.empty()) {
|
||||
auto error = Error(ErrorInfo {
|
||||
.level = lvlInfo,
|
||||
.msg = hintfmt("breakpoint reached"),
|
||||
.errPos = state.positions[pos],
|
||||
});
|
||||
|
||||
auto & dt = state.debugTraces.front();
|
||||
state.runDebugRepl(&error, dt.env, dt.expr);
|
||||
|
||||
if (state.debugQuit) {
|
||||
// If the user elects to quit the repl, throw an exception.
|
||||
throw Error(ErrorInfo{
|
||||
.level = lvlInfo,
|
||||
.msg = hintfmt("quit the debugger"),
|
||||
.errPos = state.positions[noPos],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Return the value we were passed.
|
||||
v = *args[0];
|
||||
}
|
||||
});
|
||||
|
||||
static RegisterPrimOp primop_abort({
|
||||
.name = "abort",
|
||||
.args = {"s"},
|
||||
|
@ -725,7 +765,7 @@ static RegisterPrimOp primop_abort({
|
|||
PathSet context;
|
||||
auto s = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the error message passed to builtins.abort").toOwned();
|
||||
throw Abort("evaluation aborted with the following error message: '%1%'", s);
|
||||
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -744,7 +784,7 @@ static RegisterPrimOp primop_throw({
|
|||
PathSet context;
|
||||
auto s = state.coerceToString(pos, *args[0], context,
|
||||
"while evaluating the error message passed to builtin.throw").toOwned();
|
||||
throw ThrownError(s);
|
||||
state.debugThrowLastTrace(ThrownError(s));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -811,6 +851,18 @@ static RegisterPrimOp primop_floor({
|
|||
static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
auto attrs = state.buildBindings(2);
|
||||
|
||||
/* increment state.trylevel, and decrement it when this function returns. */
|
||||
MaintainCount trylevel(state.trylevel);
|
||||
|
||||
void (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
|
||||
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
|
||||
{
|
||||
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
|
||||
savedDebugRepl = state.debugRepl;
|
||||
state.debugRepl = nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
state.forceValue(*args[0], pos);
|
||||
attrs.insert(state.sValue, args[0]);
|
||||
|
@ -819,6 +871,11 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
|
|||
attrs.alloc(state.sValue).mkBool(false);
|
||||
attrs.alloc("success").mkBool(false);
|
||||
}
|
||||
|
||||
// restore the debugRepl pointer if we saved it earlier.
|
||||
if (savedDebugRepl)
|
||||
state.debugRepl = savedDebugRepl;
|
||||
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
|
@ -930,6 +987,15 @@ static RegisterPrimOp primop_trace({
|
|||
});
|
||||
|
||||
|
||||
/* Takes two arguments and evaluates to the second one. Used as the
|
||||
* builtins.traceVerbose implementation when --trace-verbose is not enabled
|
||||
*/
|
||||
static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[1], pos);
|
||||
v = *args[1];
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* Derivations
|
||||
*************************************************************/
|
||||
|
@ -995,37 +1061,37 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
|
||||
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
|
||||
else
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
auto handleOutputs = [&](const Strings & ss) {
|
||||
outputs.clear();
|
||||
for (auto & j : ss) {
|
||||
if (outputs.find(j) != outputs.end())
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("duplicate derivation output '%1%'", j),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
/* !!! Check whether j is a valid attribute
|
||||
name. */
|
||||
/* Derivations cannot be named ‘drv’, because
|
||||
then we'd have an attribute ‘drvPath’ in
|
||||
the resulting set. */
|
||||
if (j == "drv")
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid derivation output name 'drv'" ),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
outputs.insert(j);
|
||||
}
|
||||
if (outputs.empty())
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("derivation cannot have an empty set of outputs"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -1154,23 +1220,23 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
|
||||
/* Do we have all required attributes? */
|
||||
if (drv.builder == "")
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("required attribute 'builder' missing"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
|
||||
if (drv.platform == "")
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("required attribute 'system' missing"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
|
||||
/* Check whether the derivation name is valid. */
|
||||
if (isDerivation(drvName))
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
|
||||
if (outputHash) {
|
||||
/* Handle fixed-output derivations.
|
||||
|
@ -1178,10 +1244,10 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
Ignore `__contentAddressed` because fixed output derivations are
|
||||
already content addressed. */
|
||||
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
|
||||
.errPos = state.positions[posDrvName]
|
||||
});
|
||||
}));
|
||||
|
||||
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||
|
||||
|
@ -1241,8 +1307,13 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
|
|||
switch (hashModulo.kind) {
|
||||
case DrvHash::Kind::Regular:
|
||||
for (auto & i : outputs) {
|
||||
auto h = hashModulo.hashes.at(i);
|
||||
auto outPath = state.store->makeOutputPath(i, h, drvName);
|
||||
auto h = get(hashModulo.hashes, i);
|
||||
if (!h)
|
||||
throw AssertionError({
|
||||
.msg = hintfmt("derivation produced no hash for output '%s'", i),
|
||||
.errPos = state.positions[posDrvName],
|
||||
});
|
||||
auto outPath = state.store->makeOutputPath(i, *h, drvName);
|
||||
drv.env[i] = state.store->printStorePath(outPath);
|
||||
drv.outputs.insert_or_assign(
|
||||
i,
|
||||
|
@ -1344,10 +1415,10 @@ static RegisterPrimOp primop_toPath({
|
|||
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
{
|
||||
if (evalSettings.pureEval)
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
PathSet context;
|
||||
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath"));
|
||||
|
@ -1356,10 +1427,10 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
|
|||
e.g. nix-push does the right thing. */
|
||||
if (!state.store->isStorePath(path)) path = canonPath(path, true);
|
||||
if (!state.store->isInStore(path))
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("path '%1%' is not in the Nix store", path),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
auto path2 = state.store->toStorePath(path).first;
|
||||
if (!settings.readOnlyMode)
|
||||
state.store->ensurePath(path2);
|
||||
|
@ -1462,7 +1533,7 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
auto path = realisePath(state, pos, *args[0]);
|
||||
auto s = readFile(path);
|
||||
if (s.find((char) 0) != std::string::npos)
|
||||
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
|
||||
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
|
||||
StorePathSet refs;
|
||||
if (state.store->isInStore(path)) {
|
||||
try {
|
||||
|
@ -1509,13 +1580,12 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
auto rewrites = state.realiseContext(context);
|
||||
path = rewriteStrings(path, rewrites);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
searchPath.emplace_back(prefix, path);
|
||||
}
|
||||
|
||||
|
@ -1536,10 +1606,10 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
|
||||
std::optional<HashType> ht = parseHashType(type);
|
||||
if (!ht)
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("unknown hash type '%1%'", type),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
auto path = realisePath(state, pos, *args[1]);
|
||||
|
||||
|
@ -1776,13 +1846,13 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
|
|||
|
||||
for (auto path : context) {
|
||||
if (path.at(0) != '/')
|
||||
throw EvalError( {
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt(
|
||||
"in 'toFile': the file named '%1%' must not contain a reference "
|
||||
"to a derivation but contains (%2%)",
|
||||
name, path),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
refs.insert(state.store->parseStorePath(path));
|
||||
}
|
||||
|
||||
|
@ -1940,7 +2010,7 @@ static void addPath(
|
|||
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
|
||||
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs);
|
||||
if (expectedHash && expectedStorePath != dstPath)
|
||||
throw Error("store path mismatch in (possibly filtered) path added from '%s'", path);
|
||||
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
|
||||
state.allowAndSetStorePathString(dstPath, v);
|
||||
} else
|
||||
state.allowAndSetStorePathString(*expectedStorePath, v);
|
||||
|
@ -2037,16 +2107,16 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
else if (n == "sha256")
|
||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), htSHA256);
|
||||
else
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]),
|
||||
.errPos = state.positions[attr.pos]
|
||||
});
|
||||
}));
|
||||
}
|
||||
if (path.empty())
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
if (name.empty())
|
||||
name = baseNameOf(path);
|
||||
|
||||
|
@ -2405,10 +2475,10 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
|
|||
return;
|
||||
}
|
||||
if (!args[0]->isLambda())
|
||||
throw TypeError({
|
||||
state.debugThrowLastTrace(TypeError({
|
||||
.msg = hintfmt("'functionArgs' requires a function"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
if (!args[0]->lambda.fun->hasFormals()) {
|
||||
v.mkAttrs(&state.emptyBindings);
|
||||
|
@ -2496,7 +2566,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
|
|||
attrsSeen[attr.name].first++;
|
||||
} catch (TypeError & e) {
|
||||
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
|
||||
throw;
|
||||
state.debugThrowLastTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2583,10 +2653,10 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
|
|||
{
|
||||
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
|
||||
if (n < 0 || (unsigned int) n >= list.listSize())
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("list index %1% is out of bounds", n),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
state.forceValue(*list.listElems()[n], pos);
|
||||
v = *list.listElems()[n];
|
||||
}
|
||||
|
@ -2631,10 +2701,10 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
{
|
||||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
|
||||
if (args[0]->listSize() == 0)
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("'tail' called on an empty list"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
state.mkList(v, args[0]->listSize() - 1);
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||
|
@ -2881,10 +2951,10 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
|
|||
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
|
||||
|
||||
if (len < 0)
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("cannot create list of size %1%", len),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
state.mkList(v, len);
|
||||
|
||||
|
@ -3099,7 +3169,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
|
|||
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
|
||||
} catch (TypeError &e) {
|
||||
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
|
||||
throw;
|
||||
state.debugThrowLastTrace(e);
|
||||
}
|
||||
len += lists[n].listSize();
|
||||
}
|
||||
|
@ -3200,10 +3270,10 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
|
||||
NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
|
||||
if (f2 == 0)
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("division by zero"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
|
||||
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2);
|
||||
|
@ -3212,10 +3282,10 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
|
|||
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
|
||||
/* Avoid division overflow as it might raise SIGFPE. */
|
||||
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("overflow in integer division"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
v.mkInt(i1 / i2);
|
||||
}
|
||||
|
@ -3347,10 +3417,10 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
|
|||
auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
|
||||
|
||||
if (start < 0)
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("negative start position in 'substring'"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context);
|
||||
}
|
||||
|
@ -3398,10 +3468,10 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
|
|||
auto type = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
|
||||
std::optional<HashType> ht = parseHashType(type);
|
||||
if (!ht)
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(Error({
|
||||
.msg = hintfmt("unknown hash type '%1%'", type),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
PathSet context; // discarded
|
||||
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
|
||||
|
@ -3468,19 +3538,18 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
(v.listElems()[i] = state.allocValue())->mkString(match[i + 1].str());
|
||||
}
|
||||
|
||||
} catch (std::regex_error &e) {
|
||||
} catch (std::regex_error & e) {
|
||||
if (e.code() == std::regex_constants::error_space) {
|
||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
} else {
|
||||
throw EvalError({
|
||||
}));
|
||||
} else
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid regular expression '%s'", re),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3515,7 +3584,7 @@ static RegisterPrimOp primop_match({
|
|||
builtins.match "[[:space:]]+([[:upper:]]+)[[:space:]]+" " FOO "
|
||||
```
|
||||
|
||||
Evaluates to `[ "foo" ]`.
|
||||
Evaluates to `[ "FOO" ]`.
|
||||
)s",
|
||||
.fun = prim_match,
|
||||
});
|
||||
|
@ -3573,19 +3642,18 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
|||
|
||||
assert(idx == 2 * len + 1);
|
||||
|
||||
} catch (std::regex_error &e) {
|
||||
} catch (std::regex_error & e) {
|
||||
if (e.code() == std::regex_constants::error_space) {
|
||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
} else {
|
||||
throw EvalError({
|
||||
}));
|
||||
} else
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("invalid regular expression '%s'", re),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3618,7 +3686,7 @@ static RegisterPrimOp primop_split({
|
|||
Evaluates to `[ "" [ "a" null ] "b" [ null "c" ] "" ]`.
|
||||
|
||||
```nix
|
||||
builtins.split "([[:upper:]]+)" " FOO "
|
||||
builtins.split "([[:upper:]]+)" " FOO "
|
||||
```
|
||||
|
||||
Evaluates to `[ " " [ "FOO" ] " " ]`.
|
||||
|
@ -3661,10 +3729,10 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
|||
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
|
||||
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
|
||||
if (args[0]->listSize() != args[1]->listSize())
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
std::vector<std::string> from;
|
||||
from.reserve(args[0]->listSize());
|
||||
|
@ -3882,6 +3950,18 @@ void EvalState::createBaseEnv()
|
|||
addPrimOp("__exec", 1, prim_exec);
|
||||
}
|
||||
|
||||
addPrimOp({
|
||||
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
|
||||
.arity = 2,
|
||||
.name = "__traceVerbose",
|
||||
.args = { "e1", "e2" },
|
||||
.doc = R"(
|
||||
Evaluate *e1* and print its abstract syntax representation on standard
|
||||
error if `--trace-verbose` is enabled. Then return *e2*. This function
|
||||
is useful for debugging.
|
||||
)",
|
||||
});
|
||||
|
||||
/* Add a value containing the current Nix expression search path. */
|
||||
mkList(v, searchPath.size());
|
||||
int n = 0;
|
||||
|
@ -3917,7 +3997,7 @@ void EvalState::createBaseEnv()
|
|||
because attribute lookups expect it to be sorted. */
|
||||
baseEnv.values[0]->attrs->sort();
|
||||
|
||||
staticBaseEnv.sort();
|
||||
staticBaseEnv->sort();
|
||||
|
||||
/* Note: we have to initialize the 'derivation' constant *after*
|
||||
building baseEnv/staticBaseEnv because it uses 'builtins'. */
|
||||
|
|
|
@ -108,16 +108,16 @@ static void fetchTree(
|
|||
|
||||
if (auto aType = args[0]->attrs->get(state.sType)) {
|
||||
if (type)
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("unexpected attribute 'type'"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
|
||||
} else if (!type)
|
||||
throw Error({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
attrs.emplace("type", type.value());
|
||||
|
||||
|
@ -138,16 +138,16 @@ static void fetchTree(
|
|||
else if (attr.value->type() == nInt)
|
||||
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
|
||||
else
|
||||
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value));
|
||||
state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||
state.symbols[attr.name], showType(*attr.value)));
|
||||
}
|
||||
|
||||
if (!params.allowNameArgument)
|
||||
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
||||
throw Error({
|
||||
.msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
|
||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||
} else {
|
||||
|
@ -167,7 +167,7 @@ static void fetchTree(
|
|||
input = lookupInRegistries(state.store, input).first;
|
||||
|
||||
if (evalSettings.pureEval && !input.isLocked())
|
||||
throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]);
|
||||
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
|
||||
|
||||
auto [tree, input2] = input.fetch(state.store);
|
||||
|
||||
|
@ -204,17 +204,17 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
else if (n == "name")
|
||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
|
||||
else
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
|
||||
.errPos = state.positions[attr.pos]
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
if (!url)
|
||||
throw EvalError({
|
||||
state.debugThrowLastTrace(EvalError({
|
||||
.msg = hintfmt("'url' argument required"),
|
||||
.errPos = state.positions[pos]
|
||||
});
|
||||
}));
|
||||
} else
|
||||
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
|
||||
|
||||
|
@ -226,7 +226,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
name = baseNameOf(*url);
|
||||
|
||||
if (evalSettings.pureEval && !expectedHash)
|
||||
throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who);
|
||||
state.debugThrowLastTrace(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
|
||||
|
||||
// early exit if pinned and already in the store
|
||||
if (expectedHash && expectedHash->type == htSHA256) {
|
||||
|
@ -253,8 +253,8 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
? state.store->queryPathInfo(storePath)->narHash
|
||||
: hashFile(htSHA256, state.store->toRealPath(storePath));
|
||||
if (hash != *expectedHash)
|
||||
throw Error((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));
|
||||
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)));
|
||||
}
|
||||
|
||||
state.allowAndSetStorePathString(storePath, v);
|
||||
|
@ -362,6 +362,10 @@ static RegisterPrimOp primop_fetchGit({
|
|||
A Boolean parameter that specifies whether submodules should be
|
||||
checked out. Defaults to `false`.
|
||||
|
||||
- shallow\
|
||||
A Boolean parameter that specifies whether fetching a shallow clone
|
||||
is allowed. Defaults to `false`.
|
||||
|
||||
- allRefs\
|
||||
Whether to fetch all refs of the repository. With this argument being
|
||||
true, it's possible to load a `rev` from *any* `ref` (by default only
|
||||
|
|
68
src/libexpr/tests/json.cc
Normal file
68
src/libexpr/tests/json.cc
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "libexprtests.hh"
|
||||
#include "value-to-json.hh"
|
||||
|
||||
namespace nix {
|
||||
// Testing the conversion to JSON
|
||||
|
||||
class JSONValueTest : public LibExprTest {
|
||||
protected:
|
||||
std::string getJSONValue(Value& value) {
|
||||
std::stringstream ss;
|
||||
PathSet ps;
|
||||
printValueAsJSON(state, true, value, noPos, ss, ps);
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(JSONValueTest, null) {
|
||||
Value v;
|
||||
v.mkNull();
|
||||
ASSERT_EQ(getJSONValue(v), "null");
|
||||
}
|
||||
|
||||
TEST_F(JSONValueTest, BoolFalse) {
|
||||
Value v;
|
||||
v.mkBool(false);
|
||||
ASSERT_EQ(getJSONValue(v),"false");
|
||||
}
|
||||
|
||||
TEST_F(JSONValueTest, BoolTrue) {
|
||||
Value v;
|
||||
v.mkBool(true);
|
||||
ASSERT_EQ(getJSONValue(v), "true");
|
||||
}
|
||||
|
||||
TEST_F(JSONValueTest, IntPositive) {
|
||||
Value v;
|
||||
v.mkInt(100);
|
||||
ASSERT_EQ(getJSONValue(v), "100");
|
||||
}
|
||||
|
||||
TEST_F(JSONValueTest, IntNegative) {
|
||||
Value v;
|
||||
v.mkInt(-100);
|
||||
ASSERT_EQ(getJSONValue(v), "-100");
|
||||
}
|
||||
|
||||
TEST_F(JSONValueTest, String) {
|
||||
Value v;
|
||||
v.mkString("test");
|
||||
ASSERT_EQ(getJSONValue(v), "\"test\"");
|
||||
}
|
||||
|
||||
TEST_F(JSONValueTest, StringQuotes) {
|
||||
Value v;
|
||||
|
||||
v.mkString("test\"");
|
||||
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
|
||||
}
|
||||
|
||||
// The dummy store doesn't support writing files. Fails with this exception message:
|
||||
// C++ exception with description "error: operation 'addToStoreFromDump' is
|
||||
// not supported by store 'dummy'" thrown in the test body.
|
||||
TEST_F(JSONValueTest, DISABLED_Path) {
|
||||
Value v;
|
||||
v.mkPath("test");
|
||||
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
|
||||
}
|
||||
} /* namespace nix */
|
136
src/libexpr/tests/libexprtests.hh
Normal file
136
src/libexpr/tests/libexprtests.hh
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "value.hh"
|
||||
#include "nixexpr.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
|
||||
namespace nix {
|
||||
class LibExprTest : public ::testing::Test {
|
||||
public:
|
||||
static void SetUpTestSuite() {
|
||||
initGC();
|
||||
}
|
||||
|
||||
protected:
|
||||
LibExprTest()
|
||||
: store(openStore("dummy://"))
|
||||
, state({}, store)
|
||||
{
|
||||
}
|
||||
Value eval(std::string input, bool forceValue = true) {
|
||||
Value v;
|
||||
Expr * e = state.parseExprFromString(input, "");
|
||||
assert(e);
|
||||
state.eval(e, v);
|
||||
if (forceValue)
|
||||
state.forceValue(v, noPos);
|
||||
return v;
|
||||
}
|
||||
|
||||
Symbol createSymbol(const char * value) {
|
||||
return state.symbols.create(value);
|
||||
}
|
||||
|
||||
ref<Store> store;
|
||||
EvalState state;
|
||||
};
|
||||
|
||||
MATCHER(IsListType, "") {
|
||||
return arg != nList;
|
||||
}
|
||||
|
||||
MATCHER(IsList, "") {
|
||||
return arg.type() == nList;
|
||||
}
|
||||
|
||||
MATCHER(IsString, "") {
|
||||
return arg.type() == nString;
|
||||
}
|
||||
|
||||
MATCHER(IsNull, "") {
|
||||
return arg.type() == nNull;
|
||||
}
|
||||
|
||||
MATCHER(IsThunk, "") {
|
||||
return arg.type() == nThunk;
|
||||
}
|
||||
|
||||
MATCHER(IsAttrs, "") {
|
||||
return arg.type() == nAttrs;
|
||||
}
|
||||
|
||||
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s)) {
|
||||
if (arg.type() != nString) {
|
||||
return false;
|
||||
}
|
||||
return std::string_view(arg.string.s) == s;
|
||||
}
|
||||
|
||||
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) {
|
||||
if (arg.type() != nInt) {
|
||||
return false;
|
||||
}
|
||||
return arg.integer == v;
|
||||
}
|
||||
|
||||
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) {
|
||||
if (arg.type() != nFloat) {
|
||||
return false;
|
||||
}
|
||||
return arg.fpoint == v;
|
||||
}
|
||||
|
||||
MATCHER(IsTrue, "") {
|
||||
if (arg.type() != nBool) {
|
||||
return false;
|
||||
}
|
||||
return arg.boolean == true;
|
||||
}
|
||||
|
||||
MATCHER(IsFalse, "") {
|
||||
if (arg.type() != nBool) {
|
||||
return false;
|
||||
}
|
||||
return arg.boolean == false;
|
||||
}
|
||||
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n)) {
|
||||
if (arg.type() != nList) {
|
||||
*result_listener << "Expected list got " << arg.type();
|
||||
return false;
|
||||
} else if (arg.listSize() != (size_t)n) {
|
||||
*result_listener << "Expected as list of size " << n << " got " << arg.listSize();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) {
|
||||
if (arg.type() != nAttrs) {
|
||||
*result_listener << "Expexted set got " << arg.type();
|
||||
return false;
|
||||
} else if (arg.attrs->size() != (size_t)n) {
|
||||
*result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} /* namespace nix */
|
15
src/libexpr/tests/local.mk
Normal file
15
src/libexpr/tests/local.mk
Normal file
|
@ -0,0 +1,15 @@
|
|||
check: libexpr-tests_RUN
|
||||
|
||||
programs += libexpr-tests
|
||||
|
||||
libexpr-tests_DIR := $(d)
|
||||
|
||||
libexpr-tests_INSTALL_DIR :=
|
||||
|
||||
libexpr-tests_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
|
||||
|
||||
libexpr-tests_LIBS = libexpr libutil libstore libfetchers
|
||||
|
||||
libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock
|
839
src/libexpr/tests/primops.cc
Normal file
839
src/libexpr/tests/primops.cc
Normal file
|
@ -0,0 +1,839 @@
|
|||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "libexprtests.hh"
|
||||
|
||||
namespace nix {
|
||||
class CaptureLogger : public Logger
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
public:
|
||||
CaptureLogger() {}
|
||||
|
||||
std::string get() const {
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void log(Verbosity lvl, const FormatOrString & fs) override {
|
||||
oss << fs.s << std::endl;
|
||||
}
|
||||
|
||||
void logEI(const ErrorInfo & ei) override {
|
||||
showErrorInfo(oss, ei, loggerSettings.showTrace.get());
|
||||
}
|
||||
};
|
||||
|
||||
class CaptureLogging {
|
||||
Logger * oldLogger;
|
||||
std::unique_ptr<CaptureLogger> tempLogger;
|
||||
public:
|
||||
CaptureLogging() : tempLogger(std::make_unique<CaptureLogger>()) {
|
||||
oldLogger = logger;
|
||||
logger = tempLogger.get();
|
||||
}
|
||||
|
||||
~CaptureLogging() {
|
||||
logger = oldLogger;
|
||||
}
|
||||
|
||||
std::string get() const {
|
||||
return tempLogger->get();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Testing eval of PrimOp's
|
||||
class PrimOpTest : public LibExprTest {};
|
||||
|
||||
|
||||
TEST_F(PrimOpTest, throw) {
|
||||
ASSERT_THROW(eval("throw \"foo\""), ThrownError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, abort) {
|
||||
ASSERT_THROW(eval("abort \"abort\""), Abort);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, ceil) {
|
||||
auto v = eval("builtins.ceil 1.9");
|
||||
ASSERT_THAT(v, IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, floor) {
|
||||
auto v = eval("builtins.floor 1.9");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, tryEvalFailure) {
|
||||
auto v = eval("builtins.tryEval (throw \"\")");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
auto s = createSymbol("success");
|
||||
auto p = v.attrs->get(s);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_THAT(*p->value, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, tryEvalSuccess) {
|
||||
auto v = eval("builtins.tryEval 123");
|
||||
ASSERT_THAT(v, IsAttrs());
|
||||
auto s = createSymbol("success");
|
||||
auto p = v.attrs->get(s);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_THAT(*p->value, IsTrue());
|
||||
s = createSymbol("value");
|
||||
p = v.attrs->get(s);
|
||||
ASSERT_NE(p, nullptr);
|
||||
ASSERT_THAT(*p->value, IsIntEq(123));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, getEnv) {
|
||||
setenv("_NIX_UNIT_TEST_ENV_VALUE", "test value", 1);
|
||||
auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\"");
|
||||
ASSERT_THAT(v, IsStringEq("test value"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, seq) {
|
||||
ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, seqNotDeep) {
|
||||
auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }");
|
||||
ASSERT_THAT(v, IsAttrs());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, deepSeq) {
|
||||
ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, trace) {
|
||||
CaptureLogging l;
|
||||
auto v = eval("builtins.trace \"test string 123\" 123");
|
||||
ASSERT_THAT(v, IsIntEq(123));
|
||||
auto text = l.get();
|
||||
ASSERT_NE(text.find("test string 123"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, placeholder) {
|
||||
auto v = eval("builtins.placeholder \"out\"");
|
||||
ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, baseNameOf) {
|
||||
auto v = eval("builtins.baseNameOf /some/path");
|
||||
ASSERT_THAT(v, IsStringEq("path"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, dirOf) {
|
||||
auto v = eval("builtins.dirOf /some/path");
|
||||
ASSERT_THAT(v, IsPathEq("/some"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, attrValues) {
|
||||
auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }");
|
||||
ASSERT_THAT(v, IsListOfSize(2));
|
||||
ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
|
||||
ASSERT_THAT(*v.listElems()[1], IsStringEq("foo"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, getAttr) {
|
||||
auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }");
|
||||
ASSERT_THAT(v, IsStringEq("foo"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, getAttrNotFound) {
|
||||
// FIXME: TypeError is really bad here, also the error wording is worse
|
||||
// than on Nix <=2.3
|
||||
ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, unsafeGetAttrPos) {
|
||||
// The `y` attribute is at position
|
||||
const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }";
|
||||
auto v = eval(expr);
|
||||
ASSERT_THAT(v, IsAttrsOfSize(3));
|
||||
|
||||
auto file = v.attrs->find(createSymbol("file"));
|
||||
ASSERT_NE(file, nullptr);
|
||||
// FIXME: The file when running these tests is the input string?!?
|
||||
ASSERT_THAT(*file->value, IsStringEq(expr));
|
||||
|
||||
auto line = v.attrs->find(createSymbol("line"));
|
||||
ASSERT_NE(line, nullptr);
|
||||
ASSERT_THAT(*line->value, IsIntEq(1));
|
||||
|
||||
auto column = v.attrs->find(createSymbol("column"));
|
||||
ASSERT_NE(column, nullptr);
|
||||
ASSERT_THAT(*column->value, IsIntEq(33));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, hasAttr) {
|
||||
auto v = eval("builtins.hasAttr \"x\" { x = 1; }");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, hasAttrNotFound) {
|
||||
auto v = eval("builtins.hasAttr \"x\" { }");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, isAttrs) {
|
||||
auto v = eval("builtins.isAttrs {}");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, isAttrsFalse) {
|
||||
auto v = eval("builtins.isAttrs null");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, removeAttrs) {
|
||||
auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, removeAttrsRetains) {
|
||||
auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||
ASSERT_NE(v.attrs->find(createSymbol("y")), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, listToAttrsEmptyList) {
|
||||
auto v = eval("builtins.listToAttrs []");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||
ASSERT_EQ(v.type(), nAttrs);
|
||||
ASSERT_EQ(v.attrs->size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, listToAttrsNotFieldName) {
|
||||
ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, listToAttrs) {
|
||||
auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||
auto key = v.attrs->find(createSymbol("key"));
|
||||
ASSERT_NE(key, nullptr);
|
||||
ASSERT_THAT(*key->value, IsIntEq(123));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, intersectAttrs) {
|
||||
auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||
auto b = v.attrs->find(createSymbol("b"));
|
||||
ASSERT_NE(b, nullptr);
|
||||
ASSERT_THAT(*b->value, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, catAttrs) {
|
||||
auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]");
|
||||
ASSERT_THAT(v, IsListOfSize(2));
|
||||
ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
|
||||
ASSERT_THAT(*v.listElems()[1], IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, functionArgs) {
|
||||
auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
|
||||
auto x = v.attrs->find(createSymbol("x"));
|
||||
ASSERT_NE(x, nullptr);
|
||||
ASSERT_THAT(*x->value, IsFalse());
|
||||
|
||||
auto y = v.attrs->find(createSymbol("y"));
|
||||
ASSERT_NE(y, nullptr);
|
||||
ASSERT_THAT(*y->value, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, mapAttrs) {
|
||||
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
|
||||
auto a = v.attrs->find(createSymbol("a"));
|
||||
ASSERT_NE(a, nullptr);
|
||||
ASSERT_THAT(*a->value, IsThunk());
|
||||
state.forceValue(*a->value, noPos);
|
||||
ASSERT_THAT(*a->value, IsIntEq(10));
|
||||
|
||||
auto b = v.attrs->find(createSymbol("b"));
|
||||
ASSERT_NE(b, nullptr);
|
||||
ASSERT_THAT(*b->value, IsThunk());
|
||||
state.forceValue(*b->value, noPos);
|
||||
ASSERT_THAT(*b->value, IsIntEq(20));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, isList) {
|
||||
auto v = eval("builtins.isList []");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, isListFalse) {
|
||||
auto v = eval("builtins.isList null");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, elemtAt) {
|
||||
auto v = eval("builtins.elemAt [0 1 2 3] 3");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, elemtAtOutOfBounds) {
|
||||
ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, head) {
|
||||
auto v = eval("builtins.head [ 3 2 1 0 ]");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, headEmpty) {
|
||||
ASSERT_THROW(eval("builtins.head [ ]"), Error);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, headWrongType) {
|
||||
ASSERT_THROW(eval("builtins.head { }"), Error);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, tail) {
|
||||
auto v = eval("builtins.tail [ 3 2 1 0 ]");
|
||||
ASSERT_THAT(v, IsListOfSize(3));
|
||||
for (const auto [n, elem] : enumerate(v.listItems()))
|
||||
ASSERT_THAT(*elem, IsIntEq(2 - static_cast<int>(n)));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, tailEmpty) {
|
||||
ASSERT_THROW(eval("builtins.tail []"), Error);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, map) {
|
||||
auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]");
|
||||
ASSERT_THAT(v, IsListOfSize(3));
|
||||
auto elem = v.listElems()[0];
|
||||
ASSERT_THAT(*elem, IsThunk());
|
||||
state.forceValue(*elem, noPos);
|
||||
ASSERT_THAT(*elem, IsStringEq("foobar"));
|
||||
|
||||
elem = v.listElems()[1];
|
||||
ASSERT_THAT(*elem, IsThunk());
|
||||
state.forceValue(*elem, noPos);
|
||||
ASSERT_THAT(*elem, IsStringEq("foobla"));
|
||||
|
||||
elem = v.listElems()[2];
|
||||
ASSERT_THAT(*elem, IsThunk());
|
||||
state.forceValue(*elem, noPos);
|
||||
ASSERT_THAT(*elem, IsStringEq("fooabc"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, filter) {
|
||||
auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]");
|
||||
ASSERT_THAT(v, IsListOfSize(3));
|
||||
for (const auto elem : v.listItems())
|
||||
ASSERT_THAT(*elem, IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, elemTrue) {
|
||||
auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, elemFalse) {
|
||||
auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, concatLists) {
|
||||
auto v = eval("builtins.concatLists [[1 2] [3 4]]");
|
||||
ASSERT_THAT(v, IsListOfSize(4));
|
||||
for (const auto [i, elem] : enumerate(v.listItems()))
|
||||
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, length) {
|
||||
auto v = eval("builtins.length [ 1 2 3 ]");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, foldStrict) {
|
||||
auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]");
|
||||
ASSERT_THAT(v, IsIntEq(6));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, anyTrue) {
|
||||
auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, anyFalse) {
|
||||
auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, allTrue) {
|
||||
auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, allFalse) {
|
||||
auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, genList) {
|
||||
auto v = eval("builtins.genList (x: x + 1) 3");
|
||||
ASSERT_EQ(v.type(), nList);
|
||||
ASSERT_EQ(v.listSize(), 3);
|
||||
for (const auto [i, elem] : enumerate(v.listItems())) {
|
||||
ASSERT_THAT(*elem, IsThunk());
|
||||
state.forceValue(*elem, noPos);
|
||||
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i)+1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, sortLessThan) {
|
||||
auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]");
|
||||
ASSERT_EQ(v.type(), nList);
|
||||
ASSERT_EQ(v.listSize(), 6);
|
||||
|
||||
const std::vector<int> numbers = { 42, 77, 147, 249, 483, 526 };
|
||||
for (const auto [n, elem] : enumerate(v.listItems()))
|
||||
ASSERT_THAT(*elem, IsIntEq(numbers[n]));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, partition) {
|
||||
auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
|
||||
auto right = v.attrs->get(createSymbol("right"));
|
||||
ASSERT_NE(right, nullptr);
|
||||
ASSERT_THAT(*right->value, IsListOfSize(2));
|
||||
ASSERT_THAT(*right->value->listElems()[0], IsIntEq(23));
|
||||
ASSERT_THAT(*right->value->listElems()[1], IsIntEq(42));
|
||||
|
||||
auto wrong = v.attrs->get(createSymbol("wrong"));
|
||||
ASSERT_NE(wrong, nullptr);
|
||||
ASSERT_EQ(wrong->value->type(), nList);
|
||||
ASSERT_EQ(wrong->value->listSize(), 3);
|
||||
ASSERT_THAT(*wrong->value, IsListOfSize(3));
|
||||
ASSERT_THAT(*wrong->value->listElems()[0], IsIntEq(1));
|
||||
ASSERT_THAT(*wrong->value->listElems()[1], IsIntEq(9));
|
||||
ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, concatMap) {
|
||||
auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]");
|
||||
ASSERT_EQ(v.type(), nList);
|
||||
ASSERT_EQ(v.listSize(), 6);
|
||||
|
||||
const std::vector<int> numbers = { 1, 2, 0, 3, 4, 0 };
|
||||
for (const auto [n, elem] : enumerate(v.listItems()))
|
||||
ASSERT_THAT(*elem, IsIntEq(numbers[n]));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, addInt) {
|
||||
auto v = eval("builtins.add 3 5");
|
||||
ASSERT_THAT(v, IsIntEq(8));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, addFloat) {
|
||||
auto v = eval("builtins.add 3.0 5.0");
|
||||
ASSERT_THAT(v, IsFloatEq(8.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, addFloatToInt) {
|
||||
auto v = eval("builtins.add 3.0 5");
|
||||
ASSERT_THAT(v, IsFloatEq(8.0));
|
||||
|
||||
v = eval("builtins.add 3 5.0");
|
||||
ASSERT_THAT(v, IsFloatEq(8.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, subInt) {
|
||||
auto v = eval("builtins.sub 5 2");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, subFloat) {
|
||||
auto v = eval("builtins.sub 5.0 2.0");
|
||||
ASSERT_THAT(v, IsFloatEq(3.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, subFloatFromInt) {
|
||||
auto v = eval("builtins.sub 5.0 2");
|
||||
ASSERT_THAT(v, IsFloatEq(3.0));
|
||||
|
||||
v = eval("builtins.sub 4 2.0");
|
||||
ASSERT_THAT(v, IsFloatEq(2.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, mulInt) {
|
||||
auto v = eval("builtins.mul 3 5");
|
||||
ASSERT_THAT(v, IsIntEq(15));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, mulFloat) {
|
||||
auto v = eval("builtins.mul 3.0 5.0");
|
||||
ASSERT_THAT(v, IsFloatEq(15.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, mulFloatMixed) {
|
||||
auto v = eval("builtins.mul 3 5.0");
|
||||
ASSERT_THAT(v, IsFloatEq(15.0));
|
||||
|
||||
v = eval("builtins.mul 2.0 5");
|
||||
ASSERT_THAT(v, IsFloatEq(10.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, divInt) {
|
||||
auto v = eval("builtins.div 5 (-1)");
|
||||
ASSERT_THAT(v, IsIntEq(-5));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, divIntZero) {
|
||||
ASSERT_THROW(eval("builtins.div 5 0"), EvalError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, divFloat) {
|
||||
auto v = eval("builtins.div 5.0 (-1)");
|
||||
ASSERT_THAT(v, IsFloatEq(-5.0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, divFloatZero) {
|
||||
ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, bitOr) {
|
||||
auto v = eval("builtins.bitOr 1 2");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, bitXor) {
|
||||
auto v = eval("builtins.bitXor 3 2");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, lessThanFalse) {
|
||||
auto v = eval("builtins.lessThan 3 1");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, lessThanTrue) {
|
||||
auto v = eval("builtins.lessThan 1 3");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, toStringAttrsThrows) {
|
||||
ASSERT_THROW(eval("builtins.toString {}"), EvalError);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, toStringLambdaThrows) {
|
||||
ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError);
|
||||
}
|
||||
|
||||
class ToStringPrimOpTest :
|
||||
public PrimOpTest,
|
||||
public testing::WithParamInterface<std::tuple<std::string, std::string_view>>
|
||||
{};
|
||||
|
||||
TEST_P(ToStringPrimOpTest, toString) {
|
||||
const auto [input, output] = GetParam();
|
||||
auto v = eval(input);
|
||||
ASSERT_THAT(v, IsStringEq(output));
|
||||
}
|
||||
|
||||
#define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " input), std::string_view(output)))
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
toString,
|
||||
ToStringPrimOpTest,
|
||||
testing::Values(
|
||||
CASE(R"("foo")", "foo"),
|
||||
CASE(R"(1)", "1"),
|
||||
CASE(R"([1 2 3])", "1 2 3"),
|
||||
CASE(R"(.123)", "0.123000"),
|
||||
CASE(R"(true)", "1"),
|
||||
CASE(R"(false)", ""),
|
||||
CASE(R"(null)", ""),
|
||||
CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"),
|
||||
CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"),
|
||||
CASE(R"({ outPath = "foo"; })", "foo"),
|
||||
CASE(R"(./test)", "/test")
|
||||
)
|
||||
);
|
||||
#undef CASE
|
||||
|
||||
TEST_F(PrimOpTest, substring){
|
||||
auto v = eval("builtins.substring 0 3 \"nixos\"");
|
||||
ASSERT_THAT(v, IsStringEq("nix"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, substringSmallerString){
|
||||
auto v = eval("builtins.substring 0 3 \"n\"");
|
||||
ASSERT_THAT(v, IsStringEq("n"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, substringEmptyString){
|
||||
auto v = eval("builtins.substring 1 3 \"\"");
|
||||
ASSERT_THAT(v, IsStringEq(""));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, stringLength) {
|
||||
auto v = eval("builtins.stringLength \"123\"");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
TEST_F(PrimOpTest, hashStringMd5) {
|
||||
auto v = eval("builtins.hashString \"md5\" \"asdf\"");
|
||||
ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, hashStringSha1) {
|
||||
auto v = eval("builtins.hashString \"sha1\" \"asdf\"");
|
||||
ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, hashStringSha256) {
|
||||
auto v = eval("builtins.hashString \"sha256\" \"asdf\"");
|
||||
ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, hashStringSha512) {
|
||||
auto v = eval("builtins.hashString \"sha512\" \"asdf\"");
|
||||
ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, hashStringInvalidHashType) {
|
||||
ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, nixPath) {
|
||||
auto v = eval("builtins.nixPath");
|
||||
ASSERT_EQ(v.type(), nList);
|
||||
// We can't test much more as currently the EvalSettings are a global
|
||||
// that we can't easily swap / replace
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, langVersion) {
|
||||
auto v = eval("builtins.langVersion");
|
||||
ASSERT_EQ(v.type(), nInt);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, storeDir) {
|
||||
auto v = eval("builtins.storeDir");
|
||||
ASSERT_THAT(v, IsStringEq("/nix/store"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, nixVersion) {
|
||||
auto v = eval("builtins.nixVersion");
|
||||
ASSERT_THAT(v, IsStringEq(nixVersion));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, currentSystem) {
|
||||
auto v = eval("builtins.currentSystem");
|
||||
ASSERT_THAT(v, IsStringEq(settings.thisSystem.get()));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, derivation) {
|
||||
auto v = eval("derivation");
|
||||
ASSERT_EQ(v.type(), nFunction);
|
||||
ASSERT_TRUE(v.isLambda());
|
||||
ASSERT_NE(v.lambda.fun, nullptr);
|
||||
ASSERT_TRUE(v.lambda.fun->hasFormals());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, currentTime) {
|
||||
auto v = eval("builtins.currentTime");
|
||||
ASSERT_EQ(v.type(), nInt);
|
||||
ASSERT_TRUE(v.integer > 0);
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, splitVersion) {
|
||||
auto v = eval("builtins.splitVersion \"1.2.3git\"");
|
||||
ASSERT_THAT(v, IsListOfSize(4));
|
||||
|
||||
const std::vector<std::string_view> strings = { "1", "2", "3", "git" };
|
||||
for (const auto [n, p] : enumerate(v.listItems()))
|
||||
ASSERT_THAT(*p, IsStringEq(strings[n]));
|
||||
}
|
||||
|
||||
class CompareVersionsPrimOpTest :
|
||||
public PrimOpTest,
|
||||
public testing::WithParamInterface<std::tuple<std::string, const int>>
|
||||
{};
|
||||
|
||||
TEST_P(CompareVersionsPrimOpTest, compareVersions) {
|
||||
auto [expression, expectation] = GetParam();
|
||||
auto v = eval(expression);
|
||||
ASSERT_THAT(v, IsIntEq(expectation));
|
||||
}
|
||||
|
||||
#define CASE(a, b, expected) (std::make_tuple("builtins.compareVersions \"" #a "\" \"" #b "\"", expected))
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
compareVersions,
|
||||
CompareVersionsPrimOpTest,
|
||||
testing::Values(
|
||||
// The first two are weird cases. Intuition tells they should
|
||||
// be the same but they aren't.
|
||||
CASE(1.0, 1.0.0, -1),
|
||||
CASE(1.0.0, 1.0, 1),
|
||||
// the following are from the nix-env manual:
|
||||
CASE(1.0, 2.3, -1),
|
||||
CASE(2.1, 2.3, -1),
|
||||
CASE(2.3, 2.3, 0),
|
||||
CASE(2.5, 2.3, 1),
|
||||
CASE(3.1, 2.3, 1),
|
||||
CASE(2.3.1, 2.3, 1),
|
||||
CASE(2.3.1, 2.3a, 1),
|
||||
CASE(2.3pre1, 2.3, -1),
|
||||
CASE(2.3pre3, 2.3pre12, -1),
|
||||
CASE(2.3a, 2.3c, -1),
|
||||
CASE(2.3pre1, 2.3c, -1),
|
||||
CASE(2.3pre1, 2.3q, -1)
|
||||
)
|
||||
);
|
||||
#undef CASE
|
||||
|
||||
|
||||
class ParseDrvNamePrimOpTest :
|
||||
public PrimOpTest,
|
||||
public testing::WithParamInterface<std::tuple<std::string, std::string_view, std::string_view>>
|
||||
{};
|
||||
|
||||
TEST_P(ParseDrvNamePrimOpTest, parseDrvName) {
|
||||
auto [input, expectedName, expectedVersion] = GetParam();
|
||||
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
|
||||
auto v = eval(expr);
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
|
||||
auto name = v.attrs->find(createSymbol("name"));
|
||||
ASSERT_TRUE(name);
|
||||
ASSERT_THAT(*name->value, IsStringEq(expectedName));
|
||||
|
||||
auto version = v.attrs->find(createSymbol("version"));
|
||||
ASSERT_TRUE(version);
|
||||
ASSERT_THAT(*version->value, IsStringEq(expectedVersion));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
parseDrvName,
|
||||
ParseDrvNamePrimOpTest,
|
||||
testing::Values(
|
||||
std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"),
|
||||
std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git")
|
||||
)
|
||||
);
|
||||
|
||||
TEST_F(PrimOpTest, replaceStrings) {
|
||||
// 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"));
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, split1) {
|
||||
// v = [ "" [ "a" ] "c" ]
|
||||
auto v = eval("builtins.split \"(a)b\" \"abc\"");
|
||||
ASSERT_THAT(v, IsListOfSize(3));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
|
||||
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[2], IsStringEq("c"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, split2) {
|
||||
// v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ]
|
||||
auto v = eval("builtins.split \"([ac])\" \"abc\"");
|
||||
ASSERT_THAT(v, IsListOfSize(5));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[1], IsListOfSize(1));
|
||||
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[3], IsListOfSize(1));
|
||||
ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsStringEq("c"));
|
||||
|
||||
ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, split3) {
|
||||
auto v = eval("builtins.split \"(a)|(c)\" \"abc\"");
|
||||
ASSERT_THAT(v, IsListOfSize(5));
|
||||
|
||||
// First list element
|
||||
ASSERT_THAT(*v.listElems()[0], IsStringEq(""));
|
||||
|
||||
// 2nd list element is a list [ "" null ]
|
||||
ASSERT_THAT(*v.listElems()[1], IsListOfSize(2));
|
||||
ASSERT_THAT(*v.listElems()[1]->listElems()[0], IsStringEq("a"));
|
||||
ASSERT_THAT(*v.listElems()[1]->listElems()[1], IsNull());
|
||||
|
||||
// 3rd element
|
||||
ASSERT_THAT(*v.listElems()[2], IsStringEq("b"));
|
||||
|
||||
// 4th element is a list: [ null "c" ]
|
||||
ASSERT_THAT(*v.listElems()[3], IsListOfSize(2));
|
||||
ASSERT_THAT(*v.listElems()[3]->listElems()[0], IsNull());
|
||||
ASSERT_THAT(*v.listElems()[3]->listElems()[1], IsStringEq("c"));
|
||||
|
||||
// 5th element is the empty string
|
||||
ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, split4) {
|
||||
auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \"");
|
||||
ASSERT_THAT(v, IsListOfSize(3));
|
||||
auto first = v.listElems()[0];
|
||||
auto second = v.listElems()[1];
|
||||
auto third = v.listElems()[2];
|
||||
|
||||
ASSERT_THAT(*first, IsStringEq(" "));
|
||||
|
||||
ASSERT_THAT(*second, IsListOfSize(1));
|
||||
ASSERT_THAT(*second->listElems()[0], IsStringEq("FOO"));
|
||||
|
||||
ASSERT_THAT(*third, IsStringEq(" "));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, match1) {
|
||||
auto v = eval("builtins.match \"ab\" \"abc\"");
|
||||
ASSERT_THAT(v, IsNull());
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, match2) {
|
||||
auto v = eval("builtins.match \"abc\" \"abc\"");
|
||||
ASSERT_THAT(v, IsListOfSize(0));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, match3) {
|
||||
auto v = eval("builtins.match \"a(b)(c)\" \"abc\"");
|
||||
ASSERT_THAT(v, IsListOfSize(2));
|
||||
ASSERT_THAT(*v.listElems()[0], IsStringEq("b"));
|
||||
ASSERT_THAT(*v.listElems()[1], IsStringEq("c"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, match4) {
|
||||
auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \"");
|
||||
ASSERT_THAT(v, IsListOfSize(1));
|
||||
ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO"));
|
||||
}
|
||||
|
||||
TEST_F(PrimOpTest, attrNames) {
|
||||
auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }");
|
||||
ASSERT_THAT(v, IsListOfSize(4));
|
||||
|
||||
// ensure that the list is sorted
|
||||
const std::vector<std::string_view> expected { "a", "x", "y", "z" };
|
||||
for (const auto [n, elem] : enumerate(v.listItems()))
|
||||
ASSERT_THAT(*elem, IsStringEq(expected[n]));
|
||||
}
|
||||
} /* namespace nix */
|
196
src/libexpr/tests/trivial.cc
Normal file
196
src/libexpr/tests/trivial.cc
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include "libexprtests.hh"
|
||||
|
||||
namespace nix {
|
||||
// Testing of trivial expressions
|
||||
class TrivialExpressionTest : public LibExprTest {};
|
||||
|
||||
TEST_F(TrivialExpressionTest, true) {
|
||||
auto v = eval("true");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, false) {
|
||||
auto v = eval("false");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, null) {
|
||||
auto v = eval("null");
|
||||
ASSERT_THAT(v, IsNull());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, 1) {
|
||||
auto v = eval("1");
|
||||
ASSERT_THAT(v, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, 1plus1) {
|
||||
auto v = eval("1+1");
|
||||
ASSERT_THAT(v, IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, minus1) {
|
||||
auto v = eval("-1");
|
||||
ASSERT_THAT(v, IsIntEq(-1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, 1minus1) {
|
||||
auto v = eval("1-1");
|
||||
ASSERT_THAT(v, IsIntEq(0));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, lambdaAdd) {
|
||||
auto v = eval("let add = a: b: a + b; in add 1 2");
|
||||
ASSERT_THAT(v, IsIntEq(3));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, list) {
|
||||
auto v = eval("[]");
|
||||
ASSERT_THAT(v, IsListOfSize(0));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, attrs) {
|
||||
auto v = eval("{}");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, float) {
|
||||
auto v = eval("1.234");
|
||||
ASSERT_THAT(v, IsFloatEq(1.234));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, updateAttrs) {
|
||||
auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||
auto a = v.attrs->find(createSymbol("a"));
|
||||
ASSERT_NE(a, nullptr);
|
||||
ASSERT_THAT(*a->value, IsIntEq(3));
|
||||
|
||||
auto b = v.attrs->find(createSymbol("b"));
|
||||
ASSERT_NE(b, nullptr);
|
||||
ASSERT_THAT(*b->value, IsIntEq(2));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, hasAttrOpFalse) {
|
||||
auto v = eval("{} ? a");
|
||||
ASSERT_THAT(v, IsFalse());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, hasAttrOpTrue) {
|
||||
auto v = eval("{ a = 123; } ? a");
|
||||
ASSERT_THAT(v, IsTrue());
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, withFound) {
|
||||
auto v = eval("with { a = 23; }; a");
|
||||
ASSERT_THAT(v, IsIntEq(23));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, withNotFound) {
|
||||
ASSERT_THROW(eval("with {}; a"), Error);
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, withOverride) {
|
||||
auto v = eval("with { a = 23; }; with { a = 42; }; a");
|
||||
ASSERT_THAT(v, IsIntEq(42));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, letOverWith) {
|
||||
auto v = eval("let a = 23; in with { a = 1; }; a");
|
||||
ASSERT_THAT(v, IsIntEq(23));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, multipleLet) {
|
||||
auto v = eval("let a = 23; in let a = 42; in a");
|
||||
ASSERT_THAT(v, IsIntEq(42));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, defaultFunctionArgs) {
|
||||
auto v = eval("({ a ? 123 }: a) {}");
|
||||
ASSERT_THAT(v, IsIntEq(123));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride) {
|
||||
auto v = eval("({ a ? 123 }: a) { a = 5; }");
|
||||
ASSERT_THAT(v, IsIntEq(5));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack) {
|
||||
auto v = eval("({ a ? 123 }@args: args) {}");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront) {
|
||||
auto v = eval("(args@{ a ? 123 }: args) {}");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(0));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, assertThrows) {
|
||||
ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error);
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, assertPassed) {
|
||||
auto v = eval("let x = arg: assert arg == 1; 123; in x 1");
|
||||
ASSERT_THAT(v, IsIntEq(123));
|
||||
}
|
||||
|
||||
class AttrSetMergeTrvialExpressionTest :
|
||||
public TrivialExpressionTest,
|
||||
public testing::WithParamInterface<const char*>
|
||||
{};
|
||||
|
||||
TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy) {
|
||||
// Usually Nix rejects duplicate keys in an attrset but it does allow
|
||||
// so if it is an attribute set that contains disjoint sets of keys.
|
||||
// The below is equivalent to `{a.b = 1; a.c = 2; }`.
|
||||
// The attribute set `a` will be a Thunk at first as the attribuets
|
||||
// have to be merged (or otherwise computed) and that is done in a lazy
|
||||
// manner.
|
||||
|
||||
auto expr = GetParam();
|
||||
auto v = eval(expr);
|
||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||
|
||||
auto a = v.attrs->find(createSymbol("a"));
|
||||
ASSERT_NE(a, nullptr);
|
||||
|
||||
ASSERT_THAT(*a->value, IsThunk());
|
||||
state.forceValue(*a->value, noPos);
|
||||
|
||||
ASSERT_THAT(*a->value, IsAttrsOfSize(2));
|
||||
|
||||
auto b = a->value->attrs->find(createSymbol("b"));
|
||||
ASSERT_NE(b, nullptr);
|
||||
ASSERT_THAT(*b->value, IsIntEq(1));
|
||||
|
||||
auto c = a->value->attrs->find(createSymbol("c"));
|
||||
ASSERT_NE(c, nullptr);
|
||||
ASSERT_THAT(*c->value, IsIntEq(2));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
attrsetMergeLazy,
|
||||
AttrSetMergeTrvialExpressionTest,
|
||||
testing::Values(
|
||||
"{ a.b = 1; a.c = 2; }",
|
||||
"{ a = { b = 1; }; a = { c = 2; }; }"
|
||||
)
|
||||
);
|
||||
|
||||
TEST_F(TrivialExpressionTest, functor) {
|
||||
auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5");
|
||||
ASSERT_THAT(v, IsIntEq(15));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, bindOr) {
|
||||
auto v = eval("{ or = 1; }");
|
||||
ASSERT_THAT(v, IsAttrsOfSize(1));
|
||||
auto b = v.attrs->find(createSymbol("or"));
|
||||
ASSERT_NE(b, nullptr);
|
||||
ASSERT_THAT(*b->value, IsIntEq(1));
|
||||
}
|
||||
|
||||
TEST_F(TrivialExpressionTest, orCantBeUsed) {
|
||||
ASSERT_THROW(eval("let or = 1; in or"), Error);
|
||||
}
|
||||
} /* namespace nix */
|
|
@ -10,7 +10,7 @@
|
|||
namespace nix {
|
||||
|
||||
void printValueAsJSON(EvalState & state, bool strict,
|
||||
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context)
|
||||
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
|
@ -32,7 +32,10 @@ void printValueAsJSON(EvalState & state, bool strict,
|
|||
break;
|
||||
|
||||
case nPath:
|
||||
out.write(state.copyPathToStore(context, v.path));
|
||||
if (copyToStore)
|
||||
out.write(state.copyPathToStore(context, v.path));
|
||||
else
|
||||
out.write(v.path);
|
||||
break;
|
||||
|
||||
case nNull:
|
||||
|
@ -54,10 +57,10 @@ void printValueAsJSON(EvalState & state, bool strict,
|
|||
for (auto & j : names) {
|
||||
Attr & a(*v.attrs->find(state.symbols.create(j)));
|
||||
auto placeholder(obj.placeholder(j));
|
||||
printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context);
|
||||
printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context, copyToStore);
|
||||
}
|
||||
} else
|
||||
printValueAsJSON(state, strict, *i->value, i->pos, out, context);
|
||||
printValueAsJSON(state, strict, *i->value, i->pos, out, context, copyToStore);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -65,13 +68,13 @@ void printValueAsJSON(EvalState & state, bool strict,
|
|||
auto list(out.list());
|
||||
for (auto elem : v.listItems()) {
|
||||
auto placeholder(list.placeholder());
|
||||
printValueAsJSON(state, strict, *elem, pos, placeholder, context);
|
||||
printValueAsJSON(state, strict, *elem, pos, placeholder, context, copyToStore);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case nExternal:
|
||||
v.external->printValueAsJSON(state, strict, out, context);
|
||||
v.external->printValueAsJSON(state, strict, out, context, copyToStore);
|
||||
break;
|
||||
|
||||
case nFloat:
|
||||
|
@ -85,21 +88,22 @@ void printValueAsJSON(EvalState & state, bool strict,
|
|||
.errPos = state.positions[v.determinePos(pos)]
|
||||
});
|
||||
e.addTrace(state.positions[pos], hintfmt("message for the trace"));
|
||||
state.debugThrowLastTrace(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void printValueAsJSON(EvalState & state, bool strict,
|
||||
Value & v, const PosIdx pos, std::ostream & str, PathSet & context)
|
||||
Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore)
|
||||
{
|
||||
JSONPlaceholder out(str);
|
||||
printValueAsJSON(state, strict, v, pos, out, context);
|
||||
printValueAsJSON(state, strict, v, pos, out, context, copyToStore);
|
||||
}
|
||||
|
||||
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
||||
JSONPlaceholder & out, PathSet & context) const
|
||||
JSONPlaceholder & out, PathSet & context, bool copyToStore) const
|
||||
{
|
||||
throw TypeError("cannot convert %1% to JSON", showType());
|
||||
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ namespace nix {
|
|||
class JSONPlaceholder;
|
||||
|
||||
void printValueAsJSON(EvalState & state, bool strict,
|
||||
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context);
|
||||
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore = true);
|
||||
|
||||
void printValueAsJSON(EvalState & state, bool strict,
|
||||
Value & v, const PosIdx pos, std::ostream & str, PathSet & context);
|
||||
Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true);
|
||||
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ class ExternalValueBase
|
|||
|
||||
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
|
||||
virtual void printValueAsJSON(EvalState & state, bool strict,
|
||||
JSONPlaceholder & out, PathSet & context) const;
|
||||
JSONPlaceholder & out, PathSet & context, bool copyToStore = true) const;
|
||||
|
||||
/* Print the value as XML. Defaults to unevaluated */
|
||||
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
|
@ -404,9 +404,9 @@ public:
|
|||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::vector<Value *, traceable_allocator<Value *> > ValueVector;
|
||||
typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *> > > ValueMap;
|
||||
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector> > > ValueVectorMap;
|
||||
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
|
||||
typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap;
|
||||
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>> ValueVectorMap;
|
||||
#else
|
||||
typedef std::vector<Value *> ValueVector;
|
||||
typedef std::map<Symbol, Value *> ValueMap;
|
||||
|
|
|
@ -70,7 +70,7 @@ struct FetchSettings : public Config
|
|||
Setting<bool> warnDirty{this, true, "warn-dirty",
|
||||
"Whether to warn about dirty Git/Mercurial trees."};
|
||||
|
||||
Setting<std::string> flakeRegistry{this, "https://github.com/NixOS/flake-registry/raw/master/flake-registry.json", "flake-registry",
|
||||
Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry",
|
||||
"Path or URI of the global flake registry."};
|
||||
|
||||
Setting<bool> useRegistries{this, true, "use-registries",
|
||||
|
|
|
@ -5,15 +5,20 @@
|
|||
#include "store-api.hh"
|
||||
#include "url-parts.hh"
|
||||
#include "pathlocks.hh"
|
||||
#include "util.hh"
|
||||
#include "git.hh"
|
||||
|
||||
#include "fetch-settings.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace nix::fetchers {
|
||||
namespace {
|
||||
|
||||
// Explicit initial branch of our bare repo to suppress warnings from new version of git.
|
||||
// The value itself does not matter, since we always fetch a specific revision or branch.
|
||||
|
@ -21,25 +26,228 @@ namespace nix::fetchers {
|
|||
// old version of git, which will ignore unrecognized `-c` options.
|
||||
const std::string gitInitialBranch = "__nix_dummy_branch";
|
||||
|
||||
static std::string getGitDir()
|
||||
bool isCacheFileWithinTtl(const time_t now, const struct stat & st)
|
||||
{
|
||||
auto gitDir = getEnv("GIT_DIR");
|
||||
if (!gitDir) {
|
||||
return ".git";
|
||||
return st.st_mtime + settings.tarballTtl > now;
|
||||
}
|
||||
|
||||
bool touchCacheFile(const Path& path, const time_t& touch_time)
|
||||
{
|
||||
struct timeval times[2];
|
||||
times[0].tv_sec = touch_time;
|
||||
times[0].tv_usec = 0;
|
||||
times[1].tv_sec = touch_time;
|
||||
times[1].tv_usec = 0;
|
||||
|
||||
return lutimes(path.c_str(), times) == 0;
|
||||
}
|
||||
|
||||
Path getCachePath(std::string key)
|
||||
{
|
||||
return getCacheDir() + "/nix/gitv3/" +
|
||||
hashString(htSHA256, key).to_string(Base32, false);
|
||||
}
|
||||
|
||||
// Returns the name of the HEAD branch.
|
||||
//
|
||||
// Returns the head branch name as reported by git ls-remote --symref, e.g., if
|
||||
// ls-remote returns the output below, "main" is returned based on the ref line.
|
||||
//
|
||||
// ref: refs/heads/main HEAD
|
||||
// ...
|
||||
std::optional<std::string> readHead(const Path & path)
|
||||
{
|
||||
auto [exit_code, output] = runProgram(RunOptions {
|
||||
.program = "git",
|
||||
.args = {"ls-remote", "--symref", path},
|
||||
});
|
||||
if (exit_code != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *gitDir;
|
||||
|
||||
std::string_view line = output;
|
||||
line = line.substr(0, line.find("\n"));
|
||||
if (const auto parseResult = git::parseLsRemoteLine(line)) {
|
||||
switch (parseResult->kind) {
|
||||
case git::LsRemoteRefLine::Kind::Symbolic:
|
||||
debug("resolved HEAD ref '%s' for repo '%s'", parseResult->target, path);
|
||||
break;
|
||||
case git::LsRemoteRefLine::Kind::Object:
|
||||
debug("resolved HEAD rev '%s' for repo '%s'", parseResult->target, path);
|
||||
break;
|
||||
}
|
||||
return parseResult->target;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::string readHead(const Path & path)
|
||||
// Persist the HEAD ref from the remote repo in the local cached repo.
|
||||
bool storeCachedHead(const std::string& actualUrl, const std::string& headRef)
|
||||
{
|
||||
return chomp(runProgram("git", true, { "-C", path, "--git-dir", ".git", "rev-parse", "--abbrev-ref", "HEAD" }));
|
||||
Path cacheDir = getCachePath(actualUrl);
|
||||
auto gitDir = ".";
|
||||
try {
|
||||
runProgram("git", true, { "-C", cacheDir, "--git-dir", gitDir, "symbolic-ref", "--", "HEAD", headRef });
|
||||
} catch (ExecError &e) {
|
||||
if (!WIFEXITED(e.status)) throw;
|
||||
return false;
|
||||
}
|
||||
/* No need to touch refs/HEAD, because `git symbolic-ref` updates the mtime. */
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isNotDotGitDirectory(const Path & path)
|
||||
std::optional<std::string> readHeadCached(const std::string& actualUrl)
|
||||
{
|
||||
// Create a cache path to store the branch of the HEAD ref. Append something
|
||||
// in front of the URL to prevent collision with the repository itself.
|
||||
Path cacheDir = getCachePath(actualUrl);
|
||||
Path headRefFile = cacheDir + "/HEAD";
|
||||
|
||||
time_t now = time(0);
|
||||
struct stat st;
|
||||
std::optional<std::string> cachedRef;
|
||||
if (stat(headRefFile.c_str(), &st) == 0) {
|
||||
cachedRef = readHead(cacheDir);
|
||||
if (cachedRef != std::nullopt &&
|
||||
*cachedRef != gitInitialBranch &&
|
||||
isCacheFileWithinTtl(now, st)) {
|
||||
debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl);
|
||||
return cachedRef;
|
||||
}
|
||||
}
|
||||
|
||||
auto ref = readHead(actualUrl);
|
||||
if (ref) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
if (cachedRef) {
|
||||
// If the cached git ref is expired in fetch() below, and the 'git fetch'
|
||||
// fails, it falls back to continuing with the most recent version.
|
||||
// This function must behave the same way, so we return the expired
|
||||
// cached ref here.
|
||||
warn("could not get HEAD ref for repository '%s'; using expired cached ref '%s'", actualUrl, *cachedRef);
|
||||
return *cachedRef;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool isNotDotGitDirectory(const Path & path)
|
||||
{
|
||||
return baseNameOf(path) != ".git";
|
||||
}
|
||||
|
||||
struct WorkdirInfo
|
||||
{
|
||||
bool clean = false;
|
||||
bool hasHead = false;
|
||||
};
|
||||
|
||||
// Returns whether a git workdir is clean and has commits.
|
||||
WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir)
|
||||
{
|
||||
const bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
|
||||
std::string gitDir(".git");
|
||||
|
||||
auto env = getEnv();
|
||||
// Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
|
||||
// that way unknown errors can lead to a failure instead of continuing through the wrong code path
|
||||
env["LC_ALL"] = "C";
|
||||
|
||||
/* Check whether HEAD points to something that looks like a commit,
|
||||
since that is the refrence we want to use later on. */
|
||||
auto result = runProgram(RunOptions {
|
||||
.program = "git",
|
||||
.args = { "-C", workdir, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
|
||||
.environment = env,
|
||||
.mergeStderrToStdout = true
|
||||
});
|
||||
auto exitCode = WEXITSTATUS(result.first);
|
||||
auto errorMessage = result.second;
|
||||
|
||||
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
|
||||
throw Error("'%s' is not a Git repository", workdir);
|
||||
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
|
||||
// indicates that the repo does not have any commits
|
||||
// we want to proceed and will consider it dirty later
|
||||
} else if (exitCode != 0) {
|
||||
// any other errors should lead to a failure
|
||||
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", workdir, exitCode, errorMessage);
|
||||
}
|
||||
|
||||
bool clean = false;
|
||||
bool hasHead = exitCode == 0;
|
||||
|
||||
try {
|
||||
if (hasHead) {
|
||||
// Using git diff is preferrable over lower-level operations here,
|
||||
// because its conceptually simpler and we only need the exit code anyways.
|
||||
auto gitDiffOpts = Strings({ "-C", workdir, "--git-dir", gitDir, "diff", "HEAD", "--quiet"});
|
||||
if (!submodules) {
|
||||
// Changes in submodules should only make the tree dirty
|
||||
// when those submodules will be copied as well.
|
||||
gitDiffOpts.emplace_back("--ignore-submodules");
|
||||
}
|
||||
gitDiffOpts.emplace_back("--");
|
||||
runProgram("git", true, gitDiffOpts);
|
||||
|
||||
clean = true;
|
||||
}
|
||||
} catch (ExecError & e) {
|
||||
if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw;
|
||||
}
|
||||
|
||||
return WorkdirInfo { .clean = clean, .hasHead = hasHead };
|
||||
}
|
||||
|
||||
std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, const Path & workdir, const WorkdirInfo & workdirInfo)
|
||||
{
|
||||
const bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
|
||||
auto gitDir = ".git";
|
||||
|
||||
if (!fetchSettings.allowDirty)
|
||||
throw Error("Git tree '%s' is dirty", workdir);
|
||||
|
||||
if (fetchSettings.warnDirty)
|
||||
warn("Git tree '%s' is dirty", workdir);
|
||||
|
||||
auto gitOpts = Strings({ "-C", workdir, "--git-dir", gitDir, "ls-files", "-z" });
|
||||
if (submodules)
|
||||
gitOpts.emplace_back("--recurse-submodules");
|
||||
|
||||
auto files = tokenizeString<std::set<std::string>>(
|
||||
runProgram("git", true, gitOpts), "\0"s);
|
||||
|
||||
Path actualPath(absPath(workdir));
|
||||
|
||||
PathFilter filter = [&](const Path & p) -> bool {
|
||||
assert(hasPrefix(p, actualPath));
|
||||
std::string file(p, actualPath.size() + 1);
|
||||
|
||||
auto st = lstat(p);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
auto prefix = file + "/";
|
||||
auto i = files.lower_bound(prefix);
|
||||
return i != files.end() && hasPrefix(*i, prefix);
|
||||
}
|
||||
|
||||
return files.count(file);
|
||||
};
|
||||
|
||||
auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||
|
||||
// FIXME: maybe we should use the timestamp of the last
|
||||
// modified dirty file?
|
||||
input.attrs.insert_or_assign(
|
||||
"lastModified",
|
||||
workdirInfo.hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
|
||||
|
||||
return {std::move(storePath), input};
|
||||
}
|
||||
} // end namespace
|
||||
|
||||
struct GitInputScheme : InputScheme
|
||||
{
|
||||
std::optional<Input> inputFromURL(const ParsedURL & url) override
|
||||
|
@ -159,10 +367,10 @@ struct GitInputScheme : InputScheme
|
|||
{
|
||||
auto sourcePath = getSourcePath(input);
|
||||
assert(sourcePath);
|
||||
auto gitDir = getGitDir();
|
||||
auto gitDir = ".git";
|
||||
|
||||
runProgram("git", true,
|
||||
{ "-C", *sourcePath, "--git-dir", gitDir, "add", "--force", "--intent-to-add", "--", std::string(file) });
|
||||
{ "-C", *sourcePath, "--git-dir", gitDir, "add", "--intent-to-add", "--", std::string(file) });
|
||||
|
||||
if (commitMsg)
|
||||
runProgram("git", true,
|
||||
|
@ -185,7 +393,7 @@ struct GitInputScheme : InputScheme
|
|||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||
{
|
||||
Input input(_input);
|
||||
auto gitDir = getGitDir();
|
||||
auto gitDir = ".git";
|
||||
|
||||
std::string name = input.getName();
|
||||
|
||||
|
@ -234,123 +442,50 @@ struct GitInputScheme : InputScheme
|
|||
auto [isLocal, actualUrl_] = getActualUrl(input);
|
||||
auto actualUrl = actualUrl_; // work around clang bug
|
||||
|
||||
// If this is a local directory and no ref or revision is
|
||||
// given, then allow the use of an unclean working tree.
|
||||
/* If this is a local directory and no ref or revision is given,
|
||||
allow fetching directly from a dirty workdir. */
|
||||
if (!input.getRef() && !input.getRev() && isLocal) {
|
||||
bool clean = false;
|
||||
|
||||
auto env = getEnv();
|
||||
// Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
|
||||
// that way unknown errors can lead to a failure instead of continuing through the wrong code path
|
||||
env["LC_ALL"] = "C";
|
||||
|
||||
/* Check whether HEAD points to something that looks like a commit,
|
||||
since that is the refrence we want to use later on. */
|
||||
auto result = runProgram(RunOptions {
|
||||
.program = "git",
|
||||
.args = { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
|
||||
.environment = env,
|
||||
.mergeStderrToStdout = true
|
||||
});
|
||||
auto exitCode = WEXITSTATUS(result.first);
|
||||
auto errorMessage = result.second;
|
||||
|
||||
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
|
||||
throw Error("'%s' is not a Git repository", actualUrl);
|
||||
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
|
||||
// indicates that the repo does not have any commits
|
||||
// we want to proceed and will consider it dirty later
|
||||
} else if (exitCode != 0) {
|
||||
// any other errors should lead to a failure
|
||||
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage);
|
||||
}
|
||||
|
||||
bool hasHead = exitCode == 0;
|
||||
try {
|
||||
if (hasHead) {
|
||||
// Using git diff is preferrable over lower-level operations here,
|
||||
// because its conceptually simpler and we only need the exit code anyways.
|
||||
auto gitDiffOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "diff", "HEAD", "--quiet"});
|
||||
if (!submodules) {
|
||||
// Changes in submodules should only make the tree dirty
|
||||
// when those submodules will be copied as well.
|
||||
gitDiffOpts.emplace_back("--ignore-submodules");
|
||||
}
|
||||
gitDiffOpts.emplace_back("--");
|
||||
runProgram("git", true, gitDiffOpts);
|
||||
|
||||
clean = true;
|
||||
}
|
||||
} catch (ExecError & e) {
|
||||
if (!WIFEXITED(e.status) || WEXITSTATUS(e.status) != 1) throw;
|
||||
}
|
||||
|
||||
if (!clean) {
|
||||
|
||||
/* This is an unclean working tree. So copy all tracked files. */
|
||||
|
||||
if (!fetchSettings.allowDirty)
|
||||
throw Error("Git tree '%s' is dirty", actualUrl);
|
||||
|
||||
if (fetchSettings.warnDirty)
|
||||
warn("Git tree '%s' is dirty", actualUrl);
|
||||
|
||||
auto gitOpts = Strings({ "-C", actualUrl, "--git-dir", gitDir, "ls-files", "-z" });
|
||||
if (submodules)
|
||||
gitOpts.emplace_back("--recurse-submodules");
|
||||
|
||||
auto files = tokenizeString<std::set<std::string>>(
|
||||
runProgram("git", true, gitOpts), "\0"s);
|
||||
|
||||
Path actualPath(absPath(actualUrl));
|
||||
|
||||
PathFilter filter = [&](const Path & p) -> bool {
|
||||
assert(hasPrefix(p, actualPath));
|
||||
std::string file(p, actualPath.size() + 1);
|
||||
|
||||
auto st = lstat(p);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
auto prefix = file + "/";
|
||||
auto i = files.lower_bound(prefix);
|
||||
return i != files.end() && hasPrefix(*i, prefix);
|
||||
}
|
||||
|
||||
return files.count(file);
|
||||
};
|
||||
|
||||
auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||
|
||||
// FIXME: maybe we should use the timestamp of the last
|
||||
// modified dirty file?
|
||||
input.attrs.insert_or_assign(
|
||||
"lastModified",
|
||||
hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
|
||||
|
||||
return {std::move(storePath), input};
|
||||
auto workdirInfo = getWorkdirInfo(input, actualUrl);
|
||||
if (!workdirInfo.clean) {
|
||||
return fetchFromWorkdir(store, input, actualUrl, workdirInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!input.getRef()) input.attrs.insert_or_assign("ref", isLocal ? readHead(actualUrl) : "master");
|
||||
|
||||
Attrs unlockedAttrs({
|
||||
{"type", cacheType},
|
||||
{"name", name},
|
||||
{"url", actualUrl},
|
||||
{"ref", *input.getRef()},
|
||||
});
|
||||
|
||||
Path repoDir;
|
||||
|
||||
if (isLocal) {
|
||||
if (!input.getRef()) {
|
||||
auto head = readHead(actualUrl);
|
||||
if (!head) {
|
||||
warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl);
|
||||
head = "master";
|
||||
}
|
||||
input.attrs.insert_or_assign("ref", *head);
|
||||
unlockedAttrs.insert_or_assign("ref", *head);
|
||||
}
|
||||
|
||||
if (!input.getRev())
|
||||
input.attrs.insert_or_assign("rev",
|
||||
Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", *input.getRef() })), htSHA1).gitRev());
|
||||
|
||||
repoDir = actualUrl;
|
||||
|
||||
} else {
|
||||
const bool useHeadRef = !input.getRef();
|
||||
if (useHeadRef) {
|
||||
auto head = readHeadCached(actualUrl);
|
||||
if (!head) {
|
||||
warn("could not read HEAD ref from repo at '%s', using 'master'", actualUrl);
|
||||
head = "master";
|
||||
}
|
||||
input.attrs.insert_or_assign("ref", *head);
|
||||
unlockedAttrs.insert_or_assign("ref", *head);
|
||||
}
|
||||
|
||||
if (auto res = getCache()->lookup(store, unlockedAttrs)) {
|
||||
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
|
||||
|
@ -360,7 +495,7 @@ struct GitInputScheme : InputScheme
|
|||
}
|
||||
}
|
||||
|
||||
Path cacheDir = getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, actualUrl).to_string(Base32, false);
|
||||
Path cacheDir = getCachePath(actualUrl);
|
||||
repoDir = cacheDir;
|
||||
gitDir = ".";
|
||||
|
||||
|
@ -383,7 +518,7 @@ struct GitInputScheme : InputScheme
|
|||
repo. */
|
||||
if (input.getRev()) {
|
||||
try {
|
||||
runProgram("git", true, { "-C", repoDir, "cat-file", "-e", input.getRev()->gitRev() });
|
||||
runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "cat-file", "-e", input.getRev()->gitRev() });
|
||||
doFetch = false;
|
||||
} catch (ExecError & e) {
|
||||
if (WIFEXITED(e.status)) {
|
||||
|
@ -400,7 +535,7 @@ struct GitInputScheme : InputScheme
|
|||
git fetch to update the local ref to the remote ref. */
|
||||
struct stat st;
|
||||
doFetch = stat(localRefFile.c_str(), &st) != 0 ||
|
||||
(uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now;
|
||||
!isCacheFileWithinTtl(now, st);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,19 +553,16 @@ struct GitInputScheme : InputScheme
|
|||
: ref == "HEAD"
|
||||
? *ref
|
||||
: "refs/heads/" + *ref;
|
||||
runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) });
|
||||
runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) });
|
||||
} catch (Error & e) {
|
||||
if (!pathExists(localRefFile)) throw;
|
||||
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl);
|
||||
}
|
||||
|
||||
struct timeval times[2];
|
||||
times[0].tv_sec = now;
|
||||
times[0].tv_usec = 0;
|
||||
times[1].tv_sec = now;
|
||||
times[1].tv_usec = 0;
|
||||
|
||||
utimes(localRefFile.c_str(), times);
|
||||
if (!touchCacheFile(localRefFile, now))
|
||||
warn("could not update mtime for file '%s': %s", localRefFile, strerror(errno));
|
||||
if (useHeadRef && !storeCachedHead(actualUrl, *input.getRef()))
|
||||
warn("could not update cached head '%s' for '%s'", *input.getRef(), actualUrl);
|
||||
}
|
||||
|
||||
if (!input.getRev())
|
||||
|
@ -442,7 +574,7 @@ struct GitInputScheme : InputScheme
|
|||
bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-parse", "--is-shallow-repository" })) == "true";
|
||||
|
||||
if (isShallow && !shallow)
|
||||
throw Error("'%s' is a shallow Git repository, but a non-shallow repository is needed", actualUrl);
|
||||
throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified.", actualUrl);
|
||||
|
||||
// FIXME: check whether rev is an ancestor of ref.
|
||||
|
||||
|
@ -459,7 +591,7 @@ struct GitInputScheme : InputScheme
|
|||
|
||||
auto result = runProgram(RunOptions {
|
||||
.program = "git",
|
||||
.args = { "-C", repoDir, "cat-file", "commit", input.getRev()->gitRev() },
|
||||
.args = { "-C", repoDir, "--git-dir", gitDir, "cat-file", "commit", input.getRev()->gitRev() },
|
||||
.mergeStderrToStdout = true
|
||||
});
|
||||
if (WEXITSTATUS(result.first) == 128
|
||||
|
@ -498,7 +630,7 @@ struct GitInputScheme : InputScheme
|
|||
auto source = sinkToSource([&](Sink & sink) {
|
||||
runProgram2({
|
||||
.program = "git",
|
||||
.args = { "-C", repoDir, "archive", input.getRev()->gitRev() },
|
||||
.args = { "-C", repoDir, "--git-dir", gitDir, "archive", input.getRev()->gitRev() },
|
||||
.standardOut = &sink
|
||||
});
|
||||
});
|
||||
|
@ -508,7 +640,7 @@ struct GitInputScheme : InputScheme
|
|||
|
||||
auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||
|
||||
auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "log", "-1", "--format=%ct", "--no-show-signature", input.getRev()->gitRev() }));
|
||||
auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "log", "-1", "--format=%ct", "--no-show-signature", input.getRev()->gitRev() }));
|
||||
|
||||
Attrs infoAttrs({
|
||||
{"rev", input.getRev()->gitRev()},
|
||||
|
@ -517,7 +649,7 @@ struct GitInputScheme : InputScheme
|
|||
|
||||
if (!shallow)
|
||||
infoAttrs.insert_or_assign("revCount",
|
||||
std::stoull(runProgram("git", true, { "-C", repoDir, "rev-list", "--count", input.getRev()->gitRev() })));
|
||||
std::stoull(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--count", input.getRev()->gitRev() })));
|
||||
|
||||
if (!_input.getRev())
|
||||
getCache()->add(
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "store-api.hh"
|
||||
#include "types.hh"
|
||||
#include "url-parts.hh"
|
||||
|
||||
#include "git.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "fetch-settings.hh"
|
||||
|
||||
|
@ -243,7 +243,10 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
|||
Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
|
||||
auto url = fmt("https://api.%s/repos/%s/%s/commits/%s", // FIXME: check
|
||||
auto url = fmt(
|
||||
host == "github.com"
|
||||
? "https://api.%s/repos/%s/%s/commits/%s"
|
||||
: "https://%s/api/v3/repos/%s/%s/commits/%s",
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef());
|
||||
|
||||
Headers headers = makeHeadersWithAuthTokens(host);
|
||||
|
@ -262,7 +265,10 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
|||
// FIXME: use regular /archive URLs instead? api.github.com
|
||||
// might have stricter rate limits.
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com");
|
||||
auto url = fmt("https://api.%s/repos/%s/%s/tarball/%s", // FIXME: check if this is correct for self hosted instances
|
||||
auto url = fmt(
|
||||
host == "github.com"
|
||||
? "https://api.%s/repos/%s/%s/tarball/%s"
|
||||
: "https://%s/api/v3/repos/%s/%s/tarball/%s",
|
||||
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||
input.getRev()->to_string(Base16, false));
|
||||
|
||||
|
@ -375,7 +381,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
|||
|
||||
Headers headers = makeHeadersWithAuthTokens(host);
|
||||
|
||||
std::string ref_uri;
|
||||
std::string refUri;
|
||||
if (ref == "HEAD") {
|
||||
auto file = store->toRealPath(
|
||||
downloadFile(store, fmt("%s/HEAD", base_url), "source", false, headers).storePath);
|
||||
|
@ -383,35 +389,32 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
|||
std::string line;
|
||||
getline(is, line);
|
||||
|
||||
auto ref_index = line.find("ref: ");
|
||||
if (ref_index == std::string::npos) {
|
||||
auto remoteLine = git::parseLsRemoteLine(line);
|
||||
if (!remoteLine) {
|
||||
throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref);
|
||||
}
|
||||
|
||||
ref_uri = line.substr(ref_index+5, line.length()-1);
|
||||
} else
|
||||
ref_uri = fmt("refs/(heads|tags)/%s", ref);
|
||||
refUri = remoteLine->target;
|
||||
} else {
|
||||
refUri = fmt("refs/(heads|tags)/%s", ref);
|
||||
}
|
||||
std::regex refRegex(refUri);
|
||||
|
||||
auto file = store->toRealPath(
|
||||
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
|
||||
std::ifstream is(file);
|
||||
|
||||
std::string line;
|
||||
std::string id;
|
||||
while(getline(is, line)) {
|
||||
// Append $ to avoid partial name matches
|
||||
std::regex pattern(fmt("%s$", ref_uri));
|
||||
|
||||
if (std::regex_search(line, pattern)) {
|
||||
id = line.substr(0, line.find('\t'));
|
||||
break;
|
||||
}
|
||||
std::optional<std::string> id;
|
||||
while(!id && getline(is, line)) {
|
||||
auto parsedLine = git::parseLsRemoteLine(line);
|
||||
if (parsedLine && parsedLine->reference && std::regex_match(*parsedLine->reference, refRegex))
|
||||
id = parsedLine->target;
|
||||
}
|
||||
|
||||
if(id.empty())
|
||||
if(!id)
|
||||
throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref);
|
||||
|
||||
auto rev = Hash::parseAny(id, htSHA1);
|
||||
auto rev = Hash::parseAny(*id, htSHA1);
|
||||
debug("HEAD revision for '%s' is %s", fmt("%s/%s", base_url, ref), rev.gitRev());
|
||||
return rev;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "archive.hh"
|
||||
#include "tarfile.hh"
|
||||
#include "types.hh"
|
||||
#include "split.hh"
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
|
@ -168,24 +169,34 @@ std::pair<Tree, time_t> downloadTarball(
|
|||
};
|
||||
}
|
||||
|
||||
struct TarballInputScheme : InputScheme
|
||||
// An input scheme corresponding to a curl-downloadable resource.
|
||||
struct CurlInputScheme : InputScheme
|
||||
{
|
||||
virtual const std::string inputType() const = 0;
|
||||
const std::set<std::string> transportUrlSchemes = {"file", "http", "https"};
|
||||
|
||||
const bool hasTarballExtension(std::string_view path) const
|
||||
{
|
||||
return hasSuffix(path, ".zip") || hasSuffix(path, ".tar")
|
||||
|| hasSuffix(path, ".tgz") || hasSuffix(path, ".tar.gz")
|
||||
|| hasSuffix(path, ".tar.xz") || hasSuffix(path, ".tar.bz2")
|
||||
|| hasSuffix(path, ".tar.zst");
|
||||
}
|
||||
|
||||
virtual bool isValidURL(const ParsedURL & url) const = 0;
|
||||
|
||||
std::optional<Input> inputFromURL(const ParsedURL & url) override
|
||||
{
|
||||
if (url.scheme != "file" && url.scheme != "http" && url.scheme != "https") return {};
|
||||
|
||||
if (!hasSuffix(url.path, ".zip")
|
||||
&& !hasSuffix(url.path, ".tar")
|
||||
&& !hasSuffix(url.path, ".tgz")
|
||||
&& !hasSuffix(url.path, ".tar.gz")
|
||||
&& !hasSuffix(url.path, ".tar.xz")
|
||||
&& !hasSuffix(url.path, ".tar.bz2")
|
||||
&& !hasSuffix(url.path, ".tar.zst"))
|
||||
return {};
|
||||
if (!isValidURL(url))
|
||||
return std::nullopt;
|
||||
|
||||
Input input;
|
||||
input.attrs.insert_or_assign("type", "tarball");
|
||||
input.attrs.insert_or_assign("url", url.to_string());
|
||||
|
||||
auto urlWithoutApplicationScheme = url;
|
||||
urlWithoutApplicationScheme.scheme = parseUrlScheme(url.scheme).transport;
|
||||
|
||||
input.attrs.insert_or_assign("type", inputType());
|
||||
input.attrs.insert_or_assign("url", urlWithoutApplicationScheme.to_string());
|
||||
auto narHash = url.query.find("narHash");
|
||||
if (narHash != url.query.end())
|
||||
input.attrs.insert_or_assign("narHash", narHash->second);
|
||||
|
@ -194,14 +205,17 @@ struct TarballInputScheme : InputScheme
|
|||
|
||||
std::optional<Input> inputFromAttrs(const Attrs & attrs) override
|
||||
{
|
||||
if (maybeGetStrAttr(attrs, "type") != "tarball") return {};
|
||||
auto type = maybeGetStrAttr(attrs, "type");
|
||||
if (type != inputType()) return {};
|
||||
|
||||
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack"};
|
||||
for (auto & [name, value] : attrs)
|
||||
if (name != "type" && name != "url" && /* name != "hash" && */ name != "narHash" && name != "name")
|
||||
throw Error("unsupported tarball input attribute '%s'", name);
|
||||
if (!allowedNames.count(name))
|
||||
throw Error("unsupported %s input attribute '%s'", *type, name);
|
||||
|
||||
Input input;
|
||||
input.attrs = attrs;
|
||||
|
||||
//input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
|
||||
return input;
|
||||
}
|
||||
|
@ -209,14 +223,9 @@ struct TarballInputScheme : InputScheme
|
|||
ParsedURL toURL(const Input & input) override
|
||||
{
|
||||
auto url = parseURL(getStrAttr(input.attrs, "url"));
|
||||
// NAR hashes are preferred over file hashes since tar/zip files
|
||||
// don't have a canonical representation.
|
||||
// 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));
|
||||
/*
|
||||
else if (auto hash = maybeGetStrAttr(input.attrs, "hash"))
|
||||
url.query.insert_or_assign("hash", Hash(*hash).to_string(SRI, true));
|
||||
*/
|
||||
return url;
|
||||
}
|
||||
|
||||
|
@ -225,6 +234,42 @@ struct TarballInputScheme : InputScheme
|
|||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct FileInputScheme : CurlInputScheme
|
||||
{
|
||||
const std::string inputType() const override { return "file"; }
|
||||
|
||||
bool isValidURL(const ParsedURL & url) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
|
||||
&& (parsedUrlScheme.application
|
||||
? parsedUrlScheme.application.value() == inputType()
|
||||
: !hasTarballExtension(url.path));
|
||||
}
|
||||
|
||||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
|
||||
{
|
||||
auto file = downloadFile(store, getStrAttr(input.attrs, "url"), input.getName(), false);
|
||||
return {std::move(file.storePath), input};
|
||||
}
|
||||
};
|
||||
|
||||
struct TarballInputScheme : CurlInputScheme
|
||||
{
|
||||
const std::string inputType() const override { return "tarball"; }
|
||||
|
||||
bool isValidURL(const ParsedURL & url) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
|
||||
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
|
||||
&& (parsedUrlScheme.application
|
||||
? parsedUrlScheme.application.value() == inputType()
|
||||
: hasTarballExtension(url.path));
|
||||
}
|
||||
|
||||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
|
||||
{
|
||||
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
|
||||
|
@ -233,5 +278,6 @@ struct TarballInputScheme : InputScheme
|
|||
};
|
||||
|
||||
static auto rTarballInputScheme = OnStartup([] { registerInputScheme(std::make_unique<TarballInputScheme>()); });
|
||||
static auto rFileInputScheme = OnStartup([] { registerInputScheme(std::make_unique<FileInputScheme>()); });
|
||||
|
||||
}
|
||||
|
|
|
@ -30,8 +30,11 @@ Logger * makeDefaultLogger() {
|
|||
return makeJSONLogger(*makeSimpleLogger(true));
|
||||
case LogFormat::bar:
|
||||
return makeProgressBar();
|
||||
case LogFormat::barWithLogs:
|
||||
return makeProgressBar(true);
|
||||
case LogFormat::barWithLogs: {
|
||||
auto logger = makeProgressBar();
|
||||
logger->setPrintBuildLogs(true);
|
||||
return logger;
|
||||
}
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <map>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -48,6 +49,7 @@ private:
|
|||
bool visible = true;
|
||||
ActivityId parent;
|
||||
std::optional<std::string> name;
|
||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||
};
|
||||
|
||||
struct ActivitiesByType
|
||||
|
@ -79,22 +81,22 @@ private:
|
|||
|
||||
std::condition_variable quitCV, updateCV;
|
||||
|
||||
bool printBuildLogs;
|
||||
bool printBuildLogs = false;
|
||||
bool isTTY;
|
||||
|
||||
public:
|
||||
|
||||
ProgressBar(bool printBuildLogs, bool isTTY)
|
||||
: printBuildLogs(printBuildLogs)
|
||||
, isTTY(isTTY)
|
||||
ProgressBar(bool isTTY)
|
||||
: isTTY(isTTY)
|
||||
{
|
||||
state_.lock()->active = isTTY;
|
||||
updateThread = std::thread([&]() {
|
||||
auto state(state_.lock());
|
||||
auto nextWakeup = std::chrono::milliseconds::max();
|
||||
while (state->active) {
|
||||
if (!state->haveUpdate)
|
||||
state.wait(updateCV);
|
||||
draw(*state);
|
||||
state.wait_for(updateCV, nextWakeup);
|
||||
nextWakeup = draw(*state);
|
||||
state.wait_for(quitCV, std::chrono::milliseconds(50));
|
||||
}
|
||||
});
|
||||
|
@ -118,7 +120,8 @@ public:
|
|||
updateThread.join();
|
||||
}
|
||||
|
||||
bool isVerbose() override {
|
||||
bool isVerbose() override
|
||||
{
|
||||
return printBuildLogs;
|
||||
}
|
||||
|
||||
|
@ -159,11 +162,13 @@ public:
|
|||
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
|
||||
log(*state, lvl, s + "...");
|
||||
|
||||
state->activities.emplace_back(ActInfo());
|
||||
state->activities.emplace_back(ActInfo {
|
||||
.s = s,
|
||||
.type = type,
|
||||
.parent = parent,
|
||||
.startTime = std::chrono::steady_clock::now()
|
||||
});
|
||||
auto i = std::prev(state->activities.end());
|
||||
i->s = s;
|
||||
i->type = type;
|
||||
i->parent = parent;
|
||||
state->its.emplace(act, i);
|
||||
state->activitiesByType[type].its.emplace(act, i);
|
||||
|
||||
|
@ -327,10 +332,12 @@ public:
|
|||
updateCV.notify_one();
|
||||
}
|
||||
|
||||
void draw(State & state)
|
||||
std::chrono::milliseconds draw(State & state)
|
||||
{
|
||||
auto nextWakeup = std::chrono::milliseconds::max();
|
||||
|
||||
state.haveUpdate = false;
|
||||
if (!state.active) return;
|
||||
if (!state.active) return nextWakeup;
|
||||
|
||||
std::string line;
|
||||
|
||||
|
@ -341,12 +348,25 @@ public:
|
|||
line += "]";
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (!state.activities.empty()) {
|
||||
if (!status.empty()) line += " ";
|
||||
auto i = state.activities.rbegin();
|
||||
|
||||
while (i != state.activities.rend() && (!i->visible || (i->s.empty() && i->lastLine.empty())))
|
||||
while (i != state.activities.rend()) {
|
||||
if (i->visible && (!i->s.empty() || !i->lastLine.empty())) {
|
||||
/* Don't show activities until some time has
|
||||
passed, to avoid displaying very short
|
||||
activities. */
|
||||
auto delay = std::chrono::milliseconds(10);
|
||||
if (i->startTime + delay < now)
|
||||
break;
|
||||
else
|
||||
nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime)));
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i != state.activities.rend()) {
|
||||
line += i->s;
|
||||
|
@ -366,6 +386,8 @@ public:
|
|||
if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
|
||||
|
||||
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
|
||||
|
||||
return nextWakeup;
|
||||
}
|
||||
|
||||
std::string getStatus(State & state)
|
||||
|
@ -480,19 +502,21 @@ public:
|
|||
draw(*state);
|
||||
return s[0];
|
||||
}
|
||||
|
||||
virtual void setPrintBuildLogs(bool printBuildLogs)
|
||||
{
|
||||
this->printBuildLogs = printBuildLogs;
|
||||
}
|
||||
};
|
||||
|
||||
Logger * makeProgressBar(bool printBuildLogs)
|
||||
Logger * makeProgressBar()
|
||||
{
|
||||
return new ProgressBar(
|
||||
printBuildLogs,
|
||||
shouldANSI()
|
||||
);
|
||||
return new ProgressBar(shouldANSI());
|
||||
}
|
||||
|
||||
void startProgressBar(bool printBuildLogs)
|
||||
void startProgressBar()
|
||||
{
|
||||
logger = makeProgressBar(printBuildLogs);
|
||||
logger = makeProgressBar();
|
||||
}
|
||||
|
||||
void stopProgressBar()
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
Logger * makeProgressBar(bool printBuildLogs = false);
|
||||
Logger * makeProgressBar();
|
||||
|
||||
void startProgressBar(bool printBuildLogs = false);
|
||||
void startProgressBar();
|
||||
|
||||
void stopProgressBar();
|
||||
|
||||
|
|
|
@ -181,8 +181,9 @@ void initNix()
|
|||
/* Reset SIGCHLD to its default. */
|
||||
struct sigaction act;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_handler = SIG_DFL;
|
||||
act.sa_flags = 0;
|
||||
|
||||
act.sa_handler = SIG_DFL;
|
||||
if (sigaction(SIGCHLD, &act, 0))
|
||||
throw SysError("resetting SIGCHLD");
|
||||
|
||||
|
@ -194,9 +195,20 @@ void initNix()
|
|||
/* HACK: on darwin, we need can’t use sigprocmask with SIGWINCH.
|
||||
* Instead, add a dummy sigaction handler, and signalHandlerThread
|
||||
* can handle the rest. */
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = sigHandler;
|
||||
if (sigaction(SIGWINCH, &sa, 0)) throw SysError("handling SIGWINCH");
|
||||
act.sa_handler = sigHandler;
|
||||
if (sigaction(SIGWINCH, &act, 0)) throw SysError("handling SIGWINCH");
|
||||
|
||||
/* Disable SA_RESTART for interrupts, so that system calls on this thread
|
||||
* error with EINTR like they do on Linux.
|
||||
* Most signals on BSD systems default to SA_RESTART on, but Nix
|
||||
* expects EINTR from syscalls to properly exit. */
|
||||
act.sa_handler = SIG_DFL;
|
||||
if (sigaction(SIGINT, &act, 0)) throw SysError("handling SIGINT");
|
||||
if (sigaction(SIGTERM, &act, 0)) throw SysError("handling SIGTERM");
|
||||
if (sigaction(SIGHUP, &act, 0)) throw SysError("handling SIGHUP");
|
||||
if (sigaction(SIGPIPE, &act, 0)) throw SysError("handling SIGPIPE");
|
||||
if (sigaction(SIGQUIT, &act, 0)) throw SysError("handling SIGQUIT");
|
||||
if (sigaction(SIGTRAP, &act, 0)) throw SysError("handling SIGTRAP");
|
||||
#endif
|
||||
|
||||
/* Register a SIGSEGV handler to detect stack overflows. */
|
||||
|
|
|
@ -344,7 +344,7 @@ void DerivationGoal::gaveUpOnSubstitution()
|
|||
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
|
||||
/* Ensure that pure, non-fixed-output derivations don't
|
||||
depend on impure derivations. */
|
||||
if (drv->type().isPure() && !drv->type().isFixed()) {
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
|
||||
auto inputDrv = worker.evalStore.readDerivation(i.first);
|
||||
if (!inputDrv.type().isPure())
|
||||
throw Error("pure derivation '%s' depends on impure derivation '%s'",
|
||||
|
@ -705,8 +705,7 @@ static void movePath(const Path & src, const Path & dst)
|
|||
if (changePerm)
|
||||
chmod_(src, st.st_mode | S_IWUSR);
|
||||
|
||||
if (rename(src.c_str(), dst.c_str()))
|
||||
throw SysError("renaming '%1%' to '%2%'", src, dst);
|
||||
renameFile(src, dst);
|
||||
|
||||
if (changePerm)
|
||||
chmod_(dst, st.st_mode);
|
||||
|
@ -786,8 +785,7 @@ void runPostBuildHook(
|
|||
Store & store,
|
||||
Logger & logger,
|
||||
const StorePath & drvPath,
|
||||
StorePathSet outputPaths
|
||||
)
|
||||
const StorePathSet & outputPaths)
|
||||
{
|
||||
auto hook = settings.postBuildHook;
|
||||
if (hook == "")
|
||||
|
@ -906,7 +904,7 @@ void DerivationGoal::buildDone()
|
|||
auto builtOutputs = registerOutputs();
|
||||
|
||||
StorePathSet outputPaths;
|
||||
for (auto & [_, output] : buildResult.builtOutputs)
|
||||
for (auto & [_, output] : builtOutputs)
|
||||
outputPaths.insert(output.outPath);
|
||||
runPostBuildHook(
|
||||
worker.store,
|
||||
|
@ -915,12 +913,6 @@ void DerivationGoal::buildDone()
|
|||
outputPaths
|
||||
);
|
||||
|
||||
if (buildMode == bmCheck) {
|
||||
cleanupPostOutputsRegisteredModeCheck();
|
||||
done(BuildResult::Built, std::move(builtOutputs));
|
||||
return;
|
||||
}
|
||||
|
||||
cleanupPostOutputsRegisteredModeNonCheck();
|
||||
|
||||
/* Repeat the build if necessary. */
|
||||
|
@ -985,21 +977,28 @@ void DerivationGoal::resolvedFinished()
|
|||
realWantedOutputs = resolvedDrv.outputNames();
|
||||
|
||||
for (auto & wantedOutput : realWantedOutputs) {
|
||||
assert(initialOutputs.count(wantedOutput) != 0);
|
||||
assert(resolvedHashes.count(wantedOutput) != 0);
|
||||
auto realisation = resolvedResult.builtOutputs.at(
|
||||
DrvOutput { resolvedHashes.at(wantedOutput), wantedOutput });
|
||||
auto initialOutput = get(initialOutputs, wantedOutput);
|
||||
auto resolvedHash = get(resolvedHashes, wantedOutput);
|
||||
if ((!initialOutput) || (!resolvedHash))
|
||||
throw Error(
|
||||
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)",
|
||||
worker.store.printStorePath(drvPath), wantedOutput);
|
||||
auto realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput });
|
||||
if (!realisation)
|
||||
throw Error(
|
||||
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)",
|
||||
worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput);
|
||||
if (drv->type().isPure()) {
|
||||
auto newRealisation = realisation;
|
||||
newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput };
|
||||
auto newRealisation = *realisation;
|
||||
newRealisation.id = DrvOutput { initialOutput->outputHash, wantedOutput };
|
||||
newRealisation.signatures.clear();
|
||||
if (!drv->type().isFixed())
|
||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath);
|
||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
|
||||
signRealisation(newRealisation);
|
||||
worker.store.registerDrvOutput(newRealisation);
|
||||
}
|
||||
outputPaths.insert(realisation.outPath);
|
||||
builtOutputs.emplace(realisation.id, realisation);
|
||||
outputPaths.insert(realisation->outPath);
|
||||
builtOutputs.emplace(realisation->id, *realisation);
|
||||
}
|
||||
|
||||
runPostBuildHook(
|
||||
|
@ -1295,7 +1294,11 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
|||
DrvOutputs validOutputs;
|
||||
|
||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||
InitialOutput & info = initialOutputs.at(i.first);
|
||||
auto initialOutput = get(initialOutputs, i.first);
|
||||
if (!initialOutput)
|
||||
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
|
||||
continue;
|
||||
auto & info = *initialOutput;
|
||||
info.wanted = wantOutput(i.first, wantedOutputs);
|
||||
if (info.wanted)
|
||||
wantedOutputsLeft.erase(i.first);
|
||||
|
@ -1310,7 +1313,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
|||
: PathStatus::Corrupt,
|
||||
};
|
||||
}
|
||||
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first};
|
||||
auto drvOutput = DrvOutput{info.outputHash, i.first};
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
if (auto real = worker.store.queryRealisation(drvOutput)) {
|
||||
info.known = {
|
||||
|
|
|
@ -7,6 +7,22 @@ HookInstance::HookInstance()
|
|||
{
|
||||
debug("starting build hook '%s'", settings.buildHook);
|
||||
|
||||
auto buildHookArgs = tokenizeString<std::list<std::string>>(settings.buildHook.get());
|
||||
|
||||
if (buildHookArgs.empty())
|
||||
throw Error("'build-hook' setting is empty");
|
||||
|
||||
auto buildHook = buildHookArgs.front();
|
||||
buildHookArgs.pop_front();
|
||||
|
||||
Strings args;
|
||||
|
||||
for (auto & arg : buildHookArgs)
|
||||
args.push_back(arg);
|
||||
|
||||
args.push_back(std::string(baseNameOf(settings.buildHook.get())));
|
||||
args.push_back(std::to_string(verbosity));
|
||||
|
||||
/* Create a pipe to get the output of the child. */
|
||||
fromHook.create();
|
||||
|
||||
|
@ -36,14 +52,9 @@ HookInstance::HookInstance()
|
|||
if (dup2(builderOut.readSide.get(), 5) == -1)
|
||||
throw SysError("dupping builder's stdout/stderr");
|
||||
|
||||
Strings args = {
|
||||
std::string(baseNameOf(settings.buildHook.get())),
|
||||
std::to_string(verbosity),
|
||||
};
|
||||
execv(buildHook.c_str(), stringsToCharPtrs(args).data());
|
||||
|
||||
execv(settings.buildHook.get().c_str(), stringsToCharPtrs(args).data());
|
||||
|
||||
throw SysError("executing '%s'", settings.buildHook);
|
||||
throw SysError("executing '%s'", buildHook);
|
||||
});
|
||||
|
||||
pid.setSeparatePG(true);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "worker-protocol.hh"
|
||||
#include "topo-sort.hh"
|
||||
#include "callback.hh"
|
||||
#include "json-utils.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
|
@ -56,8 +57,6 @@
|
|||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void handleDiffHook(
|
||||
|
@ -224,8 +223,7 @@ static void movePath(const Path & src, const Path & dst)
|
|||
if (changePerm)
|
||||
chmod_(src, st.st_mode | S_IWUSR);
|
||||
|
||||
if (rename(src.c_str(), dst.c_str()))
|
||||
throw SysError("renaming '%1%' to '%2%'", src, dst);
|
||||
renameFile(src, dst);
|
||||
|
||||
if (changePerm)
|
||||
chmod_(dst, st.st_mode);
|
||||
|
@ -312,7 +310,7 @@ bool LocalDerivationGoal::cleanupDecideWhetherDiskFull()
|
|||
if (buildMode != bmCheck && status.known->isValid()) continue;
|
||||
auto p = worker.store.printStorePath(status.known->path);
|
||||
if (pathExists(chrootRootDir + p))
|
||||
rename((chrootRootDir + p).c_str(), p.c_str());
|
||||
renameFile((chrootRootDir + p), p);
|
||||
}
|
||||
|
||||
return diskFull;
|
||||
|
@ -482,7 +480,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
temporary build directory. The text files have the format used
|
||||
by `nix-store --register-validity'. However, the deriver
|
||||
fields are left empty. */
|
||||
auto s = get(drv->env, "exportReferencesGraph").value_or("");
|
||||
auto s = getOr(drv->env, "exportReferencesGraph", "");
|
||||
Strings ss = tokenizeString<Strings>(s);
|
||||
if (ss.size() % 2 != 0)
|
||||
throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s);
|
||||
|
@ -846,18 +844,43 @@ void LocalDerivationGoal::startBuilder()
|
|||
/* Some distros patch Linux to not allow unprivileged
|
||||
* user namespaces. If we get EPERM or EINVAL, try
|
||||
* without CLONE_NEWUSER and see if that works.
|
||||
* Details: https://salsa.debian.org/kernel-team/linux/-/commit/d98e00eda6bea437e39b9e80444eee84a32438a6
|
||||
*/
|
||||
usingUserNamespace = false;
|
||||
flags &= ~CLONE_NEWUSER;
|
||||
child = clone(childEntry, stack + stackSize, flags, this);
|
||||
}
|
||||
/* Otherwise exit with EPERM so we can handle this in the
|
||||
parent. This is only done when sandbox-fallback is set
|
||||
to true (the default). */
|
||||
if (child == -1 && (errno == EPERM || errno == EINVAL) && settings.sandboxFallback)
|
||||
_exit(1);
|
||||
if (child == -1) throw SysError("cloning builder process");
|
||||
|
||||
if (child == -1) {
|
||||
switch(errno) {
|
||||
case EPERM:
|
||||
case EINVAL: {
|
||||
int errno_ = errno;
|
||||
if (!userNamespacesEnabled && errno==EPERM)
|
||||
notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/user/max_user_namespaces");
|
||||
if (userNamespacesEnabled) {
|
||||
Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
|
||||
if (pathExists(procSysKernelUnprivilegedUsernsClone)
|
||||
&& trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") {
|
||||
notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/kernel/unprivileged_userns_clone");
|
||||
}
|
||||
}
|
||||
Path procSelfNsUser = "/proc/self/ns/user";
|
||||
if (!pathExists(procSelfNsUser))
|
||||
notice("/proc/self/ns/user does not exist; your kernel was likely built without CONFIG_USER_NS=y, which is required for sandboxing");
|
||||
/* Otherwise exit with EPERM so we can handle this in the
|
||||
parent. This is only done when sandbox-fallback is set
|
||||
to true (the default). */
|
||||
if (settings.sandboxFallback)
|
||||
_exit(1);
|
||||
/* Mention sandbox-fallback in the error message so the user
|
||||
knows that having it disabled contributed to the
|
||||
unrecoverability of this failure */
|
||||
throw SysError(errno_, "creating sandboxed builder process using clone(), without sandbox-fallback");
|
||||
}
|
||||
default:
|
||||
throw SysError("creating sandboxed builder process using clone()");
|
||||
}
|
||||
}
|
||||
writeFull(builderOut.writeSide.get(),
|
||||
fmt("%d %d\n", usingUserNamespace, child));
|
||||
_exit(0);
|
||||
|
@ -989,7 +1012,7 @@ void LocalDerivationGoal::initTmpDir() {
|
|||
there is no size constraint). */
|
||||
if (!parsedDrv->getStructuredAttrs()) {
|
||||
|
||||
StringSet passAsFile = tokenizeString<StringSet>(get(drv->env, "passAsFile").value_or(""));
|
||||
StringSet passAsFile = tokenizeString<StringSet>(getOr(drv->env, "passAsFile", ""));
|
||||
for (auto & i : drv->env) {
|
||||
if (passAsFile.find(i.first) == passAsFile.end()) {
|
||||
env[i.first] = i.second;
|
||||
|
@ -1718,7 +1741,19 @@ void LocalDerivationGoal::runChild()
|
|||
|
||||
for (auto & i : dirsInChroot) {
|
||||
if (i.second.source == "/proc") continue; // backwards compatibility
|
||||
doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
|
||||
|
||||
#if HAVE_EMBEDDED_SANDBOX_SHELL
|
||||
if (i.second.source == "__embedded_sandbox_shell__") {
|
||||
static unsigned char sh[] = {
|
||||
#include "embedded-sandbox-shell.gen.hh"
|
||||
};
|
||||
auto dst = chrootRootDir + i.first;
|
||||
createDirs(dirOf(dst));
|
||||
writeFile(dst, std::string_view((const char *) sh, sizeof(sh)));
|
||||
chmod_(dst, 0555);
|
||||
} else
|
||||
#endif
|
||||
doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
|
||||
}
|
||||
|
||||
/* Bind a new instance of procfs on /proc. */
|
||||
|
@ -2128,12 +2163,22 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
std::map<std::string, std::variant<AlreadyRegistered, PerhapsNeedToRegister>> outputReferencesIfUnregistered;
|
||||
std::map<std::string, struct stat> outputStats;
|
||||
for (auto & [outputName, _] : drv->outputs) {
|
||||
auto actualPath = toRealPathChroot(worker.store.printStorePath(scratchOutputs.at(outputName)));
|
||||
auto scratchOutput = get(scratchOutputs, outputName);
|
||||
if (!scratchOutput)
|
||||
throw BuildError(
|
||||
"builder for '%s' has no scratch output for '%s'",
|
||||
worker.store.printStorePath(drvPath), outputName);
|
||||
auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchOutput));
|
||||
|
||||
outputsToSort.insert(outputName);
|
||||
|
||||
/* Updated wanted info to remove the outputs we definitely don't need to register */
|
||||
auto & initialInfo = initialOutputs.at(outputName);
|
||||
auto initialOutput = get(initialOutputs, outputName);
|
||||
if (!initialOutput)
|
||||
throw BuildError(
|
||||
"builder for '%s' has no initial output for '%s'",
|
||||
worker.store.printStorePath(drvPath), outputName);
|
||||
auto & initialInfo = *initialOutput;
|
||||
|
||||
/* Don't register if already valid, and not checking */
|
||||
initialInfo.wanted = buildMode == bmCheck
|
||||
|
@ -2185,6 +2230,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
|
||||
auto sortedOutputNames = topoSort(outputsToSort,
|
||||
{[&](const std::string & name) {
|
||||
auto orifu = get(outputReferencesIfUnregistered, name);
|
||||
if (!orifu)
|
||||
throw BuildError(
|
||||
"no output reference for '%s' in build of '%s'",
|
||||
name, worker.store.printStorePath(drvPath));
|
||||
return std::visit(overloaded {
|
||||
/* Since we'll use the already installed versions of these, we
|
||||
can treat them as leaves and ignore any references they
|
||||
|
@ -2199,7 +2249,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
referencedOutputs.insert(o);
|
||||
return referencedOutputs;
|
||||
},
|
||||
}, outputReferencesIfUnregistered.at(name));
|
||||
}, *orifu);
|
||||
}},
|
||||
{[&](const std::string & path, const std::string & parent) {
|
||||
// TODO with more -vvvv also show the temporary paths for manual inspection.
|
||||
|
@ -2213,9 +2263,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
OutputPathMap finalOutputs;
|
||||
|
||||
for (auto & outputName : sortedOutputNames) {
|
||||
auto output = drv->outputs.at(outputName);
|
||||
auto & scratchPath = scratchOutputs.at(outputName);
|
||||
auto actualPath = toRealPathChroot(worker.store.printStorePath(scratchPath));
|
||||
auto output = get(drv->outputs, outputName);
|
||||
auto scratchPath = get(scratchOutputs, outputName);
|
||||
assert(output && scratchPath);
|
||||
auto actualPath = toRealPathChroot(worker.store.printStorePath(*scratchPath));
|
||||
|
||||
auto finish = [&](StorePath finalStorePath) {
|
||||
/* Store the final path */
|
||||
|
@ -2223,10 +2274,13 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
/* The rewrite rule will be used in downstream outputs that refer to
|
||||
use. This is why the topological sort is essential to do first
|
||||
before this for loop. */
|
||||
if (scratchPath != finalStorePath)
|
||||
outputRewrites[std::string { scratchPath.hashPart() }] = std::string { finalStorePath.hashPart() };
|
||||
if (*scratchPath != finalStorePath)
|
||||
outputRewrites[std::string { scratchPath->hashPart() }] = std::string { finalStorePath.hashPart() };
|
||||
};
|
||||
|
||||
auto orifu = get(outputReferencesIfUnregistered, outputName);
|
||||
assert(orifu);
|
||||
|
||||
std::optional<StorePathSet> referencesOpt = std::visit(overloaded {
|
||||
[&](const AlreadyRegistered & skippedFinalPath) -> std::optional<StorePathSet> {
|
||||
finish(skippedFinalPath.path);
|
||||
|
@ -2235,7 +2289,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
[&](const PerhapsNeedToRegister & r) -> std::optional<StorePathSet> {
|
||||
return r.refs;
|
||||
},
|
||||
}, outputReferencesIfUnregistered.at(outputName));
|
||||
}, *orifu);
|
||||
|
||||
if (!referencesOpt)
|
||||
continue;
|
||||
|
@ -2268,25 +2322,29 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
for (auto & r : references) {
|
||||
auto name = r.name();
|
||||
auto origHash = std::string { r.hashPart() };
|
||||
if (r == scratchPath)
|
||||
if (r == *scratchPath) {
|
||||
res.first = true;
|
||||
else if (outputRewrites.count(origHash) == 0)
|
||||
res.second.insert(r);
|
||||
else {
|
||||
std::string newRef = outputRewrites.at(origHash);
|
||||
} else if (auto outputRewrite = get(outputRewrites, origHash)) {
|
||||
std::string newRef = *outputRewrite;
|
||||
newRef += '-';
|
||||
newRef += name;
|
||||
res.second.insert(StorePath { newRef });
|
||||
} else {
|
||||
res.second.insert(r);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
|
||||
auto & st = outputStats.at(outputName);
|
||||
auto st = get(outputStats, outputName);
|
||||
if (!st)
|
||||
throw BuildError(
|
||||
"output path %1% without valid stats info",
|
||||
actualPath);
|
||||
if (outputHash.method == FileIngestionMethod::Flat) {
|
||||
/* The output path should be a regular file without execute permission. */
|
||||
if (!S_ISREG(st.st_mode) || (st.st_mode & S_IXUSR) != 0)
|
||||
if (!S_ISREG(st->st_mode) || (st->st_mode & S_IXUSR) != 0)
|
||||
throw BuildError(
|
||||
"output path '%1%' should be a non-executable regular file "
|
||||
"since recursive hashing is not enabled (outputHashMode=flat)",
|
||||
|
@ -2294,7 +2352,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
}
|
||||
rewriteOutput();
|
||||
/* FIXME optimize and deduplicate with addToStore */
|
||||
std::string oldHashPart { scratchPath.hashPart() };
|
||||
std::string oldHashPart { scratchPath->hashPart() };
|
||||
HashModuloSink caSink { outputHash.hashType, oldHashPart };
|
||||
switch (outputHash.method) {
|
||||
case FileIngestionMethod::Recursive:
|
||||
|
@ -2313,13 +2371,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
outputPathName(drv->name, outputName),
|
||||
refs.second,
|
||||
refs.first);
|
||||
if (scratchPath != finalPath) {
|
||||
if (*scratchPath != finalPath) {
|
||||
// Also rewrite the output path
|
||||
auto source = sinkToSource([&](Sink & nextSink) {
|
||||
StringSink sink;
|
||||
dumpPath(actualPath, sink);
|
||||
RewritingSink rsink2(oldHashPart, std::string(finalPath.hashPart()), nextSink);
|
||||
rsink2(sink.s);
|
||||
dumpPath(actualPath, rsink2);
|
||||
rsink2.flush();
|
||||
});
|
||||
Path tmpPath = actualPath + ".tmp";
|
||||
|
@ -2354,9 +2410,9 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
auto requiredFinalPath = output.path;
|
||||
/* Preemptively add rewrite rule for final hash, as that is
|
||||
what the NAR hash will use rather than normalized-self references */
|
||||
if (scratchPath != requiredFinalPath)
|
||||
if (*scratchPath != requiredFinalPath)
|
||||
outputRewrites.insert_or_assign(
|
||||
std::string { scratchPath.hashPart() },
|
||||
std::string { scratchPath->hashPart() },
|
||||
std::string { requiredFinalPath.hashPart() });
|
||||
rewriteOutput();
|
||||
auto narHashAndSize = hashPath(htSHA256, actualPath);
|
||||
|
@ -2409,7 +2465,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
});
|
||||
},
|
||||
|
||||
}, output.raw());
|
||||
}, output->raw());
|
||||
|
||||
/* FIXME: set proper permissions in restorePath() so
|
||||
we don't have to do another traversal. */
|
||||
|
@ -2425,7 +2481,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
derivations. */
|
||||
PathLocks dynamicOutputLock;
|
||||
dynamicOutputLock.setDeletion(true);
|
||||
auto optFixedPath = output.path(worker.store, drv->name, outputName);
|
||||
auto optFixedPath = output->path(worker.store, drv->name, outputName);
|
||||
if (!optFixedPath ||
|
||||
worker.store.printStorePath(*optFixedPath) != finalDestPath)
|
||||
{
|
||||
|
@ -2491,11 +2547,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
|
||||
/* For debugging, print out the referenced and unreferenced paths. */
|
||||
for (auto & i : inputPaths) {
|
||||
auto j = references.find(i);
|
||||
if (j == references.end())
|
||||
debug("unreferenced input: '%1%'", worker.store.printStorePath(i));
|
||||
else
|
||||
if (references.count(i))
|
||||
debug("referenced input: '%1%'", worker.store.printStorePath(i));
|
||||
else
|
||||
debug("unreferenced input: '%1%'", worker.store.printStorePath(i));
|
||||
}
|
||||
|
||||
if (curRound == nrRounds) {
|
||||
|
@ -2567,8 +2622,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
Path prev = path + checkSuffix;
|
||||
deletePath(prev);
|
||||
Path dst = path + checkSuffix;
|
||||
if (rename(path.c_str(), dst.c_str()))
|
||||
throw SysError("renaming '%s' to '%s'", path, dst);
|
||||
renameFile(path, dst);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2612,9 +2666,11 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
DrvOutputs builtOutputs;
|
||||
|
||||
for (auto & [outputName, newInfo] : infos) {
|
||||
auto oldinfo = get(initialOutputs, outputName);
|
||||
assert(oldinfo);
|
||||
auto thisRealisation = Realisation {
|
||||
.id = DrvOutput {
|
||||
initialOutputs.at(outputName).outputHash,
|
||||
oldinfo->outputHash,
|
||||
outputName
|
||||
},
|
||||
.outPath = newInfo.path
|
||||
|
@ -2710,9 +2766,10 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
|
|||
for (auto & i : *value) {
|
||||
if (worker.store.isStorePath(i))
|
||||
spec.insert(worker.store.parseStorePath(i));
|
||||
else if (outputs.count(i))
|
||||
spec.insert(outputs.at(i).path);
|
||||
else throw BuildError("derivation contains an illegal reference specifier '%s'", i);
|
||||
else if (auto output = get(outputs, i))
|
||||
spec.insert(output->path);
|
||||
else
|
||||
throw BuildError("derivation contains an illegal reference specifier '%s'", i);
|
||||
}
|
||||
|
||||
auto used = recursive
|
||||
|
@ -2751,24 +2808,18 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
|
|||
};
|
||||
|
||||
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
|
||||
auto outputChecks = structuredAttrs->find("outputChecks");
|
||||
if (outputChecks != structuredAttrs->end()) {
|
||||
auto output = outputChecks->find(outputName);
|
||||
|
||||
if (output != outputChecks->end()) {
|
||||
if (auto outputChecks = get(*structuredAttrs, "outputChecks")) {
|
||||
if (auto output = get(*outputChecks, outputName)) {
|
||||
Checks checks;
|
||||
|
||||
auto maxSize = output->find("maxSize");
|
||||
if (maxSize != output->end())
|
||||
if (auto maxSize = get(*output, "maxSize"))
|
||||
checks.maxSize = maxSize->get<uint64_t>();
|
||||
|
||||
auto maxClosureSize = output->find("maxClosureSize");
|
||||
if (maxClosureSize != output->end())
|
||||
if (auto maxClosureSize = get(*output, "maxClosureSize"))
|
||||
checks.maxClosureSize = maxClosureSize->get<uint64_t>();
|
||||
|
||||
auto get = [&](const std::string & name) -> std::optional<Strings> {
|
||||
auto i = output->find(name);
|
||||
if (i != output->end()) {
|
||||
auto get_ = [&](const std::string & name) -> std::optional<Strings> {
|
||||
if (auto i = get(*output, name)) {
|
||||
Strings res;
|
||||
for (auto j = i->begin(); j != i->end(); ++j) {
|
||||
if (!j->is_string())
|
||||
|
@ -2781,10 +2832,10 @@ void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo
|
|||
return {};
|
||||
};
|
||||
|
||||
checks.allowedReferences = get("allowedReferences");
|
||||
checks.allowedRequisites = get("allowedRequisites");
|
||||
checks.disallowedReferences = get("disallowedReferences");
|
||||
checks.disallowedRequisites = get("disallowedRequisites");
|
||||
checks.allowedReferences = get_("allowedReferences");
|
||||
checks.allowedRequisites = get_("allowedRequisites");
|
||||
checks.disallowedReferences = get_("disallowedReferences");
|
||||
checks.disallowedRequisites = get_("disallowedRequisites");
|
||||
|
||||
applyChecks(checks);
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ void PathSubstitutionGoal::tryNext()
|
|||
only after we've downloaded the path. */
|
||||
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
|
||||
{
|
||||
warn("the substitute for '%s' from '%s' is not signed by any of the keys in 'trusted-public-keys'",
|
||||
warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
|
||||
worker.store.printStorePath(storePath), sub->getUri());
|
||||
tryNext();
|
||||
return;
|
||||
|
|
|
@ -350,7 +350,7 @@ void Worker::waitForInput()
|
|||
become `available'. Note that `available' (i.e., non-blocking)
|
||||
includes EOF. */
|
||||
std::vector<struct pollfd> pollStatus;
|
||||
std::map <int, int> fdToPollStatus;
|
||||
std::map<int, size_t> fdToPollStatus;
|
||||
for (auto & i : children) {
|
||||
for (auto & j : i.fds) {
|
||||
pollStatus.push_back((struct pollfd) { .fd = j, .events = POLLIN });
|
||||
|
@ -380,7 +380,10 @@ void Worker::waitForInput()
|
|||
std::set<int> fds2(j->fds);
|
||||
std::vector<unsigned char> buffer(4096);
|
||||
for (auto & k : fds2) {
|
||||
if (pollStatus.at(fdToPollStatus.at(k)).revents) {
|
||||
const auto fdPollStatusId = get(fdToPollStatus, k);
|
||||
assert(fdPollStatusId);
|
||||
assert(*fdPollStatusId < pollStatus.size());
|
||||
if (pollStatus.at(*fdPollStatusId).revents) {
|
||||
ssize_t rd = ::read(k, buffer.data(), buffer.size());
|
||||
// FIXME: is there a cleaner way to handle pt close
|
||||
// than EIO? Is this even standard?
|
||||
|
|
|
@ -93,8 +93,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
|||
auto prevPriority = state.priorities[dstFile];
|
||||
if (prevPriority == priority)
|
||||
throw Error(
|
||||
"packages '%1%' and '%2%' have the same priority %3%; "
|
||||
"files '%1%' and '%2%' have the same priority %3%; "
|
||||
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
|
||||
"or type 'nix profile install --help' if using 'nix profile' to find out how"
|
||||
"to change the priority of one of the conflicting packages"
|
||||
" (0 being the highest priority)",
|
||||
srcFile, readLink(dstFile), priority);
|
||||
|
|
|
@ -24,7 +24,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
|||
|
||||
Path storePath = getAttr("out");
|
||||
auto mainUrl = getAttr("url");
|
||||
bool unpack = get(drv.env, "unpack").value_or("") == "1";
|
||||
bool unpack = getOr(drv.env, "unpack", "") == "1";
|
||||
|
||||
/* Note: have to use a fresh fileTransfer here because we're in
|
||||
a forked process. */
|
||||
|
|
|
@ -22,8 +22,7 @@ void builtinUnpackChannel(const BasicDerivation & drv)
|
|||
auto entries = readDirectory(out);
|
||||
if (entries.size() != 1)
|
||||
throw Error("channel tarball '%s' contains more than one file", src);
|
||||
if (rename((out + "/" + entries[0].name).c_str(), (out + "/" + channelName).c_str()) == -1)
|
||||
throw SysError("renaming channel directory");
|
||||
renameFile((out + "/" + entries[0].name), (out + "/" + channelName));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -661,8 +661,10 @@ DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOut
|
|||
if (res.kind == DrvHash::Kind::Deferred)
|
||||
kind = DrvHash::Kind::Deferred;
|
||||
for (auto & outputName : inputOutputs) {
|
||||
const auto h = res.hashes.at(outputName);
|
||||
inputs2[h.to_string(Base16, false)].insert(outputName);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -836,8 +838,11 @@ 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())) {
|
||||
auto & h = hashModulo.hashes.at(outputName);
|
||||
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
||||
auto h = get(hashModulo.hashes, outputName);
|
||||
if (!h)
|
||||
throw Error("derivation '%s' output '%s' has no hash (derivations.cc/rewriteDerivation)",
|
||||
drv.name, outputName);
|
||||
auto outPath = store.makeOutputPath(outputName, *h, drv.name);
|
||||
drv.env[outputName] = store.printStorePath(outPath);
|
||||
output = DerivationOutput::InputAddressed {
|
||||
.path = std::move(outPath),
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
|
||||
|
@ -17,12 +19,12 @@ nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
|
|||
res["drvPath"] = store->printStorePath(drvPath);
|
||||
// Fallback for the input-addressed derivation case: We expect to always be
|
||||
// able to print the output paths, so let’s do it
|
||||
auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
|
||||
const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
|
||||
for (const auto& output : outputs) {
|
||||
if (knownOutputs.at(output))
|
||||
res["outputs"][output] = store->printStorePath(knownOutputs.at(output).value());
|
||||
else
|
||||
res["outputs"][output] = nullptr;
|
||||
auto knownOutput = get(knownOutputs, output);
|
||||
res["outputs"][output] = (knownOutput && *knownOutput)
|
||||
? store->printStorePath(**knownOutput)
|
||||
: nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -123,10 +125,15 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
|||
for (auto& [outputName, outputPath] : p.outputs) {
|
||||
if (settings.isExperimentalFeatureEnabled(
|
||||
Xp::CaDerivations)) {
|
||||
auto drvOutput = get(drvHashes, outputName);
|
||||
if (!drvOutput)
|
||||
throw Error(
|
||||
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
|
||||
store.printStorePath(p.drvPath), outputName);
|
||||
auto thisRealisation = store.queryRealisation(
|
||||
DrvOutput{drvHashes.at(outputName), outputName});
|
||||
assert(thisRealisation); // We’ve built it, so we must h
|
||||
// ve the realisation
|
||||
DrvOutput{*drvOutput, outputName});
|
||||
assert(thisRealisation); // We’ve built it, so we must
|
||||
// have the realisation
|
||||
res.insert(*thisRealisation);
|
||||
} else {
|
||||
res.insert(outputPath);
|
||||
|
|
|
@ -308,6 +308,9 @@ struct curlFileTransfer : public FileTransfer
|
|||
|
||||
curl_easy_setopt(req, CURLOPT_HTTPHEADER, requestHeaders);
|
||||
|
||||
if (settings.downloadSpeed.get() > 0)
|
||||
curl_easy_setopt(req, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) (settings.downloadSpeed.get() * 1024));
|
||||
|
||||
if (request.head)
|
||||
curl_easy_setopt(req, CURLOPT_NOBODY, 1);
|
||||
|
||||
|
@ -692,10 +695,10 @@ struct curlFileTransfer : public FileTransfer
|
|||
#if ENABLE_S3
|
||||
auto [bucketName, key, params] = parseS3Uri(request.uri);
|
||||
|
||||
std::string profile = get(params, "profile").value_or("");
|
||||
std::string region = get(params, "region").value_or(Aws::Region::US_EAST_1);
|
||||
std::string scheme = get(params, "scheme").value_or("");
|
||||
std::string endpoint = get(params, "endpoint").value_or("");
|
||||
std::string profile = getOr(params, "profile", "");
|
||||
std::string region = getOr(params, "region", Aws::Region::US_EAST_1);
|
||||
std::string scheme = getOr(params, "scheme", "");
|
||||
std::string endpoint = getOr(params, "endpoint", "");
|
||||
|
||||
S3Helper s3Helper(profile, region, scheme, endpoint);
|
||||
|
||||
|
|
|
@ -39,9 +39,7 @@ static void makeSymlink(const Path & link, const Path & target)
|
|||
createSymlink(target, tempLink);
|
||||
|
||||
/* Atomically replace the old one. */
|
||||
if (rename(tempLink.c_str(), link.c_str()) == -1)
|
||||
throw SysError("cannot rename '%1%' to '%2%'",
|
||||
tempLink , link);
|
||||
renameFile(tempLink, link);
|
||||
}
|
||||
|
||||
|
||||
|
@ -135,6 +133,7 @@ void LocalStore::addTempRoot(const StorePath & path)
|
|||
state->fdRootsSocket.close();
|
||||
goto restart;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,6 +152,7 @@ void LocalStore::addTempRoot(const StorePath & path)
|
|||
state->fdRootsSocket.close();
|
||||
goto restart;
|
||||
}
|
||||
throw;
|
||||
} catch (EndOfFile & e) {
|
||||
debug("GC socket disconnected");
|
||||
state->fdRootsSocket.close();
|
||||
|
|
|
@ -36,7 +36,6 @@ Settings::Settings()
|
|||
, nixStateDir(canonPath(getEnv("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
|
||||
, nixConfDir(canonPath(getEnv("NIX_CONF_DIR").value_or(NIX_CONF_DIR)))
|
||||
, nixUserConfFiles(getUserConfigFiles())
|
||||
, nixLibexecDir(canonPath(getEnv("NIX_LIBEXEC_DIR").value_or(NIX_LIBEXEC_DIR)))
|
||||
, nixBinDir(canonPath(getEnv("NIX_BIN_DIR").value_or(NIX_BIN_DIR)))
|
||||
, nixManDir(canonPath(NIX_MAN_DIR))
|
||||
, nixDaemonSocketFile(canonPath(getEnv("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
|
||||
|
@ -67,12 +66,13 @@ Settings::Settings()
|
|||
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL);
|
||||
#endif
|
||||
|
||||
|
||||
/* chroot-like behavior from Apple's sandbox */
|
||||
/* chroot-like behavior from Apple's sandbox */
|
||||
#if __APPLE__
|
||||
sandboxPaths = tokenizeString<StringSet>("/System/Library/Frameworks /System/Library/PrivateFrameworks /bin/sh /bin/bash /private/tmp /private/var/tmp /usr/lib");
|
||||
allowedImpureHostPrefixes = tokenizeString<StringSet>("/System/Library /usr/lib /dev /bin/sh");
|
||||
#endif
|
||||
|
||||
buildHook = getSelfExe().value_or("nix") + " __build-remote";
|
||||
}
|
||||
|
||||
void loadConfFile()
|
||||
|
@ -114,7 +114,13 @@ std::vector<Path> getUserConfigFiles()
|
|||
|
||||
unsigned int Settings::getDefaultCores()
|
||||
{
|
||||
return std::max(1U, std::thread::hardware_concurrency());
|
||||
const unsigned int concurrency = std::max(1U, std::thread::hardware_concurrency());
|
||||
const unsigned int maxCPU = getMaxCPU();
|
||||
|
||||
if (maxCPU > 0)
|
||||
return maxCPU;
|
||||
else
|
||||
return concurrency;
|
||||
}
|
||||
|
||||
StringSet Settings::getDefaultSystemFeatures()
|
||||
|
|
|
@ -79,9 +79,6 @@ public:
|
|||
/* A list of user configuration files to load. */
|
||||
std::vector<Path> nixUserConfFiles;
|
||||
|
||||
/* The directory where internal helper programs are stored. */
|
||||
Path nixLibexecDir;
|
||||
|
||||
/* The directory where the main programs are stored. */
|
||||
Path nixBinDir;
|
||||
|
||||
|
@ -195,7 +192,7 @@ public:
|
|||
)",
|
||||
{"build-timeout"}};
|
||||
|
||||
PathSetting buildHook{this, true, nixLibexecDir + "/nix/build-remote", "build-hook",
|
||||
PathSetting buildHook{this, true, "", "build-hook",
|
||||
"The path of the helper program that executes builds to remote machines."};
|
||||
|
||||
Setting<std::string> builders{
|
||||
|
@ -749,6 +746,13 @@ public:
|
|||
/nix/store/xfghy8ixrhz3kyy6p724iv3cxji088dx-bash-4.4-p23`.
|
||||
)"};
|
||||
|
||||
Setting<unsigned int> downloadSpeed {
|
||||
this, 0, "download-speed",
|
||||
R"(
|
||||
Specify the maximum transfer rate in kilobytes per second you want
|
||||
Nix to use for downloads.
|
||||
)"};
|
||||
|
||||
Setting<std::string> netrcFile{
|
||||
this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file",
|
||||
R"(
|
||||
|
@ -802,7 +806,7 @@ public:
|
|||
)"};
|
||||
|
||||
Setting<StringSet> ignoredAcls{
|
||||
this, {"security.selinux", "system.nfs4_acl"}, "ignored-acls",
|
||||
this, {"security.selinux", "system.nfs4_acl", "security.csm"}, "ignored-acls",
|
||||
R"(
|
||||
A list of ACLs that should be ignored, normally Nix attempts to
|
||||
remove all ACLs from files and directories in the Nix store, but
|
||||
|
|
|
@ -161,7 +161,12 @@ protected:
|
|||
void getFile(const std::string & path,
|
||||
Callback<std::optional<std::string>> callback) noexcept override
|
||||
{
|
||||
checkEnabled();
|
||||
try {
|
||||
checkEnabled();
|
||||
} catch (...) {
|
||||
callback.rethrow();
|
||||
return;
|
||||
}
|
||||
|
||||
auto request(makeRequest(path));
|
||||
|
||||
|
|
|
@ -57,8 +57,7 @@ protected:
|
|||
AutoDelete del(tmp, false);
|
||||
StreamToSourceAdapter source(istream);
|
||||
writeFile(tmp, source);
|
||||
if (rename(tmp.c_str(), path2.c_str()))
|
||||
throw SysError("renaming '%1%' to '%2%'", tmp, path2);
|
||||
renameFile(tmp, path2);
|
||||
del.cancel();
|
||||
}
|
||||
|
||||
|
@ -69,6 +68,7 @@ protected:
|
|||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOENT)
|
||||
throw NoSuchBinaryCacheFile("file '%s' does not exist in binary cache", path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ bool LocalBinaryCacheStore::fileExists(const std::string & path)
|
|||
|
||||
std::set<std::string> LocalBinaryCacheStore::uriSchemes()
|
||||
{
|
||||
if (getEnv("_NIX_FORCE_HTTP_BINARY_CACHE_STORE") == "1")
|
||||
if (getEnv("_NIX_FORCE_HTTP") == "1")
|
||||
return {};
|
||||
else
|
||||
return {"file"};
|
||||
|
|
|
@ -718,7 +718,11 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
|||
// somewhat expensive so we do lazily
|
||||
hashesModulo = hashDerivationModulo(*this, drv, true);
|
||||
}
|
||||
StorePath recomputed = makeOutputPath(i.first, hashesModulo->hashes.at(i.first), drvName);
|
||||
auto currentOutputHash = get(hashesModulo->hashes, i.first);
|
||||
if (!currentOutputHash)
|
||||
throw Error("derivation '%s' has unexpected output '%s' (local-store / hashesModulo) named '%s'",
|
||||
printStorePath(drvPath), printStorePath(doia.path), i.first);
|
||||
StorePath recomputed = makeOutputPath(i.first, *currentOutputHash, drvName);
|
||||
if (doia.path != recomputed)
|
||||
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
|
||||
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
||||
|
@ -1426,8 +1430,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
|||
writeFile(realPath, dumpSource);
|
||||
} else {
|
||||
/* Move the temporary path we restored above. */
|
||||
if (rename(tempPath.c_str(), realPath.c_str()))
|
||||
throw Error("renaming '%s' to '%s'", tempPath, realPath);
|
||||
moveFile(tempPath, realPath);
|
||||
}
|
||||
|
||||
/* For computing the nar hash. In recursive SHA-256 mode, this
|
||||
|
@ -1938,8 +1941,7 @@ void LocalStore::addBuildLog(const StorePath & drvPath, std::string_view log)
|
|||
|
||||
writeFile(tmpFile, compress("bzip2", log));
|
||||
|
||||
if (rename(tmpFile.c_str(), logPath.c_str()) != 0)
|
||||
throw SysError("renaming '%1%' to '%2%'", tmpFile, logPath);
|
||||
renameFile(tmpFile, logPath);
|
||||
}
|
||||
|
||||
std::optional<std::string> LocalStore::getVersion()
|
||||
|
|
|
@ -39,14 +39,23 @@ libstore_CXXFLAGS += \
|
|||
-DNIX_STATE_DIR=\"$(localstatedir)/nix\" \
|
||||
-DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \
|
||||
-DNIX_CONF_DIR=\"$(sysconfdir)/nix\" \
|
||||
-DNIX_LIBEXEC_DIR=\"$(libexecdir)\" \
|
||||
-DNIX_BIN_DIR=\"$(bindir)\" \
|
||||
-DNIX_MAN_DIR=\"$(mandir)\" \
|
||||
-DLSOF=\"$(lsof)\"
|
||||
|
||||
ifeq ($(embedded_sandbox_shell),yes)
|
||||
libstore_CXXFLAGS += -DSANDBOX_SHELL=\"__embedded_sandbox_shell__\"
|
||||
|
||||
$(d)/build/local-derivation-goal.cc: $(d)/embedded-sandbox-shell.gen.hh
|
||||
|
||||
$(d)/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
|
||||
$(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
|
||||
@mv $@.tmp $@
|
||||
else
|
||||
ifneq ($(sandbox_shell),)
|
||||
libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\""
|
||||
endif
|
||||
endif
|
||||
|
||||
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
|
||||
|
||||
|
|
|
@ -67,13 +67,26 @@ bool UserLock::findFreeUser() {
|
|||
#if __linux__
|
||||
/* Get the list of supplementary groups of this build user. This
|
||||
is usually either empty or contains a group such as "kvm". */
|
||||
supplementaryGIDs.resize(10);
|
||||
int ngroups = supplementaryGIDs.size();
|
||||
int err = getgrouplist(pw->pw_name, pw->pw_gid,
|
||||
supplementaryGIDs.data(), &ngroups);
|
||||
if (err == -1)
|
||||
throw Error("failed to get list of supplementary groups for '%1%'", pw->pw_name);
|
||||
int ngroups = 32; // arbitrary initial guess
|
||||
supplementaryGIDs.resize(ngroups);
|
||||
|
||||
int err = getgrouplist(pw->pw_name, pw->pw_gid, supplementaryGIDs.data(),
|
||||
&ngroups);
|
||||
|
||||
// Our initial size of 32 wasn't sufficient, the correct size has
|
||||
// been stored in ngroups, so we try again.
|
||||
if (err == -1) {
|
||||
supplementaryGIDs.resize(ngroups);
|
||||
err = getgrouplist(pw->pw_name, pw->pw_gid, supplementaryGIDs.data(),
|
||||
&ngroups);
|
||||
}
|
||||
|
||||
// If it failed once more, then something must be broken.
|
||||
if (err == -1)
|
||||
throw Error("failed to get list of supplementary groups for '%1%'",
|
||||
pw->pw_name);
|
||||
|
||||
// Finally, trim back the GID list to its real size
|
||||
supplementaryGIDs.resize(ngroups);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -278,11 +278,16 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
|||
std::set<Realisation> inputRealisations;
|
||||
|
||||
for (const auto & [inputDrv, outputNames] : drv.inputDrvs) {
|
||||
auto outputHashes =
|
||||
const auto outputHashes =
|
||||
staticOutputHashes(store, store.readDerivation(inputDrv));
|
||||
for (const auto & outputName : outputNames) {
|
||||
auto outputHash = get(outputHashes, outputName);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"output '%s' of derivation '%s' isn't realised", outputName,
|
||||
store.printStorePath(inputDrv));
|
||||
auto thisRealisation = store.queryRealisation(
|
||||
DrvOutput{outputHashes.at(outputName), outputName});
|
||||
DrvOutput{*outputHash, outputName});
|
||||
if (!thisRealisation)
|
||||
throw Error(
|
||||
"output '%s' of derivation '%s' isn't built", outputName,
|
||||
|
|
|
@ -62,6 +62,9 @@ public:
|
|||
/* How often to purge expired entries from the cache. */
|
||||
const int purgeInterval = 24 * 3600;
|
||||
|
||||
/* How long to cache binary cache info (i.e. /nix-cache-info) */
|
||||
const int cacheInfoTtl = 7 * 24 * 3600;
|
||||
|
||||
struct Cache
|
||||
{
|
||||
int id;
|
||||
|
@ -98,7 +101,7 @@ public:
|
|||
"insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)");
|
||||
|
||||
state->queryCache.create(state->db,
|
||||
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ?");
|
||||
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ?");
|
||||
|
||||
state->insertNAR.create(state->db,
|
||||
"insert or replace into NARs(cache, hashPart, namePart, url, compression, fileHash, fileSize, narHash, "
|
||||
|
@ -183,7 +186,7 @@ public:
|
|||
|
||||
auto i = state->caches.find(uri);
|
||||
if (i == state->caches.end()) {
|
||||
auto queryCache(state->queryCache.use()(uri));
|
||||
auto queryCache(state->queryCache.use()(uri)(time(0) - cacheInfoTtl));
|
||||
if (!queryCache.next())
|
||||
return std::nullopt;
|
||||
state->caches.emplace(uri,
|
||||
|
|
|
@ -69,8 +69,6 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
|||
if (value != "unknown-deriver")
|
||||
deriver = StorePath(value);
|
||||
}
|
||||
else if (name == "System")
|
||||
system = value;
|
||||
else if (name == "Sig")
|
||||
sigs.insert(value);
|
||||
else if (name == "CA") {
|
||||
|
@ -106,9 +104,6 @@ std::string NarInfo::to_string(const Store & store) const
|
|||
if (deriver)
|
||||
res += "Deriver: " + std::string(deriver->to_string()) + "\n";
|
||||
|
||||
if (!system.empty())
|
||||
res += "System: " + system + "\n";
|
||||
|
||||
for (auto sig : sigs)
|
||||
res += "Sig: " + sig + "\n";
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ struct NarInfo : ValidPathInfo
|
|||
std::string compression;
|
||||
std::optional<Hash> fileHash;
|
||||
uint64_t fileSize = 0;
|
||||
std::string system;
|
||||
|
||||
NarInfo() = delete;
|
||||
NarInfo(StorePath && path, Hash narHash) : ValidPathInfo(std::move(path), narHash) { }
|
||||
|
|
|
@ -229,7 +229,9 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
|||
}
|
||||
|
||||
/* Atomically replace the old file with the new hard link. */
|
||||
if (rename(tempLink.c_str(), path.c_str()) == -1) {
|
||||
try {
|
||||
renameFile(tempLink, path);
|
||||
} catch (SysError & e) {
|
||||
if (unlink(tempLink.c_str()) == -1)
|
||||
printError("unable to unlink '%1%'", tempLink);
|
||||
if (errno == EMLINK) {
|
||||
|
@ -240,7 +242,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
|
|||
debug("'%s' has reached maximum number of links", linkPath);
|
||||
return;
|
||||
}
|
||||
throw SysError("cannot rename '%1%' to '%2%'", tempLink, path);
|
||||
throw;
|
||||
}
|
||||
|
||||
stats.filesLinked++;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include "path-with-outputs.hh"
|
||||
#include "store-api.hh"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -68,4 +71,57 @@ StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std:
|
|||
return StorePathWithOutputs { store.followLinksToStorePath(path), std::move(outputs) };
|
||||
}
|
||||
|
||||
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s)
|
||||
{
|
||||
static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))");
|
||||
|
||||
std::smatch match;
|
||||
if (!std::regex_match(s, match, regex))
|
||||
return {s, DefaultOutputs()};
|
||||
|
||||
if (match[3].matched)
|
||||
return {match[1], AllOutputs()};
|
||||
|
||||
return {match[1], tokenizeString<OutputNames>(match[4].str(), ",")};
|
||||
}
|
||||
|
||||
std::string printOutputsSpec(const OutputsSpec & outputsSpec)
|
||||
{
|
||||
if (std::get_if<DefaultOutputs>(&outputsSpec))
|
||||
return "";
|
||||
|
||||
if (std::get_if<AllOutputs>(&outputsSpec))
|
||||
return "^*";
|
||||
|
||||
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
return "^" + concatStringsSep(",", *outputNames);
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec)
|
||||
{
|
||||
if (std::get_if<DefaultOutputs>(&outputsSpec))
|
||||
json = nullptr;
|
||||
|
||||
else if (std::get_if<AllOutputs>(&outputsSpec))
|
||||
json = std::vector<std::string>({"*"});
|
||||
|
||||
else if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
|
||||
json = *outputNames;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec)
|
||||
{
|
||||
if (json.is_null())
|
||||
outputsSpec = DefaultOutputs();
|
||||
else {
|
||||
auto names = json.get<OutputNames>();
|
||||
if (names == OutputNames({"*"}))
|
||||
outputsSpec = AllOutputs();
|
||||
else
|
||||
outputsSpec = names;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "path.hh"
|
||||
#include "derived-path.hh"
|
||||
#include "nlohmann/json_fwd.hpp"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -32,4 +33,25 @@ StorePathWithOutputs parsePathWithOutputs(const Store & store, std::string_view
|
|||
|
||||
StorePathWithOutputs followLinksToStorePathWithOutputs(const Store & store, std::string_view pathWithOutputs);
|
||||
|
||||
typedef std::set<std::string> OutputNames;
|
||||
|
||||
struct AllOutputs {
|
||||
bool operator < (const AllOutputs & _) const { return false; }
|
||||
};
|
||||
|
||||
struct DefaultOutputs {
|
||||
bool operator < (const DefaultOutputs & _) const { return false; }
|
||||
};
|
||||
|
||||
typedef std::variant<DefaultOutputs, AllOutputs, OutputNames> OutputsSpec;
|
||||
|
||||
/* Parse a string of the form 'prefix^output1,...outputN' or
|
||||
'prefix^*', returning the prefix and the outputs spec. */
|
||||
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s);
|
||||
|
||||
std::string printOutputsSpec(const OutputsSpec & outputsSpec);
|
||||
|
||||
void to_json(nlohmann::json &, const OutputsSpec &);
|
||||
void from_json(const nlohmann::json &, OutputsSpec &);
|
||||
|
||||
}
|
||||
|
|
|
@ -580,7 +580,6 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
|
||||
try {
|
||||
conn->to.written = 0;
|
||||
conn->to.warn = true;
|
||||
connections->incCapacity();
|
||||
{
|
||||
Finally cleanup([&]() { connections->decCapacity(); });
|
||||
|
@ -591,7 +590,6 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
|
|||
dumpString(contents, conn->to);
|
||||
}
|
||||
}
|
||||
conn->to.warn = false;
|
||||
conn.processStderr();
|
||||
} catch (SysError & e) {
|
||||
/* Daemon closed while we were sending the path. Probably OOM
|
||||
|
@ -673,6 +671,23 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
|
|||
}
|
||||
|
||||
|
||||
void RemoteStore::addMultipleToStore(
|
||||
PathsSource & pathsToCopy,
|
||||
Activity & act,
|
||||
RepairFlag repair,
|
||||
CheckSigsFlag checkSigs)
|
||||
{
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
sink << pathsToCopy.size();
|
||||
for (auto & [pathInfo, pathSource] : pathsToCopy) {
|
||||
pathInfo.write(sink, *this, 16);
|
||||
pathSource->drainInto(sink);
|
||||
}
|
||||
});
|
||||
|
||||
addMultipleToStore(*source, repair, checkSigs);
|
||||
}
|
||||
|
||||
void RemoteStore::addMultipleToStore(
|
||||
Source & source,
|
||||
RepairFlag repair,
|
||||
|
@ -718,36 +733,34 @@ void RemoteStore::registerDrvOutput(const Realisation & info)
|
|||
void RemoteStore::queryRealisationUncached(const DrvOutput & id,
|
||||
Callback<std::shared_ptr<const Realisation>> callback) noexcept
|
||||
{
|
||||
auto conn(getConnection());
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 27) {
|
||||
warn("the daemon is too old to support content-addressed derivations, please upgrade it to 2.4");
|
||||
try {
|
||||
callback(nullptr);
|
||||
} catch (...) { return callback.rethrow(); }
|
||||
}
|
||||
|
||||
conn->to << wopQueryRealisation;
|
||||
conn->to << id.to_string();
|
||||
conn.processStderr();
|
||||
|
||||
auto real = [&]() -> std::shared_ptr<const Realisation> {
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 31) {
|
||||
auto outPaths = worker_proto::read(
|
||||
*this, conn->from, Phantom<std::set<StorePath>> {});
|
||||
if (outPaths.empty())
|
||||
return nullptr;
|
||||
return std::make_shared<const Realisation>(Realisation { .id = id, .outPath = *outPaths.begin() });
|
||||
} else {
|
||||
auto realisations = worker_proto::read(
|
||||
*this, conn->from, Phantom<std::set<Realisation>> {});
|
||||
if (realisations.empty())
|
||||
return nullptr;
|
||||
return std::make_shared<const Realisation>(*realisations.begin());
|
||||
}
|
||||
}();
|
||||
|
||||
try {
|
||||
auto conn(getConnection());
|
||||
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 27) {
|
||||
warn("the daemon is too old to support content-addressed derivations, please upgrade it to 2.4");
|
||||
return callback(nullptr);
|
||||
}
|
||||
|
||||
conn->to << wopQueryRealisation;
|
||||
conn->to << id.to_string();
|
||||
conn.processStderr();
|
||||
|
||||
auto real = [&]() -> std::shared_ptr<const Realisation> {
|
||||
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 31) {
|
||||
auto outPaths = worker_proto::read(
|
||||
*this, conn->from, Phantom<std::set<StorePath>> {});
|
||||
if (outPaths.empty())
|
||||
return nullptr;
|
||||
return std::make_shared<const Realisation>(Realisation { .id = id, .outPath = *outPaths.begin() });
|
||||
} else {
|
||||
auto realisations = worker_proto::read(
|
||||
*this, conn->from, Phantom<std::set<Realisation>> {});
|
||||
if (realisations.empty())
|
||||
return nullptr;
|
||||
return std::make_shared<const Realisation>(*realisations.begin());
|
||||
}
|
||||
}();
|
||||
|
||||
callback(std::shared_ptr<const Realisation>(real));
|
||||
} catch (...) { return callback.rethrow(); }
|
||||
}
|
||||
|
@ -853,15 +866,15 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
|
|||
|
||||
OutputPathMap outputs;
|
||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
auto drvOutputs = drv.outputsAndOptPaths(*this);
|
||||
const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||
const auto drvOutputs = drv.outputsAndOptPaths(*this);
|
||||
for (auto & output : bfd.outputs) {
|
||||
if (!outputHashes.count(output))
|
||||
auto outputHash = get(outputHashes, output);
|
||||
if (!outputHash)
|
||||
throw Error(
|
||||
"the derivation '%s' doesn't have an output named '%s'",
|
||||
printStorePath(bfd.drvPath), output);
|
||||
auto outputId =
|
||||
DrvOutput{outputHashes.at(output), output};
|
||||
auto outputId = DrvOutput{ *outputHash, output };
|
||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||
auto realisation =
|
||||
queryRealisation(outputId);
|
||||
|
@ -874,13 +887,14 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
|
|||
} else {
|
||||
// If ca-derivations isn't enabled, assume that
|
||||
// the output path is statically known.
|
||||
assert(drvOutputs.count(output));
|
||||
assert(drvOutputs.at(output).second);
|
||||
const auto drvOutput = get(drvOutputs, output);
|
||||
assert(drvOutput);
|
||||
assert(drvOutput->second);
|
||||
res.builtOutputs.emplace(
|
||||
outputId,
|
||||
Realisation {
|
||||
.id = outputId,
|
||||
.outPath = *drvOutputs.at(output).second
|
||||
.outPath = *drvOutput->second,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,12 @@ public:
|
|||
RepairFlag repair,
|
||||
CheckSigsFlag checkSigs) override;
|
||||
|
||||
void addMultipleToStore(
|
||||
PathsSource & pathsToCopy,
|
||||
Activity & act,
|
||||
RepairFlag repair,
|
||||
CheckSigsFlag checkSigs) override;
|
||||
|
||||
StorePath addTextToStore(
|
||||
std::string_view name,
|
||||
std::string_view s,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "ref.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Aws { namespace Client { class ClientConfiguration; } }
|
||||
namespace Aws { namespace S3 { class S3Client; } }
|
||||
|
|
|
@ -98,7 +98,9 @@
|
|||
(allow file*
|
||||
(literal "/private/var/select/sh"))
|
||||
|
||||
; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin.
|
||||
; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin (and vice versa).
|
||||
(allow file-read*
|
||||
(subpath "/Library/Apple/usr/libexec/oah")
|
||||
(subpath "/System/Library/Apple/usr/libexec/oah"))
|
||||
(subpath "/System/Library/Apple/usr/libexec/oah")
|
||||
(subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
|
||||
(subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))
|
||||
|
|
|
@ -14,3 +14,7 @@
|
|||
|
||||
; Allow DNS lookups.
|
||||
(allow network-outbound (remote unix-socket (path-literal "/private/var/run/mDNSResponder")))
|
||||
|
||||
; Allow access to trustd.
|
||||
(allow mach-lookup (global-name "com.apple.trustd"))
|
||||
(allow mach-lookup (global-name "com.apple.trustd.agent"))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
create table if not exists ValidPaths (
|
||||
id integer primary key autoincrement not null,
|
||||
path text unique not null,
|
||||
hash text not null,
|
||||
hash text not null, -- base16 representation
|
||||
registrationTime integer not null,
|
||||
deriver text,
|
||||
narSize integer,
|
||||
|
|
|
@ -258,6 +258,84 @@ StorePath Store::addToStore(
|
|||
return addToStoreFromDump(*source, name, method, hashAlgo, repair, references);
|
||||
}
|
||||
|
||||
void Store::addMultipleToStore(
|
||||
PathsSource & pathsToCopy,
|
||||
Activity & act,
|
||||
RepairFlag repair,
|
||||
CheckSigsFlag checkSigs)
|
||||
{
|
||||
std::atomic<size_t> nrDone{0};
|
||||
std::atomic<size_t> nrFailed{0};
|
||||
std::atomic<uint64_t> bytesExpected{0};
|
||||
std::atomic<uint64_t> nrRunning{0};
|
||||
|
||||
using PathWithInfo = std::pair<ValidPathInfo, std::unique_ptr<Source>>;
|
||||
|
||||
std::map<StorePath, PathWithInfo *> infosMap;
|
||||
StorePathSet storePathsToAdd;
|
||||
for (auto & thingToAdd : pathsToCopy) {
|
||||
infosMap.insert_or_assign(thingToAdd.first.path, &thingToAdd);
|
||||
storePathsToAdd.insert(thingToAdd.first.path);
|
||||
}
|
||||
|
||||
auto showProgress = [&]() {
|
||||
act.progress(nrDone, pathsToCopy.size(), nrRunning, nrFailed);
|
||||
};
|
||||
|
||||
ThreadPool pool;
|
||||
|
||||
processGraph<StorePath>(pool,
|
||||
storePathsToAdd,
|
||||
|
||||
[&](const StorePath & path) {
|
||||
|
||||
auto & [info, _] = *infosMap.at(path);
|
||||
|
||||
if (isValidPath(info.path)) {
|
||||
nrDone++;
|
||||
showProgress();
|
||||
return StorePathSet();
|
||||
}
|
||||
|
||||
bytesExpected += info.narSize;
|
||||
act.setExpected(actCopyPath, bytesExpected);
|
||||
|
||||
return info.references;
|
||||
},
|
||||
|
||||
[&](const StorePath & path) {
|
||||
checkInterrupt();
|
||||
|
||||
auto & [info_, source_] = *infosMap.at(path);
|
||||
auto info = info_;
|
||||
info.ultimate = false;
|
||||
|
||||
/* Make sure that the Source object is destroyed when
|
||||
we're done. In particular, a SinkToSource object must
|
||||
be destroyed to ensure that the destructors on its
|
||||
stack frame are run; this includes
|
||||
LegacySSHStore::narFromPath()'s connection lock. */
|
||||
auto source = std::move(source_);
|
||||
|
||||
if (!isValidPath(info.path)) {
|
||||
MaintainCount<decltype(nrRunning)> mc(nrRunning);
|
||||
showProgress();
|
||||
try {
|
||||
addToStore(info, *source, repair, checkSigs);
|
||||
} catch (Error & e) {
|
||||
nrFailed++;
|
||||
if (!settings.keepGoing)
|
||||
throw e;
|
||||
printMsg(lvlError, "could not copy %s: %s", printStorePath(path), e.what());
|
||||
showProgress();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nrDone++;
|
||||
showProgress();
|
||||
});
|
||||
}
|
||||
|
||||
void Store::addMultipleToStore(
|
||||
Source & source,
|
||||
|
@ -992,113 +1070,61 @@ std::map<StorePath, StorePath> copyPaths(
|
|||
for (auto & path : storePaths)
|
||||
if (!valid.count(path)) missing.insert(path);
|
||||
|
||||
Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
|
||||
|
||||
// In the general case, `addMultipleToStore` requires a sorted list of
|
||||
// store paths to add, so sort them right now
|
||||
auto sortedMissing = srcStore.topoSortPaths(missing);
|
||||
std::reverse(sortedMissing.begin(), sortedMissing.end());
|
||||
|
||||
std::map<StorePath, StorePath> pathsMap;
|
||||
for (auto & path : storePaths)
|
||||
pathsMap.insert_or_assign(path, path);
|
||||
|
||||
Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
|
||||
Store::PathsSource pathsToCopy;
|
||||
|
||||
auto sorted = srcStore.topoSortPaths(missing);
|
||||
std::reverse(sorted.begin(), sorted.end());
|
||||
auto computeStorePathForDst = [&](const ValidPathInfo & currentPathInfo) -> StorePath {
|
||||
auto storePathForSrc = currentPathInfo.path;
|
||||
auto storePathForDst = storePathForSrc;
|
||||
if (currentPathInfo.ca && currentPathInfo.references.empty()) {
|
||||
storePathForDst = dstStore.makeFixedOutputPathFromCA(storePathForSrc.name(), *currentPathInfo.ca);
|
||||
if (dstStore.storeDir == srcStore.storeDir)
|
||||
assert(storePathForDst == storePathForSrc);
|
||||
if (storePathForDst != storePathForSrc)
|
||||
debug("replaced path '%s' to '%s' for substituter '%s'",
|
||||
srcStore.printStorePath(storePathForSrc),
|
||||
dstStore.printStorePath(storePathForDst),
|
||||
dstStore.getUri());
|
||||
}
|
||||
return storePathForDst;
|
||||
};
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
sink << sorted.size();
|
||||
for (auto & storePath : sorted) {
|
||||
for (auto & missingPath : sortedMissing) {
|
||||
auto info = srcStore.queryPathInfo(missingPath);
|
||||
|
||||
auto storePathForDst = computeStorePathForDst(*info);
|
||||
pathsMap.insert_or_assign(missingPath, storePathForDst);
|
||||
|
||||
ValidPathInfo infoForDst = *info;
|
||||
infoForDst.path = storePathForDst;
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) {
|
||||
// We can reasonably assume that the copy will happen whenever we
|
||||
// read the path, so log something about that at that point
|
||||
auto srcUri = srcStore.getUri();
|
||||
auto dstUri = dstStore.getUri();
|
||||
auto storePathS = srcStore.printStorePath(storePath);
|
||||
auto storePathS = srcStore.printStorePath(missingPath);
|
||||
Activity act(*logger, lvlInfo, actCopyPath,
|
||||
makeCopyPathMessage(srcUri, dstUri, storePathS),
|
||||
{storePathS, srcUri, dstUri});
|
||||
PushActivity pact(act.id);
|
||||
|
||||
auto info = srcStore.queryPathInfo(storePath);
|
||||
info->write(sink, srcStore, 16);
|
||||
srcStore.narFromPath(storePath, sink);
|
||||
}
|
||||
});
|
||||
|
||||
dstStore.addMultipleToStore(*source, repair, checkSigs);
|
||||
|
||||
#if 0
|
||||
std::atomic<size_t> nrDone{0};
|
||||
std::atomic<size_t> nrFailed{0};
|
||||
std::atomic<uint64_t> bytesExpected{0};
|
||||
std::atomic<uint64_t> nrRunning{0};
|
||||
|
||||
auto showProgress = [&]() {
|
||||
act.progress(nrDone, missing.size(), nrRunning, nrFailed);
|
||||
};
|
||||
|
||||
ThreadPool pool;
|
||||
|
||||
processGraph<StorePath>(pool,
|
||||
StorePathSet(missing.begin(), missing.end()),
|
||||
|
||||
[&](const StorePath & storePath) {
|
||||
auto info = srcStore.queryPathInfo(storePath);
|
||||
auto storePathForDst = storePath;
|
||||
if (info->ca && info->references.empty()) {
|
||||
storePathForDst = dstStore.makeFixedOutputPathFromCA(storePath.name(), *info->ca);
|
||||
if (dstStore.storeDir == srcStore.storeDir)
|
||||
assert(storePathForDst == storePath);
|
||||
if (storePathForDst != storePath)
|
||||
debug("replaced path '%s' to '%s' for substituter '%s'",
|
||||
srcStore.printStorePath(storePath),
|
||||
dstStore.printStorePath(storePathForDst),
|
||||
dstStore.getUri());
|
||||
}
|
||||
pathsMap.insert_or_assign(storePath, storePathForDst);
|
||||
|
||||
if (dstStore.isValidPath(storePath)) {
|
||||
nrDone++;
|
||||
showProgress();
|
||||
return StorePathSet();
|
||||
}
|
||||
|
||||
bytesExpected += info->narSize;
|
||||
act.setExpected(actCopyPath, bytesExpected);
|
||||
|
||||
return info->references;
|
||||
},
|
||||
|
||||
[&](const StorePath & storePath) {
|
||||
checkInterrupt();
|
||||
|
||||
auto info = srcStore.queryPathInfo(storePath);
|
||||
|
||||
auto storePathForDst = storePath;
|
||||
if (info->ca && info->references.empty()) {
|
||||
storePathForDst = dstStore.makeFixedOutputPathFromCA(storePath.name(), *info->ca);
|
||||
if (dstStore.storeDir == srcStore.storeDir)
|
||||
assert(storePathForDst == storePath);
|
||||
if (storePathForDst != storePath)
|
||||
debug("replaced path '%s' to '%s' for substituter '%s'",
|
||||
srcStore.printStorePath(storePath),
|
||||
dstStore.printStorePath(storePathForDst),
|
||||
dstStore.getUri());
|
||||
}
|
||||
pathsMap.insert_or_assign(storePath, storePathForDst);
|
||||
|
||||
if (!dstStore.isValidPath(storePathForDst)) {
|
||||
MaintainCount<decltype(nrRunning)> mc(nrRunning);
|
||||
showProgress();
|
||||
try {
|
||||
copyStorePath(srcStore, dstStore, storePath, repair, checkSigs);
|
||||
} catch (Error &e) {
|
||||
nrFailed++;
|
||||
if (!settings.keepGoing)
|
||||
throw e;
|
||||
printMsg(lvlError, "could not copy %s: %s", dstStore.printStorePath(storePath), e.what());
|
||||
showProgress();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nrDone++;
|
||||
showProgress();
|
||||
srcStore.narFromPath(missingPath, sink);
|
||||
});
|
||||
#endif
|
||||
pathsToCopy.push_back(std::pair{infoForDst, std::move(source)});
|
||||
}
|
||||
|
||||
dstStore.addMultipleToStore(pathsToCopy, act, repair, checkSigs);
|
||||
|
||||
return pathsMap;
|
||||
}
|
||||
|
@ -1302,7 +1328,8 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri_
|
|||
return {uri, params};
|
||||
}
|
||||
|
||||
static bool isNonUriPath(const std::string & spec) {
|
||||
static bool isNonUriPath(const std::string & spec)
|
||||
{
|
||||
return
|
||||
// is not a URL
|
||||
spec.find("://") == std::string::npos
|
||||
|
@ -1314,11 +1341,36 @@ static bool isNonUriPath(const std::string & spec) {
|
|||
std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Params & params)
|
||||
{
|
||||
if (uri == "" || uri == "auto") {
|
||||
auto stateDir = get(params, "state").value_or(settings.nixStateDir);
|
||||
auto stateDir = getOr(params, "state", settings.nixStateDir);
|
||||
if (access(stateDir.c_str(), R_OK | W_OK) == 0)
|
||||
return std::make_shared<LocalStore>(params);
|
||||
else if (pathExists(settings.nixDaemonSocketFile))
|
||||
return std::make_shared<UDSRemoteStore>(params);
|
||||
#if __linux__
|
||||
else if (!pathExists(stateDir)
|
||||
&& params.empty()
|
||||
&& getuid() != 0
|
||||
&& !getEnv("NIX_STORE_DIR").has_value()
|
||||
&& !getEnv("NIX_STATE_DIR").has_value())
|
||||
{
|
||||
/* If /nix doesn't exist, there is no daemon socket, and
|
||||
we're not root, then automatically set up a chroot
|
||||
store in ~/.local/share/nix/root. */
|
||||
auto chrootStore = getDataDir() + "/nix/root";
|
||||
if (!pathExists(chrootStore)) {
|
||||
try {
|
||||
createDirs(chrootStore);
|
||||
} catch (Error & e) {
|
||||
return std::make_shared<LocalStore>(params);
|
||||
}
|
||||
warn("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
|
||||
} else
|
||||
debug("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
|
||||
Store::Params params2;
|
||||
params2["root"] = chrootStore;
|
||||
return std::make_shared<LocalStore>(params2);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
return std::make_shared<LocalStore>(params);
|
||||
} else if (uri == "daemon") {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "nar-info.hh"
|
||||
#include "realisation.hh"
|
||||
#include "path.hh"
|
||||
#include "derived-path.hh"
|
||||
|
@ -359,12 +360,22 @@ public:
|
|||
virtual void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs) = 0;
|
||||
|
||||
// A list of paths infos along with a source providing the content of the
|
||||
// associated store path
|
||||
using PathsSource = std::vector<std::pair<ValidPathInfo, std::unique_ptr<Source>>>;
|
||||
|
||||
/* Import multiple paths into the store. */
|
||||
virtual void addMultipleToStore(
|
||||
Source & source,
|
||||
RepairFlag repair = NoRepair,
|
||||
CheckSigsFlag checkSigs = CheckSigs);
|
||||
|
||||
virtual void addMultipleToStore(
|
||||
PathsSource & pathsToCopy,
|
||||
Activity & act,
|
||||
RepairFlag repair = NoRepair,
|
||||
CheckSigsFlag checkSigs = CheckSigs);
|
||||
|
||||
/* Copy the contents of a path to the store and register the
|
||||
validity the resulting path. The resulting path is returned.
|
||||
The function object `filter' can be used to exclude files (see
|
||||
|
|
46
src/libstore/tests/path-with-outputs.cc
Normal file
46
src/libstore/tests/path-with-outputs.cc
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "path-with-outputs.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(parseOutputsSpec, basic)
|
||||
{
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^*");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get_if<AllOutputs>(&outputsSpec));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out"}));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin");
|
||||
ASSERT_EQ(prefix, "foo");
|
||||
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin");
|
||||
ASSERT_EQ(prefix, "foo^bar");
|
||||
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
|
||||
}
|
||||
|
||||
{
|
||||
auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()");
|
||||
ASSERT_EQ(prefix, "foo^&*()");
|
||||
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -124,7 +124,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
|
|||
bool anyCompleted = false;
|
||||
for (size_t n = 0 ; n < flag.handler.arity; ++n) {
|
||||
if (pos == end) {
|
||||
if (flag.handler.arity == ArityAny) break;
|
||||
if (flag.handler.arity == ArityAny || anyCompleted) break;
|
||||
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
|
||||
}
|
||||
if (auto prefix = needsCompletion(*pos)) {
|
||||
|
@ -362,6 +362,14 @@ bool MultiCommand::processArgs(const Strings & args, bool finish)
|
|||
return Args::processArgs(args, finish);
|
||||
}
|
||||
|
||||
void MultiCommand::completionHook()
|
||||
{
|
||||
if (command)
|
||||
return command->second->completionHook();
|
||||
else
|
||||
return Args::completionHook();
|
||||
}
|
||||
|
||||
nlohmann::json MultiCommand::toJSON()
|
||||
{
|
||||
auto cmds = nlohmann::json::object();
|
||||
|
|
|
@ -25,6 +25,8 @@ public:
|
|||
/* Return a short one-line description of the command. */
|
||||
virtual std::string description() { return ""; }
|
||||
|
||||
virtual bool forceImpureByDefault() { return false; }
|
||||
|
||||
/* Return documentation about this command, in Markdown format. */
|
||||
virtual std::string doc() { return ""; }
|
||||
|
||||
|
@ -146,6 +148,11 @@ protected:
|
|||
argument (if any) have been processed. */
|
||||
virtual void initialFlagsProcessed() {}
|
||||
|
||||
/* Called after the command line has been processed if we need to generate
|
||||
completions. Useful for commands that need to know the whole command line
|
||||
in order to know what completions to generate. */
|
||||
virtual void completionHook() { }
|
||||
|
||||
public:
|
||||
|
||||
void addFlag(Flag && flag);
|
||||
|
@ -221,6 +228,8 @@ public:
|
|||
|
||||
bool processArgs(const Strings & args, bool finish) override;
|
||||
|
||||
void completionHook() override;
|
||||
|
||||
nlohmann::json toJSON() override;
|
||||
};
|
||||
|
||||
|
|
|
@ -98,6 +98,15 @@ struct ErrPos {
|
|||
}
|
||||
};
|
||||
|
||||
std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
|
||||
|
||||
void printCodeLines(std::ostream & out,
|
||||
const std::string & prefix,
|
||||
const ErrPos & errPos,
|
||||
const LinesOfCode & loc);
|
||||
|
||||
void printAtPos(const ErrPos & pos, std::ostream & out);
|
||||
|
||||
struct Trace {
|
||||
std::optional<ErrPos> pos;
|
||||
hintformat hint;
|
||||
|
@ -201,13 +210,19 @@ public:
|
|||
int errNo;
|
||||
|
||||
template<typename... Args>
|
||||
SysError(const Args & ... args)
|
||||
SysError(int errNo_, const Args & ... args)
|
||||
: Error("")
|
||||
{
|
||||
errNo = errno;
|
||||
errNo = errNo_;
|
||||
auto hf = hintfmt(args...);
|
||||
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
SysError(const Args & ... args)
|
||||
: SysError(errno, args ...)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
|
|||
{ Xp::RecursiveNix, "recursive-nix" },
|
||||
{ Xp::NoUrlLiterals, "no-url-literals" },
|
||||
{ Xp::FetchClosure, "fetch-closure" },
|
||||
{ Xp::ReplFlake, "repl-flake" },
|
||||
};
|
||||
|
||||
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
||||
|
@ -35,7 +36,9 @@ const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::str
|
|||
|
||||
std::string_view showExperimentalFeature(const ExperimentalFeature feature)
|
||||
{
|
||||
return stringifiedXpFeatures.at(feature);
|
||||
const auto ret = get(stringifiedXpFeatures, feature);
|
||||
assert(ret);
|
||||
return *ret;
|
||||
}
|
||||
|
||||
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
|
||||
|
@ -58,11 +61,13 @@ std::ostream & operator <<(std::ostream & str, const ExperimentalFeature & featu
|
|||
return str << showExperimentalFeature(feature);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const ExperimentalFeature& feature) {
|
||||
void to_json(nlohmann::json & j, const ExperimentalFeature & feature)
|
||||
{
|
||||
j = showExperimentalFeature(feature);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, ExperimentalFeature& feature) {
|
||||
void from_json(const nlohmann::json & j, ExperimentalFeature & feature)
|
||||
{
|
||||
const std::string input = j;
|
||||
const auto parsed = parseExperimentalFeature(input);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ enum struct ExperimentalFeature
|
|||
RecursiveNix,
|
||||
NoUrlLiterals,
|
||||
FetchClosure,
|
||||
ReplFlake,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -55,7 +56,7 @@ public:
|
|||
* Semi-magic conversion to and from json.
|
||||
* See the nlohmann/json readme for more details.
|
||||
*/
|
||||
void to_json(nlohmann::json&, const ExperimentalFeature&);
|
||||
void from_json(const nlohmann::json&, ExperimentalFeature&);
|
||||
void to_json(nlohmann::json &, const ExperimentalFeature &);
|
||||
void from_json(const nlohmann::json &, ExperimentalFeature &);
|
||||
|
||||
}
|
||||
|
|
172
src/libutil/filesystem.cc
Normal file
172
src/libutil/filesystem.cc
Normal file
|
@ -0,0 +1,172 @@
|
|||
#include <sys/time.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "finally.hh"
|
||||
#include "util.hh"
|
||||
#include "types.hh"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace nix {
|
||||
|
||||
static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
|
||||
int & counter)
|
||||
{
|
||||
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
|
||||
if (includePid)
|
||||
return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
|
||||
else
|
||||
return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
|
||||
}
|
||||
|
||||
Path createTempDir(const Path & tmpRoot, const Path & prefix,
|
||||
bool includePid, bool useGlobalCounter, mode_t mode)
|
||||
{
|
||||
static int globalCounter = 0;
|
||||
int localCounter = 0;
|
||||
int & counter(useGlobalCounter ? globalCounter : localCounter);
|
||||
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
|
||||
if (mkdir(tmpDir.c_str(), mode) == 0) {
|
||||
#if __FreeBSD__
|
||||
/* Explicitly set the group of the directory. This is to
|
||||
work around around problems caused by BSD's group
|
||||
ownership semantics (directories inherit the group of
|
||||
the parent). For instance, the group of /tmp on
|
||||
FreeBSD is "wheel", so all directories created in /tmp
|
||||
will be owned by "wheel"; but if the user is not in
|
||||
"wheel", then "tar" will fail to unpack archives that
|
||||
have the setgid bit set on directories. */
|
||||
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
|
||||
throw SysError("setting group of directory '%1%'", tmpDir);
|
||||
#endif
|
||||
return tmpDir;
|
||||
}
|
||||
if (errno != EEXIST)
|
||||
throw SysError("creating directory '%1%'", tmpDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
||||
{
|
||||
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
|
||||
// Strictly speaking, this is UB, but who cares...
|
||||
// FIXME: use O_TMPFILE.
|
||||
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
|
||||
if (!fd)
|
||||
throw SysError("creating temporary file '%s'", tmpl);
|
||||
closeOnExec(fd.get());
|
||||
return {std::move(fd), tmpl};
|
||||
}
|
||||
|
||||
void createSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime)
|
||||
{
|
||||
if (symlink(target.c_str(), link.c_str()))
|
||||
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
|
||||
if (mtime) {
|
||||
struct timeval times[2];
|
||||
times[0].tv_sec = *mtime;
|
||||
times[0].tv_usec = 0;
|
||||
times[1].tv_sec = *mtime;
|
||||
times[1].tv_usec = 0;
|
||||
if (lutimes(link.c_str(), times))
|
||||
throw SysError("setting time of symlink '%s'", link);
|
||||
}
|
||||
}
|
||||
|
||||
void replaceSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime)
|
||||
{
|
||||
for (unsigned int n = 0; true; n++) {
|
||||
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
|
||||
|
||||
try {
|
||||
createSymlink(target, tmp, mtime);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == EEXIST) continue;
|
||||
throw;
|
||||
}
|
||||
|
||||
renameFile(tmp, link);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setWriteTime(const fs::path & p, const struct stat & st)
|
||||
{
|
||||
struct timeval times[2];
|
||||
times[0] = {
|
||||
.tv_sec = st.st_atime,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
times[1] = {
|
||||
.tv_sec = st.st_mtime,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
if (lutimes(p.c_str(), times) != 0)
|
||||
throw SysError("changing modification time of '%s'", p);
|
||||
}
|
||||
|
||||
void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
|
||||
{
|
||||
// TODO: Rewrite the `is_*` to use `symlink_status()`
|
||||
auto statOfFrom = lstat(from.path().c_str());
|
||||
auto fromStatus = from.symlink_status();
|
||||
|
||||
// Mark the directory as writable so that we can delete its children
|
||||
if (andDelete && fs::is_directory(fromStatus)) {
|
||||
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
|
||||
}
|
||||
|
||||
|
||||
if (fs::is_symlink(fromStatus) || fs::is_regular_file(fromStatus)) {
|
||||
fs::copy(from.path(), to, fs::copy_options::copy_symlinks | fs::copy_options::overwrite_existing);
|
||||
} else if (fs::is_directory(fromStatus)) {
|
||||
fs::create_directory(to);
|
||||
for (auto & entry : fs::directory_iterator(from.path())) {
|
||||
copy(entry, to / entry.path().filename(), andDelete);
|
||||
}
|
||||
} else {
|
||||
throw Error("file '%s' has an unsupported type", from.path());
|
||||
}
|
||||
|
||||
setWriteTime(to, statOfFrom);
|
||||
if (andDelete) {
|
||||
if (!fs::is_symlink(fromStatus))
|
||||
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
|
||||
fs::remove(from.path());
|
||||
}
|
||||
}
|
||||
|
||||
void renameFile(const Path & oldName, const Path & newName)
|
||||
{
|
||||
fs::rename(oldName, newName);
|
||||
}
|
||||
|
||||
void moveFile(const Path & oldName, const Path & newName)
|
||||
{
|
||||
try {
|
||||
renameFile(oldName, newName);
|
||||
} catch (fs::filesystem_error & e) {
|
||||
auto oldPath = fs::path(oldName);
|
||||
auto newPath = fs::path(newName);
|
||||
// For the move to be as atomic as possible, copy to a temporary
|
||||
// directory
|
||||
fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
|
||||
Finally removeTemp = [&]() { fs::remove(temp); };
|
||||
auto tempCopyTarget = temp / "copy-target";
|
||||
if (e.code().value() == EXDEV) {
|
||||
fs::remove(newPath);
|
||||
warn("Can’t rename %s as %s, copying instead", oldName, newName);
|
||||
copy(fs::directory_entry(oldPath), tempCopyTarget, true);
|
||||
renameFile(tempCopyTarget, newPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
25
src/libutil/git.cc
Normal file
25
src/libutil/git.cc
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "git.hh"
|
||||
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
namespace git {
|
||||
|
||||
std::optional<LsRemoteRefLine> parseLsRemoteLine(std::string_view line)
|
||||
{
|
||||
const static std::regex line_regex("^(ref: *)?([^\\s]+)(?:\\t+(.*))?$");
|
||||
std::match_results<std::string_view::const_iterator> match;
|
||||
if (!std::regex_match(line.cbegin(), line.cend(), match, line_regex))
|
||||
return std::nullopt;
|
||||
|
||||
return LsRemoteRefLine {
|
||||
.kind = match[1].length() == 0
|
||||
? LsRemoteRefLine::Kind::Object
|
||||
: LsRemoteRefLine::Kind::Symbolic,
|
||||
.target = match[2],
|
||||
.reference = match[3].length() == 0 ? std::nullopt : std::optional<std::string>{ match[3] }
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
40
src/libutil/git.hh
Normal file
40
src/libutil/git.hh
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
|
||||
namespace nix {
|
||||
|
||||
namespace git {
|
||||
|
||||
// A line from the output of `git ls-remote --symref`.
|
||||
//
|
||||
// These can be of two kinds:
|
||||
//
|
||||
// - Symbolic references of the form
|
||||
//
|
||||
// ref: {target} {reference}
|
||||
//
|
||||
// where {target} is itself a reference and {reference} is optional
|
||||
//
|
||||
// - Object references of the form
|
||||
//
|
||||
// {target} {reference}
|
||||
//
|
||||
// where {target} is a commit id and {reference} is mandatory
|
||||
struct LsRemoteRefLine {
|
||||
enum struct Kind {
|
||||
Symbolic,
|
||||
Object
|
||||
};
|
||||
Kind kind;
|
||||
std::string target;
|
||||
std::optional<std::string> reference;
|
||||
};
|
||||
|
||||
std::optional<LsRemoteRefLine> parseLsRemoteLine(std::string_view line);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -8,9 +8,9 @@ std::string hiliteMatches(
|
|||
std::string_view prefix,
|
||||
std::string_view postfix)
|
||||
{
|
||||
// Avoid copy on zero matches
|
||||
// Avoid extra work on zero matches
|
||||
if (matches.size() == 0)
|
||||
return (std::string) s;
|
||||
return std::string(s);
|
||||
|
||||
std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) {
|
||||
return a.position() < b.position();
|
||||
|
|
21
src/libutil/json-utils.hh
Normal file
21
src/libutil/json-utils.hh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
|
||||
{
|
||||
auto i = map.find(key);
|
||||
if (i == map.end()) return nullptr;
|
||||
return &*i;
|
||||
}
|
||||
|
||||
nlohmann::json * get(nlohmann::json & map, const std::string & key)
|
||||
{
|
||||
auto i = map.find(key);
|
||||
if (i == map.end()) return nullptr;
|
||||
return &*i;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
#include "json.hh"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace nix {
|
||||
|
||||
void toJSON(std::ostream & str, const char * start, const char * end)
|
||||
template<>
|
||||
void toJSON<std::string_view>(std::ostream & str, const std::string_view & s)
|
||||
{
|
||||
constexpr size_t BUF_SIZE = 4096;
|
||||
char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts
|
||||
|
@ -20,7 +22,7 @@ void toJSON(std::ostream & str, const char * start, const char * end)
|
|||
};
|
||||
|
||||
put('"');
|
||||
for (auto i = start; i != end; i++) {
|
||||
for (auto i = s.begin(); i != s.end(); i++) {
|
||||
if (bufPos >= BUF_SIZE) flush();
|
||||
if (*i == '\"' || *i == '\\') { put('\\'); put(*i); }
|
||||
else if (*i == '\n') { put('\\'); put('n'); }
|
||||
|
@ -43,7 +45,7 @@ void toJSON(std::ostream & str, const char * start, const char * end)
|
|||
|
||||
void toJSON(std::ostream & str, const char * s)
|
||||
{
|
||||
if (!s) str << "null"; else toJSON(str, s, s + strlen(s));
|
||||
if (!s) str << "null"; else toJSON(str, std::string_view(s));
|
||||
}
|
||||
|
||||
template<> void toJSON<int>(std::ostream & str, const int & n) { str << n; }
|
||||
|
@ -54,11 +56,7 @@ template<> void toJSON<long long>(std::ostream & str, const long long & n) { str
|
|||
template<> void toJSON<unsigned long long>(std::ostream & str, const unsigned long long & n) { str << n; }
|
||||
template<> void toJSON<float>(std::ostream & str, const float & n) { str << n; }
|
||||
template<> void toJSON<double>(std::ostream & str, const double & n) { str << n; }
|
||||
|
||||
template<> void toJSON<std::string>(std::ostream & str, const std::string & s)
|
||||
{
|
||||
toJSON(str, s.c_str(), s.c_str() + s.size());
|
||||
}
|
||||
template<> void toJSON<std::string>(std::ostream & str, const std::string & s) { toJSON(str, (std::string_view) s); }
|
||||
|
||||
template<> void toJSON<bool>(std::ostream & str, const bool & b)
|
||||
{
|
||||
|
@ -153,7 +151,7 @@ JSONObject::~JSONObject()
|
|||
}
|
||||
}
|
||||
|
||||
void JSONObject::attr(const std::string & s)
|
||||
void JSONObject::attr(std::string_view s)
|
||||
{
|
||||
comma();
|
||||
toJSON(state->str, s);
|
||||
|
@ -161,19 +159,19 @@ void JSONObject::attr(const std::string & s)
|
|||
if (state->indent) state->str << ' ';
|
||||
}
|
||||
|
||||
JSONList JSONObject::list(const std::string & name)
|
||||
JSONList JSONObject::list(std::string_view name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONList(state);
|
||||
}
|
||||
|
||||
JSONObject JSONObject::object(const std::string & name)
|
||||
JSONObject JSONObject::object(std::string_view name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONObject(state);
|
||||
}
|
||||
|
||||
JSONPlaceholder JSONObject::placeholder(const std::string & name)
|
||||
JSONPlaceholder JSONObject::placeholder(std::string_view name)
|
||||
{
|
||||
attr(name);
|
||||
return JSONPlaceholder(state);
|
||||
|
@ -195,7 +193,11 @@ JSONObject JSONPlaceholder::object()
|
|||
|
||||
JSONPlaceholder::~JSONPlaceholder()
|
||||
{
|
||||
assert(!first || std::uncaught_exceptions());
|
||||
if (first) {
|
||||
assert(std::uncaught_exceptions());
|
||||
if (state->stack != 0)
|
||||
write(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
namespace nix {
|
||||
|
||||
void toJSON(std::ostream & str, const char * start, const char * end);
|
||||
void toJSON(std::ostream & str, const char * s);
|
||||
|
||||
template<typename T>
|
||||
|
@ -107,7 +106,7 @@ private:
|
|||
open();
|
||||
}
|
||||
|
||||
void attr(const std::string & s);
|
||||
void attr(std::string_view s);
|
||||
|
||||
public:
|
||||
|
||||
|
@ -128,18 +127,18 @@ public:
|
|||
~JSONObject();
|
||||
|
||||
template<typename T>
|
||||
JSONObject & attr(const std::string & name, const T & v)
|
||||
JSONObject & attr(std::string_view name, const T & v)
|
||||
{
|
||||
attr(name);
|
||||
toJSON(state->str, v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
JSONList list(const std::string & name);
|
||||
JSONList list(std::string_view name);
|
||||
|
||||
JSONObject object(const std::string & name);
|
||||
JSONObject object(std::string_view name);
|
||||
|
||||
JSONPlaceholder placeholder(const std::string & name);
|
||||
JSONPlaceholder placeholder(std::string_view name);
|
||||
};
|
||||
|
||||
class JSONPlaceholder : JSONWriter
|
||||
|
|
|
@ -111,6 +111,9 @@ public:
|
|||
|
||||
virtual std::optional<char> ask(std::string_view s)
|
||||
{ return {}; }
|
||||
|
||||
virtual void setPrintBuildLogs(bool printBuildLogs)
|
||||
{ }
|
||||
};
|
||||
|
||||
ActivityId getCurActivity();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
namespace nix {
|
||||
|
||||
/* A simple non-nullable reference-counted pointer. Actually a wrapper
|
||||
around std::shared_ptr that prevents non-null constructions. */
|
||||
around std::shared_ptr that prevents null constructions. */
|
||||
template<typename T>
|
||||
class ref
|
||||
{
|
||||
|
|
|
@ -48,24 +48,9 @@ FdSink::~FdSink()
|
|||
}
|
||||
|
||||
|
||||
size_t threshold = 256 * 1024 * 1024;
|
||||
|
||||
static void warnLargeDump()
|
||||
{
|
||||
warn("dumping very large path (> 256 MiB); this may run out of memory");
|
||||
}
|
||||
|
||||
|
||||
void FdSink::write(std::string_view data)
|
||||
{
|
||||
written += data.size();
|
||||
static bool warned = false;
|
||||
if (warn && !warned) {
|
||||
if (written > threshold) {
|
||||
warnLargeDump();
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
try {
|
||||
writeFull(fd, data);
|
||||
} catch (SysError & e) {
|
||||
|
@ -448,11 +433,6 @@ Error readError(Source & source)
|
|||
|
||||
void StringSink::operator () (std::string_view data)
|
||||
{
|
||||
static bool warned = false;
|
||||
if (!warned && s.size() > threshold) {
|
||||
warnLargeDump();
|
||||
warned = true;
|
||||
}
|
||||
s.append(data);
|
||||
}
|
||||
|
||||
|
|
|
@ -97,19 +97,17 @@ protected:
|
|||
struct FdSink : BufferedSink
|
||||
{
|
||||
int fd;
|
||||
bool warn = false;
|
||||
size_t written = 0;
|
||||
|
||||
FdSink() : fd(-1) { }
|
||||
FdSink(int fd) : fd(fd) { }
|
||||
FdSink(FdSink&&) = default;
|
||||
|
||||
FdSink& operator=(FdSink && s)
|
||||
FdSink & operator=(FdSink && s)
|
||||
{
|
||||
flush();
|
||||
fd = s.fd;
|
||||
s.fd = -1;
|
||||
warn = s.warn;
|
||||
written = s.written;
|
||||
return *this;
|
||||
}
|
||||
|
|
33
src/libutil/tests/git.cc
Normal file
33
src/libutil/tests/git.cc
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "git.hh"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
TEST(GitLsRemote, parseSymrefLineWithReference) {
|
||||
auto line = "ref: refs/head/main HEAD";
|
||||
auto res = git::parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
|
||||
ASSERT_EQ(res->target, "refs/head/main");
|
||||
ASSERT_EQ(res->reference, "HEAD");
|
||||
}
|
||||
|
||||
TEST(GitLsRemote, parseSymrefLineWithNoReference) {
|
||||
auto line = "ref: refs/head/main";
|
||||
auto res = git::parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic);
|
||||
ASSERT_EQ(res->target, "refs/head/main");
|
||||
ASSERT_EQ(res->reference, std::nullopt);
|
||||
}
|
||||
|
||||
TEST(GitLsRemote, parseObjectRefLine) {
|
||||
auto line = "abc123 refs/head/main";
|
||||
auto res = git::parseLsRemoteLine(line);
|
||||
ASSERT_TRUE(res.has_value());
|
||||
ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Object);
|
||||
ASSERT_EQ(res->target, "abc123");
|
||||
ASSERT_EQ(res->reference, "refs/head/main");
|
||||
}
|
||||
}
|
||||
|
|
@ -102,8 +102,8 @@ namespace nix {
|
|||
|
||||
TEST(toJSON, substringEscape) {
|
||||
std::stringstream out;
|
||||
const char *s = "foo\t";
|
||||
toJSON(out, s+3, s + strlen(s));
|
||||
std::string_view s = "foo\t";
|
||||
toJSON(out, s.substr(3));
|
||||
|
||||
ASSERT_EQ(out.str(), "\"\\t\"");
|
||||
}
|
||||
|
|
|
@ -548,7 +548,7 @@ namespace nix {
|
|||
|
||||
TEST(get, emptyContainer) {
|
||||
StringMap s = { };
|
||||
auto expected = std::nullopt;
|
||||
auto expected = nullptr;
|
||||
|
||||
ASSERT_EQ(get(s, "one"), expected);
|
||||
}
|
||||
|
@ -559,7 +559,23 @@ namespace nix {
|
|||
s["two"] = "er";
|
||||
auto expected = "yi";
|
||||
|
||||
ASSERT_EQ(get(s, "one"), expected);
|
||||
ASSERT_EQ(*get(s, "one"), expected);
|
||||
}
|
||||
|
||||
TEST(getOr, emptyContainer) {
|
||||
StringMap s = { };
|
||||
auto expected = "yi";
|
||||
|
||||
ASSERT_EQ(getOr(s, "one", "yi"), expected);
|
||||
}
|
||||
|
||||
TEST(getOr, getFromContainer) {
|
||||
StringMap s;
|
||||
s["one"] = "yi";
|
||||
s["two"] = "er";
|
||||
auto expected = "yi";
|
||||
|
||||
ASSERT_EQ(getOr(s, "one", "nope"), expected);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "url.hh"
|
||||
#include "url-parts.hh"
|
||||
#include "util.hh"
|
||||
#include "split.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
@ -136,4 +137,21 @@ bool ParsedURL::operator ==(const ParsedURL & other) const
|
|||
&& fragment == other.fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a URL scheme of the form '(applicationScheme\+)?transportScheme'
|
||||
* into a tuple '(applicationScheme, transportScheme)'
|
||||
*
|
||||
* > parseUrlScheme("http") == ParsedUrlScheme{ {}, "http"}
|
||||
* > parseUrlScheme("tarball+http") == ParsedUrlScheme{ {"tarball"}, "http"}
|
||||
*/
|
||||
ParsedUrlScheme parseUrlScheme(std::string_view scheme)
|
||||
{
|
||||
auto application = splitPrefixTo(scheme, '+');
|
||||
auto transport = scheme;
|
||||
return ParsedUrlScheme {
|
||||
.application = application,
|
||||
.transport = transport,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,4 +27,19 @@ std::map<std::string, std::string> decodeQuery(const std::string & query);
|
|||
|
||||
ParsedURL parseURL(const std::string & url);
|
||||
|
||||
/*
|
||||
* Although that’s not really standardized anywhere, an number of tools
|
||||
* use a scheme of the form 'x+y' in urls, where y is the “transport layer”
|
||||
* scheme, and x is the “application layer” scheme.
|
||||
*
|
||||
* For example git uses `git+https` to designate remotes using a Git
|
||||
* protocol over http.
|
||||
*/
|
||||
struct ParsedUrlScheme {
|
||||
std::optional<std::string_view> application;
|
||||
std::string_view transport;
|
||||
};
|
||||
|
||||
ParsedUrlScheme parseUrlScheme(std::string_view scheme);
|
||||
|
||||
}
|
||||
|
|
|
@ -29,11 +29,15 @@
|
|||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/syscall.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <mntent.h>
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -504,61 +508,6 @@ void deletePath(const Path & path, uint64_t & bytesFreed)
|
|||
}
|
||||
|
||||
|
||||
static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
|
||||
int & counter)
|
||||
{
|
||||
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
|
||||
if (includePid)
|
||||
return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
|
||||
else
|
||||
return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
|
||||
}
|
||||
|
||||
|
||||
Path createTempDir(const Path & tmpRoot, const Path & prefix,
|
||||
bool includePid, bool useGlobalCounter, mode_t mode)
|
||||
{
|
||||
static int globalCounter = 0;
|
||||
int localCounter = 0;
|
||||
int & counter(useGlobalCounter ? globalCounter : localCounter);
|
||||
|
||||
while (1) {
|
||||
checkInterrupt();
|
||||
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
|
||||
if (mkdir(tmpDir.c_str(), mode) == 0) {
|
||||
#if __FreeBSD__
|
||||
/* Explicitly set the group of the directory. This is to
|
||||
work around around problems caused by BSD's group
|
||||
ownership semantics (directories inherit the group of
|
||||
the parent). For instance, the group of /tmp on
|
||||
FreeBSD is "wheel", so all directories created in /tmp
|
||||
will be owned by "wheel"; but if the user is not in
|
||||
"wheel", then "tar" will fail to unpack archives that
|
||||
have the setgid bit set on directories. */
|
||||
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
|
||||
throw SysError("setting group of directory '%1%'", tmpDir);
|
||||
#endif
|
||||
return tmpDir;
|
||||
}
|
||||
if (errno != EEXIST)
|
||||
throw SysError("creating directory '%1%'", tmpDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
||||
{
|
||||
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
|
||||
// Strictly speaking, this is UB, but who cares...
|
||||
// FIXME: use O_TMPFILE.
|
||||
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
|
||||
if (!fd)
|
||||
throw SysError("creating temporary file '%s'", tmpl);
|
||||
closeOnExec(fd.get());
|
||||
return {std::move(fd), tmpl};
|
||||
}
|
||||
|
||||
|
||||
std::string getUserName()
|
||||
{
|
||||
auto pw = getpwuid(geteuid());
|
||||
|
@ -573,7 +522,21 @@ Path getHome()
|
|||
{
|
||||
static Path homeDir = []()
|
||||
{
|
||||
std::optional<std::string> unownedUserHomeDir = {};
|
||||
auto homeDir = getEnv("HOME");
|
||||
if (homeDir) {
|
||||
// Only use $HOME if doesn't exist or is owned by the current user.
|
||||
struct stat st;
|
||||
int result = stat(homeDir->c_str(), &st);
|
||||
if (result != 0) {
|
||||
if (errno != ENOENT) {
|
||||
warn("couldn't stat $HOME ('%s') for reason other than not existing ('%d'), falling back to the one defined in the 'passwd' file", *homeDir, errno);
|
||||
homeDir.reset();
|
||||
}
|
||||
} else if (st.st_uid != geteuid()) {
|
||||
unownedUserHomeDir.swap(homeDir);
|
||||
}
|
||||
}
|
||||
if (!homeDir) {
|
||||
std::vector<char> buf(16384);
|
||||
struct passwd pwbuf;
|
||||
|
@ -582,6 +545,9 @@ Path getHome()
|
|||
|| !pw || !pw->pw_dir || !pw->pw_dir[0])
|
||||
throw Error("cannot determine user's home directory");
|
||||
homeDir = pw->pw_dir;
|
||||
if (unownedUserHomeDir.has_value() && unownedUserHomeDir != homeDir) {
|
||||
warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file ('%s')", *unownedUserHomeDir, *homeDir);
|
||||
}
|
||||
}
|
||||
return *homeDir;
|
||||
}();
|
||||
|
@ -619,6 +585,27 @@ Path getDataDir()
|
|||
}
|
||||
|
||||
|
||||
std::optional<Path> getSelfExe()
|
||||
{
|
||||
static auto cached = []() -> std::optional<Path>
|
||||
{
|
||||
#if __linux__
|
||||
return readLink("/proc/self/exe");
|
||||
#elif __APPLE__
|
||||
char buf[1024];
|
||||
uint32_t size = sizeof(buf);
|
||||
if (_NSGetExecutablePath(buf, &size) == 0)
|
||||
return buf;
|
||||
else
|
||||
return std::nullopt;
|
||||
#else
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}();
|
||||
return cached;
|
||||
}
|
||||
|
||||
|
||||
Paths createDirs(const Path & path)
|
||||
{
|
||||
Paths created;
|
||||
|
@ -642,44 +629,6 @@ Paths createDirs(const Path & path)
|
|||
}
|
||||
|
||||
|
||||
void createSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime)
|
||||
{
|
||||
if (symlink(target.c_str(), link.c_str()))
|
||||
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
|
||||
if (mtime) {
|
||||
struct timeval times[2];
|
||||
times[0].tv_sec = *mtime;
|
||||
times[0].tv_usec = 0;
|
||||
times[1].tv_sec = *mtime;
|
||||
times[1].tv_usec = 0;
|
||||
if (lutimes(link.c_str(), times))
|
||||
throw SysError("setting time of symlink '%s'", link);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void replaceSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime)
|
||||
{
|
||||
for (unsigned int n = 0; true; n++) {
|
||||
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
|
||||
|
||||
try {
|
||||
createSymlink(target, tmp, mtime);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == EEXIST) continue;
|
||||
throw;
|
||||
}
|
||||
|
||||
if (rename(tmp.c_str(), link.c_str()) != 0)
|
||||
throw SysError("renaming '%1%' to '%2%'", tmp, link);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void readFull(int fd, char * buf, size_t count)
|
||||
{
|
||||
while (count) {
|
||||
|
@ -752,7 +701,55 @@ void drainFD(int fd, Sink & sink, bool block)
|
|||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
unsigned int getMaxCPU()
|
||||
{
|
||||
#if __linux__
|
||||
try {
|
||||
FILE *fp = fopen("/proc/mounts", "r");
|
||||
if (!fp)
|
||||
return 0;
|
||||
|
||||
Strings cgPathParts;
|
||||
|
||||
struct mntent *ent;
|
||||
while ((ent = getmntent(fp))) {
|
||||
std::string mountType, mountPath;
|
||||
|
||||
mountType = ent->mnt_type;
|
||||
mountPath = ent->mnt_dir;
|
||||
|
||||
if (mountType == "cgroup2") {
|
||||
cgPathParts.push_back(mountPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (cgPathParts.size() > 0 && pathExists("/proc/self/cgroup")) {
|
||||
std::string currentCgroup = readFile("/proc/self/cgroup");
|
||||
Strings cgValues = tokenizeString<Strings>(currentCgroup, ":");
|
||||
cgPathParts.push_back(trim(cgValues.back(), "\n"));
|
||||
cgPathParts.push_back("cpu.max");
|
||||
std::string fullCgPath = canonPath(concatStringsSep("/", cgPathParts));
|
||||
|
||||
if (pathExists(fullCgPath)) {
|
||||
std::string cpuMax = readFile(fullCgPath);
|
||||
std::vector<std::string> cpuMaxParts = tokenizeString<std::vector<std::string>>(cpuMax, " ");
|
||||
std::string quota = cpuMaxParts[0];
|
||||
std::string period = trim(cpuMaxParts[1], "\n");
|
||||
|
||||
if (quota != "max")
|
||||
return std::ceil(std::stoi(quota) / std::stof(period));
|
||||
}
|
||||
}
|
||||
} catch (Error &) { ignoreException(); }
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -1588,7 +1585,6 @@ std::string stripIndentation(std::string_view s)
|
|||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};
|
||||
|
||||
|
||||
|
@ -1819,7 +1815,7 @@ AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
|
|||
if (chmod(path.c_str(), mode) == -1)
|
||||
throw SysError("changing permissions on '%1%'", path);
|
||||
|
||||
if (listen(fdSocket.get(), 5) == -1)
|
||||
if (listen(fdSocket.get(), 100) == -1)
|
||||
throw SysError("cannot listen on socket '%1%'", path);
|
||||
|
||||
return fdSocket;
|
||||
|
|
|
@ -149,10 +149,14 @@ std::vector<Path> getConfigDirs();
|
|||
/* Return $XDG_DATA_HOME or $HOME/.local/share. */
|
||||
Path getDataDir();
|
||||
|
||||
/* Return the path of the current executable. */
|
||||
std::optional<Path> getSelfExe();
|
||||
|
||||
/* Create a directory and all its parents, if necessary. Returns the
|
||||
list of created directories, in order of creation. */
|
||||
Paths createDirs(const Path & path);
|
||||
inline Paths createDirs(PathView path) {
|
||||
inline Paths createDirs(PathView path)
|
||||
{
|
||||
return createDirs(Path(path));
|
||||
}
|
||||
|
||||
|
@ -164,6 +168,17 @@ void createSymlink(const Path & target, const Path & link,
|
|||
void replaceSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime = {});
|
||||
|
||||
void renameFile(const Path & src, const Path & dst);
|
||||
|
||||
/**
|
||||
* Similar to 'renameFile', but fallback to a copy+remove if `src` and `dst`
|
||||
* are on a different filesystem.
|
||||
*
|
||||
* Beware that this might not be atomic because of the copy that happens behind
|
||||
* the scenes
|
||||
*/
|
||||
void moveFile(const Path & src, const Path & dst);
|
||||
|
||||
|
||||
/* Wrappers arount read()/write() that read/write exactly the
|
||||
requested number of bytes. */
|
||||
|
@ -178,6 +193,9 @@ std::string drainFD(int fd, bool block = true, const size_t reserveSize=0);
|
|||
|
||||
void drainFD(int fd, Sink & sink, bool block = true);
|
||||
|
||||
/* If cgroups are active, attempt to calculate the number of CPUs available.
|
||||
If cgroups are unavailable or if cpu.max is set to "max", return 0. */
|
||||
unsigned int getMaxCPU();
|
||||
|
||||
/* Automatic cleanup of resources. */
|
||||
|
||||
|
@ -543,13 +561,31 @@ std::string stripIndentation(std::string_view s);
|
|||
|
||||
/* Get a value for the specified key from an associate container. */
|
||||
template <class T>
|
||||
std::optional<typename T::mapped_type> get(const T & map, const typename T::key_type & key)
|
||||
const typename T::mapped_type * get(const T & map, const typename T::key_type & key)
|
||||
{
|
||||
auto i = map.find(key);
|
||||
if (i == map.end()) return {};
|
||||
return std::optional<typename T::mapped_type>(i->second);
|
||||
if (i == map.end()) return nullptr;
|
||||
return &i->second;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename T::mapped_type * get(T & map, const typename T::key_type & key)
|
||||
{
|
||||
auto i = map.find(key);
|
||||
if (i == map.end()) return nullptr;
|
||||
return &i->second;
|
||||
}
|
||||
|
||||
/* Get a value for the specified key from an associate container, or a default value if the key isn't present. */
|
||||
template <class T>
|
||||
const typename T::mapped_type & getOr(T & map,
|
||||
const typename T::key_type & key,
|
||||
const typename T::mapped_type & defaultValue)
|
||||
{
|
||||
auto i = map.find(key);
|
||||
if (i == map.end()) return defaultValue;
|
||||
return i->second;
|
||||
}
|
||||
|
||||
/* Remove and return the first item from a container. */
|
||||
template <class T>
|
||||
|
@ -682,4 +718,19 @@ template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
|||
std::string showBytes(uint64_t bytes);
|
||||
|
||||
|
||||
/* Provide an addition operator between strings and string_views
|
||||
inexplicably omitted from the standard library. */
|
||||
inline std::string operator + (const std::string & s1, std::string_view s2)
|
||||
{
|
||||
auto s = s1;
|
||||
s.append(s2);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string operator + (std::string && s, std::string_view s2)
|
||||
{
|
||||
s.append(s2);
|
||||
return std::move(s);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
42
src/nix-build/nix-build.cc
Executable file → Normal file
42
src/nix-build/nix-build.cc
Executable file → Normal file
|
@ -257,11 +257,12 @@ static void main_nix_build(int argc, char * * argv)
|
|||
|
||||
auto autoArgs = myArgs.getAutoArgs(*state);
|
||||
|
||||
auto autoArgsWithInNixShell = autoArgs;
|
||||
if (runEnv) {
|
||||
auto newArgs = state->buildBindings(autoArgs->size() + 1);
|
||||
auto newArgs = state->buildBindings(autoArgsWithInNixShell->size() + 1);
|
||||
newArgs.alloc("inNixShell").mkBool(true);
|
||||
for (auto & i : *autoArgs) newArgs.insert(i);
|
||||
autoArgs = newArgs.finish();
|
||||
autoArgsWithInNixShell = newArgs.finish();
|
||||
}
|
||||
|
||||
if (packages) {
|
||||
|
@ -316,10 +317,39 @@ static void main_nix_build(int argc, char * * argv)
|
|||
Value vRoot;
|
||||
state->eval(e, vRoot);
|
||||
|
||||
std::function<bool(const Value & v)> takesNixShellAttr;
|
||||
takesNixShellAttr = [&](const Value & v) {
|
||||
if (!runEnv) {
|
||||
return false;
|
||||
}
|
||||
bool add = false;
|
||||
if (v.type() == nFunction && v.lambda.fun->hasFormals()) {
|
||||
for (auto & i : v.lambda.fun->formals->formals) {
|
||||
if (state->symbols[i.name] == "inNixShell") {
|
||||
add = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return add;
|
||||
};
|
||||
|
||||
for (auto & i : attrPaths) {
|
||||
Value & v(*findAlongAttrPath(*state, i, *autoArgs, vRoot).first);
|
||||
Value & v(*findAlongAttrPath(
|
||||
*state,
|
||||
i,
|
||||
takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs,
|
||||
vRoot
|
||||
).first);
|
||||
state->forceValue(v, [&]() { return v.determinePos(noPos); });
|
||||
getDerivations(*state, v, "", *autoArgs, drvs, false);
|
||||
getDerivations(
|
||||
*state,
|
||||
v,
|
||||
"",
|
||||
takesNixShellAttr(v) ? *autoArgsWithInNixShell : *autoArgs,
|
||||
drvs,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,7 +401,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
auto bashDrv = drv->requireDrvPath();
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = bashDrv,
|
||||
.outputs = {},
|
||||
.outputs = {"out"},
|
||||
});
|
||||
pathsToCopy.insert(bashDrv);
|
||||
shellDrv = bashDrv;
|
||||
|
@ -440,7 +470,7 @@ static void main_nix_build(int argc, char * * argv)
|
|||
env["NIX_STORE"] = store->storeDir;
|
||||
env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores);
|
||||
|
||||
auto passAsFile = tokenizeString<StringSet>(get(drv.env, "passAsFile").value_or(""));
|
||||
auto passAsFile = tokenizeString<StringSet>(getOr(drv.env, "passAsFile", ""));
|
||||
|
||||
bool keepTmp = false;
|
||||
int fileNr = 0;
|
||||
|
|
|
@ -37,6 +37,7 @@ void removeOldGenerations(std::string dir)
|
|||
link = readLink(path);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == ENOENT) continue;
|
||||
throw;
|
||||
}
|
||||
if (link.find("link") != std::string::npos) {
|
||||
printInfo(format("removing old generations of profile %1%") % path);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue