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 path-info

Also improve content-address.hh API docs.
This commit is contained in:
John Ericson 2023-03-30 16:28:53 -04:00
commit aa99005004
315 changed files with 6195 additions and 3610 deletions

View file

@ -219,7 +219,7 @@ static int main_build_remote(int argc, char * * argv)
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error);
printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str());
std::cerr << "# decline\n";
}
@ -305,7 +305,7 @@ connected:
std::set<Realisation> missingRealisations;
StorePathSet missingPaths;
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
@ -337,7 +337,7 @@ connected:
for (auto & realisation : missingRealisations) {
// Should hold, because if the feature isn't enabled the set
// of missing realisations should be empty
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
store->registerDrvOutput(realisation);
}

View file

@ -0,0 +1,11 @@
#include "command-installable-value.hh"
namespace nix {
void InstallableValueCommand::run(ref<Store> store, ref<Installable> installable)
{
auto installableValue = InstallableValue::require(installable);
run(store, installableValue);
}
}

View file

@ -0,0 +1,13 @@
#include "installable-value.hh"
#include "command.hh"
namespace nix {
struct InstallableValueCommand : InstallableCommand
{
virtual void run(ref<Store> store, ref<InstallableValue> installable) = 0;
void run(ref<Store> store, ref<Installable> installable) override;
};
}

View file

@ -165,7 +165,7 @@ BuiltPathsCommand::BuiltPathsCommand(bool recursive)
});
}
void BuiltPathsCommand::run(ref<Store> store)
void BuiltPathsCommand::run(ref<Store> store, Installables && installables)
{
BuiltPaths paths;
if (all) {
@ -211,7 +211,7 @@ void StorePathsCommand::run(ref<Store> store, BuiltPaths && paths)
run(store, std::move(sorted));
}
void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePaths)
void StorePathCommand::run(ref<Store> store, StorePaths && storePaths)
{
if (storePaths.size() != 1)
throw UsageError("this command requires exactly one store path");
@ -246,7 +246,7 @@ void MixProfile::updateProfile(const BuiltPaths & buildables)
{
if (!profile) return;
std::vector<StorePath> result;
StorePaths result;
for (auto & buildable : buildables) {
std::visit(overloaded {

View file

@ -1,6 +1,6 @@
#pragma once
#include "installables.hh"
#include "installable-value.hh"
#include "args.hh"
#include "common-eval-args.hh"
#include "path.hh"
@ -18,17 +18,21 @@ class EvalState;
struct Pos;
class Store;
static constexpr Command::Category catHelp = -1;
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";
static constexpr auto installablesCategory = "Options that change the interpretation of [installables](@docroot@/command-ref/new-cli/nix.md#installables)";
struct NixMultiCommand : virtual MultiCommand, virtual Command
{
nlohmann::json toJSON() override;
};
// For the overloaded run methods
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
/* A command that requires a Nix store. */
struct StoreCommand : virtual Command
{
@ -97,10 +101,10 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
SourceExprCommand();
std::vector<std::shared_ptr<Installable>> parseInstallables(
Installables parseInstallables(
ref<Store> store, std::vector<std::string> ss);
std::shared_ptr<Installable> parseInstallable(
ref<Installable> parseInstallable(
ref<Store> store, const std::string & installable);
virtual Strings getDefaultFlakeAttrPaths();
@ -115,34 +119,43 @@ struct MixReadOnlyOption : virtual Args
MixReadOnlyOption();
};
/* A command that operates on a list of "installables", which can be
store paths, attribute paths, Nix expressions, etc. */
struct InstallablesCommand : virtual Args, SourceExprCommand
/* Like InstallablesCommand but the installables are not loaded */
struct RawInstallablesCommand : virtual Args, SourceExprCommand
{
std::vector<std::shared_ptr<Installable>> installables;
RawInstallablesCommand();
InstallablesCommand();
virtual void run(ref<Store> store, std::vector<std::string> && rawInstallables) = 0;
void prepare() override;
Installables load();
void run(ref<Store> store) override;
virtual bool useDefaultInstallables() { return true; }
// FIXME make const after CmdRepl's override is fixed up
virtual void applyDefaultInstallables(std::vector<std::string> & rawInstallables);
bool readFromStdIn = false;
std::vector<std::string> getFlakesForCompletion() override;
protected:
private:
std::vector<std::string> _installables;
std::vector<std::string> rawInstallables;
};
/* A command that operates on a list of "installables", which can be
store paths, attribute paths, Nix expressions, etc. */
struct InstallablesCommand : RawInstallablesCommand
{
virtual void run(ref<Store> store, Installables && installables) = 0;
void run(ref<Store> store, std::vector<std::string> && rawInstallables) override;
};
/* A command that operates on exactly one "installable" */
struct InstallableCommand : virtual Args, SourceExprCommand
{
std::shared_ptr<Installable> installable;
InstallableCommand();
void prepare() override;
virtual void run(ref<Store> store, ref<Installable> installable) = 0;
void run(ref<Store> store) override;
std::vector<std::string> getFlakesForCompletion() override
{
@ -177,22 +190,18 @@ public:
BuiltPathsCommand(bool recursive = false);
using StoreCommand::run;
virtual void run(ref<Store> store, BuiltPaths && paths) = 0;
void run(ref<Store> store) override;
void run(ref<Store> store, Installables && installables) override;
bool useDefaultInstallables() override { return !all; }
void applyDefaultInstallables(std::vector<std::string> & rawInstallables) override;
};
struct StorePathsCommand : public BuiltPathsCommand
{
StorePathsCommand(bool recursive = false);
using BuiltPathsCommand::run;
virtual void run(ref<Store> store, std::vector<StorePath> && storePaths) = 0;
virtual void run(ref<Store> store, StorePaths && storePaths) = 0;
void run(ref<Store> store, BuiltPaths && paths) override;
};
@ -200,11 +209,9 @@ struct StorePathsCommand : public BuiltPathsCommand
/* 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, std::vector<StorePath> && storePaths) override;
void run(ref<Store> store, StorePaths && storePaths) override;
};
/* A helper class for registering commands globally. */

View file

@ -136,7 +136,11 @@ MixEvalArgs::MixEvalArgs()
addFlag({
.longName = "eval-store",
.description = "The Nix store to use for evaluations.",
.description =
R"(
The [URL of the Nix store](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
to use for evaluation, i.e. to store derivations (`.drv` files) and inputs referenced by them.
)",
.category = category,
.labels = {"store-url"},
.handler = {&evalStoreUrl},
@ -166,7 +170,7 @@ Path lookupFileArg(EvalState & state, std::string_view s)
}
else if (hasPrefix(s, "flake:")) {
settings.requireExperimentalFeature(Xp::Flakes);
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
return state.store->toRealPath(storePath);

View file

@ -87,6 +87,10 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
.drvPath = drvPath,
.outputs = outputs,
},
.info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value {
/* FIXME: reconsider backwards compatibility above
so we can fill in this info. */
}),
});
return res;

View file

@ -10,7 +10,10 @@ std::string InstallableDerivedPath::what() const
DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths()
{
return {{.path = derivedPath, .info = {} }};
return {{
.path = derivedPath,
.info = make_ref<ExtraPathInfo>(),
}};
}
std::optional<StorePath> InstallableDerivedPath::getStorePath()
@ -31,27 +34,24 @@ InstallableDerivedPath InstallableDerivedPath::parse(
ExtendedOutputsSpec extendedOutputsSpec)
{
auto derivedPath = std::visit(overloaded {
// If the user did not use ^, we treat the output more liberally.
// If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual
// store path.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
// First, we accept a symlink chain or an actual store path.
auto storePath = store->followLinksToStorePath(prefix);
// Second, we see if the store path ends in `.drv` to decide what sort
// of derived path they want.
//
// This handling predates the `^` syntax. The `^*` in
// `/nix/store/hash-foo.drv^*` unambiguously means "do the
// `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could
// also unambiguously mean "do the DerivedPath::Opaque` case".
//
// Issue #7261 tracks reconsidering this `.drv` dispatching.
return storePath.isDerivation()
? (DerivedPath) DerivedPath::Built {
.drvPath = std::move(storePath),
.outputs = OutputsSpec::All {},
}
: (DerivedPath) DerivedPath::Opaque {
.path = std::move(storePath),
// Remove this prior to stabilizing the new CLI.
if (storePath.isDerivation()) {
auto oldDerivedPath = DerivedPath::Built {
.drvPath = storePath,
.outputs = OutputsSpec::All { },
};
warn(
"The interpretation of store paths arguments ending in `.drv` recently changed. If this command is now failing try again with '%s'",
oldDerivedPath.to_string(*store));
};
return DerivedPath::Opaque {
.path = std::move(storePath),
};
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {

View file

@ -101,7 +101,8 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
return {{
.path = DerivedPath::Opaque {
.path = std::move(storePath),
}
},
.info = make_ref<ExtraPathInfo>(),
}};
}
@ -113,7 +114,8 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
return {{
.path = DerivedPath::Opaque {
.path = std::move(*storePath),
}
},
.info = make_ref<ExtraPathInfo>(),
}};
} else
throw Error("flake output attribute '%s' evaluates to the string '%s' which is not a store path", attrPath, s);
@ -160,13 +162,16 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
},
}, extendedOutputsSpec.raw()),
},
.info = {
.priority = priority,
.originalRef = flakeRef,
.resolvedRef = getLockedFlake()->flake.lockedRef,
.attrPath = attrPath,
.extendedOutputsSpec = extendedOutputsSpec,
}
.info = make_ref<ExtraPathInfoFlake>(
ExtraPathInfoValue::Value {
.priority = priority,
.attrPath = attrPath,
.extendedOutputsSpec = extendedOutputsSpec,
},
ExtraPathInfoFlake::Flake {
.originalRef = flakeRef,
.resolvedRef = getLockedFlake()->flake.lockedRef,
}),
}};
}
@ -178,8 +183,7 @@ std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
std::vector<ref<eval_cache::AttrCursor>>
InstallableFlake::getCursors(EvalState & state)
{
auto evalCache = openEvalCache(state,
std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)));
auto evalCache = openEvalCache(state, getLockedFlake());
auto root = evalCache->getRoot();
@ -213,6 +217,7 @@ std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{
if (!_lockedFlake) {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
// FIXME why this side effect?
lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
}
@ -230,7 +235,7 @@ FlakeRef InstallableFlake::nixpkgsFlakeRef() const
}
}
return Installable::nixpkgsFlakeRef();
return InstallableValue::nixpkgsFlakeRef();
}
}

View file

@ -4,6 +4,30 @@
namespace nix {
/**
* Extra info about a \ref DerivedPath "derived path" that ultimately
* come from a Flake.
*
* Invariant: every ExtraPathInfo gotten from an InstallableFlake should
* be possible to downcast to an ExtraPathInfoFlake.
*/
struct ExtraPathInfoFlake : ExtraPathInfoValue
{
/**
* Extra struct to get around C++ designated initializer limitations
*/
struct Flake {
FlakeRef originalRef;
FlakeRef resolvedRef;
};
Flake flake;
ExtraPathInfoFlake(Value && v, Flake && f)
: ExtraPathInfoValue(std::move(v)), flake(f)
{ }
};
struct InstallableFlake : InstallableValue
{
FlakeRef flakeRef;
@ -33,8 +57,10 @@ struct InstallableFlake : InstallableValue
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/* Get a cursor to every attrpath in getActualAttrPaths()
that exists. However if none exists, throw an exception. */
/**
* Get a cursor to every attrpath in getActualAttrPaths() that
* exists. However if none exists, throw an exception.
*/
std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state) override;

View file

@ -0,0 +1,44 @@
#include "installable-value.hh"
#include "eval-cache.hh"
namespace nix {
std::vector<ref<eval_cache::AttrCursor>>
InstallableValue::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
[&]() { return toValue(state).first; });
return {evalCache->getRoot()};
}
ref<eval_cache::AttrCursor>
InstallableValue::getCursor(EvalState & state)
{
/* Although getCursors should return at least one element, in case it doesn't,
bound check to avoid an undefined behavior for vector[0] */
return getCursors(state).at(0);
}
static UsageError nonValueInstallable(Installable & installable)
{
return UsageError("installable '%s' does not correspond to a Nix language value", installable.what());
}
InstallableValue & InstallableValue::require(Installable & installable)
{
auto * castedInstallable = dynamic_cast<InstallableValue *>(&installable);
if (!castedInstallable)
throw nonValueInstallable(installable);
return *castedInstallable;
}
ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
{
auto castedInstallable = installable.dynamic_pointer_cast<InstallableValue>();
if (!castedInstallable)
throw nonValueInstallable(*installable);
return ref { castedInstallable };
}
}

View file

@ -1,14 +1,107 @@
#pragma once
#include "installables.hh"
#include "flake/flake.hh"
namespace nix {
struct DrvInfo;
struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
struct App
{
std::vector<DerivedPath> context;
Path program;
// FIXME: add args, sandbox settings, metadata, ...
};
struct UnresolvedApp
{
App unresolved;
App resolve(ref<Store> evalStore, ref<Store> store);
};
/**
* Extra info about a \ref DerivedPath "derived path" that ultimately
* come from a Nix language value.
*
* Invariant: every ExtraPathInfo gotten from an InstallableValue should
* be possible to downcast to an ExtraPathInfoValue.
*/
struct ExtraPathInfoValue : ExtraPathInfo
{
/**
* Extra struct to get around C++ designated initializer limitations
*/
struct Value {
/**
* An optional priority for use with "build envs". See Package
*/
std::optional<NixInt> priority;
/**
* The attribute path associated with this value. The idea is
* that an installable referring to a value typically refers to
* a larger value, from which we project a smaller value out
* with this.
*/
std::string attrPath;
/**
* \todo merge with DerivedPath's 'outputs' field?
*/
ExtendedOutputsSpec extendedOutputsSpec;
};
Value value;
ExtraPathInfoValue(Value && v)
: value(v)
{ }
virtual ~ExtraPathInfoValue() = default;
};
/**
* An Installable which corresponds a Nix langauge value, in addition to
* a collection of \ref DerivedPath "derived paths".
*/
struct InstallableValue : Installable
{
ref<EvalState> state;
InstallableValue(ref<EvalState> state) : state(state) {}
virtual ~InstallableValue() { }
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
/**
* Get a cursor to each value this Installable could refer to.
* However if none exists, throw exception instead of returning
* empty vector.
*/
virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state);
/**
* Get the first and most preferred cursor this Installable could
* refer to, or throw an exception if none exists.
*/
virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state);
UnresolvedApp toApp(EvalState & state);
virtual FlakeRef nixpkgsFlakeRef() const
{
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
}
static InstallableValue & require(Installable & installable);
static ref<InstallableValue> require(ref<Installable> installable);
};
}

View file

@ -153,7 +153,7 @@ SourceExprCommand::SourceExprCommand()
.longName = "file",
.shortName = 'f',
.description =
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
"Interpret [*installables*](@docroot@/command-ref/new-cli/nix.md#installables) as attribute paths relative to the Nix expression stored in *file*. "
"If *file* is the character -, then a Nix expression will be read from standard input. "
"Implies `--impure`.",
.category = installablesCategory,
@ -164,7 +164,7 @@ SourceExprCommand::SourceExprCommand()
addFlag({
.longName = "expr",
.description = "Interpret installables as attribute paths relative to the Nix expression *expr*.",
.description = "Interpret [*installables*](@docroot@/command-ref/new-cli/nix.md#installables) as attribute paths relative to the Nix expression *expr*.",
.category = installablesCategory,
.labels = {"expr"},
.handler = {&expr}
@ -332,7 +332,7 @@ void completeFlakeRefWithFragment(
void completeFlakeRef(ref<Store> store, std::string_view prefix)
{
if (!settings.isExperimentalFeatureEnabled(Xp::Flakes))
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
return;
if (prefix == "")
@ -364,23 +364,6 @@ DerivedPathWithInfo Installable::toDerivedPath()
return std::move(buildables[0]);
}
std::vector<ref<eval_cache::AttrCursor>>
Installable::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
[&]() { return toValue(state).first; });
return {evalCache->getRoot()};
}
ref<eval_cache::AttrCursor>
Installable::getCursor(EvalState & state)
{
/* Although getCursors should return at least one element, in case it doesn't,
bound check to avoid an undefined behavior for vector[0] */
return getCursors(state).at(0);
}
static StorePath getDeriver(
ref<Store> store,
const Installable & i,
@ -422,10 +405,10 @@ ref<eval_cache::EvalCache> openEvalCache(
});
}
std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
Installables SourceExprCommand::parseInstallables(
ref<Store> store, std::vector<std::string> ss)
{
std::vector<std::shared_ptr<Installable>> result;
Installables result;
if (file || expr) {
if (file && expr)
@ -451,7 +434,7 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back(
std::make_shared<InstallableAttrPath>(
make_ref<InstallableAttrPath>(
InstallableAttrPath::parse(
state, *this, vFile, prefix, extendedOutputsSpec)));
}
@ -468,7 +451,7 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
if (prefix.find('/') != std::string::npos) {
try {
result.push_back(std::make_shared<InstallableDerivedPath>(
result.push_back(make_ref<InstallableDerivedPath>(
InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec)));
continue;
} catch (BadStorePath &) {
@ -480,7 +463,7 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
try {
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath("."));
result.push_back(std::make_shared<InstallableFlake>(
result.push_back(make_ref<InstallableFlake>(
this,
getEvalState(),
std::move(flakeRef),
@ -501,7 +484,7 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
return result;
}
std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
ref<Installable> SourceExprCommand::parseInstallable(
ref<Store> store, const std::string & installable)
{
auto installables = parseInstallables(store, {installable});
@ -513,7 +496,7 @@ std::vector<BuiltPathWithResult> Installable::build(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
BuildMode bMode)
{
std::vector<BuiltPathWithResult> res;
@ -522,11 +505,11 @@ std::vector<BuiltPathWithResult> Installable::build(
return res;
}
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Installable::build2(
std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build2(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
BuildMode bMode)
{
if (mode == Realise::Nothing)
@ -534,8 +517,8 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Instal
struct Aux
{
ExtraPathInfo info;
std::shared_ptr<Installable> installable;
ref<ExtraPathInfo> info;
ref<Installable> installable;
};
std::vector<DerivedPath> pathsToBuild;
@ -548,7 +531,7 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Instal
}
}
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> res;
std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> res;
switch (mode) {
@ -620,7 +603,7 @@ BuiltPaths Installable::toBuiltPaths(
ref<Store> store,
Realise mode,
OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables)
const Installables & installables)
{
if (operateOn == OperateOn::Output) {
BuiltPaths res;
@ -642,7 +625,7 @@ StorePathSet Installable::toStorePaths(
ref<Store> evalStore,
ref<Store> store,
Realise mode, OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables)
const Installables & installables)
{
StorePathSet outPaths;
for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) {
@ -656,7 +639,7 @@ StorePath Installable::toStorePath(
ref<Store> evalStore,
ref<Store> store,
Realise mode, OperateOn operateOn,
std::shared_ptr<Installable> installable)
ref<Installable> installable)
{
auto paths = toStorePaths(evalStore, store, mode, operateOn, {installable});
@ -668,7 +651,7 @@ StorePath Installable::toStorePath(
StorePathSet Installable::toDerivations(
ref<Store> store,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
bool useDeriver)
{
StorePathSet drvPaths;
@ -677,9 +660,12 @@ StorePathSet Installable::toDerivations(
for (const auto & b : i->toDerivedPaths())
std::visit(overloaded {
[&](const DerivedPath::Opaque & bo) {
if (!useDeriver)
throw Error("argument '%s' did not evaluate to a derivation", i->what());
drvPaths.insert(getDeriver(store, *i, bo.path));
drvPaths.insert(
bo.path.isDerivation()
? bo.path
: useDeriver
? getDeriver(store, *i, bo.path)
: throw Error("argument '%s' did not evaluate to a derivation", i->what()));
},
[&](const DerivedPath::Built & bfd) {
drvPaths.insert(bfd.drvPath);
@ -689,36 +675,55 @@ StorePathSet Installable::toDerivations(
return drvPaths;
}
InstallablesCommand::InstallablesCommand()
RawInstallablesCommand::RawInstallablesCommand()
{
addFlag({
.longName = "stdin",
.description = "Read installables from the standard input.",
.handler = {&readFromStdIn, true}
});
expectArgs({
.label = "installables",
.handler = {&_installables},
.handler = {&rawInstallables},
.completer = {[&](size_t, std::string_view prefix) {
completeInstallable(prefix);
}}
});
}
void InstallablesCommand::prepare()
void RawInstallablesCommand::applyDefaultInstallables(std::vector<std::string> & rawInstallables)
{
installables = load();
}
Installables InstallablesCommand::load()
{
if (_installables.empty() && useDefaultInstallables())
if (rawInstallables.empty()) {
// FIXME: commands like "nix profile install" should not have a
// default, probably.
_installables.push_back(".");
return parseInstallables(getStore(), _installables);
rawInstallables.push_back(".");
}
}
std::vector<std::string> InstallablesCommand::getFlakesForCompletion()
void RawInstallablesCommand::run(ref<Store> store)
{
if (_installables.empty() && useDefaultInstallables())
return {"."};
return _installables;
if (readFromStdIn && !isatty(STDIN_FILENO)) {
std::string word;
while (std::cin >> word) {
rawInstallables.emplace_back(std::move(word));
}
}
applyDefaultInstallables(rawInstallables);
run(store, std::move(rawInstallables));
}
std::vector<std::string> RawInstallablesCommand::getFlakesForCompletion()
{
applyDefaultInstallables(rawInstallables);
return rawInstallables;
}
void InstallablesCommand::run(ref<Store> store, std::vector<std::string> && rawInstallables)
{
auto installables = parseInstallables(store, rawInstallables);
run(store, std::move(installables));
}
InstallableCommand::InstallableCommand()
@ -734,9 +739,16 @@ InstallableCommand::InstallableCommand()
});
}
void InstallableCommand::prepare()
void InstallableCommand::run(ref<Store> store)
{
installable = parseInstallable(getStore(), _installable);
auto installable = parseInstallable(store, _installable);
run(store, std::move(installable));
}
void BuiltPathsCommand::applyDefaultInstallables(std::vector<std::string> & rawInstallables)
{
if (rawInstallables.empty() && !all)
rawInstallables.push_back(".");
}
}

View file

@ -4,9 +4,7 @@
#include "path.hh"
#include "outputs-spec.hh"
#include "derived-path.hh"
#include "eval.hh"
#include "store-api.hh"
#include "flake/flake.hh"
#include "build-result.hh"
#include <optional>
@ -14,122 +12,156 @@
namespace nix {
struct DrvInfo;
struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
struct App
{
std::vector<DerivedPath> context;
Path program;
// FIXME: add args, sandbox settings, metadata, ...
};
struct UnresolvedApp
{
App unresolved;
App resolve(ref<Store> evalStore, ref<Store> store);
};
enum class Realise {
/* Build the derivation. Postcondition: the
derivation outputs exist. */
/**
* Build the derivation.
*
* Postcondition: the derivation outputs exist.
*/
Outputs,
/* Don't build the derivation. Postcondition: the store derivation
exists. */
/**
* Don't build the derivation.
*
* Postcondition: the store derivation exists.
*/
Derivation,
/* Evaluate in dry-run mode. Postcondition: nothing. */
// FIXME: currently unused, but could be revived if we can
// evaluate derivations in-memory.
/**
* Evaluate in dry-run mode.
*
* Postcondition: nothing.
*
* \todo currently unused, but could be revived if we can evaluate
* derivations in-memory.
*/
Nothing
};
/* How to handle derivations in commands that operate on store paths. */
/**
* How to handle derivations in commands that operate on store paths.
*/
enum class OperateOn {
/* Operate on the output path. */
/**
* Operate on the output path.
*/
Output,
/* Operate on the .drv path. */
/**
* Operate on the .drv path.
*/
Derivation
};
/**
* Extra info about a DerivedPath
*
* Yes, this is empty, but that is intended. It will be sub-classed by
* the subclasses of Installable to allow those to provide more info.
* Certain commands will make use of this info.
*/
struct ExtraPathInfo
{
std::optional<NixInt> priority;
std::optional<FlakeRef> originalRef;
std::optional<FlakeRef> resolvedRef;
std::optional<std::string> attrPath;
// FIXME: merge with DerivedPath's 'outputs' field?
std::optional<ExtendedOutputsSpec> extendedOutputsSpec;
virtual ~ExtraPathInfo() = default;
};
/* A derived path with any additional info that commands might
need from the derivation. */
/**
* A DerivedPath with \ref ExtraPathInfo "any additional info" that
* commands might need from the derivation.
*/
struct DerivedPathWithInfo
{
DerivedPath path;
ExtraPathInfo info;
ref<ExtraPathInfo> info;
};
/**
* Like DerivedPathWithInfo but extending BuiltPath with \ref
* ExtraPathInfo "extra info" and also possibly the \ref BuildResult
* "result of building".
*/
struct BuiltPathWithResult
{
BuiltPath path;
ExtraPathInfo info;
ref<ExtraPathInfo> info;
std::optional<BuildResult> result;
};
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
*/
typedef std::vector<DerivedPathWithInfo> DerivedPathsWithInfo;
struct Installable;
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
*/
typedef std::vector<ref<Installable>> Installables;
/**
* Installables are the main positional arguments for the Nix
* Command-line.
*
* This base class is very flexible, and just assumes and the
* Installable refers to a collection of \ref DerivedPath "derived paths" with
* \ref ExtraPathInfo "extra info".
*/
struct Installable
{
virtual ~Installable() { }
/**
* What Installable is this?
*
* Prints back valid CLI syntax that would result in this same
* installable. It doesn't need to be exactly what the user wrote,
* just something that means the same thing.
*/
virtual std::string what() const = 0;
/**
* Get the collection of \ref DerivedPathWithInfo "derived paths
* with info" that this \ref Installable instalallable denotes.
*
* This is the main method of this class
*/
virtual DerivedPathsWithInfo toDerivedPaths() = 0;
/**
* A convenience wrapper of the above for when we expect an
* installable to produce a single \ref DerivedPath "derived path"
* only.
*
* If no or multiple \ref DerivedPath "derived paths" are produced,
* and error is raised.
*/
DerivedPathWithInfo toDerivedPath();
UnresolvedApp toApp(EvalState & state);
virtual std::pair<Value *, PosIdx> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
}
/* Return a value only if this installable is a store path or a
symlink to it. */
/**
* Return a value only if this installable is a store path or a
* symlink to it.
*
* \todo should we move this to InstallableDerivedPath? It is only
* supposed to work there anyways. Can always downcast.
*/
virtual std::optional<StorePath> getStorePath()
{
return {};
}
/* Get a cursor to each value this Installable could refer to. However
if none exists, throw exception instead of returning empty vector. */
virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state);
/* Get the first and most preferred cursor this Installable could refer
to, or throw an exception if none exists. */
virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state);
virtual FlakeRef nixpkgsFlakeRef() const
{
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
}
static std::vector<BuiltPathWithResult> build(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
BuildMode bMode = bmNormal);
static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> build2(
static std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> build2(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
BuildMode bMode = bmNormal);
static std::set<StorePath> toStorePaths(
@ -137,18 +169,18 @@ struct Installable
ref<Store> store,
Realise mode,
OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables);
const Installables & installables);
static StorePath toStorePath(
ref<Store> evalStore,
ref<Store> store,
Realise mode,
OperateOn operateOn,
std::shared_ptr<Installable> installable);
ref<Installable> installable);
static std::set<StorePath> toDerivations(
ref<Store> store,
const std::vector<std::shared_ptr<Installable>> & installables,
const Installables & installables,
bool useDeriver = false);
static BuiltPaths toBuiltPaths(
@ -156,9 +188,7 @@ struct Installable
ref<Store> store,
Realise mode,
OperateOn operateOn,
const std::vector<std::shared_ptr<Installable>> & installables);
const Installables & installables);
};
typedef std::vector<std::shared_ptr<Installable>> Installables;
}

View file

@ -8,6 +8,7 @@
#include "eval-inline.hh"
#include "filetransfer.hh"
#include "function-trace.hh"
#include "profiles.hh"
#include <algorithm>
#include <chrono>
@ -368,7 +369,7 @@ void initGC()
size = (pageSize * pages) / 4; // 25% of RAM
if (size > maxSize) size = maxSize;
#endif
debug(format("setting initial heap size to %1% bytes") % size);
debug("setting initial heap size to %1% bytes", size);
GC_expand_hp(size);
}
@ -609,7 +610,7 @@ Path EvalState::checkSourcePath(const Path & path_)
}
/* Resolve symlinks. */
debug(format("checking access to '%s'") % abspath);
debug("checking access to '%s'", abspath);
Path path = canonPath(abspath, true);
for (auto & i : *allowedPaths) {
@ -2491,8 +2492,8 @@ Strings EvalSettings::getDefaultNixPath()
if (!evalSettings.restrictEval && !evalSettings.pureEval) {
add(settings.useXDGBaseDirectories ? getStateDir() + "/nix/defexpr/channels" : getHome() + "/.nix-defexpr/channels");
add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
add(settings.nixStateDir + "/profiles/per-user/root/channels");
add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
add(rootChannelsDir());
}
return res;

View file

@ -320,7 +320,7 @@ LockedFlake lockFlake(
const FlakeRef & topRef,
const LockFlags & lockFlags)
{
settings.requireExperimentalFeature(Xp::Flakes);
experimentalFeatureSettings.require(Xp::Flakes);
FlakeCache flakeCache;

View file

@ -469,7 +469,7 @@ expr_simple
new ExprString(std::move(path))});
}
| URI {
static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals);
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
if (noURLLiterals)
throw ParseError({
.msg = hintfmt("URL literals are disabled"),
@ -732,7 +732,7 @@ Expr * EvalState::parseExprFromString(std::string s, const Path & basePath)
Expr * EvalState::parseStdin()
{
//Activity act(*logger, lvlTalkative, format("parsing standard input"));
//Activity act(*logger, lvlTalkative, "parsing standard input");
auto buffer = drainFD(0);
// drainFD should have left some extra space for terminators
buffer.append("\0\0", 2);
@ -816,7 +816,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
}
else if (hasPrefix(elem.second, "flake:")) {
settings.requireExperimentalFeature(Xp::Flakes);
experimentalFeatureSettings.require(Xp::Flakes);
auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false);
debug("fetching flake search path element '%s''", elem.second);
auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath;
@ -835,7 +835,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
}
}
debug(format("resolved search path element '%s' to '%s'") % elem.second % res.second);
debug("resolved search path element '%s' to '%s'", elem.second, res.second);
searchPathResolved[elem.second] = res;
return res;

View file

@ -254,9 +254,16 @@ static RegisterPrimOp primop_import({
.args = {"path"},
// TODO turn "normal path values" into link below
.doc = R"(
Load, parse and return the Nix expression in the file *path*. If
*path* is a directory, the file ` default.nix ` in that directory
is loaded. Evaluation aborts if the file doesnt exist or contains
Load, parse and return the Nix expression in the file *path*.
The value *path* can be a path, a string, or an attribute set with an
`__toString` attribute or a `outPath` attribute (as derivations or flake
inputs typically have).
If *path* is a directory, the file `default.nix` in that directory
is loaded.
Evaluation aborts if the file doesnt exist or contains
an incorrect Nix expression. `import` implements Nixs module
system: you can put any Nix expression (such as a set or a
function) in a separate file, and use it from Nix expressions in
@ -1141,13 +1148,13 @@ drvName, Bindings * attrs, Value & v)
if (i->name == state.sContentAddressed) {
contentAddressed = state.forceBool(*i->value, noPos, context_below);
if (contentAddressed)
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
}
else if (i->name == state.sImpure) {
isImpure = state.forceBool(*i->value, noPos, context_below);
if (isImpure)
settings.requireExperimentalFeature(Xp::ImpureDerivations);
experimentalFeatureSettings.require(Xp::ImpureDerivations);
}
/* The `args' attribute is special: it supplies the
@ -4126,7 +4133,7 @@ void EvalState::createBaseEnv()
if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps)
if (!primOp.experimentalFeature
|| settings.isExperimentalFeatureEnabled(*primOp.experimentalFeature))
|| experimentalFeatureSettings.isEnabled(*primOp.experimentalFeature))
{
addPrimOp({
.fun = primOp.fun,

View file

@ -190,7 +190,7 @@ static void fetchTree(
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
settings.requireExperimentalFeature(Xp::Flakes);
experimentalFeatureSettings.require(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
}
@ -358,36 +358,44 @@ static RegisterPrimOp primop_fetchGit({
of the repo at that URL is fetched. Otherwise, it can be an
attribute with the following attributes (all except `url` optional):
- url\
The URL of the repo.
- `url`
- name\
The name of the directory the repo should be exported to in the
store. Defaults to the basename of the URL.
The URL of the repo.
- rev\
The git revision to fetch. Defaults to the tip of `ref`.
- `name` (default: *basename of the URL*)
- ref\
The git ref to look for the requested revision under. This is
often a branch or tag name. Defaults to `HEAD`.
The name of the directory the repo should be exported to in the store.
By default, the `ref` value is prefixed with `refs/heads/`. As
of Nix 2.3.0 Nix will not prefix `refs/heads/` if `ref` starts
with `refs/`.
- `rev` (default: *the tip of `ref`*)
- submodules\
A Boolean parameter that specifies whether submodules should be
checked out. Defaults to `false`.
The [Git revision] to fetch.
This is typically a commit hash.
- shallow\
A Boolean parameter that specifies whether fetching a shallow clone
is allowed. Defaults to `false`.
[Git revision]: https://git-scm.com/docs/git-rev-parse#_specifying_revisions
- 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).
- `ref` (default: `HEAD`)
The [Git reference] under which to look for the requested revision.
This is often a branch or tag name.
[Git reference]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
By default, the `ref` value is prefixed with `refs/heads/`.
As of 2.3.0, Nix will not prefix `refs/heads/` if `ref` starts with `refs/`.
- `submodules` (default: `false`)
A Boolean parameter that specifies whether submodules should be checked out.
- `shallow` (default: `false`)
A Boolean parameter that specifies whether fetching a shallow clone is allowed.
- `allRefs`
Whether to fetch all references 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`.
@ -478,10 +486,10 @@ static RegisterPrimOp primop_fetchGit({
builtins.fetchGit ./work-dir
```
If the URL points to a local directory, and no `ref` or `rev` is
given, `fetchGit` will use the current content of the checked-out
files, even if they are not committed or added to Git's index. It will
only consider files added to the Git repository, as listed by `git ls-files`.
If the URL points to a local directory, and no `ref` or `rev` is
given, `fetchGit` will use the current content of the checked-out
files, even if they are not committed or added to Git's index. It will
only consider files added to the Git repository, as listed by `git ls-files`.
)",
.fun = prim_fetchGit,
});

View file

@ -15,8 +15,8 @@ namespace nix {
return oss.str();
}
void log(Verbosity lvl, const FormatOrString & fs) override {
oss << fs.s << std::endl;
void log(Verbosity lvl, std::string_view s) override {
oss << s << std::endl;
}
void logEI(const ErrorInfo & ei) override {

View file

@ -26,8 +26,8 @@ static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{
if (auto path = std::get_if<Path>(&pos.origin))
xmlAttrs["path"] = *path;
xmlAttrs["line"] = (format("%1%") % pos.line).str();
xmlAttrs["column"] = (format("%1%") % pos.column).str();
xmlAttrs["line"] = fmt("%1%", pos.line);
xmlAttrs["column"] = fmt("%1%", pos.column);
}
@ -64,7 +64,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
switch (v.type()) {
case nInt:
doc.writeEmptyElement("int", singletonAttrs("value", (format("%1%") % v.integer).str()));
doc.writeEmptyElement("int", singletonAttrs("value", fmt("%1%", v.integer)));
break;
case nBool:
@ -156,7 +156,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break;
case nFloat:
doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
doc.writeEmptyElement("float", singletonAttrs("value", fmt("%1%", v.fpoint)));
break;
case nThunk:

View file

@ -75,21 +75,25 @@ struct FetchSettings : public Config
Path or URI of the global flake registry.
When empty, disables the global flake registry.
)"};
)",
{}, true, Xp::Flakes};
Setting<bool> useRegistries{this, true, "use-registries",
"Whether to use flake registries to resolve flake references."};
"Whether to use flake registries to resolve flake references.",
{}, true, Xp::Flakes};
Setting<bool> acceptFlakeConfig{this, false, "accept-flake-config",
"Whether to accept nix configuration from a flake without prompting."};
"Whether to accept nix configuration from a flake without prompting.",
{}, true, Xp::Flakes};
Setting<std::string> commitLockFileSummary{
this, "", "commit-lockfile-summary",
R"(
The commit summary to use when committing changed flake lock files. If
empty, the summary is generated based on the action performed.
)"};
)",
{}, true, Xp::Flakes};
};
// FIXME: don't use a global variable.

View file

@ -63,6 +63,11 @@ public:
one that contains a commit hash or content hash. */
bool isLocked() const { return locked; }
/* Check whether the input carries all necessary info required
for cache insertion and substitution.
These fields are used to uniquely identify cached trees
within the "tarball TTL" window without necessarily
indicating that the input's origin is unchanged. */
bool hasAllInfo() const;
bool operator ==(const Input & other) const;

View file

@ -266,7 +266,7 @@ struct GitInputScheme : InputScheme
for (auto & [name, value] : url.query) {
if (name == "rev" || name == "ref")
attrs.emplace(name, value);
else if (name == "shallow" || name == "submodules")
else if (name == "shallow" || name == "submodules" || name == "allRefs")
attrs.emplace(name, Explicit<bool> { value == "1" });
else
url2.query.emplace(name, value);

View file

@ -125,11 +125,11 @@ public:
return printBuildLogs;
}
void log(Verbosity lvl, const FormatOrString & fs) override
void log(Verbosity lvl, std::string_view s) override
{
if (lvl > verbosity) return;
auto state(state_.lock());
log(*state, lvl, fs.s);
log(*state, lvl, s);
}
void logEI(const ErrorInfo & ei) override
@ -142,7 +142,7 @@ public:
log(*state, ei.level, oss.str());
}
void log(State & state, Verbosity lvl, const std::string & s)
void log(State & state, Verbosity lvl, std::string_view s)
{
if (state.active) {
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n");

View file

@ -84,8 +84,18 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
downloadSizeMiB,
narSizeMiB);
}
for (auto & i : willSubstitute)
printMsg(lvl, " %s", store->printStorePath(i));
std::vector<const StorePath *> willSubstituteSorted = {};
std::for_each(willSubstitute.begin(), willSubstitute.end(),
[&](const StorePath &p) { willSubstituteSorted.push_back(&p); });
std::sort(willSubstituteSorted.begin(), willSubstituteSorted.end(),
[](const StorePath *lhs, const StorePath *rhs) {
if (lhs->name() == rhs->name())
return lhs->to_string() < rhs->to_string();
else
return lhs->name() < rhs->name();
});
for (auto p : willSubstituteSorted)
printMsg(lvl, " %s", store->printStorePath(*p));
}
if (!unknown.empty()) {
@ -347,7 +357,7 @@ void parseCmdLine(const std::string & programName, const Strings & args,
void printVersion(const std::string & programName)
{
std::cout << format("%1% (Nix) %2%") % programName % nixVersion << std::endl;
std::cout << fmt("%1% (Nix) %2%", programName, nixVersion) << std::endl;
if (verbosity > lvlInfo) {
Strings cfg;
#if HAVE_BOEHMGC

View file

@ -16,17 +16,33 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<std::string> compression{(StoreConfig*) this, "xz", "compression", "NAR compression method ('xz', 'bzip2', 'gzip', 'zstd', or 'none')"};
const Setting<bool> writeNARListing{(StoreConfig*) this, false, "write-nar-listing", "whether to write a JSON file listing the files in each NAR"};
const Setting<bool> writeDebugInfo{(StoreConfig*) this, false, "index-debug-info", "whether to index DWARF debug info files by build ID"};
const Setting<Path> secretKeyFile{(StoreConfig*) this, "", "secret-key", "path to secret key used to sign the binary cache"};
const Setting<Path> localNarCache{(StoreConfig*) this, "", "local-nar-cache", "path to a local cache of NARs"};
const Setting<std::string> compression{(StoreConfig*) this, "xz", "compression",
"NAR compression method (`xz`, `bzip2`, `gzip`, `zstd`, or `none`)."};
const Setting<bool> writeNARListing{(StoreConfig*) this, false, "write-nar-listing",
"Whether to write a JSON file that lists the files in each NAR."};
const Setting<bool> writeDebugInfo{(StoreConfig*) this, false, "index-debug-info",
R"(
Whether to index DWARF debug info files by build ID. This allows [`dwarffs`](https://github.com/edolstra/dwarffs) to
fetch debug info on demand
)"};
const Setting<Path> secretKeyFile{(StoreConfig*) this, "", "secret-key",
"Path to the secret key used to sign the binary cache."};
const Setting<Path> localNarCache{(StoreConfig*) this, "", "local-nar-cache",
"Path to a local cache of NARs fetched from this binary cache, used by commands such as `nix store cat`."};
const Setting<bool> parallelCompression{(StoreConfig*) this, false, "parallel-compression",
"enable multi-threading compression for NARs, available for xz and zstd only currently"};
"Enable multi-threaded compression of NARs. This is currently only available for `xz` and `zstd`."};
const Setting<int> compressionLevel{(StoreConfig*) this, -1, "compression-level",
"specify 'preset level' of compression to be used with NARs: "
"meaning and accepted range of values depends on compression method selected, "
"other than -1 which we reserve to indicate Nix defaults should be used"};
R"(
The *preset level* to be used when compressing NARs.
The meaning and accepted values depend on the compression method selected.
`-1` specifies that the default compression level should be used.
)"};
};
class BinaryCacheStore : public virtual BinaryCacheStoreConfig,

View file

@ -199,10 +199,10 @@ void DerivationGoal::haveDerivation()
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
if (!drv->type().hasKnownOutputPaths())
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
if (!drv->type().isPure()) {
settings.requireExperimentalFeature(Xp::ImpureDerivations);
experimentalFeatureSettings.require(Xp::ImpureDerivations);
for (auto & [outputName, output] : drv->outputs) {
auto randomPath = StorePath::random(outputPathName(drv->name, outputName));
@ -336,7 +336,7 @@ void DerivationGoal::gaveUpOnSubstitution()
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
/* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */
if (settings.isExperimentalFeatureEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
if (experimentalFeatureSettings.isEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = worker.evalStore.readDerivation(i.first);
if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'",
@ -477,7 +477,7 @@ void DerivationGoal::inputsRealised()
ca.fixed
/* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */
? settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
? experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
/* Must resolve if floating and there are any inputs
drvs. */
: true);
@ -488,7 +488,7 @@ void DerivationGoal::inputsRealised()
}, drvType.raw());
if (resolveDrv && !fullDrv.inputDrvs.empty()) {
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
/* We are be able to resolve this derivation based on the
now-known results of dependencies. If so, we become a
@ -732,7 +732,7 @@ void replaceValidPath(const Path & storePath, const Path & tmpPath)
tmpPath (the replacement), so we have to move it out of the
way first. We'd better not be interrupted here, because if
we're repairing (say) Glibc, we end up with a broken system. */
Path oldPath = (format("%1%.old-%2%-%3%") % storePath % getpid() % random()).str();
Path oldPath = fmt("%1%.old-%2%-%3%", storePath, getpid(), random());
if (pathExists(storePath))
movePath(storePath, oldPath);
@ -1352,7 +1352,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
};
}
auto drvOutput = DrvOutput{info.outputHash, i.first};
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
if (auto real = worker.store.queryRealisation(drvOutput)) {
info.known = {
.path = real->outPath,

View file

@ -61,20 +61,25 @@ void DrvOutputSubstitutionGoal::tryNext()
// FIXME: Make async
// outputInfo = sub->queryRealisation(id);
outPipe.create();
promise = decltype(promise)();
/* The callback of the curl download below can outlive `this` (if
some other error occurs), so it must not touch `this`. So put
the shared state in a separate refcounted object. */
downloadState = std::make_shared<DownloadState>();
downloadState->outPipe.create();
sub->queryRealisation(
id, { [&](std::future<std::shared_ptr<const Realisation>> res) {
id,
{ [downloadState(downloadState)](std::future<std::shared_ptr<const Realisation>> res) {
try {
Finally updateStats([this]() { outPipe.writeSide.close(); });
promise.set_value(res.get());
Finally updateStats([&]() { downloadState->outPipe.writeSide.close(); });
downloadState->promise.set_value(res.get());
} catch (...) {
promise.set_exception(std::current_exception());
downloadState->promise.set_exception(std::current_exception());
}
} });
worker.childStarted(shared_from_this(), {outPipe.readSide.get()}, true, false);
worker.childStarted(shared_from_this(), {downloadState->outPipe.readSide.get()}, true, false);
state = &DrvOutputSubstitutionGoal::realisationFetched;
}
@ -84,7 +89,7 @@ void DrvOutputSubstitutionGoal::realisationFetched()
worker.childTerminated(this);
try {
outputInfo = promise.get_future().get();
outputInfo = downloadState->promise.get_future().get();
} catch (std::exception & e) {
printError(e.what());
substituterFailed = true;
@ -155,7 +160,7 @@ void DrvOutputSubstitutionGoal::work()
void DrvOutputSubstitutionGoal::handleEOF(int fd)
{
if (fd == outPipe.readSide.get()) worker.wakeUp(shared_from_this());
if (fd == downloadState->outPipe.readSide.get()) worker.wakeUp(shared_from_this());
}

View file

@ -16,7 +16,7 @@ class Worker;
// 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;
@ -30,9 +30,13 @@ private:
/* The current substituter. */
std::shared_ptr<Store> sub;
Pipe outPipe;
std::thread thr;
std::promise<std::shared_ptr<const Realisation>> promise;
struct DownloadState
{
Pipe outPipe;
std::promise<std::shared_ptr<const Realisation>> promise;
};
std::shared_ptr<DownloadState> downloadState;
/* Whether a substituter failed. */
bool substituterFailed = false;

View file

@ -78,9 +78,9 @@ void Goal::amDone(ExitCode result, std::optional<Error> ex)
}
void Goal::trace(const FormatOrString & fs)
void Goal::trace(std::string_view s)
{
debug("%1%: %2%", name, fs.s);
debug("%1%: %2%", name, s);
}
}

View file

@ -88,7 +88,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
abort();
}
void trace(const FormatOrString & fs);
void trace(std::string_view s);
std::string getName()
{

View file

@ -35,7 +35,10 @@ HookInstance::HookInstance()
/* Fork the hook. */
pid = startProcess([&]() {
commonChildInit(fromHook);
if (dup2(fromHook.writeSide.get(), STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file");
commonChildInit();
if (chdir("/") == -1) throw SysError("changing into /");

View file

@ -292,7 +292,7 @@ void LocalDerivationGoal::closeReadPipes()
if (hook) {
DerivationGoal::closeReadPipes();
} else
builderOut.readSide = -1;
builderOut.close();
}
@ -413,7 +413,7 @@ void LocalDerivationGoal::startBuilder()
)
{
#if __linux__
settings.requireExperimentalFeature(Xp::Cgroups);
experimentalFeatureSettings.require(Xp::Cgroups);
auto cgroupFS = getCgroupFS();
if (!cgroupFS)
@ -650,7 +650,7 @@ void LocalDerivationGoal::startBuilder()
/* Clean up the chroot directory automatically. */
autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootRootDir);
// FIXME: make this 0700
if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
@ -753,8 +753,7 @@ void LocalDerivationGoal::startBuilder()
throw Error("home directory '%1%' exists; please remove it to assure purity of builds without sandboxing", homeDir);
if (useChroot && settings.preBuildHook != "" && dynamic_cast<Derivation *>(drv.get())) {
printMsg(lvlChatty, format("executing pre-build hook '%1%'")
% settings.preBuildHook);
printMsg(lvlChatty, "executing pre-build hook '%1%'", settings.preBuildHook);
auto args = useChroot ? Strings({worker.store.printStorePath(drvPath), chrootRootDir}) :
Strings({ worker.store.printStorePath(drvPath) });
enum BuildHookState {
@ -803,15 +802,13 @@ void LocalDerivationGoal::startBuilder()
/* Create the log file. */
Path logFile = openLogFile();
/* Create a pipe to get the output of the builder. */
//builderOut.create();
builderOut.readSide = posix_openpt(O_RDWR | O_NOCTTY);
if (!builderOut.readSide)
/* Create a pseudoterminal to get the output of the builder. */
builderOut = posix_openpt(O_RDWR | O_NOCTTY);
if (!builderOut)
throw SysError("opening pseudoterminal master");
// FIXME: not thread-safe, use ptsname_r
std::string slaveName(ptsname(builderOut.readSide.get()));
std::string slaveName = ptsname(builderOut.get());
if (buildUser) {
if (chmod(slaveName.c_str(), 0600))
@ -822,34 +819,34 @@ void LocalDerivationGoal::startBuilder()
}
#if __APPLE__
else {
if (grantpt(builderOut.readSide.get()))
if (grantpt(builderOut.get()))
throw SysError("granting access to pseudoterminal slave");
}
#endif
#if 0
// Mount the pt in the sandbox so that the "tty" command works.
// FIXME: this doesn't work with the new devpts in the sandbox.
if (useChroot)
dirsInChroot[slaveName] = {slaveName, false};
#endif
if (unlockpt(builderOut.readSide.get()))
if (unlockpt(builderOut.get()))
throw SysError("unlocking pseudoterminal");
builderOut.writeSide = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
if (!builderOut.writeSide)
throw SysError("opening pseudoterminal slave");
/* Open the slave side of the pseudoterminal and use it as stderr. */
auto openSlave = [&]()
{
AutoCloseFD builderOut = open(slaveName.c_str(), O_RDWR | O_NOCTTY);
if (!builderOut)
throw SysError("opening pseudoterminal slave");
// Put the pt into raw mode to prevent \n -> \r\n translation.
struct termios term;
if (tcgetattr(builderOut.writeSide.get(), &term))
throw SysError("getting pseudoterminal attributes");
// Put the pt into raw mode to prevent \n -> \r\n translation.
struct termios term;
if (tcgetattr(builderOut.get(), &term))
throw SysError("getting pseudoterminal attributes");
cfmakeraw(&term);
cfmakeraw(&term);
if (tcsetattr(builderOut.writeSide.get(), TCSANOW, &term))
throw SysError("putting pseudoterminal into raw mode");
if (tcsetattr(builderOut.get(), TCSANOW, &term))
throw SysError("putting pseudoterminal into raw mode");
if (dup2(builderOut.get(), STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file");
};
buildResult.startTime = time(0);
@ -898,7 +895,16 @@ void LocalDerivationGoal::startBuilder()
usingUserNamespace = userNamespacesSupported();
Pipe sendPid;
sendPid.create();
Pid helper = startProcess([&]() {
sendPid.readSide.close();
/* We need to open the slave early, before
CLONE_NEWUSER. Otherwise we get EPERM when running as
root. */
openSlave();
/* Drop additional groups here because we can't do it
after we've created the new user namespace. FIXME:
@ -920,11 +926,12 @@ void LocalDerivationGoal::startBuilder()
pid_t child = startProcess([&]() { runChild(); }, options);
writeFull(builderOut.writeSide.get(),
fmt("%d %d\n", usingUserNamespace, child));
writeFull(sendPid.writeSide.get(), fmt("%d\n", child));
_exit(0);
});
sendPid.writeSide.close();
if (helper.wait() != 0)
throw Error("unable to start build process");
@ -936,10 +943,9 @@ void LocalDerivationGoal::startBuilder()
userNamespaceSync.writeSide = -1;
});
auto ss = tokenizeString<std::vector<std::string>>(readLine(builderOut.readSide.get()));
assert(ss.size() == 2);
usingUserNamespace = ss[0] == "1";
pid = string2Int<pid_t>(ss[1]).value();
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
assert(ss.size() == 1);
pid = string2Int<pid_t>(ss[0]).value();
if (usingUserNamespace) {
/* Set the UID/GID mapping of the builder's user namespace
@ -994,21 +1000,21 @@ void LocalDerivationGoal::startBuilder()
#endif
{
pid = startProcess([&]() {
openSlave();
runChild();
});
}
/* parent */
pid.setSeparatePG(true);
builderOut.writeSide = -1;
worker.childStarted(shared_from_this(), {builderOut.readSide.get()}, true, true);
worker.childStarted(shared_from_this(), {builderOut.get()}, true, true);
/* Check if setting up the build environment failed. */
std::vector<std::string> msgs;
while (true) {
std::string msg = [&]() {
try {
return readLine(builderOut.readSide.get());
return readLine(builderOut.get());
} catch (Error & e) {
auto status = pid.wait();
e.addTrace({}, "while waiting for the build environment for '%s' to initialize (%s, previous messages: %s)",
@ -1020,7 +1026,7 @@ void LocalDerivationGoal::startBuilder()
}();
if (msg.substr(0, 1) == "\2") break;
if (msg.substr(0, 1) == "\1") {
FdSource source(builderOut.readSide.get());
FdSource source(builderOut.get());
auto ex = readError(source);
ex.addTrace({}, "while setting up the build environment");
throw ex;
@ -1104,7 +1110,7 @@ void LocalDerivationGoal::initEnv()
env["NIX_STORE"] = worker.store.storeDir;
/* The maximum number of cores to utilize for parallel building. */
env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str();
env["NIX_BUILD_CORES"] = fmt("%d", settings.buildCores);
initTmpDir();
@ -1155,10 +1161,10 @@ void LocalDerivationGoal::writeStructuredAttrs()
writeFile(tmpDir + "/.attrs.sh", rewriteStrings(jsonSh, inputRewrites));
chownToBuilder(tmpDir + "/.attrs.sh");
env["NIX_ATTRS_SH_FILE"] = tmpDir + "/.attrs.sh";
env["NIX_ATTRS_SH_FILE"] = tmpDirInSandbox + "/.attrs.sh";
writeFile(tmpDir + "/.attrs.json", rewriteStrings(json.dump(), inputRewrites));
chownToBuilder(tmpDir + "/.attrs.json");
env["NIX_ATTRS_JSON_FILE"] = tmpDir + "/.attrs.json";
env["NIX_ATTRS_JSON_FILE"] = tmpDirInSandbox + "/.attrs.json";
}
}
@ -1414,7 +1420,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
void LocalDerivationGoal::startDaemon()
{
settings.requireExperimentalFeature(Xp::RecursiveNix);
experimentalFeatureSettings.require(Xp::RecursiveNix);
Store::Params params;
params["path-info-cache-size"] = "0";
@ -1650,7 +1656,7 @@ void LocalDerivationGoal::runChild()
try { /* child */
commonChildInit(builderOut);
commonChildInit();
try {
setupSeccomp();
@ -2063,7 +2069,7 @@ void LocalDerivationGoal::runChild()
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
Path globalTmpDir = canonPath(getEnv("TMPDIR").value_or("/tmp"), true);
Path globalTmpDir = canonPath(getEnvNonEmpty("TMPDIR").value_or("/tmp"), true);
/* They don't like trailing slashes on subpath directives */
if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
@ -2274,7 +2280,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
bool discardReferences = false;
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) {
settings.requireExperimentalFeature(Xp::DiscardReferences);
experimentalFeatureSettings.require(Xp::DiscardReferences);
if (auto output = get(*udr, outputName)) {
if (!output->is_boolean())
throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string());
@ -2686,7 +2692,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
},
.outPath = newInfo.path
};
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)
&& drv->type().isPure())
{
signRealisation(thisRealisation);
@ -2884,7 +2890,7 @@ void LocalDerivationGoal::deleteTmpDir(bool force)
bool LocalDerivationGoal::isReadDesc(int fd)
{
return (hook && DerivationGoal::isReadDesc(fd)) ||
(!hook && fd == builderOut.readSide.get());
(!hook && fd == builderOut.get());
}

View file

@ -24,8 +24,9 @@ struct LocalDerivationGoal : public DerivationGoal
/* The path of the temporary directory in the sandbox. */
Path tmpDirInSandbox;
/* Pipe for the builder's standard output/error. */
Pipe builderOut;
/* Master side of the pseudoterminal used for the builder's
standard output/error. */
AutoCloseFD builderOut;
/* Pipe for synchronising updates to the builder namespaces. */
Pipe userNamespaceSync;

View file

@ -92,13 +92,11 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
if (S_ISLNK(dstSt.st_mode)) {
auto prevPriority = state.priorities[dstFile];
if (prevPriority == priority)
throw Error(
"files '%1%' and '%2%' have the same priority %3%; "
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
"or type 'nix profile install --help' if using 'nix profile' to find out how "
"to change the priority of one of the conflicting packages"
" (0 being the highest priority)",
srcFile, readLink(dstFile), priority);
throw BuildEnvFileConflictError(
readLink(dstFile),
srcFile,
priority
);
if (prevPriority < priority)
continue;
if (unlink(dstFile.c_str()) == -1)

View file

@ -12,6 +12,32 @@ struct Package {
Package(const Path & path, bool active, int priority) : path{path}, active{active}, priority{priority} {}
};
class BuildEnvFileConflictError : public Error
{
public:
const Path fileA;
const Path fileB;
int priority;
BuildEnvFileConflictError(
const Path fileA,
const Path fileB,
int priority
)
: Error(
"Unable to build profile. There is a conflict for the following files:\n"
"\n"
" %1%\n"
" %2%",
fileA,
fileB
)
, fileA(fileA)
, fileB(fileB)
, priority(priority)
{}
};
typedef std::vector<Package> Packages;
void buildProfile(const Path & out, Packages && pkgs);

View file

@ -22,13 +22,6 @@ std::string makeFileIngestionPrefix(FileIngestionMethod m)
}
}
std::string makeFixedOutputCA(FileIngestionMethod method, const Hash & hash)
{
return "fixed:"
+ makeFileIngestionPrefix(method)
+ hash.to_string(Base32, true);
}
std::string renderContentAddress(ContentAddress ca)
{
return std::visit(overloaded {

View file

@ -14,10 +14,28 @@ namespace nix {
/* We only have one way to hash text with references, so this is a single-value
type, mainly useful with std::variant.
*/
/**
* The single way we can serialize "text" file system objects.
*
* Somewhat obscure, used by \ref Derivation derivations and
* `builtins.toFile` currently.
*/
struct TextHashMethod : std::monostate { };
/**
* An enumeration of the main ways we can serialize file system
* objects.
*/
enum struct FileIngestionMethod : uint8_t {
/**
* Flat-file hashing. Directly ingest the contents of a single file
*/
Flat = false,
/**
* Recursive (or NAR) hashing. Serializes the file-system object in Nix
* Archive format and ingest that
*/
Recursive = true
};
@ -26,15 +44,21 @@ struct FixedOutputHashMethod {
HashType hashType;
};
/* Compute the prefix to the hash algorithm which indicates how the files were
ingested. */
/**
* Compute the prefix to the hash algorithm which indicates how the
* files were ingested.
*/
std::string makeFileIngestionPrefix(FileIngestionMethod m);
/* Just the type of a content address. Combine with the hash itself, and we
have a `ContentAddress` as defined below. Combine that, in turn, with info
on references, and we have `ContentAddressWithReferences`, as defined
further below. */
/**
* An enumeration of all the ways we can serialize file system objects.
*
* Just the type of a content address. Combine with the hash itself, and
* we have a `ContentAddress` as defined below. Combine that, in turn,
* with info on references, and we have `ContentAddressWithReferences`,
* as defined further below.
*/
typedef std::variant<
TextHashMethod,
FixedOutputHashMethod
@ -48,37 +72,58 @@ std::string renderContentAddressMethod(ContentAddressMethod caMethod);
* Mini content address
*/
/**
* Somewhat obscure, used by \ref Derivation derivations and
* `builtins.toFile` currently.
*/
struct TextHash {
/**
* Hash of the contents of the text/file.
*/
Hash hash;
GENERATE_CMP(TextHash, me->hash);
};
/// Pair of a hash, and how the file system was ingested
/**
* Used by most store objects that are content-addressed.
*/
struct FixedOutputHash {
/**
* How the file system objects are serialized
*/
FileIngestionMethod method;
/**
* Hash of that serialization
*/
Hash hash;
std::string printMethodAlgo() const;
GENERATE_CMP(FixedOutputHash, me->method, me->hash);
};
/*
We've accumulated several types of content-addressed paths over the years;
fixed-output derivations support multiple hash algorithms and serialisation
methods (flat file vs NAR). Thus, ca has one of the following forms:
* text:sha256:<sha256 hash of file contents>: For paths
computed by makeTextPath() / addTextToStore().
* fixed:<r?>:<ht>:<h>: For paths computed by
makeFixedOutputPath() / addToStore().
*/
/**
* We've accumulated several types of content-addressed paths over the
* years; fixed-output derivations support multiple hash algorithms and
* serialisation methods (flat file vs NAR). Thus, ca has one of the
* following forms:
*
* - text:sha256:<sha256 hash of file contents>: For paths
* computed by Store::makeTextPath() / Store::addTextToStore().
*
* - fixed:<r?>:<ht>:<h>: For paths computed by
* Store::makeFixedOutputPath() / Store::addToStore().
*/
typedef std::variant<
TextHash, // for paths computed by makeTextPath() / addTextToStore
FixedOutputHash // for path computed by makeFixedOutputPath
TextHash,
FixedOutputHash
> ContentAddress;
/**
* Compute the content-addressability assertion (ValidPathInfo::ca) for
* paths created by Store::makeFixedOutputPath() / Store::addToStore().
*/
std::string renderContentAddress(ContentAddress ca);
std::string renderContentAddress(std::optional<ContentAddress> ca);
@ -90,15 +135,33 @@ std::optional<ContentAddress> parseContentAddressOpt(std::string_view rawCaOpt);
Hash getContentAddressHash(const ContentAddress & ca);
/*
* References set
/**
* A set of references to other store objects.
*
* References to other store objects are tracked with store paths, self
* references however are tracked with a boolean.
*/
struct StoreReferences {
/**
* References to other store objects
*/
StorePathSet others;
/**
* Reference to this store object
*/
bool self = false;
/**
* @return true iff no references, i.e. others is empty and self is
* false.
*/
bool empty() const;
/**
* Returns the numbers of references, i.e. the size of others + 1
* iff self is true.
*/
size_t size() const;
GENERATE_CMP(StoreReferences, me->self, me->others);
@ -113,7 +176,10 @@ struct StoreReferences {
// This matches the additional info that we need for makeTextPath
struct TextInfo {
TextHash hash;
// References for the paths, self references disallowed
/**
* References to other store objects only; self references
* disallowed
*/
StorePathSet references;
GENERATE_CMP(TextInfo, me->hash, me->references);
@ -121,17 +187,28 @@ struct TextInfo {
struct FixedOutputInfo {
FixedOutputHash hash;
// References for the paths
/**
* References to other store objects or this one.
*/
StoreReferences references;
GENERATE_CMP(FixedOutputInfo, me->hash, me->references);
};
/**
* Ways of content addressing but not a complete ContentAddress.
*
* A ContentAddress without a Hash.
*/
typedef std::variant<
TextInfo,
FixedOutputInfo
> ContentAddressWithReferences;
/**
* Create a ContentAddressWithReferences from a mere ContentAddress, by
* assuming no references in all cases.
*/
ContentAddressWithReferences caWithoutRefs(const ContentAddress &);
}

View file

@ -67,12 +67,12 @@ struct TunnelLogger : public Logger
state->pendingMsgs.push_back(s);
}
void log(Verbosity lvl, const FormatOrString & fs) override
void log(Verbosity lvl, std::string_view s) override
{
if (lvl > verbosity) return;
StringSink buf;
buf << STDERR_NEXT << (fs.s + "\n");
buf << STDERR_NEXT << (s + "\n");
enqueueMsg(buf.s);
}
@ -231,10 +231,10 @@ struct ClientSettings
try {
if (name == "ssh-auth-sock") // obsolete
;
else if (name == settings.experimentalFeatures.name) {
else if (name == experimentalFeatureSettings.experimentalFeatures.name) {
// We dont want to forward the experimental features to
// the daemon, as that could cause some pretty weird stuff
if (parseFeatures(tokenizeString<StringSet>(value)) != settings.experimentalFeatures.get())
if (parseFeatures(tokenizeString<StringSet>(value)) != experimentalFeatureSettings.experimentalFeatures.get())
debug("Ignoring the client-specified experimental features");
} else if (name == settings.pluginFiles.name) {
if (tokenizeString<Paths>(value) != settings.pluginFiles.get())

View file

@ -221,7 +221,7 @@ static DerivationOutput parseDerivationOutput(const Store & store,
}
const auto hashType = parseHashType(hashAlgo);
if (hash == "impure") {
settings.requireExperimentalFeature(Xp::ImpureDerivations);
experimentalFeatureSettings.require(Xp::ImpureDerivations);
assert(pathS == "");
return DerivationOutput::Impure {
.method = std::move(method),
@ -236,7 +236,7 @@ static DerivationOutput parseDerivationOutput(const Store & store,
},
};
} else {
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
assert(pathS == "");
return DerivationOutput::CAFloating {
.method = std::move(method),

View file

@ -17,42 +17,72 @@ class Store;
/* Abstract syntax of derivations. */
/* The traditional non-fixed-output derivation type. */
/**
* The traditional non-fixed-output derivation type.
*/
struct DerivationOutputInputAddressed
{
StorePath path;
};
/* Fixed-output derivations, whose output paths are content addressed
according to that fixed output. */
/**
* Fixed-output derivations, whose output paths are content
* addressed according to that fixed output.
*/
struct DerivationOutputCAFixed
{
FixedOutputHash hash; /* hash used for expected hash computation */
/**
* hash used for expected hash computation
*/
FixedOutputHash hash;
/**
* Return the \ref StorePath "store path" corresponding to this output
*
* @param drvName The name of the derivation this is an output of, without the `.drv`.
* @param outputName The name of this output.
*/
StorePath path(const Store & store, std::string_view drvName, std::string_view outputName) const;
};
/* Floating-output derivations, whose output paths are content addressed, but
not fixed, and so are dynamically calculated from whatever the output ends
up being. */
/**
* Floating-output derivations, whose output paths are content
* addressed, but not fixed, and so are dynamically calculated from
* whatever the output ends up being.
* */
struct DerivationOutputCAFloating
{
/* information used for expected hash computation */
/**
* How the file system objects will be serialized for hashing
*/
FileIngestionMethod method;
/**
* How the serialization will be hashed
*/
HashType hashType;
};
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
* known yet.
/**
* Input-addressed output which depends on a (CA) derivation whose hash
* isn't known yet.
*/
struct DerivationOutputDeferred {};
/* Impure output which is moved to a content-addressed location (like
CAFloating) but isn't registered as a realization.
/**
* Impure output which is moved to a content-addressed location (like
* CAFloating) but isn't registered as a realization.
*/
struct DerivationOutputImpure
{
/* information used for expected hash computation */
/**
* How the file system objects will be serialized for hashing
*/
FileIngestionMethod method;
/**
* How the serialization will be hashed
*/
HashType hashType;
};
@ -64,6 +94,9 @@ typedef std::variant<
DerivationOutputImpure
> _DerivationOutputRaw;
/**
* A single output of a BasicDerivation (and Derivation).
*/
struct DerivationOutput : _DerivationOutputRaw
{
using Raw = _DerivationOutputRaw;
@ -75,9 +108,12 @@ struct DerivationOutput : _DerivationOutputRaw
using Deferred = DerivationOutputDeferred;
using Impure = DerivationOutputImpure;
/* 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 */
/**
* \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
*/
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
inline const Raw & raw() const {
@ -92,26 +128,61 @@ struct DerivationOutput : _DerivationOutputRaw
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
/* These are analogues to the previous DerivationOutputs data type, but they
also contains, for each output, the (optional) store path in which it would
be written. To calculate values of these types, see the corresponding
functions in BasicDerivation */
/**
* These are analogues to the previous DerivationOutputs data type,
* but they also contains, for each output, the (optional) store
* path in which it would be written. To calculate values of these
* types, see the corresponding functions in BasicDerivation.
*/
typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePath>>>
DerivationOutputsAndOptPaths;
/* For inputs that are sub-derivations, we specify exactly which
output IDs we are interested in. */
/**
* For inputs that are sub-derivations, we specify exactly which
* output IDs we are interested in.
*/
typedef std::map<StorePath, StringSet> DerivationInputs;
/**
* Input-addressed derivation types
*/
struct DerivationType_InputAddressed {
/**
* True iff the derivation type can't be determined statically,
* for instance because it (transitively) depends on a content-addressed
* derivation.
*/
bool deferred;
};
/**
* Content-addressed derivation types
*/
struct DerivationType_ContentAddressed {
/**
* Whether the derivation should be built safely inside a sandbox.
*/
bool sandboxed;
/**
* Whether the derivation's outputs' content-addresses are "fixed"
* or "floating.
*
* - Fixed: content-addresses are written down as part of the
* derivation itself. If the outputs don't end up matching the
* build fails.
*
* - Floating: content-addresses are not written down, we do not
* know them until we perform the build.
*/
bool fixed;
};
/**
* Impure derivation type
*
* This is similar at buil-time to the content addressed, not standboxed, not fixed
* type, but has some restrictions on its usage.
*/
struct DerivationType_Impure {
};
@ -128,30 +199,38 @@ struct DerivationType : _DerivationTypeRaw {
using ContentAddressed = DerivationType_ContentAddressed;
using Impure = DerivationType_Impure;
/* Do the outputs of the derivation have paths calculated from their content,
or from the derivation itself? */
/**
* Do the outputs of the derivation have paths calculated from their
* content, or from the derivation itself?
*/
bool isCA() const;
/* Is the content of the outputs fixed a-priori via a hash? Never true for
non-CA derivations. */
/**
* Is the content of the outputs fixed <em>a priori</em> via a hash?
* Never true for non-CA derivations.
*/
bool isFixed() const;
/* Whether the derivation is fully sandboxed. If false, the
sandbox is opened up, e.g. the derivation has access to the
network. Note that whether or not we actually sandbox the
derivation is controlled separately. Always true for non-CA
derivations. */
/**
* Whether the derivation is fully sandboxed. If false, the sandbox
* is opened up, e.g. the derivation has access to the network. Note
* that whether or not we actually sandbox the derivation is
* controlled separately. Always true for non-CA derivations.
*/
bool isSandboxed() const;
/* Whether the derivation is expected to produce the same result
every time, and therefore it only needs to be built once. This
is only false for derivations that have the attribute '__impure
= true'. */
/**
* Whether the derivation is expected to produce the same result
* every time, and therefore it only needs to be built once. This is
* only false for derivations that have the attribute '__impure =
* true'.
*/
bool isPure() const;
/* Does the derivation knows its own output paths?
Only true when there's no floating-ca derivation involved in the
closure, or if fixed output.
/**
* Does the derivation knows its own output paths?
* Only true when there's no floating-ca derivation involved in the
* closure, or if fixed output.
*/
bool hasKnownOutputPaths() const;
@ -175,15 +254,21 @@ struct BasicDerivation
bool isBuiltin() const;
/* Return true iff this is a fixed-output derivation. */
/**
* Return true iff this is a fixed-output derivation.
*/
DerivationType type() const;
/* Return the output names of a derivation. */
/**
* Return the output names of a derivation.
*/
StringSet outputNames() const;
/* Calculates the maps that contains all the DerivationOutputs, but
augmented with knowledge of the Store paths they would be written
into. */
/**
* Calculates the maps that contains all the DerivationOutputs, but
* augmented with knowledge of the Store paths they would be written
* into.
*/
DerivationOutputsAndOptPaths outputsAndOptPaths(const Store & store) const;
static std::string_view nameFromPath(const StorePath & storePath);
@ -191,23 +276,33 @@ struct BasicDerivation
struct Derivation : BasicDerivation
{
DerivationInputs inputDrvs; /* inputs that are sub-derivations */
/**
* inputs that are sub-derivations
*/
DerivationInputs inputDrvs;
/* Print a derivation. */
/**
* Print a derivation.
*/
std::string unparse(const Store & store, bool maskOutputs,
std::map<std::string, StringSet> * actualInputs = nullptr) const;
/* Return the underlying basic derivation but with these changes:
1. Input drvs are emptied, but the outputs of them that were used are
added directly to input sources.
2. Input placeholders are replaced with realized input store paths. */
/**
* Return the underlying basic derivation but with these changes:
*
* 1. Input drvs are emptied, but the outputs of them that were used
* are added directly to input sources.
*
* 2. Input placeholders are replaced with realized input store
* paths.
*/
std::optional<BasicDerivation> tryResolve(Store & store) const;
/* Like the above, but instead of querying the Nix database for
realisations, uses a given mapping from input derivation paths
+ output names to actual output store paths. */
/**
* Like the above, but instead of querying the Nix database for
* realisations, uses a given mapping from input derivation paths +
* output names to actual output store paths.
*/
std::optional<BasicDerivation> tryResolve(
Store & store,
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const;
@ -222,81 +317,108 @@ struct Derivation : BasicDerivation
class Store;
/* Write a derivation to the Nix store, and return its path. */
/**
* Write a derivation to the Nix store, and return its path.
*/
StorePath writeDerivation(Store & store,
const Derivation & drv,
RepairFlag repair = NoRepair,
bool readOnly = false);
/* Read a derivation from a file. */
/**
* Read a derivation from a file.
*/
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name);
// FIXME: remove
/**
* \todo Remove.
*
* Use Path::isDerivation instead.
*/
bool isDerivation(std::string_view fileName);
/* Calculate the name that will be used for the store path for this
output.
This is usually <drv-name>-<output-name>, but is just <drv-name> when
the output name is "out". */
/**
* Calculate the name that will be used for the store path for this
* output.
*
* This is usually <drv-name>-<output-name>, but is just <drv-name> when
* the output name is "out".
*/
std::string outputPathName(std::string_view drvName, std::string_view outputName);
// The hashes modulo of a derivation.
//
// Each output is given a hash, although in practice only the content-addressed
// derivations (fixed-output or not) will have a different hash for each
// output.
/**
* The hashes modulo of a derivation.
*
* Each output is given a hash, although in practice only the content-addressed
* derivations (fixed-output or not) will have a different hash for each
* output.
*/
struct DrvHash {
/**
* Map from output names to hashes
*/
std::map<std::string, Hash> hashes;
enum struct Kind : bool {
// Statically determined derivations.
// This hash will be directly used to compute the output paths
/**
* Statically determined derivations.
* This hash will be directly used to compute the output paths
*/
Regular,
// Floating-output derivations (and their reverse dependencies).
/**
* Floating-output derivations (and their reverse dependencies).
*/
Deferred,
};
/**
* The kind of derivation this is, simplified for just "derivation hash
* modulo" purposes.
*/
Kind kind;
};
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept;
/* Returns hashes with the details of fixed-output subderivations
expunged.
A fixed-output derivation is a derivation whose outputs have a
specified content hash and hash algorithm. (Currently they must have
exactly one output (`out'), which is specified using the `outputHash'
and `outputHashAlgo' attributes, but the algorithm doesn't assume
this.) We don't want changes to such derivations to propagate upwards
through the dependency graph, changing output paths everywhere.
For instance, if we change the url in a call to the `fetchurl'
function, we do not want to rebuild everything depending on it---after
all, (the hash of) the file being downloaded is unchanged. So the
*output paths* should not change. On the other hand, the *derivation
paths* should change to reflect the new dependency graph.
For fixed-output derivations, this returns a map from the name of
each output to its hash, unique up to the output's contents.
For regular derivations, it returns a single hash of the derivation
ATerm, after subderivations have been likewise expunged from that
derivation.
/**
* Returns hashes with the details of fixed-output subderivations
* expunged.
*
* A fixed-output derivation is a derivation whose outputs have a
* specified content hash and hash algorithm. (Currently they must have
* exactly one output (`out'), which is specified using the `outputHash'
* and `outputHashAlgo' attributes, but the algorithm doesn't assume
* this.) We don't want changes to such derivations to propagate upwards
* through the dependency graph, changing output paths everywhere.
*
* For instance, if we change the url in a call to the `fetchurl'
* function, we do not want to rebuild everything depending on it---after
* all, (the hash of) the file being downloaded is unchanged. So the
* *output paths* should not change. On the other hand, the *derivation
* paths* should change to reflect the new dependency graph.
*
* For fixed-output derivations, this returns a map from the name of
* each output to its hash, unique up to the output's contents.
*
* For regular derivations, it returns a single hash of the derivation
* ATerm, after subderivations have been likewise expunged from that
* derivation.
*/
DrvHash 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).
FIXME: what is the Hash in this map?
/**
* Return a map associating each output to a hash that uniquely identifies its
* derivation (modulo the self-references).
*
* \todo What is the Hash in this map?
*/
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv);
/* Memoisation of hashDerivationModulo(). */
/**
* Memoisation of hashDerivationModulo().
*/
typedef std::map<StorePath, DrvHash> DrvHashes;
// FIXME: global, though at least thread-safe.
@ -308,21 +430,25 @@ struct Sink;
Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv, std::string_view name);
void writeDerivation(Sink & out, const Store & store, const BasicDerivation & drv);
/* This creates an opaque and almost certainly unique string
deterministically from the output name.
It is used as a placeholder to allow derivations to refer to their
own outputs without needing to use the hash of a derivation in
itself, making the hash near-impossible to calculate. */
/**
* This creates an opaque and almost certainly unique string
* deterministically from the output name.
*
* It is used as a placeholder to allow derivations to refer to their
* own outputs without needing to use the hash of a derivation in
* itself, making the hash near-impossible to calculate.
*/
std::string hashPlaceholder(const std::string_view outputName);
/* This creates an opaque and almost certainly unique string
deterministically from a derivation path and output name.
It is used as a placeholder to allow derivations to refer to
content-addressed paths whose content --- and thus the path
themselves --- isn't yet known. This occurs when a derivation has a
dependency which is a CA derivation. */
/**
* This creates an opaque and almost certainly unique string
* deterministically from a derivation path and output name.
*
* It is used as a placeholder to allow derivations to refer to
* content-addressed paths whose content --- and thus the path
* themselves --- isn't yet known. This occurs when a derivation has a
* dependency which is a CA derivation.
*/
std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName);
extern const Hash impureOutputHash;

View file

@ -105,7 +105,7 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
auto drvHashes =
staticOutputHashes(store, store.readDerivation(p.drvPath));
for (auto& [outputName, outputPath] : p.outputs) {
if (settings.isExperimentalFeatureEnabled(
if (experimentalFeatureSettings.isEnabled(
Xp::CaDerivations)) {
auto drvOutput = get(drvHashes, outputName);
if (!drvOutput)

View file

@ -105,7 +105,7 @@ using _BuiltPathRaw = std::variant<
>;
/**
* A built path. Similar to a `DerivedPath`, but enriched with the corresponding
* A built path. Similar to a DerivedPath, but enriched with the corresponding
* output path(s).
*/
struct BuiltPath : _BuiltPathRaw {

View file

@ -7,6 +7,13 @@ struct DummyStoreConfig : virtual StoreConfig {
using StoreConfig::StoreConfig;
const std::string name() override { return "Dummy Store"; }
std::string doc() override
{
return
#include "dummy-store.md"
;
}
};
struct DummyStore : public virtual DummyStoreConfig, public virtual Store

View file

@ -0,0 +1,13 @@
R"(
**Store URL format**: `dummy://`
This store type represents a store that contains no store paths and
cannot be written to. It's useful when you want to use the Nix
evaluator when no actual Nix store exists, e.g.
```console
# nix eval --store dummy:// --expr '1 + 2'
```
)"

View file

@ -16,7 +16,7 @@ void Store::exportPaths(const StorePathSet & paths, Sink & sink)
//logger->incExpected(doneLabel, sorted.size());
for (auto & path : sorted) {
//Activity act(*logger, lvlInfo, format("exporting path '%s'") % path);
//Activity act(*logger, lvlInfo, "exporting path '%s'", path);
sink << 1;
exportPath(path, sink);
//logger->incProgress(doneLabel);
@ -71,7 +71,7 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
auto path = parseStorePath(readString(source));
//Activity act(*logger, lvlInfo, format("importing path '%s'") % info.path);
//Activity act(*logger, lvlInfo, "importing path '%s'", info.path);
auto references = worker_proto::read(*this, source, Phantom<StorePathSet> {});
auto deriver = readString(source);

View file

@ -88,6 +88,10 @@ struct curlFileTransfer : public FileTransfer
{request.uri}, request.parentAct)
, callback(std::move(callback))
, finalSink([this](std::string_view data) {
if (errorSink) {
(*errorSink)(data);
}
if (this->request.dataCallback) {
auto httpStatus = getHTTPStatus();
@ -163,8 +167,6 @@ struct curlFileTransfer : public FileTransfer
}
}
if (errorSink)
(*errorSink)({(char *) contents, realSize});
(*decompressionSink)({(char *) contents, realSize});
return realSize;
@ -183,7 +185,7 @@ struct curlFileTransfer : public FileTransfer
{
size_t realSize = size * nmemb;
std::string line((char *) contents, realSize);
printMsg(lvlVomit, format("got header for '%s': %s") % request.uri % trim(line));
printMsg(lvlVomit, "got header for '%s': %s", request.uri, trim(line));
static std::regex statusLine("HTTP/[^ ]+ +[0-9]+(.*)", std::regex::extended | std::regex::icase);
std::smatch match;
if (std::regex_match(line, match, statusLine)) {
@ -207,7 +209,7 @@ struct curlFileTransfer : public FileTransfer
long httpStatus = 0;
curl_easy_getinfo(req, CURLINFO_RESPONSE_CODE, &httpStatus);
if (result.etag == request.expectedETag && httpStatus == 200) {
debug(format("shutting down on 200 HTTP response with expected ETag"));
debug("shutting down on 200 HTTP response with expected ETag");
return 0;
}
} else if (name == "content-encoding")
@ -316,7 +318,7 @@ struct curlFileTransfer : public FileTransfer
if (request.verifyTLS) {
if (settings.caFile != "")
curl_easy_setopt(req, CURLOPT_CAINFO, settings.caFile.c_str());
curl_easy_setopt(req, CURLOPT_CAINFO, settings.caFile.get().c_str());
} else {
curl_easy_setopt(req, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(req, CURLOPT_SSL_VERIFYHOST, 0);

View file

@ -34,8 +34,7 @@ static void makeSymlink(const Path & link, const Path & target)
createDirs(dirOf(link));
/* Create the new symlink. */
Path tempLink = (format("%1%.tmp-%2%-%3%")
% link % getpid() % random()).str();
Path tempLink = fmt("%1%.tmp-%2%-%3%", link, getpid(), random());
createSymlink(target, tempLink);
/* Atomically replace the old one. */
@ -197,7 +196,7 @@ void LocalStore::findTempRoots(Roots & tempRoots, bool censor)
pid_t pid = std::stoi(i.name);
debug(format("reading temporary root file '%1%'") % path);
debug("reading temporary root file '%1%'", path);
AutoCloseFD fd(open(path.c_str(), O_CLOEXEC | O_RDWR, 0666));
if (!fd) {
/* It's okay if the file has disappeared. */
@ -263,7 +262,7 @@ void LocalStore::findRoots(const Path & path, unsigned char type, Roots & roots)
target = absPath(target, dirOf(path));
if (!pathExists(target)) {
if (isInDir(path, stateDir + "/" + gcRootsDir + "/auto")) {
printInfo(format("removing stale link from '%1%' to '%2%'") % path % target);
printInfo("removing stale link from '%1%' to '%2%'", path, target);
unlink(path.c_str());
}
} else {
@ -372,29 +371,29 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
while (errno = 0, ent = readdir(procDir.get())) {
checkInterrupt();
if (std::regex_match(ent->d_name, digitsRegex)) {
readProcLink(fmt("/proc/%s/exe" ,ent->d_name), unchecked);
readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked);
auto fdStr = fmt("/proc/%s/fd", ent->d_name);
auto fdDir = AutoCloseDir(opendir(fdStr.c_str()));
if (!fdDir) {
if (errno == ENOENT || errno == EACCES)
continue;
throw SysError("opening %1%", fdStr);
}
struct dirent * fd_ent;
while (errno = 0, fd_ent = readdir(fdDir.get())) {
if (fd_ent->d_name[0] != '.')
readProcLink(fmt("%s/%s", fdStr, fd_ent->d_name), unchecked);
}
if (errno) {
if (errno == ESRCH)
continue;
throw SysError("iterating /proc/%1%/fd", ent->d_name);
}
fdDir.reset();
try {
readProcLink(fmt("/proc/%s/exe" ,ent->d_name), unchecked);
readProcLink(fmt("/proc/%s/cwd", ent->d_name), unchecked);
auto fdStr = fmt("/proc/%s/fd", ent->d_name);
auto fdDir = AutoCloseDir(opendir(fdStr.c_str()));
if (!fdDir) {
if (errno == ENOENT || errno == EACCES)
continue;
throw SysError("opening %1%", fdStr);
}
struct dirent * fd_ent;
while (errno = 0, fd_ent = readdir(fdDir.get())) {
if (fd_ent->d_name[0] != '.')
readProcLink(fmt("%s/%s", fdStr, fd_ent->d_name), unchecked);
}
if (errno) {
if (errno == ESRCH)
continue;
throw SysError("iterating /proc/%1%/fd", ent->d_name);
}
fdDir.reset();
auto mapFile = fmt("/proc/%s/maps", ent->d_name);
auto mapLines = tokenizeString<std::vector<std::string>>(readFile(mapFile), "\n");
for (const auto & line : mapLines) {
@ -863,7 +862,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
continue;
}
printMsg(lvlTalkative, format("deleting unused link '%1%'") % path);
printMsg(lvlTalkative, "deleting unused link '%1%'", path);
if (unlink(path.c_str()) == -1)
throw SysError("deleting '%1%'", path);

View file

@ -30,28 +30,23 @@ static GlobalConfig::Register rSettings(&settings);
Settings::Settings()
: nixPrefix(NIX_PREFIX)
, nixStore(canonPath(getEnv("NIX_STORE_DIR").value_or(getEnv("NIX_STORE").value_or(NIX_STORE_DIR))))
, nixDataDir(canonPath(getEnv("NIX_DATA_DIR").value_or(NIX_DATA_DIR)))
, nixLogDir(canonPath(getEnv("NIX_LOG_DIR").value_or(NIX_LOG_DIR)))
, nixStateDir(canonPath(getEnv("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
, nixConfDir(canonPath(getEnv("NIX_CONF_DIR").value_or(NIX_CONF_DIR)))
, nixStore(canonPath(getEnvNonEmpty("NIX_STORE_DIR").value_or(getEnvNonEmpty("NIX_STORE").value_or(NIX_STORE_DIR))))
, nixDataDir(canonPath(getEnvNonEmpty("NIX_DATA_DIR").value_or(NIX_DATA_DIR)))
, nixLogDir(canonPath(getEnvNonEmpty("NIX_LOG_DIR").value_or(NIX_LOG_DIR)))
, nixStateDir(canonPath(getEnvNonEmpty("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
, nixConfDir(canonPath(getEnvNonEmpty("NIX_CONF_DIR").value_or(NIX_CONF_DIR)))
, nixUserConfFiles(getUserConfigFiles())
, nixBinDir(canonPath(getEnv("NIX_BIN_DIR").value_or(NIX_BIN_DIR)))
, nixBinDir(canonPath(getEnvNonEmpty("NIX_BIN_DIR").value_or(NIX_BIN_DIR)))
, nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnv("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
{
buildUsersGroup = getuid() == 0 ? "nixbld" : "";
lockCPU = getEnv("NIX_AFFINITY_HACK") == "1";
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
caFile = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
if (caFile == "") {
for (auto & fn : {"/etc/ssl/certs/ca-certificates.crt", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"})
if (pathExists(fn)) {
caFile = fn;
break;
}
}
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
if (sslOverride != "")
caFile = sslOverride;
/* Backwards compatibility. */
auto s = getEnv("NIX_REMOTE_SYSTEMS");
@ -166,18 +161,6 @@ StringSet Settings::getDefaultExtraPlatforms()
return extraPlatforms;
}
bool Settings::isExperimentalFeatureEnabled(const ExperimentalFeature & feature)
{
auto & f = experimentalFeatures.get();
return std::find(f.begin(), f.end(), feature) != f.end();
}
void Settings::requireExperimentalFeature(const ExperimentalFeature & feature)
{
if (!isExperimentalFeatureEnabled(feature))
throw MissingExperimentalFeature(feature);
}
bool Settings::isWSL1()
{
struct utsname utsbuf;
@ -187,6 +170,13 @@ bool Settings::isWSL1()
return hasSuffix(utsbuf.release, "-Microsoft");
}
Path Settings::getDefaultSSLCertFile()
{
for (auto & fn : {"/etc/ssl/certs/ca-certificates.crt", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"})
if (pathExists(fn)) return fn;
return "";
}
const std::string nixVersion = PACKAGE_VERSION;
NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {

View file

@ -3,7 +3,6 @@
#include "types.hh"
#include "config.hh"
#include "util.hh"
#include "experimental-features.hh"
#include <map>
#include <limits>
@ -64,6 +63,8 @@ class Settings : public Config {
bool isWSL1();
Path getDefaultSSLCertFile();
public:
Settings();
@ -97,7 +98,12 @@ public:
Path nixDaemonSocketFile;
Setting<std::string> storeUri{this, getEnv("NIX_REMOTE").value_or("auto"), "store",
"The default Nix store to use."};
R"(
The [URL of the Nix store](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
to use for most operations.
See [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md)
for supported store types and settings.
)"};
Setting<bool> keepFailed{this, false, "keep-failed",
"Whether to keep temporary directories of failed builds."};
@ -678,8 +684,9 @@ public:
Strings{"https://cache.nixos.org/"},
"substituters",
R"(
A list of URLs of substituters, separated by whitespace. Substituters
are tried based on their Priority value, which each substituter can set
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
to be used as substituters, separated by whitespace.
Substituters are tried based on their Priority value, which each substituter can set
independently. Lower value means higher priority.
The default is `https://cache.nixos.org`, with a Priority of 40.
@ -697,7 +704,8 @@ public:
Setting<StringSet> trustedSubstituters{
this, {}, "trusted-substituters",
R"(
A list of URLs of substituters, separated by whitespace. These are
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format),
separated by whitespace. These are
not used by default, but can be enabled by users of the Nix daemon
by specifying `--option substituters urls` on the command
line. Unprivileged users are only allowed to pass a subset of the
@ -826,8 +834,22 @@ public:
> `.netrc`.
)"};
/* Path to the SSL CA file used */
Path caFile;
Setting<Path> caFile{
this, getDefaultSSLCertFile(), "ssl-cert-file",
R"(
The path of a file containing CA certificates used to
authenticate `https://` downloads. Nix by default will use
the first of the following files that exists:
1. `/etc/ssl/certs/ca-certificates.crt`
2. `/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt`
The path can be overridden by the following environment
variables, in order of precedence:
1. `NIX_SSL_CERT_FILE`
2. `SSL_CERT_FILE`
)"};
#if __linux__
Setting<bool> filterSyscalls{
@ -932,13 +954,6 @@ public:
are loaded as plugins (non-recursively).
)"};
Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
"Experimental Nix features to enable."};
bool isExperimentalFeatureEnabled(const ExperimentalFeature &);
void requireExperimentalFeature(const ExperimentalFeature &);
Setting<size_t> narBufferSize{this, 32 * 1024 * 1024, "nar-buffer-size",
"Maximum size of NARs before spilling them to disk."};

View file

@ -12,7 +12,14 @@ struct HttpBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
{
using BinaryCacheStoreConfig::BinaryCacheStoreConfig;
const std::string name() override { return "Http Binary Cache Store"; }
const std::string name() override { return "HTTP Binary Cache Store"; }
std::string doc() override
{
return
#include "http-binary-cache-store.md"
;
}
};
class HttpBinaryCacheStore : public virtual HttpBinaryCacheStoreConfig, public virtual BinaryCacheStore

View file

@ -0,0 +1,8 @@
R"(
**Store URL format**: `http://...`, `https://...`
This store allows a binary cache to be accessed via the HTTP
protocol.
)"

View file

@ -1,3 +1,4 @@
#include "ssh-store-config.hh"
#include "archive.hh"
#include "pool.hh"
#include "remote-store.hh"
@ -12,17 +13,24 @@
namespace nix {
struct LegacySSHStoreConfig : virtual StoreConfig
struct LegacySSHStoreConfig : virtual CommonSSHStoreConfig
{
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"};
using CommonSSHStoreConfig::CommonSSHStoreConfig;
const std::string name() override { return "Legacy SSH Store"; }
const Setting<Path> remoteProgram{(StoreConfig*) this, "nix-store", "remote-program",
"Path to the `nix-store` executable on the remote machine."};
const Setting<int> maxConnections{(StoreConfig*) this, 1, "max-connections",
"Maximum number of concurrent SSH connections."};
const std::string name() override { return "SSH Store"; }
std::string doc() override
{
return
#include "legacy-ssh-store.md"
;
}
};
struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Store
@ -51,6 +59,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
LegacySSHStore(const std::string & scheme, const std::string & host, const Params & params)
: StoreConfig(params)
, CommonSSHStoreConfig(params)
, LegacySSHStoreConfig(params)
, Store(params)
, host(host)

View file

@ -0,0 +1,8 @@
R"(
**Store URL format**: `ssh://[username@]hostname`
This store type allows limited access to a remote store on another
machine via SSH.
)"

View file

@ -11,6 +11,13 @@ struct LocalBinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
using BinaryCacheStoreConfig::BinaryCacheStoreConfig;
const std::string name() override { return "Local Binary Cache Store"; }
std::string doc() override
{
return
#include "local-binary-cache-store.md"
;
}
};
class LocalBinaryCacheStore : public virtual LocalBinaryCacheStoreConfig, public virtual BinaryCacheStore

View file

@ -0,0 +1,16 @@
R"(
**Store URL format**: `file://`*path*
This store allows reading and writing a binary cache stored in *path*
in the local filesystem. If *path* does not exist, it will be created.
For example, the following builds or downloads `nixpkgs#hello` into
the local store and then copies it to the binary cache in
`/tmp/binary-cache`:
```
# nix copy --to file:///tmp/binary-cache nixpkgs#hello
```
)"

View file

@ -9,20 +9,28 @@ namespace nix {
struct LocalFSStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
// FIXME: the (StoreConfig*) cast works around a bug in gcc that causes
// it to omit the call to the Setting constructor. Clang works fine
// either way.
const PathSetting rootDir{(StoreConfig*) this, true, "",
"root", "directory prefixed to all other paths"};
"root",
"Directory prefixed to all other paths."};
const PathSetting stateDir{(StoreConfig*) this, false,
rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
"state", "directory where Nix will store state"};
"state",
"Directory where Nix will store state."};
const PathSetting logDir{(StoreConfig*) this, false,
rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
"log", "directory where Nix will store state"};
"log",
"directory where Nix will store log files."};
const PathSetting realStoreDir{(StoreConfig*) this, false,
rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
"physical path to the Nix store"};
"Physical path of the Nix store."};
};
class LocalFSStore : public virtual LocalFSStoreConfig,

View file

@ -44,6 +44,13 @@
namespace nix {
std::string LocalStoreConfig::doc()
{
return
#include "local-store.md"
;
}
struct LocalStore::State::Stmts {
/* Some precompiled SQLite statements. */
SQLiteStmt RegisterValidPath;
@ -280,7 +287,7 @@ LocalStore::LocalStore(const Params & params)
else if (curSchema == 0) { /* new store */
curSchema = nixSchemaVersion;
openDB(*state, true);
writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str(), 0666, true);
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
}
else if (curSchema < nixSchemaVersion) {
@ -329,14 +336,14 @@ LocalStore::LocalStore(const Params & params)
txn.commit();
}
writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str(), 0666, true);
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true);
lockFile(globalLock.get(), ltRead, true);
}
else openDB(*state, false);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
}
@ -366,7 +373,7 @@ LocalStore::LocalStore(const Params & params)
state->stmts->QueryPathFromHashPart.create(state->db,
"select path from ValidPaths where path >= ? limit 1;");
state->stmts->QueryValidPaths.create(state->db, "select path from ValidPaths");
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
state->stmts->RegisterRealisedOutput.create(state->db,
R"(
insert into Realisations (drvPath, outputName, outputPath, signatures)
@ -413,6 +420,13 @@ LocalStore::LocalStore(const Params & params)
}
LocalStore::LocalStore(std::string scheme, std::string path, const Params & params)
: LocalStore(params)
{
throw UnimplementedError("LocalStore");
}
AutoCloseFD LocalStore::openGCLock()
{
Path fnGCLock = stateDir + "/gc.lock";
@ -754,7 +768,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
{
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info))
registerDrvOutput(info);
else
@ -763,7 +777,7 @@ void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag check
void LocalStore::registerDrvOutput(const Realisation & info)
{
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
retrySQLite<void>([&]() {
auto state(_state.lock());
if (auto oldR = queryRealisation_(*state, info.id)) {
@ -1052,7 +1066,7 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath & path_)
return outputs;
});
if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations))
if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
return outputs;
auto drv = readInvalidDerivation(path);
@ -1580,7 +1594,7 @@ void LocalStore::invalidatePathChecked(const StorePath & path)
bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
{
printInfo(format("reading the Nix store..."));
printInfo("reading the Nix store...");
bool errors = false;
@ -1970,5 +1984,6 @@ std::optional<std::string> LocalStore::getVersion()
return nixVersion;
}
static RegisterStoreImplementation<LocalStore, LocalStoreConfig> regLocalStore;
} // namespace nix

View file

@ -38,11 +38,13 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
Setting<bool> requireSigs{(StoreConfig*) this,
settings.requireSigs,
"require-sigs", "whether store paths should have a trusted signature on import"};
"require-sigs",
"Whether store paths copied into this store should have a trusted signature."};
const std::string name() override { return "Local Store"; }
};
std::string doc() override;
};
class LocalStore : public virtual LocalStoreConfig, public virtual LocalFSStore, public virtual GcStore
{
@ -100,9 +102,13 @@ public:
/* Initialise the local store, upgrading the schema if
necessary. */
LocalStore(const Params & params);
LocalStore(std::string scheme, std::string path, const Params & params);
~LocalStore();
static std::set<std::string> uriSchemes()
{ return {}; }
/* Implementations of abstract store API methods. */
std::string getUri() override;

View file

@ -0,0 +1,39 @@
R"(
**Store URL format**: `local`, *root*
This store type accesses a Nix store in the local filesystem directly
(i.e. not via the Nix daemon). *root* is an absolute path that is
prefixed to other directories such as the Nix store directory. The
store pseudo-URL `local` denotes a store that uses `/` as its root
directory.
A store that uses a *root* other than `/` is called a *chroot
store*. With such stores, the store directory is "logically" still
`/nix/store`, so programs stored in them can only be built and
executed by `chroot`-ing into *root*. Chroot stores only support
building and running on Linux when [`mount namespaces`](https://man7.org/linux/man-pages/man7/mount_namespaces.7.html) and [`user namespaces`](https://man7.org/linux/man-pages/man7/user_namespaces.7.html) are
enabled.
For example, the following uses `/tmp/root` as the chroot environment
to build or download `nixpkgs#hello` and then execute it:
```console
# nix run --store /tmp/root nixpkgs#hello
Hello, world!
```
Here, the "physical" store location is `/tmp/root/nix/store`, and
Nix's store metadata is in `/tmp/root/nix/var/nix/db`.
It is also possible, but not recommended, to change the "logical"
location of the Nix store from its default of `/nix/store`. This makes
it impossible to use default substituters such as
`https://cache.nixos.org/`, and thus you may have to build everything
locally. Here is an example:
```console
# nix build --store 'local?store=/tmp/my-nix/store&state=/tmp/my-nix/state&log=/tmp/my-nix/log' nixpkgs#hello
```
)"

View file

@ -129,7 +129,7 @@ struct AutoUserLock : UserLock
useUserNamespace = false;
#endif
settings.requireExperimentalFeature(Xp::AutoAllocateUids);
experimentalFeatureSettings.require(Xp::AutoAllocateUids);
assert(settings.startId > 0);
assert(settings.uidCount % maxIdsPerBuild == 0);
assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits<uid_t>::max());

View file

@ -326,7 +326,7 @@ OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd,
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
store.printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
DrvOutput outputId { *outputHash, output };
auto realisation = store.queryRealisation(outputId);
if (!realisation)

View file

@ -55,7 +55,7 @@ LocalStore::InodeHash LocalStore::loadInodeHash()
}
if (errno) throw SysError("reading directory '%1%'", linksDir);
printMsg(lvlTalkative, format("loaded %1% hash inodes") % inodeHash.size());
printMsg(lvlTalkative, "loaded %1% hash inodes", inodeHash.size());
return inodeHash;
}
@ -73,7 +73,7 @@ Strings LocalStore::readDirectoryIgnoringInodes(const Path & path, const InodeHa
checkInterrupt();
if (inodeHash.count(dirent->d_ino)) {
debug(format("'%1%' is already linked") % dirent->d_name);
debug("'%1%' is already linked", dirent->d_name);
continue;
}
@ -102,7 +102,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
if (std::regex_search(path, std::regex("\\.app/Contents/.+$")))
{
debug(format("'%1%' is not allowed to be linked in macOS") % path);
debug("'%1%' is not allowed to be linked in macOS", path);
return;
}
#endif
@ -146,7 +146,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
contents of the symlink (i.e. the result of readlink()), not
the contents of the target (which may not even exist). */
Hash hash = hashPath(htSHA256, path).first;
debug(format("'%1%' has hash '%2%'") % path % hash.to_string(Base32, true));
debug("'%1%' has hash '%2%'", path, hash.to_string(Base32, true));
/* Check if this is a known hash. */
Path linkPath = linksDir + "/" + hash.to_string(Base32, false);
@ -196,11 +196,11 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
auto stLink = lstat(linkPath);
if (st.st_ino == stLink.st_ino) {
debug(format("'%1%' is already linked to '%2%'") % path % linkPath);
debug("'%1%' is already linked to '%2%'", path, linkPath);
return;
}
printMsg(lvlTalkative, format("linking '%1%' to '%2%'") % path % linkPath);
printMsg(lvlTalkative, "linking '%1%' to '%2%'", path, linkPath);
/* Make the containing directory writable, but only if it's not
the store itself (we don't want or need to mess with its
@ -213,8 +213,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
its timestamp back to 0. */
MakeReadOnly makeReadOnly(mustToggle ? dirOfPath : "");
Path tempLink = (format("%1%/.tmp-link-%2%-%3%")
% realStoreDir % getpid() % random()).str();
Path tempLink = fmt("%1%/.tmp-link-%2%-%3%", realStoreDir, getpid(), random());
if (link(linkPath.c_str(), tempLink.c_str()) == -1) {
if (errno == EMLINK) {
@ -222,7 +221,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
systems). This is likely to happen with empty files.
Just shrug and ignore. */
if (st.st_size)
printInfo(format("'%1%' has maximum number of links") % linkPath);
printInfo("'%1%' has maximum number of links", linkPath);
return;
}
throw SysError("cannot link '%1%' to '%2%'", tempLink, linkPath);

View file

@ -9,6 +9,9 @@
namespace nix {
/**
* A non-empty set of outputs, specified by name
*/
struct OutputNames : std::set<std::string> {
using std::set<std::string>::set;
@ -18,6 +21,9 @@ struct OutputNames : std::set<std::string> {
: std::set<std::string>(s)
{ assert(!empty()); }
/**
* Needs to be "inherited manually"
*/
OutputNames(std::set<std::string> && s)
: std::set<std::string>(s)
{ assert(!empty()); }
@ -28,6 +34,9 @@ struct OutputNames : std::set<std::string> {
OutputNames() = delete;
};
/**
* The set of all outputs, without needing to name them explicitly
*/
struct AllOutputs : std::monostate { };
typedef std::variant<AllOutputs, OutputNames> _OutputsSpecRaw;
@ -36,7 +45,9 @@ struct OutputsSpec : _OutputsSpecRaw {
using Raw = _OutputsSpecRaw;
using Raw::Raw;
/* Force choosing a variant */
/**
* Force choosing a variant
*/
OutputsSpec() = delete;
using Names = OutputNames;
@ -52,14 +63,20 @@ struct OutputsSpec : _OutputsSpecRaw {
bool contains(const std::string & output) const;
/* Create a new OutputsSpec which is the union of this and that. */
/**
* Create a new OutputsSpec which is the union of this and that.
*/
OutputsSpec union_(const OutputsSpec & that) const;
/* Whether this OutputsSpec is a subset of that. */
/**
* Whether this OutputsSpec is a subset of that.
*/
bool isSubsetOf(const OutputsSpec & outputs) const;
/* Parse a string of the form 'output1,...outputN' or
'*', returning the outputs spec. */
/**
* Parse a string of the form 'output1,...outputN' or '*', returning
* the outputs spec.
*/
static OutputsSpec parse(std::string_view s);
static std::optional<OutputsSpec> parseOpt(std::string_view s);
@ -81,8 +98,10 @@ struct ExtendedOutputsSpec : _ExtendedOutputsSpecRaw {
return static_cast<const Raw &>(*this);
}
/* Parse a string of the form 'prefix^output1,...outputN' or
'prefix^*', returning the prefix and the extended outputs spec. */
/**
* Parse a string of the form 'prefix^output1,...outputN' or
* 'prefix^*', returning the prefix and the extended outputs spec.
*/
static std::pair<std::string_view, ExtendedOutputsSpec> parse(std::string_view s);
static std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> parseOpt(std::string_view s);

View file

@ -8,13 +8,22 @@ namespace nix {
struct Hash;
/**
* \ref StorePath "Store path" is the fundamental reference type of Nix.
* A store paths refers to a Store object.
*
* See glossary.html#gloss-store-path for more information on a
* conceptual level.
*/
class StorePath
{
std::string baseName;
public:
/* Size of the hash part of store paths, in base-32 characters. */
/**
* Size of the hash part of store paths, in base-32 characters.
*/
constexpr static size_t HashLen = 32; // i.e. 160 bits
constexpr static size_t MaxPathLen = 211;
@ -45,8 +54,9 @@ public:
return baseName != other.baseName;
}
/* Check whether a file name ends with the extension for
derivations. */
/**
* Check whether a file name ends with the extension for derivations.
*/
bool isDerivation() const;
std::string_view name() const
@ -67,7 +77,10 @@ public:
typedef std::set<StorePath> StorePathSet;
typedef std::vector<StorePath> StorePaths;
/* Extension of derivations in the Nix store. */
/**
* The file extension of \ref Derivation derivations when serialized
* into store objects.
*/
const std::string drvExtension = ".drv";
}

View file

@ -96,7 +96,7 @@ bool PathLocks::lockPaths(const PathSet & paths,
checkInterrupt();
Path lockPath = path + ".lock";
debug(format("locking path '%1%'") % path);
debug("locking path '%1%'", path);
AutoCloseFD fd;
@ -118,7 +118,7 @@ bool PathLocks::lockPaths(const PathSet & paths,
}
}
debug(format("lock acquired on '%1%'") % lockPath);
debug("lock acquired on '%1%'", lockPath);
/* Check that the lock file hasn't become stale (i.e.,
hasn't been unlinked). */
@ -130,7 +130,7 @@ bool PathLocks::lockPaths(const PathSet & paths,
a lock on a deleted file. This means that other
processes may create and acquire a lock on
`lockPath', and proceed. So we must retry. */
debug(format("open lock file '%1%' has become stale") % lockPath);
debug("open lock file '%1%' has become stale", lockPath);
else
break;
}
@ -163,7 +163,7 @@ void PathLocks::unlock()
"error (ignored): cannot close lock file on '%1%'",
i.second);
debug(format("lock released on '%1%'") % i.second);
debug("lock released on '%1%'", i.second);
}
fds.clear();

View file

@ -64,7 +64,7 @@ std::pair<Generations, std::optional<GenerationNumber>> findGenerations(Path pro
static void makeName(const Path & profile, GenerationNumber num,
Path & outLink)
{
Path prefix = (format("%1%-%2%") % profile % num).str();
Path prefix = fmt("%1%-%2%", profile, num);
outLink = prefix + "-link";
}
@ -269,7 +269,7 @@ void switchGeneration(
void lockProfile(PathLocks & lock, const Path & profile)
{
lock.lockPaths({profile}, (format("waiting for lock on profile '%1%'") % profile).str());
lock.lockPaths({profile}, fmt("waiting for lock on profile '%1%'", profile));
lock.setDeletion(true);
}
@ -282,28 +282,48 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir()
{
auto profileRoot = createNixStateDir() + "/profiles";
auto profileRoot =
(getuid() == 0)
? rootProfilesDir()
: createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
}
Path rootProfilesDir()
{
return settings.nixStateDir + "/profiles/per-user/root";
}
Path getDefaultProfile()
{
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try {
auto profile =
getuid() == 0
? settings.nixStateDir + "/profiles/default"
: profilesDir() + "/profile";
auto profile = profilesDir() + "/profile";
if (!pathExists(profileLink)) {
replaceSymlink(profile, profileLink);
}
// Backwards compatibiliy measure: Make root's profile available as
// `.../default` as it's what NixOS and most of the init scripts expect
Path globalProfileLink = settings.nixStateDir + "/profiles/default";
if (getuid() == 0 && !pathExists(globalProfileLink)) {
replaceSymlink(profile, globalProfileLink);
}
return absPath(readLink(profileLink), dirOf(profileLink));
} catch (Error &) {
return profileLink;
}
}
Path defaultChannelsDir()
{
return profilesDir() + "/channels";
}
Path rootChannelsDir()
{
return rootProfilesDir() + "/channels";
}
}

View file

@ -68,13 +68,32 @@ void lockProfile(PathLocks & lock, const Path & profile);
rebuilt. */
std::string optimisticLockProfile(const Path & profile);
/* Creates and returns the path to a directory suitable for storing the users
profiles. */
/**
* Create and return the path to a directory suitable for storing the users
* profiles.
*/
Path profilesDir();
/* Resolve the default profile (~/.nix-profile by default, $XDG_STATE_HOME/
nix/profile if XDG Base Directory Support is enabled), and create if doesn't
exist */
/**
* Return the path to the profile directory for root (but don't try creating it)
*/
Path rootProfilesDir();
/**
* Create and return the path to the file used for storing the users's channels
*/
Path defaultChannelsDir();
/**
* Return the path to the channel directory for root (but don't try creating it)
*/
Path rootChannelsDir();
/**
* Resolve the default profile (~/.nix-profile by default,
* $XDG_STATE_HOME/nix/profile if XDG Base Directory Support is enabled),
* and create if doesn't exist
*/
Path getDefaultProfile();
}

View file

@ -39,8 +39,7 @@ static void search(
if (!match) continue;
std::string ref(s.substr(i, refLength));
if (hashes.erase(ref)) {
debug(format("found reference to '%1%' at offset '%2%'")
% ref % i);
debug("found reference to '%1%' at offset '%2%'", ref, i);
seen.insert(ref);
}
++i;

View file

@ -265,7 +265,7 @@ void RemoteStore::setOptions(Connection & conn)
overrides.erase(settings.buildCores.name);
overrides.erase(settings.useSubstitutes.name);
overrides.erase(loggerSettings.showTrace.name);
overrides.erase(settings.experimentalFeatures.name);
overrides.erase(experimentalFeatureSettings.experimentalFeatures.name);
overrides.erase(settings.pluginFiles.name);
conn.to << overrides.size();
for (auto & i : overrides)
@ -876,7 +876,7 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
"the derivation '%s' doesn't have an output named '%s'",
printStorePath(bfd.drvPath), output);
auto outputId = DrvOutput{ *outputHash, output };
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
auto realisation =
queryRealisation(outputId);
if (!realisation)

View file

@ -22,11 +22,13 @@ struct RemoteStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<int> maxConnections{(StoreConfig*) this, 1,
"max-connections", "maximum number of concurrent connections to the Nix daemon"};
const Setting<int> maxConnections{(StoreConfig*) this, 1, "max-connections",
"Maximum number of concurrent connections to the Nix daemon."};
const Setting<unsigned int> maxConnectionAge{(StoreConfig*) this, std::numeric_limits<unsigned int>::max(),
"max-connection-age", "number of seconds to reuse a connection"};
const Setting<unsigned int> maxConnectionAge{(StoreConfig*) this,
std::numeric_limits<unsigned int>::max(),
"max-connection-age",
"Maximum age of a connection before it is closed."};
};
/* FIXME: RemoteStore is a misnomer - should be something like
@ -38,8 +40,6 @@ class RemoteStore : public virtual RemoteStoreConfig,
{
public:
virtual bool sameMachine() = 0;
RemoteStore(const Params & params);
/* Implementations of abstract store API methods. */

View file

@ -40,12 +40,12 @@ struct S3Error : public Error
/* Helper: given an Outcome<R, E>, return R in case of success, or
throw an exception in case of an error. */
template<typename R, typename E>
R && checkAws(const FormatOrString & fs, Aws::Utils::Outcome<R, E> && outcome)
R && checkAws(std::string_view s, Aws::Utils::Outcome<R, E> && outcome)
{
if (!outcome.IsSuccess())
throw S3Error(
outcome.GetError().GetErrorType(),
fs.s + ": " + outcome.GetError().GetMessage());
s + ": " + outcome.GetError().GetMessage());
return outcome.GetResultWithOwnership();
}
@ -192,19 +192,72 @@ S3BinaryCacheStore::S3BinaryCacheStore(const Params & params)
struct S3BinaryCacheStoreConfig : virtual BinaryCacheStoreConfig
{
using BinaryCacheStoreConfig::BinaryCacheStoreConfig;
const Setting<std::string> profile{(StoreConfig*) this, "", "profile", "The name of the AWS configuration profile to use."};
const Setting<std::string> region{(StoreConfig*) this, Aws::Region::US_EAST_1, "region", {"aws-region"}};
const Setting<std::string> scheme{(StoreConfig*) this, "", "scheme", "The scheme to use for S3 requests, https by default."};
const Setting<std::string> endpoint{(StoreConfig*) this, "", "endpoint", "An optional override of the endpoint to use when talking to S3."};
const Setting<std::string> narinfoCompression{(StoreConfig*) this, "", "narinfo-compression", "compression method for .narinfo files"};
const Setting<std::string> lsCompression{(StoreConfig*) this, "", "ls-compression", "compression method for .ls files"};
const Setting<std::string> logCompression{(StoreConfig*) this, "", "log-compression", "compression method for log/* files"};
const Setting<std::string> profile{(StoreConfig*) this, "", "profile",
R"(
The name of the AWS configuration profile to use. By default
Nix will use the `default` profile.
)"};
const Setting<std::string> region{(StoreConfig*) this, Aws::Region::US_EAST_1, "region",
R"(
The region of the S3 bucket. If your bucket is not in
`useast-1`, you should always explicitly specify the region
parameter.
)"};
const Setting<std::string> scheme{(StoreConfig*) this, "", "scheme",
R"(
The scheme used for S3 requests, `https` (default) or `http`. This
option allows you to disable HTTPS for binary caches which don't
support it.
> **Note**
>
> HTTPS should be used if the cache might contain sensitive
> information.
)"};
const Setting<std::string> endpoint{(StoreConfig*) this, "", "endpoint",
R"(
The URL of the endpoint of an S3-compatible service such as MinIO.
Do not specify this setting if you're using Amazon S3.
> **Note**
>
> This endpoint must support HTTPS and will use path-based
> addressing instead of virtual host based addressing.
)"};
const Setting<std::string> narinfoCompression{(StoreConfig*) this, "", "narinfo-compression",
"Compression method for `.narinfo` files."};
const Setting<std::string> lsCompression{(StoreConfig*) this, "", "ls-compression",
"Compression method for `.ls` files."};
const Setting<std::string> logCompression{(StoreConfig*) this, "", "log-compression",
R"(
Compression method for `log/*` files. It is recommended to
use a compression method supported by most web browsers
(e.g. `brotli`).
)"};
const Setting<bool> multipartUpload{
(StoreConfig*) this, false, "multipart-upload", "whether to use multi-part uploads"};
(StoreConfig*) this, false, "multipart-upload",
"Whether to use multi-part uploads."};
const Setting<uint64_t> bufferSize{
(StoreConfig*) this, 5 * 1024 * 1024, "buffer-size", "size (in bytes) of each part in multi-part uploads"};
(StoreConfig*) this, 5 * 1024 * 1024, "buffer-size",
"Size (in bytes) of each part in multi-part uploads."};
const std::string name() override { return "S3 Binary Cache Store"; }
std::string doc() override
{
return
#include "s3-binary-cache-store.md"
;
}
};
struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual S3BinaryCacheStore
@ -430,9 +483,9 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
std::string marker;
do {
debug(format("listing bucket 's3://%s' from key '%s'...") % bucketName % marker);
debug("listing bucket 's3://%s' from key '%s'...", bucketName, marker);
auto res = checkAws(format("AWS error listing bucket '%s'") % bucketName,
auto res = checkAws(fmt("AWS error listing bucket '%s'", bucketName),
s3Helper.client->ListObjects(
Aws::S3::Model::ListObjectsRequest()
.WithBucket(bucketName)
@ -441,8 +494,8 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
auto & contents = res.GetContents();
debug(format("got %d keys, next marker '%s'")
% contents.size() % res.GetNextMarker());
debug("got %d keys, next marker '%s'",
contents.size(), res.GetNextMarker());
for (auto object : contents) {
auto & key = object.GetKey();

View file

@ -0,0 +1,8 @@
R"(
**Store URL format**: `s3://`*bucket-name*
This store allows reading and writing a binary cache stored in an AWS
S3 bucket.
)"

View file

@ -0,0 +1,26 @@
#include "store-api.hh"
namespace nix {
struct CommonSSHStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<Path> sshKey{(StoreConfig*) this, "", "ssh-key",
"Path to the SSH private key used to authenticate to the remote machine."};
const Setting<std::string> sshPublicHostKey{(StoreConfig*) this, "", "base64-ssh-public-host-key",
"The public host key of the remote machine."};
const Setting<bool> compress{(StoreConfig*) this, false, "compress",
"Whether to enable SSH compression."};
const Setting<std::string> remoteStore{(StoreConfig*) this, "", "remote-store",
R"(
[Store URL](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
to be used on the remote machine. The default is `auto`
(i.e. use the Nix daemon or `/nix/store` directly).
)"};
};
}

View file

@ -1,3 +1,4 @@
#include "ssh-store-config.hh"
#include "store-api.hh"
#include "remote-store.hh"
#include "remote-fs-accessor.hh"
@ -8,17 +9,22 @@
namespace nix {
struct SSHStoreConfig : virtual RemoteStoreConfig
struct SSHStoreConfig : virtual RemoteStoreConfig, virtual CommonSSHStoreConfig
{
using RemoteStoreConfig::RemoteStoreConfig;
using CommonSSHStoreConfig::CommonSSHStoreConfig;
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-daemon", "remote-program", "path to the nix-daemon executable on the remote system"};
const Setting<std::string> remoteStore{(StoreConfig*) this, "", "remote-store", "URI of the store on the remote system"};
const Setting<Path> remoteProgram{(StoreConfig*) this, "nix-daemon", "remote-program",
"Path to the `nix-daemon` executable on the remote machine."};
const std::string name() override { return "SSH Store"; }
const std::string name() override { return "Experimental SSH Store"; }
std::string doc() override
{
return
#include "ssh-store.md"
;
}
};
class SSHStore : public virtual SSHStoreConfig, public virtual RemoteStore
@ -28,6 +34,7 @@ public:
SSHStore(const std::string & scheme, const std::string & host, const Params & params)
: StoreConfig(params)
, RemoteStoreConfig(params)
, CommonSSHStoreConfig(params)
, SSHStoreConfig(params)
, Store(params)
, RemoteStore(params)
@ -49,9 +56,6 @@ public:
return *uriSchemes().begin() + "://" + host;
}
bool sameMachine() override
{ return false; }
// FIXME extend daemon protocol, move implementation to RemoteStore
std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ unsupported("getBuildLogExact"); }

View file

@ -0,0 +1,8 @@
R"(
**Store URL format**: `ssh-ng://[username@]hostname`
Experimental store type that allows full access to a Nix store on a
remote machine.
)"

View file

@ -465,10 +465,10 @@ StringSet StoreConfig::getDefaultSystemFeatures()
{
auto res = settings.systemFeatures.get();
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations))
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
res.insert("ca-derivations");
if (settings.isExperimentalFeatureEnabled(Xp::RecursiveNix))
if (experimentalFeatureSettings.isEnabled(Xp::RecursiveNix))
res.insert("recursive-nix");
return res;
@ -810,13 +810,13 @@ std::string Store::makeValidityRegistration(const StorePathSet & paths,
if (showHash) {
s += info->narHash.to_string(Base16, false) + "\n";
s += (format("%1%\n") % info->narSize).str();
s += fmt("%1%\n", info->narSize);
}
auto deriver = showDerivers && info->deriver ? printStorePath(*info->deriver) : "";
s += deriver + "\n";
s += (format("%1%\n") % info->references.size()).str();
s += fmt("%1%\n", info->references.size());
for (auto & j : info->references)
s += printStorePath(j) + "\n";
@ -875,6 +875,7 @@ json Store::pathInfoToJSON(const StorePathSet & storePaths,
auto info = queryPathInfo(storePath);
jsonPath["path"] = printStorePath(info->path);
jsonPath["valid"] = true;
jsonPath["narHash"] = info->narHash.to_string(hashBase, true);
jsonPath["narSize"] = info->narSize;
@ -1038,7 +1039,7 @@ std::map<StorePath, StorePath> copyPaths(
for (auto & path : paths) {
storePaths.insert(path.path());
if (auto realisation = std::get_if<Realisation>(&path.raw)) {
settings.requireExperimentalFeature(Xp::CaDerivations);
experimentalFeatureSettings.require(Xp::CaDerivations);
toplevelRealisations.insert(*realisation);
}
}
@ -1124,6 +1125,8 @@ std::map<StorePath, StorePath> copyPaths(
return storePathForDst;
};
uint64_t total = 0;
for (auto & missingPath : sortedMissing) {
auto info = srcStore.queryPathInfo(missingPath);
@ -1144,7 +1147,13 @@ std::map<StorePath, StorePath> copyPaths(
{storePathS, srcUri, dstUri});
PushActivity pact(act.id);
srcStore.narFromPath(missingPath, sink);
LambdaSink progressSink([&](std::string_view data) {
total += data.size();
act.progress(total, info->narSize);
});
TeeSink tee { sink, progressSink };
srcStore.narFromPath(missingPath, tee);
});
pathsToCopy.push_back(std::pair{infoForDst, std::move(source)});
}
@ -1265,7 +1274,7 @@ std::optional<StorePath> Store::getBuildDerivationPath(const StorePath & path)
}
}
if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations) || !isValidPath(path))
if (!experimentalFeatureSettings.isEnabled(Xp::CaDerivations) || !isValidPath(path))
return path;
auto drv = readDerivation(path);

File diff suppressed because it is too large Load diff

View file

@ -26,9 +26,9 @@ UDSRemoteStore::UDSRemoteStore(const Params & params)
UDSRemoteStore::UDSRemoteStore(
const std::string scheme,
std::string socket_path,
const Params & params)
const std::string scheme,
std::string socket_path,
const Params & params)
: UDSRemoteStore(params)
{
path.emplace(socket_path);

View file

@ -15,6 +15,13 @@ struct UDSRemoteStoreConfig : virtual LocalFSStoreConfig, virtual RemoteStoreCon
}
const std::string name() override { return "Local Daemon Store"; }
std::string doc() override
{
return
#include "uds-remote-store.md"
;
}
};
class UDSRemoteStore : public virtual UDSRemoteStoreConfig, public virtual LocalFSStore, public virtual RemoteStore
@ -29,9 +36,6 @@ public:
static std::set<std::string> uriSchemes()
{ return {"unix"}; }
bool sameMachine() override
{ return true; }
ref<FSAccessor> getFSAccessor() override
{ return LocalFSStore::getFSAccessor(); }

View file

@ -0,0 +1,9 @@
R"(
**Store URL format**: `daemon`, `unix://`*path*
This store type accesses a Nix store by talking to a Nix daemon
listening on the Unix domain socket *path*. The store pseudo-URL
`daemon` is equivalent to `unix:///nix/var/nix/daemon-socket/socket`.
)"

View file

@ -87,7 +87,7 @@ static time_t dump(const Path & path, Sink & sink, PathFilter & filter)
std::string name(i.name);
size_t pos = i.name.find(caseHackSuffix);
if (pos != std::string::npos) {
debug(format("removing case hack suffix from '%1%'") % (path + "/" + i.name));
debug("removing case hack suffix from '%1%'", path + "/" + i.name);
name.erase(pos);
}
if (!unhacked.emplace(name, i.name).second)
@ -262,7 +262,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
if (archiveSettings.useCaseHack) {
auto i = names.find(name);
if (i != names.end()) {
debug(format("case collision between '%1%' and '%2%'") % i->first % name);
debug("case collision between '%1%' and '%2%'", i->first, name);
name += caseHackSuffix;
name += std::to_string(++i->second);
} else

View file

@ -52,7 +52,7 @@ std::shared_ptr<Completions> completions;
std::string completionMarker = "___COMPLETE___";
std::optional<std::string> needsCompletion(std::string_view s)
static std::optional<std::string> needsCompletion(std::string_view s)
{
if (!completions) return {};
auto i = s.find(completionMarker);
@ -120,6 +120,12 @@ void Args::parseCmdline(const Strings & _cmdline)
if (!argsSeen)
initialFlagsProcessed();
/* Now that we are done parsing, make sure that any experimental
* feature required by the flags is enabled */
for (auto & f : flagExperimentalFeatures)
experimentalFeatureSettings.require(f);
}
bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
@ -128,12 +134,18 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
auto process = [&](const std::string & name, const Flag & flag) -> bool {
++pos;
if (auto & f = flag.experimentalFeature)
flagExperimentalFeatures.insert(*f);
std::vector<std::string> args;
bool anyCompleted = false;
for (size_t n = 0 ; n < flag.handler.arity; ++n) {
if (pos == end) {
if (flag.handler.arity == ArityAny || anyCompleted) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
throw UsageError(
"flag '%s' requires %d argument(s), but only %d were given",
name, flag.handler.arity, n);
}
if (auto prefix = needsCompletion(*pos)) {
anyCompleted = true;
@ -152,7 +164,11 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
for (auto & [name, flag] : longFlags) {
if (!hiddenCategories.count(flag->category)
&& hasPrefix(name, std::string(*prefix, 2)))
{
if (auto & f = flag->experimentalFeature)
flagExperimentalFeatures.insert(*f);
completions->add("--" + name, flag->description);
}
}
return false;
}
@ -172,7 +188,8 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
if (prefix == "-") {
completions->add("--");
for (auto & [flagName, flag] : shortFlags)
completions->add(std::string("-") + flagName, flag->description);
if (experimentalFeatureSettings.isEnabled(flag->experimentalFeature))
completions->add(std::string("-") + flagName, flag->description);
}
}
@ -219,6 +236,8 @@ nlohmann::json Args::toJSON()
auto flags = nlohmann::json::object();
for (auto & [name, flag] : longFlags) {
/* Skip experimental flags when listing flags. */
if (!experimentalFeatureSettings.isEnabled(flag->experimentalFeature)) continue;
auto j = nlohmann::json::object();
if (flag->aliases.count(name)) continue;
if (flag->shortName)

View file

@ -117,6 +117,8 @@ protected:
Handler handler;
std::function<void(size_t, std::string_view)> completer;
std::optional<ExperimentalFeature> experimentalFeature;
static Flag mkHashTypeFlag(std::string && longName, HashType * ht);
static Flag mkHashTypeOptFlag(std::string && longName, std::optional<HashType> * oht);
};
@ -188,6 +190,16 @@ public:
friend class MultiCommand;
MultiCommand * parent = nullptr;
private:
/**
* Experimental features needed when parsing args. These are checked
* after flag parsing is completed in order to support enabling
* experimental features coming after the flag that needs the
* experimental feature.
*/
std::set<ExperimentalFeature> flagExperimentalFeatures;
};
/* A command is an argument parser that can be executed by calling its
@ -198,7 +210,6 @@ struct Command : virtual public Args
virtual ~Command() { }
virtual void prepare() { };
virtual void run() = 0;
typedef int Category;
@ -254,8 +265,6 @@ enum CompletionType {
};
extern CompletionType completionType;
std::optional<std::string> needsCompletion(std::string_view s);
void completePath(size_t, std::string_view prefix);
void completeDir(size_t, std::string_view prefix);

View file

@ -70,10 +70,17 @@ void AbstractConfig::reapplyUnknownSettings()
set(s.first, s.second);
}
// Whether we should process the option. Excludes aliases, which are handled elsewhere, and disabled features.
static bool applicable(const Config::SettingData & sd)
{
return !sd.isAlias
&& experimentalFeatureSettings.isEnabled(sd.setting->experimentalFeature);
}
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (auto & opt : _settings)
if (!opt.second.isAlias && (!overriddenOnly || opt.second.setting->overridden))
if (applicable(opt.second) && (!overriddenOnly || opt.second.setting->overridden))
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
}
@ -147,9 +154,8 @@ nlohmann::json Config::toJSON()
{
auto res = nlohmann::json::object();
for (auto & s : _settings)
if (!s.second.isAlias) {
if (applicable(s.second))
res.emplace(s.first, s.second.setting->toJSON());
}
return res;
}
@ -157,24 +163,31 @@ std::string Config::toKeyValue()
{
auto res = std::string();
for (auto & s : _settings)
if (!s.second.isAlias) {
if (applicable(s.second))
res += fmt("%s = %s\n", s.first, s.second.setting->to_string());
}
return res;
}
void Config::convertToArgs(Args & args, const std::string & category)
{
for (auto & s : _settings)
for (auto & s : _settings) {
/* We do include args for settings gated on disabled
experimental-features. The args themselves however will also be
gated on any experimental feature the underlying setting is. */
if (!s.second.isAlias)
s.second.setting->convertToArg(args, category);
}
}
AbstractSetting::AbstractSetting(
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases)
: name(name), description(stripIndentation(description)), aliases(aliases)
const std::set<std::string> & aliases,
std::optional<ExperimentalFeature> experimentalFeature)
: name(name)
, description(stripIndentation(description))
, aliases(aliases)
, experimentalFeature(experimentalFeature)
{
}
@ -210,6 +223,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
.category = category,
.labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s); }},
.experimentalFeature = experimentalFeature,
});
if (isAppendable())
@ -219,6 +233,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
.category = category,
.labels = {"value"},
.handler = {[this](std::string s) { overridden = true; set(s, true); }},
.experimentalFeature = experimentalFeature,
});
}
@ -270,13 +285,15 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
.longName = name,
.description = fmt("Enable the `%s` setting.", name),
.category = category,
.handler = {[this]() { override(true); }}
.handler = {[this]() { override(true); }},
.experimentalFeature = experimentalFeature,
});
args.addFlag({
.longName = "no-" + name,
.description = fmt("Disable the `%s` setting.", name),
.category = category,
.handler = {[this]() { override(false); }}
.handler = {[this]() { override(false); }},
.experimentalFeature = experimentalFeature,
});
}
@ -444,4 +461,30 @@ GlobalConfig::Register::Register(Config * config)
configRegistrations->emplace_back(config);
}
ExperimentalFeatureSettings experimentalFeatureSettings;
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const
{
auto & f = experimentalFeatures.get();
return std::find(f.begin(), f.end(), feature) != f.end();
}
void ExperimentalFeatureSettings::require(const ExperimentalFeature & feature) const
{
if (!isEnabled(feature))
throw MissingExperimentalFeature(feature);
}
bool ExperimentalFeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const
{
return !feature || isEnabled(*feature);
}
void ExperimentalFeatureSettings::require(const std::optional<ExperimentalFeature> & feature) const
{
if (feature) require(*feature);
}
}

View file

@ -1,12 +1,13 @@
#pragma once
#include <cassert>
#include <map>
#include <set>
#include "types.hh"
#include <nlohmann/json_fwd.hpp>
#pragma once
#include "types.hh"
#include "experimental-features.hh"
namespace nix {
@ -194,12 +195,15 @@ public:
bool overridden = false;
std::optional<ExperimentalFeature> experimentalFeature;
protected:
AbstractSetting(
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases);
const std::set<std::string> & aliases,
std::optional<ExperimentalFeature> experimentalFeature = std::nullopt);
virtual ~AbstractSetting()
{
@ -240,8 +244,9 @@ public:
const bool documentDefault,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {})
: AbstractSetting(name, description, aliases)
const std::set<std::string> & aliases = {},
std::optional<ExperimentalFeature> experimentalFeature = std::nullopt)
: AbstractSetting(name, description, aliases, experimentalFeature)
, value(def)
, defaultValue(def)
, documentDefault(documentDefault)
@ -296,8 +301,9 @@ public:
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {},
const bool documentDefault = true)
: BaseSetting<T>(def, documentDefault, name, description, aliases)
const bool documentDefault = true,
std::optional<ExperimentalFeature> experimentalFeature = std::nullopt)
: BaseSetting<T>(def, documentDefault, name, description, aliases, experimentalFeature)
{
options->addSetting(this);
}
@ -357,4 +363,37 @@ struct GlobalConfig : public AbstractConfig
extern GlobalConfig globalConfig;
struct ExperimentalFeatureSettings : Config {
Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
"Experimental Nix features to enable."};
/**
* Check whether the given experimental feature is enabled.
*/
bool isEnabled(const ExperimentalFeature &) const;
/**
* Require an experimental feature be enabled, throwing an error if it is
* not.
*/
void require(const ExperimentalFeature &) const;
/**
* `std::nullopt` pointer means no feature, which means there is nothing that could be
* disabled, and so the function returns true in that case.
*/
bool isEnabled(const std::optional<ExperimentalFeature> &) const;
/**
* `std::nullopt` pointer means no feature, which means there is nothing that could be
* disabled, and so the function does nothing in that case.
*/
void require(const std::optional<ExperimentalFeature> &) const;
};
// FIXME: don't use a global variable.
extern ExperimentalFeatureSettings experimentalFeatureSettings;
}

View file

@ -302,14 +302,14 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
if (!einfo.traces.empty()) {
size_t count = 0;
for (const auto & trace : einfo.traces) {
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
if (!showTrace && count > 3) {
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
break;
}
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
count++;
frameOnly = trace.frame;

View file

@ -15,9 +15,9 @@ static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
{
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
if (includePid)
return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
return fmt("%1%/%2%-%3%-%4%", tmpRoot, prefix, getpid(), counter++);
else
return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
return fmt("%1%/%2%-%3%", tmpRoot, prefix, counter++);
}
Path createTempDir(const Path & tmpRoot, const Path & prefix,

View file

@ -17,16 +17,6 @@ using boost::format;
struct nop { template<typename... T> nop(T...) {} };
struct FormatOrString
{
std::string s;
FormatOrString(std::string s) : s(std::move(s)) { };
template<class F>
FormatOrString(const F & f) : s(f.str()) { };
FormatOrString(const char * s) : s(s) { };
};
/* A helper for formatting strings. fmt(format, a_0, ..., a_n) is
equivalent to boost::format(format) % a_0 % ... %
... a_n. However, fmt(s) is equivalent to s (so no %-expansion
@ -53,11 +43,6 @@ inline std::string fmt(const char * s)
return s;
}
inline std::string fmt(const FormatOrString & fs)
{
return fs.s;
}
template<typename... Args>
inline std::string fmt(const std::string & fs, const Args & ... args)
{

View file

@ -71,12 +71,13 @@ const std::string base16Chars = "0123456789abcdef";
static std::string printHash16(const Hash & hash)
{
char buf[hash.hashSize * 2];
std::string buf;
buf.reserve(hash.hashSize * 2);
for (unsigned int i = 0; i < hash.hashSize; i++) {
buf[i * 2] = base16Chars[hash.hash[i] >> 4];
buf[i * 2 + 1] = base16Chars[hash.hash[i] & 0x0f];
buf.push_back(base16Chars[hash.hash[i] >> 4]);
buf.push_back(base16Chars[hash.hash[i] & 0x0f]);
}
return std::string(buf, hash.hashSize * 2);
return buf;
}
@ -130,7 +131,7 @@ std::string Hash::to_string(Base base, bool includeType) const
break;
case Base64:
case SRI:
s += base64Encode(std::string((const char *) hash, hashSize));
s += base64Encode(std::string_view((const char *) hash, hashSize));
break;
}
return s;
@ -403,7 +404,7 @@ HashType parseHashType(std::string_view s)
throw UsageError("unknown hash algorithm '%1%'", s);
}
std::string printHashType(HashType ht)
std::string_view printHashType(HashType ht)
{
switch (ht) {
case htMD5: return "md5";

View file

@ -133,7 +133,7 @@ HashType parseHashType(std::string_view s);
std::optional<HashType> parseHashTypeOpt(std::string_view s);
/* And the reverse. */
std::string printHashType(HashType ht);
std::string_view printHashType(HashType ht);
union Ctx;

View file

@ -32,7 +32,8 @@ void Logger::warn(const std::string & msg)
void Logger::writeToStdout(std::string_view s)
{
std::cout << s << "\n";
writeFull(STDOUT_FILENO, s);
writeFull(STDOUT_FILENO, "\n");
}
class SimpleLogger : public Logger
@ -53,7 +54,7 @@ public:
return printBuildLogs;
}
void log(Verbosity lvl, const FormatOrString & fs) override
void log(Verbosity lvl, std::string_view s) override
{
if (lvl > verbosity) return;
@ -71,7 +72,7 @@ public:
prefix = std::string("<") + c + ">";
}
writeToStderr(prefix + filterANSIEscapes(fs.s, !tty) + "\n");
writeToStderr(prefix + filterANSIEscapes(s, !tty) + "\n");
}
void logEI(const ErrorInfo & ei) override
@ -84,7 +85,7 @@ public:
void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
const std::string & s, const Fields & fields, ActivityId parent)
override
override
{
if (lvl <= verbosity && !s.empty())
log(lvl, s + "...");
@ -173,12 +174,12 @@ struct JSONLogger : Logger {
prevLogger.log(lvlError, "@nix " + json.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace));
}
void log(Verbosity lvl, const FormatOrString & fs) override
void log(Verbosity lvl, std::string_view s) override
{
nlohmann::json json;
json["action"] = "msg";
json["level"] = lvl;
json["msg"] = fs.s;
json["msg"] = s;
write(json);
}

View file

@ -75,11 +75,11 @@ public:
// Whether the logger prints the whole build log
virtual bool isVerbose() { return false; }
virtual void log(Verbosity lvl, const FormatOrString & fs) = 0;
virtual void log(Verbosity lvl, std::string_view s) = 0;
void log(const FormatOrString & fs)
void log(std::string_view s)
{
log(lvlInfo, fs);
log(lvlInfo, s);
}
virtual void logEI(const ErrorInfo & ei) = 0;
@ -102,11 +102,9 @@ public:
virtual void writeToStdout(std::string_view s);
template<typename... Args>
inline void cout(const std::string & fs, const Args & ... args)
inline void cout(const Args & ... args)
{
boost::format f(fs);
formatHelper(f, args...);
writeToStdout(f.str());
writeToStdout(fmt(args...));
}
virtual std::optional<char> ask(std::string_view s)

View file

@ -415,7 +415,7 @@ Error readError(Source & source)
auto msg = readString(source);
ErrorInfo info {
.level = level,
.msg = hintformat(std::move(format("%s") % msg)),
.msg = hintformat(fmt("%s", msg)),
};
auto havePos = readNum<size_t>(source);
assert(havePos == 0);
@ -424,7 +424,7 @@ Error readError(Source & source)
havePos = readNum<size_t>(source);
assert(havePos == 0);
info.traces.push_back(Trace {
.hint = hintformat(std::move(format("%s") % readString(source)))
.hint = hintformat(fmt("%s", readString(source)))
});
}
return Error(std::move(info));

View file

@ -23,7 +23,7 @@ const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)";
const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)";
// A Git ref (i.e. branch or tag name).
const static std::string refRegexS = "[a-zA-Z0-9][a-zA-Z0-9_.\\/-]*"; // FIXME: check
const static std::string refRegexS = "[a-zA-Z0-9@][a-zA-Z0-9_.\\/@-]*"; // FIXME: check
extern std::regex refRegex;
// Instead of defining what a good Git Ref is, we define what a bad Git Ref is

View file

@ -54,6 +54,11 @@ std::optional<std::string> getEnv(const std::string & key)
return std::string(value);
}
std::optional<std::string> getEnvNonEmpty(const std::string & key) {
auto value = getEnv(key);
if (value == "") return {};
return value;
}
std::map<std::string, std::string> getEnv()
{
@ -523,7 +528,7 @@ void deletePath(const Path & path)
void deletePath(const Path & path, uint64_t & bytesFreed)
{
//Activity act(*logger, lvlDebug, format("recursively deleting path '%1%'") % path);
//Activity act(*logger, lvlDebug, "recursively deleting path '%1%'", path);
bytesFreed = 0;
_deletePath(path, bytesFreed);
}
@ -1065,12 +1070,14 @@ static pid_t doFork(bool allowVfork, std::function<void()> fun)
}
#if __linux__
static int childEntry(void * arg)
{
auto main = (std::function<void()> *) arg;
(*main)();
return 1;
}
#endif
pid_t startProcess(std::function<void()> fun, const ProcessOptions & options)
@ -1394,14 +1401,14 @@ std::string statusToString(int status)
{
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
if (WIFEXITED(status))
return (format("failed with exit code %1%") % WEXITSTATUS(status)).str();
return fmt("failed with exit code %1%", WEXITSTATUS(status));
else if (WIFSIGNALED(status)) {
int sig = WTERMSIG(status);
#if HAVE_STRSIGNAL
const char * description = strsignal(sig);
return (format("failed due to signal %1% (%2%)") % sig % description).str();
return fmt("failed due to signal %1% (%2%)", sig, description);
#else
return (format("failed due to signal %1%") % sig).str();
return fmt("failed due to signal %1%", sig);
#endif
}
else
@ -1470,7 +1477,7 @@ bool shouldANSI()
&& !getEnv("NO_COLOR").has_value();
}
std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned int width)
std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int width)
{
std::string t, e;
size_t w = 0;
@ -1961,7 +1968,7 @@ std::string showBytes(uint64_t bytes)
// FIXME: move to libstore/build
void commonChildInit(Pipe & logPipe)
void commonChildInit()
{
logger = makeSimpleLogger();
@ -1975,10 +1982,6 @@ void commonChildInit(Pipe & logPipe)
if (setsid() == -1)
throw SysError("creating a new session");
/* Dup the write side of the logger pipe into stderr. */
if (dup2(logPipe.writeSide.get(), STDERR_FILENO) == -1)
throw SysError("cannot pipe standard error into log file");
/* Dup stderr to stdout. */
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
throw SysError("cannot dup stderr into stdout");

View file

@ -39,6 +39,10 @@ extern const std::string nativeSystem;
/* Return an environment variable. */
std::optional<std::string> getEnv(const std::string & key);
/* Return a non empty environment variable. Returns nullopt if the env
variable is set to "" */
std::optional<std::string> getEnvNonEmpty(const std::string & key);
/* Get the entire environment. */
std::map<std::string, std::string> getEnv();
@ -446,7 +450,6 @@ template<class C> Strings quoteStrings(const C & c)
return res;
}
/* Remove trailing whitespace from a string. FIXME: return
std::string_view. */
std::string chomp(std::string_view s);
@ -569,7 +572,7 @@ bool shouldANSI();
some escape sequences (such as colour setting) are copied but not
included in the character count. Also, tabs are expanded to
spaces. */
std::string filterANSIEscapes(const std::string & s,
std::string filterANSIEscapes(std::string_view s,
bool filterAll = false,
unsigned int width = std::numeric_limits<unsigned int>::max());
@ -700,7 +703,7 @@ typedef std::function<bool(const Path & path)> PathFilter;
extern PathFilter defaultPathFilter;
/* Common initialisation performed in child processes. */
void commonChildInit(Pipe & logPipe);
void commonChildInit();
/* Create a Unix domain socket. */
AutoCloseFD createUnixDomainSocket();

View file

@ -219,9 +219,9 @@ static void main_nix_build(int argc, char * * argv)
// read the shebang to understand which packages to read from. Since
// this is handled via nix-shell -p, we wrap our ruby script execution
// in ruby -e 'load' which ignores the shebangs.
envCommand = (format("exec %1% %2% -e 'load(ARGV.shift)' -- %3% %4%") % execArgs % interpreter % shellEscape(script) % joined.str()).str();
envCommand = fmt("exec %1% %2% -e 'load(ARGV.shift)' -- %3% %4%", execArgs, interpreter, shellEscape(script), joined.str());
} else {
envCommand = (format("exec %1% %2% %3% %4%") % execArgs % interpreter % shellEscape(script) % joined.str()).str();
envCommand = fmt("exec %1% %2% %3% %4%", execArgs, interpreter, shellEscape(script), joined.str());
}
}
@ -440,7 +440,7 @@ static void main_nix_build(int argc, char * * argv)
shell = store->printStorePath(shellDrvOutputs.at("out").value()) + "/bin/bash";
}
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
auto resolvedDrv = drv.tryResolve(*store);
assert(resolvedDrv && "Successfully resolved the derivation");
drv = *resolvedDrv;

View file

@ -168,7 +168,8 @@ static int main_nix_channel(int argc, char ** argv)
nixDefExpr = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : home + "/.nix-defexpr";
// Figure out the name of the channels profile.
profile = profilesDir() + "/channels";
profile = profilesDir() + "/channels";
createDirs(dirOf(profile));
enum {
cNone,

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