1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-06 21:41:48 +02:00

Merge remote-tracking branch 'upstream/master' into auto-uid-allocation

This commit is contained in:
John Ericson 2021-04-27 19:06:58 +00:00
commit e023c985d5
383 changed files with 16916 additions and 9809 deletions

View file

@ -17,7 +17,7 @@
#include "store-api.hh"
#include "derivations.hh"
#include "local-store.hh"
#include "../nix/legacy.hh"
#include "legacy.hh"
using namespace nix;
using std::cin;
@ -53,6 +53,9 @@ static int main_build_remote(int argc, char * * argv)
unsetenv("DISPLAY");
unsetenv("SSH_ASKPASS");
/* If we ever use the common args framework, make sure to
remove initPlugins below and initialize settings first.
*/
if (argc != 2)
throw UsageError("called without required arguments");
@ -71,11 +74,15 @@ static int main_build_remote(int argc, char * * argv)
initPlugins();
auto store = openStore().cast<LocalStore>();
auto store = openStore();
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
that gets cleared on reboot, but it wouldn't work on macOS. */
currentLoad = store->stateDir + "/current-load";
auto currentLoadName = "/current-load";
if (auto localStore = store.dynamic_pointer_cast<LocalFSStore>())
currentLoad = std::string { localStore->stateDir } + currentLoadName;
else
currentLoad = settings.nixStateDir + currentLoadName;
std::shared_ptr<Store> sshStore;
AutoCloseFD bestSlotLock;
@ -172,13 +179,14 @@ static int main_build_remote(int argc, char * * argv)
else
{
// build the hint template.
string hintstring = "derivation: %s\nrequired (system, features): (%s, %s)";
hintstring += "\n%s available machines:";
hintstring += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)";
string errorText =
"Failed to find a machine for remote build!\n"
"derivation: %s\nrequired (system, features): (%s, %s)";
errorText += "\n%s available machines:";
errorText += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)";
for (unsigned int i = 0; i < machines.size(); ++i) {
hintstring += "\n(%s, %s, %s, %s)";
}
for (unsigned int i = 0; i < machines.size(); ++i)
errorText += "\n(%s, %s, %s, %s)";
// add the template values.
string drvstr;
@ -187,25 +195,21 @@ static int main_build_remote(int argc, char * * argv)
else
drvstr = "<unknown>";
auto hint = hintformat(hintstring);
hint
% drvstr
% neededSystem
% concatStringsSep<StringSet>(", ", requiredFeatures)
% machines.size();
auto error = hintformat(errorText);
error
% drvstr
% neededSystem
% concatStringsSep<StringSet>(", ", requiredFeatures)
% machines.size();
for (auto & m : machines) {
hint % concatStringsSep<vector<string>>(", ", m.systemTypes)
% m.maxJobs
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
}
for (auto & m : machines)
error
% concatStringsSep<vector<string>>(", ", m.systemTypes)
% m.maxJobs
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
logErrorInfo(lvlInfo, {
.name = "Remote build",
.description = "Failed to find a machine for remote build!",
.hint = hint
});
printMsg(canBuildLocally ? lvlChatty : lvlWarn, error);
std::cerr << "# decline\n";
}
@ -230,12 +234,9 @@ static int main_build_remote(int argc, char * * argv)
} catch (std::exception & e) {
auto msg = chomp(drainFD(5, false));
logError({
.name = "Remote build",
.hint = hintfmt("cannot build on '%s': %s%s",
bestMachine->storeUri, e.what(),
(msg.empty() ? "" : ": " + msg))
});
printError("cannot build on '%s': %s%s",
bestMachine->storeUri, e.what(),
msg.empty() ? "" : ": " + msg);
bestMachine->enabled = false;
continue;
}
@ -250,7 +251,7 @@ connected:
std::cerr << "# accept\n" << storeUri << "\n";
auto inputs = readStrings<PathSet>(source);
auto outputs = readStrings<PathSet>(source);
auto wantedOutputs = readStrings<StringSet>(source);
AutoCloseFD uploadLock = openLockFile(currentLoad + "/" + escapeUri(storeUri) + ".upload-lock", true);
@ -275,6 +276,7 @@ connected:
uploadLock = -1;
auto drv = store->readDerivation(*drvPath);
auto outputHashes = staticOutputHashes(*store, drv);
drv.inputSrcs = store->parseStorePathSet(inputs);
auto result = sshStore->buildDerivation(*drvPath, drv);
@ -282,15 +284,42 @@ connected:
if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
StorePathSet missing;
for (auto & path : outputs)
if (!store->isValidPath(store->parseStorePath(path))) missing.insert(store->parseStorePath(path));
std::set<Realisation> missingRealisations;
StorePathSet missingPaths;
if (settings.isExperimentalFeatureEnabled("ca-derivations") && !derivationHasKnownOutputPaths(drv.type())) {
for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
if (!store->queryRealisation(thisOutputId)) {
debug("missing output %s", outputName);
assert(result.builtOutputs.count(thisOutputId));
auto newRealisation = result.builtOutputs.at(thisOutputId);
missingRealisations.insert(newRealisation);
missingPaths.insert(newRealisation.outPath);
}
}
} else {
auto outputPaths = drv.outputsAndOptPaths(*store);
for (auto & [outputName, hopefullyOutputPath] : outputPaths) {
assert(hopefullyOutputPath.second);
if (!store->isValidPath(*hopefullyOutputPath.second))
missingPaths.insert(*hopefullyOutputPath.second);
}
}
if (!missing.empty()) {
if (!missingPaths.empty()) {
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri));
for (auto & i : missing)
store->locksHeld.insert(store->printStorePath(i)); /* FIXME: ugly */
copyPaths(ref<Store>(sshStore), store, missing, NoRepair, NoCheckSigs, NoSubstitute);
if (auto localStore = store.dynamic_pointer_cast<LocalStore>())
for (auto & path : missingPaths)
localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */
copyPaths(ref<Store>(sshStore), store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute);
}
// XXX: Should be done as part of `copyPaths`
for (auto & realisation : missingRealisations) {
// Should hold, because if the feature isn't enabled the set
// of missing realisations should be empty
settings.requireExperimentalFeature("ca-derivations");
store->registerDrvOutput(realisation);
}
return 0;

View file

