forked from lix-project/lix
Merge pull request #4836 from NixOS/ca/track-drvoutput-dependencies-2-le-retour
Track the dependencies of CA realisations
This commit is contained in:
commit
4f9508c3b5
|
@ -927,6 +927,7 @@ void DerivationGoal::resolvedFinished() {
|
||||||
auto newRealisation = *realisation;
|
auto newRealisation = *realisation;
|
||||||
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
|
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
|
||||||
newRealisation.signatures.clear();
|
newRealisation.signatures.clear();
|
||||||
|
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
|
||||||
signRealisation(newRealisation);
|
signRealisation(newRealisation);
|
||||||
worker.store.registerDrvOutput(newRealisation);
|
worker.store.registerDrvOutput(newRealisation);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,10 +3,19 @@
|
||||||
-- is enabled
|
-- is enabled
|
||||||
|
|
||||||
create table if not exists Realisations (
|
create table if not exists Realisations (
|
||||||
|
id integer primary key autoincrement not null,
|
||||||
drvPath text not null,
|
drvPath text not null,
|
||||||
outputName text not null, -- symbolic output id, usually "out"
|
outputName text not null, -- symbolic output id, usually "out"
|
||||||
outputPath integer not null,
|
outputPath integer not null,
|
||||||
signatures text, -- space-separated list
|
signatures text, -- space-separated list
|
||||||
primary key (drvPath, outputName),
|
|
||||||
foreign key (outputPath) references ValidPaths(id) on delete cascade
|
foreign key (outputPath) references ValidPaths(id) on delete cascade
|
||||||
);
|
);
|
||||||
|
|
||||||
|
create index if not exists IndexRealisations on Realisations(drvPath, outputName);
|
||||||
|
|
||||||
|
create table if not exists RealisationsRefs (
|
||||||
|
referrer integer not null,
|
||||||
|
realisationReference integer,
|
||||||
|
foreign key (referrer) references Realisations(id) on delete cascade,
|
||||||
|
foreign key (realisationReference) references Realisations(id) on delete restrict
|
||||||
|
);
|
||||||
|
|
|
@ -59,6 +59,8 @@ struct LocalStore::State::Stmts {
|
||||||
SQLiteStmt QueryAllRealisedOutputs;
|
SQLiteStmt QueryAllRealisedOutputs;
|
||||||
SQLiteStmt QueryPathFromHashPart;
|
SQLiteStmt QueryPathFromHashPart;
|
||||||
SQLiteStmt QueryValidPaths;
|
SQLiteStmt QueryValidPaths;
|
||||||
|
SQLiteStmt QueryRealisationReferences;
|
||||||
|
SQLiteStmt AddRealisationReference;
|
||||||
};
|
};
|
||||||
|
|
||||||
int getSchema(Path schemaPath)
|
int getSchema(Path schemaPath)
|
||||||
|
@ -76,7 +78,7 @@ int getSchema(Path schemaPath)
|
||||||
|
|
||||||
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
||||||
{
|
{
|
||||||
const int nixCASchemaVersion = 1;
|
const int nixCASchemaVersion = 2;
|
||||||
int curCASchema = getSchema(schemaPath);
|
int curCASchema = getSchema(schemaPath);
|
||||||
if (curCASchema != nixCASchemaVersion) {
|
if (curCASchema != nixCASchemaVersion) {
|
||||||
if (curCASchema > nixCASchemaVersion) {
|
if (curCASchema > nixCASchemaVersion) {
|
||||||
|
@ -94,7 +96,39 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
||||||
#include "ca-specific-schema.sql.gen.hh"
|
#include "ca-specific-schema.sql.gen.hh"
|
||||||
;
|
;
|
||||||
db.exec(schema);
|
db.exec(schema);
|
||||||
|
curCASchema = nixCASchemaVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (curCASchema < 2) {
|
||||||
|
SQLiteTxn txn(db);
|
||||||
|
// Ugly little sql dance to add a new `id` column and make it the primary key
|
||||||
|
db.exec(R"(
|
||||||
|
create table Realisations2 (
|
||||||
|
id integer primary key autoincrement not null,
|
||||||
|
drvPath text not null,
|
||||||
|
outputName text not null, -- symbolic output id, usually "out"
|
||||||
|
outputPath integer not null,
|
||||||
|
signatures text, -- space-separated list
|
||||||
|
foreign key (outputPath) references ValidPaths(id) on delete cascade
|
||||||
|
);
|
||||||
|
insert into Realisations2 (drvPath, outputName, outputPath, signatures)
|
||||||
|
select drvPath, outputName, outputPath, signatures from Realisations;
|
||||||
|
drop table Realisations;
|
||||||
|
alter table Realisations2 rename to Realisations;
|
||||||
|
)");
|
||||||
|
db.exec(R"(
|
||||||
|
create index if not exists IndexRealisations on Realisations(drvPath, outputName);
|
||||||
|
|
||||||
|
create table if not exists RealisationsRefs (
|
||||||
|
referrer integer not null,
|
||||||
|
realisationReference integer,
|
||||||
|
foreign key (referrer) references Realisations(id) on delete cascade,
|
||||||
|
foreign key (realisationReference) references Realisations(id) on delete restrict
|
||||||
|
);
|
||||||
|
)");
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
|
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
|
||||||
lockFile(lockFd.get(), ltRead, true);
|
lockFile(lockFd.get(), ltRead, true);
|
||||||
}
|
}
|
||||||
|
@ -313,7 +347,7 @@ LocalStore::LocalStore(const Params & params)
|
||||||
)");
|
)");
|
||||||
state->stmts->QueryRealisedOutput.create(state->db,
|
state->stmts->QueryRealisedOutput.create(state->db,
|
||||||
R"(
|
R"(
|
||||||
select Output.path, Realisations.signatures from Realisations
|
select Realisations.id, Output.path, Realisations.signatures from Realisations
|
||||||
inner join ValidPaths as Output on Output.id = Realisations.outputPath
|
inner join ValidPaths as Output on Output.id = Realisations.outputPath
|
||||||
where drvPath = ? and outputName = ?
|
where drvPath = ? and outputName = ?
|
||||||
;
|
;
|
||||||
|
@ -325,6 +359,19 @@ LocalStore::LocalStore(const Params & params)
|
||||||
where drvPath = ?
|
where drvPath = ?
|
||||||
;
|
;
|
||||||
)");
|
)");
|
||||||
|
state->stmts->QueryRealisationReferences.create(state->db,
|
||||||
|
R"(
|
||||||
|
select drvPath, outputName from Realisations
|
||||||
|
join RealisationsRefs on realisationReference = Realisations.id
|
||||||
|
where referrer = ?;
|
||||||
|
)");
|
||||||
|
state->stmts->AddRealisationReference.create(state->db,
|
||||||
|
R"(
|
||||||
|
insert or replace into RealisationsRefs (referrer, realisationReference)
|
||||||
|
values (
|
||||||
|
?,
|
||||||
|
(select id from Realisations where drvPath = ? and outputName = ?));
|
||||||
|
)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -661,14 +708,22 @@ void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag check
|
||||||
void LocalStore::registerDrvOutput(const Realisation & info)
|
void LocalStore::registerDrvOutput(const Realisation & info)
|
||||||
{
|
{
|
||||||
settings.requireExperimentalFeature("ca-derivations");
|
settings.requireExperimentalFeature("ca-derivations");
|
||||||
auto state(_state.lock());
|
|
||||||
retrySQLite<void>([&]() {
|
retrySQLite<void>([&]() {
|
||||||
|
auto state(_state.lock());
|
||||||
state->stmts->RegisterRealisedOutput.use()
|
state->stmts->RegisterRealisedOutput.use()
|
||||||
(info.id.strHash())
|
(info.id.strHash())
|
||||||
(info.id.outputName)
|
(info.id.outputName)
|
||||||
(printStorePath(info.outPath))
|
(printStorePath(info.outPath))
|
||||||
(concatStringsSep(" ", info.signatures))
|
(concatStringsSep(" ", info.signatures))
|
||||||
.exec();
|
.exec();
|
||||||
|
uint64_t myId = state->db.getLastInsertedRowId();
|
||||||
|
for (auto & [outputId, _] : info.dependentRealisations) {
|
||||||
|
state->stmts->AddRealisationReference.use()
|
||||||
|
(myId)
|
||||||
|
(outputId.strHash())
|
||||||
|
(outputId.outputName)
|
||||||
|
.exec();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1684,14 +1739,38 @@ std::optional<const Realisation> LocalStore::queryRealisation(
|
||||||
typedef std::optional<const Realisation> Ret;
|
typedef std::optional<const Realisation> Ret;
|
||||||
return retrySQLite<Ret>([&]() -> Ret {
|
return retrySQLite<Ret>([&]() -> Ret {
|
||||||
auto state(_state.lock());
|
auto state(_state.lock());
|
||||||
auto use(state->stmts->QueryRealisedOutput.use()(id.strHash())(
|
auto useQueryRealisedOutput(state->stmts->QueryRealisedOutput.use()
|
||||||
id.outputName));
|
(id.strHash())
|
||||||
if (!use.next())
|
(id.outputName));
|
||||||
|
if (!useQueryRealisedOutput.next())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
auto outputPath = parseStorePath(use.getStr(0));
|
auto realisationDbId = useQueryRealisedOutput.getInt(0);
|
||||||
auto signatures = tokenizeString<StringSet>(use.getStr(1));
|
auto outputPath = parseStorePath(useQueryRealisedOutput.getStr(1));
|
||||||
|
auto signatures =
|
||||||
|
tokenizeString<StringSet>(useQueryRealisedOutput.getStr(2));
|
||||||
|
|
||||||
|
std::map<DrvOutput, StorePath> dependentRealisations;
|
||||||
|
auto useRealisationRefs(
|
||||||
|
state->stmts->QueryRealisationReferences.use()
|
||||||
|
(realisationDbId));
|
||||||
|
while (useRealisationRefs.next()) {
|
||||||
|
auto depHash = useRealisationRefs.getStr(0);
|
||||||
|
auto depOutputName = useRealisationRefs.getStr(1);
|
||||||
|
auto useQueryRealisedOutput(state->stmts->QueryRealisedOutput.use()
|
||||||
|
(depHash)
|
||||||
|
(depOutputName));
|
||||||
|
assert(useQueryRealisedOutput.next());
|
||||||
|
auto outputPath = parseStorePath(useQueryRealisedOutput.getStr(1));
|
||||||
|
auto depId = DrvOutput { Hash::parseAnyPrefixed(depHash), depOutputName };
|
||||||
|
dependentRealisations.insert({depId, outputPath});
|
||||||
|
}
|
||||||
|
|
||||||
return Ret{Realisation{
|
return Ret{Realisation{
|
||||||
.id = id, .outPath = outputPath, .signatures = signatures}};
|
.id = id,
|
||||||
|
.outPath = outputPath,
|
||||||
|
.signatures = signatures,
|
||||||
|
.dependentRealisations = dependentRealisations,
|
||||||
|
}};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -254,5 +254,44 @@ StorePaths Store::topoSortPaths(const StorePathSet & paths)
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
|
const std::set<Realisation> & inputRealisations,
|
||||||
|
const StorePathSet & pathReferences)
|
||||||
|
{
|
||||||
|
std::map<DrvOutput, StorePath> res;
|
||||||
|
|
||||||
|
for (const auto & input : inputRealisations) {
|
||||||
|
if (pathReferences.count(input.outPath)) {
|
||||||
|
res.insert({input.id, input.outPath});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
|
Store & store,
|
||||||
|
const Derivation & drv,
|
||||||
|
const StorePath & outputPath)
|
||||||
|
{
|
||||||
|
std::set<Realisation> inputRealisations;
|
||||||
|
|
||||||
|
for (const auto& [inputDrv, outputNames] : drv.inputDrvs) {
|
||||||
|
auto outputHashes =
|
||||||
|
staticOutputHashes(store, store.readDerivation(inputDrv));
|
||||||
|
for (const auto& outputName : outputNames) {
|
||||||
|
auto thisRealisation = store.queryRealisation(
|
||||||
|
DrvOutput{outputHashes.at(outputName), outputName});
|
||||||
|
if (!thisRealisation)
|
||||||
|
throw Error(
|
||||||
|
"output '%s' of derivation '%s' isn’t built", outputName,
|
||||||
|
store.printStorePath(inputDrv));
|
||||||
|
inputRealisations.insert(*thisRealisation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info = store.queryPathInfo(outputPath);
|
||||||
|
|
||||||
|
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "closure.hh"
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -21,11 +22,52 @@ std::string DrvOutput::to_string() const {
|
||||||
return strHash() + "!" + outputName;
|
return strHash() + "!" + outputName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<Realisation> Realisation::closure(Store & store, const std::set<Realisation> & startOutputs)
|
||||||
|
{
|
||||||
|
std::set<Realisation> res;
|
||||||
|
Realisation::closure(store, startOutputs, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Realisation::closure(Store & store, const std::set<Realisation> & startOutputs, std::set<Realisation> & res)
|
||||||
|
{
|
||||||
|
auto getDeps = [&](const Realisation& current) -> std::set<Realisation> {
|
||||||
|
std::set<Realisation> res;
|
||||||
|
for (auto& [currentDep, _] : current.dependentRealisations) {
|
||||||
|
if (auto currentRealisation = store.queryRealisation(currentDep))
|
||||||
|
res.insert(*currentRealisation);
|
||||||
|
else
|
||||||
|
throw Error(
|
||||||
|
"Unrealised derivation '%s'", currentDep.to_string());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
computeClosure<Realisation>(
|
||||||
|
startOutputs, res,
|
||||||
|
[&](const Realisation& current,
|
||||||
|
std::function<void(std::promise<std::set<Realisation>>&)>
|
||||||
|
processEdges) {
|
||||||
|
std::promise<std::set<Realisation>> promise;
|
||||||
|
try {
|
||||||
|
auto res = getDeps(current);
|
||||||
|
promise.set_value(res);
|
||||||
|
} catch (...) {
|
||||||
|
promise.set_exception(std::current_exception());
|
||||||
|
}
|
||||||
|
return processEdges(promise);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
nlohmann::json Realisation::toJSON() const {
|
nlohmann::json Realisation::toJSON() const {
|
||||||
|
auto jsonDependentRealisations = nlohmann::json::object();
|
||||||
|
for (auto & [depId, depOutPath] : dependentRealisations)
|
||||||
|
jsonDependentRealisations.emplace(depId.to_string(), depOutPath.to_string());
|
||||||
return nlohmann::json{
|
return nlohmann::json{
|
||||||
{"id", id.to_string()},
|
{"id", id.to_string()},
|
||||||
{"outPath", outPath.to_string()},
|
{"outPath", outPath.to_string()},
|
||||||
{"signatures", signatures},
|
{"signatures", signatures},
|
||||||
|
{"dependentRealisations", jsonDependentRealisations},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +93,16 @@ Realisation Realisation::fromJSON(
|
||||||
if (auto signaturesIterator = json.find("signatures"); signaturesIterator != json.end())
|
if (auto signaturesIterator = json.find("signatures"); signaturesIterator != json.end())
|
||||||
signatures.insert(signaturesIterator->begin(), signaturesIterator->end());
|
signatures.insert(signaturesIterator->begin(), signaturesIterator->end());
|
||||||
|
|
||||||
|
std::map <DrvOutput, StorePath> dependentRealisations;
|
||||||
|
if (auto jsonDependencies = json.find("dependentRealisations"); jsonDependencies != json.end())
|
||||||
|
for (auto & [jsonDepId, jsonDepOutPath] : jsonDependencies->get<std::map<std::string, std::string>>())
|
||||||
|
dependentRealisations.insert({DrvOutput::parse(jsonDepId), StorePath(jsonDepOutPath)});
|
||||||
|
|
||||||
return Realisation{
|
return Realisation{
|
||||||
.id = DrvOutput::parse(getField("id")),
|
.id = DrvOutput::parse(getField("id")),
|
||||||
.outPath = StorePath(getField("outPath")),
|
.outPath = StorePath(getField("outPath")),
|
||||||
.signatures = signatures,
|
.signatures = signatures,
|
||||||
|
.dependentRealisations = dependentRealisations,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,14 @@ struct Realisation {
|
||||||
|
|
||||||
StringSet signatures;
|
StringSet signatures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The realisations that are required for the current one to be valid.
|
||||||
|
*
|
||||||
|
* When importing this realisation, the store will first check that all its
|
||||||
|
* dependencies exist, and map to the correct output path
|
||||||
|
*/
|
||||||
|
std::map<DrvOutput, StorePath> dependentRealisations;
|
||||||
|
|
||||||
nlohmann::json toJSON() const;
|
nlohmann::json toJSON() const;
|
||||||
static Realisation fromJSON(const nlohmann::json& json, const std::string& whence);
|
static Realisation fromJSON(const nlohmann::json& json, const std::string& whence);
|
||||||
|
|
||||||
|
@ -36,6 +44,9 @@ struct Realisation {
|
||||||
bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const;
|
bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const;
|
||||||
size_t checkSignatures(const PublicKeys & publicKeys) const;
|
size_t checkSignatures(const PublicKeys & publicKeys) const;
|
||||||
|
|
||||||
|
static std::set<Realisation> closure(Store &, const std::set<Realisation> &);
|
||||||
|
static void closure(Store &, const std::set<Realisation> &, std::set<Realisation>& res);
|
||||||
|
|
||||||
StorePath getPath() const { return outPath; }
|
StorePath getPath() const { return outPath; }
|
||||||
|
|
||||||
GENERATE_CMP(Realisation, me->id, me->outPath);
|
GENERATE_CMP(Realisation, me->id, me->outPath);
|
||||||
|
|
|
@ -780,20 +780,39 @@ std::map<StorePath, StorePath> copyPaths(ref<Store> srcStore, ref<Store> dstStor
|
||||||
RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
|
RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
|
||||||
{
|
{
|
||||||
StorePathSet storePaths;
|
StorePathSet storePaths;
|
||||||
std::set<Realisation> realisations;
|
std::set<Realisation> toplevelRealisations;
|
||||||
for (auto & path : paths) {
|
for (auto & path : paths) {
|
||||||
storePaths.insert(path.path());
|
storePaths.insert(path.path());
|
||||||
if (auto realisation = std::get_if<Realisation>(&path.raw)) {
|
if (auto realisation = std::get_if<Realisation>(&path.raw)) {
|
||||||
settings.requireExperimentalFeature("ca-derivations");
|
settings.requireExperimentalFeature("ca-derivations");
|
||||||
realisations.insert(*realisation);
|
toplevelRealisations.insert(*realisation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute);
|
auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute);
|
||||||
|
|
||||||
|
ThreadPool pool;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (auto & realisation : realisations) {
|
// Copy the realisation closure
|
||||||
dstStore->registerDrvOutput(realisation, checkSigs);
|
processGraph<Realisation>(
|
||||||
}
|
pool, Realisation::closure(*srcStore, toplevelRealisations),
|
||||||
} catch (MissingExperimentalFeature & e) {
|
[&](const Realisation& current) -> std::set<Realisation> {
|
||||||
|
std::set<Realisation> children;
|
||||||
|
for (const auto& [drvOutput, _] : current.dependentRealisations) {
|
||||||
|
auto currentChild = srcStore->queryRealisation(drvOutput);
|
||||||
|
if (!currentChild)
|
||||||
|
throw Error(
|
||||||
|
"Incomplete realisation closure: '%s' is a "
|
||||||
|
"dependency of '%s' but isn’t registered",
|
||||||
|
drvOutput.to_string(), current.id.to_string());
|
||||||
|
children.insert(*currentChild);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
},
|
||||||
|
[&](const Realisation& current) -> void {
|
||||||
|
dstStore->registerDrvOutput(current, checkSigs);
|
||||||
|
});
|
||||||
|
} catch (MissingExperimentalFeature& e) {
|
||||||
// Don't fail if the remote doesn't support CA derivations is it might
|
// Don't fail if the remote doesn't support CA derivations is it might
|
||||||
// not be within our control to change that, and we might still want
|
// not be within our control to change that, and we might still want
|
||||||
// to at least copy the output paths.
|
// to at least copy the output paths.
|
||||||
|
|
|
@ -864,4 +864,9 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri)
|
||||||
|
|
||||||
std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv);
|
std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv);
|
||||||
|
|
||||||
|
std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
|
Store & store,
|
||||||
|
const Derivation & drv,
|
||||||
|
const StorePath & outputPath);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue