Merge pull request #7763 from obsidiansystems/installable-wide-info

Stratify `ExtraPathInfo` along `Installable` hierarchy
This commit is contained in:
Eelco Dolstra 2023-03-27 17:04:08 +02:00 committed by GitHub
commit 5e3f855526
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 254 additions and 76 deletions

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "installables.hh" #include "installable-value.hh"
#include "args.hh" #include "args.hh"
#include "common-eval-args.hh" #include "common-eval-args.hh"
#include "path.hh" #include "path.hh"

View file

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

View file

@ -10,7 +10,10 @@ std::string InstallableDerivedPath::what() const
DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths() DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths()
{ {
return {{.path = derivedPath, .info = {} }}; return {{
.path = derivedPath,
.info = make_ref<ExtraPathInfo>(),
}};
} }
std::optional<StorePath> InstallableDerivedPath::getStorePath() std::optional<StorePath> InstallableDerivedPath::getStorePath()

View file

@ -101,7 +101,8 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
return {{ return {{
.path = DerivedPath::Opaque { .path = DerivedPath::Opaque {
.path = std::move(storePath), .path = std::move(storePath),
} },
.info = make_ref<ExtraPathInfo>(),
}}; }};
} }
@ -113,7 +114,8 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
return {{ return {{
.path = DerivedPath::Opaque { .path = DerivedPath::Opaque {
.path = std::move(*storePath), .path = std::move(*storePath),
} },
.info = make_ref<ExtraPathInfo>(),
}}; }};
} else } else
throw Error("flake output attribute '%s' evaluates to the string '%s' which is not a store path", attrPath, s); 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()), }, extendedOutputsSpec.raw()),
}, },
.info = { .info = make_ref<ExtraPathInfoFlake>(
.priority = priority, ExtraPathInfoValue::Value {
.originalRef = flakeRef, .priority = priority,
.resolvedRef = getLockedFlake()->flake.lockedRef, .attrPath = attrPath,
.attrPath = attrPath, .extendedOutputsSpec = extendedOutputsSpec,
.extendedOutputsSpec = extendedOutputsSpec, },
} ExtraPathInfoFlake::Flake {
.originalRef = flakeRef,
.resolvedRef = getLockedFlake()->flake.lockedRef,
}),
}}; }};
} }
@ -212,6 +217,7 @@ std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{ {
if (!_lockedFlake) { if (!_lockedFlake) {
flake::LockFlags lockFlagsApplyConfig = lockFlags; flake::LockFlags lockFlagsApplyConfig = lockFlags;
// FIXME why this side effect?
lockFlagsApplyConfig.applyNixConfig = true; lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig)); _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
} }
@ -229,7 +235,7 @@ FlakeRef InstallableFlake::nixpkgsFlakeRef() const
} }
} }
return Installable::nixpkgsFlakeRef(); return InstallableValue::nixpkgsFlakeRef();
} }
} }

View file

@ -4,6 +4,30 @@
namespace nix { 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 struct InstallableFlake : InstallableValue
{ {
FlakeRef flakeRef; FlakeRef flakeRef;
@ -33,8 +57,10 @@ struct InstallableFlake : InstallableValue
std::pair<Value *, PosIdx> toValue(EvalState & state) override; 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>> std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state) override; getCursors(EvalState & state) override;

View file

@ -1,9 +1,15 @@
#pragma once #pragma once
#include "installables.hh" #include "installables.hh"
#include "flake/flake.hh"
namespace nix { namespace nix {
struct DrvInfo;
struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
struct App struct App
{ {
std::vector<DerivedPath> context; std::vector<DerivedPath> context;
@ -17,26 +23,83 @@ struct UnresolvedApp
App resolve(ref<Store> evalStore, ref<Store> store); 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 struct InstallableValue : Installable
{ {
ref<EvalState> state; ref<EvalState> state;
InstallableValue(ref<EvalState> state) : state(state) {} InstallableValue(ref<EvalState> state) : state(state) {}
virtual ~InstallableValue() { }
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0; 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. */ * 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>> virtual std::vector<ref<eval_cache::AttrCursor>>
getCursors(EvalState & state); getCursors(EvalState & state);
/* Get the first and most preferred cursor this Installable could refer /**
to, or throw an exception if none exists. */ * Get the first and most preferred cursor this Installable could
* refer to, or throw an exception if none exists.
*/
virtual ref<eval_cache::AttrCursor> virtual ref<eval_cache::AttrCursor>
getCursor(EvalState & state); getCursor(EvalState & state);
UnresolvedApp toApp(EvalState & state); UnresolvedApp toApp(EvalState & state);
virtual FlakeRef nixpkgsFlakeRef() const
{
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
}
static InstallableValue & require(Installable & installable); static InstallableValue & require(Installable & installable);
static ref<InstallableValue> require(ref<Installable> installable); static ref<InstallableValue> require(ref<Installable> installable);
}; };

View file

@ -517,7 +517,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
struct Aux struct Aux
{ {
ExtraPathInfo info; ref<ExtraPathInfo> info;
ref<Installable> installable; ref<Installable> installable;
}; };

View file

@ -4,9 +4,7 @@
#include "path.hh" #include "path.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "derived-path.hh" #include "derived-path.hh"
#include "eval.hh"
#include "store-api.hh" #include "store-api.hh"
#include "flake/flake.hh"
#include "build-result.hh" #include "build-result.hh"
#include <optional> #include <optional>
@ -14,83 +12,144 @@
namespace nix { namespace nix {
struct DrvInfo; struct DrvInfo;
struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; }
enum class Realise { enum class Realise {
/* Build the derivation. Postcondition: the /**
derivation outputs exist. */ * Build the derivation.
*
* Postcondition: the derivation outputs exist.
*/
Outputs, Outputs,
/* Don't build the derivation. Postcondition: the store derivation /**
exists. */ * Don't build the derivation.
*
* Postcondition: the store derivation exists.
*/
Derivation, Derivation,
/* Evaluate in dry-run mode. Postcondition: nothing. */ /**
// FIXME: currently unused, but could be revived if we can * Evaluate in dry-run mode.
// evaluate derivations in-memory. *
* Postcondition: nothing.
*
* \todo currently unused, but could be revived if we can evaluate
* derivations in-memory.
*/
Nothing 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 { enum class OperateOn {
/* Operate on the output path. */ /**
* Operate on the output path.
*/
Output, Output,
/* Operate on the .drv path. */ /**
* Operate on the .drv path.
*/
Derivation 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 struct ExtraPathInfo
{ {
std::optional<NixInt> priority; virtual ~ExtraPathInfo() = default;
std::optional<FlakeRef> originalRef;
std::optional<FlakeRef> resolvedRef;
std::optional<std::string> attrPath;
// FIXME: merge with DerivedPath's 'outputs' field?
std::optional<ExtendedOutputsSpec> extendedOutputsSpec;
}; };
/* 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 struct DerivedPathWithInfo
{ {
DerivedPath path; 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 struct BuiltPathWithResult
{ {
BuiltPath path; BuiltPath path;
ExtraPathInfo info; ref<ExtraPathInfo> info;
std::optional<BuildResult> result; std::optional<BuildResult> result;
}; };
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
*/
typedef std::vector<DerivedPathWithInfo> DerivedPathsWithInfo; typedef std::vector<DerivedPathWithInfo> DerivedPathsWithInfo;
struct Installable; struct Installable;
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
*/
typedef std::vector<ref<Installable>> Installables; 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 struct Installable
{ {
virtual ~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; 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; 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(); DerivedPathWithInfo toDerivedPath();
/* 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() virtual std::optional<StorePath> getStorePath()
{ {
return {}; return {};
} }
virtual FlakeRef nixpkgsFlakeRef() const
{
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
}
static std::vector<BuiltPathWithResult> build( static std::vector<BuiltPathWithResult> build(
ref<Store> evalStore, ref<Store> evalStore,
ref<Store> store, ref<Store> store,

View file

@ -1,4 +1,3 @@
#include "eval.hh"
#include "command.hh" #include "command.hh"
#include "common-args.hh" #include "common-args.hh"
#include "shared.hh" #include "shared.hh"

View file

@ -5,6 +5,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "local-fs-store.hh" #include "local-fs-store.hh"
#include "fs-accessor.hh" #include "fs-accessor.hh"
#include "eval-inline.hh"
using namespace nix; using namespace nix;

View file

@ -1,6 +1,6 @@
#include "eval.hh" #include "eval.hh"
#include "command.hh"
#include "installable-flake.hh" #include "installable-flake.hh"
#include "command-installable-value.hh"
#include "common-args.hh" #include "common-args.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
@ -252,7 +252,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
throw Error("get-env.sh failed to produce an environment"); throw Error("get-env.sh failed to produce an environment");
} }
struct Common : InstallableCommand, MixProfile struct Common : InstallableValueCommand, MixProfile
{ {
std::set<std::string> ignoreVars{ std::set<std::string> ignoreVars{
"BASHOPTS", "BASHOPTS",
@ -374,7 +374,7 @@ struct Common : InstallableCommand, MixProfile
return res; return res;
} }
StorePath getShellOutPath(ref<Store> store, ref<Installable> installable) StorePath getShellOutPath(ref<Store> store, ref<InstallableValue> installable)
{ {
auto path = installable->getStorePath(); auto path = installable->getStorePath();
if (path && hasSuffix(path->to_string(), "-env")) if (path && hasSuffix(path->to_string(), "-env"))
@ -393,7 +393,7 @@ struct Common : InstallableCommand, MixProfile
} }
std::pair<BuildEnvironment, std::string> std::pair<BuildEnvironment, std::string>
getBuildEnvironment(ref<Store> store, ref<Installable> installable) getBuildEnvironment(ref<Store> store, ref<InstallableValue> installable)
{ {
auto shellOutPath = getShellOutPath(store, installable); auto shellOutPath = getShellOutPath(store, installable);
@ -481,7 +481,7 @@ struct CmdDevelop : Common, MixEnvironment
; ;
} }
void run(ref<Store> store, ref<Installable> installable) override void run(ref<Store> store, ref<InstallableValue> installable) override
{ {
auto [buildEnvironment, gcroot] = getBuildEnvironment(store, installable); auto [buildEnvironment, gcroot] = getBuildEnvironment(store, installable);
@ -605,7 +605,7 @@ struct CmdPrintDevEnv : Common, MixJSON
Category category() override { return catUtility; } Category category() override { return catUtility; }
void run(ref<Store> store, ref<Installable> installable) override void run(ref<Store> store, ref<InstallableValue> installable) override
{ {
auto buildEnvironment = getBuildEnvironment(store, installable).first; auto buildEnvironment = getBuildEnvironment(store, installable).first;

View file

@ -254,13 +254,19 @@ struct ProfileManifest
} }
}; };
static std::map<Installable *, std::pair<BuiltPaths, ExtraPathInfo>> static std::map<Installable *, std::pair<BuiltPaths, ref<ExtraPathInfo>>>
builtPathsPerInstallable( builtPathsPerInstallable(
const std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> & builtPaths) const std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> & builtPaths)
{ {
std::map<Installable *, std::pair<BuiltPaths, ExtraPathInfo>> res; std::map<Installable *, std::pair<BuiltPaths, ref<ExtraPathInfo>>> res;
for (auto & [installable, builtPath] : builtPaths) { for (auto & [installable, builtPath] : builtPaths) {
auto & r = res[&*installable]; auto & r = res.insert({
&*installable,
{
{},
make_ref<ExtraPathInfo>(),
}
}).first->second;
/* Note that there could be conflicting info /* Note that there could be conflicting info
(e.g. meta.priority fields) if the installable returned (e.g. meta.priority fields) if the installable returned
multiple derivations. So pick one arbitrarily. FIXME: multiple derivations. So pick one arbitrarily. FIXME:
@ -307,14 +313,16 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
for (auto & installable : installables) { for (auto & installable : installables) {
ProfileElement element; ProfileElement element;
auto & [res, info] = builtPaths[&*installable]; auto iter = builtPaths.find(&*installable);
if (iter == builtPaths.end()) continue;
auto & [res, info] = iter->second;
if (info.originalRef && info.resolvedRef && info.attrPath && info.extendedOutputsSpec) { if (auto * info2 = dynamic_cast<ExtraPathInfoFlake *>(&*info)) {
element.source = ProfileElementSource { element.source = ProfileElementSource {
.originalRef = *info.originalRef, .originalRef = info2->flake.originalRef,
.resolvedRef = *info.resolvedRef, .resolvedRef = info2->flake.resolvedRef,
.attrPath = *info.attrPath, .attrPath = info2->value.attrPath,
.outputs = *info.extendedOutputsSpec, .outputs = info2->value.extendedOutputsSpec,
}; };
} }
@ -323,7 +331,12 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
element.priority = element.priority =
priority priority
? *priority ? *priority
: info.priority.value_or(defaultPriority); : ({
auto * info2 = dynamic_cast<ExtraPathInfoValue *>(&*info);
info2
? info2->value.priority.value_or(defaultPriority)
: defaultPriority;
});
element.updateStorePaths(getEvalStore(), store, res); element.updateStorePaths(getEvalStore(), store, res);
@ -541,19 +554,20 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
auto derivedPaths = installable->toDerivedPaths(); auto derivedPaths = installable->toDerivedPaths();
if (derivedPaths.empty()) continue; if (derivedPaths.empty()) continue;
auto & info = derivedPaths[0].info; auto * infop = dynamic_cast<ExtraPathInfoFlake *>(&*derivedPaths[0].info);
// `InstallableFlake` should use `ExtraPathInfoFlake`.
assert(infop);
auto & info = *infop;
assert(info.resolvedRef && info.attrPath); if (element.source->resolvedRef == info.flake.resolvedRef) continue;
if (element.source->resolvedRef == info.resolvedRef) continue;
printInfo("upgrading '%s' from flake '%s' to '%s'", printInfo("upgrading '%s' from flake '%s' to '%s'",
element.source->attrPath, element.source->resolvedRef, *info.resolvedRef); element.source->attrPath, element.source->resolvedRef, info.flake.resolvedRef);
element.source = ProfileElementSource { element.source = ProfileElementSource {
.originalRef = installable->flakeRef, .originalRef = installable->flakeRef,
.resolvedRef = *info.resolvedRef, .resolvedRef = info.flake.resolvedRef,
.attrPath = *info.attrPath, .attrPath = info.value.attrPath,
.outputs = installable->extendedOutputsSpec, .outputs = installable->extendedOutputsSpec,
}; };
@ -582,7 +596,10 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
for (size_t i = 0; i < installables.size(); ++i) { for (size_t i = 0; i < installables.size(); ++i) {
auto & installable = installables.at(i); auto & installable = installables.at(i);
auto & element = manifest.elements[indices.at(i)]; auto & element = manifest.elements[indices.at(i)];
element.updateStorePaths(getEvalStore(), store, builtPaths[&*installable].first); element.updateStorePaths(
getEvalStore(),
store,
builtPaths.find(&*installable)->second.first);
} }
updateProfile(manifest.build(store)); updateProfile(manifest.build(store));