Allow signing packages manually after the build

Simply adding `--sign` to the `makepkg` flags doesn't work because it would
require setting up GPG within the chroot environment (of `makechrootpkg`).

When debugging it is anyways annoying that `makepkg` sends the `gpg` output
to `/dev/null`. This way the logs are preserved.
This commit is contained in:
Martchus 2021-03-14 21:53:51 +01:00
parent 45bf4fa234
commit 1c75e8f957
8 changed files with 166 additions and 40 deletions

View File

@ -210,6 +210,11 @@ BuildActionMetaInfo::BuildActionMetaInfo()
.desc = "The test files directory to use (instead of the globally configured one)",
.param = "test-files-dir",
},
BuildActionSettingMetaInfo{
.name = "GPG key to sign packages",
.desc = "The GPG key to sign packages (instead of the globally configured one)",
.param = "gpg-key",
},
},
.directory = true,
.sourceDb = false,

View File

@ -84,7 +84,7 @@ enum class CleanRepositoryFlags : BuildActionFlagType {
};
enum class CheckForProblemsSettings : std::size_t { IgnoreDeps, IgnoreLibDeps };
enum class PrepareBuildSettings : std::size_t { PKGBUILDsDirs };
enum class ConductBuildSettings : std::size_t { ChrootDir, ChrootDefaultUser, CCacheDir, PackageCacheDir, TestFilesDir };
enum class ConductBuildSettings : std::size_t { ChrootDir, ChrootDefaultUser, CCacheDir, PackageCacheDir, TestFilesDir, GpgKey };
enum class CustomCommandSettings : std::size_t { Command, SharedLocks, ExclusiveLocks };
struct LIBREPOMGR_EXPORT BuildActionFlagMetaInfo : public ReflectiveRapidJSON::JsonSerializable<BuildActionFlagMetaInfo> {

View File

@ -532,8 +532,8 @@ private:
};
struct BinaryPackageInfo {
const std::string *const name;
const std::string *const fileName;
const std::string name;
const std::string fileName;
std::filesystem::path path;
const bool isAny = false;
bool artefactAlreadyPresent = false;
@ -554,6 +554,12 @@ struct LIBREPOMGR_EXPORT ConductBuild
void run();
private:
struct BuildResult {
std::vector<std::string> binaryPackageNames;
const std::string *repoPath = nullptr, *dbFilePath = nullptr;
bool needsStaging = false;
};
void makeMakepkgConfigFile(const std::filesystem::path &makepkgConfigPath);
void makePacmanConfigFile(
const std::filesystem::path &pacmanConfigPath, const std::vector<std::pair<std::string, std::multimap<std::string, std::string>>> &dbConfig);
@ -571,7 +577,14 @@ private:
const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const std::string &packageName, bool hasFailuresInPreviousBatches);
void addPackageToRepo(
const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const std::string &packageName, PackageBuildProgress &packageProgress);
void invokeGpg(const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const std::string &packageName,
PackageBuildProgress &packageProgress, std::vector<BinaryPackageInfo> &&binaryPackages, BuildResult &&buildResult);
void invokeRepoAdd(const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const std::string &packageName,
PackageBuildProgress &packageProgress, BuildResult &&buildResult);
void checkDownloadErrorsAndMakePackages(BatchProcessingSession::ContainerType &&failedPackages);
void checkGpgErrorsAndContinueAddingPackagesToRepo(const BatchProcessingSession::SharedPointerType &makepkgchrootSession,
const std::string &packageName, PackageBuildProgress &packageProgress, BuildResult &&buildResult,
MultiSession<std::string>::ContainerType &&failedPackages);
void handleMakechrootpkgErrorsAndAddPackageToRepo(const BatchProcessingSession::SharedPointerType &makepkgchrootSession,
const std::string &packageName, PackageBuildProgress &packageProgress, boost::process::child &&child, ProcessResult &&result);
void handleRepoAddErrorsAndMakeNextPackage(const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const std::string &packageName,
@ -593,11 +606,13 @@ private:
std::string m_globalCcacheDir;
std::string m_globalPackageCacheDir;
std::string m_globalTestFilesDir;
std::string m_gpgKey;
std::string m_chrootRootUser;
boost::filesystem::path m_makePkgPath;
boost::filesystem::path m_makeChrootPkgPath;
boost::filesystem::path m_updatePkgSumsPath;
boost::filesystem::path m_repoAddPath;
boost::filesystem::path m_gpgPath;
std::filesystem::path m_makepkgConfigPath;
std::filesystem::path m_pacmanConfigPath;
std::filesystem::path m_pacmanStagingConfigPath;

View File

@ -224,18 +224,21 @@ void ConductBuild::run()
const auto ccacheDirSetting = typeInfo.settings[static_cast<std::size_t>(ConductBuildSettings::CCacheDir)].param;
const auto pkgCacheDirSetting = typeInfo.settings[static_cast<std::size_t>(ConductBuildSettings::PackageCacheDir)].param;
const auto testFilesDirSetting = typeInfo.settings[static_cast<std::size_t>(ConductBuildSettings::TestFilesDir)].param;
const auto gpgKeySetting = typeInfo.settings[static_cast<std::size_t>(ConductBuildSettings::GpgKey)].param;
metaInfoLock.unlock();
const auto &chrootDir = findSetting(chrootDirSetting);
const auto &chrootDefaultUser = findSetting(chrootDefaultUserSetting);
m_globalCcacheDir = findSetting(ccacheDirSetting);
m_globalPackageCacheDir = findSetting(pkgCacheDirSetting);
m_globalTestFilesDir = findSetting(testFilesDirSetting);
m_gpgKey = findSetting(gpgKeySetting);
auto setupReadLock = m_setup.lockToRead();
m_workingDirectory = determineWorkingDirectory(buildDataWorkingDirectory);
m_makePkgPath = findExecutable(m_setup.building.makePkgPath);
m_makeChrootPkgPath = findExecutable(m_setup.building.makeChrootPkgPath);
m_updatePkgSumsPath = findExecutable(m_setup.building.updatePkgSumsPath);
m_repoAddPath = findExecutable(m_setup.building.repoAddPath);
m_gpgPath = findExecutable(m_setup.building.gpgPath);
setupReadLock.unlock();
// check executables
@ -330,6 +333,11 @@ void ConductBuild::run()
m_globalTestFilesDir = m_setup.building.testFilesDir;
}
m_chrootRootUser = m_setup.building.chrootRootUser;
if (m_gpgKey == "none") {
m_gpgKey.clear();
} else if (m_gpgKey.empty()) {
m_gpgKey = m_setup.building.defaultGpgKey;
}
setupReadLock.unlock();
// fill ommitted build progress configuration with defaults from global config
@ -967,10 +975,10 @@ void ConductBuild::addPackageToRepo(
{
// make arrays to store binary package names
auto binaryPackages = std::vector<BinaryPackageInfo>{};
auto binaryPackageNames = std::vector<std::string>{};
static const auto anyArch = std::vector<std::string>{ "any" };
// determine name of source package to be copied
auto buildResult = BuildResult{};
auto readLock = lockToRead();
const auto &buildData = m_buildPreparation.buildData[packageName];
const auto &firstPackage = buildData.packages.front();
@ -978,16 +986,16 @@ void ConductBuild::addPackageToRepo(
// determine names of binary packages to be copied
binaryPackages.reserve(buildData.packages.size());
binaryPackageNames.reserve(buildData.packages.size());
buildResult.binaryPackageNames.reserve(buildData.packages.size());
for (const auto &package : buildData.packages) {
const auto isAny = package->sourceInfo->archs
== anyArch; // FIXME: Shouldn't there still be a package->archs if e.g. base is x86_64 but a split package any?
const auto &arch = isAny ? "any" : m_buildPreparation.targetArch;
const auto &packageFileName = binaryPackageNames.emplace_back(
const auto &packageFileName = buildResult.binaryPackageNames.emplace_back(
package->name % '-' % (packageProgress.updatedVersion.empty() ? package->version : packageProgress.updatedVersion) % '-' % arch
+ m_binaryPackageExtension);
binaryPackages.emplace_back(
BinaryPackageInfo{ &package->name, &packageFileName, packageProgress.buildDirectory % '/' + packageFileName, isAny, false });
BinaryPackageInfo{ package->name, packageFileName, packageProgress.buildDirectory % '/' + packageFileName, isAny, false });
}
// check whether all packages exists
@ -998,7 +1006,7 @@ void ConductBuild::addPackageToRepo(
}
for (auto &binaryPackage : binaryPackages) {
if (!std::filesystem::is_regular_file(binaryPackage.path)) {
missingPackages.emplace_back(std::move(*binaryPackage.fileName));
missingPackages.emplace_back(std::move(binaryPackage.fileName));
}
}
} catch (const std::filesystem::filesystem_error &e) {
@ -1027,12 +1035,12 @@ void ConductBuild::addPackageToRepo(
// check whether staging is needed
// note: Calling checkWhetherStagingIsNeededAndPopulateRebuildList() in the condition first do prevent short-circuit evaluation. We always
// want to do the check in order to populate the rebuild list - even if staging is already enabled anyways.
auto needsStaging = makepkgchrootSession->isStagingEnabled();
buildResult.needsStaging = makepkgchrootSession->isStagingEnabled();
try {
if (packageProgress.stagingNeeded != PackageStagingNeeded::No) {
packageProgress.stagingNeeded = checkWhetherStagingIsNeededAndPopulateRebuildList(packageName, buildData, binaryPackages);
if (packageProgress.stagingNeeded == PackageStagingNeeded::Yes) {
needsStaging = true;
buildResult.needsStaging = true;
makepkgchrootSession->enableStagingInNextBatch();
}
}
@ -1059,7 +1067,7 @@ void ConductBuild::addPackageToRepo(
}
bool binaryPackageArtefactsAlreadyPresent = true;
for (auto &binaryPackage : binaryPackages) {
binaryPackage.path = packageProgress.buildDirectory % '/' + *binaryPackage.fileName;
binaryPackage.path = packageProgress.buildDirectory % '/' + binaryPackage.fileName;
for (const auto &artefact : m_buildAction->artefacts) {
if (artefact == binaryPackage.path) {
binaryPackage.artefactAlreadyPresent = true;
@ -1084,41 +1092,28 @@ void ConductBuild::addPackageToRepo(
}
// copy source and binary packages
const auto &repoPath = needsStaging ? m_buildProgress.stagingRepoPath : m_buildProgress.targetRepoPath;
const auto &dbFilePath = needsStaging ? m_buildProgress.stagingDbFilePath : m_buildProgress.targetDbFilePath;
buildResult.repoPath = buildResult.needsStaging ? &m_buildProgress.stagingRepoPath : &m_buildProgress.targetRepoPath;
buildResult.dbFilePath = buildResult.needsStaging ? &m_buildProgress.stagingDbFilePath : &m_buildProgress.targetDbFilePath;
try {
const auto sourceRepoPath = std::filesystem::path(repoPath + "/../src/");
auto anyRepoPath = std::filesystem::path();
const auto sourceRepoPath = std::filesystem::path(argsToString(buildResult.repoPath, "/../src/"));
std::filesystem::create_directories(sourceRepoPath);
std::filesystem::copy(sourcePackagePath, sourceRepoPath / sourcePackageName, std::filesystem::copy_options::update_existing);
for (const auto &binaryPackage : binaryPackages) {
const auto signaturePath = std::filesystem::path(argsToString(binaryPackage.path, ".sig"));
if (!binaryPackage.isAny) {
std::filesystem::copy(binaryPackage.path, repoPath % '/' + *binaryPackage.fileName, std::filesystem::copy_options::update_existing);
if (std::filesystem::exists(signaturePath)) {
std::filesystem::copy(
signaturePath, repoPath % '/' % *binaryPackage.fileName + ".sig", std::filesystem::copy_options::update_existing);
}
std::filesystem::copy(
binaryPackage.path, *buildResult.repoPath % '/' + binaryPackage.fileName, std::filesystem::copy_options::update_existing);
continue;
}
if (anyRepoPath.empty()) {
std::filesystem::create_directories(anyRepoPath = repoPath + "/../any");
std::filesystem::create_directories(anyRepoPath = argsToString(buildResult.repoPath, "/../any"));
}
std::filesystem::copy(binaryPackage.path, anyRepoPath / *binaryPackage.fileName, std::filesystem::copy_options::update_existing);
const auto symlink = std::filesystem::path(repoPath % '/' + *binaryPackage.fileName);
std::filesystem::copy(binaryPackage.path, anyRepoPath / binaryPackage.fileName, std::filesystem::copy_options::update_existing);
const auto symlink = std::filesystem::path(*buildResult.repoPath % '/' + binaryPackage.fileName);
if (std::filesystem::exists(symlink) && !std::filesystem::is_symlink(symlink)) {
std::filesystem::remove(symlink);
}
std::filesystem::create_symlink("../any/" + *binaryPackage.fileName, symlink);
if (std::filesystem::exists(signaturePath)) {
std::filesystem::copy(
signaturePath, argsToString(anyRepoPath, '/', *binaryPackage.fileName, ".sig"), std::filesystem::copy_options::update_existing);
const auto symlink = std::filesystem::path(argsToString(repoPath, '/', *binaryPackage.fileName, ".sig"));
if (std::filesystem::exists(symlink) && !std::filesystem::is_symlink(symlink)) {
std::filesystem::remove(symlink);
}
std::filesystem::create_symlink("../any/" % *binaryPackage.fileName + ".sig", symlink);
}
std::filesystem::create_symlink("../any/" + binaryPackage.fileName, symlink);
}
} catch (const std::filesystem::filesystem_error &e) {
auto writeLock = lockToWrite(readLock);
@ -1130,16 +1125,88 @@ void ConductBuild::addPackageToRepo(
enqueueMakechrootpkg(makepkgchrootSession, 1);
return;
}
readLock.unlock();
// add completed package to repository
if (!m_gpgKey.empty()) {
// sign package before adding to repository if GPG key specified
invokeGpg(makepkgchrootSession, packageName, packageProgress, std::move(binaryPackages), std::move(buildResult));
} else {
invokeRepoAdd(makepkgchrootSession, packageName, packageProgress, std::move(buildResult));
}
}
void ConductBuild::invokeGpg(const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const string &packageName,
PackageBuildProgress &packageProgress, std::vector<BinaryPackageInfo> &&binaryPackages, BuildResult &&buildResult)
{
const auto *const repoPath = buildResult.repoPath;
auto signingSession = MultiSession<std::string>::create(m_setup.building.ioContext,
[this, makepkgchrootSession, &packageName, &packageProgress, buildResult = std::move(buildResult)](
MultiSession<std::string>::ContainerType &&failedPackages) mutable {
checkGpgErrorsAndContinueAddingPackagesToRepo(
makepkgchrootSession, packageName, packageProgress, std::move(buildResult), std::move(failedPackages));
});
for (const auto &binaryPackage : binaryPackages) {
auto processSession
= m_buildAction->makeBuildProcess("gpg for " + binaryPackage.name, packageProgress.buildDirectory % "/gpg-" % binaryPackage.name + ".log",
[this, signingSession, &packageProgress, repoPath, isAny = binaryPackage.isAny, binaryPackageName = binaryPackage.fileName](
boost::process::child &&child, ProcessResult &&result) mutable {
if (result.errorCode) {
// check for invocation error
m_buildAction->log()(
Phrases::ErrorMessage, "Unable to invoke gpg for ", binaryPackageName, ": ", result.errorCode.message(), '\n');
} else if (child.exit_code() != 0) {
// check for bad exit code
m_buildAction->log()(Phrases::ErrorMessage, "gpg invocation for ", binaryPackageName,
" exited with non-zero exit code: ", child.exit_code(), '\n');
} else {
// move signature to repository
try {
const auto buildDirSignaturePath
= std::filesystem::path(argsToString(packageProgress.buildDirectory, '/', binaryPackageName, ".sig"));
if (!std::filesystem::exists(buildDirSignaturePath)) {
m_buildAction->log()(Phrases::ErrorMessage, "Signature of \"", binaryPackageName,
"\" could not be created: ", buildDirSignaturePath, " does not exist after invoking gpg\n");
} else if (!isAny) {
std::filesystem::copy(buildDirSignaturePath, *repoPath % '/' % binaryPackageName + ".sig",
std::filesystem::copy_options::update_existing);
return;
} else {
std::filesystem::copy(buildDirSignaturePath, argsToString(repoPath, "/../any/", binaryPackageName, ".sig"),
std::filesystem::copy_options::update_existing);
const auto symlink = std::filesystem::path(argsToString(repoPath, '/', binaryPackageName, ".sig"));
if (std::filesystem::exists(symlink) && !std::filesystem::is_symlink(symlink)) {
std::filesystem::remove(symlink);
}
std::filesystem::create_symlink("../any/" % binaryPackageName + ".sig", symlink);
return;
}
} catch (const std::filesystem::filesystem_error &e) {
m_buildAction->log()(
Phrases::ErrorMessage, "Unable to copy signature of \"", binaryPackageName, "\" to repository: ", e.what(), '\n');
}
}
// consider the package failed
signingSession->addResponse(std::move(binaryPackageName));
});
processSession->launch(boost::process::start_dir(packageProgress.buildDirectory), m_gpgPath, "--detach-sign", "--yes", "--use-agent",
"--no-armor", "-u", m_gpgKey, binaryPackage.fileName);
m_buildAction->log()(Phrases::InfoMessage, "Signing ", binaryPackage.fileName, '\n');
}
}
void ConductBuild::invokeRepoAdd(const BatchProcessingSession::SharedPointerType &makepkgchrootSession, const string &packageName,
PackageBuildProgress &packageProgress, BuildResult &&buildResult)
{
auto processSession = m_buildAction->makeBuildProcess("repo-add for " + packageName, packageProgress.buildDirectory + "/repo-add.log",
std::bind(&ConductBuild::handleRepoAddErrorsAndMakeNextPackage, this, makepkgchrootSession, std::ref(packageName), std::ref(packageProgress),
std::placeholders::_1, std::placeholders::_2));
processSession->locks().emplace_back(m_setup.locks.acquireToWrite(m_buildAction->log(),
ServiceSetup::Locks::forDatabase(needsStaging ? m_buildPreparation.stagingDb : m_buildPreparation.targetDb, m_buildPreparation.targetArch)));
processSession->launch(boost::process::start_dir(repoPath), m_repoAddPath, dbFilePath, binaryPackageNames);
m_buildAction->log()(Phrases::InfoMessage, "Adding ", packageName, " to repo\n", ps(Phrases::SubMessage), "repo path: ", repoPath, '\n',
ps(Phrases::SubMessage), "db path: ", dbFilePath, '\n', ps(Phrases::SubMessage), "package(s): ", joinStrings(binaryPackageNames), '\n');
ServiceSetup::Locks::forDatabase(
buildResult.needsStaging ? m_buildPreparation.stagingDb : m_buildPreparation.targetDb, m_buildPreparation.targetArch)));
processSession->launch(boost::process::start_dir(*buildResult.repoPath), m_repoAddPath, *buildResult.dbFilePath, buildResult.binaryPackageNames);
m_buildAction->log()(Phrases::InfoMessage, "Adding ", packageName, " to repo\n", ps(Phrases::SubMessage), "repo path: ", buildResult.repoPath,
'\n', ps(Phrases::SubMessage), "db path: ", buildResult.dbFilePath, '\n', ps(Phrases::SubMessage),
"package(s): ", joinStrings(buildResult.binaryPackageNames), '\n');
}
void ConductBuild::checkDownloadErrorsAndMakePackages(BatchProcessingSession::ContainerType &&failedPackages)
@ -1172,6 +1239,24 @@ void ConductBuild::checkDownloadErrorsAndMakePackages(BatchProcessingSession::Co
maxParallelInvocations);
}
void ConductBuild::checkGpgErrorsAndContinueAddingPackagesToRepo(const BatchProcessingSession::SharedPointerType &makepkgchrootSession,
const std::string &packageName, PackageBuildProgress &packageProgress, BuildResult &&buildResult,
MultiSession<std::string>::ContainerType &&failedPackages)
{
if (!failedPackages.empty()) {
auto lock = lockToWrite();
packageProgress.error = argsToString("failed to sign packages: ", joinStrings(failedPackages, ", "));
dumpBuildProgress();
lock.unlock();
makepkgchrootSession->addResponse(std::string(packageName));
enqueueMakechrootpkg(makepkgchrootSession, 1);
return;
}
if (!reportAbortedIfAborted()) {
invokeRepoAdd(makepkgchrootSession, packageName, packageProgress, std::move(buildResult));
}
}
static void assignNewChrootUser(std::string &&newChrootUser, PackageBuildProgress &packageProgress)
{
if (newChrootUser.empty()) {
@ -1262,7 +1347,7 @@ void ConductBuild::handleRepoAddErrorsAndMakeNextPackage(const BatchProcessingSe
lock.unlock();
m_buildAction->log()(
Phrases::ErrorMessage, "repo-add invocation for ", packageName, " exited with non-zero exit code: ", child.exit_code(), '\n');
makepkgchrootSession->addResponse(string(packageName));
makepkgchrootSession->addResponse(std::string(packageName));
} else {
packageProgress.addedToRepo = true;
dumpBuildProgress();
@ -1368,7 +1453,7 @@ PackageStagingNeeded ConductBuild::checkWhetherStagingIsNeededAndPopulateRebuild
}
const auto &packages = db->packages;
for (const auto &builtPackage : builtPackages) {
if (const auto i = packages.find(*builtPackage.name); i != packages.end()) {
if (const auto i = packages.find(builtPackage.name); i != packages.end()) {
LibPkg::Package::exportProvides(i->second, removedProvides, removedLibProvides);
if (affectedDbName.empty()) {
affectedDbName = dbName;

View File

@ -107,10 +107,12 @@ void ServiceSetup::BuildSetup::applyConfig(const std::multimap<std::string, std:
convertValue(multimap, "updpkgsums_path", updatePkgSumsPath);
convertValue(multimap, "repo_add_path", repoAddPath);
convertValue(multimap, "repo_remove_path", repoRemovePath);
convertValue(multimap, "gpg_path", gpgPath);
convertValue(multimap, "ccache_dir", ccacheDir);
convertValue(multimap, "chroot_dir", chrootDir);
convertValue(multimap, "chroot_root_user", chrootRootUser);
convertValue(multimap, "chroot_default_user", chrootDefaultUser);
convertValue(multimap, "default_gpg_key", defaultGpgKey);
convertValue(multimap, "pacman_config_file_path", pacmanConfigFilePath);
convertValue(multimap, "makepkg_config_file_path", makepkgConfigFilePath);
convertValue(multimap, "makechrootpkg_flags", makechrootpkgFlags);

View File

@ -89,10 +89,12 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable {
std::string updatePkgSumsPath = "updpkgsums";
std::string repoAddPath = "repo-add";
std::string repoRemovePath = "repo-remove";
std::string gpgPath = "gpg";
std::string ccacheDir;
std::string chrootDir;
std::string chrootRootUser = "root";
std::string chrootDefaultUser = "buildservice";
std::string defaultGpgKey;
std::string pacmanConfigFilePath; // FIXME: not useful after all?; using config-$arch directory within chrootDir instead
std::string makepkgConfigFilePath; // FIXME: not useful after all?; using config-$arch directory within chrootDir instead
std::vector<std::string> makechrootpkgFlags;

View File

@ -0,0 +1,4 @@
#!/bin/bash
echo "fake gpg: $@"
echo "fake signature with GPG key ${@: -2}" > "${@: -1}.sig"
exit 0

View File

@ -106,6 +106,8 @@ void BuildActionsTests::loadBasicTestSetup()
m_setup.building.makeChrootPkgPath = std::filesystem::absolute(testFilePath("scripts/fake_makechrootpkg.sh"));
m_setup.building.updatePkgSumsPath = std::filesystem::absolute(testFilePath("scripts/fake_updatepkgsums.sh"));
m_setup.building.repoAddPath = std::filesystem::absolute(testFilePath("scripts/fake_repo_add.sh"));
m_setup.building.gpgPath = std::filesystem::absolute(testFilePath("scripts/fake_gpg.sh"));
m_setup.building.defaultGpgKey = "1234567890";
m_setup.configFilePath = std::filesystem::absolute(testFilePath("test-config/server.conf"));
std::filesystem::remove_all(m_setup.workingDirectory);
@ -546,6 +548,13 @@ void BuildActionsTests::testConductingBuild()
"no staging needed: package added to repo (1)", std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst"));
CPPUNIT_ASSERT_MESSAGE("no staging needed: package added to repo (2)",
std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst"));
CPPUNIT_ASSERT_MESSAGE("no staging needed: signature added to repo (0)",
std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst.sig"));
CPPUNIT_ASSERT_MESSAGE("no staging needed: signature added to repo (1)",
std::filesystem::is_regular_file("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig"));
CPPUNIT_ASSERT_EQUAL_MESSAGE("no staging needed: signature looks as expected",
"fake signature with GPG key 1234567890 boost-libs-1.73.0-1-x86_64.pkg.tar.zst\n"s,
readFile("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig"));
// add packages needing a rebuild to trigger auto-staging
m_setup.config.loadAllPackages(false);
@ -594,4 +603,8 @@ void BuildActionsTests::testConductingBuild()
std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst"));
CPPUNIT_ASSERT_MESSAGE("staging needed: package added to repo (2)",
std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst"));
CPPUNIT_ASSERT_MESSAGE("staging needed: signature added to repo (0)",
std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-1.73.0-1-x86_64.pkg.tar.zst.sig"));
CPPUNIT_ASSERT_MESSAGE("staging needed: signature added to repo (1)",
std::filesystem::is_regular_file("repos/boost-staging/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig"));
}