@ -11,11 +11,20 @@ extern char * * environ __attribute__((weak));
namespace nix {
Commands * RegisterCommand::commands = nullptr;
RegisterCommand::Commands * RegisterCommand::commands = nullptr;
void NixMultiCommand::printHelp(const string & programName, std::ostream & out)
nix::Commands RegisterCommand::getCommandsFor(const std::vector<std::string> & prefix)
{
MultiCommand::printHelp(programName, out);
nix::Commands res;
for (auto & [name, command] : *RegisterCommand::commands)
if (name.size() == prefix.size() + 1) {
bool equal = true;
for (size_t i = 0; i < prefix.size(); ++i)
if (name[i] != prefix[i]) equal = false;
if (equal)
res.insert_or_assign(name[prefix.size()], command);
}
return res;
}
nlohmann::json NixMultiCommand::toJSON()
@ -45,57 +54,72 @@ void StoreCommand::run()
run(getStore());
}
StorePathsCommand::StorePathsCommand(bool recursive)
RealisedPathsCommand::RealisedPathsCommand(bool recursive)
: recursive(recursive)
{
if (recursive)
addFlag({
.longName = "no-recursive",
.description = "apply operation to specified paths only",
.description = "Apply operation to specified paths only.",
.category = installablesCategory,
.handler = {&this->recursive, false},
});
else
addFlag({
.longName = "recursive",
.shortName = 'r',
.description = "apply operation to closure of the specified paths",
.description = "Apply operation to closure of the specified paths.",
.category = installablesCategory,
.handler = {&this->recursive, true},
});
mkFlag(0, "all", "apply operation to the entire store", &all);
addFlag({
.longName = "all",
.description = "Apply the operation to every store path.",
.category = installablesCategory,
.handler = {&all, true},
});
}
void StorePathsCommand::run(ref<Store> store)
void RealisedPathsCommand::run(ref<Store> store)
{
StorePaths storePaths;
std::vector<RealisedPath> paths;
if (all) {
if (installables.size())
throw UsageError("'--all' does not expect arguments");
// XXX: Only uses opaque paths, ignores all the realisations
for (auto & p : store->queryAllValidPaths())
storePaths.push_back(p);
}
else {
for (auto & p : toStorePaths(store, realiseMode, operateOn, installables))
storePaths.push_back(p);
paths.push_back(p);
} else {
auto pathSet = toRealisedPaths(store, realiseMode, operateOn, installables);
if (recursive) {
StorePathSet closure;
store->computeFSClosure(StorePathSet(storePaths.begin(), storePaths.end()), closure, false, false);
storePaths.clear();
for (auto & p : closure)
storePaths.push_back(p);
auto roots = std::move(pathSet);
pathSet = {};
RealisedPath::closure(*store, roots, pathSet);
}
for (auto & path : pathSet)
paths.push_back(path);
}
run(store, std::move(paths));
}
StorePathsCommand::StorePathsCommand(bool recursive)
: RealisedPathsCommand(recursive)
{
}
void StorePathsCommand::run(ref<Store> store, std::vector<RealisedPath> paths)
{
StorePaths storePaths;
for (auto & p : paths)
storePaths.push_back(p.path());
run(store, std::move(storePaths));
}
void StorePathCommand::run(ref<Store> store)
void StorePathCommand::run(ref<Store> store, std::vector<StorePath> storePaths)
{
auto storePaths = toStorePaths(store, Realise::Nothing, operateOn, installables);
if (storePaths.size() != 1)
throw UsageError("this command requires exactly one store path");
@ -119,7 +143,7 @@ MixProfile::MixProfile()
{
addFlag({
.longName = "profile",
.description = "profile to update",
.description = "The profile to update.",
.labels = {"path"},
.handler = {&profile},
.completer = completePath
@ -138,7 +162,7 @@ void MixProfile::updateProfile(const StorePath & storePath)
profile2, storePath));
}
void MixProfile::updateProfile(const Buildables & buildables)
void MixProfile::updateProfile(const DerivedPathsWithHints & buildables)
{
if (!profile) return;
@ -146,10 +170,10 @@ void MixProfile::updateProfile(const Buildables & buildables)
for (auto & buildable : buildables) {
std::visit(overloaded {
[&](BuildableOpaque bo) {
[&](DerivedPathWithHints::Opaque bo) {
result.push_back(bo.path);
},
[&](BuildableFromDrv bfd) {
[&](DerivedPathWithHints::Built bfd) {
for (auto & output : bfd.outputs) {
/* Output path should be known because we just tried to
build it. */
@ -157,7 +181,7 @@ void MixProfile::updateProfile(const Buildables & buildables)
result.push_back(*output.second);
}
},
}, buildable);
}, buildable.raw());
}
if (result.size() != 1)
@ -176,14 +200,14 @@ MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
addFlag({
.longName = "ignore-environment",
.shortName = 'i',
.description = "clear the entire environment (except those specified with --keep)",
.description = "Clear the entire environment (except those specified with `--keep`).",
.handler = {&ignoreEnvironment, true},
});
addFlag({
.longName = "keep",
.shortName = 'k',
.description = "keep specified environment variable",
.description = "Keep the environment variable *name*.",
.labels = {"name"},
.handler = {[&](std::string s) { keep.insert(s); }},
});
@ -191,7 +215,7 @@ MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
addFlag({
.longName = "unset",
.shortName = 'u',
.description = "unset specified environment variable",
.description = "Unset the environment variable *name*.",
.labels = {"name"},
.handler = {[&](std::string s) { unset.insert(s); }},
});

View file

@ -13,6 +13,8 @@ namespace nix {
extern std::string programPath;
extern char * * savedArgv;
class EvalState;
struct Pos;
class Store;
@ -21,10 +23,10 @@ static constexpr Command::Category catSecondary = 100;
static constexpr Command::Category catUtility = 101;
static constexpr Command::Category catNixInstallation = 102;
static constexpr auto installablesCategory = "Options that change the interpretation of installables";
struct NixMultiCommand : virtual MultiCommand, virtual Command
{
void printHelp(const string & programName, std::ostream & out) override;
nlohmann::json toJSON() override;
};
@ -46,6 +48,8 @@ struct EvalCommand : virtual StoreCommand, MixEvalArgs
ref<EvalState> getEvalState();
std::shared_ptr<EvalState> evalState;
~EvalCommand();
};
struct MixFlakeOptions : virtual Args, EvalCommand
@ -139,7 +143,7 @@ private:
};
/* A command that operates on zero or more store paths. */
struct StorePathsCommand : public InstallablesCommand
struct RealisedPathsCommand : public InstallablesCommand
{
private:
@ -152,47 +156,67 @@ protected:
public:
StorePathsCommand(bool recursive = false);
RealisedPathsCommand(bool recursive = false);
using StoreCommand::run;
virtual void run(ref<Store> store, std::vector<StorePath> storePaths) = 0;
virtual void run(ref<Store> store, std::vector<RealisedPath> paths) = 0;
void run(ref<Store> store) override;
bool useDefaultInstallables() override { return !all; }
};
/* A command that operates on exactly one store path. */
struct StorePathCommand : public InstallablesCommand
struct StorePathsCommand : public RealisedPathsCommand
{
using StoreCommand::run;
StorePathsCommand(bool recursive = false);
using RealisedPathsCommand::run;
virtual void run(ref<Store> store, std::vector<StorePath> storePaths) = 0;
void run(ref<Store> store, std::vector<RealisedPath> paths) override;
};
/* A command that operates on exactly one store path. */
struct StorePathCommand : public StorePathsCommand
{
using StorePathsCommand::run;
virtual void run(ref<Store> store, const StorePath & storePath) = 0;
void run(ref<Store> store) override;
void run(ref<Store> store, std::vector<StorePath> storePaths) override;
};
/* A helper class for registering commands globally. */
struct RegisterCommand
{
typedef std::map<std::vector<std::string>, std::function<ref<Command>()>> Commands;
static Commands * commands;
RegisterCommand(const std::string & name,
RegisterCommand(std::vector<std::string> && name,
std::function<ref<Command>()> command)
{
if (!commands) commands = new Commands;
commands->emplace(name, command);
}
static nix::Commands getCommandsFor(const std::vector<std::string> & prefix);
};
template<class T>
static RegisterCommand registerCommand(const std::string & name)
{
return RegisterCommand(name, [](){ return make_ref<T>(); });
return RegisterCommand({name}, [](){ return make_ref<T>(); });
}
Buildables build(ref<Store> store, Realise mode,
template<class T>
static RegisterCommand registerCommand2(std::vector<std::string> && name)
{
return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
}
DerivedPathsWithHints build(ref<Store> store, Realise mode,
std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode = bmNormal);
std::set<StorePath> toStorePaths(ref<Store> store,
@ -207,6 +231,12 @@ std::set<StorePath> toDerivations(ref<Store> store,
std::vector<std::shared_ptr<Installable>> installables,
bool useDeriver = false);
std::set<RealisedPath> toRealisedPaths(
ref<Store> store,
Realise mode,
OperateOn operateOn,
std::vector<std::shared_ptr<Installable>> installables);
/* Helper function to generate args that invoke $EDITOR on
filename:lineno. */
Strings editorFor(const Pos & pos);
@ -222,7 +252,7 @@ struct MixProfile : virtual StoreCommand
/* If 'profile' is set, make it point at the store path produced
by 'buildables'. */
void updateProfile(const Buildables & buildables);
void updateProfile(const DerivedPathsWithHints & buildables);
};
struct MixDefaultProfile : MixProfile
@ -252,6 +282,8 @@ void completeFlakeRefWithFragment(
const Strings & defaultFlakeAttrPaths,
std::string_view prefix);
std::string showVersions(const std::set<std::string> & versions);
void printClosureDiff(
ref<Store> store,
const StorePath & beforePath,

View file

@ -16,6 +16,8 @@
#include <regex>
#include <queue>
#include <nlohmann/json.hpp>
namespace nix {
void completeFlakeInputPath(
@ -31,39 +33,47 @@ void completeFlakeInputPath(
MixFlakeOptions::MixFlakeOptions()
{
auto category = "Common flake-related options";
addFlag({
.longName = "recreate-lock-file",
.description = "recreate lock file from scratch",
.description = "Recreate the flake's lock file from scratch.",
.category = category,
.handler = {&lockFlags.recreateLockFile, true}
});
addFlag({
.longName = "no-update-lock-file",
.description = "do not allow any updates to the lock file",
.description = "Do not allow any updates to the flake's lock file.",
.category = category,
.handler = {&lockFlags.updateLockFile, false}
});
addFlag({
.longName = "no-write-lock-file",
.description = "do not write the newly generated lock file",
.description = "Do not write the flake's newly generated lock file.",
.category = category,
.handler = {&lockFlags.writeLockFile, false}
});
addFlag({
.longName = "no-registries",
.description = "don't use flake registries",
.description = "Don't allow lookups in the flake registries.",
.category = category,
.handler = {&lockFlags.useRegistries, false}
});
addFlag({
.longName = "commit-lock-file",
.description = "commit changes to the lock file",
.description = "Commit changes to the flake's lock file.",
.category = category,
.handler = {&lockFlags.commitLockFile, true}
});
addFlag({
.longName = "update-input",
.description = "update a specific flake input",
.description = "Update a specific flake input (ignoring its previous entry in the lock file).",
.category = category,
.labels = {"input-path"},
.handler = {[&](std::string s) {
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
@ -76,9 +86,11 @@ MixFlakeOptions::MixFlakeOptions()
addFlag({
.longName = "override-input",
.description = "override a specific flake input (e.g. `dwarffs/nixpkgs`)",
.description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.",
.category = category,
.labels = {"input-path", "flake-url"},
.handler = {[&](std::string inputPath, std::string flakeRef) {
lockFlags.writeLockFile = false;
lockFlags.inputOverrides.insert_or_assign(
flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath(".")));
@ -87,7 +99,8 @@ MixFlakeOptions::MixFlakeOptions()
addFlag({
.longName = "inputs-from",
.description = "use the inputs of the specified flake as registry entries",
.description = "Use the inputs of the specified flake as registry entries.",
.category = category,
.labels = {"flake-url"},
.handler = {[&](std::string flakeRef) {
auto evalState = getEvalState();
@ -116,22 +129,25 @@ SourceExprCommand::SourceExprCommand()
addFlag({
.longName = "file",
.shortName = 'f',
.description = "evaluate *file* rather than the default",
.description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.",
.category = installablesCategory,
.labels = {"file"},
.handler = {&file},
.completer = completePath
});
addFlag({
.longName ="expr",
.description = "evaluate attributes from *expr*",
.longName = "expr",
.description = "Interpret installables as attribute paths relative to the Nix expression *expr*.",
.category = installablesCategory,
.labels = {"expr"},
.handler = {&expr}
});
addFlag({
.longName ="derivation",
.description = "operate on the store derivation rather than its outputs",
.longName = "derivation",
.description = "Operate on the store derivation rather than its outputs.",
.category = installablesCategory,
.handler = {&operateOn, OperateOn::Derivation},
});
}
@ -240,6 +256,12 @@ ref<EvalState> EvalCommand::getEvalState()
return ref<EvalState>(evalState);
}
EvalCommand::~EvalCommand()
{
if (evalState)
evalState->printStats();
}
void completeFlakeRef(ref<Store> store, std::string_view prefix)
{
if (prefix == "")
@ -263,9 +285,9 @@ void completeFlakeRef(ref<Store> store, std::string_view prefix)
}
}
Buildable Installable::toBuildable()
DerivedPathWithHints Installable::toDerivedPathWithHints()
{
auto buildables = toBuildables();
auto buildables = toDerivedPathsWithHints();
if (buildables.size() != 1)
throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size());
return std::move(buildables[0]);
@ -299,7 +321,7 @@ struct InstallableStorePath : Installable
std::string what() override { return store->printStorePath(storePath); }
Buildables toBuildables() override
DerivedPathsWithHints toDerivedPathsWithHints() override
{
if (storePath.isDerivation()) {
std::map<std::string, std::optional<StorePath>> outputs;
@ -307,14 +329,14 @@ struct InstallableStorePath : Installable
for (auto & [name, output] : drv.outputsAndOptPaths(*store))
outputs.emplace(name, output.second);
return {
BuildableFromDrv {
DerivedPathWithHints::Built {
.drvPath = storePath,
.outputs = std::move(outputs)
}
};
} else {
return {
BuildableOpaque {
DerivedPathWithHints::Opaque {
.path = storePath,
}
};
@ -327,9 +349,9 @@ struct InstallableStorePath : Installable
}
};
Buildables InstallableValue::toBuildables()
DerivedPathsWithHints InstallableValue::toDerivedPathsWithHints()
{
Buildables res;
DerivedPathsWithHints res;
std::map<StorePath, std::map<std::string, std::optional<StorePath>>> drvsToOutputs;
@ -342,7 +364,7 @@ Buildables InstallableValue::toBuildables()
}
for (auto & i : drvsToOutputs)
res.push_back(BuildableFromDrv { i.first, i.second });
res.push_back(DerivedPathWithHints::Built { i.first, i.second });
return res;
}
@ -382,7 +404,7 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
for (auto & drvInfo : drvInfos) {
res.push_back({
state->store->parseStorePath(drvInfo.queryDrvPath()),
state->store->parseStorePath(drvInfo.queryOutPath()),
state->store->maybeParseStorePath(drvInfo.queryOutPath()),
drvInfo.queryOutputName()
});
}
@ -456,6 +478,23 @@ static std::string showAttrPaths(const std::vector<std::string> & paths)
return s;
}
InstallableFlake::InstallableFlake(
SourceExprCommand * cmd,
ref<EvalState> state,
FlakeRef && flakeRef,
Strings && attrPaths,
Strings && prefixes,
const flake::LockFlags & lockFlags)
: InstallableValue(state),
flakeRef(flakeRef),
attrPaths(attrPaths),
prefixes(prefixes),
lockFlags(lockFlags)
{
if (cmd && cmd->getAutoArgs(*state)->size())
throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
}
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{
auto lockedFlake = getLockedFlake();
@ -474,7 +513,7 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto drvInfo = DerivationInfo{
std::move(drvPath),
state->store->parseStorePath(attr->getAttr(state->sOutPath)->getString()),
state->store->maybeParseStorePath(attr->getAttr(state->sOutPath)->getString()),
attr->getAttr(state->sOutputName)->getString()
};
@ -533,8 +572,11 @@ InstallableFlake::getCursors(EvalState & state)
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{
if (!_lockedFlake)
if (!_lockedFlake) {
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags));
_lockedFlake->flake.config.apply();
// FIXME: send new config to the daemon.
}
return _lockedFlake;
}
@ -585,9 +627,12 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
try {
auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
result.push_back(std::make_shared<InstallableFlake>(
getEvalState(), std::move(flakeRef),
this,
getEvalState(),
std::move(flakeRef),
fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
getDefaultFlakeAttrPathPrefixes(), lockFlags));
getDefaultFlakeAttrPathPrefixes(),
lockFlags));
continue;
} catch (...) {
ex = std::current_exception();
@ -626,29 +671,30 @@ std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
return installables.front();
}
Buildables build(ref<Store> store, Realise mode,
DerivedPathsWithHints build(ref<Store> store, Realise mode,
std::vector<std::shared_ptr<Installable>> installables, BuildMode bMode)
{
if (mode == Realise::Nothing)
settings.readOnlyMode = true;
Buildables buildables;
DerivedPathsWithHints buildables;
std::vector<StorePathWithOutputs> pathsToBuild;
std::vector<DerivedPath> pathsToBuild;
for (auto & i : installables) {
for (auto & b : i->toBuildables()) {
for (auto & b : i->toDerivedPathsWithHints()) {
std::visit(overloaded {
[&](BuildableOpaque bo) {
pathsToBuild.push_back({bo.path});
[&](DerivedPathWithHints::Opaque bo) {
pathsToBuild.push_back(bo);
},
[&](BuildableFromDrv bfd) {
[&](DerivedPathWithHints::Built bfd) {
StringSet outputNames;
for (auto & output : bfd.outputs)
outputNames.insert(output.first);
pathsToBuild.push_back({bfd.drvPath, outputNames});
pathsToBuild.push_back(
DerivedPath::Built{bfd.drvPath, outputNames});
},
}, b);
}, b.raw());
buildables.push_back(std::move(b));
}
}
@ -661,36 +707,65 @@ Buildables build(ref<Store> store, Realise mode,
return buildables;
}
StorePathSet toStorePaths(ref<Store> store,
Realise mode, OperateOn operateOn,
std::set<RealisedPath> toRealisedPaths(
ref<Store> store,
Realise mode,
OperateOn operateOn,
std::vector<std::shared_ptr<Installable>> installables)
{
StorePathSet outPaths;
std::set<RealisedPath> res;
if (operateOn == OperateOn::Output) {
for (auto & b : build(store, mode, installables))
std::visit(overloaded {
[&](BuildableOpaque bo) {
outPaths.insert(bo.path);
[&](DerivedPathWithHints::Opaque bo) {
res.insert(bo.path);
},
[&](BuildableFromDrv bfd) {
[&](DerivedPathWithHints::Built bfd) {
auto drv = store->readDerivation(bfd.drvPath);
auto outputHashes = staticOutputHashes(*store, drv);
for (auto & output : bfd.outputs) {
if (!output.second)
throw Error("Cannot operate on output of unbuilt CA drv");
outPaths.insert(*output.second);
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
if (!outputHashes.count(output.first))
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
store->printStorePath(bfd.drvPath),
output.first);
auto outputId = DrvOutput{outputHashes.at(output.first), output.first};
auto realisation = store->queryRealisation(outputId);
if (!realisation)
throw Error("cannot operate on an output of unbuilt content-addresed derivation '%s'", outputId.to_string());
res.insert(RealisedPath{*realisation});
}
else {
// If ca-derivations isn't enabled, behave as if
// all the paths are opaque to keep the default
// behavior
assert(output.second);
res.insert(*output.second);
}
}
},
}, b);
}, b.raw());
} else {
if (mode == Realise::Nothing)
settings.readOnlyMode = true;
for (auto & i : installables)
for (auto & b : i->toBuildables())
if (auto bfd = std::get_if<BuildableFromDrv>(&b))
outPaths.insert(bfd->drvPath);
for (auto & b : i->toDerivedPathsWithHints())
if (auto bfd = std::get_if<DerivedPathWithHints::Built>(&b))
res.insert(bfd->drvPath);
}
return res;
}
StorePathSet toStorePaths(ref<Store> store,
Realise mode, OperateOn operateOn,
std::vector<std::shared_ptr<Installable>> installables)
{
StorePathSet outPaths;
for (auto & path : toRealisedPaths(store, mode, operateOn, installables))
outPaths.insert(path.path());
return outPaths;
}
@ -712,9 +787,9 @@ StorePathSet toDerivations(ref<Store> store,
StorePathSet drvPaths;
for (auto & i : installables)
for (auto & b : i->toBuildables())
for (auto & b : i->toDerivedPathsWithHints())
std::visit(overloaded {
[&](BuildableOpaque bo) {
[&](DerivedPathWithHints::Opaque bo) {
if (!useDeriver)
throw Error("argument '%s' did not evaluate to a derivation", i->what());
auto derivers = store->queryValidDerivers(bo.path);
@ -723,10 +798,10 @@ StorePathSet toDerivations(ref<Store> store,
// FIXME: use all derivers?
drvPaths.insert(*derivers.begin());
},
[&](BuildableFromDrv bfd) {
[&](DerivedPathWithHints::Built bfd) {
drvPaths.insert(bfd.drvPath);
},
}, b);
}, b.raw());
return drvPaths;
}

View file

@ -2,6 +2,8 @@
#include "util.hh"
#include "path.hh"
#include "path-with-outputs.hh"
#include "derived-path.hh"
#include "eval.hh"
#include "flake/flake.hh"
@ -14,22 +16,6 @@ struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
struct BuildableOpaque {
StorePath path;
};
struct BuildableFromDrv {
StorePath drvPath;
std::map<std::string, std::optional<StorePath>> outputs;
};
typedef std::variant<
BuildableOpaque,
BuildableFromDrv
> Buildable;
typedef std::vector<Buildable> Buildables;
struct App
{
std::vector<StorePathWithOutputs> context;
@ -43,9 +29,9 @@ struct Installable
virtual std::string what() = 0;
virtual Buildables toBuildables() = 0;
virtual DerivedPathsWithHints toDerivedPathsWithHints() = 0;
Buildable toBuildable();
DerivedPathWithHints toDerivedPathWithHints();
App toApp(EvalState & state);
@ -88,7 +74,7 @@ struct InstallableValue : Installable
virtual std::vector<DerivationInfo> toDerivations() = 0;
Buildables toBuildables() override;
DerivedPathsWithHints toDerivedPathsWithHints() override;
};
struct InstallableFlake : InstallableValue
@ -99,11 +85,13 @@ struct InstallableFlake : InstallableValue
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
InstallableFlake(ref<EvalState> state, FlakeRef && flakeRef,
Strings && attrPaths, Strings && prefixes, const flake::LockFlags & lockFlags)
: InstallableValue(state), flakeRef(flakeRef), attrPaths(attrPaths),
prefixes(prefixes), lockFlags(lockFlags)
{ }
InstallableFlake(
SourceExprCommand * cmd,
ref<EvalState> state,
FlakeRef && flakeRef,
Strings && attrPaths,
Strings && prefixes,
const flake::LockFlags & lockFlags);
std::string what() override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }

15
src/libcmd/local.mk Normal file
View file

@ -0,0 +1,15 @@
libraries += libcmd
libcmd_NAME = libnixcmd
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_LDFLAGS = -llowdown
libcmd_LIBS = libstore libutil libexpr libmain libfetchers
$(eval $(call install-file-in, $(d)/nix-cmd.pc, $(prefix)/lib/pkgconfig, 0644))

View file

@ -3,9 +3,7 @@
#include "finally.hh"
#include <sys/queue.h>
extern "C" {
#include <lowdown.h>
}
namespace nix {
@ -42,7 +40,9 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
throw Error("cannot allocate Markdown output buffer");
Finally freeBuffer([&]() { lowdown_buf_free(buf); });
lowdown_term_rndr(buf, nullptr, renderer, node);
int rndr_res = lowdown_term_rndr(buf, nullptr, renderer, node);
if (!rndr_res)
throw Error("allocation error while rendering Markdown");
return std::string(buf->data, buf->size);
}

9
src/libcmd/nix-cmd.pc.in Normal file
View file

@ -0,0 +1,9 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@
Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixcmd
Cflags: -I${includedir}/nix -std=c++17

View file

@ -52,9 +52,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
for (auto & attr : tokens) {
/* Is i an index (integer) or a normal attribute name? */
enum { apAttr, apIndex } apType = apAttr;
unsigned int attrIndex;
if (string2Int(attr, attrIndex)) apType = apIndex;
auto attrIndex = string2Int<unsigned int>(attr);
/* Evaluate the expression. */
Value * vNew = state.allocValue();
@ -65,9 +63,9 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
/* It should evaluate to either a set or an expression,
according to what is specified in the attrPath. */
if (apType == apAttr) {
if (!attrIndex) {
if (v->type != tAttrs)
if (v->type() != nAttrs)
throw TypeError(
"the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath,
@ -82,17 +80,17 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
pos = *a->pos;
}
else if (apType == apIndex) {
else {
if (!v->isList())
throw TypeError(
"the expression selected by the selection path '%1%' should be a list but is %2%",
attrPath,
showType(*v));
if (attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", attrIndex, attrPath);
if (*attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
v = v->listElems()[attrIndex];
v = v->listElems()[*attrIndex];
pos = noPos;
}

View file

@ -24,9 +24,7 @@ void EvalState::mkAttrs(Value & v, size_t capacity)
v = vEmptySet;
return;
}
clearValue(v);
v.type = tAttrs;
v.attrs = allocBindings(capacity);
v.mkAttrs(allocBindings(capacity));
nrAttrsets++;
nrAttrsInAttrsets += capacity;
}

View file

@ -77,7 +77,7 @@ public:
auto a = get(name);
if (!a)
throw Error({
.hint = hintfmt("attribute '%s' missing", name),
.msg = hintfmt("attribute '%s' missing", name),
.errPos = pos
});

View file

@ -12,16 +12,20 @@ namespace nix {
MixEvalArgs::MixEvalArgs()
{
auto category = "Common evaluation options";
addFlag({
.longName = "arg",
.description = "argument to be passed to Nix functions",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) { autoArgs[name] = 'E' + expr; }}
});
addFlag({
.longName = "argstr",
.description = "string-valued argument to be passed to Nix functions",
.description = "Pass the string *string* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "string"},
.handler = {[&](std::string name, std::string s) { autoArgs[name] = 'S' + s; }},
});
@ -29,14 +33,16 @@ MixEvalArgs::MixEvalArgs()
addFlag({
.longName = "include",
.shortName = 'I',
.description = "add a path to the list of locations used to look up `<...>` file names",
.description = "Add *path* to the list of locations used to look up `<...>` file names.",
.category = category,
.labels = {"path"},
.handler = {[&](std::string s) { searchPath.push_back(s); }}
});
addFlag({
.longName = "impure",
.description = "allow access to mutable paths and repositories",
.description = "Allow access to mutable paths and repositories.",
.category = category,
.handler = {[&]() {
evalSettings.pureEval = false;
}},
@ -44,7 +50,8 @@ MixEvalArgs::MixEvalArgs()
addFlag({
.longName = "override-flake",
.description = "override a flake registry value",
.description = "Override the flake registries, redirecting *original-ref* to *resolved-ref*.",
.category = category,
.labels = {"original-ref", "resolved-ref"},
.handler = {[&](std::string _from, std::string _to) {
auto from = parseFlakeRef(_from, absPath("."));

View file

@ -390,14 +390,14 @@ Value & AttrCursor::forceValue()
}
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
if (v.type == tString)
if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
else if (v.type == tPath)
cachedValue = {root->db->setString(getKey(), v.path), v.path};
else if (v.type == tBool)
else if (v.type() == nPath)
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 == tAttrs)
else if (v.type() == nAttrs)
; // FIXME: do something?
else
cachedValue = {root->db->setMisc(getKey()), misc_t()};
@ -442,7 +442,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
auto & v = forceValue();
if (v.type != tAttrs)
if (v.type() != nAttrs)
return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
@ -512,10 +512,10 @@ std::string AttrCursor::getString()
auto & v = forceValue();
if (v.type != tString && v.type != tPath)
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type));
if (v.type() != nString && v.type() != nPath)
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
return v.type == tString ? v.string.s : v.path;
return v.type() == nString ? v.string.s : v.path;
}
string_t AttrCursor::getStringWithContext()
@ -525,8 +525,17 @@ string_t AttrCursor::getStringWithContext()
cachedValue = root->db->getAttr(getKey(), root->state.symbols);
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
debug("using cached string attribute '%s'", getAttrPathStr());
return *s;
bool valid = true;
for (auto & c : s->second) {
if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) {
valid = false;
break;
}
}
if (valid) {
debug("using cached string attribute '%s'", getAttrPathStr());
return *s;
}
} else
throw TypeError("'%s' is not a string", getAttrPathStr());
}
@ -534,12 +543,12 @@ string_t AttrCursor::getStringWithContext()
auto & v = forceValue();
if (v.type == tString)
if (v.type() == nString)
return {v.string.s, v.getContext()};
else if (v.type == tPath)
else if (v.type() == nPath)
return {v.path, {}};
else
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type));
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()));
}
bool AttrCursor::getBool()
@ -558,7 +567,7 @@ bool AttrCursor::getBool()
auto & v = forceValue();
if (v.type != tBool)
if (v.type() != nBool)
throw TypeError("'%s' is not a Boolean", getAttrPathStr());
return v.boolean;
@ -580,7 +589,7 @@ std::vector<Symbol> AttrCursor::getAttrs()
auto & v = forceValue();
if (v.type != tAttrs)
if (v.type() != nAttrs)
throw TypeError("'%s' is not an attribute set", getAttrPathStr());
std::vector<Symbol> attrs;

View file

@ -10,7 +10,7 @@ namespace nix {
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s))
{
throw EvalError({
.hint = hintfmt(s),
.msg = hintfmt(s),
.errPos = pos
});
}
@ -24,7 +24,7 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v))
{
throw TypeError({
.hint = hintfmt(s, showType(v)),
.msg = hintfmt(s, showType(v)),
.errPos = pos
});
}
@ -32,23 +32,21 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
void EvalState::forceValue(Value & v, const Pos & pos)
{
if (v.type == tThunk) {
if (v.isThunk()) {
Env * env = v.thunk.env;
Expr * expr = v.thunk.expr;
try {
v.type = tBlackhole;
v.mkBlackhole();
//checkInterrupt();
expr->eval(*this, *env, v);
} catch (...) {
v.type = tThunk;
v.thunk.env = env;
v.thunk.expr = expr;
v.mkThunk(env, expr);
throw;
}
}
else if (v.type == tApp)
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.type == tBlackhole)
else if (v.isBlackhole())
throwEvalError(pos, "infinite recursion encountered");
}
@ -56,7 +54,7 @@ void EvalState::forceValue(Value & v, const Pos & pos)
inline void EvalState::forceAttrs(Value & v)
{
forceValue(v);
if (v.type != tAttrs)
if (v.type() != nAttrs)
throwTypeError("value is %1% while a set was expected", v);
}
@ -64,7 +62,7 @@ inline void EvalState::forceAttrs(Value & v)
inline void EvalState::forceAttrs(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type != tAttrs)
if (v.type() != nAttrs)
throwTypeError(pos, "value is %1% while a set was expected", v);
}

View file

@ -27,6 +27,10 @@
#include <gc/gc.h>
#include <gc/gc_cpp.h>
#include <boost/coroutine2/coroutine.hpp>
#include <boost/coroutine2/protected_fixedsize_stack.hpp>
#include <boost/context/stack_context.hpp>
#endif
namespace nix {
@ -64,7 +68,7 @@ RootValue allocRootValue(Value * v)
}
static void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v)
void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v)
{
checkInterrupt();
@ -73,7 +77,7 @@ static void printValue(std::ostream & str, std::set<const Value *> & active, con
return;
}
switch (v.type) {
switch (v.internalType) {
case tInt:
str << v.integer;
break;
@ -154,32 +158,27 @@ std::ostream & operator << (std::ostream & str, const Value & v)
const Value *getPrimOp(const Value &v) {
const Value * primOp = &v;
while (primOp->type == tPrimOpApp) {
while (primOp->isPrimOpApp()) {
primOp = primOp->primOpApp.left;
}
assert(primOp->type == tPrimOp);
assert(primOp->isPrimOp());
return primOp;
}
string showType(ValueType type)
{
switch (type) {
case tInt: return "an integer";
case tBool: return "a Boolean";
case tString: return "a string";
case tPath: return "a path";
case tNull: return "null";
case tAttrs: return "a set";
case tList1: case tList2: case tListN: return "a list";
case tThunk: return "a thunk";
case tApp: return "a function application";
case tLambda: return "a function";
case tBlackhole: return "a black hole";
case tPrimOp: return "a built-in function";
case tPrimOpApp: return "a partially applied built-in function";
case tExternal: return "an external value";
case tFloat: return "a float";
case nInt: return "an integer";
case nBool: return "a Boolean";
case nString: return "a string";
case nPath: return "a path";
case nNull: return "null";
case nAttrs: return "a set";
case nList: return "a list";
case nFunction: return "a function";
case nExternal: return "an external value";
case nFloat: return "a float";
case nThunk: return "a thunk";
}
abort();
}
@ -187,15 +186,18 @@ string showType(ValueType type)
string showType(const Value & v)
{
switch (v.type) {
switch (v.internalType) {
case tString: return v.string.context ? "a string with context" : "a string";
case tPrimOp:
return fmt("the built-in function '%s'", string(v.primOp->name));
case tPrimOpApp:
return fmt("the partially applied built-in function '%s'", string(getPrimOp(v)->primOp->name));
case tExternal: return v.external->showType();
case tThunk: return "a thunk";
case tApp: return "a function application";
case tBlackhole: return "a black hole";
default:
return showType(v.type);
return showType(v.type());
}
}
@ -203,12 +205,13 @@ string showType(const Value & v)
bool Value::isTrivial() const
{
return
type != tApp
&& type != tPrimOpApp
&& (type != tThunk
internalType != tApp
&& internalType != tPrimOpApp
&& (internalType != tThunk
|| (dynamic_cast<ExprAttrs *>(thunk.expr)
&& ((ExprAttrs *) thunk.expr)->dynamicAttrs.empty())
|| dynamic_cast<ExprLambda *>(thunk.expr));
|| dynamic_cast<ExprLambda *>(thunk.expr)
|| dynamic_cast<ExprList *>(thunk.expr));
}
@ -219,6 +222,31 @@ static void * oomHandler(size_t requested)
/* Convert this to a proper C++ exception. */
throw std::bad_alloc();
}
class BoehmGCStackAllocator : public StackAllocator {
boost::coroutines2::protected_fixedsize_stack stack {
// We allocate 8 MB, the default max stack size on NixOS.
// A smaller stack might be quicker to allocate but reduces the stack
// depth available for source filter expressions etc.
std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))
};
public:
boost::context::stack_context allocate() override {
auto sctx = stack.allocate();
GC_add_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp);
return sctx;
}
void deallocate(boost::context::stack_context sctx) override {
GC_remove_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp);
stack.deallocate(sctx);
}
};
static BoehmGCStackAllocator boehmGCStackAllocator;
#endif
@ -256,6 +284,8 @@ void initGC()
GC_set_oom_fn(oomHandler);
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
/* Set the initial heap size to something fairly big (25% of
physical RAM, up to a maximum of 384 MiB) so that in most cases
we don't need to garbage collect at all. (Collection has a
@ -372,11 +402,6 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
for (auto & i : evalSettings.nixPath.get()) addToSearchPath(i);
}
try {
addToSearchPath("nix=" + canonPath(settings.nixDataDir + "/nix/corepkgs", true));
} catch (Error &) {
}
if (evalSettings.restrictEval || evalSettings.pureEval) {
allowedPaths = PathSet();
@ -400,9 +425,7 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
}
}
clearValue(vEmptySet);
vEmptySet.type = tAttrs;
vEmptySet.attrs = allocBindings(0);
vEmptySet.mkAttrs(allocBindings(0));
createBaseEnv();
}
@ -429,6 +452,8 @@ Path EvalState::checkSourcePath(const Path & path_)
*/
Path abspath = canonPath(path_);
if (hasPrefix(abspath, corepkgsPrefix)) return abspath;
for (auto & i : *allowedPaths) {
if (isDirOrInDir(abspath, i)) {
found = true;
@ -518,16 +543,14 @@ Value * EvalState::addPrimOp(const string & name,
the primop to a dummy value. */
if (arity == 0) {
auto vPrimOp = allocValue();
vPrimOp->type = tPrimOp;
vPrimOp->primOp = new PrimOp { .fun = primOp, .arity = 1, .name = sym };
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym });
Value v;
mkApp(v, *vPrimOp, *vPrimOp);
return addConstant(name, v);
}
Value * v = allocValue();
v->type = tPrimOp;
v->primOp = new PrimOp { .fun = primOp, .arity = arity, .name = sym };
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl;
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
@ -542,8 +565,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
if (primOp.arity == 0) {
primOp.arity = 1;
auto vPrimOp = allocValue();
vPrimOp->type = tPrimOp;
vPrimOp->primOp = new PrimOp(std::move(primOp));
vPrimOp->mkPrimOp(new PrimOp(std::move(primOp)));
Value v;
mkApp(v, *vPrimOp, *vPrimOp);
return addConstant(primOp.name, v);
@ -554,8 +576,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
primOp.name = symbols.create(std::string(primOp.name, 2));
Value * v = allocValue();
v->type = tPrimOp;
v->primOp = new PrimOp(std::move(primOp));
v->mkPrimOp(new PrimOp(std::move(primOp)));
staticBaseEnv.vars[envName] = baseEnvDispl;
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
@ -571,10 +592,8 @@ Value & EvalState::getBuiltin(const string & name)
std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
{
if (v.type == tPrimOp || v.type == tPrimOpApp) {
if (v.isPrimOp()) {
auto v2 = &v;
while (v2->type == tPrimOpApp)
v2 = v2->primOpApp.left;
if (v2->primOp->doc)
return Doc {
.pos = noPos,
@ -601,7 +620,7 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2))
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2))
{
throw EvalError({
.hint = hintfmt(s, s2),
.msg = hintfmt(s, s2),
.errPos = pos
});
}
@ -614,7 +633,7 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, con
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3))
{
throw EvalError({
.hint = hintfmt(s, s2, s3),
.msg = hintfmt(s, s2, s3),
.errPos = pos
});
}
@ -623,7 +642,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const
{
// p1 is where the error occurred; p2 is a position mentioned in the message.
throw EvalError({
.hint = hintfmt(s, sym, p2),
.msg = hintfmt(s, sym, p2),
.errPos = p1
});
}
@ -631,20 +650,15 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s))
{
throw TypeError({
.hint = hintfmt(s),
.msg = hintfmt(s),
.errPos = pos
});
}
LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1))
{
throw TypeError(s, s1);
}
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2))
{
throw TypeError({
.hint = hintfmt(s, fun.showNamePos(), s2),
.msg = hintfmt(s, fun.showNamePos(), s2),
.errPos = pos
});
}
@ -652,7 +666,7 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1))
{
throw AssertionError({
.hint = hintfmt(s, s1),
.msg = hintfmt(s, s1),
.errPos = pos
});
}
@ -660,7 +674,15 @@ LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s,
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1))
{
throw UndefinedVarError({
.hint = hintfmt(s, s1),
.msg = hintfmt(s, s1),
.errPos = pos
});
}
LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1))
{
throw MissingArgumentError({
.msg = hintfmt(s, s1),
.errPos = pos
});
}
@ -678,15 +700,13 @@ LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, con
void mkString(Value & v, const char * s)
{
mkStringNoCopy(v, dupString(s));
v.mkString(dupString(s));
}
Value & mkString(Value & v, std::string_view s, const PathSet & context)
{
v.type = tString;
v.string.s = dupStringWithLen(s.data(), s.size());
v.string.context = 0;
v.mkString(dupStringWithLen(s.data(), s.size()));
if (!context.empty()) {
size_t n = 0;
v.string.context = (const char * *)
@ -701,7 +721,7 @@ Value & mkString(Value & v, std::string_view s, const PathSet & context)
void mkPath(Value & v, const char * s)
{
mkPathNoCopy(v, dupString(s));
v.mkPath(dupString(s));
}
@ -762,16 +782,9 @@ Env & EvalState::allocEnv(size_t size)
void EvalState::mkList(Value & v, size_t size)
{
clearValue(v);
if (size == 1)
v.type = tList1;
else if (size == 2)
v.type = tList2;
else {
v.type = tListN;
v.bigList.size = size;
v.bigList.elems = size ? (Value * *) allocBytes(size * sizeof(Value *)) : 0;
}
v.mkList(size);
if (size > 2)
v.bigList.elems = (Value * *) allocBytes(size * sizeof(Value *));
nrListElems += size;
}
@ -780,9 +793,7 @@ unsigned long nrThunks = 0;
static inline void mkThunk(Value & v, Env & env, Expr * expr)
{
v.type = tThunk;
v.thunk.env = &env;
v.thunk.expr = expr;
v.mkThunk(&env, expr);
nrThunks++;
}
@ -917,7 +928,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e)
{
Value v;
e->eval(*this, env, v);
if (v.type != tBool)
if (v.type() != nBool)
throwTypeError("value is %1% while a Boolean was expected", v);
return v.boolean;
}
@ -927,7 +938,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos)
{
Value v;
e->eval(*this, env, v);
if (v.type != tBool)
if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
return v.boolean;
}
@ -936,7 +947,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos)
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
{
e->eval(*this, env, v);
if (v.type != tAttrs)
if (v.type() != nAttrs)
throwTypeError("value is %1% while a set was expected", v);
}
@ -1036,7 +1047,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Value nameVal;
i.nameExpr->eval(state, *dynamicEnv, nameVal);
state.forceValue(nameVal, i.pos);
if (nameVal.type == tNull)
if (nameVal.type() == nNull)
continue;
state.forceStringNoCtx(nameVal);
Symbol nameSym = state.symbols.create(nameVal.string.s);
@ -1121,7 +1132,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
Symbol name = getName(i, state, env);
if (def) {
state.forceValue(*vAttrs, pos);
if (vAttrs->type != tAttrs ||
if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{
def->eval(state, env, v);
@ -1161,7 +1172,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
state.forceValue(*vAttrs);
Bindings::iterator j;
Symbol name = getName(i, state, env);
if (vAttrs->type != tAttrs ||
if (vAttrs->type() != nAttrs ||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
{
mkBool(v, false);
@ -1177,9 +1188,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
void ExprLambda::eval(EvalState & state, Env & env, Value & v)
{
v.type = tLambda;
v.lambda.env = &env;
v.lambda.fun = this;
v.mkLambda(&env, this);
}
@ -1197,11 +1206,11 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
/* Figure out the number of arguments still needed. */
size_t argsDone = 0;
Value * primOp = &fun;
while (primOp->type == tPrimOpApp) {
while (primOp->isPrimOpApp()) {
argsDone++;
primOp = primOp->primOpApp.left;
}
assert(primOp->type == tPrimOp);
assert(primOp->isPrimOp());
auto arity = primOp->primOp->arity;
auto argsLeft = arity - argsDone;
@ -1212,7 +1221,7 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
Value * vArgs[arity];
auto n = arity - 1;
vArgs[n--] = &arg;
for (Value * arg = &fun; arg->type == tPrimOpApp; arg = arg->primOpApp.left)
for (Value * arg = &fun; arg->isPrimOpApp(); arg = arg->primOpApp.left)
vArgs[n--] = arg->primOpApp.right;
/* And call the primop. */
@ -1222,9 +1231,7 @@ void EvalState::callPrimOp(Value & fun, Value & arg, Value & v, const Pos & pos)
} else {
Value * fun2 = allocValue();
*fun2 = fun;
v.type = tPrimOpApp;
v.primOpApp.left = fun2;
v.primOpApp.right = &arg;
v.mkPrimOpApp(fun2, &arg);
}
}
@ -1234,12 +1241,12 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
forceValue(fun, pos);
if (fun.type == tPrimOp || fun.type == tPrimOpApp) {
if (fun.isPrimOp() || fun.isPrimOpApp()) {
callPrimOp(fun, arg, v, pos);
return;
}
if (fun.type == tAttrs) {
if (fun.type() == nAttrs) {
auto found = fun.attrs->find(sFunctor);
if (found != fun.attrs->end()) {
/* fun may be allocated on the stack of the calling function,
@ -1255,7 +1262,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
}
}
if (fun.type != tLambda)
if (!fun.isLambda())
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
ExprLambda & lambda(*fun.lambda.fun);
@ -1338,7 +1345,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
{
forceValue(fun);
if (fun.type == tAttrs) {
if (fun.type() == nAttrs) {
auto found = fun.attrs->find(sFunctor);
if (found != fun.attrs->end()) {
Value * v = allocValue();
@ -1348,7 +1355,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
}
}
if (fun.type != tLambda || !fun.lambda.fun->matchAttrs) {
if (!fun.isLambda() || !fun.lambda.fun->matchAttrs) {
res = fun;
return;
}
@ -1370,7 +1377,13 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) {
actualArgs->attrs->push_back(*j);
} else if (!i.def) {
throwTypeError("cannot auto-call a function that has an argument without a default value ('%1%')", i.name);
throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%')
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.)", i.name);
}
}
}
@ -1404,7 +1417,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
if (!state.evalBool(env, cond, pos)) {
std::ostringstream out;
cond->show(out);
throwAssertionError(pos, "assertion '%1%' failed at %2%", out.str());
throwAssertionError(pos, "assertion '%1%' failed", out.str());
}
body->eval(state, env, v);
}
@ -1532,7 +1545,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
NixFloat nf = 0;
bool first = !forceString;
ValueType firstType = tString;
ValueType firstType = nString;
for (auto & i : *es) {
Value vTmp;
@ -1543,36 +1556,36 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
since paths are copied when they are used in a derivation),
and none of the strings are allowed to have contexts. */
if (first) {
firstType = vTmp.type;
firstType = vTmp.type();
first = false;
}
if (firstType == tInt) {
if (vTmp.type == tInt) {
if (firstType == nInt) {
if (vTmp.type() == nInt) {
n += vTmp.integer;
} else if (vTmp.type == tFloat) {
} else if (vTmp.type() == nFloat) {
// Upgrade the type from int to float;
firstType = tFloat;
firstType = nFloat;
nf = n;
nf += vTmp.fpoint;
} else
throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp));
} else if (firstType == tFloat) {
if (vTmp.type == tInt) {
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
} else if (vTmp.type == tFloat) {
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
throwEvalError(pos, "cannot add %1% to a float", showType(vTmp));
} else
s << state.coerceToString(pos, vTmp, context, false, firstType == tString);
s << state.coerceToString(pos, vTmp, context, false, firstType == nString);
}
if (firstType == tInt)
if (firstType == nInt)
mkInt(v, n);
else if (firstType == tFloat)
else if (firstType == nFloat)
mkFloat(v, nf);
else if (firstType == tPath) {
else if (firstType == nPath) {
if (!context.empty())
throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
auto path = canonPath(s.str());
@ -1599,7 +1612,7 @@ void EvalState::forceValueDeep(Value & v)
forceValue(v);
if (v.type == tAttrs) {
if (v.type() == nAttrs) {
for (auto & i : *v.attrs)
try {
recurse(*i.value);
@ -1622,7 +1635,7 @@ void EvalState::forceValueDeep(Value & v)
NixInt EvalState::forceInt(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type != tInt)
if (v.type() != nInt)
throwTypeError(pos, "value is %1% while an integer was expected", v);
return v.integer;
}
@ -1631,9 +1644,9 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type == tInt)
if (v.type() == nInt)
return v.integer;
else if (v.type != tFloat)
else if (v.type() != nFloat)
throwTypeError(pos, "value is %1% while a float was expected", v);
return v.fpoint;
}
@ -1642,7 +1655,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
bool EvalState::forceBool(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type != tBool)
if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
return v.boolean;
}
@ -1650,14 +1663,14 @@ bool EvalState::forceBool(Value & v, const Pos & pos)
bool EvalState::isFunctor(Value & fun)
{
return fun.type == tAttrs && fun.attrs->find(sFunctor) != fun.attrs->end();
return fun.type() == nAttrs && fun.attrs->find(sFunctor) != fun.attrs->end();
}
void EvalState::forceFunction(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v))
if (v.type() != nFunction && !isFunctor(v))
throwTypeError(pos, "value is %1% while a function was expected", v);
}
@ -1665,7 +1678,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
string EvalState::forceString(Value & v, const Pos & pos)
{
forceValue(v, pos);
if (v.type != tString) {
if (v.type() != nString) {
if (pos)
throwTypeError(pos, "value is %1% while a string was expected", v);
else
@ -1698,7 +1711,7 @@ void copyContext(const Value & v, PathSet & context)
std::vector<std::pair<Path, std::string>> Value::getContext()
{
std::vector<std::pair<Path, std::string>> res;
assert(type == tString);
assert(internalType == tString);
if (string.context)
for (const char * * p = string.context; *p; ++p)
res.push_back(decodeContext(*p));
@ -1731,11 +1744,11 @@ string EvalState::forceStringNoCtx(Value & v, const Pos & pos)
bool EvalState::isDerivation(Value & v)
{
if (v.type != tAttrs) return false;
if (v.type() != nAttrs) return false;
Bindings::iterator i = v.attrs->find(sType);
if (i == v.attrs->end()) return false;
forceValue(*i->value);
if (i->value->type != tString) return false;
if (i->value->type() != nString) return false;
return strcmp(i->value->string.s, "derivation") == 0;
}
@ -1760,17 +1773,17 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
string s;
if (v.type == tString) {
if (v.type() == nString) {
copyContext(v, context);
return v.string.s;
}
if (v.type == tPath) {
if (v.type() == nPath) {
Path path(canonPath(v.path));
return copyToStore ? copyPathToStore(context, path) : path;
}
if (v.type == tAttrs) {
if (v.type() == nAttrs) {
auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore);
if (maybeString) {
return *maybeString;
@ -1780,18 +1793,18 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
}
if (v.type == tExternal)
if (v.type() == nExternal)
return v.external->coerceToString(pos, context, coerceMore, copyToStore);
if (coerceMore) {
/* Note that `false' is represented as an empty string for
shell scripting convenience, just like `null'. */
if (v.type == tBool && v.boolean) return "1";
if (v.type == tBool && !v.boolean) return "";
if (v.type == tInt) return std::to_string(v.integer);
if (v.type == tFloat) return std::to_string(v.fpoint);
if (v.type == tNull) return "";
if (v.type() == nBool && v.boolean) return "1";
if (v.type() == nBool && !v.boolean) return "";
if (v.type() == nInt) return std::to_string(v.integer);
if (v.type() == nFloat) return std::to_string(v.fpoint);
if (v.type() == nNull) return "";
if (v.isList()) {
string result;
@ -1854,40 +1867,38 @@ bool EvalState::eqValues(Value & v1, Value & v2)
if (&v1 == &v2) return true;
// Special case type-compatibility between float and int
if (v1.type == tInt && v2.type == tFloat)
if (v1.type() == nInt && v2.type() == nFloat)
return v1.integer == v2.fpoint;
if (v1.type == tFloat && v2.type == tInt)
if (v1.type() == nFloat && v2.type() == nInt)
return v1.fpoint == v2.integer;
// All other types are not compatible with each other.
if (v1.type != v2.type) return false;
if (v1.type() != v2.type()) return false;
switch (v1.type) {
switch (v1.type()) {
case tInt:
case nInt:
return v1.integer == v2.integer;
case tBool:
case nBool:
return v1.boolean == v2.boolean;
case tString:
case nString:
return strcmp(v1.string.s, v2.string.s) == 0;
case tPath:
case nPath:
return strcmp(v1.path, v2.path) == 0;
case tNull:
case nNull:
return true;
case tList1:
case tList2:
case tListN:
case nList:
if (v1.listSize() != v2.listSize()) return false;
for (size_t n = 0; n < v1.listSize(); ++n)
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false;
return true;
case tAttrs: {
case nAttrs: {
/* If both sets denote a derivation (type = "derivation"),
then compare their outPaths. */
if (isDerivation(v1) && isDerivation(v2)) {
@ -1909,15 +1920,13 @@ bool EvalState::eqValues(Value & v1, Value & v2)
}
/* Functions are incomparable. */
case tLambda:
case tPrimOp:
case tPrimOpApp:
case nFunction:
return false;
case tExternal:
case nExternal:
return *v1.external == *v2.external;
case tFloat:
case nFloat:
return v1.fpoint == v2.fpoint;
default:
@ -2046,7 +2055,7 @@ void EvalState::printStats()
string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
{
throw TypeError({
.hint = hintfmt("cannot coerce %1% to a string", showType()),
.msg = hintfmt("cannot coerce %1% to a string", showType()),
.errPos = pos
});
}
@ -2072,10 +2081,19 @@ EvalSettings::EvalSettings()
Strings EvalSettings::getDefaultNixPath()
{
Strings res;
auto add = [&](const Path & p) { if (pathExists(p)) { res.push_back(p); } };
auto add = [&](const Path & p, const std::string & s = std::string()) {
if (pathExists(p)) {
if (s.empty()) {
res.push_back(p);
} else {
res.push_back(s + "=" + p);
}
}
};
add(getHome() + "/.nix-defexpr/channels");
add("nixpkgs=" + settings.nixStateDir + "/nix/profiles/per-user/root/channels/nixpkgs");
add(settings.nixStateDir + "/nix/profiles/per-user/root/channels");
add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
add(settings.nixStateDir + "/profiles/per-user/root/channels");
return res;
}

View file

@ -432,4 +432,6 @@ struct EvalSettings : Config
extern EvalSettings evalSettings;
static const std::string corepkgsPrefix{"/__corepkgs__/"};
}

41
src/libexpr/fetchurl.nix Normal file
View file

@ -0,0 +1,41 @@
{ system ? "" # obsolete
, url
, hash ? "" # an SRI hash
# Legacy hash specification
, md5 ? "", sha1 ? "", sha256 ? "", sha512 ? ""
, outputHash ?
if hash != "" then hash else if sha512 != "" then sha512 else if sha1 != "" then sha1 else if md5 != "" then md5 else sha256
, outputHashAlgo ?
if hash != "" then "" else if sha512 != "" then "sha512" else if sha1 != "" then "sha1" else if md5 != "" then "md5" else "sha256"
, executable ? false
, unpack ? false
, name ? baseNameOf (toString url)
}:
derivation {
builder = "builtin:fetchurl";
# New-style output content requirements.
inherit outputHashAlgo outputHash;
outputHashMode = if unpack || executable then "recursive" else "flat";
inherit name url executable unpack;
system = "builtin";
# No need to double the amount of network traffic
preferLocalBuild = true;
impureEnvVars = [
# We borrow these environment variables from the caller to allow
# easy proxy configuration. This is impure, but a fixed-output
# derivation like fetchurl is allowed to do so since its result is
# by definition pure.
"http_proxy" "https_proxy" "ftp_proxy" "all_proxy" "no_proxy"
];
# To make "nix-prefetch-url" work.
urls = [ url ];
}

View file

@ -0,0 +1,81 @@
#include "flake.hh"
#include <nlohmann/json.hpp>
namespace nix::flake {
// setting name -> setting value -> allow or ignore.
typedef std::map<std::string, std::map<std::string, bool>> TrustedList;
Path trustedListPath()
{
return getDataDir() + "/nix/trusted-settings.json";
}
static TrustedList readTrustedList()
{
auto path = trustedListPath();
if (!pathExists(path)) return {};
auto json = nlohmann::json::parse(readFile(path));
return json;
}
static void writeTrustedList(const TrustedList & trustedList)
{
writeFile(trustedListPath(), nlohmann::json(trustedList).dump());
}
void ConfigFile::apply()
{
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-suffix"};
for (auto & [name, value] : settings) {
auto baseName = hasPrefix(name, "extra-") ? std::string(name, 6) : name;
// FIXME: Move into libutil/config.cc.
std::string valueS;
if (auto s = std::get_if<std::string>(&value))
valueS = *s;
else if (auto n = std::get_if<int64_t>(&value))
valueS = fmt("%d", n);
else if (auto b = std::get_if<Explicit<bool>>(&value))
valueS = b->t ? "true" : "false";
else if (auto ss = std::get_if<std::vector<std::string>>(&value))
valueS = concatStringsSep(" ", *ss); // FIXME: evil
else
assert(false);
if (!whitelist.count(baseName)) {
auto trustedList = readTrustedList();
bool trusted = false;
if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
trusted = *saved;
} else {
// FIXME: filter ANSI escapes, newlines, \r, etc.
if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) != 'y') {
if (std::tolower(logger->ask("do you want to permanently mark this value as untrusted (y/N)?").value_or('n')) == 'y') {
trustedList[name][valueS] = false;
writeTrustedList(trustedList);
}
} else {
if (std::tolower(logger->ask("do you want to permanently mark this value as trusted (y/N)?").value_or('n')) == 'y') {
trustedList[name][valueS] = trusted = true;
writeTrustedList(trustedList);
}
}
}
if (!trusted) {
warn("ignoring untrusted flake configuration setting '%s'", name);
continue;
}
}
globalConfig.set(name, valueS);
}
}
}

View file

@ -71,14 +71,20 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
return {std::move(tree), resolvedRef, lockedRef};
}
static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos)
{
if (value.isThunk() && value.isTrivial())
state.forceValue(value, pos);
}
static void expectType(EvalState & state, ValueType type,
Value & value, const Pos & pos)
{
if (value.type == tThunk && value.isTrivial())
state.forceValue(value, pos);
if (value.type != type)
forceTrivialValue(state, value, pos);
if (value.type() != type)
throw Error("expected %s but got %s at %s",
showType(type), showType(value.type), pos);
showType(type), showType(value.type()), pos);
}
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
@ -87,7 +93,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
static FlakeInput parseFlakeInput(EvalState & state,
const std::string & inputName, Value * value, const Pos & pos)
{
expectType(state, tAttrs, *value, pos);
expectType(state, nAttrs, *value, pos);
FlakeInput input;
@ -102,24 +108,32 @@ static FlakeInput parseFlakeInput(EvalState & state,
for (nix::Attr attr : *(value->attrs)) {
try {
if (attr.name == sUrl) {
expectType(state, tString, *attr.value, *attr.pos);
expectType(state, nString, *attr.value, *attr.pos);
url = attr.value->string.s;
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
expectType(state, tBool, *attr.value, *attr.pos);
expectType(state, nBool, *attr.value, *attr.pos);
input.isFlake = attr.value->boolean;
} else if (attr.name == sInputs) {
input.overrides = parseFlakeInputs(state, attr.value, *attr.pos);
} else if (attr.name == sFollows) {
expectType(state, tString, *attr.value, *attr.pos);
expectType(state, nString, *attr.value, *attr.pos);
input.follows = parseInputPath(attr.value->string.s);
} else {
state.forceValue(*attr.value);
if (attr.value->type == tString)
attrs.emplace(attr.name, attr.value->string.s);
else
throw TypeError("flake input attribute '%s' is %s while a string is expected",
attr.name, showType(*attr.value));
switch (attr.value->type()) {
case nString:
attrs.emplace(attr.name, attr.value->string.s);
break;
case nBool:
attrs.emplace(attr.name, Explicit<bool> { attr.value->boolean });
break;
case nInt:
attrs.emplace(attr.name, (long unsigned int)attr.value->integer);
break;
default:
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
attr.name, showType(*attr.value));
}
}
} catch (Error & e) {
e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name));
@ -153,7 +167,7 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
{
std::map<FlakeId, FlakeInput> inputs;
expectType(state, tAttrs, *value, pos);
expectType(state, nAttrs, *value, pos);
for (nix::Attr & inputAttr : *(*value).attrs) {
inputs.emplace(inputAttr.name,
@ -194,15 +208,10 @@ static Flake getFlake(
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
if (vInfo.attrs->get(sEdition))
warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
expectType(state, nString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
@ -214,9 +223,9 @@ static Flake getFlake(
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
expectType(state, tLambda, *outputs->value, *outputs->pos);
expectType(state, nFunction, *outputs->value, *outputs->pos);
if (outputs->value->lambda.fun->matchAttrs) {
if (outputs->value->isLambda() && outputs->value->lambda.fun->matchAttrs) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
@ -228,11 +237,41 @@ static Flake getFlake(
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
auto sNixConfig = state.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos);
for (auto & setting : *nixConfig->value->attrs) {
forceTrivialValue(state, *setting.value, *setting.pos);
if (setting.value->type() == nString)
flake.config.settings.insert({setting.name, state.forceStringNoCtx(*setting.value, *setting.pos)});
else if (setting.value->type() == nInt)
flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)});
else if (setting.value->type() == nBool)
flake.config.settings.insert({setting.name, state.forceBool(*setting.value, *setting.pos)});
else if (setting.value->type() == nList) {
std::vector<std::string> ss;
for (unsigned int n = 0; n < setting.value->listSize(); ++n) {
auto elem = setting.value->listElems()[n];
if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
setting.name, showType(*setting.value));
ss.push_back(state.forceStringNoCtx(*elem, *setting.pos));
}
flake.config.settings.insert({setting.name, ss});
}
else
throw TypeError("flake configuration setting '%s' is %s",
setting.name, showType(*setting.value));
}
}
for (auto & attr : *vInfo.attrs) {
if (attr.name != sEdition &&
attr.name != state.sDescription &&
if (attr.name != state.sDescription &&
attr.name != sInputs &&
attr.name != sOutputs)
attr.name != sOutputs &&
attr.name != sNixConfig)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, attr.name, *attr.pos);
}
@ -259,284 +298,298 @@ LockedFlake lockFlake(
auto flake = getFlake(state, topRef, lockFlags.useRegistries, flakeCache);
// FIXME: symlink attack
auto oldLockFile = LockFile::read(
flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
try {
debug("old lock file: %s", oldLockFile);
// FIXME: symlink attack
auto oldLockFile = LockFile::read(
flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
// FIXME: check whether all overrides are used.
std::map<InputPath, FlakeInput> overrides;
std::set<InputPath> overridesUsed, updatesUsed;
debug("old lock file: %s", oldLockFile);
for (auto & i : lockFlags.inputOverrides)
overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
// FIXME: check whether all overrides are used.
std::map<InputPath, FlakeInput> overrides;
std::set<InputPath> overridesUsed, updatesUsed;
LockFile newLockFile;
for (auto & i : lockFlags.inputOverrides)
overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
std::vector<FlakeRef> parents;
LockFile newLockFile;
std::function<void(
const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode)>
computeLocks;
std::vector<FlakeRef> parents;
computeLocks = [&](
const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode)
{
debug("computing lock file node '%s'", printInputPath(inputPathPrefix));
std::function<void(
const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode)>
computeLocks;
/* Get the overrides (i.e. attributes of the form
'inputs.nixops.inputs.nixpkgs.url = ...'). */
// FIXME: check this
for (auto & [id, input] : flake.inputs) {
for (auto & [idOverride, inputOverride] : input.overrides) {
computeLocks = [&](
const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node,
const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode)
{
debug("computing lock file node '%s'", printInputPath(inputPathPrefix));
/* Get the overrides (i.e. attributes of the form
'inputs.nixops.inputs.nixpkgs.url = ...'). */
// FIXME: check this
for (auto & [id, input] : flake.inputs) {
for (auto & [idOverride, inputOverride] : input.overrides) {
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
inputPath.push_back(idOverride);
overrides.insert_or_assign(inputPath, inputOverride);
}
}
/* 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). */
for (auto & [id, input2] : flakeInputs) {
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
inputPath.push_back(idOverride);
overrides.insert_or_assign(inputPath, inputOverride);
}
}
auto inputPathS = printInputPath(inputPath);
debug("computing input '%s'", inputPathS);
/* 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). */
for (auto & [id, input2] : flakeInputs) {
auto inputPath(inputPathPrefix);
inputPath.push_back(id);
auto inputPathS = printInputPath(inputPath);
debug("computing input '%s'", inputPathS);
try {
/* Do we have an override for this input from one of the
ancestors? */
auto i = overrides.find(inputPath);
bool hasOverride = i != overrides.end();
if (hasOverride) overridesUsed.insert(inputPath);
auto & input = hasOverride ? i->second : input2;
/* Do we have an override for this input from one of the
ancestors? */
auto i = overrides.find(inputPath);
bool hasOverride = i != overrides.end();
if (hasOverride) overridesUsed.insert(inputPath);
auto & input = hasOverride ? i->second : input2;
/* Resolve 'follows' later (since it may refer to an input
path we haven't processed yet. */
if (input.follows) {
InputPath target;
if (hasOverride || input.absolute)
/* 'follows' from an override is relative to the
root of the graph. */
target = *input.follows;
else {
/* Otherwise, it's relative to the current flake. */
target = inputPathPrefix;
for (auto & i : *input.follows) target.push_back(i);
}
debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
node->inputs.insert_or_assign(id, target);
continue;
}
/* Resolve 'follows' later (since it may refer to an input
path we haven't processed yet. */
if (input.follows) {
InputPath target;
if (hasOverride || input.absolute)
/* 'follows' from an override is relative to the
root of the graph. */
target = *input.follows;
else {
/* Otherwise, it's relative to the current flake. */
target = inputPathPrefix;
for (auto & i : *input.follows) target.push_back(i);
}
debug("input '%s' follows '%s'", inputPathS, printInputPath(target));
node->inputs.insert_or_assign(id, target);
continue;
}
assert(input.ref);
assert(input.ref);
/* Do we have an entry in the existing lock file? And we
don't have a --update-input flag for this input? */
std::shared_ptr<LockedNode> oldLock;
/* Do we have an entry in the existing lock file? And we
don't have a --update-input flag for this input? */
std::shared_ptr<LockedNode> oldLock;
updatesUsed.insert(inputPath);
updatesUsed.insert(inputPath);
if (oldNode && !lockFlags.inputUpdates.count(inputPath))
if (auto oldLock2 = get(oldNode->inputs, id))
if (auto oldLock3 = std::get_if<0>(&*oldLock2))
oldLock = *oldLock3;
if (oldNode && !lockFlags.inputUpdates.count(inputPath))
if (auto oldLock2 = get(oldNode->inputs, id))
if (auto oldLock3 = std::get_if<0>(&*oldLock2))
oldLock = *oldLock3;
if (oldLock
&& oldLock->originalRef == *input.ref
&& !hasOverride)
{
debug("keeping existing input '%s'", inputPathS);
if (oldLock
&& oldLock->originalRef == *input.ref
&& !hasOverride)
{
debug("keeping existing input '%s'", inputPathS);
/* Copy the input from the old lock since its flakeref
didn't change and there is no override from a
higher level flake. */
auto childNode = std::make_shared<LockedNode>(
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
/* Copy the input from the old lock since its flakeref
didn't change and there is no override from a
higher level flake. */
auto childNode = std::make_shared<LockedNode>(
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
node->inputs.insert_or_assign(id, childNode);
node->inputs.insert_or_assign(id, childNode);
/* If we have an --update-input flag for an input
of this input, then we must fetch the flake to
update it. */
auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
/* If we have an --update-input flag for an input
of this input, then we must fetch the flake to
update it. */
auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
auto hasChildUpdate =
lb != lockFlags.inputUpdates.end()
&& lb->size() > inputPath.size()
&& std::equal(inputPath.begin(), inputPath.end(), lb->begin());
auto hasChildUpdate =
lb != lockFlags.inputUpdates.end()
&& lb->size() > inputPath.size()
&& std::equal(inputPath.begin(), inputPath.end(), lb->begin());
if (hasChildUpdate) {
auto inputFlake = getFlake(
state, oldLock->lockedRef, false, flakeCache);
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock);
} else {
/* No need to fetch this flake, we can be
lazy. However there may be new overrides on the
inputs of this flake, so we need to check
those. */
FlakeInputs fakeInputs;
if (hasChildUpdate) {
auto inputFlake = getFlake(
state, oldLock->lockedRef, false, flakeCache);
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock);
} else {
/* No need to fetch this flake, we can be
lazy. However there may be new overrides on the
inputs of this flake, so we need to check
those. */
FlakeInputs fakeInputs;
for (auto & i : oldLock->inputs) {
if (auto lockedNode = std::get_if<0>(&i.second)) {
fakeInputs.emplace(i.first, FlakeInput {
.ref = (*lockedNode)->originalRef,
.isFlake = (*lockedNode)->isFlake,
});
} else if (auto follows = std::get_if<1>(&i.second)) {
fakeInputs.emplace(i.first, FlakeInput {
.follows = *follows,
.absolute = true
});
for (auto & i : oldLock->inputs) {
if (auto lockedNode = std::get_if<0>(&i.second)) {
fakeInputs.emplace(i.first, FlakeInput {
.ref = (*lockedNode)->originalRef,
.isFlake = (*lockedNode)->isFlake,
});
} else if (auto follows = std::get_if<1>(&i.second)) {
fakeInputs.emplace(i.first, FlakeInput {
.follows = *follows,
.absolute = true
});
}
}
computeLocks(fakeInputs, childNode, inputPath, oldLock);
}
} else {
/* We need to create a new lock file entry. So fetch
this input. */
debug("creating new input '%s'", inputPathS);
if (!lockFlags.allowMutable && !input.ref->input.isImmutable())
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
if (input.isFlake) {
auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache);
/* 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);
node->inputs.insert_or_assign(id, childNode);
/* Guard against circular flake imports. */
for (auto & parent : parents)
if (parent == *input.ref)
throw Error("found circular import of flake '%s'", parent);
parents.push_back(*input.ref);
Finally cleanup([&]() { parents.pop_back(); });
/* Recursively process the inputs of this
flake. Also, unless we already have this flake
in the top-level lock file, use this flake's
own lock file. */
computeLocks(
inputFlake.inputs, childNode, inputPath,
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root);
}
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, lockFlags.useRegistries, flakeCache);
node->inputs.insert_or_assign(id,
std::make_shared<LockedNode>(lockedRef, *input.ref, false));
}
}
computeLocks(fakeInputs, childNode, inputPath, oldLock);
}
} else {
/* We need to create a new lock file entry. So fetch
this input. */
debug("creating new input '%s'", inputPathS);
if (!lockFlags.allowMutable && !input.ref->input.isImmutable())
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
if (input.isFlake) {
auto inputFlake = getFlake(state, *input.ref, lockFlags.useRegistries, flakeCache);
/* 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);
node->inputs.insert_or_assign(id, childNode);
/* Guard against circular flake imports. */
for (auto & parent : parents)
if (parent == *input.ref)
throw Error("found circular import of flake '%s'", parent);
parents.push_back(*input.ref);
Finally cleanup([&]() { parents.pop_back(); });
/* Recursively process the inputs of this
flake. Also, unless we already have this flake
in the top-level lock file, use this flake's
own lock file. */
computeLocks(
inputFlake.inputs, childNode, inputPath,
oldLock
? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read(
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root);
}
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, lockFlags.useRegistries, flakeCache);
node->inputs.insert_or_assign(id,
std::make_shared<LockedNode>(lockedRef, *input.ref, false));
} catch (Error & e) {
e.addTrace({}, "while updating the flake input '%s'", inputPathS);
throw;
}
}
}
};
};
computeLocks(
flake.inputs, newLockFile.root, {},
lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
computeLocks(
flake.inputs, newLockFile.root, {},
lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
warn("the flag '--override-input %s %s' does not match any input",
printInputPath(i.first), i.second);
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
warn("the flag '--override-input %s %s' does not match any input",
printInputPath(i.first), i.second);
for (auto & i : lockFlags.inputUpdates)
if (!updatesUsed.count(i))
warn("the flag '--update-input %s' does not match any input", printInputPath(i));
for (auto & i : lockFlags.inputUpdates)
if (!updatesUsed.count(i))
warn("the flag '--update-input %s' does not match any input", printInputPath(i));
/* Check 'follows' inputs. */
newLockFile.check();
/* Check 'follows' inputs. */
newLockFile.check();
debug("new lock file: %s", newLockFile);
debug("new lock file: %s", newLockFile);
/* Check whether we need to / can write the new lock file. */
if (!(newLockFile == oldLockFile)) {
/* Check whether we need to / can write the new lock file. */
if (!(newLockFile == oldLockFile)) {
auto diff = LockFile::diff(oldLockFile, newLockFile);
auto diff = LockFile::diff(oldLockFile, newLockFile);
if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getSourcePath()) {
if (!newLockFile.isImmutable()) {
if (settings.warnDirty)
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
} else {
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getSourcePath()) {
if (!newLockFile.isImmutable()) {
if (settings.warnDirty)
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
} else {
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
auto path = *sourcePath + "/" + relPath;
auto path = *sourcePath + "/" + relPath;
bool lockFileExists = pathExists(path);
bool lockFileExists = pathExists(path);
if (lockFileExists) {
auto s = chomp(diff);
if (s.empty())
warn("updating lock file '%s'", path);
else
warn("updating lock file '%s':\n%s", path, s);
} else
warn("creating lock file '%s'", path);
if (lockFileExists) {
auto s = chomp(diff);
if (s.empty())
warn("updating lock file '%s'", path);
else
warn("updating lock file '%s':\n%s", path, s);
} else
warn("creating lock file '%s'", path);
newLockFile.write(path);
newLockFile.write(path);
topRef.input.markChangedFile(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
lockFlags.commitLockFile
? std::optional<std::string>(fmt("%s: %s\n\nFlake input changes:\n\n%s",
relPath, lockFileExists ? "Update" : "Add", diff))
: std::nullopt);
topRef.input.markChangedFile(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
lockFlags.commitLockFile
? std::optional<std::string>(fmt("%s: %s\n\nFlake input changes:\n\n%s",
relPath, lockFileExists ? "Update" : "Add", diff))
: std::nullopt);
/* Rewriting the lockfile changed the top-level
repo, so we should re-read it. FIXME: we could
also just clear the 'rev' field... */
auto prevLockedRef = flake.lockedRef;
FlakeCache dummyCache;
flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache);
/* Rewriting the lockfile changed the top-level
repo, so we should re-read it. FIXME: we could
also just clear the 'rev' field... */
auto prevLockedRef = flake.lockedRef;
FlakeCache dummyCache;
flake = getFlake(state, topRef, lockFlags.useRegistries, dummyCache);
if (lockFlags.commitLockFile &&
flake.lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
if (lockFlags.commitLockFile &&
flake.lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
/* Make sure that we picked up the change,
i.e. the tree should usually be dirty
now. Corner case: we could have reverted from a
dirty to a clean tree! */
if (flake.lockedRef.input == prevLockedRef.input
&& !flake.lockedRef.input.isImmutable())
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
}
/* Make sure that we picked up the change,
i.e. the tree should usually be dirty
now. Corner case: we could have reverted from a
dirty to a clean tree! */
if (flake.lockedRef.input == prevLockedRef.input
&& !flake.lockedRef.input.isImmutable())
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
}
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
} else
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
}
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
}
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
} catch (Error & e) {
e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string());
throw;
}
}
void callFlake(EvalState & state,

View file

@ -17,23 +17,55 @@ struct FlakeInput;
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
/* FlakeInput is the 'Flake'-level parsed form of the "input" entries
* in the flake file.
*
* A FlakeInput is normally constructed by the 'parseFlakeInput'
* function which parses the input specification in the '.flake' file
* to create a 'FlakeRef' (a fetcher, the fetcher-specific
* representation of the input specification, and possibly the fetched
* local store path result) and then creating this FlakeInput to hold
* that FlakeRef, along with anything that might override that
* FlakeRef (like command-line overrides or "follows" specifications).
*
* A FlakeInput is also sometimes constructed directly from a FlakeRef
* instead of starting at the flake-file input specification
* (e.g. overrides, follows, and implicit inputs).
*
* A FlakeInput will usually have one of either "ref" or "follows"
* set. If not otherwise specified, a "ref" will be generated to a
* 'type="indirect"' flake, which is treated as simply the name of a
* flake to be resolved in the registry.
*/
struct FlakeInput
{
std::optional<FlakeRef> ref;
bool isFlake = true;
bool isFlake = true; // true = process flake to get outputs, false = (fetched) static source path
std::optional<InputPath> follows;
bool absolute = false; // whether 'follows' is relative to the flake root
FlakeInputs overrides;
};
struct ConfigFile
{
using ConfigValue = std::variant<std::string, int64_t, Explicit<bool>, std::vector<std::string>>;
std::map<std::string, ConfigValue> settings;
void apply();
};
/* The contents of a flake.nix file. */
struct Flake
{
FlakeRef originalRef;
FlakeRef resolvedRef;
FlakeRef lockedRef;
FlakeRef originalRef; // the original flake specification (by the user)
FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake
FlakeRef lockedRef; // the specific local store result of invoking the fetcher
std::optional<std::string> description;
std::shared_ptr<const fetchers::Tree> sourceInfo;
FlakeInputs inputs;
ConfigFile config; // 'nixConfig' attribute
~Flake();
};
@ -81,7 +113,7 @@ struct LockFlags
/* Whether to commit changes to flake.lock. */
bool commitLockFile = false;
/* Flake inputs to be overriden. */
/* Flake inputs to be overridden. */
std::map<InputPath, FlakeRef> inputOverrides;
/* Flake inputs to be updated. This means that any existing lock

View file

@ -12,10 +12,33 @@ class Store;
typedef std::string FlakeId;
/* A flake reference specifies how to fetch a flake or raw source
* (e.g. from a Git repository). It is created from a URL-like syntax
* (e.g. 'github:NixOS/patchelf'), an attrset representation (e.g. '{
* type="github"; owner = "NixOS"; repo = "patchelf"; }'), or a local
* path.
*
* Each flake will have a number of FlakeRef objects: one for each
* input to the flake.
*
* The normal method of constructing a FlakeRef is by starting with an
* input description (usually the attrs or a url from the flake file),
* locating a fetcher for that input, and then capturing the Input
* 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
* be lazy), but the fetcher can be invoked at any time via the
* FlakeRef to ensure the store is populated with this input.
*/
struct FlakeRef
{
/* fetcher-specific representation of the input, sufficient to
perform the fetch operation. */
fetchers::Input input;
/* sub-path within the fetched input that represents this input */
Path subdir;
bool operator==(const FlakeRef & other) const;

View file

@ -34,7 +34,8 @@ LockedNode::LockedNode(const nlohmann::json & json)
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isImmutable())
throw Error("lockfile contains mutable lock '%s'", attrsToJson(lockedRef.input.toAttrs()));
throw Error("lockfile contains mutable lock '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
StorePath LockedNode::computeStorePath(Store & store) const
@ -77,7 +78,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
{
if (jsonNode.find("inputs") == jsonNode.end()) return;
for (auto & i : jsonNode["inputs"].items()) {
if (i.value().is_array()) {
if (i.value().is_array()) { // FIXME: remove, obsolete
InputPath path;
for (auto & j : i.value())
path.push_back(j);
@ -86,10 +87,13 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
std::string inputKey = i.value();
auto k = nodeMap.find(inputKey);
if (k == nodeMap.end()) {
auto jsonNode2 = json["nodes"][inputKey];
auto input = std::make_shared<LockedNode>(jsonNode2);
auto nodes = json["nodes"];
auto jsonNode2 = nodes.find(inputKey);
if (jsonNode2 == nodes.end())
throw Error("lock file references missing node '%s'", inputKey);
auto input = std::make_shared<LockedNode>(*jsonNode2);
k = nodeMap.insert_or_assign(inputKey, input).first;
getInputs(*input, jsonNode2);
getInputs(*input, *jsonNode2);
}
if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second))
node.inputs.insert_or_assign(i.key(), child);
@ -110,7 +114,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
// a bit since we don't need to worry about cycles.
}
nlohmann::json LockFile::toJson() const
nlohmann::json LockFile::toJSON() const
{
nlohmann::json nodes;
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
@ -154,8 +158,8 @@ nlohmann::json LockFile::toJson() const
}
if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) {
n["original"] = fetchers::attrsToJson(lockedNode->originalRef.toAttrs());
n["locked"] = fetchers::attrsToJson(lockedNode->lockedRef.toAttrs());
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
if (!lockedNode->isFlake) n["flake"] = false;
}
@ -174,7 +178,7 @@ nlohmann::json LockFile::toJson() const
std::string LockFile::to_string() const
{
return toJson().dump(2);
return toJSON().dump(2);
}
LockFile LockFile::read(const Path & path)
@ -185,7 +189,7 @@ LockFile LockFile::read(const Path & path)
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
{
stream << lockFile.toJson().dump(2);
stream << lockFile.toJSON().dump(2);
return stream;
}
@ -223,7 +227,7 @@ bool LockFile::isImmutable() const
bool LockFile::operator ==(const LockFile & other) const
{
// FIXME: slow
return toJson() == other.toJson();
return toJSON() == other.toJSON();
}
InputPath parseInputPath(std::string_view s)

View file

@ -52,7 +52,7 @@ struct LockFile
LockFile() {};
LockFile(const nlohmann::json & json, const Path & path);
nlohmann::json toJson() const;
nlohmann::json toJSON() const;
std::string to_string() const;

View file

@ -2,6 +2,7 @@
#include "util.hh"
#include "eval-inline.hh"
#include "store-api.hh"
#include "path-with-outputs.hh"
#include <cstring>
#include <regex>
@ -19,7 +20,7 @@ DrvInfo::DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs)
DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs)
: state(&state), attrs(nullptr), attrPath("")
{
auto [drvPath, selectedOutputs] = store->parsePathWithOutputs(drvPathWithOutputs);
auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs);
this->drvPath = store->printStorePath(drvPath);
@ -128,7 +129,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
if (!outTI->isList()) throw errMsg;
Outputs result;
for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); ++i) {
if ((*i)->type != tString) throw errMsg;
if ((*i)->type() != nString) throw errMsg;
auto out = outputs.find((*i)->string.s);
if (out == outputs.end()) throw errMsg;
result.insert(*out);
@ -172,20 +173,20 @@ StringSet DrvInfo::queryMetaNames()
bool DrvInfo::checkMeta(Value & v)
{
state->forceValue(v);
if (v.isList()) {
if (v.type() == nList) {
for (unsigned int n = 0; n < v.listSize(); ++n)
if (!checkMeta(*v.listElems()[n])) return false;
return true;
}
else if (v.type == tAttrs) {
else if (v.type() == nAttrs) {
Bindings::iterator i = v.attrs->find(state->sOutPath);
if (i != v.attrs->end()) return false;
for (auto & i : *v.attrs)
if (!checkMeta(*i.value)) return false;
return true;
}
else return v.type == tInt || v.type == tBool || v.type == tString ||
v.type == tFloat;
else return v.type() == nInt || v.type() == nBool || v.type() == nString ||
v.type() == nFloat;
}
@ -201,7 +202,7 @@ Value * DrvInfo::queryMeta(const string & name)
string DrvInfo::queryMetaString(const string & name)
{
Value * v = queryMeta(name);
if (!v || v->type != tString) return "";
if (!v || v->type() != nString) return "";
return v->string.s;
}
@ -210,12 +211,12 @@ NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type == tInt) return v->integer;
if (v->type == tString) {
if (v->type() == nInt) return v->integer;
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
NixInt n;
if (string2Int(v->string.s, n)) return n;
if (auto n = string2Int<NixInt>(v->string.s))
return *n;
}
return def;
}
@ -224,12 +225,12 @@ NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type == tFloat) return v->fpoint;
if (v->type == tString) {
if (v->type() == nFloat) return v->fpoint;
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
float meta fields. */
NixFloat n;
if (string2Float(v->string.s, n)) return n;
if (auto n = string2Float<NixFloat>(v->string.s))
return *n;
}
return def;
}
@ -239,8 +240,8 @@ bool DrvInfo::queryMetaBool(const string & name, bool def)
{
Value * v = queryMeta(name);
if (!v) return def;
if (v->type == tBool) return v->boolean;
if (v->type == tString) {
if (v->type() == nBool) return v->boolean;
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
if (strcmp(v->string.s, "true") == 0) return true;
@ -331,7 +332,7 @@ static void getDerivations(EvalState & state, Value & vIn,
/* Process the expression. */
if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) ;
else if (v.type == tAttrs) {
else if (v.type() == nAttrs) {
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
@ -353,7 +354,7 @@ static void getDerivations(EvalState & state, Value & vIn,
/* If the value of this attribute is itself a set,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (i->value->type == tAttrs) {
if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
@ -362,7 +363,7 @@ static void getDerivations(EvalState & state, Value & vIn,
}
}
else if (v.isList()) {
else if (v.type() == nList) {
for (unsigned int n = 0; n < v.listSize(); ++n) {
string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str());
if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))

View file

@ -12,6 +12,10 @@
%{
#ifdef __clang__
#pragma clang diagnostic ignored "-Wunneeded-internal-declaration"
#endif
#include <boost/lexical_cast.hpp>
#include "nixexpr.hh"

View file

@ -15,7 +15,7 @@ libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/lib
libexpr_LIBS = libutil libstore libfetchers
libexpr_LDFLAGS =
libexpr_LDFLAGS = -lboost_context
ifneq ($(OS), FreeBSD)
libexpr_LDFLAGS += -ldl
endif
@ -35,13 +35,11 @@ $(d)/lexer-tab.cc $(d)/lexer-tab.hh: $(d)/lexer.l
clean-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
dist-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
$(eval $(call install-file-in, $(d)/nix-expr.pc, $(prefix)/lib/pkgconfig, 0644))
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh $(d)/primops/derivation.nix.gen.hh
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh
$(d)/flake/flake.cc: $(d)/flake/call-flake.nix.gen.hh

View file

@ -284,7 +284,7 @@ void ExprVar::bindVars(const StaticEnv & env)
"undefined variable" error now. */
if (withLevel == -1)
throw UndefinedVarError({
.hint = hintfmt("undefined variable '%1%'", name),
.msg = hintfmt("undefined variable '%1%'", name),
.errPos = pos
});
fromWith = true;

View file

@ -17,6 +17,7 @@ MakeError(ThrownError, AssertionError);
MakeError(Abort, EvalError);
MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
@ -129,7 +130,7 @@ struct ExprPath : Expr
{
string s;
Value v;
ExprPath(const string & s) : s(s) { mkPathNoCopy(v, this->s.c_str()); };
ExprPath(const string & s) : s(s) { v.mkPath(this->s.c_str()); };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env);
};
@ -238,7 +239,7 @@ struct ExprLambda : Expr
{
if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end())
throw ParseError({
.hint = hintfmt("duplicate formal function argument '%1%'", arg),
.msg = hintfmt("duplicate formal function argument '%1%'", arg),
.errPos = pos
});
};

View file

@ -32,7 +32,7 @@ namespace nix {
Path basePath;
Symbol file;
FileOrigin origin;
ErrorInfo error;
std::optional<ErrorInfo> error;
Symbol sLetBody;
ParseData(EvalState & state)
: state(state)
@ -66,8 +66,8 @@ namespace nix {
static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos)
{
throw ParseError({
.hint = hintfmt("attribute '%1%' already defined at %2%",
showAttrPath(attrPath), prevPos),
.msg = hintfmt("attribute '%1%' already defined at %2%",
showAttrPath(attrPath), prevPos),
.errPos = pos
});
}
@ -75,7 +75,7 @@ static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prev
static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
{
throw ParseError({
.hint = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos),
.msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos),
.errPos = pos
});
}
@ -146,7 +146,7 @@ static void addFormal(const Pos & pos, Formals * formals, const Formal & formal)
{
if (!formals->argNames.insert(formal.name).second)
throw ParseError({
.hint = hintfmt("duplicate formal function argument '%1%'",
.msg = hintfmt("duplicate formal function argument '%1%'",
formal.name),
.errPos = pos
});
@ -258,7 +258,7 @@ static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error)
{
data->error = {
.hint = hintfmt(error),
.msg = hintfmt(error),
.errPos = makeCurPos(*loc, data)
};
}
@ -338,7 +338,7 @@ expr_function
| LET binds IN expr_function
{ if (!$2->dynamicAttrs.empty())
throw ParseError({
.hint = hintfmt("dynamic attributes not allowed in let"),
.msg = hintfmt("dynamic attributes not allowed in let"),
.errPos = CUR_POS
});
$$ = new ExprLet($2, $4);
@ -418,7 +418,7 @@ expr_simple
static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals");
if (noURLLiterals)
throw ParseError({
.hint = hintfmt("URL literals are disabled"),
.msg = hintfmt("URL literals are disabled"),
.errPos = CUR_POS
});
$$ = new ExprString(data->symbols.create($1));
@ -491,7 +491,7 @@ attrs
delete str;
} else
throw ParseError({
.hint = hintfmt("dynamic attributes not allowed in inherit"),
.msg = hintfmt("dynamic attributes not allowed in inherit"),
.errPos = makeCurPos(@2, data)
});
}
@ -576,7 +576,7 @@ Expr * EvalState::parse(const char * text, FileOrigin origin,
ParseData data(*this);
data.origin = origin;
switch (origin) {
case foFile:
case foFile:
data.file = data.symbols.create(path);
break;
case foStdin:
@ -593,7 +593,7 @@ Expr * EvalState::parse(const char * text, FileOrigin origin,
int res = yyparse(scanner, &data);
yylex_destroy(scanner);
if (res) throw ParseError(data.error);
if (res) throw ParseError(data.error.value());
data.result->bindVars(staticEnv);
@ -698,8 +698,12 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos
Path res = r.second + suffix;
if (pathExists(res)) return canonPath(res);
}
if (hasPrefix(path, "nix/"))
return corepkgsPrefix + path.substr(4);
throw ThrownError({
.hint = hintfmt(evalSettings.pureEval
.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),
@ -721,8 +725,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
store, resolveUri(elem.second), "source", false).first.storePath) };
} catch (FileTransferError & e) {
logWarning({
.name = "Entry download",
.hint = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
});
res = { false, "" };
}
@ -732,8 +735,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
res = { true, path };
else {
logWarning({
.name = "Entry not found",
.hint = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second)
.msg = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second)
});
res = { false, "" };
}

View file

@ -35,7 +35,7 @@ InvalidPathError::InvalidPathError(const Path & path) :
void EvalState::realiseContext(const PathSet & context)
{
std::vector<StorePathWithOutputs> drvs;
std::vector<DerivedPath::Built> drvs;
for (auto & i : context) {
auto [ctxS, outputName] = decodeContext(i);
@ -43,7 +43,7 @@ void EvalState::realiseContext(const PathSet & context)
if (!store->isValidPath(ctx))
throw InvalidPathError(store->printStorePath(ctx));
if (!outputName.empty() && ctx.isDerivation()) {
drvs.push_back(StorePathWithOutputs{ctx, {outputName}});
drvs.push_back({ctx, {outputName}});
}
}
@ -51,14 +51,16 @@ void EvalState::realiseContext(const PathSet & context)
if (!evalSettings.enableImportFromDerivation)
throw EvalError("attempted to realize '%1%' during evaluation but 'allow-import-from-derivation' is false",
store->printStorePath(drvs.begin()->path));
store->printStorePath(drvs.begin()->drvPath));
/* For performance, prefetch all substitute info. */
StorePathSet willBuild, willSubstitute, unknown;
uint64_t downloadSize, narSize;
store->queryMissing(drvs, willBuild, willSubstitute, unknown, downloadSize, narSize);
std::vector<DerivedPath> buildReqs;
for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d });
store->queryMissing(buildReqs, willBuild, willSubstitute, unknown, downloadSize, narSize);
store->buildPaths(drvs);
store->buildPaths(buildReqs);
/* Add the output of this derivations to the allowed
paths. */
@ -115,9 +117,12 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
state.realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path),
.msg = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
} catch (Error & e) {
e.addTrace(pos, "while importing '%s'", path);
throw e;
}
Path realPath = state.checkSourcePath(state.toRealPath(path, context));
@ -164,7 +169,15 @@ static void import(EvalState & state, const Pos & pos, Value & vPath, Value * vS
state.forceFunction(**fun, pos);
mkApp(v, **fun, w);
state.forceAttrs(v, pos);
} else {
}
else if (path == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh"
, "/"), v);
}
else {
if (!vScope)
state.evalFile(realPath, v);
else {
@ -274,7 +287,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
state.realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt(
.msg = hintfmt(
"cannot import '%1%', since path '%2%' is not valid",
path, e.path),
.errPos = pos
@ -314,7 +327,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
auto count = args[0]->listSize();
if (count == 0) {
throw EvalError({
.hint = hintfmt("at least one argument to 'exec' required"),
.msg = hintfmt("at least one argument to 'exec' required"),
.errPos = pos
});
}
@ -328,7 +341,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
state.realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
program, e.path),
.errPos = pos
});
@ -356,24 +369,20 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
{
state.forceValue(*args[0], pos);
string t;
switch (args[0]->type) {
case tInt: t = "int"; break;
case tBool: t = "bool"; break;
case tString: t = "string"; break;
case tPath: t = "path"; break;
case tNull: t = "null"; break;
case tAttrs: t = "set"; break;
case tList1: case tList2: case tListN: t = "list"; break;
case tLambda:
case tPrimOp:
case tPrimOpApp:
t = "lambda";
break;
case tExternal:
switch (args[0]->type()) {
case nInt: t = "int"; break;
case nBool: t = "bool"; break;
case nString: t = "string"; break;
case nPath: t = "path"; break;
case nNull: t = "null"; break;
case nAttrs: t = "set"; break;
case nList: t = "list"; break;
case nFunction: t = "lambda"; break;
case nExternal:
t = args[0]->external->typeOf();
break;
case tFloat: t = "float"; break;
default: abort();
case nFloat: t = "float"; break;
case nThunk: abort();
}
mkString(v, state.symbols.create(t));
}
@ -393,7 +402,7 @@ static RegisterPrimOp primop_typeOf({
static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tNull);
mkBool(v, args[0]->type() == nNull);
}
static RegisterPrimOp primop_isNull({
@ -413,18 +422,7 @@ static RegisterPrimOp primop_isNull({
static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
bool res;
switch (args[0]->type) {
case tLambda:
case tPrimOp:
case tPrimOpApp:
res = true;
break;
default:
res = false;
break;
}
mkBool(v, res);
mkBool(v, args[0]->type() == nFunction);
}
static RegisterPrimOp primop_isFunction({
@ -440,7 +438,7 @@ static RegisterPrimOp primop_isFunction({
static void prim_isInt(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tInt);
mkBool(v, args[0]->type() == nInt);
}
static RegisterPrimOp primop_isInt({
@ -456,7 +454,7 @@ static RegisterPrimOp primop_isInt({
static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tFloat);
mkBool(v, args[0]->type() == nFloat);
}
static RegisterPrimOp primop_isFloat({
@ -472,7 +470,7 @@ static RegisterPrimOp primop_isFloat({
static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tString);
mkBool(v, args[0]->type() == nString);
}
static RegisterPrimOp primop_isString({
@ -488,7 +486,7 @@ static RegisterPrimOp primop_isString({
static void prim_isBool(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tBool);
mkBool(v, args[0]->type() == nBool);
}
static RegisterPrimOp primop_isBool({
@ -504,7 +502,7 @@ static RegisterPrimOp primop_isBool({
static void prim_isPath(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tPath);
mkBool(v, args[0]->type() == nPath);
}
static RegisterPrimOp primop_isPath({
@ -520,20 +518,20 @@ struct CompareValues
{
bool operator () (const Value * v1, const Value * v2) const
{
if (v1->type == tFloat && v2->type == tInt)
if (v1->type() == nFloat && v2->type() == nInt)
return v1->fpoint < v2->integer;
if (v1->type == tInt && v2->type == tFloat)
if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint;
if (v1->type != v2->type)
if (v1->type() != v2->type())
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
switch (v1->type) {
case tInt:
switch (v1->type()) {
case nInt:
return v1->integer < v2->integer;
case tFloat:
case nFloat:
return v1->fpoint < v2->fpoint;
case tString:
case nString:
return strcmp(v1->string.s, v2->string.s) < 0;
case tPath:
case nPath:
return strcmp(v1->path, v2->path) < 0;
default:
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
@ -558,7 +556,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
args[0]->attrs->find(state.symbols.create("startSet"));
if (startSet == args[0]->attrs->end())
throw EvalError({
.hint = hintfmt("attribute 'startSet' required"),
.msg = hintfmt("attribute 'startSet' required"),
.errPos = pos
});
state.forceList(*startSet->value, pos);
@ -572,7 +570,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
args[0]->attrs->find(state.symbols.create("operator"));
if (op == args[0]->attrs->end())
throw EvalError({
.hint = hintfmt("attribute 'operator' required"),
.msg = hintfmt("attribute 'operator' required"),
.errPos = pos
});
state.forceValue(*op->value, pos);
@ -594,7 +592,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
e->attrs->find(state.symbols.create("key"));
if (key == e->attrs->end())
throw EvalError({
.hint = hintfmt("attribute 'key' required"),
.msg = hintfmt("attribute 'key' required"),
.errPos = pos
});
state.forceValue(*key->value, pos);
@ -700,10 +698,14 @@ static RegisterPrimOp primop_tryEval({
Try to shallowly evaluate *e*. Return a set containing the
attributes `success` (`true` if *e* evaluated successfully,
`false` if an error was thrown) and `value`, equalling *e* if
successful and `false` otherwise. Note that this doesn't evaluate
*e* deeply, so ` let e = { x = throw ""; }; in (builtins.tryEval
e).success ` will be `true`. Using ` builtins.deepSeq ` one can
get the expected result: `let e = { x = throw ""; }; in
successful and `false` otherwise. `tryEval` will only prevent
errors created by `throw` or `assert` from being thrown.
Errors `tryEval` will not catch are for example those created
by `abort` and type errors generated by builtins. Also note that
this doesn't evaluate *e* deeply, so `let e = { x = throw ""; };
in (builtins.tryEval e).success` will be `true`. Using
`builtins.deepSeq` one can get the expected result:
`let e = { x = throw ""; }; in
(builtins.tryEval (builtins.deepSeq e e)).success` will be
`false`.
)",
@ -777,7 +779,7 @@ static RegisterPrimOp primop_deepSeq({
static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
if (args[0]->type == tString)
if (args[0]->type() == nString)
printError("trace: %1%", args[0]->string.s);
else
printError("trace: %1%", *args[0]);
@ -817,7 +819,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
Bindings::iterator attr = args[0]->attrs->find(state.sName);
if (attr == args[0]->attrs->end())
throw EvalError({
.hint = hintfmt("required attribute 'name' missing"),
.msg = hintfmt("required attribute 'name' missing"),
.errPos = pos
});
string drvName;
@ -866,7 +868,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else
throw EvalError({
.hint = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.errPos = posDrvName
});
};
@ -876,7 +878,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
for (auto & j : ss) {
if (outputs.find(j) != outputs.end())
throw EvalError({
.hint = hintfmt("duplicate derivation output '%1%'", j),
.msg = hintfmt("duplicate derivation output '%1%'", j),
.errPos = posDrvName
});
/* !!! Check whether j is a valid attribute
@ -886,14 +888,14 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
the resulting set. */
if (j == "drv")
throw EvalError({
.hint = hintfmt("invalid derivation output name 'drv'" ),
.msg = hintfmt("invalid derivation output name 'drv'" ),
.errPos = posDrvName
});
outputs.insert(j);
}
if (outputs.empty())
throw EvalError({
.hint = hintfmt("derivation cannot have an empty set of outputs"),
.msg = hintfmt("derivation cannot have an empty set of outputs"),
.errPos = posDrvName
});
};
@ -902,7 +904,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
if (ignoreNulls) {
state.forceValue(*i->value, pos);
if (i->value->type == tNull) continue;
if (i->value->type() == nNull) continue;
}
if (i->name == state.sContentAddressed) {
@ -1014,20 +1016,20 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* Do we have all required attributes? */
if (drv.builder == "")
throw EvalError({
.hint = hintfmt("required attribute 'builder' missing"),
.msg = hintfmt("required attribute 'builder' missing"),
.errPos = posDrvName
});
if (drv.platform == "")
throw EvalError({
.hint = hintfmt("required attribute 'system' missing"),
.msg = hintfmt("required attribute 'system' missing"),
.errPos = posDrvName
});
/* Check whether the derivation name is valid. */
if (isDerivation(drvName))
throw EvalError({
.hint = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
.errPos = posDrvName
});
@ -1038,7 +1040,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
already content addressed. */
if (outputs.size() != 1 || *(outputs.begin()) != "out")
throw Error({
.hint = hintfmt("multiple outputs are not supported in fixed-output derivations"),
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
.errPos = posDrvName
});
@ -1089,18 +1091,35 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
// Regular, non-CA derivation should always return a single hash and not
// hash per output.
Hash h = std::get<0>(hashDerivationModulo(*state.store, Derivation(drv), true));
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
std::visit(overloaded {
[&](Hash h) {
for (auto & i : outputs) {
auto outPath = state.store->makeOutputPath(i, h, drvName);
drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign(i,
DerivationOutput {
.output = DerivationOutputInputAddressed {
.path = std::move(outPath),
},
});
}
},
[&](CaOutputHashes) {
// Shouldn't happen as the toplevel derivation is not CA.
assert(false);
},
[&](DeferredHash _) {
for (auto & i : outputs) {
drv.outputs.insert_or_assign(i,
DerivationOutput {
.output = DerivationOutputDeferred{},
});
}
},
},
hashModulo);
for (auto & i : outputs) {
auto outPath = state.store->makeOutputPath(i, h, drvName);
drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign(i,
DerivationOutput {
.output = DerivationOutputInputAddressed {
.path = std::move(outPath),
},
});
}
}
/* Write the resulting term into the Nix store directory. */
@ -1115,9 +1134,10 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
However, we don't bother doing this for floating CA derivations because
their "hash modulo" is indeterminate until built. */
if (drv.type() != DerivationType::CAFloating)
drvHashes.insert_or_assign(drvPath,
hashDerivationModulo(*state.store, Derivation(drv), false));
if (drv.type() != DerivationType::CAFloating) {
auto h = hashDerivationModulo(*state.store, Derivation(drv), false);
drvHashes.lock()->insert_or_assign(drvPath, h);
}
state.mkAttrs(v, 1 + drv.outputs.size());
mkString(*state.allocAttr(v, state.sDrvPath), drvPathS, {"=" + drvPathS});
@ -1200,7 +1220,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V
if (!state.store->isStorePath(path)) path = canonPath(path, true);
if (!state.store->isInStore(path))
throw EvalError({
.hint = hintfmt("path '%1%' is not in the Nix store", path),
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = pos
});
auto path2 = state.store->toStorePath(path).first;
@ -1236,7 +1256,7 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
state.realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt(
.msg = hintfmt(
"cannot check the existence of '%1%', since path '%2%' is not valid",
path, e.path),
.errPos = pos
@ -1290,7 +1310,7 @@ static void prim_dirOf(EvalState & state, const Pos & pos, Value * * args, Value
{
PathSet context;
Path dir = dirOf(state.coerceToString(pos, *args[0], context, false, false));
if (args[0]->type == tPath) mkPath(v, dir.c_str()); else mkString(v, dir, context);
if (args[0]->type() == nPath) mkPath(v, dir.c_str()); else mkString(v, dir, context);
}
static RegisterPrimOp primop_dirOf({
@ -1313,7 +1333,7 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
state.realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
.msg = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
}
@ -1352,7 +1372,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
i = v2.attrs->find(state.symbols.create("path"));
if (i == v2.attrs->end())
throw EvalError({
.hint = hintfmt("attribute 'path' missing"),
.msg = hintfmt("attribute 'path' missing"),
.errPos = pos
});
@ -1363,7 +1383,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
state.realiseContext(context);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
}
@ -1389,7 +1409,7 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
std::optional<HashType> ht = parseHashType(type);
if (!ht)
throw Error({
.hint = hintfmt("unknown hash type '%1%'", type),
.msg = hintfmt("unknown hash type '%1%'", type),
.errPos = pos
});
@ -1419,7 +1439,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
state.realiseContext(ctx);
} catch (InvalidPathError & e) {
throw EvalError({
.hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
.msg = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
.errPos = pos
});
}
@ -1431,7 +1451,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name));
if (ent.type == DT_UNKNOWN)
ent.type = getFileType(path + "/" + ent.name);
mkStringNoCopy(*ent_val,
ent_val->mkString(
ent.type == DT_REG ? "regular" :
ent.type == DT_DIR ? "directory" :
ent.type == DT_LNK ? "symlink" :
@ -1603,7 +1623,12 @@ static RegisterPrimOp primop_toJSON({
static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
string s = state.forceStringNoCtx(*args[0], pos);
parseJSON(state, s, v);
try {
parseJSON(state, s, v);
} catch (JSONParseError &e) {
e.addTrace(pos, "while decoding a JSON string");
throw e;
}
}
static RegisterPrimOp primop_fromJSON({
@ -1634,7 +1659,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
for (auto path : context) {
if (path.at(0) != '/')
throw EvalError( {
.hint = hintfmt(
.msg = hintfmt(
"in 'toFile': the file named '%1%' must not contain a reference "
"to a derivation but contains (%2%)",
name, path),
@ -1785,14 +1810,14 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
Path path = state.coerceToPath(pos, *args[1], context);
if (!context.empty())
throw EvalError({
.hint = hintfmt("string '%1%' cannot refer to other paths", path),
.msg = hintfmt("string '%1%' cannot refer to other paths", path),
.errPos = pos
});
state.forceValue(*args[0], pos);
if (args[0]->type != tLambda)
if (args[0]->type() != nFunction)
throw TypeError({
.hint = hintfmt(
.msg = hintfmt(
"first argument in call to 'filterSource' is not a function but %1%",
showType(*args[0])),
.errPos = pos
@ -1859,7 +1884,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
path = state.coerceToPath(*attr.pos, *attr.value, context);
if (!context.empty())
throw EvalError({
.hint = hintfmt("string '%1%' cannot refer to other paths", path),
.msg = hintfmt("string '%1%' cannot refer to other paths", path),
.errPos = *attr.pos
});
} else if (attr.name == state.sName)
@ -1873,13 +1898,13 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
else
throw EvalError({
.hint = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name),
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name),
.errPos = *attr.pos
});
}
if (path.empty())
throw EvalError({
.hint = hintfmt("'path' required"),
.msg = hintfmt("'path' required"),
.errPos = pos
});
if (name.empty())
@ -1994,7 +2019,7 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
if (i == args[1]->attrs->end())
throw EvalError({
.hint = hintfmt("attribute '%1%' missing", attr),
.msg = hintfmt("attribute '%1%' missing", attr),
.errPos = pos
});
// !!! add to stack trace?
@ -2056,7 +2081,7 @@ static RegisterPrimOp primop_hasAttr({
static void prim_isAttrs(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->type == tAttrs);
mkBool(v, args[0]->type() == nAttrs);
}
static RegisterPrimOp primop_isAttrs({
@ -2126,7 +2151,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
Bindings::iterator j = v2.attrs->find(state.sName);
if (j == v2.attrs->end())
throw TypeError({
.hint = hintfmt("'name' attribute missing in a call to 'listToAttrs'"),
.msg = hintfmt("'name' attribute missing in a call to 'listToAttrs'"),
.errPos = pos
});
string name = state.forceStringNoCtx(*j->value, pos);
@ -2136,7 +2161,7 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue));
if (j2 == v2.attrs->end())
throw TypeError({
.hint = hintfmt("'value' attribute missing in a call to 'listToAttrs'"),
.msg = hintfmt("'value' attribute missing in a call to 'listToAttrs'"),
.errPos = pos
});
v.attrs->push_back(Attr(sym, j2->value, j2->pos));
@ -2236,13 +2261,13 @@ static RegisterPrimOp primop_catAttrs({
static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
if (args[0]->type == tPrimOpApp || args[0]->type == tPrimOp) {
if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) {
state.mkAttrs(v, 0);
return;
}
if (args[0]->type != tLambda)
if (!args[0]->isLambda())
throw TypeError({
.hint = hintfmt("'functionArgs' requires a function"),
.msg = hintfmt("'functionArgs' requires a function"),
.errPos = pos
});
@ -2319,7 +2344,7 @@ static RegisterPrimOp primop_mapAttrs({
static void prim_isList(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceValue(*args[0], pos);
mkBool(v, args[0]->isList());
mkBool(v, args[0]->type() == nList);
}
static RegisterPrimOp primop_isList({
@ -2336,7 +2361,7 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu
state.forceList(list, pos);
if (n < 0 || (unsigned int) n >= list.listSize())
throw Error({
.hint = hintfmt("list index %1% is out of bounds", n),
.msg = hintfmt("list index %1% is out of bounds", n),
.errPos = pos
});
state.forceValue(*list.listElems()[n], pos);
@ -2384,7 +2409,7 @@ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value
state.forceList(*args[0], pos);
if (args[0]->listSize() == 0)
throw Error({
.hint = hintfmt("'tail' called on an empty list"),
.msg = hintfmt("'tail' called on an empty list"),
.errPos = pos
});
@ -2623,7 +2648,7 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val
if (len < 0)
throw EvalError({
.hint = hintfmt("cannot create list of size %1%", len),
.msg = hintfmt("cannot create list of size %1%", len),
.errPos = pos
});
@ -2671,7 +2696,7 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value
auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass
callFunction. */
if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan)
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
return CompareValues()(a, b);
Value vTmp1, vTmp2;
@ -2813,7 +2838,7 @@ static void prim_add(EvalState & state, const Pos & pos, Value * * args, Value &
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
if (args[0]->type == tFloat || args[1]->type == tFloat)
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
mkFloat(v, state.forceFloat(*args[0], pos) + state.forceFloat(*args[1], pos));
else
mkInt(v, state.forceInt(*args[0], pos) + state.forceInt(*args[1], pos));
@ -2832,7 +2857,7 @@ static void prim_sub(EvalState & state, const Pos & pos, Value * * args, Value &
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
if (args[0]->type == tFloat || args[1]->type == tFloat)
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
mkFloat(v, state.forceFloat(*args[0], pos) - state.forceFloat(*args[1], pos));
else
mkInt(v, state.forceInt(*args[0], pos) - state.forceInt(*args[1], pos));
@ -2851,7 +2876,7 @@ static void prim_mul(EvalState & state, const Pos & pos, Value * * args, Value &
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);
if (args[0]->type == tFloat || args[1]->type == tFloat)
if (args[0]->type() == nFloat || args[1]->type() == nFloat)
mkFloat(v, state.forceFloat(*args[0], pos) * state.forceFloat(*args[1], pos));
else
mkInt(v, state.forceInt(*args[0], pos) * state.forceInt(*args[1], pos));
@ -2874,11 +2899,11 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value &
NixFloat f2 = state.forceFloat(*args[1], pos);
if (f2 == 0)
throw EvalError({
.hint = hintfmt("division by zero"),
.msg = hintfmt("division by zero"),
.errPos = pos
});
if (args[0]->type == tFloat || args[1]->type == tFloat) {
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
} else {
NixInt i1 = state.forceInt(*args[0], pos);
@ -2886,7 +2911,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value &
/* Avoid division overflow as it might raise SIGFPE. */
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
throw EvalError({
.hint = hintfmt("overflow in integer division"),
.msg = hintfmt("overflow in integer division"),
.errPos = pos
});
@ -3017,7 +3042,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V
if (start < 0)
throw EvalError({
.hint = hintfmt("negative start position in 'substring'"),
.msg = hintfmt("negative start position in 'substring'"),
.errPos = pos
});
@ -3068,7 +3093,7 @@ static void prim_hashString(EvalState & state, const Pos & pos, Value * * args,
std::optional<HashType> ht = parseHashType(type);
if (!ht)
throw Error({
.hint = hintfmt("unknown hash type '%1%'", type),
.msg = hintfmt("unknown hash type '%1%'", type),
.errPos = pos
});
@ -3132,12 +3157,12 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
throw EvalError({
.hint = hintfmt("memory limit exceeded by regular expression '%s'", re),
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = pos
});
} else {
throw EvalError({
.hint = hintfmt("invalid regular expression '%s'", re),
.msg = hintfmt("invalid regular expression '%s'", re),
.errPos = pos
});
}
@ -3240,12 +3265,12 @@ static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value
if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
throw EvalError({
.hint = hintfmt("memory limit exceeded by regular expression '%s'", re),
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = pos
});
} else {
throw EvalError({
.hint = hintfmt("invalid regular expression '%s'", re),
.msg = hintfmt("invalid regular expression '%s'", re),
.errPos = pos
});
}
@ -3325,7 +3350,7 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar
state.forceList(*args[1], pos);
if (args[0]->listSize() != args[1]->listSize())
throw EvalError({
.hint = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
.errPos = pos
});

View file

@ -147,7 +147,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
for (auto & i : *args[1]->attrs) {
if (!state.store->isStorePath(i.name))
throw EvalError({
.hint = hintfmt("Context key '%s' is not a store path", i.name),
.msg = hintfmt("Context key '%s' is not a store path", i.name),
.errPos = *i.pos
});
if (!settings.readOnlyMode)
@ -164,7 +164,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
if (state.forceBool(*iter->value, *iter->pos)) {
if (!isDerivation(i.name)) {
throw EvalError({
.hint = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
.errPos = *i.pos
});
}
@ -177,7 +177,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
state.forceList(*iter->value, *iter->pos);
if (iter->value->listSize() && !isDerivation(i.name)) {
throw EvalError({
.hint = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
.errPos = *i.pos
});
}

View file

@ -17,7 +17,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
state.forceValue(*args[0]);
if (args[0]->type == tAttrs) {
if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
@ -38,14 +38,14 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
name = state.forceStringNoCtx(*attr.value, *attr.pos);
else
throw EvalError({
.hint = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
.errPos = *attr.pos
});
}
if (url.empty())
throw EvalError({
.hint = hintfmt("'url' argument required"),
.msg = hintfmt("'url' argument required"),
.errPos = pos
});

View file

@ -39,11 +39,12 @@ void emitTreeAttrs(
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
auto emptyHash = Hash(htSHA1);
mkString(*state.allocAttr(v, state.symbols.create("rev")), emptyHash.gitRev());
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitRev());
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), emptyHash.gitShortRev());
}
if (input.getType() == "git")
mkBool(*state.allocAttr(v, state.symbols.create("submodules")), maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
mkBool(*state.allocAttr(v, state.symbols.create("submodules")),
fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
if (auto revCount = input.getRevCount())
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *revCount);
@ -84,26 +85,26 @@ static void fetchTree(
state.forceValue(*args[0]);
if (args[0]->type == tAttrs) {
if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
fetchers::Attrs attrs;
for (auto & attr : *args[0]->attrs) {
state.forceValue(*attr.value);
if (attr.value->type == tPath || attr.value->type == tString)
if (attr.value->type() == nPath || attr.value->type() == nString)
addURI(
state,
attrs,
attr.name,
state.coerceToString(*attr.pos, *attr.value, context, false, false)
);
else if (attr.value->type == tString)
else if (attr.value->type() == nString)
addURI(state, attrs, attr.name, attr.value->string.s);
else if (attr.value->type == tBool)
attrs.emplace(attr.name, fetchers::Explicit<bool>{attr.value->boolean});
else if (attr.value->type == tInt)
attrs.emplace(attr.name, attr.value->integer);
else if (attr.value->type() == nBool)
attrs.emplace(attr.name, Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt)
attrs.emplace(attr.name, uint64_t(attr.value->integer));
else
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
attr.name, showType(*attr.value));
@ -114,7 +115,7 @@ static void fetchTree(
if (!attrs.count("type"))
throw Error({
.hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
.errPos = pos
});
@ -152,6 +153,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
fetchTree(state, pos, args, v, std::nullopt);
}
// FIXME: document
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
@ -162,7 +164,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
state.forceValue(*args[0]);
if (args[0]->type == tAttrs) {
if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
@ -176,14 +178,14 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
name = state.forceStringNoCtx(*attr.value, *attr.pos);
else
throw EvalError({
.hint = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
.msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
.errPos = *attr.pos
});
}
if (!url)
throw EvalError({
.hint = hintfmt("'url' argument required"),
.msg = hintfmt("'url' argument required"),
.errPos = pos
});
} else
@ -211,7 +213,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
? state.store->queryPathInfo(storePath)->narHash
: hashFile(htSHA256, path);
if (hash != *expectedHash)
throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n wanted: %s\n got: %s",
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));
}
@ -323,6 +325,11 @@ static RegisterPrimOp primop_fetchGit({
A Boolean parameter that specifies whether submodules should be
checked out. 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
`rev`s from the specified `ref` are supported).
Here are some examples of how to use `fetchGit`.
- To fetch a private repository over SSH:

View file

@ -82,7 +82,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
visit(v, parser(tomlStream).parse());
} catch (std::runtime_error & e) {
throw EvalError({
.hint = hintfmt("while parsing a TOML string: %s", e.what()),
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
.errPos = pos
});
}

View file

@ -16,30 +16,30 @@ void printValueAsJSON(EvalState & state, bool strict,
if (strict) state.forceValue(v);
switch (v.type) {
switch (v.type()) {
case tInt:
case nInt:
out.write(v.integer);
break;
case tBool:
case nBool:
out.write(v.boolean);
break;
case tString:
case nString:
copyContext(v, context);
out.write(v.string.s);
break;
case tPath:
case nPath:
out.write(state.copyPathToStore(context, v.path));
break;
case tNull:
case nNull:
out.write(nullptr);
break;
case tAttrs: {
case nAttrs: {
auto maybeString = state.tryAttrsToString(noPos, v, context, false, false);
if (maybeString) {
out.write(*maybeString);
@ -61,7 +61,7 @@ void printValueAsJSON(EvalState & state, bool strict,
break;
}
case tList1: case tList2: case tListN: {
case nList: {
auto list(out.list());
for (unsigned int n = 0; n < v.listSize(); ++n) {
auto placeholder(list.placeholder());
@ -70,15 +70,18 @@ void printValueAsJSON(EvalState & state, bool strict,
break;
}
case tExternal:
case nExternal:
v.external->printValueAsJSON(state, strict, out, context);
break;
case tFloat:
case nFloat:
out.write(v.fpoint);
break;
default:
case nThunk:
throw TypeError("cannot convert %1% to JSON", showType(v));
case nFunction:
throw TypeError("cannot convert %1% to JSON", showType(v));
}
}

View file

@ -58,31 +58,31 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
if (strict) state.forceValue(v);
switch (v.type) {
switch (v.type()) {
case tInt:
case nInt:
doc.writeEmptyElement("int", singletonAttrs("value", (format("%1%") % v.integer).str()));
break;
case tBool:
case nBool:
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false"));
break;
case tString:
case nString:
/* !!! show the context? */
copyContext(v, context);
doc.writeEmptyElement("string", singletonAttrs("value", v.string.s));
break;
case tPath:
case nPath:
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
break;
case tNull:
case nNull:
doc.writeEmptyElement("null");
break;
case tAttrs:
case nAttrs:
if (state.isDerivation(v)) {
XMLAttrs xmlAttrs;
@ -92,14 +92,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
a = v.attrs->find(state.sDrvPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value);
if (a->value->type == tString)
if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
}
a = v.attrs->find(state.sOutPath);
if (a != v.attrs->end()) {
if (strict) state.forceValue(*a->value);
if (a->value->type == tString)
if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->string.s;
}
@ -118,14 +118,19 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
case tList1: case tList2: case tListN: {
case nList: {
XMLOpenElement _(doc, "list");
for (unsigned int n = 0; n < v.listSize(); ++n)
printValueAsXML(state, strict, location, *v.listElems()[n], doc, context, drvsSeen);
break;
}
case tLambda: {
case nFunction: {
if (!v.isLambda()) {
// FIXME: Serialize primops and primopapps
doc.writeEmptyElement("unevaluated");
break;
}
XMLAttrs xmlAttrs;
if (location) posToXML(xmlAttrs, v.lambda.fun->pos);
XMLOpenElement _(doc, "function", xmlAttrs);
@ -143,15 +148,15 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
}
case tExternal:
case nExternal:
v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen);
break;
case tFloat:
case nFloat:
doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
break;
default:
case nThunk:
doc.writeEmptyElement("unevaluated");
}
}

View file

@ -27,8 +27,24 @@ typedef enum {
tPrimOpApp,
tExternal,
tFloat
} ValueType;
} InternalType;
// This type abstracts over all actual value types in the language,
// grouping together implementation details like tList*, different function
// types, and types in non-normal form (so thunks and co.)
typedef enum {
nThunk,
nInt,
nFloat,
nBool,
nString,
nPath,
nNull,
nAttrs,
nList,
nFunction,
nExternal
} ValueType;
class Bindings;
struct Env;
@ -90,7 +106,28 @@ std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
struct Value
{
ValueType type;
private:
InternalType internalType;
friend std::string showType(const Value & v);
friend void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v);
public:
// Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's
// needed by callers into methods of this type
// type() == nThunk
inline bool isThunk() const { return internalType == tThunk; };
inline bool isApp() const { return internalType == tApp; };
inline bool isBlackhole() const { return internalType == tBlackhole; };
// type() == nFunction
inline bool isLambda() const { return internalType == tLambda; };
inline bool isPrimOp() const { return internalType == tPrimOp; };
inline bool isPrimOpApp() const { return internalType == tPrimOpApp; };
union
{
NixInt integer;
@ -147,24 +184,161 @@ struct Value
NixFloat fpoint;
};
// Returns the normal type of a Value. This only returns nThunk if the
// Value hasn't been forceValue'd
inline ValueType type() const
{
switch (internalType) {
case tInt: return nInt;
case tBool: return nBool;
case tString: return nString;
case tPath: return nPath;
case tNull: return nNull;
case tAttrs: return nAttrs;
case tList1: case tList2: case tListN: return nList;
case tLambda: case tPrimOp: case tPrimOpApp: return nFunction;
case tExternal: return nExternal;
case tFloat: return nFloat;
case tThunk: case tApp: case tBlackhole: return nThunk;
}
abort();
}
/* After overwriting an app node, be sure to clear pointers in the
Value to ensure that the target isn't kept alive unnecessarily. */
inline void clearValue()
{
app.left = app.right = 0;
}
inline void mkInt(NixInt n)
{
clearValue();
internalType = tInt;
integer = n;
}
inline void mkBool(bool b)
{
clearValue();
internalType = tBool;
boolean = b;
}
inline void mkString(const char * s, const char * * context = 0)
{
internalType = tString;
string.s = s;
string.context = context;
}
inline void mkPath(const char * s)
{
clearValue();
internalType = tPath;
path = s;
}
inline void mkNull()
{
clearValue();
internalType = tNull;
}
inline void mkAttrs(Bindings * a)
{
clearValue();
internalType = tAttrs;
attrs = a;
}
inline void mkList(size_t size)
{
clearValue();
if (size == 1)
internalType = tList1;
else if (size == 2)
internalType = tList2;
else {
internalType = tListN;
bigList.size = size;
}
}
inline void mkThunk(Env * e, Expr * ex)
{
internalType = tThunk;
thunk.env = e;
thunk.expr = ex;
}
inline void mkApp(Value * l, Value * r)
{
internalType = tApp;
app.left = l;
app.right = r;
}
inline void mkLambda(Env * e, ExprLambda * f)
{
internalType = tLambda;
lambda.env = e;
lambda.fun = f;
}
inline void mkBlackhole()
{
internalType = tBlackhole;
// Value will be overridden anyways
}
inline void mkPrimOp(PrimOp * p)
{
clearValue();
internalType = tPrimOp;
primOp = p;
}
inline void mkPrimOpApp(Value * l, Value * r)
{
internalType = tPrimOpApp;
app.left = l;
app.right = r;
}
inline void mkExternal(ExternalValueBase * e)
{
clearValue();
internalType = tExternal;
external = e;
}
inline void mkFloat(NixFloat n)
{
clearValue();
internalType = tFloat;
fpoint = n;
}
bool isList() const
{
return type == tList1 || type == tList2 || type == tListN;
return internalType == tList1 || internalType == tList2 || internalType == tListN;
}
Value * * listElems()
{
return type == tList1 || type == tList2 ? smallList : bigList.elems;
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
}
const Value * const * listElems() const
{
return type == tList1 || type == tList2 ? smallList : bigList.elems;
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
}
size_t listSize() const
{
return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size;
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
}
/* Check whether forcing this value requires a trivial amount of
@ -176,86 +350,42 @@ struct Value
};
/* After overwriting an app node, be sure to clear pointers in the
Value to ensure that the target isn't kept alive unnecessarily. */
static inline void clearValue(Value & v)
{
v.app.left = v.app.right = 0;
}
// TODO: Remove these static functions, replace call sites with v.mk* instead
static inline void mkInt(Value & v, NixInt n)
{
clearValue(v);
v.type = tInt;
v.integer = n;
v.mkInt(n);
}
static inline void mkFloat(Value & v, NixFloat n)
{
clearValue(v);
v.type = tFloat;
v.fpoint = n;
v.mkFloat(n);
}
static inline void mkBool(Value & v, bool b)
{
clearValue(v);
v.type = tBool;
v.boolean = b;
v.mkBool(b);
}
static inline void mkNull(Value & v)
{
clearValue(v);
v.type = tNull;
v.mkNull();
}
static inline void mkApp(Value & v, Value & left, Value & right)
{
v.type = tApp;
v.app.left = &left;
v.app.right = &right;
v.mkApp(&left, &right);
}
static inline void mkPrimOpApp(Value & v, Value & left, Value & right)
{
v.type = tPrimOpApp;
v.app.left = &left;
v.app.right = &right;
}
static inline void mkStringNoCopy(Value & v, const char * s)
{
v.type = tString;
v.string.s = s;
v.string.context = 0;
}
static inline void mkString(Value & v, const Symbol & s)
{
mkStringNoCopy(v, ((const string &) s).c_str());
v.mkString(((const string &) s).c_str());
}
void mkString(Value & v, const char * s);
static inline void mkPathNoCopy(Value & v, const char * s)
{
clearValue(v);
v.type = tPath;
v.path = s;
}
void mkPath(Value & v, const char * s);

View file

@ -11,11 +11,11 @@ Attrs jsonToAttrs(const nlohmann::json & json)
for (auto & i : json.items()) {
if (i.value().is_number())
attrs.emplace(i.key(), i.value().get<int64_t>());
attrs.emplace(i.key(), i.value().get<uint64_t>());
else if (i.value().is_string())
attrs.emplace(i.key(), i.value().get<std::string>());
else if (i.value().is_boolean())
attrs.emplace(i.key(), i.value().get<bool>());
attrs.emplace(i.key(), Explicit<bool> { i.value().get<bool>() });
else
throw Error("unsupported input attribute type in lock file");
}
@ -23,7 +23,7 @@ Attrs jsonToAttrs(const nlohmann::json & json)
return attrs;
}
nlohmann::json attrsToJson(const Attrs & attrs)
nlohmann::json attrsToJSON(const Attrs & attrs)
{
nlohmann::json json;
for (auto & attr : attrs) {
@ -44,7 +44,7 @@ std::optional<std::string> maybeGetStrAttr(const Attrs & attrs, const std::strin
if (i == attrs.end()) return {};
if (auto v = std::get_if<std::string>(&i->second))
return *v;
throw Error("input attribute '%s' is not a string %s", name, attrsToJson(attrs).dump());
throw Error("input attribute '%s' is not a string %s", name, attrsToJSON(attrs).dump());
}
std::string getStrAttr(const Attrs & attrs, const std::string & name)

View file

@ -8,24 +8,12 @@
namespace nix::fetchers {
/* Wrap bools to prevent string literals (i.e. 'char *') from being
cast to a bool in Attr. */
template<typename T>
struct Explicit {
T t;
bool operator ==(const Explicit<T> & other) const
{
return t == other.t;
}
};
typedef std::variant<std::string, uint64_t, Explicit<bool>> Attr;
typedef std::map<std::string, Attr> Attrs;
Attrs jsonToAttrs(const nlohmann::json & json);
nlohmann::json attrsToJson(const Attrs & attrs);
nlohmann::json attrsToJSON(const Attrs & attrs);
std::optional<std::string> maybeGetStrAttr(const Attrs & attrs, const std::string & name);

View file

@ -55,8 +55,8 @@ struct CacheImpl : Cache
bool immutable) override
{
_state.lock()->add.use()
(attrsToJson(inAttrs).dump())
(attrsToJson(infoAttrs).dump())
(attrsToJSON(inAttrs).dump())
(attrsToJSON(infoAttrs).dump())
(store->printStorePath(storePath))
(immutable)
(time(0)).exec();
@ -70,7 +70,7 @@ struct CacheImpl : Cache
if (!res->expired)
return std::make_pair(std::move(res->infoAttrs), std::move(res->storePath));
debug("ignoring expired cache entry '%s'",
attrsToJson(inAttrs).dump());
attrsToJSON(inAttrs).dump());
}
return {};
}
@ -81,15 +81,15 @@ struct CacheImpl : Cache
{
auto state(_state.lock());
auto inAttrsJson = attrsToJson(inAttrs).dump();
auto inAttrsJSON = attrsToJSON(inAttrs).dump();
auto stmt(state->lookup.use()(inAttrsJson));
auto stmt(state->lookup.use()(inAttrsJSON));
if (!stmt.next()) {
debug("did not find cache entry for '%s'", inAttrsJson);
debug("did not find cache entry for '%s'", inAttrsJSON);
return {};
}
auto infoJson = stmt.getStr(0);
auto infoJSON = stmt.getStr(0);
auto storePath = store->parseStorePath(stmt.getStr(1));
auto immutable = stmt.getInt(2) != 0;
auto timestamp = stmt.getInt(3);
@ -97,16 +97,16 @@ struct CacheImpl : Cache
store->addTempRoot(storePath);
if (!store->isValidPath(storePath)) {
// FIXME: we could try to substitute 'storePath'.
debug("ignoring disappeared cache entry '%s'", inAttrsJson);
debug("ignoring disappeared cache entry '%s'", inAttrsJSON);
return {};
}
debug("using cache entry '%s' -> '%s', '%s'",
inAttrsJson, infoJson, store->printStorePath(storePath));
inAttrsJSON, infoJSON, store->printStorePath(storePath));
return Result {
.expired = !immutable && (settings.tarballTtl.get() == 0 || timestamp + settings.tarballTtl < time(0)),
.infoAttrs = jsonToAttrs(nlohmann::json::parse(infoJson)),
.infoAttrs = jsonToAttrs(nlohmann::json::parse(infoJSON)),
.storePath = std::move(storePath)
};
}

View file

@ -65,7 +65,7 @@ Input Input::fromAttrs(Attrs && attrs)
ParsedURL Input::toURL() const
{
if (!scheme)
throw Error("cannot show unsupported input '%s'", attrsToJson(attrs));
throw Error("cannot show unsupported input '%s'", attrsToJSON(attrs));
return scheme->toURL(*this);
}
@ -110,7 +110,7 @@ bool Input::contains(const Input & other) const
std::pair<Tree, Input> Input::fetch(ref<Store> store) const
{
if (!scheme)
throw Error("cannot fetch unsupported input '%s'", attrsToJson(toAttrs()));
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
/* The tree may already be in the Nix store, or it could be
substituted (which is often faster than fetching from the
@ -132,7 +132,14 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
}
}
auto [tree, input] = scheme->fetch(store, *this);
auto [tree, input] = [&]() -> std::pair<Tree, Input> {
try {
return scheme->fetch(store, *this);
} catch (Error & e) {
e.addTrace({}, "while fetching the input '%s'", to_string());
throw;
}
}();
if (tree.actualPath == "")
tree.actualPath = store->toRealPath(tree.storePath);
@ -247,7 +254,7 @@ std::optional<time_t> Input::getLastModified() const
ParsedURL InputScheme::toURL(const Input & input)
{
throw Error("don't know how to convert input '%s' to a URL", attrsToJson(input.attrs));
throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs));
}
Input InputScheme::applyOverrides(

View file

@ -21,6 +21,14 @@ struct Tree
struct InputScheme;
/* The Input object is generated by a specific fetcher, based on the
* user-supplied input attribute in the flake.nix file, and contains
* the information that the specific fetcher needs to perform the
* actual fetch. The Input object is most commonly created via the
* "fromURL()" or "fromAttrs()" static functions which are provided
* the url or attrset specified in the flake file.
*/
struct Input
{
friend struct InputScheme;
@ -84,6 +92,16 @@ public:
std::optional<time_t> getLastModified() const;
};
/* The InputScheme represents a type of fetcher. Each fetcher
* registers with nix at startup time. When processing an input for a
* flake, each scheme is given an opportunity to "recognize" that
* input from the url or attributes in the flake file's specification
* and return an Input object to represent the input if it is
* recognized. The Input object contains the information the fetcher
* needs to actually perform the "fetch()" when called.
*/
struct InputScheme
{
virtual ~InputScheme()
@ -127,7 +145,13 @@ DownloadFileResult downloadFile(
bool immutable,
const Headers & headers = {});
std::pair<Tree, time_t> downloadTarball(
struct DownloadTarballMeta
{
time_t lastModified;
std::string effectiveUrl;
};
std::pair<Tree, DownloadTarballMeta> downloadTarball(
ref<Store> store,
const std::string & url,
const std::string & name,

View file

@ -59,12 +59,13 @@ struct GitInputScheme : InputScheme
if (maybeGetStrAttr(attrs, "type") != "git") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash")
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs")
throw Error("unsupported Git input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url"));
maybeGetBoolAttr(attrs, "shallow");
maybeGetBoolAttr(attrs, "submodules");
maybeGetBoolAttr(attrs, "allRefs");
if (auto ref = maybeGetStrAttr(attrs, "ref")) {
if (std::regex_search(*ref, badGitRefRegex))
@ -152,12 +153,14 @@ struct GitInputScheme : InputScheme
std::pair<bool, std::string> getActualUrl(const Input & input) const
{
// Don't clone file:// URIs (but otherwise treat them the
// same as remote URIs, i.e. don't use the working tree or
// HEAD).
// file:// URIs are normally not cloned (but otherwise treated the
// same as remote URIs, i.e. we don't use the working tree or
// HEAD). Exception: If _NIX_FORCE_HTTP is set, or the repo is a bare git
// repo, treat as a remote URI to force a clone.
static bool forceHttp = getEnv("_NIX_FORCE_HTTP") == "1"; // for testing
auto url = parseURL(getStrAttr(input.attrs, "url"));
bool isLocal = url.scheme == "file" && !forceHttp;
bool isBareRepository = url.scheme == "file" && !pathExists(url.path + "/.git");
bool isLocal = url.scheme == "file" && !forceHttp && !isBareRepository;
return {isLocal, isLocal ? url.path : url.base};
}
@ -169,10 +172,12 @@ struct GitInputScheme : InputScheme
bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false);
bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
bool allRefs = maybeGetBoolAttr(input.attrs, "allRefs").value_or(false);
std::string cacheType = "git";
if (shallow) cacheType += "-shallow";
if (submodules) cacheType += "-submodules";
if (allRefs) cacheType += "-all-refs";
auto getImmutableAttrs = [&]()
{
@ -273,7 +278,7 @@ struct GitInputScheme : InputScheme
haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {
Tree(store->printStorePath(storePath), std::move(storePath)),
Tree(store->toRealPath(storePath), std::move(storePath)),
input
};
}
@ -338,11 +343,15 @@ struct GitInputScheme : InputScheme
}
}
} else {
/* If the local ref is older than tarball-ttl seconds, do a
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;
if (allRefs) {
doFetch = true;
} else {
/* If the local ref is older than tarball-ttl seconds, do a
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;
}
}
if (doFetch) {
@ -352,9 +361,13 @@ struct GitInputScheme : InputScheme
// we're using --quiet for now. Should process its stderr.
try {
auto ref = input.getRef();
auto fetchRef = ref->compare(0, 5, "refs/") == 0
? *ref
: "refs/heads/" + *ref;
auto fetchRef = allRefs
? "refs/*"
: ref->compare(0, 5, "refs/") == 0
? *ref
: ref == "HEAD"
? *ref
: "refs/heads/" + *ref;
runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) });
} catch (Error & e) {
if (!pathExists(localRefFile)) throw;
@ -392,6 +405,28 @@ struct GitInputScheme : InputScheme
AutoDelete delTmpDir(tmpDir, true);
PathFilter filter = defaultPathFilter;
RunOptions checkCommitOpts(
"git",
{ "-C", repoDir, "cat-file", "commit", input.getRev()->gitRev() }
);
checkCommitOpts.searchPath = true;
checkCommitOpts.mergeStderrToStdout = true;
auto result = runProgram(checkCommitOpts);
if (WEXITSTATUS(result.first) == 128
&& result.second.find("bad file") != std::string::npos
) {
throw Error(
"Cannot find Git revision '%s' in ref '%s' of repository '%s'! "
"Please make sure that the " ANSI_BOLD "rev" ANSI_NORMAL " exists on the "
ANSI_BOLD "ref" ANSI_NORMAL " you've specified or add " ANSI_BOLD
"allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".",
input.getRev()->gitRev(),
*input.getRef(),
actualUrl
);
}
if (submodules) {
Path tmpGitDir = createTempDir();
AutoDelete delTmpGitDir(tmpGitDir, true);

View file

@ -37,15 +37,29 @@ struct GitArchiveInputScheme : InputScheme
std::optional<std::string> ref;
std::optional<std::string> host_url;
if (path.size() == 2) {
} else if (path.size() == 3) {
auto size = path.size();
if (size == 3) {
if (std::regex_match(path[2], revRegex))
rev = Hash::parseAny(path[2], htSHA1);
else if (std::regex_match(path[2], refRegex))
ref = path[2];
else
throw BadURL("in URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[2]);
} else
} else if (size > 3) {
std::string rs;
for (auto i = std::next(path.begin(), 2); i != path.end(); i++) {
rs += *i;
if (std::next(i) != path.end()) {
rs += "/";
}
}
if (std::regex_match(rs, refRegex)) {
ref = rs;
} else {
throw BadURL("in URL '%s', '%s' is not a branch/tag name", url.url, rs);
}
} else if (size < 2)
throw BadURL("URL '%s' is invalid", url.url);
for (auto &[name, value] : url.query) {
@ -193,16 +207,16 @@ struct GitArchiveInputScheme : InputScheme
auto url = getDownloadUrl(input);
auto [tree, lastModified] = downloadTarball(store, url.url, "source", true, url.headers);
auto [tree, meta] = downloadTarball(store, url.url, "source", true, url.headers);
input.attrs.insert_or_assign("lastModified", lastModified);
input.attrs.insert_or_assign("lastModified", uint64_t(meta.lastModified));
getCache()->add(
store,
immutableAttrs,
{
{"rev", rev->gitRev()},
{"lastModified", lastModified}
{"lastModified", uint64_t(meta.lastModified)}
},
tree.storePath,
true);

View file

@ -11,6 +11,36 @@ using namespace std::string_literals;
namespace nix::fetchers {
namespace {
RunOptions hgOptions(const Strings & args) {
RunOptions opts("hg", args);
opts.searchPath = true;
auto env = getEnv();
// Set HGPLAIN: this means we get consistent output from hg and avoids leakage from a user or system .hgrc.
env["HGPLAIN"] = "";
opts.environment = env;
return opts;
}
// runProgram wrapper that uses hgOptions instead of stock RunOptions.
string runHg(const Strings & args, const std::optional<std::string> & input = {})
{
RunOptions opts = hgOptions(args);
opts.input = input;
auto res = runProgram(opts);
if (!statusOk(res.first))
throw ExecError(res.first, fmt("hg %1%", statusToString(res.first)));
return res.second;
}
}
struct MercurialInputScheme : InputScheme
{
std::optional<Input> inputFromURL(const ParsedURL & url) override
@ -100,11 +130,11 @@ struct MercurialInputScheme : InputScheme
assert(sourcePath);
// FIXME: shut up if file is already tracked.
runProgram("hg", true,
runHg(
{ "add", *sourcePath + "/" + std::string(file) });
if (commitMsg)
runProgram("hg", true,
runHg(
{ "commit", *sourcePath + "/" + std::string(file), "-m", *commitMsg });
}
@ -130,7 +160,7 @@ struct MercurialInputScheme : InputScheme
if (!input.getRef() && !input.getRev() && isLocal && pathExists(actualUrl + "/.hg")) {
bool clean = runProgram("hg", true, { "status", "-R", actualUrl, "--modified", "--added", "--removed" }) == "";
bool clean = runHg({ "status", "-R", actualUrl, "--modified", "--added", "--removed" }) == "";
if (!clean) {
@ -143,10 +173,10 @@ struct MercurialInputScheme : InputScheme
if (settings.warnDirty)
warn("Mercurial tree '%s' is unclean", actualUrl);
input.attrs.insert_or_assign("ref", chomp(runProgram("hg", true, { "branch", "-R", actualUrl })));
input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl })));
auto files = tokenizeString<std::set<std::string>>(
runProgram("hg", true, { "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
PathFilter filter = [&](const Path & p) -> bool {
assert(hasPrefix(p, actualUrl));
@ -166,7 +196,7 @@ struct MercurialInputScheme : InputScheme
auto storePath = store->addToStore("source", actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
return {
Tree(store->printStorePath(storePath), std::move(storePath)),
Tree(store->toRealPath(storePath), std::move(storePath)),
input
};
}
@ -224,33 +254,33 @@ struct MercurialInputScheme : InputScheme
if (!(input.getRev()
&& pathExists(cacheDir)
&& runProgram(
RunOptions("hg", { "log", "-R", cacheDir, "-r", input.getRev()->gitRev(), "--template", "1" })
hgOptions({ "log", "-R", cacheDir, "-r", input.getRev()->gitRev(), "--template", "1" })
.killStderr(true)).second == "1"))
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", actualUrl));
if (pathExists(cacheDir)) {
try {
runProgram("hg", true, { "pull", "-R", cacheDir, "--", actualUrl });
runHg({ "pull", "-R", cacheDir, "--", actualUrl });
}
catch (ExecError & e) {
string transJournal = cacheDir + "/.hg/store/journal";
/* hg throws "abandoned transaction" error only if this file exists */
if (pathExists(transJournal)) {
runProgram("hg", true, { "recover", "-R", cacheDir });
runProgram("hg", true, { "pull", "-R", cacheDir, "--", actualUrl });
runHg({ "recover", "-R", cacheDir });
runHg({ "pull", "-R", cacheDir, "--", actualUrl });
} else {
throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status)));
}
}
} else {
createDirs(dirOf(cacheDir));
runProgram("hg", true, { "clone", "--noupdate", "--", actualUrl, cacheDir });
runHg({ "clone", "--noupdate", "--", actualUrl, cacheDir });
}
}
auto tokens = tokenizeString<std::vector<std::string>>(
runProgram("hg", true, { "log", "-R", cacheDir, "-r", revOrRef, "--template", "{node} {rev} {branch}" }));
runHg({ "log", "-R", cacheDir, "-r", revOrRef, "--template", "{node} {rev} {branch}" }));
assert(tokens.size() == 3);
input.attrs.insert_or_assign("rev", Hash::parseAny(tokens[0], htSHA1).gitRev());
@ -263,7 +293,7 @@ struct MercurialInputScheme : InputScheme
Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir, true);
runProgram("hg", true, { "archive", "-R", cacheDir, "-r", input.getRev()->gitRev(), tmpDir });
runHg({ "archive", "-R", cacheDir, "-r", input.getRev()->gitRev(), tmpDir });
deletePath(tmpDir + "/.hg_archival.txt");
@ -271,7 +301,7 @@ struct MercurialInputScheme : InputScheme
Attrs infoAttrs({
{"rev", input.getRev()->gitRev()},
{"revCount", (int64_t) revCount},
{"revCount", (uint64_t) revCount},
});
if (!_input.getRev())

View file

@ -20,10 +20,10 @@ struct PathInputScheme : InputScheme
if (name == "rev" || name == "narHash")
input.attrs.insert_or_assign(name, value);
else if (name == "revCount" || name == "lastModified") {
uint64_t n;
if (!string2Int(value, n))
if (auto n = string2Int<uint64_t>(value))
input.attrs.insert_or_assign(name, *n);
else
throw Error("path URL '%s' has invalid parameter '%s'", url.to_string(), name);
input.attrs.insert_or_assign(name, n);
}
else
throw Error("path URL '%s' has unsupported parameter '%s'", url.to_string(), name);

View file

@ -60,10 +60,10 @@ void Registry::write(const Path & path)
nlohmann::json arr;
for (auto & entry : entries) {
nlohmann::json obj;
obj["from"] = attrsToJson(entry.from.toAttrs());
obj["to"] = attrsToJson(entry.to.toAttrs());
obj["from"] = attrsToJSON(entry.from.toAttrs());
obj["to"] = attrsToJSON(entry.to.toAttrs());
if (!entry.extraAttrs.empty())
obj["to"].update(attrsToJson(entry.extraAttrs));
obj["to"].update(attrsToJSON(entry.extraAttrs));
if (entry.exact)
obj["exact"] = true;
arr.emplace_back(std::move(obj));
@ -114,7 +114,7 @@ static std::shared_ptr<Registry> getSystemRegistry()
Path getUserRegistryPath()
{
return getHome() + "/.config/nix/registry.json";
return getConfigDir() + "/nix/registry.json";
}
std::shared_ptr<Registry> getUserRegistry()

View file

@ -64,7 +64,6 @@ DownloadFileResult downloadFile(
if (res.cached) {
assert(cached);
assert(request.expectedETag == res.etag);
storePath = std::move(cached->storePath);
} else {
StringSink sink;
@ -110,7 +109,7 @@ DownloadFileResult downloadFile(
};
}
std::pair<Tree, time_t> downloadTarball(
std::pair<Tree, DownloadTarballMeta> downloadTarball(
ref<Store> store,
const std::string & url,
const std::string & name,
@ -128,7 +127,10 @@ std::pair<Tree, time_t> downloadTarball(
if (cached && !cached->expired)
return {
Tree(store->toRealPath(cached->storePath), std::move(cached->storePath)),
getIntAttr(cached->infoAttrs, "lastModified")
{
.lastModified = time_t(getIntAttr(cached->infoAttrs, "lastModified")),
.effectiveUrl = maybeGetStrAttr(cached->infoAttrs, "effectiveUrl").value_or(url),
},
};
auto res = downloadFile(store, url, name, immutable, headers);
@ -152,7 +154,8 @@ std::pair<Tree, time_t> downloadTarball(
}
Attrs infoAttrs({
{"lastModified", lastModified},
{"lastModified", uint64_t(lastModified)},
{"effectiveUrl", res.effectiveUrl},
{"etag", res.etag},
});
@ -165,7 +168,10 @@ std::pair<Tree, time_t> downloadTarball(
return {
Tree(store->toRealPath(*unpackedStorePath), std::move(*unpackedStorePath)),
lastModified,
{
.lastModified = lastModified,
.effectiveUrl = res.effectiveUrl,
},
};
}
@ -224,9 +230,11 @@ struct TarballInputScheme : InputScheme
return true;
}
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
{
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), "source", false).first;
Input input(_input);
auto [tree, meta] = downloadTarball(store, getStrAttr(input.attrs, "url"), "source", false);
input.attrs.insert_or_assign("url", meta.effectiveUrl);
return {std::move(tree), input};
}
};

View file

@ -10,25 +10,28 @@ MixCommonArgs::MixCommonArgs(const string & programName)
addFlag({
.longName = "verbose",
.shortName = 'v',
.description = "increase verbosity level",
.description = "Increase the logging verbosity level.",
.category = loggingCategory,
.handler = {[]() { verbosity = (Verbosity) (verbosity + 1); }},
});
addFlag({
.longName = "quiet",
.description = "decrease verbosity level",
.description = "Decrease the logging verbosity level.",
.category = loggingCategory,
.handler = {[]() { verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError; }},
});
addFlag({
.longName = "debug",
.description = "enable debug output",
.description = "Set the logging verbosity level to 'debug'.",
.category = loggingCategory,
.handler = {[]() { verbosity = lvlDebug; }},
});
addFlag({
.longName = "option",
.description = "set a Nix configuration option (overriding `nix.conf`)",
.description = "Set the Nix configuration setting *name* to *value* (overriding `nix.conf`).",
.labels = {"name", "value"},
.handler = {[](std::string name, std::string value) {
try {
@ -44,15 +47,15 @@ MixCommonArgs::MixCommonArgs(const string & programName)
globalConfig.getSettings(settings);
for (auto & s : settings)
if (hasPrefix(s.first, prefix))
completions->add(s.first, s.second.description);
completions->add(s.first, fmt("Set the `%s` setting.", s.first));
}
}
});
addFlag({
.longName = "log-format",
.description = "format of log output; `raw`, `internal-json`, `bar` "
"or `bar-with-logs`",
.description = "Set the format of log output; one of `raw`, `internal-json`, `bar` or `bar-with-logs`.",
.category = loggingCategory,
.labels = {"format"},
.handler = {[](std::string format) { setLogFormat(format); }},
});
@ -60,14 +63,14 @@ MixCommonArgs::MixCommonArgs(const string & programName)
addFlag({
.longName = "max-jobs",
.shortName = 'j',
.description = "maximum number of parallel builds",
.description = "The maximum number of parallel builds.",
.labels = Strings{"jobs"},
.handler = {[=](std::string s) {
settings.set("max-jobs", s);
}}
});
std::string cat = "config";
std::string cat = "Options to override configuration settings";
globalConfig.convertToArgs(*this, cat);
// Backward compatibility hack: nix-env already had a --system flag.
@ -76,4 +79,11 @@ MixCommonArgs::MixCommonArgs(const string & programName)
hiddenCategories.insert(cat);
}
void MixCommonArgs::initialFlagsProcessed()
{
initPlugins();
pluginsInited();
}
}

View file

@ -4,10 +4,17 @@
namespace nix {
struct MixCommonArgs : virtual Args
//static constexpr auto commonArgsCategory = "Miscellaneous common options";
static constexpr auto loggingCategory = "Logging-related options";
class MixCommonArgs : public virtual Args
{
void initialFlagsProcessed() override;
public:
string programName;
MixCommonArgs(const string & programName);
protected:
virtual void pluginsInited() {}
};
struct MixDryRun : virtual Args
@ -16,7 +23,12 @@ struct MixDryRun : virtual Args
MixDryRun()
{
mkFlag(0, "dry-run", "show what this command would do without doing it", &dryRun);
addFlag({
.longName = "dry-run",
.description = "Show what this command would do without doing it.",
//.category = commonArgsCategory,
.handler = {&dryRun, true},
});
}
};
@ -26,7 +38,12 @@ struct MixJSON : virtual Args
MixJSON()
{
mkFlag(0, "json", "produce JSON output", &json);
addFlag({
.longName = "json",
.description = "Produce output in JSON format, suitable for consumption by another program.",
//.category = commonArgsCategory,
.handler = {&json, true},
});
}
};

View file

@ -12,7 +12,7 @@ LogFormat parseLogFormat(const std::string & logFormatStr) {
else if (logFormatStr == "raw-with-logs")
return LogFormat::rawWithLogs;
else if (logFormatStr == "internal-json")
return LogFormat::internalJson;
return LogFormat::internalJSON;
else if (logFormatStr == "bar")
return LogFormat::bar;
else if (logFormatStr == "bar-with-logs")
@ -26,7 +26,7 @@ Logger * makeDefaultLogger() {
return makeSimpleLogger(false);
case LogFormat::rawWithLogs:
return makeSimpleLogger(true);
case LogFormat::internalJson:
case LogFormat::internalJSON:
return makeJSONLogger(*makeSimpleLogger(true));
case LogFormat::bar:
return makeProgressBar();

View file

@ -7,7 +7,7 @@ namespace nix {
enum class LogFormat {
raw,
rawWithLogs,
internalJson,
internalJSON,
bar,
barWithLogs,
};

View file

@ -122,6 +122,7 @@ public:
void log(Verbosity lvl, const FormatOrString & fs) override
{
if (lvl > verbosity) return;
auto state(state_.lock());
log(*state, lvl, fs.s);
}
@ -466,6 +467,17 @@ public:
Logger::writeToStdout(s);
}
}
std::optional<char> ask(std::string_view msg) override
{
auto state(state_.lock());
if (!state->active || !isatty(STDIN_FILENO)) return {};
std::cerr << fmt("\r\e[K%s ", msg);
auto s = trim(readLine(STDIN_FILENO));
if (s.size() != 1) return {};
draw(*state);
return s[0];
}
};
Logger * makeProgressBar(bool printBuildLogs)

View file

@ -18,6 +18,8 @@
#include <openssl/crypto.h>
#include <sodium.h>
namespace nix {
@ -34,7 +36,7 @@ void printGCWarning()
}
void printMissing(ref<Store> store, const std::vector<StorePathWithOutputs> & paths, Verbosity lvl)
void printMissing(ref<Store> store, const std::vector<DerivedPath> & paths, Verbosity lvl)
{
uint64_t downloadSize, narSize;
StorePathSet willBuild, willSubstitute, unknown;
@ -126,6 +128,9 @@ void initNix()
CRYPTO_set_locking_callback(opensslLockCallback);
#endif
if (sodium_init() == -1)
throw Error("could not initialise libsodium");
loadConfFile();
startSignalHandlerThread();
@ -181,50 +186,64 @@ LegacyArgs::LegacyArgs(const std::string & programName,
addFlag({
.longName = "no-build-output",
.shortName = 'Q',
.description = "do not show build output",
.description = "Do not show build output.",
.handler = {[&]() {setLogFormat(LogFormat::raw); }},
});
addFlag({
.longName = "keep-failed",
.shortName ='K',
.description = "keep temporary directories of failed builds",
.description = "Keep temporary directories of failed builds.",
.handler = {&(bool&) settings.keepFailed, true},
});
addFlag({
.longName = "keep-going",
.shortName ='k',
.description = "keep going after a build fails",
.description = "Keep going after a build fails.",
.handler = {&(bool&) settings.keepGoing, true},
});
addFlag({
.longName = "fallback",
.description = "build from source if substitution fails",
.description = "Build from source if substitution fails.",
.handler = {&(bool&) settings.tryFallback, true},
});
auto intSettingAlias = [&](char shortName, const std::string & longName,
const std::string & description, const std::string & dest) {
mkFlag<unsigned int>(shortName, longName, description, [=](unsigned int n) {
settings.set(dest, std::to_string(n));
const std::string & description, const std::string & dest)
{
addFlag({
.longName = longName,
.shortName = shortName,
.description = description,
.labels = {"n"},
.handler = {[=](std::string s) {
auto n = string2IntWithUnitPrefix<uint64_t>(s);
settings.set(dest, std::to_string(n));
}}
});
};
intSettingAlias(0, "cores", "maximum number of CPU cores to use inside a build", "cores");
intSettingAlias(0, "max-silent-time", "number of seconds of silence before a build is killed", "max-silent-time");
intSettingAlias(0, "timeout", "number of seconds before a build is killed", "timeout");
intSettingAlias(0, "cores", "Maximum number of CPU cores to use inside a build.", "cores");
intSettingAlias(0, "max-silent-time", "Number of seconds of silence before a build is killed.", "max-silent-time");
intSettingAlias(0, "timeout", "Number of seconds before a build is killed.", "timeout");
mkFlag(0, "readonly-mode", "do not write to the Nix store",
&settings.readOnlyMode);
addFlag({
.longName = "readonly-mode",
.description = "Do not write to the Nix store.",
.handler = {&settings.readOnlyMode, true},
});
mkFlag(0, "no-gc-warning", "disable warning about not using '--add-root'",
&gcWarning, false);
addFlag({
.longName = "no-gc-warning",
.description = "Disable warnings about not using `--add-root`.",
.handler = {&gcWarning, true},
});
addFlag({
.longName = "store",
.description = "URI of the Nix store to use",
.description = "The URL of the Nix store to use.",
.labels = {"store-uri"},
.handler = {&(std::string&) settings.storeUri},
});
@ -274,9 +293,7 @@ void printVersion(const string & programName)
#if HAVE_BOEHMGC
cfg.push_back("gc");
#endif
#if HAVE_SODIUM
cfg.push_back("signed-caches");
#endif
std::cout << "System type: " << settings.thisSystem << "\n";
std::cout << "Additional system types: " << concatStringsSep(", ", settings.extraPlatforms.get()) << "\n";
std::cout << "Features: " << concatStringsSep(", ", cfg) << "\n";
@ -293,7 +310,7 @@ void printVersion(const string & programName)
void showManPage(const string & name)
{
restoreSignals();
restoreProcessContext();
setenv("MANPATH", settings.nixManDir.c_str(), 1);
execlp("man", "man", name.c_str(), nullptr);
throw SysError("command 'man %1%' failed", name.c_str());
@ -356,7 +373,7 @@ RunPager::RunPager()
throw SysError("dupping stdin");
if (!getenv("LESS"))
setenv("LESS", "FRSXMK", 1);
restoreSignals();
restoreProcessContext();
if (pager)
execl("/bin/sh", "sh", "-c", pager, nullptr);
execlp("pager", "pager", nullptr);

View file

@ -4,6 +4,7 @@
#include "args.hh"
#include "common-args.hh"
#include "path.hh"
#include "derived-path.hh"
#include <signal.h>
@ -42,7 +43,7 @@ struct StorePathWithOutputs;
void printMissing(
ref<Store> store,
const std::vector<StorePathWithOutputs> & paths,
const std::vector<DerivedPath> & paths,
Verbosity lvl = lvlInfo);
void printMissing(ref<Store> store, const StorePathSet & willBuild,
@ -57,23 +58,7 @@ template<class N> N getIntArg(const string & opt,
{
++i;
if (i == end) throw UsageError("'%1%' requires an argument", opt);
string s = *i;
N multiplier = 1;
if (allowUnit && !s.empty()) {
char u = std::toupper(*s.rbegin());
if (std::isalpha(u)) {
if (u == 'K') multiplier = 1ULL << 10;
else if (u == 'M') multiplier = 1ULL << 20;
else if (u == 'G') multiplier = 1ULL << 30;
else if (u == 'T') multiplier = 1ULL << 40;
else throw UsageError("invalid unit specifier '%1%'", u);
s.resize(s.size() - 1);
}
}
N n;
if (!string2Int(s, n))
throw UsageError("'%1%' requires an integer argument", opt);
return n * multiplier;
return string2IntWithUnitPrefix<N>(*i);
}

View file

@ -86,8 +86,7 @@ void BinaryCacheStore::getFile(const std::string & path, Sink & sink)
promise.set_exception(std::current_exception());
}
}});
auto data = promise.get_future().get();
sink((unsigned char *) data->data(), data->size());
sink(*promise.get_future().get());
}
std::shared_ptr<std::string> BinaryCacheStore::getFile(const std::string & path)
@ -434,7 +433,9 @@ StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s
if (!repair && isValidPath(path))
return path;
auto source = StringSource { s };
StringSink sink;
dumpString(s, sink);
auto source = StringSource { *sink.s };
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
ValidPathInfo info { path, nar.first };
info.narSize = nar.second;
@ -444,6 +445,24 @@ StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s
})->path;
}
std::optional<const Realisation> BinaryCacheStore::queryRealisation(const DrvOutput & id)
{
auto outputInfoFilePath = realisationsPrefix + "/" + id.to_string() + ".doi";
auto rawOutputInfo = getFile(outputInfoFilePath);
if (rawOutputInfo) {
return {Realisation::fromJSON(
nlohmann::json::parse(*rawOutputInfo), outputInfoFilePath)};
} else {
return std::nullopt;
}
}
void BinaryCacheStore::registerDrvOutput(const Realisation& info) {
auto filePath = realisationsPrefix + "/" + info.id.to_string() + ".doi";
upsertFile(filePath, info.toJSON().dump(), "application/json");
}
ref<FSAccessor> BinaryCacheStore::getFSAccessor()
{
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), localNarCache);

View file

@ -24,7 +24,7 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
"enable multi-threading compression, available for xz only currently"};
};
class BinaryCacheStore : public Store, public virtual BinaryCacheStoreConfig
class BinaryCacheStore : public virtual BinaryCacheStoreConfig, public virtual Store
{
private:
@ -33,6 +33,9 @@ private:
protected:
// The prefix under which realisation infos will be stored
const std::string realisationsPrefix = "/realisations";
BinaryCacheStore(const Params & params);
public:
@ -99,15 +102,12 @@ public:
StorePath addTextToStore(const string & name, const string & s,
const StorePathSet & references, RepairFlag repair) override;
void registerDrvOutput(const Realisation & info) override;
std::optional<const Realisation> queryRealisation(const DrvOutput &) override;
void narFromPath(const StorePath & path, Sink & sink) override;
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode) override
{ unsupported("buildDerivation"); }
void ensurePath(const StorePath & path) override
{ unsupported("ensurePath"); }
ref<FSAccessor> getFSAccessor() override;
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,8 @@
#include "parsed-derivations.hh"
#include "lock.hh"
#include "local-store.hh"
#include "store-api.hh"
#include "pathlocks.hh"
#include "goal.hh"
namespace nix {
@ -37,18 +38,21 @@ struct InitialOutputStatus {
struct InitialOutput {
bool wanted;
Hash outputHash;
std::optional<InitialOutputStatus> known;
};
class DerivationGoal : public Goal
struct DerivationGoal : public Goal
{
private:
/* Whether to use an on-disk .drv file. */
bool useDerivation;
/* The path of the derivation. */
StorePath drvPath;
/* The path of the corresponding resolved derivation */
std::optional<BasicDerivation> resolvedDrv;
/* The specific outputs that we need to build. Empty means all of
them. */
StringSet wantedOutputs;
@ -61,7 +65,7 @@ private:
bool retrySubstitution;
/* The derivation stored at drvPath. */
std::unique_ptr<BasicDerivation> drv;
std::unique_ptr<Derivation> drv;
std::unique_ptr<ParsedDerivation> parsedDrv;
@ -76,18 +80,6 @@ private:
std::map<std::string, InitialOutput> initialOutputs;
/* User selected for running the builder. */
std::unique_ptr<UserLock> buildUser;
/* The process ID of the builder. */
Pid pid;
/* The temporary directory. */
Path tmpDir;
/* The path of the temporary directory in the sandbox. */
Path tmpDirInSandbox;
/* File descriptor for the log file. */
AutoCloseFD fdLogFile;
std::shared_ptr<BufferedSink> logFileSink, logSink;
@ -103,86 +95,15 @@ private:
std::string currentHookLine;
/* Pipe for the builder's standard output/error. */
Pipe builderOut;
/* Pipe for synchronising updates to the builder namespaces. */
Pipe userNamespaceSync;
/* The mount namespace of the builder, used to add additional
paths to the sandbox as a result of recursive Nix calls. */
AutoCloseFD sandboxMountNamespace;
/* On Linux, whether we're doing the build in its own user
namespace. */
bool usingUserNamespace = true;
/* The build hook. */
std::unique_ptr<HookInstance> hook;
/* Whether we're currently doing a chroot build. */
bool useChroot = false;
Path chrootRootDir;
/* Whether to give the build more than 1 UID. */
bool useUidRange = false;
/* Whether to make the 'systemd' cgroup controller available to
the build. */
bool useSystemdCgroup = false;
/* RAII object to delete the chroot directory. */
std::shared_ptr<AutoDelete> autoDelChroot;
/* The sort of derivation we are building. */
DerivationType derivationType;
/* Whether to run the build in a private network namespace. */
bool privateNetwork = false;
typedef void (DerivationGoal::*GoalState)();
GoalState state;
/* Stuff we need to pass to initChild(). */
struct ChrootPath {
Path source;
bool optional;
ChrootPath(Path source = "", bool optional = false)
: source(source), optional(optional)
{ }
};
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
DirsInChroot dirsInChroot;
typedef map<string, string> Environment;
Environment env;
#if __APPLE__
typedef string SandboxProfile;
SandboxProfile additionalSandboxProfile;
#endif
/* Hash rewriting. */
StringMap inputRewrites, outputRewrites;
typedef map<StorePath, StorePath> RedirectedOutputs;
RedirectedOutputs redirectedOutputs;
/* The outputs paths used during the build.
- Input-addressed derivations or fixed content-addressed outputs are
sometimes built when some of their outputs already exist, and can not
be hidden via sandboxing. We use temporary locations instead and
rewrite after the build. Otherwise the regular predetermined paths are
put here.
- Floating content-addressed derivations do not know their final build
output paths until the outputs are hashed, so random locations are
used, and then renamed. The randomness helps guard against hidden
self-references.
*/
OutputPathMap scratchOutputs;
/* The final output paths of the build.
- For input-addressed derivations, always the precomputed paths
@ -195,11 +116,6 @@ private:
BuildMode buildMode;
/* If we're repairing without a chroot, there may be outputs that
are valid but corrupt. So we redirect these outputs to
temporary paths. */
StorePathSet redirectedBadOutputs;
BuildResult result;
/* The current round, if we're building multiple times. */
@ -207,17 +123,6 @@ private:
size_t nrRounds;
/* Path registration info from the previous round, if we're
building multiple times. Since this contains the hash, it
allows us to compare whether two rounds produced the same
result. */
std::map<Path, ValidPathInfo> prevInfos;
uid_t sandboxUid() { return usingUserNamespace ? (useUidRange ? 0 : 1000) : buildUser->getUID(); }
gid_t sandboxGid() { return usingUserNamespace ? (useUidRange ? 0 : 100) : buildUser->getGID(); }
const static Path homeDir;
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
std::unique_ptr<Activity> act;
@ -230,40 +135,13 @@ private:
/* The remote machine on which we're building. */
std::string machineName;
/* The recursive Nix daemon socket. */
AutoCloseFD daemonSocket;
/* The daemon main thread. */
std::thread daemonThread;
/* The daemon worker threads. */
std::vector<std::thread> daemonWorkerThreads;
/* Paths that were added via recursive Nix calls. */
StorePathSet addedPaths;
/* Recursive Nix calls are only allowed to build or realize paths
in the original input closure or added via a recursive Nix call
(so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
/nix/store/<bla> is some arbitrary path in a binary cache). */
bool isAllowed(const StorePath & path)
{
return inputPaths.count(path) || addedPaths.count(path);
}
friend struct RestrictedStore;
public:
DerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const StringSet & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
~DerivationGoal();
/* Whether we need to perform hash rewriting if there are valid output paths. */
bool needsHashRewrite();
virtual ~DerivationGoal();
void timedOut(Error && ex) override;
@ -271,17 +149,11 @@ public:
void work() override;
StorePath getDrvPath()
{
return drvPath;
}
/* Add wanted outputs to an already existing derivation goal. */
void addWantedOutputs(const StringSet & outputs);
BuildResult getResult() { return result; }
private:
/* The states. */
void getDerivation();
void loadDerivation();
@ -291,7 +163,7 @@ private:
void closureRepaired();
void inputsRealised();
void tryToBuild();
void tryLocalBuild();
virtual void tryLocalBuild();
void buildDone();
void resolvedFinished();
@ -299,51 +171,33 @@ private:
/* Is the build hook willing to perform the build? */
HookReply tryBuildHook();
/* Start building a derivation. */
void startBuilder();
/* Fill in the environment for the builder. */
void initEnv();
/* Setup tmp dir location. */
void initTmpDir();
/* Write a JSON file containing the derivation attributes. */
void writeStructuredAttrs();
void startDaemon();
void stopDaemon();
/* Add 'path' to the set of paths that may be referenced by the
outputs, and make it appear in the sandbox. */
void addDependency(const StorePath & path);
/* Make a file owned by the builder. */
void chownToBuilder(const Path & path);
/* Run the builder's process. */
void runChild();
friend int childEntry(void *);
virtual int getChildStatus();
/* Check that the derivation outputs all exist and register them
as valid. */
void registerOutputs();
/* Check that an output meets the requirements specified by the
'outputChecks' attribute (or the legacy
'{allowed,disallowed}{References,Requisites}' attributes). */
void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
virtual void registerOutputs();
/* Open a log file and a pipe to it. */
Path openLogFile();
/* Sign the newly built realisation if the store allows it */
virtual void signRealisation(Realisation&) {}
/* Close the log file. */
void closeLogFile();
/* Delete the temporary directory, if we have one. */
void deleteTmpDir(bool force);
/* Close the read side of the logger pipe. */
virtual void closeReadPipes();
/* Cleanup hooks for buildDone() */
virtual void cleanupHookFinally();
virtual void cleanupPreChildKill();
virtual void cleanupPostChildKill();
virtual bool cleanupDecideWhetherDiskFull();
virtual void cleanupPostOutputsRegisteredModeCheck();
virtual void cleanupPostOutputsRegisteredModeNonCheck();
virtual bool isReadDesc(int fd);
/* Callback used by the worker to write to the log. */
void handleChildOutput(int fd, const string & data) override;
@ -360,17 +214,7 @@ private:
void checkPathValidity();
/* Forcibly kill the child process, if any. */
void killChild();
/* Create alternative path calculated from but distinct from the
input, so we can avoid overwriting outputs (or other store paths)
that already exist. */
StorePath makeFallbackPath(const StorePath & path);
/* Make a path to another based on the output name along with the
derivation hash. */
/* FIXME add option to randomize, so we can audit whether our
rewrites caught everything */
StorePath makeFallbackPath(std::string_view outputName);
virtual void killChild();
void repairClosure();
@ -383,4 +227,6 @@ private:
StorePathSet exportReferences(const StorePathSet & storePaths);
};
MakeError(NotDeterministic, BuildError);
}

View file

@ -0,0 +1,95 @@
#include "drv-output-substitution-goal.hh"
#include "worker.hh"
#include "substitution-goal.hh"
namespace nix {
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
: Goal(worker)
, id(id)
{
state = &DrvOutputSubstitutionGoal::init;
name = fmt("substitution of '%s'", id.to_string());
trace("created");
}
void DrvOutputSubstitutionGoal::init()
{
trace("init");
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
tryNext();
}
void DrvOutputSubstitutionGoal::tryNext()
{
trace("Trying next substituter");
if (subs.size() == 0) {
/* None left. Terminate this goal and let someone else deal
with it. */
debug("drv output '%s' is required, but there is no substituter that can provide it", id.to_string());
/* Hack: don't indicate failure if there were no substituters.
In that case the calling derivation should just do a
build. */
amDone(substituterFailed ? ecFailed : ecNoSubstituters);
if (substituterFailed) {
worker.failedSubstitutions++;
worker.updateProgress();
}
return;
}
auto sub = subs.front();
subs.pop_front();
// FIXME: Make async
outputInfo = sub->queryRealisation(id);
if (!outputInfo) {
tryNext();
return;
}
addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath));
if (waitees.empty()) outPathValid();
else state = &DrvOutputSubstitutionGoal::outPathValid;
}
void DrvOutputSubstitutionGoal::outPathValid()
{
assert(outputInfo);
trace("Output path substituted");
if (nrFailed > 0) {
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
return;
}
worker.store.registerDrvOutput(*outputInfo);
finished();
}
void DrvOutputSubstitutionGoal::finished()
{
trace("finished");
amDone(ecSuccess);
}
string DrvOutputSubstitutionGoal::key()
{
/* "a$" ensures substitution goals happen before derivation
goals. */
return "a$" + std::string(id.to_string());
}
void DrvOutputSubstitutionGoal::work()
{
(this->*state)();
}
}

View file

@ -0,0 +1,50 @@
#pragma once
#include "store-api.hh"
#include "goal.hh"
#include "realisation.hh"
namespace nix {
class Worker;
// Substitution of a derivation output.
// This is done in three steps:
// 1. Fetch the output info from a substituter
// 2. Substitute the corresponding output path
// 3. Register the output info
class DrvOutputSubstitutionGoal : public Goal {
private:
// The drv output we're trying to substitue
DrvOutput id;
// The realisation corresponding to the given output id.
// Will be filled once we can get it.
std::optional<Realisation> outputInfo;
/* The remaining substituters. */
std::list<ref<Store>> subs;
/* Whether a substituter failed. */
bool substituterFailed = false;
public:
DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
typedef void (DrvOutputSubstitutionGoal::*GoalState)();
GoalState state;
void init();
void tryNext();
void outPathValid();
void finished();
void timedOut(Error && ex) override { abort(); };
string key() override;
void work() override;
};
}

View file

@ -2,34 +2,24 @@
#include "worker.hh"
#include "substitution-goal.hh"
#include "derivation-goal.hh"
#include "local-store.hh"
namespace nix {
static void primeCache(Store & store, const std::vector<StorePathWithOutputs> & paths)
{
StorePathSet willBuild, willSubstitute, unknown;
uint64_t downloadSize, narSize;
store.queryMissing(paths, willBuild, willSubstitute, unknown, downloadSize, narSize);
if (!willBuild.empty() && 0 == settings.maxBuildJobs && getMachines().empty())
throw Error(
"%d derivations need to be built, but neither local builds ('--max-jobs') "
"nor remote builds ('--builders') are enabled", willBuild.size());
}
void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode)
void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMode)
{
Worker worker(*this);
primeCache(*this, drvPaths);
Goals goals;
for (auto & path : drvPaths) {
if (path.path.isDerivation())
goals.insert(worker.makeDerivationGoal(path.path, path.outputs, buildMode));
else
goals.insert(worker.makeSubstitutionGoal(path.path, buildMode == bmRepair ? Repair : NoRepair));
for (auto & br : reqs) {
std::visit(overloaded {
[&](DerivedPath::Built bfd) {
goals.insert(worker.makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode));
},
[&](DerivedPath::Opaque bo) {
goals.insert(worker.makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair));
},
}, br.raw());
}
worker.run(goals);
@ -44,9 +34,8 @@ void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths,
ex = i->ex;
}
if (i->exitCode != Goal::ecSuccess) {
DerivationGoal * i2 = dynamic_cast<DerivationGoal *>(i.get());
if (i2) failed.insert(i2->getDrvPath());
else failed.insert(dynamic_cast<SubstitutionGoal *>(i.get())->getStorePath());
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
else if (auto i2 = dynamic_cast<PathSubstitutionGoal *>(i.get())) failed.insert(i2->storePath);
}
}
@ -59,7 +48,7 @@ void LocalStore::buildPaths(const std::vector<StorePathWithOutputs> & drvPaths,
}
}
BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode)
{
Worker worker(*this);
@ -74,20 +63,38 @@ BuildResult LocalStore::buildDerivation(const StorePath & drvPath, const BasicDe
result.status = BuildResult::MiscFailure;
result.errorMsg = e.msg();
}
// XXX: Should use `goal->queryPartialDerivationOutputMap()` once it's
// extended to return the full realisation for each output
auto staticDrvOutputs = drv.outputsAndOptPaths(*this);
auto outputHashes = staticOutputHashes(*this, drv);
for (auto & [outputName, staticOutput] : staticDrvOutputs) {
auto outputId = DrvOutput{outputHashes.at(outputName), outputName};
if (staticOutput.second)
result.builtOutputs.insert_or_assign(
outputId,
Realisation{ outputId, *staticOutput.second}
);
if (settings.isExperimentalFeatureEnabled("ca-derivations") && !derivationHasKnownOutputPaths(drv.type())) {
auto realisation = this->queryRealisation(outputId);
if (realisation)
result.builtOutputs.insert_or_assign(
outputId,
*realisation
);
}
}
return result;
}
void LocalStore::ensurePath(const StorePath & path)
void Store::ensurePath(const StorePath & path)
{
/* If the path is already valid, we're done. */
if (isValidPath(path)) return;
primeCache(*this, {{path}});
Worker worker(*this);
GoalPtr goal = worker.makeSubstitutionGoal(path);
GoalPtr goal = worker.makePathSubstitutionGoal(path);
Goals goals = {goal};
worker.run(goals);
@ -105,7 +112,7 @@ void LocalStore::ensurePath(const StorePath & path)
void LocalStore::repairPath(const StorePath & path)
{
Worker worker(*this);
GoalPtr goal = worker.makeSubstitutionGoal(path, Repair);
GoalPtr goal = worker.makePathSubstitutionGoal(path, Repair);
Goals goals = {goal};
worker.run(goals);

View file

@ -78,6 +78,8 @@ void Goal::amDone(ExitCode result, std::optional<Error> ex)
}
waiters.clear();
worker.removeGoal(shared_from_this());
cleanup();
}

View file

@ -7,7 +7,7 @@ namespace nix {
/* Forward definition. */
struct Goal;
struct Worker;
class Worker;
/* A pointer to a goal. */
typedef std::shared_ptr<Goal> GoalPtr;
@ -46,7 +46,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
unsigned int nrNoSubstituters;
/* Number of substitution goals we are/were waiting for that
failed because othey had unsubstitutable references. */
failed because they had unsubstitutable references. */
unsigned int nrIncompleteClosure;
/* Name of this goal for debugging purposes. */
@ -100,6 +100,8 @@ struct Goal : public std::enable_shared_from_this<Goal>
virtual string key() = 0;
void amDone(ExitCode result, std::optional<Error> ex = {});
virtual void cleanup() { }
};
void addToWeakGoals(WeakGoals & goals, GoalPtr p);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,209 @@
#pragma once
#include "derivation-goal.hh"
#include "local-store.hh"
namespace nix {
struct LocalDerivationGoal : public DerivationGoal
{
LocalStore & getLocalStore();
/* User selected for running the builder. */
std::unique_ptr<UserLock> buildUser;
/* The process ID of the builder. */
Pid pid;
/* The temporary directory. */
Path tmpDir;
/* The path of the temporary directory in the sandbox. */
Path tmpDirInSandbox;
/* Pipe for the builder's standard output/error. */
Pipe builderOut;
/* Pipe for synchronising updates to the builder namespaces. */
Pipe userNamespaceSync;
/* The mount namespace of the builder, used to add additional
paths to the sandbox as a result of recursive Nix calls. */
AutoCloseFD sandboxMountNamespace;
/* On Linux, whether we're doing the build in its own user
namespace. */
bool usingUserNamespace = true;
/* Whether we're currently doing a chroot build. */
bool useChroot = false;
Path chrootRootDir;
/* Whether to give the build more than 1 UID. */
bool useUidRange = false;
/* Whether to make the 'systemd' cgroup controller available to
the build. */
bool useSystemdCgroup = false;
/* RAII object to delete the chroot directory. */
std::shared_ptr<AutoDelete> autoDelChroot;
/* Whether to run the build in a private network namespace. */
bool privateNetwork = false;
/* Stuff we need to pass to initChild(). */
struct ChrootPath {
Path source;
bool optional;
ChrootPath(Path source = "", bool optional = false)
: source(source), optional(optional)
{ }
};
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
DirsInChroot dirsInChroot;
typedef map<string, string> Environment;
Environment env;
#if __APPLE__
typedef string SandboxProfile;
SandboxProfile additionalSandboxProfile;
#endif
/* Hash rewriting. */
StringMap inputRewrites, outputRewrites;
typedef map<StorePath, StorePath> RedirectedOutputs;
RedirectedOutputs redirectedOutputs;
/* The outputs paths used during the build.
- Input-addressed derivations or fixed content-addressed outputs are
sometimes built when some of their outputs already exist, and can not
be hidden via sandboxing. We use temporary locations instead and
rewrite after the build. Otherwise the regular predetermined paths are
put here.
- Floating content-addressed derivations do not know their final build
output paths until the outputs are hashed, so random locations are
used, and then renamed. The randomness helps guard against hidden
self-references.
*/
OutputPathMap scratchOutputs;
/* Path registration info from the previous round, if we're
building multiple times. Since this contains the hash, it
allows us to compare whether two rounds produced the same
result. */
std::map<Path, ValidPathInfo> prevInfos;
uid_t sandboxUid() { return usingUserNamespace ? (useUidRange ? 0 : 1000) : buildUser->getUID(); }
gid_t sandboxGid() { return usingUserNamespace ? (useUidRange ? 0 : 100) : buildUser->getGID(); }
const static Path homeDir;
/* The recursive Nix daemon socket. */
AutoCloseFD daemonSocket;
/* The daemon main thread. */
std::thread daemonThread;
/* The daemon worker threads. */
std::vector<std::thread> daemonWorkerThreads;
/* Paths that were added via recursive Nix calls. */
StorePathSet addedPaths;
/* Recursive Nix calls are only allowed to build or realize paths
in the original input closure or added via a recursive Nix call
(so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
/nix/store/<bla> is some arbitrary path in a binary cache). */
bool isAllowed(const StorePath & path)
{
return inputPaths.count(path) || addedPaths.count(path);
}
bool isAllowed(const DerivedPath & req);
friend struct RestrictedStore;
using DerivationGoal::DerivationGoal;
virtual ~LocalDerivationGoal() override;
/* Whether we need to perform hash rewriting if there are valid output paths. */
bool needsHashRewrite();
/* The additional states. */
void tryLocalBuild() override;
/* Start building a derivation. */
void startBuilder();
/* Fill in the environment for the builder. */
void initEnv();
/* Setup tmp dir location. */
void initTmpDir();
/* Write a JSON file containing the derivation attributes. */
void writeStructuredAttrs();
void startDaemon();
void stopDaemon();
/* Add 'path' to the set of paths that may be referenced by the
outputs, and make it appear in the sandbox. */
void addDependency(const StorePath & path);
/* Make a file owned by the builder. */
void chownToBuilder(const Path & path);
int getChildStatus() override;
/* Run the builder's process. */
void runChild();
/* Check that the derivation outputs all exist and register them
as valid. */
void registerOutputs() override;
void signRealisation(Realisation &) override;
/* Check that an output meets the requirements specified by the
'outputChecks' attribute (or the legacy
'{allowed,disallowed}{References,Requisites}' attributes). */
void checkOutputs(const std::map<std::string, ValidPathInfo> & outputs);
/* Close the read side of the logger pipe. */
void closeReadPipes() override;
/* Cleanup hooks for buildDone() */
void cleanupHookFinally() override;
void cleanupPreChildKill() override;
void cleanupPostChildKill() override;
bool cleanupDecideWhetherDiskFull() override;
void cleanupPostOutputsRegisteredModeCheck() override;
void cleanupPostOutputsRegisteredModeNonCheck() override;
bool isReadDesc(int fd) override;
/* Delete the temporary directory, if we have one. */
void deleteTmpDir(bool force);
/* Forcibly kill the child process, if any. */
void killChild() override;
/* Create alternative path calculated from but distinct from the
input, so we can avoid overwriting outputs (or other store paths)
that already exist. */
StorePath makeFallbackPath(const StorePath & path);
/* Make a path to another based on the output name along with the
derivation hash. */
/* FIXME add option to randomize, so we can audit whether our
rewrites caught everything */
StorePath makeFallbackPath(std::string_view outputName);
};
}

View file

@ -5,40 +5,32 @@
namespace nix {
SubstitutionGoal::SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
: Goal(worker)
, storePath(storePath)
, repair(repair)
, ca(ca)
{
state = &SubstitutionGoal::init;
state = &PathSubstitutionGoal::init;
name = fmt("substitution of '%s'", worker.store.printStorePath(this->storePath));
trace("created");
maintainExpectedSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.expectedSubstitutions);
}
SubstitutionGoal::~SubstitutionGoal()
PathSubstitutionGoal::~PathSubstitutionGoal()
{
try {
if (thr.joinable()) {
// FIXME: signal worker thread to quit.
thr.join();
worker.childTerminated(this);
}
} catch (...) {
ignoreException();
}
cleanup();
}
void SubstitutionGoal::work()
void PathSubstitutionGoal::work()
{
(this->*state)();
}
void SubstitutionGoal::init()
void PathSubstitutionGoal::init()
{
trace("init");
@ -59,10 +51,12 @@ void SubstitutionGoal::init()
}
void SubstitutionGoal::tryNext()
void PathSubstitutionGoal::tryNext()
{
trace("trying next substituter");
cleanup();
if (subs.size() == 0) {
/* None left. Terminate this goal and let someone else deal
with it. */
@ -142,15 +136,10 @@ void SubstitutionGoal::tryNext()
/* Bail out early if this substituter lacks a valid
signature. LocalStore::addToStore() also checks for this, but
only after we've downloaded the path. */
if (worker.store.requireSigs
&& !sub->isTrusted
&& !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
{
logWarning({
.name = "Invalid path signature",
.hint = hintfmt("substituter '%s' does not have a valid signature for path '%s'",
sub->getUri(), worker.store.printStorePath(storePath))
});
warn("substituter '%s' does not have a valid signature for path '%s'",
sub->getUri(), worker.store.printStorePath(storePath));
tryNext();
return;
}
@ -159,16 +148,16 @@ void SubstitutionGoal::tryNext()
paths referenced by this one. */
for (auto & i : info->references)
if (i != storePath) /* ignore self-references */
addWaitee(worker.makeSubstitutionGoal(i));
addWaitee(worker.makePathSubstitutionGoal(i));
if (waitees.empty()) /* to prevent hang (no wake-up event) */
referencesValid();
else
state = &SubstitutionGoal::referencesValid;
state = &PathSubstitutionGoal::referencesValid;
}
void SubstitutionGoal::referencesValid()
void PathSubstitutionGoal::referencesValid()
{
trace("all references realised");
@ -182,12 +171,12 @@ void SubstitutionGoal::referencesValid()
if (i != storePath) /* ignore self-references */
assert(worker.store.isValidPath(i));
state = &SubstitutionGoal::tryToRun;
state = &PathSubstitutionGoal::tryToRun;
worker.wakeUp(shared_from_this());
}
void SubstitutionGoal::tryToRun()
void PathSubstitutionGoal::tryToRun()
{
trace("trying to run");
@ -210,7 +199,7 @@ void SubstitutionGoal::tryToRun()
thr = std::thread([this]() {
try {
/* Wake up the worker loop when we're done. */
Finally updateStats([this]() { outPipe.writeSide = -1; });
Finally updateStats([this]() { outPipe.writeSide.close(); });
Activity act(*logger, actSubstitute, Logger::Fields{worker.store.printStorePath(storePath), sub->getUri()});
PushActivity pact(act.id);
@ -226,11 +215,11 @@ void SubstitutionGoal::tryToRun()
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
state = &SubstitutionGoal::finished;
state = &PathSubstitutionGoal::finished;
}
void SubstitutionGoal::finished()
void PathSubstitutionGoal::finished()
{
trace("substitute finished");
@ -254,7 +243,7 @@ void SubstitutionGoal::finished()
}
/* Try the next substitute. */
state = &SubstitutionGoal::tryNext;
state = &PathSubstitutionGoal::tryNext;
worker.wakeUp(shared_from_this());
return;
}
@ -283,14 +272,31 @@ void SubstitutionGoal::finished()
}
void SubstitutionGoal::handleChildOutput(int fd, const string & data)
void PathSubstitutionGoal::handleChildOutput(int fd, const string & data)
{
}
void SubstitutionGoal::handleEOF(int fd)
void PathSubstitutionGoal::handleEOF(int fd)
{
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
}
void PathSubstitutionGoal::cleanup()
{
try {
if (thr.joinable()) {
// FIXME: signal worker thread to quit.
thr.join();
worker.childTerminated(this);
}
outPipe.close();
} catch (...) {
ignoreException();
}
}
}

View file

@ -8,16 +8,13 @@ namespace nix {
class Worker;
class SubstitutionGoal : public Goal
struct PathSubstitutionGoal : public Goal
{
friend class Worker;
private:
/* The store path that should be realised through a substitute. */
StorePath storePath;
/* The path the substituter refers to the path as. This will be
* different when the stores have different names. */
different when the stores have different names. */
std::optional<StorePath> subPath;
/* The remaining substituters. */
@ -50,15 +47,15 @@ private:
std::unique_ptr<MaintainCount<uint64_t>> maintainExpectedSubstitutions,
maintainRunningSubstitutions, maintainExpectedNar, maintainExpectedDownload;
typedef void (SubstitutionGoal::*GoalState)();
typedef void (PathSubstitutionGoal::*GoalState)();
GoalState state;
/* Content address for recomputing store path */
std::optional<ContentAddress> ca;
public:
SubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
~SubstitutionGoal();
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
~PathSubstitutionGoal();
void timedOut(Error && ex) override { abort(); };
@ -83,7 +80,7 @@ public:
void handleChildOutput(int fd, const string & data) override;
void handleEOF(int fd) override;
StorePath getStorePath() { return storePath; }
void cleanup() override;
};
}

View file

@ -1,14 +1,15 @@
#include "machines.hh"
#include "worker.hh"
#include "substitution-goal.hh"
#include "derivation-goal.hh"
#include "drv-output-substitution-goal.hh"
#include "local-derivation-goal.hh"
#include "hook-instance.hh"
#include <poll.h>
namespace nix {
Worker::Worker(LocalStore & store)
Worker::Worker(Store & store)
: act(*logger, actRealise)
, actDerivations(*logger, actBuilds)
, actSubstitutions(*logger, actCopyPaths)
@ -43,16 +44,13 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
const StringSet & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
{
WeakGoalPtr & abstract_goal_weak = derivationGoals[drvPath];
GoalPtr abstract_goal = abstract_goal_weak.lock(); // FIXME
std::shared_ptr<DerivationGoal> goal;
if (!abstract_goal) {
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
std::shared_ptr<DerivationGoal> goal = goal_weak.lock();
if (!goal) {
goal = mkDrvGoal();
abstract_goal_weak = goal;
goal_weak = goal;
wakeUp(goal);
} else {
goal = std::dynamic_pointer_cast<DerivationGoal>(abstract_goal);
assert(goal);
goal->addWantedOutputs(wantedOutputs);
}
return goal;
@ -62,8 +60,10 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, BuildMode buildMode)
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() {
return std::make_shared<DerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
? std::make_shared</* */DerivationGoal>(drvPath, wantedOutputs, *this, buildMode)
: std::make_shared<LocalDerivationGoal>(drvPath, wantedOutputs, *this, buildMode);
});
}
@ -71,32 +71,46 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() {
return std::make_shared<DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
? std::make_shared</* */DerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode)
: std::make_shared<LocalDerivationGoal>(drvPath, drv, wantedOutputs, *this, buildMode);
});
}
GoalPtr Worker::makeSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const StorePath & path, RepairFlag repair, std::optional<ContentAddress> ca)
{
WeakGoalPtr & goal_weak = substitutionGoals[path];
GoalPtr goal = goal_weak.lock(); // FIXME
std::weak_ptr<PathSubstitutionGoal> & goal_weak = substitutionGoals[path];
auto goal = goal_weak.lock(); // FIXME
if (!goal) {
goal = std::make_shared<SubstitutionGoal>(path, *this, repair, ca);
goal = std::make_shared<PathSubstitutionGoal>(path, *this, repair, ca);
goal_weak = goal;
wakeUp(goal);
}
return goal;
}
std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal(const DrvOutput& id, RepairFlag repair, std::optional<ContentAddress> ca)
{
std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
auto goal = goal_weak.lock(); // FIXME
if (!goal) {
goal = std::make_shared<DrvOutputSubstitutionGoal>(id, *this, repair, ca);
goal_weak = goal;
wakeUp(goal);
}
return goal;
}
static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
template<typename K, typename G>
static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
{
/* !!! inefficient */
for (WeakGoalMap::iterator i = goalMap.begin();
for (auto i = goalMap.begin();
i != goalMap.end(); )
if (i->second.lock() == goal) {
WeakGoalMap::iterator j = i; ++j;
auto j = i; ++j;
goalMap.erase(i);
i = j;
}
@ -106,8 +120,15 @@ static void removeGoal(GoalPtr goal, WeakGoalMap & goalMap)
void Worker::removeGoal(GoalPtr goal)
{
nix::removeGoal(goal, derivationGoals);
nix::removeGoal(goal, substitutionGoals);
if (auto drvGoal = std::dynamic_pointer_cast<DerivationGoal>(goal))
nix::removeGoal(drvGoal, derivationGoals);
else if (auto subGoal = std::dynamic_pointer_cast<PathSubstitutionGoal>(goal))
nix::removeGoal(subGoal, substitutionGoals);
else if (auto subGoal = std::dynamic_pointer_cast<DrvOutputSubstitutionGoal>(goal))
nix::removeGoal(subGoal, drvOutputSubstitutionGoals);
else
assert(false);
if (topGoals.find(goal) != topGoals.end()) {
topGoals.erase(goal);
/* If a top-level goal failed, then kill all other goals
@ -206,7 +227,21 @@ void Worker::waitForAWhile(GoalPtr goal)
void Worker::run(const Goals & _topGoals)
{
for (auto & i : _topGoals) topGoals.insert(i);
std::vector<nix::DerivedPath> topPaths;
for (auto & i : _topGoals) {
topGoals.insert(i);
if (auto goal = dynamic_cast<DerivationGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Built{goal->drvPath, goal->wantedOutputs});
} else if (auto goal = dynamic_cast<PathSubstitutionGoal *>(i.get())) {
topPaths.push_back(DerivedPath::Opaque{goal->storePath});
}
}
/* Call queryMissing() efficiently query substitutes. */
StorePathSet willBuild, willSubstitute, unknown;
uint64_t downloadSize, narSize;
store.queryMissing(topPaths, willBuild, willSubstitute, unknown, downloadSize, narSize);
debug("entered goal loop");
@ -214,7 +249,9 @@ void Worker::run(const Goals & _topGoals)
checkInterrupt();
store.autoGC(false);
// TODO GC interface?
if (auto localStore = dynamic_cast<LocalStore *>(&store))
localStore->autoGC(false);
/* Call every wake goal (in the ordering established by
CompareGoalPtrs). */
@ -439,10 +476,7 @@ bool Worker::pathContentsGood(const StorePath & path)
}
pathContentsGoodCache.insert_or_assign(path, res);
if (!res)
logError({
.name = "Corrupted path",
.hint = hintfmt("path '%s' is corrupted or missing!", store.printStorePath(path))
});
printError("path '%s' is corrupted or missing!", store.printStorePath(path));
return res;
}
@ -452,4 +486,12 @@ void Worker::markContentsGood(const StorePath & path)
pathContentsGoodCache.insert_or_assign(path, true);
}
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal) {
return subGoal;
}
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal) {
return subGoal;
}
}

View file

@ -2,13 +2,31 @@
#include "types.hh"
#include "lock.hh"
#include "local-store.hh"
#include "store-api.hh"
#include "goal.hh"
#include "realisation.hh"
#include <future>
#include <thread>
namespace nix {
/* Forward definition. */
class DerivationGoal;
struct DerivationGoal;
struct PathSubstitutionGoal;
class DrvOutputSubstitutionGoal;
/* Workaround for not being able to declare a something like
class PathSubstitutionGoal : public Goal;
even when Goal is a complete type.
This is still a static cast. The purpose of exporting it is to define it in
a place where `PathSubstitutionGoal` is concrete, and use it in a place where it
is opaque. */
GoalPtr upcast_goal(std::shared_ptr<PathSubstitutionGoal> subGoal);
GoalPtr upcast_goal(std::shared_ptr<DrvOutputSubstitutionGoal> subGoal);
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
@ -56,8 +74,9 @@ private:
/* Maps used to prevent multiple instantiations of a goal for the
same derivation / path. */
WeakGoalMap derivationGoals;
WeakGoalMap substitutionGoals;
std::map<StorePath, std::weak_ptr<DerivationGoal>> derivationGoals;
std::map<StorePath, std::weak_ptr<PathSubstitutionGoal>> substitutionGoals;
std::map<DrvOutput, std::weak_ptr<DrvOutputSubstitutionGoal>> drvOutputSubstitutionGoals;
/* Goals waiting for busy paths to be unlocked. */
WeakGoals waitingForAnyGoal;
@ -90,7 +109,7 @@ public:
/* Set if at least one derivation is not deterministic in check mode. */
bool checkMismatch;
LocalStore & store;
Store & store;
std::unique_ptr<HookInstance> hook;
@ -112,7 +131,7 @@ public:
it answers with "decline-permanently", we don't try again. */
bool tryBuildHook = true;
Worker(LocalStore & store);
Worker(Store & store);
~Worker();
/* Make a goal (with caching). */
@ -131,7 +150,8 @@ public:
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
/* substitution goal */
GoalPtr makeSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
/* Remove a dead goal. */
void removeGoal(GoalPtr goal);

View file

@ -22,10 +22,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
srcFiles = readDirectory(srcDir);
} catch (SysError & e) {
if (e.errNo == ENOTDIR) {
logWarning({
.name = "Create links - directory",
.hint = hintfmt("not including '%s' in the user environment because it's not a directory", srcDir)
});
warn("not including '%s' in the user environment because it's not a directory", srcDir);
return;
}
throw;
@ -44,10 +41,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw SysError("getting status of '%1%'", srcFile);
} catch (SysError & e) {
if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
logWarning({
.name = "Create links - skipping symlink",
.hint = hintfmt("skipping dangling symlink '%s'", dstFile)
});
warn("skipping dangling symlink '%s'", dstFile);
continue;
}
throw;

View file

@ -0,0 +1,12 @@
-- Extension of the sql schema for content-addressed derivations.
-- Won't be loaded unless the experimental feature `ca-derivations`
-- is enabled
create table if not exists Realisations (
drvPath text not null,
outputName text not null, -- symbolic output id, usually "out"
outputPath integer not null,
signatures text, -- space-separated list
primary key (drvPath, outputName),
foreign key (outputPath) references ValidPaths(id) on delete cascade
);

View file

@ -53,7 +53,10 @@ void destroyCgroup(const Path & cgroup)
for (auto & pid_s : pids) {
pid_t pid;
if (!string2Int(pid_s, pid)) throw Error("invalid pid '%s'", pid);
if (auto o = string2Int<pid_t>(pid_s))
pid = *o;
else
throw Error("invalid pid '%s'", pid);
if (pidsShown.insert(pid).second) {
try {
auto cmdline = readFile(fmt("/proc/%d/cmdline", pid));

View file

@ -2,21 +2,19 @@
#include "util.hh"
#include "globals.hh"
#if HAVE_SODIUM
#include <sodium.h>
#endif
namespace nix {
static std::pair<std::string, std::string> split(const string & s)
static std::pair<std::string_view, std::string_view> split(std::string_view s)
{
size_t colon = s.find(':');
if (colon == std::string::npos || colon == 0)
return {"", ""};
return {std::string(s, 0, colon), std::string(s, colon + 1)};
return {s.substr(0, colon), s.substr(colon + 1)};
}
Key::Key(const string & s)
Key::Key(std::string_view s)
{
auto ss = split(s);
@ -29,62 +27,57 @@ Key::Key(const string & s)
key = base64Decode(key);
}
SecretKey::SecretKey(const string & s)
std::string Key::to_string() const
{
return name + ":" + base64Encode(key);
}
SecretKey::SecretKey(std::string_view s)
: Key(s)
{
#if HAVE_SODIUM
if (key.size() != crypto_sign_SECRETKEYBYTES)
throw Error("secret key is not valid");
#endif
}
#if !HAVE_SODIUM
[[noreturn]] static void noSodium()
std::string SecretKey::signDetached(std::string_view data) const
{
throw Error("Nix was not compiled with libsodium, required for signed binary cache support");
}
#endif
std::string SecretKey::signDetached(const std::string & data) const
{
#if HAVE_SODIUM
unsigned char sig[crypto_sign_BYTES];
unsigned long long sigLen;
crypto_sign_detached(sig, &sigLen, (unsigned char *) data.data(), data.size(),
(unsigned char *) key.data());
return name + ":" + base64Encode(std::string((char *) sig, sigLen));
#else
noSodium();
#endif
}
PublicKey SecretKey::toPublicKey() const
{
#if HAVE_SODIUM
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
crypto_sign_ed25519_sk_to_pk(pk, (unsigned char *) key.data());
return PublicKey(name, std::string((char *) pk, crypto_sign_PUBLICKEYBYTES));
#else
noSodium();
#endif
}
PublicKey::PublicKey(const string & s)
SecretKey SecretKey::generate(std::string_view name)
{
unsigned char pk[crypto_sign_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_SECRETKEYBYTES];
if (crypto_sign_keypair(pk, sk) != 0)
throw Error("key generation failed");
return SecretKey(name, std::string((char *) sk, crypto_sign_SECRETKEYBYTES));
}
PublicKey::PublicKey(std::string_view s)
: Key(s)
{
#if HAVE_SODIUM
if (key.size() != crypto_sign_PUBLICKEYBYTES)
throw Error("public key is not valid");
#endif
}
bool verifyDetached(const std::string & data, const std::string & sig,
const PublicKeys & publicKeys)
{
#if HAVE_SODIUM
auto ss = split(sig);
auto key = publicKeys.find(ss.first);
auto key = publicKeys.find(std::string(ss.first));
if (key == publicKeys.end()) return false;
auto sig2 = base64Decode(ss.second);
@ -94,9 +87,6 @@ bool verifyDetached(const std::string & data, const std::string & sig,
return crypto_sign_verify_detached((unsigned char *) sig2.data(),
(unsigned char *) data.data(), data.size(),
(unsigned char *) key->second.key.data()) == 0;
#else
noSodium();
#endif
}
PublicKeys getDefaultPublicKeys()

View file

@ -13,32 +13,40 @@ struct Key
/* Construct Key from a string in the format
<name>:<key-in-base64>. */
Key(const std::string & s);
Key(std::string_view s);
std::string to_string() const;
protected:
Key(const std::string & name, const std::string & key)
: name(name), key(key) { }
Key(std::string_view name, std::string && key)
: name(name), key(std::move(key)) { }
};
struct PublicKey;
struct SecretKey : Key
{
SecretKey(const std::string & s);
SecretKey(std::string_view s);
/* Return a detached signature of the given string. */
std::string signDetached(const std::string & s) const;
std::string signDetached(std::string_view s) const;
PublicKey toPublicKey() const;
static SecretKey generate(std::string_view name);
private:
SecretKey(std::string_view name, std::string && key)
: Key(name, std::move(key)) { }
};
struct PublicKey : Key
{
PublicKey(const std::string & data);
PublicKey(std::string_view data);
private:
PublicKey(const std::string & name, const std::string & key)
: Key(name, key) { }
PublicKey(std::string_view name, std::string && key)
: Key(name, std::move(key)) { }
friend struct SecretKey;
};

View file

@ -2,6 +2,7 @@
#include "monitor-fd.hh"
#include "worker-protocol.hh"
#include "store-api.hh"
#include "path-with-outputs.hh"
#include "finally.hh"
#include "affinity.hh"
#include "archive.hh"
@ -153,10 +154,10 @@ struct TunnelSink : Sink
{
Sink & to;
TunnelSink(Sink & to) : to(to) { }
virtual void operator () (const unsigned char * data, size_t len)
void operator () (std::string_view data)
{
to << STDERR_WRITE;
writeString(data, len, to);
writeString(data, to);
}
};
@ -165,7 +166,7 @@ struct TunnelSource : BufferedSource
Source & from;
BufferedSink & to;
TunnelSource(Source & from, BufferedSink & to) : from(from), to(to) { }
size_t readUnbuffered(unsigned char * data, size_t len) override
size_t readUnbuffered(char * data, size_t len) override
{
to << STDERR_READ << len;
to.flush();
@ -215,6 +216,8 @@ struct ClientSettings
for (auto & s : ss)
if (trusted.count(s))
subs.push_back(s);
else if (!hasSuffix(s, "/") && trusted.count(s + "/"))
subs.push_back(s + "/");
else
warn("ignoring untrusted substituter '%s'", s);
res = subs;
@ -231,8 +234,6 @@ struct ClientSettings
settings.set(name, value);
else if (setSubstituters(settings.substituters))
;
else if (setSubstituters(settings.extraSubstituters))
;
else
debug("ignoring the client-specified setting '%s', because it is a restricted setting and you are not a trusted user", name);
} catch (UsageError & e) {
@ -259,6 +260,18 @@ static void writeValidPathInfo(
}
}
static std::vector<DerivedPath> readDerivedPaths(Store & store, unsigned int clientVersion, Source & from)
{
std::vector<DerivedPath> reqs;
if (GET_PROTOCOL_MINOR(clientVersion) >= 29) {
reqs = worker_proto::read(store, from, Phantom<std::vector<DerivedPath>> {});
} else {
for (auto & s : readStrings<Strings>(from))
reqs.push_back(parsePathWithOutputs(store, s).toDerivedPath());
}
return reqs;
}
static void performOp(TunnelLogger * logger, ref<Store> store,
TrustedFlag trusted, RecursiveFlag recursive, unsigned int clientVersion,
Source & from, BufferedSink & to, unsigned int op)
@ -276,8 +289,17 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
case wopQueryValidPaths: {
auto paths = worker_proto::read(*store, from, Phantom<StorePathSet> {});
SubstituteFlag substitute = NoSubstitute;
if (GET_PROTOCOL_MINOR(clientVersion) >= 27) {
substitute = readInt(from) ? Substitute : NoSubstitute;
}
logger->startWork();
auto res = store->queryValidPaths(paths);
if (substitute) {
store->substitutePaths(paths);
}
auto res = store->queryValidPaths(paths, substitute);
logger->stopWork();
worker_proto::write(*store, to, res);
break;
@ -484,9 +506,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}
case wopBuildPaths: {
std::vector<StorePathWithOutputs> drvs;
for (auto & s : readStrings<Strings>(from))
drvs.push_back(store->parsePathWithOutputs(s));
auto drvs = readDerivedPaths(*store, clientVersion, from);
BuildMode mode = bmNormal;
if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
mode = (BuildMode) readInt(from);
@ -566,6 +586,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
auto res = store->buildDerivation(drvPath, drv, buildMode);
logger->stopWork();
to << res.status << res.errorMsg;
if (GET_PROTOCOL_MINOR(clientVersion) >= 29) {
to << res.timesBuilt << res.isNonDeterministic << res.startTime << res.stopTime;
}
if (GET_PROTOCOL_MINOR(clientVersion) >= 28) {
worker_proto::write(*store, to, res.builtOutputs);
}
break;
}
@ -844,9 +870,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
}
case wopQueryMissing: {
std::vector<StorePathWithOutputs> targets;
for (auto & s : readStrings<Strings>(from))
targets.push_back(store->parsePathWithOutputs(s));
auto targets = readDerivedPaths(*store, clientVersion, from);
logger->startWork();
StorePathSet willBuild, willSubstitute, unknown;
uint64_t downloadSize, narSize;
@ -859,6 +883,28 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
break;
}
case wopRegisterDrvOutput: {
logger->startWork();
auto outputId = DrvOutput::parse(readString(from));
auto outputPath = StorePath(readString(from));
auto resolvedDrv = StorePath(readString(from));
store->registerDrvOutput(Realisation{
.id = outputId, .outPath = outputPath});
logger->stopWork();
break;
}
case wopQueryRealisation: {
logger->startWork();
auto outputId = DrvOutput::parse(readString(from));
auto info = store->queryRealisation(outputId);
logger->stopWork();
std::set<StorePath> outPaths;
if (info) outPaths.insert(info->outPath);
worker_proto::write(*store, to, outPaths);
break;
}
default:
throw Error("invalid operation %1%", op);
}

View file

@ -21,6 +21,9 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
[](DerivationOutputCAFloating dof) -> std::optional<StorePath> {
return std::nullopt;
},
[](DerivationOutputDeferred) -> std::optional<StorePath> {
return std::nullopt;
},
}, output);
}
@ -37,6 +40,7 @@ bool derivationIsCA(DerivationType dt) {
case DerivationType::InputAddressed: return false;
case DerivationType::CAFixed: return true;
case DerivationType::CAFloating: return true;
case DerivationType::DeferredInputAddressed: return false;
};
// Since enums can have non-variant values, but making a `default:` would
// disable exhaustiveness warnings.
@ -48,15 +52,28 @@ bool derivationIsFixed(DerivationType dt) {
case DerivationType::InputAddressed: return false;
case DerivationType::CAFixed: return true;
case DerivationType::CAFloating: return false;
case DerivationType::DeferredInputAddressed: return false;
};
assert(false);
}
bool derivationHasKnownOutputPaths(DerivationType dt) {
switch (dt) {
case DerivationType::InputAddressed: return true;
case DerivationType::CAFixed: return true;
case DerivationType::CAFloating: return false;
case DerivationType::DeferredInputAddressed: return false;
};
assert(false);
}
bool derivationIsImpure(DerivationType dt) {
switch (dt) {
case DerivationType::InputAddressed: return false;
case DerivationType::CAFixed: return true;
case DerivationType::CAFloating: return false;
case DerivationType::DeferredInputAddressed: return false;
};
assert(false);
}
@ -180,6 +197,11 @@ static DerivationOutput parseDerivationOutput(const Store & store,
};
}
} else {
if (pathS == "") {
return DerivationOutput {
.output = DerivationOutputDeferred { }
};
}
validatePath(pathS);
return DerivationOutput {
.output = DerivationOutputInputAddressed {
@ -325,6 +347,11 @@ string Derivation::unparse(const Store & store, bool maskOutputs,
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
s += ','; printUnquotedString(s, "");
},
[&](DerivationOutputDeferred) {
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, "");
}
}, i.second.output);
s += ')';
}
@ -389,7 +416,7 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
DerivationType BasicDerivation::type() const
{
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs;
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs, deferredIAOutputs;
std::optional<HashType> floatingHashType;
for (auto & i : outputs) {
std::visit(overloaded {
@ -408,29 +435,34 @@ DerivationType BasicDerivation::type() const
throw Error("All floating outputs must use the same hash type");
}
},
[&](DerivationOutputDeferred _) {
deferredIAOutputs.insert(i.first);
},
}, i.second.output);
}
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()) {
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
throw Error("Must have at least one output");
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty()) {
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
return DerivationType::InputAddressed;
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty()) {
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
if (fixedCAOutputs.size() > 1)
// FIXME: Experimental feature?
throw Error("Only one fixed output is allowed for now");
if (*fixedCAOutputs.begin() != "out")
throw Error("Single fixed output must be named \"out\"");
return DerivationType::CAFixed;
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty()) {
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
return DerivationType::CAFloating;
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
return DerivationType::DeferredInputAddressed;
} else {
throw Error("Can't mix derivation output types");
}
}
DrvHashes drvHashes;
Sync<DrvHashes> drvHashes;
/* pathDerivationModulo and hashDerivationModulo are mutually recursive
*/
@ -438,20 +470,22 @@ DrvHashes drvHashes;
/* Look up the derivation by value and memoize the
`hashDerivationModulo` call.
*/
static const DrvHashModulo & pathDerivationModulo(Store & store, const StorePath & drvPath)
static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & drvPath)
{
auto h = drvHashes.find(drvPath);
if (h == drvHashes.end()) {
assert(store.isValidPath(drvPath));
// Cache it
h = drvHashes.insert_or_assign(
drvPath,
hashDerivationModulo(
store,
store.readDerivation(drvPath),
false)).first;
{
auto hashes = drvHashes.lock();
auto h = hashes->find(drvPath);
if (h != hashes->end()) {
return h->second;
}
}
return h->second;
auto h = hashDerivationModulo(
store,
store.readInvalidDerivation(drvPath),
false);
// Cache it
drvHashes.lock()->insert_or_assign(drvPath, h);
return h;
}
/* See the header for interface details. These are the implementation details.
@ -473,10 +507,9 @@ static const DrvHashModulo & pathDerivationModulo(Store & store, const StorePath
*/
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
{
bool isDeferred = false;
/* Return a fixed hash for fixed-output derivations. */
switch (drv.type()) {
case DerivationType::CAFloating:
throw Error("Regular input-addressed derivations are not yet allowed to depend on CA derivations");
case DerivationType::CAFixed: {
std::map<std::string, Hash> outputHashes;
for (const auto & i : drv.outputs) {
@ -489,8 +522,13 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
}
return outputHashes;
}
case DerivationType::CAFloating:
isDeferred = true;
break;
case DerivationType::InputAddressed:
break;
case DerivationType::DeferredInputAddressed:
break;
}
/* For other derivations, replace the inputs paths with recursive
@ -503,6 +541,10 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
[&](Hash drvHash) {
inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second);
},
[&](DeferredHash deferredHash) {
isDeferred = true;
inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
},
// CA derivation's output hashes
[&](CaOutputHashes outputHashes) {
std::set<std::string> justOut = { "out" };
@ -517,15 +559,34 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
}, res);
}
return hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
if (isDeferred)
return DeferredHash { hash };
else
return hash;
}
std::string StorePathWithOutputs::to_string(const Store & store) const
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv)
{
return outputs.empty()
? store.printStorePath(path)
: store.printStorePath(path) + "!" + concatStringsSep(",", outputs);
std::map<std::string, Hash> res;
std::visit(overloaded {
[&](Hash drvHash) {
for (auto & outputName : drv.outputNames()) {
res.insert({outputName, drvHash});
}
},
[&](DeferredHash deferredHash) {
for (auto & outputName : drv.outputNames()) {
res.insert({outputName, deferredHash.hash});
}
},
[&](CaOutputHashes outputHashes) {
res = outputHashes;
},
}, hashDerivationModulo(store, drv, true));
return res;
}
@ -620,6 +681,11 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
<< "";
},
[&](DerivationOutputDeferred) {
out << ""
<< ""
<< "";
},
}, i.second.output);
}
worker_proto::write(store, out, drv.inputSrcs);
@ -645,7 +711,6 @@ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath
}
// N.B. Outputs are left unchanged
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) {
debug("Rewriting the derivation");
@ -666,11 +731,23 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
newEnv.emplace(envName, envValue);
}
drv.env = newEnv;
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
for (auto & [outputName, output] : drv.outputs) {
if (std::holds_alternative<DerivationOutputDeferred>(output.output)) {
Hash h = std::get<Hash>(hashModulo);
auto outPath = store.makeOutputPath(outputName, h, drv.name);
drv.env[outputName] = store.printStorePath(outPath);
output = DerivationOutput {
.output = DerivationOutputInputAddressed {
.path = std::move(outPath),
},
};
}
}
}
Sync<DrvPathResolutions> drvPathResolutions;
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
BasicDerivation resolved { *this };
@ -682,8 +759,13 @@ std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
StringSet newOutputNames;
for (auto & outputName : input.second) {
auto actualPathOpt = inputDrvOutputs.at(outputName);
if (!actualPathOpt)
if (!actualPathOpt) {
warn("output %s of input %s missing, aborting the resolving",
outputName,
store.printStorePath(input.first)
);
return std::nullopt;
}
auto actualPath = *actualPathOpt;
inputRewrites.emplace(
downstreamPlaceholder(store, input.first, outputName),

View file

@ -18,8 +18,6 @@ namespace nix {
/* The traditional non-fixed-output derivation type. */
struct DerivationOutputInputAddressed
{
/* Will need to become `std::optional<StorePath>` once input-addressed
derivations are allowed to depend on cont-addressed derivations */
StorePath path;
};
@ -41,14 +39,20 @@ struct DerivationOutputCAFloating
HashType hashType;
};
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
* known atm
*/
struct DerivationOutputDeferred {};
struct DerivationOutput
{
std::variant<
DerivationOutputInputAddressed,
DerivationOutputCAFixed,
DerivationOutputCAFloating
DerivationOutputCAFloating,
DerivationOutputDeferred
> output;
std::optional<HashType> hashAlgoOpt(const Store & store) const;
/* Note, when you use this function you should make sure that you're passing
the right derivation name. When in doubt, you should use the safer
interface provided by BasicDerivation::outputsAndOptPaths */
@ -72,6 +76,7 @@ typedef std::map<string, string> StringPairs;
enum struct DerivationType : uint8_t {
InputAddressed,
DeferredInputAddressed,
CAFixed,
CAFloating,
};
@ -89,6 +94,11 @@ bool derivationIsFixed(DerivationType);
derivation is controlled separately. Never true for non-CA derivations. */
bool derivationIsImpure(DerivationType);
/* Does the derivation knows its own output paths?
* Only true when there's no floating-ca derivation involved in the closure.
*/
bool derivationHasKnownOutputPaths(DerivationType);
struct BasicDerivation
{
DerivationOutputs outputs; /* keyed on symbolic IDs */
@ -167,9 +177,12 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
// whose output hashes are always known since they are fixed up-front.
typedef std::map<std::string, Hash> CaOutputHashes;
struct DeferredHash { Hash hash; };
typedef std::variant<
Hash, // regular DRV normalized hash
CaOutputHashes
CaOutputHashes, // Fixed-output derivation hashes
DeferredHash // Deferred hashes for floating outputs drvs and their dependencies
> DrvHashModulo;
/* Returns hashes with the details of fixed-output subderivations
@ -197,20 +210,17 @@ typedef std::variant<
*/
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
/*
Return a map associating each output to a hash that uniquely identifies its
derivation (modulo the self-references).
*/
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv);
/* Memoisation of hashDerivationModulo(). */
typedef std::map<StorePath, DrvHashModulo> DrvHashes;
extern DrvHashes drvHashes; // FIXME: global, not thread-safe
/* Memoisation of `readDerivation(..).resove()`. */
typedef std::map<
StorePath,
std::optional<StorePath>
> DrvPathResolutions;
// FIXME: global, though at least thread-safe.
// FIXME: arguably overlaps with hashDerivationModulo memo table.
extern Sync<DrvPathResolutions> drvPathResolutions;
extern Sync<DrvHashes> drvHashes;
bool wantOutput(const string & output, const std::set<string> & wanted);

View file

@ -0,0 +1,77 @@
#include "derived-path.hh"
#include "store-api.hh"
#include <nlohmann/json.hpp>
namespace nix {
nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
nlohmann::json res;
res["path"] = store->printStorePath(path);
return res;
}
nlohmann::json DerivedPathWithHints::Built::toJSON(ref<Store> store) const {
nlohmann::json res;
res["drvPath"] = store->printStorePath(drvPath);
for (const auto& [output, path] : outputs) {
res["outputs"][output] = path ? store->printStorePath(*path) : "";
}
return res;
}
nlohmann::json derivedPathsWithHintsToJSON(const DerivedPathsWithHints & buildables, ref<Store> store) {
auto res = nlohmann::json::array();
for (const DerivedPathWithHints & buildable : buildables) {
std::visit([&res, store](const auto & buildable) {
res.push_back(buildable.toJSON(store));
}, buildable.raw());
}
return res;
}
std::string DerivedPath::Opaque::to_string(const Store & store) const {
return store.printStorePath(path);
}
std::string DerivedPath::Built::to_string(const Store & store) const {
return store.printStorePath(drvPath)
+ "!"
+ (outputs.empty() ? std::string { "*" } : concatStringsSep(",", outputs));
}
std::string DerivedPath::to_string(const Store & store) const
{
return std::visit(
[&](const auto & req) { return req.to_string(store); },
this->raw());
}
DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_view s)
{
return {store.parseStorePath(s)};
}
DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view s)
{
size_t n = s.find("!");
assert(n != s.npos);
auto drvPath = store.parseStorePath(s.substr(0, n));
auto outputsS = s.substr(n + 1);
std::set<string> outputs;
if (outputsS != "*")
outputs = tokenizeString<std::set<string>>(outputsS);
return {drvPath, outputs};
}
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
{
size_t n = s.find("!");
return n == s.npos
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
: (DerivedPath) DerivedPath::Built::parse(store, s);
}
}

View file

@ -0,0 +1,129 @@
#pragma once
#include "util.hh"
#include "path.hh"
#include <optional>
#include <nlohmann/json_fwd.hpp>
namespace nix {
class Store;
/**
* An opaque derived path.
*
* Opaque derived paths are just store paths, and fully evaluated. They
* cannot be simplified further. Since they are opaque, they cannot be
* built, but they can fetched.
*/
struct DerivedPathOpaque {
StorePath path;
nlohmann::json toJSON(ref<Store> store) const;
std::string to_string(const Store & store) const;
static DerivedPathOpaque parse(const Store & store, std::string_view);
};
/**
* A derived path that is built from a derivation
*
* Built derived paths are pair of a derivation and some output names.
* They are evaluated by building the derivation, and then replacing the
* output names with the resulting outputs.
*
* Note that does mean a derived store paths evaluates to multiple
* opaque paths, which is sort of icky as expressions are supposed to
* evaluate to single values. Perhaps this should have just a single
* output name.
*/
struct DerivedPathBuilt {
StorePath drvPath;
std::set<std::string> outputs;
std::string to_string(const Store & store) const;
static DerivedPathBuilt parse(const Store & store, std::string_view);
};
using _DerivedPathRaw = std::variant<
DerivedPathOpaque,
DerivedPathBuilt
>;
/**
* A "derived path" is a very simple sort of expression that evaluates
* to (concrete) store path. It is either:
*
* - opaque, in which case it is just a concrete store path with
* possibly no known derivation
*
* - built, in which case it is a pair of a derivation path and an
* output name.
*/
struct DerivedPath : _DerivedPathRaw {
using Raw = _DerivedPathRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = DerivedPathBuilt;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
std::string to_string(const Store & store) const;
static DerivedPath parse(const Store & store, std::string_view);
};
/**
* A built derived path with hints in the form of optional concrete output paths.
*
* See 'DerivedPathWithHints' for more an explanation.
*/
struct DerivedPathWithHintsBuilt {
StorePath drvPath;
std::map<std::string, std::optional<StorePath>> outputs;
nlohmann::json toJSON(ref<Store> store) const;
static DerivedPathWithHintsBuilt parse(const Store & store, std::string_view);
};
using _DerivedPathWithHintsRaw = std::variant<
DerivedPath::Opaque,
DerivedPathWithHintsBuilt
>;
/**
* A derived path with hints in the form of optional concrete output paths in the built case.
*
* This type is currently just used by the CLI. The paths are filled in
* during evaluation for derivations that know what paths they will
* produce in advanced, i.e. input-addressed or fixed-output content
* addressed derivations.
*
* That isn't very good, because it puts floating content-addressed
* derivations "at a disadvantage". It would be better to never rely on
* the output path of unbuilt derivations, and exclusively use the
* realizations types to work with built derivations' concrete output
* paths.
*/
// FIXME Stop using and delete this, or if that is not possible move out of libstore to libcmd.
struct DerivedPathWithHints : _DerivedPathWithHintsRaw {
using Raw = _DerivedPathWithHintsRaw;
using Raw::Raw;
using Opaque = DerivedPathOpaque;
using Built = DerivedPathWithHintsBuilt;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
};
typedef std::vector<DerivedPathWithHints> DerivedPathsWithHints;
nlohmann::json derivedPathsWithHintsToJSON(const DerivedPathsWithHints & buildables, ref<Store> store);
}

View file

@ -9,7 +9,7 @@ struct DummyStoreConfig : virtual StoreConfig {
const std::string name() override { return "Dummy Store"; }
};
struct DummyStore : public Store, public virtual DummyStoreConfig
struct DummyStore : public virtual DummyStoreConfig, public virtual Store
{
DummyStore(const std::string scheme, const std::string uri, const Params & params)
: DummyStore(params)
@ -17,6 +17,7 @@ struct DummyStore : public Store, public virtual DummyStoreConfig
DummyStore(const Params & params)
: StoreConfig(params)
, DummyStoreConfig(params)
, Store(params)
{ }
@ -54,12 +55,8 @@ struct DummyStore : public Store, public virtual DummyStoreConfig
void narFromPath(const StorePath & path, Sink & sink) override
{ unsupported("narFromPath"); }
void ensurePath(const StorePath & path) override
{ unsupported("ensurePath"); }
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode) override
{ unsupported("buildDerivation"); }
std::optional<const Realisation> queryRealisation(const DrvOutput&) override
{ unsupported("queryRealisation"); }
};
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;

View file

@ -95,18 +95,18 @@ struct curlFileTransfer : public FileTransfer
fmt(request.data ? "uploading '%s'" : "downloading '%s'", request.uri),
{request.uri}, request.parentAct)
, callback(std::move(callback))
, finalSink([this](const unsigned char * data, size_t len) {
, finalSink([this](std::string_view data) {
if (this->request.dataCallback) {
auto httpStatus = getHTTPStatus();
/* Only write data to the sink if this is a
successful response. */
if (successfulStatuses.count(httpStatus)) {
writtenToSink += len;
this->request.dataCallback((char *) data, len);
writtenToSink += data.size();
this->request.dataCallback(data);
}
} else
this->result.data->append((char *) data, len);
this->result.data->append(data);
})
{
if (!request.expectedETag.empty())
@ -171,8 +171,8 @@ struct curlFileTransfer : public FileTransfer
}
if (errorSink)
(*errorSink)((unsigned char *) contents, realSize);
(*decompressionSink)((unsigned char *) contents, realSize);
(*errorSink)({(char *) contents, realSize});
(*decompressionSink)({(char *) contents, realSize});
return realSize;
} catch (...) {
@ -375,6 +375,13 @@ struct curlFileTransfer : public FileTransfer
else if (code == CURLE_OK && successfulStatuses.count(httpStatus))
{
result.cached = httpStatus == 304;
// In 2021, GitHub responds to If-None-Match with 304,
// but omits ETag. We just use the If-None-Match etag
// since 304 implies they are the same.
if (httpStatus == 304 && result.etag == "")
result.etag = request.expectedETag;
act.progress(result.bodySize, result.bodySize);
done = true;
callback(std::move(result));
@ -632,11 +639,7 @@ struct curlFileTransfer : public FileTransfer
workerThreadMain();
} catch (nix::Interrupted & e) {
} catch (std::exception & e) {
logError({
.name = "File transfer",
.hint = hintfmt("unexpected error in download thread: %s",
e.what())
});
printError("unexpected error in download thread: %s", e.what());
}
{
@ -776,7 +779,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
state->request.notify_one();
});
request.dataCallback = [_state](char * buf, size_t len) {
request.dataCallback = [_state](std::string_view data) {
auto state(_state->lock());
@ -794,7 +797,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
/* Append data to the buffer and wake up the calling
thread. */
state->data.append(buf, len);
state->data.append(data);
state->avail.notify_one();
};
@ -840,7 +843,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
if it's blocked on a full buffer. We don't hold the state
lock while doing this to prevent blocking the download
thread if sink() takes a long time. */
sink((unsigned char *) chunk.data(), chunk.size());
sink(chunk);
}
}
@ -852,11 +855,10 @@ FileTransferError::FileTransferError(FileTransfer::Error error, std::shared_ptr<
// FIXME: Due to https://github.com/NixOS/nix/issues/3841 we don't know how
// to print different messages for different verbosity levels. For now
// we add some heuristics for detecting when we want to show the response.
if (response && (response->size() < 1024 || response->find("<html>") != string::npos)) {
err.hint = hintfmt("%1%\n\nresponse body:\n\n%2%", normaltxt(hf.str()), *response);
} else {
err.hint = hf;
}
if (response && (response->size() < 1024 || response->find("<html>") != string::npos))
err.msg = hintfmt("%1%\n\nresponse body:\n\n%2%", normaltxt(hf.str()), chomp(*response));
else
err.msg = hf;
}
bool isUri(const string & s)

View file

@ -61,9 +61,9 @@ struct FileTransferRequest
bool decompress = true;
std::shared_ptr<std::string> data;
std::string mimeType;
std::function<void(char *, size_t)> dataCallback;
std::function<void(std::string_view data)> dataCallback;
FileTransferRequest(const std::string & uri)
FileTransferRequest(std::string_view uri)
: uri(uri), parentAct(getCurActivity()) { }
std::string verb()

View file

@ -25,7 +25,14 @@ public:
virtual StringSet readDirectory(const Path & path) = 0;
virtual std::string readFile(const Path & path) = 0;
/**
* Read a file inside the store.
*
* If `requireValidPath` is set to `true` (the default), the path must be
* inside a valid store path, otherwise it just needs to be physically
* present (but not necessarily properly registered)
*/
virtual std::string readFile(const Path & path, bool requireValidPath = true) = 0;
virtual std::string readLink(const Path & path) = 0;
};

View file

@ -3,6 +3,7 @@
#include "archive.hh"
#include "args.hh"
#include "abstract-setting-to-json.hh"
#include "compute-levels.hh"
#include <algorithm>
#include <map>
@ -80,12 +81,18 @@ void loadConfFile()
/* We only want to send overrides to the daemon, i.e. stuff from
~/.nix/nix.conf or the command line. */
globalConfig.resetOverriden();
globalConfig.resetOverridden();
auto files = settings.nixUserConfFiles;
for (auto file = files.rbegin(); file != files.rend(); file++) {
globalConfig.applyConfigFile(*file);
}
auto nixConfEnv = getEnv("NIX_CONFIG");
if (nixConfEnv.has_value()) {
globalConfig.applyConfig(nixConfEnv.value(), "NIX_CONFIG");
}
}
std::vector<Path> getUserConfigFiles()
@ -125,16 +132,48 @@ StringSet Settings::getDefaultSystemFeatures()
return features;
}
StringSet Settings::getDefaultExtraPlatforms()
{
StringSet extraPlatforms;
if (std::string{SYSTEM} == "x86_64-linux" && !isWSL1())
extraPlatforms.insert("i686-linux");
#if __linux__
StringSet levels = computeLevels();
for (auto iter = levels.begin(); iter != levels.end(); ++iter)
extraPlatforms.insert(*iter + "-linux");
#elif __APPLE__
// Rosetta 2 emulation layer can run x86_64 binaries on aarch64
// machines. Note that we cant force processes from executing
// x86_64 in aarch64 environments or vice versa since they can
// always exec with their own binary preferences.
if (pathExists("/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist")) {
if (std::string{SYSTEM} == "x86_64-darwin")
extraPlatforms.insert("aarch64-darwin");
else if (std::string{SYSTEM} == "aarch64-darwin")
extraPlatforms.insert("x86_64-darwin");
}
#endif
return extraPlatforms;
}
bool Settings::isExperimentalFeatureEnabled(const std::string & name)
{
auto & f = experimentalFeatures.get();
return std::find(f.begin(), f.end(), name) != f.end();
}
MissingExperimentalFeature::MissingExperimentalFeature(std::string feature)
: Error("experimental Nix feature '%1%' is disabled; use '--experimental-features %1%' to override", feature)
, missingFeature(feature)
{}
void Settings::requireExperimentalFeature(const std::string & name)
{
if (!isExperimentalFeatureEnabled(name))
throw Error("experimental Nix feature '%1%' is disabled; use '--experimental-features %1%' to override", name);
throw MissingExperimentalFeature(name);
}
bool Settings::isWSL1()
@ -154,7 +193,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
{SandboxMode::smDisabled, false},
});
template<> void BaseSetting<SandboxMode>::set(const std::string & str)
template<> void BaseSetting<SandboxMode>::set(const std::string & str, bool append)
{
if (str == "true") value = smEnabled;
else if (str == "relaxed") value = smRelaxed;
@ -162,6 +201,11 @@ template<> void BaseSetting<SandboxMode>::set(const std::string & str)
else throw UsageError("option '%s' has invalid value '%s'", name, str);
}
template<> bool BaseSetting<SandboxMode>::isAppendable()
{
return false;
}
template<> std::string BaseSetting<SandboxMode>::to_string() const
{
if (value == smEnabled) return "true";
@ -192,16 +236,29 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
});
}
void MaxBuildJobsSetting::set(const std::string & str)
void MaxBuildJobsSetting::set(const std::string & str, bool append)
{
if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency());
else if (!string2Int(str, value))
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
else {
if (auto n = string2Int<decltype(value)>(str))
value = *n;
else
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
}
}
void PluginFilesSetting::set(const std::string & str, bool append)
{
if (pluginsLoaded)
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
BaseSetting<Paths>::set(str, append);
}
void initPlugins()
{
assert(!settings.pluginFiles.pluginsLoaded);
for (const auto & pluginFile : settings.pluginFiles.get()) {
Paths pluginFiles;
try {
@ -227,6 +284,9 @@ void initPlugins()
unknown settings. */
globalConfig.reapplyUnknownSettings();
globalConfig.warnUnknownSettings();
/* Tell the user if they try to set plugin-files after we've already loaded */
settings.pluginFiles.pluginsLoaded = true;
}
}

View file

@ -25,7 +25,33 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
options->addSetting(this);
}
void set(const std::string & str) override;
void set(const std::string & str, bool append = false) override;
};
struct PluginFilesSetting : public BaseSetting<Paths>
{
bool pluginsLoaded = false;
PluginFilesSetting(Config * options,
const Paths & def,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {})
: BaseSetting<Paths>(def, name, description, aliases)
{
options->addSetting(this);
}
void set(const std::string & str, bool append = false) override;
};
class MissingExperimentalFeature: public Error
{
public:
std::string missingFeature;
MissingExperimentalFeature(std::string feature);
virtual const char* sname() const override { return "MissingExperimentalFeature"; }
};
class Settings : public Config {
@ -34,6 +60,8 @@ class Settings : public Config {
StringSet getDefaultSystemFeatures();
StringSet getDefaultExtraPlatforms();
bool isWSL1();
public:
@ -178,7 +206,10 @@ public:
Setting<std::string> builders{
this, "@" + nixConfDir + "/machines", "builders",
"A semicolon-separated list of build machines, in the format of `nix.machines`."};
R"(
A semicolon-separated list of build machines.
For the exact format and examples, see [the manual chapter on remote builds](../advanced-topics/distributed-builds.md)
)"};
Setting<bool> buildersUseSubstitutes{
this, false, "builders-use-substitutes",
@ -426,14 +457,6 @@ public:
Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."};
Setting<PathSet> extraSandboxPaths{
this, {}, "extra-sandbox-paths",
R"(
A list of additional paths appended to `sandbox-paths`. Useful if
you want to extend its default value.
)",
{"build-extra-chroot-dirs", "build-extra-sandbox-paths"}};
Setting<size_t> buildRepeat{
this, 0, "repeat",
R"(
@ -566,7 +589,7 @@ public:
Setting<StringSet> extraPlatforms{
this,
std::string{SYSTEM} == "x86_64-linux" && !isWSL1() ? StringSet{"i686-linux"} : StringSet{},
getDefaultExtraPlatforms(),
"extra-platforms",
R"(
Platforms other than the native one which this machine is capable of
@ -604,7 +627,7 @@ public:
Setting<Strings> substituters{
this,
nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(),
Strings{"https://cache.nixos.org/"},
"substituters",
R"(
A list of URLs of substituters, separated by whitespace. The default
@ -612,17 +635,6 @@ public:
)",
{"binary-caches"}};
// FIXME: provide a way to add to option values.
Setting<Strings> extraSubstituters{
this, {}, "extra-substituters",
R"(
Additional binary caches appended to those specified in
`substituters`. When used by unprivileged users, untrusted
substituters (i.e. those not listed in `trusted-substituters`) are
silently ignored.
)",
{"extra-binary-caches"}};
Setting<StringSet> trustedSubstituters{
this, {}, "trusted-substituters",
R"(
@ -849,7 +861,7 @@ public:
Setting<uint64_t> minFreeCheckInterval{this, 5, "min-free-check-interval",
"Number of seconds between checking free disk space."};
Setting<Paths> pluginFiles{
PluginFilesSetting pluginFiles{
this, {}, "plugin-files",
R"(
A list of plugin files to be loaded by Nix. Each of these files will
@ -861,6 +873,9 @@ public:
command, and RegisterSetting to add new nix config settings. See the
constructors for those types for more details.
Warning! These APIs are inherently unstable and may change from
release to release.
Since these files are loaded into the same address space as Nix
itself, they must be DSOs compatible with the instance of Nix
running at the time (i.e. compiled against the same headers, not
@ -899,7 +914,7 @@ public:
Example `~/.config/nix/nix.conf`:
```
access-tokens = "github.com=23ac...b289 gitlab.mycompany.com=PAT:A123Bp_Cd..EfG gitlab.com=OAuth2:1jklw3jk"
access-tokens = github.com=23ac...b289 gitlab.mycompany.com=PAT:A123Bp_Cd..EfG gitlab.com=OAuth2:1jklw3jk
```
Example `~/code/flake.nix`:

View file

@ -15,7 +15,7 @@ struct HttpBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
const std::string name() override { return "Http Binary Cache Store"; }
};
class HttpBinaryCacheStore : public BinaryCacheStore, public HttpBinaryCacheStoreConfig
class HttpBinaryCacheStore : public virtual HttpBinaryCacheStoreConfig, public virtual BinaryCacheStore
{
private:
@ -36,6 +36,9 @@ public:
const Path & _cacheUri,
const Params & params)
: StoreConfig(params)
, BinaryCacheStoreConfig(params)
, HttpBinaryCacheStoreConfig(params)
, Store(params)
, BinaryCacheStore(params)
, cacheUri(scheme + "://" + _cacheUri)
{

View file

@ -3,6 +3,7 @@
#include "remote-store.hh"
#include "serve-protocol.hh"
#include "store-api.hh"
#include "path-with-outputs.hh"
#include "worker-protocol.hh"
#include "ssh.hh"
#include "derivations.hh"
@ -15,6 +16,7 @@ struct LegacySSHStoreConfig : virtual StoreConfig
using StoreConfig::StoreConfig;
const Setting<int> maxConnections{(StoreConfig*) this, 1, "max-connections", "maximum number of concurrent SSH connections"};
const Setting<Path> sshKey{(StoreConfig*) this, "", "ssh-key", "path to an SSH private key"};
const Setting<std::string> sshPublicHostKey{(StoreConfig*) this, "", "base64-ssh-public-host-key", "The public half of the host's SSH key"};
const Setting<bool> compress{(StoreConfig*) this, false, "compress", "whether to compress the connection"};
const Setting<Path> remoteProgram{(StoreConfig*) this, "nix-store", "remote-program", "path to the nix-store executable on the remote system"};
const Setting<std::string> remoteStore{(StoreConfig*) this, "", "remote-store", "URI of the store on the remote system"};
@ -22,7 +24,7 @@ struct LegacySSHStoreConfig : virtual StoreConfig
const std::string name() override { return "Legacy SSH Store"; }
};
struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Store
{
// Hack for getting remote build log output.
// Intentionally not in `LegacySSHStoreConfig` so that it doesn't appear in
@ -48,6 +50,7 @@ struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
LegacySSHStore(const string & scheme, const string & host, const Params & params)
: StoreConfig(params)
, LegacySSHStoreConfig(params)
, Store(params)
, host(host)
, connections(make_ref<Pool<Connection>>(
@ -58,6 +61,7 @@ struct LegacySSHStore : public Store, public virtual LegacySSHStoreConfig
, master(
host,
sshKey,
sshPublicHostKey,
// Use SSH master only if using more than 1 connection.
connections->capacity() > 1,
compress,
@ -257,18 +261,29 @@ public:
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
conn->from >> status.timesBuilt >> status.isNonDeterministic >> status.startTime >> status.stopTime;
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 6) {
status.builtOutputs = worker_proto::read(*this, conn->from, Phantom<DrvOutputs> {});
}
return status;
}
void buildPaths(const std::vector<StorePathWithOutputs> & drvPaths, BuildMode buildMode) override
void buildPaths(const std::vector<DerivedPath> & drvPaths, BuildMode buildMode) override
{
auto conn(connections->get());
conn->to << cmdBuildPaths;
Strings ss;
for (auto & p : drvPaths)
ss.push_back(p.to_string(*this));
for (auto & p : drvPaths) {
auto sOrDrvPath = StorePathWithOutputs::tryFromDerivedPath(p);
std::visit(overloaded {
[&](StorePathWithOutputs s) {
ss.push_back(s.to_string(*this));
},
[&](StorePath drvPath) {
throw Error("wanted to fetch '%s' but the legacy ssh protocol doesn't support merely substituting drv files via the build paths command. It would build them instead. Try using ssh-ng://", printStorePath(drvPath));
},
}, sOrDrvPath);
}
conn->to << ss;
putBuildSettings(*conn);
@ -333,6 +348,10 @@ public:
auto conn(connections->get());
return conn->remoteVersion;
}
std::optional<const Realisation> queryRealisation(const DrvOutput&) override
// TODO: Implement
{ unsupported("queryRealisation"); }
};
static RegisterStoreImplementation<LegacySSHStore, LegacySSHStoreConfig> regLegacySSHStore;

View file

@ -2,6 +2,8 @@
#include "globals.hh"
#include "nar-info-disk-cache.hh"
#include <atomic>
namespace nix {
struct LocalBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
@ -11,7 +13,7 @@ struct LocalBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
const std::string name() override { return "Local Binary Cache Store"; }
};
class LocalBinaryCacheStore : public BinaryCacheStore, public virtual LocalBinaryCacheStoreConfig
class LocalBinaryCacheStore : public virtual LocalBinaryCacheStoreConfig, public virtual BinaryCacheStore
{
private:
@ -24,6 +26,9 @@ public:
const Path & binaryCacheDir,
const Params & params)
: StoreConfig(params)
, BinaryCacheStoreConfig(params)
, LocalBinaryCacheStoreConfig(params)
, Store(params)
, BinaryCacheStore(params)
, binaryCacheDir(binaryCacheDir)
{
@ -47,7 +52,8 @@ protected:
const std::string & mimeType) override
{
auto path2 = binaryCacheDir + "/" + path;
Path tmp = path2 + ".tmp." + std::to_string(getpid());
static std::atomic<int> counter{0};
Path tmp = fmt("%s.tmp.%d.%d", path2, getpid(), ++counter);
AutoDelete del(tmp, false);
StreamToSourceAdapter source(istream);
writeFile(tmp, source);
@ -87,6 +93,7 @@ protected:
void LocalBinaryCacheStore::init()
{
createDirs(binaryCacheDir + "/nar");
createDirs(binaryCacheDir + realisationsPrefix);
if (writeDebugInfo)
createDirs(binaryCacheDir + "/debuginfo");
BinaryCacheStore::init();

View file

@ -19,10 +19,10 @@ struct LocalStoreAccessor : public FSAccessor
LocalStoreAccessor(ref<LocalFSStore> store) : store(store) { }
Path toRealPath(const Path & path)
Path toRealPath(const Path & path, bool requireValidPath = true)
{
auto storePath = store->toStorePath(path).first;
if (!store->isValidPath(storePath))
if (requireValidPath && !store->isValidPath(storePath))
throw InvalidPath("path '%1%' is not a valid store path", store->printStorePath(storePath));
return store->getRealStoreDir() + std::string(path, store->storeDir.size());
}
@ -61,9 +61,9 @@ struct LocalStoreAccessor : public FSAccessor
return res;
}
std::string readFile(const Path & path) override
std::string readFile(const Path & path, bool requireValidPath = true) override
{
return nix::readFile(toRealPath(path));
return nix::readFile(toRealPath(path, requireValidPath));
}
std::string readLink(const Path & path) override

View file

@ -20,7 +20,7 @@ struct LocalFSStoreConfig : virtual StoreConfig
"log", "directory where Nix will store state"};
};
class LocalFSStore : public virtual Store, public virtual LocalFSStoreConfig
class LocalFSStore : public virtual LocalFSStoreConfig, public virtual Store
{
public:

View file

@ -7,6 +7,7 @@
#include "nar-info.hh"
#include "references.hh"
#include "callback.hh"
#include "topo-sort.hh"
#include <iostream>
#include <algorithm>
@ -41,9 +42,68 @@
namespace nix {
struct LocalStore::State::Stmts {
/* Some precompiled SQLite statements. */
SQLiteStmt RegisterValidPath;
SQLiteStmt UpdatePathInfo;
SQLiteStmt AddReference;
SQLiteStmt QueryPathInfo;
SQLiteStmt QueryReferences;
SQLiteStmt QueryReferrers;
SQLiteStmt InvalidatePath;
SQLiteStmt AddDerivationOutput;
SQLiteStmt RegisterRealisedOutput;
SQLiteStmt QueryValidDerivers;
SQLiteStmt QueryDerivationOutputs;
SQLiteStmt QueryRealisedOutput;
SQLiteStmt QueryAllRealisedOutputs;
SQLiteStmt QueryPathFromHashPart;
SQLiteStmt QueryValidPaths;
};
int getSchema(Path schemaPath)
{
int curSchema = 0;
if (pathExists(schemaPath)) {
string s = readFile(schemaPath);
auto n = string2Int<int>(s);
if (!n)
throw Error("'%1%' is corrupt", schemaPath);
curSchema = *n;
}
return curSchema;
}
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
{
const int nixCASchemaVersion = 1;
int curCASchema = getSchema(schemaPath);
if (curCASchema != nixCASchemaVersion) {
if (curCASchema > nixCASchemaVersion) {
throw Error("current Nix store ca-schema is version %1%, but I only support %2%",
curCASchema, nixCASchemaVersion);
}
if (!lockFile(lockFd.get(), ltWrite, false)) {
printInfo("waiting for exclusive access to the Nix store for ca drvs...");
lockFile(lockFd.get(), ltWrite, true);
}
if (curCASchema == 0) {
static const char schema[] =
#include "ca-specific-schema.sql.gen.hh"
;
db.exec(schema);
}
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
lockFile(lockFd.get(), ltRead, true);
}
}
LocalStore::LocalStore(const Params & params)
: StoreConfig(params)
, LocalFSStoreConfig(params)
, LocalStoreConfig(params)
, Store(params)
, LocalFSStore(params)
, realStoreDir_{this, false, rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
@ -59,6 +119,7 @@ LocalStore::LocalStore(const Params & params)
, locksHeld(tokenizeString<PathSet>(getEnv("NIX_HELD_LOCKS").value_or("")))
{
auto state(_state.lock());
state->stmts = std::make_unique<State::Stmts>();
/* Create missing state directories if they don't already exist. */
createDirs(realStoreDir);
@ -89,12 +150,7 @@ LocalStore::LocalStore(const Params & params)
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
if (!gr)
logError({
.name = "'build-users-group' not found",
.hint = hintfmt(
"warning: the group '%1%' specified in 'build-users-group' does not exist",
settings.buildUsersGroup)
});
printError("warning: the group '%1%' specified in 'build-users-group' does not exist", settings.buildUsersGroup);
else {
struct stat st;
if (stat(realStoreDir.c_str(), &st))
@ -221,32 +277,58 @@ LocalStore::LocalStore(const Params & params)
else openDB(*state, false);
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
}
/* Prepare SQL statements. */
state->stmtRegisterValidPath.create(state->db,
state->stmts->RegisterValidPath.create(state->db,
"insert into ValidPaths (path, hash, registrationTime, deriver, narSize, ultimate, sigs, ca) values (?, ?, ?, ?, ?, ?, ?, ?);");
state->stmtUpdatePathInfo.create(state->db,
state->stmts->UpdatePathInfo.create(state->db,
"update ValidPaths set narSize = ?, hash = ?, ultimate = ?, sigs = ?, ca = ? where path = ?;");
state->stmtAddReference.create(state->db,
state->stmts->AddReference.create(state->db,
"insert or replace into Refs (referrer, reference) values (?, ?);");
state->stmtQueryPathInfo.create(state->db,
state->stmts->QueryPathInfo.create(state->db,
"select id, hash, registrationTime, deriver, narSize, ultimate, sigs, ca from ValidPaths where path = ?;");
state->stmtQueryReferences.create(state->db,
state->stmts->QueryReferences.create(state->db,
"select path from Refs join ValidPaths on reference = id where referrer = ?;");
state->stmtQueryReferrers.create(state->db,
state->stmts->QueryReferrers.create(state->db,
"select path from Refs join ValidPaths on referrer = id where reference = (select id from ValidPaths where path = ?);");
state->stmtInvalidatePath.create(state->db,
state->stmts->InvalidatePath.create(state->db,
"delete from ValidPaths where path = ?;");
state->stmtAddDerivationOutput.create(state->db,
state->stmts->AddDerivationOutput.create(state->db,
"insert or replace into DerivationOutputs (drv, id, path) values (?, ?, ?);");
state->stmtQueryValidDerivers.create(state->db,
state->stmts->QueryValidDerivers.create(state->db,
"select v.id, v.path from DerivationOutputs d join ValidPaths v on d.drv = v.id where d.path = ?;");
state->stmtQueryDerivationOutputs.create(state->db,
state->stmts->QueryDerivationOutputs.create(state->db,
"select id, path from DerivationOutputs where drv = ?;");
// Use "path >= ?" with limit 1 rather than "path like '?%'" to
// ensure efficient lookup.
state->stmtQueryPathFromHashPart.create(state->db,
state->stmts->QueryPathFromHashPart.create(state->db,
"select path from ValidPaths where path >= ? limit 1;");
state->stmtQueryValidPaths.create(state->db, "select path from ValidPaths");
state->stmts->QueryValidPaths.create(state->db, "select path from ValidPaths");
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
state->stmts->RegisterRealisedOutput.create(state->db,
R"(
insert or replace into Realisations (drvPath, outputName, outputPath, signatures)
values (?, ?, (select id from ValidPaths where path = ?), ?)
;
)");
state->stmts->QueryRealisedOutput.create(state->db,
R"(
select Output.path, Realisations.signatures from Realisations
inner join ValidPaths as Output on Output.id = Realisations.outputPath
where drvPath = ? and outputName = ?
;
)");
state->stmts->QueryAllRealisedOutputs.create(state->db,
R"(
select outputName, Output.path from Realisations
inner join ValidPaths as Output on Output.id = Realisations.outputPath
where drvPath = ?
;
)");
}
}
@ -284,16 +366,7 @@ std::string LocalStore::getUri()
int LocalStore::getSchema()
{
int curSchema = 0;
if (pathExists(schemaPath)) {
string s = readFile(schemaPath);
if (!string2Int(s, curSchema))
throw Error("'%1%' is corrupt", schemaPath);
}
return curSchema;
}
{ return nix::getSchema(schemaPath); }
void LocalStore::openDB(State & state, bool create)
{
@ -580,21 +653,39 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
[&](DerivationOutputCAFloating _) {
/* Nothing to check */
},
[&](DerivationOutputDeferred) {
},
}, i.second.output);
}
}
void LocalStore::linkDeriverToPath(const StorePath & deriver, const string & outputName, const StorePath & output)
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
{
auto state(_state.lock());
return linkDeriverToPath(*state, queryValidPathId(*state, deriver), outputName, output);
settings.requireExperimentalFeature("ca-derivations");
if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info))
registerDrvOutput(info);
else
throw Error("cannot register realisation '%s' because it lacks a valid signature", info.outPath.to_string());
}
void LocalStore::linkDeriverToPath(State & state, uint64_t deriver, const string & outputName, const StorePath & output)
void LocalStore::registerDrvOutput(const Realisation & info)
{
settings.requireExperimentalFeature("ca-derivations");
auto state(_state.lock());
retrySQLite<void>([&]() {
state->stmts->RegisterRealisedOutput.use()
(info.id.strHash())
(info.id.outputName)
(printStorePath(info.outPath))
(concatStringsSep(" ", info.signatures))
.exec();
});
}
void LocalStore::cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output)
{
retrySQLite<void>([&]() {
state.stmtAddDerivationOutput.use()
state.stmts->AddDerivationOutput.use()
(deriver)
(outputName)
(printStorePath(output))
@ -611,7 +702,7 @@ uint64_t LocalStore::addValidPath(State & state,
throw Error("cannot add path '%s' to the Nix store because it claims to be content-addressed but isn't",
printStorePath(info.path));
state.stmtRegisterValidPath.use()
state.stmts->RegisterValidPath.use()
(printStorePath(info.path))
(info.narHash.to_string(Base16, true))
(info.registrationTime == 0 ? time(0) : info.registrationTime)
@ -628,7 +719,7 @@ uint64_t LocalStore::addValidPath(State & state,
efficiently query whether a path is an output of some
derivation. */
if (info.path.isDerivation()) {
auto drv = readDerivation(info.path);
auto drv = readInvalidDerivation(info.path);
/* Verify that the output paths in the derivation are correct
(i.e., follow the scheme for computing output paths from
@ -641,7 +732,7 @@ uint64_t LocalStore::addValidPath(State & state,
/* Floating CA derivations have indeterminate output paths until
they are built, so don't register anything in that case */
if (i.second.second)
linkDeriverToPath(state, id, i.first, *i.second.second);
cacheDrvOutputMapping(state, id, i.first, *i.second.second);
}
}
@ -659,11 +750,19 @@ void LocalStore::queryPathInfoUncached(const StorePath & path,
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept
{
try {
callback(retrySQLite<std::shared_ptr<ValidPathInfo>>([&]() {
callback(retrySQLite<std::shared_ptr<const ValidPathInfo>>([&]() {
auto state(_state.lock());
return queryPathInfoInternal(*state, path);
}));
} catch (...) { callback.rethrow(); }
}
std::shared_ptr<const ValidPathInfo> LocalStore::queryPathInfoInternal(State & state, const StorePath & path)
{
/* Get the path info. */
auto useQueryPathInfo(state->stmtQueryPathInfo.use()(printStorePath(path)));
auto useQueryPathInfo(state.stmts->QueryPathInfo.use()(printStorePath(path)));
if (!useQueryPathInfo.next())
return std::shared_ptr<ValidPathInfo>();
@ -683,7 +782,7 @@ void LocalStore::queryPathInfoUncached(const StorePath & path,
info->registrationTime = useQueryPathInfo.getInt(2);
auto s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 3);
auto s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 3);
if (s) info->deriver = parseStorePath(s);
/* Note that narSize = NULL yields 0. */
@ -691,29 +790,26 @@ void LocalStore::queryPathInfoUncached(const StorePath & path,
info->ultimate = useQueryPathInfo.getInt(5) == 1;
s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 6);
s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 6);
if (s) info->sigs = tokenizeString<StringSet>(s, " ");
s = (const char *) sqlite3_column_text(state->stmtQueryPathInfo, 7);
s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 7);
if (s) info->ca = parseContentAddressOpt(s);
/* Get the references. */
auto useQueryReferences(state->stmtQueryReferences.use()(info->id));
auto useQueryReferences(state.stmts->QueryReferences.use()(info->id));
while (useQueryReferences.next())
info->references.insert(parseStorePath(useQueryReferences.getStr(0)));
return info;
}));
} catch (...) { callback.rethrow(); }
}
/* Update path info in the database. */
void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
{
state.stmtUpdatePathInfo.use()
state.stmts->UpdatePathInfo.use()
(info.narSize, info.narSize != 0)
(info.narHash.to_string(Base16, true))
(info.ultimate ? 1 : 0, info.ultimate)
@ -726,7 +822,7 @@ void LocalStore::updatePathInfo(State & state, const ValidPathInfo & info)
uint64_t LocalStore::queryValidPathId(State & state, const StorePath & path)
{
auto use(state.stmtQueryPathInfo.use()(printStorePath(path)));
auto use(state.stmts->QueryPathInfo.use()(printStorePath(path)));
if (!use.next())
throw InvalidPath("path '%s' is not valid", printStorePath(path));
return use.getInt(0);
@ -735,7 +831,7 @@ uint64_t LocalStore::queryValidPathId(State & state, const StorePath & path)
bool LocalStore::isValidPath_(State & state, const StorePath & path)
{
return state.stmtQueryPathInfo.use()(printStorePath(path)).next();
return state.stmts->QueryPathInfo.use()(printStorePath(path)).next();
}
@ -761,7 +857,7 @@ StorePathSet LocalStore::queryAllValidPaths()
{
return retrySQLite<StorePathSet>([&]() {
auto state(_state.lock());
auto use(state->stmtQueryValidPaths.use());
auto use(state->stmts->QueryValidPaths.use());
StorePathSet res;
while (use.next()) res.insert(parseStorePath(use.getStr(0)));
return res;
@ -771,7 +867,7 @@ StorePathSet LocalStore::queryAllValidPaths()
void LocalStore::queryReferrers(State & state, const StorePath & path, StorePathSet & referrers)
{
auto useQueryReferrers(state.stmtQueryReferrers.use()(printStorePath(path)));
auto useQueryReferrers(state.stmts->QueryReferrers.use()(printStorePath(path)));
while (useQueryReferrers.next())
referrers.insert(parseStorePath(useQueryReferrers.getStr(0)));
@ -792,7 +888,7 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
return retrySQLite<StorePathSet>([&]() {
auto state(_state.lock());
auto useQueryValidDerivers(state->stmtQueryValidDerivers.use()(printStorePath(path)));
auto useQueryValidDerivers(state->stmts->QueryValidDerivers.use()(printStorePath(path)));
StorePathSet derivers;
while (useQueryValidDerivers.next())
@ -803,69 +899,38 @@ StorePathSet LocalStore::queryValidDerivers(const StorePath & path)
}
std::map<std::string, std::optional<StorePath>> LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
std::map<std::string, std::optional<StorePath>>
LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
{
auto path = path_;
std::map<std::string, std::optional<StorePath>> outputs;
Derivation drv = readDerivation(path);
for (auto & [outName, _] : drv.outputs) {
outputs.insert_or_assign(outName, std::nullopt);
}
bool haveCached = false;
{
auto resolutions = drvPathResolutions.lock();
auto resolvedPathOptIter = resolutions->find(path);
if (resolvedPathOptIter != resolutions->end()) {
auto & [_, resolvedPathOpt] = *resolvedPathOptIter;
if (resolvedPathOpt)
path = *resolvedPathOpt;
haveCached = true;
}
}
/* can't just use else-if instead of `!haveCached` because we need to unlock
`drvPathResolutions` before it is locked in `Derivation::resolve`. */
if (!haveCached && drv.type() == DerivationType::CAFloating) {
/* Try resolve drv and use that path instead. */
auto attempt = drv.tryResolve(*this);
if (!attempt)
/* If we cannot resolve the derivation, we cannot have any path
assigned so we return the map of all std::nullopts. */
return outputs;
/* Just compute store path */
auto pathResolved = writeDerivation(*this, *std::move(attempt), NoRepair, true);
/* Store in memo table. */
/* FIXME: memo logic should not be local-store specific, should have
wrapper-method instead. */
drvPathResolutions.lock()->insert_or_assign(path, pathResolved);
path = std::move(pathResolved);
}
return retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
auto outputs = retrySQLite<std::map<std::string, std::optional<StorePath>>>([&]() {
auto state(_state.lock());
std::map<std::string, std::optional<StorePath>> outputs;
uint64_t drvId;
try {
drvId = queryValidPathId(*state, path);
} catch (InvalidPath &) {
/* FIXME? if the derivation doesn't exist, we cannot have a mapping
for it. */
return outputs;
}
auto useQueryDerivationOutputs {
state->stmtQueryDerivationOutputs.use()
(drvId)
};
while (useQueryDerivationOutputs.next())
auto use(state->stmts->QueryDerivationOutputs.use()(drvId));
while (use.next())
outputs.insert_or_assign(
useQueryDerivationOutputs.getStr(0),
parseStorePath(useQueryDerivationOutputs.getStr(1))
);
use.getStr(0), parseStorePath(use.getStr(1)));
return outputs;
});
}
if (!settings.isExperimentalFeatureEnabled("ca-derivations"))
return outputs;
auto drv = readInvalidDerivation(path);
auto drvHashes = staticOutputHashes(*this, drv);
for (auto& [outputName, hash] : drvHashes) {
auto realisation = queryRealisation(DrvOutput{hash, outputName});
if (realisation)
outputs.insert_or_assign(outputName, realisation->outPath);
else
outputs.insert({outputName, std::nullopt});
}
return outputs;
}
std::optional<StorePath> LocalStore::queryPathFromHashPart(const std::string & hashPart)
{
@ -876,11 +941,11 @@ std::optional<StorePath> LocalStore::queryPathFromHashPart(const std::string & h
return retrySQLite<std::optional<StorePath>>([&]() -> std::optional<StorePath> {
auto state(_state.lock());
auto useQueryPathFromHashPart(state->stmtQueryPathFromHashPart.use()(prefix));
auto useQueryPathFromHashPart(state->stmts->QueryPathFromHashPart.use()(prefix));
if (!useQueryPathFromHashPart.next()) return {};
const char * s = (const char *) sqlite3_column_text(state->stmtQueryPathFromHashPart, 0);
const char * s = (const char *) sqlite3_column_text(state->stmts->QueryPathFromHashPart, 0);
if (s && prefix.compare(0, prefix.size(), s, prefix.size()) == 0)
return parseStorePath(s);
return {};
@ -964,9 +1029,7 @@ void LocalStore::querySubstitutablePathInfos(const StorePathCAMap & paths, Subst
void LocalStore::registerValidPath(const ValidPathInfo & info)
{
ValidPathInfos infos;
infos.push_back(info);
registerValidPaths(infos);
registerValidPaths({{info.path, info}});
}
@ -984,7 +1047,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
SQLiteTxn txn(state->db);
StorePathSet paths;
for (auto & i : infos) {
for (auto & [_, i] : infos) {
assert(i.narHash.type == htSHA256);
if (isValidPath_(*state, i.path))
updatePathInfo(*state, i);
@ -993,26 +1056,37 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
paths.insert(i.path);
}
for (auto & i : infos) {
for (auto & [_, i] : infos) {
auto referrer = queryValidPathId(*state, i.path);
for (auto & j : i.references)
state->stmtAddReference.use()(referrer)(queryValidPathId(*state, j)).exec();
state->stmts->AddReference.use()(referrer)(queryValidPathId(*state, j)).exec();
}
/* Check that the derivation outputs are correct. We can't do
this in addValidPath() above, because the references might
not be valid yet. */
for (auto & i : infos)
for (auto & [_, i] : infos)
if (i.path.isDerivation()) {
// FIXME: inefficient; we already loaded the derivation in addValidPath().
checkDerivationOutputs(i.path, readDerivation(i.path));
checkDerivationOutputs(i.path,
readInvalidDerivation(i.path));
}
/* Do a topological sort of the paths. This will throw an
error if a cycle is detected and roll back the
transaction. Cycles can only occur when a derivation
has multiple outputs. */
topoSortPaths(paths);
topoSort(paths,
{[&](const StorePath & path) {
auto i = infos.find(path);
return i == infos.end() ? StorePathSet() : i->second.references;
}},
{[&](const StorePath & path, const StorePath & parent) {
return BuildError(
"cycle detected in the references of '%s' from '%s'",
printStorePath(path),
printStorePath(parent));
}});
txn.commit();
});
@ -1025,7 +1099,7 @@ void LocalStore::invalidatePath(State & state, const StorePath & path)
{
debug("invalidating path '%s'", printStorePath(path));
state.stmtInvalidatePath.use()(printStorePath(path)).exec();
state.stmts->InvalidatePath.use()(printStorePath(path)).exec();
/* Note that the foreign key constraints on the Refs table take
care of deleting the references entries for `path'. */
@ -1036,7 +1110,6 @@ void LocalStore::invalidatePath(State & state, const StorePath & path)
}
}
const PublicKeys & LocalStore::getPublicKeys()
{
auto state(_state.lock());
@ -1045,11 +1118,20 @@ const PublicKeys & LocalStore::getPublicKeys()
return *state->publicKeys;
}
bool LocalStore::pathInfoIsUntrusted(const ValidPathInfo & info)
{
return requireSigs && !info.checkSignatures(*this, getPublicKeys());
}
bool LocalStore::realisationIsUntrusted(const Realisation & realisation)
{
return requireSigs && !realisation.checkSignatures(getPublicKeys());
}
void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs)
{
if (requireSigs && checkSigs && !info.checkSignatures(*this, getPublicKeys()))
if (checkSigs && pathInfoIsUntrusted(info))
throw Error("cannot add path '%s' because it lacks a valid signature", printStorePath(info.path));
addTempRoot(info.path);
@ -1090,11 +1172,11 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
auto hashResult = hashSink->finish();
if (hashResult.first != info.narHash)
throw Error("hash mismatch importing path '%s';\n wanted: %s\n got: %s",
throw Error("hash mismatch importing path '%s';\n specified: %s\n got: %s",
printStorePath(info.path), info.narHash.to_string(Base32, true), hashResult.first.to_string(Base32, true));
if (hashResult.second != info.narSize)
throw Error("size mismatch importing path '%s';\n wanted: %s\n got: %s",
throw Error("size mismatch importing path '%s';\n specified: %s\n got: %s",
printStorePath(info.path), info.narSize, hashResult.second);
autoGC();
@ -1138,7 +1220,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, const string & name,
dump.resize(oldSize + want);
auto got = 0;
try {
got = source.read((uint8_t *) dump.data() + oldSize, want);
got = source.read(dump.data() + oldSize, want);
} catch (EndOfFile &) {
inMemory = true;
break;
@ -1341,12 +1423,8 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
Path linkPath = linksDir + "/" + link.name;
string hash = hashPath(htSHA256, linkPath).first.to_string(Base32, false);
if (hash != link.name) {
logError({
.name = "Invalid hash",
.hint = hintfmt(
"link '%s' was modified! expected hash '%s', got '%s'",
linkPath, link.name, hash)
});
printError("link '%s' was modified! expected hash '%s', got '%s'",
linkPath, link.name, hash);
if (repair) {
if (unlink(linkPath.c_str()) == 0)
printInfo("removed link '%s'", linkPath);
@ -1379,11 +1457,8 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
auto current = hashSink->finish();
if (info->narHash != nullHash && info->narHash != current.first) {
logError({
.name = "Invalid hash - path modified",
.hint = hintfmt("path '%s' was modified! expected hash '%s', got '%s'",
printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true))
});
printError("path '%s' was modified! expected hash '%s', got '%s'",
printStorePath(i), info->narHash.to_string(Base32, true), current.first.to_string(Base32, true));
if (repair) repairPath(i); else errors = true;
} else {
@ -1434,10 +1509,7 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
if (!done.insert(pathS).second) return;
if (!isStorePath(pathS)) {
logError({
.name = "Nix path not found",
.hint = hintfmt("path '%s' is not in the Nix store", pathS)
});
printError("path '%s' is not in the Nix store", pathS);
return;
}
@ -1460,10 +1532,7 @@ void LocalStore::verifyPath(const Path & pathS, const StringSet & store,
auto state(_state.lock());
invalidatePath(*state, path);
} else {
logError({
.name = "Missing path with referrers",
.hint = hintfmt("path '%s' disappeared, but it still has valid referrers!", pathS)
});
printError("path '%s' disappeared, but it still has valid referrers!", pathS);
if (repair)
try {
repairPath(path);
@ -1553,7 +1622,7 @@ void LocalStore::addSignatures(const StorePath & storePath, const StringSet & si
SQLiteTxn txn(state->db);
auto info = std::const_pointer_cast<ValidPathInfo>(std::shared_ptr<const ValidPathInfo>(queryPathInfo(storePath)));
auto info = std::const_pointer_cast<ValidPathInfo>(queryPathInfoInternal(*state, storePath));
info->sigs.insert(sigs.begin(), sigs.end());
@ -1564,6 +1633,18 @@ void LocalStore::addSignatures(const StorePath & storePath, const StringSet & si
}
void LocalStore::signRealisation(Realisation & realisation)
{
// FIXME: keep secret keys in memory.
auto secretKeyFiles = settings.secretKeyFiles;
for (auto & secretKeyFile : secretKeyFiles.get()) {
SecretKey secretKey(readFile(secretKeyFile));
realisation.sign(secretKey);
}
}
void LocalStore::signPathInfo(ValidPathInfo & info)
{
// FIXME: keep secret keys in memory.
@ -1591,5 +1672,19 @@ void LocalStore::createUser(const std::string & userName, uid_t userId)
}
}
std::optional<const Realisation> LocalStore::queryRealisation(
const DrvOutput& id) {
typedef std::optional<const Realisation> Ret;
return retrySQLite<Ret>([&]() -> Ret {
auto state(_state.lock());
auto use(state->stmts->QueryRealisedOutput.use()(id.strHash())(
id.outputName));
if (!use.next())
return std::nullopt;
auto outputPath = parseStorePath(use.getStr(0));
auto signatures = tokenizeString<StringSet>(use.getStr(1));
return Ret{Realisation{
.id = id, .outPath = outputPath, .signatures = signatures}};
});
}
} // namespace nix

View file

@ -43,7 +43,7 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
};
class LocalStore : public LocalFSStore, public virtual LocalStoreConfig
class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore
{
private:
@ -55,19 +55,8 @@ private:
/* The SQLite database object. */
SQLite db;
/* Some precompiled SQLite statements. */
SQLiteStmt stmtRegisterValidPath;
SQLiteStmt stmtUpdatePathInfo;
SQLiteStmt stmtAddReference;
SQLiteStmt stmtQueryPathInfo;
SQLiteStmt stmtQueryReferences;
SQLiteStmt stmtQueryReferrers;
SQLiteStmt stmtInvalidatePath;
SQLiteStmt stmtAddDerivationOutput;
SQLiteStmt stmtQueryValidDerivers;
SQLiteStmt stmtQueryDerivationOutputs;
SQLiteStmt stmtQueryPathFromHashPart;
SQLiteStmt stmtQueryValidPaths;
struct Stmts;
std::unique_ptr<Stmts> stmts;
/* The file to which we write our temporary roots. */
AutoCloseFD fdTempRoots;
@ -90,7 +79,7 @@ private:
std::unique_ptr<PublicKeys> publicKeys;
};
Sync<State, std::recursive_mutex> _state;
Sync<State> _state;
public:
@ -147,6 +136,9 @@ public:
void querySubstitutablePathInfos(const StorePathCAMap & paths,
SubstitutablePathInfos & infos) override;
bool pathInfoIsUntrusted(const ValidPathInfo &) override;
bool realisationIsUntrusted(const Realisation & ) override;
void addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs) override;
@ -156,15 +148,6 @@ public:
StorePath addTextToStore(const string & name, const string & s,
const StorePathSet & references, RepairFlag repair) override;
void buildPaths(
const std::vector<StorePathWithOutputs> & paths,
BuildMode buildMode) override;
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
BuildMode buildMode) override;
void ensurePath(const StorePath & path) override;
void addTempRoot(const StorePath & path) override;
void addIndirectRoot(const Path & path) override;
@ -209,9 +192,7 @@ public:
void vacuumDB();
/* Repair the contents of the given path by redownloading it using
a substituter (if available). */
void repairPath(const StorePath & path);
void repairPath(const StorePath & path) override;
void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
@ -219,6 +200,14 @@ public:
garbage until it exceeds maxFree. */
void autoGC(bool sync = true);
/* Register the store path 'output' as the output named 'outputName' of
derivation 'deriver'. */
void registerDrvOutput(const Realisation & info) override;
void registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs) override;
void cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output);
std::optional<const Realisation> queryRealisation(const DrvOutput&) override;
private:
int getSchema();
@ -239,6 +228,8 @@ private:
void verifyPath(const Path & path, const StringSet & store,
PathSet & done, StorePathSet & validPaths, RepairFlag repair, bool & errors);
std::shared_ptr<const ValidPathInfo> queryPathInfoInternal(State & state, const StorePath & path);
void updatePathInfo(State & state, const ValidPathInfo & info);
void upgradeStore6();
@ -283,21 +274,19 @@ private:
bool isValidPath_(State & state, const StorePath & path);
void queryReferrers(State & state, const StorePath & path, StorePathSet & referrers);
/* Add signatures to a ValidPathInfo using the secret keys
/* Add signatures to a ValidPathInfo or Realisation using the secret keys
specified by the secret-key-files option. */
void signPathInfo(ValidPathInfo & info);
/* Register the store path 'output' as the output named 'outputName' of
derivation 'deriver'. */
void linkDeriverToPath(const StorePath & deriver, const string & outputName, const StorePath & output);
void linkDeriverToPath(State & state, uint64_t deriver, const string & outputName, const StorePath & output);
void signRealisation(Realisation &);
Path getRealStoreDir() override { return realStoreDir; }
void createUser(const std::string & userName, uid_t userId) override;
friend class DerivationGoal;
friend class SubstitutionGoal;
friend struct LocalDerivationGoal;
friend struct PathSubstitutionGoal;
friend struct SubstitutionGoal;
friend struct DerivationGoal;
};

View file

@ -28,7 +28,7 @@ ifeq ($(OS), SunOS)
endif
ifeq ($(HAVE_SECCOMP), 1)
libstore_LDFLAGS += -lseccomp
libstore_LDFLAGS += $(LIBSECCOMP_LIBS)
endif
libstore_CXXFLAGS += \
@ -48,7 +48,7 @@ ifneq ($(sandbox_shell),)
libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\""
endif
$(d)/local-store.cc: $(d)/schema.sql.gen.hh
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
$(d)/build.cc:
@ -58,7 +58,7 @@ $(d)/build.cc:
@echo ')foo"' >> $@.tmp
@mv $@.tmp $@
clean-files += $(d)/schema.sql.gen.hh
clean-files += $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh
$(eval $(call install-file-in, $(d)/nix-store.pc, $(prefix)/lib/pkgconfig, 0644))

View file

@ -54,9 +54,15 @@ ref<Store> Machine::openStore() const {
if (hasPrefix(storeUri, "ssh://")) {
storeParams["max-connections"] = "1";
storeParams["log-fd"] = "4";
}
if (hasPrefix(storeUri, "ssh://") || hasPrefix(storeUri, "ssh-ng://")) {
if (sshKey != "")
storeParams["ssh-key"] = sshKey;
if (sshPublicHostKey != "")
storeParams["base64-ssh-public-host-key"] = sshPublicHostKey;
}
{
auto & fs = storeParams["system-features"];
auto append = [&](auto feats) {

View file

@ -22,55 +22,53 @@ void Store::computeFSClosure(const StorePathSet & startPaths,
Sync<State> state_(State{0, paths_, 0});
std::function<void(const Path &)> enqueue;
std::function<void(const StorePath &)> enqueue;
std::condition_variable done;
enqueue = [&](const Path & path) -> void {
enqueue = [&](const StorePath & path) -> void {
{
auto state(state_.lock());
if (state->exc) return;
if (!state->paths.insert(parseStorePath(path)).second) return;
if (!state->paths.insert(path).second) return;
state->pending++;
}
queryPathInfo(parseStorePath(path), {[&, pathS(path)](std::future<ref<const ValidPathInfo>> fut) {
queryPathInfo(path, {[&](std::future<ref<const ValidPathInfo>> fut) {
// FIXME: calls to isValidPath() should be async
try {
auto info = fut.get();
auto path = parseStorePath(pathS);
if (flipDirection) {
StorePathSet referrers;
queryReferrers(path, referrers);
for (auto & ref : referrers)
if (ref != path)
enqueue(printStorePath(ref));
enqueue(ref);
if (includeOutputs)
for (auto & i : queryValidDerivers(path))
enqueue(printStorePath(i));
enqueue(i);
if (includeDerivers && path.isDerivation())
for (auto & i : queryDerivationOutputs(path))
if (isValidPath(i) && queryPathInfo(i)->deriver == path)
enqueue(printStorePath(i));
enqueue(i);
} else {
for (auto & ref : info->references)
if (ref != path)
enqueue(printStorePath(ref));
enqueue(ref);
if (includeOutputs && path.isDerivation())
for (auto & i : queryDerivationOutputs(path))
if (isValidPath(i)) enqueue(printStorePath(i));
if (isValidPath(i)) enqueue(i);
if (includeDerivers && info->deriver && isValidPath(*info->deriver))
enqueue(printStorePath(*info->deriver));
enqueue(*info->deriver);
}
@ -90,7 +88,7 @@ void Store::computeFSClosure(const StorePathSet & startPaths,
};
for (auto & startPath : startPaths)
enqueue(printStorePath(startPath));
enqueue(startPath);
{
auto state(state_.lock());
@ -119,7 +117,7 @@ std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv)
return std::nullopt;
}
void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
void Store::queryMissing(const std::vector<DerivedPath> & targets,
StorePathSet & willBuild_, StorePathSet & willSubstitute_, StorePathSet & unknown_,
uint64_t & downloadSize_, uint64_t & narSize_)
{
@ -147,7 +145,7 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
Sync<State> state_(State{{}, unknown_, willSubstitute_, willBuild_, downloadSize_, narSize_});
std::function<void(StorePathWithOutputs)> doPath;
std::function<void(DerivedPath)> doPath;
auto mustBuildDrv = [&](const StorePath & drvPath, const Derivation & drv) {
{
@ -156,17 +154,14 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
}
for (auto & i : drv.inputDrvs)
pool.enqueue(std::bind(doPath, StorePathWithOutputs { i.first, i.second }));
pool.enqueue(std::bind(doPath, DerivedPath::Built { i.first, i.second }));
};
auto checkOutput = [&](
const Path & drvPathS, ref<Derivation> drv, const Path & outPathS, ref<Sync<DrvState>> drvState_)
const StorePath & drvPath, ref<Derivation> drv, const StorePath & outPath, ref<Sync<DrvState>> drvState_)
{
if (drvState_->lock()->done) return;
auto drvPath = parseStorePath(drvPathS);
auto outPath = parseStorePath(outPathS);
SubstitutablePathInfos infos;
querySubstitutablePathInfos({{outPath, getDerivationCA(*drv)}}, infos);
@ -182,77 +177,80 @@ void Store::queryMissing(const std::vector<StorePathWithOutputs> & targets,
drvState->outPaths.insert(outPath);
if (!drvState->left) {
for (auto & path : drvState->outPaths)
pool.enqueue(std::bind(doPath, StorePathWithOutputs { path } ));
pool.enqueue(std::bind(doPath, DerivedPath::Opaque { path } ));
}
}
}
};
doPath = [&](const StorePathWithOutputs & path) {
doPath = [&](const DerivedPath & req) {
{
auto state(state_.lock());
if (!state->done.insert(path.to_string(*this)).second) return;
if (!state->done.insert(req.to_string(*this)).second) return;
}
if (path.path.isDerivation()) {
if (!isValidPath(path.path)) {
std::visit(overloaded {
[&](DerivedPath::Built bfd) {
if (!isValidPath(bfd.drvPath)) {
// FIXME: we could try to substitute the derivation.
auto state(state_.lock());
state->unknown.insert(path.path);
state->unknown.insert(bfd.drvPath);
return;
}
PathSet invalid;
StorePathSet invalid;
/* true for regular derivations, and CA derivations for which we
have a trust mapping for all wanted outputs. */
auto knownOutputPaths = true;
for (auto & [outputName, pathOpt] : queryPartialDerivationOutputMap(path.path)) {
for (auto & [outputName, pathOpt] : queryPartialDerivationOutputMap(bfd.drvPath)) {
if (!pathOpt) {
knownOutputPaths = false;
break;
}
if (wantOutput(outputName, path.outputs) && !isValidPath(*pathOpt))
invalid.insert(printStorePath(*pathOpt));
if (wantOutput(outputName, bfd.outputs) && !isValidPath(*pathOpt))
invalid.insert(*pathOpt);
}
if (knownOutputPaths && invalid.empty()) return;
auto drv = make_ref<Derivation>(derivationFromPath(path.path));
ParsedDerivation parsedDrv(StorePath(path.path), *drv);
auto drv = make_ref<Derivation>(derivationFromPath(bfd.drvPath));
ParsedDerivation parsedDrv(StorePath(bfd.drvPath), *drv);
if (knownOutputPaths && settings.useSubstitutes && parsedDrv.substitutesAllowed()) {
auto drvState = make_ref<Sync<DrvState>>(DrvState(invalid.size()));
for (auto & output : invalid)
pool.enqueue(std::bind(checkOutput, printStorePath(path.path), drv, output, drvState));
pool.enqueue(std::bind(checkOutput, bfd.drvPath, drv, output, drvState));
} else
mustBuildDrv(path.path, *drv);
mustBuildDrv(bfd.drvPath, *drv);
} else {
},
[&](DerivedPath::Opaque bo) {
if (isValidPath(path.path)) return;
if (isValidPath(bo.path)) return;
SubstitutablePathInfos infos;
querySubstitutablePathInfos({{path.path, std::nullopt}}, infos);
querySubstitutablePathInfos({{bo.path, std::nullopt}}, infos);
if (infos.empty()) {
auto state(state_.lock());
state->unknown.insert(path.path);
state->unknown.insert(bo.path);
return;
}
auto info = infos.find(path.path);
auto info = infos.find(bo.path);
assert(info != infos.end());
{
auto state(state_.lock());
state->willSubstitute.insert(path.path);
state->willSubstitute.insert(bo.path);
state->downloadSize += info->second.downloadSize;
state->narSize += info->second.narSize;
}
for (auto & ref : info->second.references)
pool.enqueue(std::bind(doPath, StorePathWithOutputs { ref }));
}
pool.enqueue(std::bind(doPath, DerivedPath::Opaque { ref }));
},
}, req.raw());
};
for (auto & path : targets)

View file

@ -80,16 +80,16 @@ string nextComponent(string::const_iterator & p,
static bool componentsLT(const string & c1, const string & c2)
{
int n1, n2;
bool c1Num = string2Int(c1, n1), c2Num = string2Int(c2, n2);
auto n1 = string2Int<int>(c1);
auto n2 = string2Int<int>(c2);
if (c1Num && c2Num) return n1 < n2;
else if (c1 == "" && c2Num) return true;
if (n1 && n2) return *n1 < *n2;
else if (c1 == "" && n2) return true;
else if (c1 == "pre" && c2 != "pre") return true;
else if (c2 == "pre") return false;
/* Assume that `2.3a' < `2.3.1'. */
else if (c2Num) return true;
else if (c1Num) return false;
else if (n2) return true;
else if (n1) return false;
else return c1 < c2;
}

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