2021-01-25 00:24:31 +01:00
|
|
|
#include "./buildactionprivate.h"
|
|
|
|
#include "./subprocess.h"
|
|
|
|
|
|
|
|
#include "../helper.h"
|
|
|
|
#include "../json.h"
|
|
|
|
#include "../serversetup.h"
|
|
|
|
|
|
|
|
#include "../webclient/aur.h"
|
|
|
|
|
|
|
|
#include "../webapi/params.h"
|
|
|
|
|
|
|
|
#include "../../libpkg/parser/database.h"
|
|
|
|
#include "../../libpkg/parser/utils.h"
|
|
|
|
|
|
|
|
#include <c++utilities/conversion/stringbuilder.h>
|
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
|
|
#include <c++utilities/io/ansiescapecodes.h>
|
|
|
|
#include <c++utilities/io/misc.h>
|
|
|
|
|
|
|
|
#include <boost/asio/read.hpp>
|
|
|
|
|
|
|
|
#include <boost/process/search_path.hpp>
|
|
|
|
#include <boost/process/start_dir.hpp>
|
|
|
|
|
|
|
|
#include <rapidjson/filewritestream.h>
|
|
|
|
#include <rapidjson/prettywriter.h>
|
|
|
|
|
|
|
|
#include <filesystem>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace CppUtilities;
|
|
|
|
using namespace CppUtilities::EscapeCodes;
|
|
|
|
|
|
|
|
namespace LibRepoMgr {
|
|
|
|
|
2021-05-16 15:45:53 +02:00
|
|
|
bool PackageBuildProgress::hasBeenAnyProgressMade() const
|
|
|
|
{
|
|
|
|
return checksumsUpdated || hasSources || !finished.isNull() || addedToRepo || stagingNeeded != PackageStagingNeeded::Undetermined;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PackageBuildProgress::resetProgress()
|
|
|
|
{
|
|
|
|
checksumsUpdated = false;
|
|
|
|
hasSources = false;
|
|
|
|
finished = DateTime();
|
|
|
|
addedToRepo = false;
|
|
|
|
stagingNeeded = PackageStagingNeeded::Undetermined;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PackageBuildProgress::resetChrootSettings()
|
|
|
|
{
|
|
|
|
chrootDirectory.clear();
|
|
|
|
chrootUser.clear();
|
|
|
|
skipChrootUpgrade = skipChrootCleanup = keepPreviousSourceTree = false;
|
|
|
|
}
|
|
|
|
|
2021-01-25 00:24:31 +01:00
|
|
|
PrepareBuild::PrepareBuild(ServiceSetup &setup, const std::shared_ptr<BuildAction> &buildAction)
|
|
|
|
: InternalBuildAction(setup, buildAction)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::run()
|
|
|
|
{
|
|
|
|
// check whether a directory has been specified
|
|
|
|
if (m_buildAction->directory.empty()) {
|
|
|
|
reportError("Unable to create working directory: no directory name specified");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check flags and settings
|
|
|
|
const auto flags = static_cast<PrepareBuildFlags>(m_buildAction->flags);
|
|
|
|
m_forceBumpPackageVersion = flags & PrepareBuildFlags::ForceBumpPkgRel;
|
|
|
|
m_cleanSourceDirectory = flags & PrepareBuildFlags::CleanSrcDir;
|
|
|
|
m_keepOrder = flags & PrepareBuildFlags::KeepOrder;
|
|
|
|
m_keepPkgRelAndEpoch = flags & PrepareBuildFlags::KeepPkgRelAndEpoch;
|
2021-05-16 15:45:53 +02:00
|
|
|
m_resetChrootSettings = flags & PrepareBuildFlags::ResetChrootSettings;
|
2021-01-25 00:24:31 +01:00
|
|
|
if (m_forceBumpPackageVersion && m_keepPkgRelAndEpoch) {
|
|
|
|
reportError("Can not force-bump pkgrel and keeping it at the same time.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto &metaInfo = m_setup.building.metaInfo;
|
|
|
|
auto metaInfoLock = metaInfo.lockToRead();
|
|
|
|
const auto &typeInfo = metaInfo.typeInfoForId(BuildActionType::PrepareBuild);
|
|
|
|
const auto pkgbuildsDirsSetting = std::string(typeInfo.settings[static_cast<std::size_t>(PrepareBuildSettings::PKGBUILDsDirs)].param);
|
|
|
|
metaInfoLock.unlock();
|
|
|
|
if (const auto i = m_buildAction->settings.find(pkgbuildsDirsSetting); i != m_buildAction->settings.end()) {
|
|
|
|
m_pkgbuildsDirs = splitString<std::vector<std::string>>(i->second, ":", EmptyPartsTreat::Omit);
|
|
|
|
}
|
|
|
|
|
|
|
|
// read values from global config
|
|
|
|
auto setupReadLock = m_setup.lockToRead();
|
|
|
|
m_makePkgPath = findExecutable(m_setup.building.makePkgPath);
|
|
|
|
copySecondVectorIntoFirstVector(m_pkgbuildsDirs, m_setup.building.pkgbuildsDirs);
|
|
|
|
m_ignoreLocalPkgbuildsRegex = m_setup.building.ignoreLocalPkgbuildsRegex;
|
|
|
|
m_workingDirectory = determineWorkingDirectory(buildDataWorkingDirectory);
|
|
|
|
setupReadLock.unlock();
|
|
|
|
|
|
|
|
// check executables
|
|
|
|
if (!checkExecutable(m_makePkgPath)) {
|
|
|
|
reportError("Unable to find makepkg executable \"" % m_setup.building.makePkgPath + "\" in PATH.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// init build action
|
|
|
|
auto configReadLock
|
|
|
|
= init(BuildActionAccess::ReadConfig, RequiredDatabases::MaybeSource | RequiredDatabases::OneDestination, RequiredParameters::Packages);
|
|
|
|
if (std::holds_alternative<std::monostate>(configReadLock)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find dependencies of the destination repository
|
|
|
|
auto *const destinationDb = (*m_destinationDbs.begin());
|
|
|
|
auto databaseDependencyOrderRes = m_setup.config.computeDatabaseDependencyOrder(*destinationDb);
|
|
|
|
if (std::holds_alternative<std::string>(databaseDependencyOrderRes)) {
|
|
|
|
configReadLock = std::monostate{};
|
|
|
|
reportError("Unable to find the destination DB's dependencies: " + get<string>(databaseDependencyOrderRes));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto &databaseDependencyOrder = get<vector<LibPkg::Database *>>(databaseDependencyOrderRes);
|
|
|
|
for (const auto *destinationDbOrDependency : databaseDependencyOrder) {
|
|
|
|
m_requiredDbs.emplace(destinationDbOrDependency->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// find dependencies of the staging repository
|
|
|
|
auto *stagingDb = m_setup.config.findDatabase(destinationDb->name + "-staging", destinationDb->arch);
|
|
|
|
auto stagingDbOrder = std::variant<std::vector<LibPkg::Database *>, std::string>();
|
|
|
|
auto hasStagingDbOrder = false;
|
|
|
|
if (!stagingDb && endsWith(destinationDb->name, "-testing")) {
|
|
|
|
stagingDb = m_setup.config.findDatabase(
|
|
|
|
argsToString(std::string_view(destinationDb->name.data(), destinationDb->name.size() - 8), "-staging"), destinationDb->arch);
|
|
|
|
}
|
|
|
|
if (!stagingDb && endsWith(destinationDb->name, "-staging")) {
|
|
|
|
// use the destination DB as staging DB if it ends with "-staging"; we can assume it was intended to use it and the auto-staging
|
|
|
|
// was just passed as usual
|
|
|
|
stagingDb = destinationDb;
|
|
|
|
stagingDbOrder = databaseDependencyOrder;
|
|
|
|
hasStagingDbOrder = true;
|
|
|
|
}
|
|
|
|
if (stagingDb) {
|
|
|
|
if (!hasStagingDbOrder) {
|
|
|
|
stagingDbOrder = m_setup.config.computeDatabaseDependencyOrder(*stagingDb);
|
|
|
|
}
|
|
|
|
if (std::holds_alternative<std::string>(stagingDbOrder)) {
|
|
|
|
m_warnings.emplace_back("Unable to find the staging DB's dependencies: " + get<string>(stagingDbOrder));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_warnings.emplace_back("Unable to find staging DB for \"" + destinationDb->name + "\". Auto-staging will not work.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// set target and staging info
|
|
|
|
m_targetDbName = destinationDb->name;
|
|
|
|
m_targetArch = destinationDb->arch;
|
|
|
|
if (stagingDb) {
|
|
|
|
m_stagingDbName = stagingDb->name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make set of databases considered as given from the specified source databases
|
|
|
|
// notes: 1. If *no* source databases are specified the destination database and all databases it is based on are considered as "given" and
|
|
|
|
// therefore not included in the build unless their package names are explicitly specified. All direct and transitive dependencies
|
|
|
|
// of the destination repository and the destination repository itself are enabled within the build root.
|
|
|
|
// 2. If source databases are specified only packages contained by these databases will be considered as "given". Any other packages
|
|
|
|
// will be included in the build. Dependencies of the destination repository and the destination repository itself are only enabled within
|
|
|
|
// the build root when also listed as source repositories.
|
|
|
|
// 3. All specified source repositories should be equal to the destination repository or a direct or transitive dependency of the destination
|
|
|
|
// repository. If that is not the case the built packages might depend on packages which are not available at install time. Hence a warning
|
|
|
|
// is emitted in that case.
|
|
|
|
std::vector<std::string> missingBaseDbs;
|
|
|
|
for (auto *const sourceDb : m_sourceDbs) {
|
|
|
|
const auto &[sourceDbName, added] = m_baseDbs.emplace(sourceDb->name);
|
|
|
|
if (!added) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (m_requiredDbs.find(*sourceDbName) == m_requiredDbs.end()) {
|
|
|
|
missingBaseDbs.emplace_back(*sourceDbName);
|
|
|
|
databaseDependencyOrder.emplace_back(sourceDb); // appending the "odd" source database (see note 3. above) should be sufficient
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!missingBaseDbs.empty()) {
|
|
|
|
m_warnings.emplace_back(
|
|
|
|
"The following source DBs are not the destination DB or a (transitive) dependency of it: " + joinStrings(missingBaseDbs, ", "));
|
|
|
|
}
|
|
|
|
|
|
|
|
// compose the list of repositories to enable within the build root (for staging)
|
|
|
|
populateDbConfig(databaseDependencyOrder, false);
|
|
|
|
if (std::holds_alternative<std::vector<LibPkg::Database *>>(stagingDbOrder)) {
|
|
|
|
populateDbConfig(std::get<std::vector<LibPkg::Database *>>(stagingDbOrder), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to find denoted packages to determine the actual package base and the existing version
|
|
|
|
// -> We might get something like "my-repo/foo" and "my-repo/bar" where "foo" and "bar" are only split
|
|
|
|
// packages from the base package "foobar" when blindly trying to "rebuild" packages from a binary
|
|
|
|
// repository. Luckily database files store the corresponding base packages.
|
|
|
|
// -> That trick doesn't work for new packages of course.
|
|
|
|
// FIXME: Maybe the AUR RPC provides useful information (which would at least help with AUR packages).
|
|
|
|
// -> The existing version is useful to know when we need to bump pkgrel or even epoch.
|
|
|
|
size_t currentIndex = 0;
|
|
|
|
for (const auto &packageName : m_buildAction->packageNames) {
|
2021-07-03 19:59:57 +02:00
|
|
|
// pick some package matching the specified packageName which has a source info; preferring packages
|
2021-01-25 00:24:31 +01:00
|
|
|
// from the destination db
|
|
|
|
auto existingPackages = m_setup.config.findPackages(packageName);
|
|
|
|
auto foundSourcePackage = false;
|
|
|
|
auto packageBuildData = PackageBuildData();
|
2021-03-22 15:08:41 +01:00
|
|
|
for (auto &existingPackage : existingPackages) {
|
2021-01-25 00:24:31 +01:00
|
|
|
// skip if package not relevant
|
2021-03-22 15:08:41 +01:00
|
|
|
if (!isExistingPackageRelevant(packageName, existingPackage, packageBuildData, *destinationDb)) {
|
2021-01-25 00:24:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// skip if package has no source info
|
2021-03-22 15:08:41 +01:00
|
|
|
const auto &sourceInfo = existingPackage.pkg->sourceInfo;
|
2021-01-25 00:24:31 +01:00
|
|
|
if (!sourceInfo || sourceInfo->name.empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
auto &buildData = m_buildDataByPackage[sourceInfo->name];
|
|
|
|
buildData.specifiedIndex = currentIndex++;
|
2021-03-22 15:08:41 +01:00
|
|
|
buildData.existingPackages.emplace_back(existingPackage.pkg);
|
2021-01-25 00:24:31 +01:00
|
|
|
if (buildData.existingVersion.empty()
|
2021-03-22 15:08:41 +01:00
|
|
|
|| LibPkg::PackageVersion::isNewer(LibPkg::PackageVersion::compare(existingPackage.pkg->version, buildData.existingVersion))) {
|
|
|
|
buildData.existingVersion = existingPackage.pkg->version;
|
2021-01-25 00:24:31 +01:00
|
|
|
}
|
|
|
|
// assume we don't have sources if going to clean up the source directory
|
|
|
|
// FIXME: It is a little bit inconsistent that in other places existing PackageBuildData is overridden and here it is re-used.
|
|
|
|
if (m_cleanSourceDirectory) {
|
|
|
|
buildData.hasSource = false;
|
|
|
|
}
|
|
|
|
foundSourcePackage = true;
|
2021-03-22 15:08:41 +01:00
|
|
|
if (std::get<LibPkg::Database *>(existingPackage.db) == destinationDb) {
|
2021-01-25 00:24:31 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-07-03 19:59:57 +02:00
|
|
|
// add an empty BuildPackage nevertheless assuming packageName specifies the base package as-is
|
2021-01-25 00:24:31 +01:00
|
|
|
if (!foundSourcePackage) {
|
|
|
|
packageBuildData.specifiedIndex = currentIndex++;
|
|
|
|
m_buildDataByPackage[packageName] = std::move(packageBuildData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
configReadLock = std::monostate{};
|
|
|
|
|
|
|
|
// make working directory
|
|
|
|
try {
|
|
|
|
filesystem::create_directories(m_workingDirectory);
|
|
|
|
} catch (const filesystem::filesystem_error &e) {
|
|
|
|
reportError("Unable to create working directory " % m_workingDirectory % ": " + e.what());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reportAbortedIfAborted()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// locate PKGBUILDs locally or download them from AUR
|
|
|
|
fetchMissingBuildData();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Populates m_dbConfig (or m_dbStagingConfig if \a forStaging) with the specified \a dbOrder.
|
|
|
|
*/
|
|
|
|
void PrepareBuild::populateDbConfig(const std::vector<LibPkg::Database *> &dbOrder, bool forStaging)
|
|
|
|
{
|
|
|
|
auto &dbConfig = forStaging ? m_stagingDbConfig : m_dbConfig;
|
|
|
|
for (auto i = dbOrder.rbegin(), end = dbOrder.rend(); i != end; ++i) {
|
|
|
|
const auto *const db = *i;
|
|
|
|
auto scopeData = IniFile::ScopeData{};
|
|
|
|
scopeData.emplace(make_pair("SigLevel", LibPkg::signatureLevelToString(db->signatureLevel)));
|
|
|
|
for (const auto &mirror : db->mirrors) {
|
|
|
|
scopeData.emplace(make_pair("Server", mirror));
|
|
|
|
}
|
|
|
|
if (m_sourceDbs.empty() || m_baseDbs.find(db->name) != m_baseDbs.cend() || (forStaging && endsWith(db->name, "-staging"))) {
|
|
|
|
dbConfig.emplace_back(IniFile::Scope(db->name, move(scopeData)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Determines whether the existing package \a package is relevant for providing the dependency with \a dependencyName considering the
|
|
|
|
* specified \a destinationDb.
|
|
|
|
* \remarks The \a package is added to the existing packages in \a packageBuildData in case it is useful for further processing even though
|
|
|
|
* not for providing the dependency (e.g. bumping pkgrel).
|
|
|
|
*/
|
|
|
|
bool PrepareBuild::isExistingPackageRelevant(
|
|
|
|
const string &dependencyName, LibPkg::PackageSearchResult &package, PackageBuildData &packageBuildData, const LibPkg::Database &destinationDb)
|
|
|
|
{
|
|
|
|
// skip db if arch isn't matching the destination db arch
|
|
|
|
const auto *const db = std::get<LibPkg::Database *>(package.db);
|
|
|
|
if (db->arch != destinationDb.arch) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// add an exact package match to the build data if it is present in the destination db or any dependency because it is
|
|
|
|
// still relevant for bumping versions, even if we ignore it because it is not present in the base DBs
|
|
|
|
const auto dependencyPresentInRequiredDbs = m_requiredDbs.find(db->name) != m_requiredDbs.end();
|
|
|
|
if (dependencyPresentInRequiredDbs && dependencyName == package.pkg->name) {
|
|
|
|
if (packageBuildData.existingVersion.empty()
|
|
|
|
|| LibPkg::PackageVersion::isNewer(LibPkg::PackageVersion::compare(package.pkg->version, packageBuildData.existingVersion))) {
|
|
|
|
packageBuildData.existingVersion = package.pkg->version;
|
|
|
|
}
|
|
|
|
packageBuildData.existingPackages.emplace_back(package.pkg);
|
|
|
|
}
|
2021-07-03 19:59:57 +02:00
|
|
|
// skip if package is not from any database available within that build
|
2021-01-25 00:24:31 +01:00
|
|
|
return (m_baseDbs.empty() && dependencyPresentInRequiredDbs) || (m_baseDbs.find(db->name) != m_baseDbs.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::addPackageToLogLine(std::string &logLine, const std::string &packageName)
|
|
|
|
{
|
|
|
|
logLine += ' ';
|
|
|
|
logLine += packageName;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::makeSrcInfo(
|
|
|
|
std::shared_ptr<WebClient::AurSnapshotQuerySession> &multiSession, const std::string &sourceDirectory, const std::string &packageName)
|
|
|
|
{
|
|
|
|
auto processSession = make_shared<ProcessSession>(
|
|
|
|
m_setup.building.ioContext, [multiSession, &sourceDirectory, &packageName](boost::process::child &&child, ProcessResult &&result) {
|
|
|
|
processSrcInfo(*multiSession, sourceDirectory, packageName, std::move(child), std::move(result));
|
|
|
|
});
|
|
|
|
processSession->launch(boost::process::start_dir(sourceDirectory), m_makePkgPath.string(), "--printsrcinfo");
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::processSrcInfo(WebClient::AurSnapshotQuerySession &multiSession, const std::string &sourceDirectory,
|
|
|
|
const std::string &packageName, boost::process::child &&child, ProcessResult &&result)
|
|
|
|
{
|
|
|
|
if (result.errorCode) {
|
|
|
|
multiSession.addResponse(WebClient::AurSnapshotResult{ .packageName = packageName,
|
|
|
|
.errorOutput = move(result.error),
|
|
|
|
.error = argsToString("Unable to invoke makepkg --printsourceinfo: ", result.errorCode.message()) });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (child.exit_code() != 0) {
|
|
|
|
multiSession.addResponse(WebClient::AurSnapshotResult{ .packageName = packageName,
|
|
|
|
.errorOutput = move(result.error),
|
|
|
|
.error = argsToString("makepkg --printsourceinfo exited with non-zero exit code: ", child.exit_code()) });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto srcInfoPath = sourceDirectory + "/.SRCINFO";
|
|
|
|
try {
|
|
|
|
writeFile(srcInfoPath, result.output);
|
|
|
|
} catch (const std::ios_base::failure &failure) {
|
|
|
|
multiSession.addResponse(
|
|
|
|
WebClient::AurSnapshotResult{ .packageName = packageName, .error = "Unable to write " % srcInfoPath % ": " + failure.what() });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
addResultFromSrcInfo(multiSession, packageName, result.output);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::addResultFromSrcInfo(WebClient::AurSnapshotQuerySession &multiSession, const std::string &packageName, const std::string &srcInfo)
|
|
|
|
{
|
|
|
|
auto snapshotResult = WebClient::AurSnapshotResult{ .packageName = packageName, .packages = LibPkg::Package::fromInfo(srcInfo, false) };
|
|
|
|
if (snapshotResult.packages.empty() || snapshotResult.packages.front()->name.empty()) {
|
|
|
|
snapshotResult.error = "Unable to parse .SRCINFO: no package name present";
|
|
|
|
} else if (!(snapshotResult.sourceInfo = snapshotResult.packages.front()->sourceInfo)) {
|
|
|
|
snapshotResult.error = "Unable to parse .SRCINFO: no source info present";
|
|
|
|
}
|
|
|
|
multiSession.addResponse(std::move(snapshotResult));
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::fetchMissingBuildData()
|
|
|
|
{
|
|
|
|
auto multiSession
|
|
|
|
= WebClient::AurSnapshotQuerySession::create(m_setup.building.ioContext, bind(&PrepareBuild::computeDependencies, this, placeholders::_1));
|
|
|
|
vector<WebClient::AurSnapshotQueryParams> snapshotQueries;
|
|
|
|
|
|
|
|
// prepare logging
|
|
|
|
auto logLines = vector<string>{
|
|
|
|
" -> Generating .SRCINFO for local PKGBUILDs:", " -> Retrieving sources from AUR:", " -> Using existing source directories:"
|
|
|
|
};
|
|
|
|
auto needToGeneratedSrcInfo = false;
|
|
|
|
auto usingExistingSourceDirectories = false;
|
|
|
|
|
|
|
|
// fetch build data (PKGBUILD, .SRCINFO, ...) for all the packages we want to build
|
|
|
|
for (auto &[packageName, buildData] : m_buildDataByPackage) {
|
|
|
|
|
|
|
|
// skip packages where the source has already been fetched in a previous invocation or which have already failed on a previous invocation
|
|
|
|
if (buildData.hasSource || !buildData.error.empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
buildData.sourceDirectory = m_workingDirectory % '/' % packageName + "/src";
|
|
|
|
|
|
|
|
// skip fetching if source directory already exists (from previous run or manual provisioning)
|
|
|
|
// clean existing source directory if cleanup is enabled
|
|
|
|
try {
|
|
|
|
const auto sourceDirectoryExists = filesystem::exists(buildData.sourceDirectory);
|
|
|
|
if (m_cleanSourceDirectory && sourceDirectoryExists) {
|
|
|
|
filesystem::remove_all(buildData.sourceDirectory);
|
|
|
|
} else if (sourceDirectoryExists) {
|
|
|
|
// verify the PKGBUILD file exists
|
|
|
|
const auto pkgbuildPath = buildData.sourceDirectory + "/PKGBUILD";
|
|
|
|
if (!filesystem::exists(pkgbuildPath)) {
|
|
|
|
multiSession->addResponse(WebClient::AurSnapshotResult{ .packageName = packageName,
|
|
|
|
.error = "Existing source directory \"" % buildData.sourceDirectory + "\" does not contain a PKGBUILD file." });
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// generate the .SRCINFO file if not already present; otherwise read the existing .SRCINFO
|
|
|
|
const auto srcInfoPath = buildData.sourceDirectory + "/.SRCINFO";
|
|
|
|
if (!filesystem::exists(srcInfoPath) || filesystem::last_write_time(srcInfoPath) < filesystem::last_write_time(pkgbuildPath)) {
|
|
|
|
makeSrcInfo(multiSession, buildData.sourceDirectory, packageName);
|
|
|
|
needToGeneratedSrcInfo = true;
|
|
|
|
} else {
|
|
|
|
string srcInfo;
|
|
|
|
try {
|
|
|
|
srcInfo = readFile(srcInfoPath, 0x10000);
|
|
|
|
} catch (const std::ios_base::failure &e) {
|
|
|
|
multiSession->addResponse(WebClient::AurSnapshotResult{ .packageName = packageName,
|
|
|
|
.error = "Unable to read .SRCINFO \"" % srcInfoPath % "\" from existing source directory: " + e.what() });
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
addResultFromSrcInfo(*multiSession, packageName, srcInfo);
|
|
|
|
}
|
|
|
|
usingExistingSourceDirectories = true;
|
|
|
|
addPackageToLogLine(logLines[0], packageName);
|
|
|
|
addPackageToLogLine(logLines[2], packageName);
|
|
|
|
buildData.hasSource = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} catch (const filesystem::filesystem_error &e) {
|
|
|
|
multiSession->addResponse(WebClient::AurSnapshotResult{ .packageName = packageName,
|
|
|
|
.error = "Unable to check existing source directory \"" % buildData.sourceDirectory % "\": " + e.what() });
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear the original source directory which is possibly still assigned from a previous run
|
|
|
|
buildData.originalSourceDirectory.clear();
|
|
|
|
|
|
|
|
// skip next block if package name matches configured pattern
|
|
|
|
LibPkg::PackageNameData packageNameData;
|
|
|
|
if (regex_match(packageName, m_ignoreLocalPkgbuildsRegex)) {
|
|
|
|
goto addAurQuery;
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy PKGBUILD from local PKGBUILDs directory and generate .SRCINFO from it via makepkg
|
|
|
|
if (!m_pkgbuildsDirs.empty()) {
|
|
|
|
packageNameData = LibPkg::PackageNameData::decompose(packageName);
|
|
|
|
}
|
|
|
|
for (const auto &pkgbuildsDir : m_pkgbuildsDirs) {
|
|
|
|
const auto variant = packageNameData.variant();
|
|
|
|
auto pkgbuildPath = pkgbuildsDir % '/' % packageNameData.actualName % '/' % variant;
|
|
|
|
try {
|
|
|
|
if (!filesystem::exists(pkgbuildPath + "/PKGBUILD")) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
buildData.originalSourceDirectory = tupleToString(pkgbuildPath);
|
|
|
|
filesystem::create_directories(buildData.sourceDirectory);
|
|
|
|
filesystem::copy(buildData.originalSourceDirectory, buildData.sourceDirectory, std::filesystem::copy_options::recursive);
|
|
|
|
|
|
|
|
} catch (const filesystem::filesystem_error &e) {
|
|
|
|
multiSession->addResponse(WebClient::AurSnapshotResult{ .packageName = packageName,
|
|
|
|
.error
|
|
|
|
= "Unable to copy files from PKGBUILDs directory " % pkgbuildsDir % " to " % buildData.sourceDirectory % ": " + e.what() });
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
makeSrcInfo(multiSession, buildData.sourceDirectory, packageName);
|
|
|
|
needToGeneratedSrcInfo = true;
|
|
|
|
addPackageToLogLine(logLines[0], packageName);
|
|
|
|
buildData.hasSource = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
addAurQuery:
|
|
|
|
// download latest snapshot containing PKGBUILD and .SRCINFO from AUR
|
|
|
|
if (!buildData.hasSource) {
|
|
|
|
snapshotQueries.emplace_back(WebClient::AurSnapshotQueryParams{
|
|
|
|
.packageName = &packageName,
|
|
|
|
.targetDirectory = &buildData.sourceDirectory,
|
|
|
|
});
|
|
|
|
addPackageToLogLine(logLines[1], packageName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// schedule async AUR downloads
|
|
|
|
if (!snapshotQueries.empty()) {
|
|
|
|
WebClient::queryAurSnapshots(m_buildAction->log(), m_setup, snapshotQueries, m_setup.building.ioContext, multiSession);
|
|
|
|
}
|
|
|
|
|
|
|
|
// log status
|
|
|
|
if (!needToGeneratedSrcInfo) {
|
|
|
|
logLines[0].clear();
|
|
|
|
}
|
|
|
|
if (snapshotQueries.empty()) {
|
|
|
|
logLines[1].clear();
|
|
|
|
}
|
|
|
|
if (!usingExistingSourceDirectories) {
|
|
|
|
logLines[2].clear();
|
|
|
|
}
|
|
|
|
m_buildAction->appendOutput(Phrases::InfoMessage, "Fetching missing build data\n", joinStrings(logLines, string(), true, string(), "\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PrepareBuild::pullFurtherDependencies(const std::vector<LibPkg::Dependency> &dependencies)
|
|
|
|
{
|
2021-07-03 19:59:57 +02:00
|
|
|
// pull further dependencies; similar to initial dependency lookup in run()
|
2021-01-25 00:24:31 +01:00
|
|
|
auto dependencyAdded = false;
|
|
|
|
const auto *const destinationDb = *m_destinationDbs.begin();
|
|
|
|
for (const auto &dependency : dependencies) {
|
|
|
|
auto dependencyExists = false;
|
|
|
|
|
|
|
|
// skip if the dependency is already in the list of packages to be built (check for cycles is done later when computing batches)
|
|
|
|
if (m_buildDataByPackage.find(dependency.name) != m_buildDataByPackage.end()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
for (const auto &[packageName, buildData] : m_buildDataByPackage) {
|
|
|
|
for (const auto &package : buildData.packages) {
|
|
|
|
if (package->providesDependency(dependency)) {
|
|
|
|
dependencyExists = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (dependencyExists) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (dependencyExists) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip dependency if it is already present in one of the dependencies considered as "given" (see note in run() function)
|
|
|
|
auto packageBuildData = PackageBuildData();
|
|
|
|
auto existingPackages = m_setup.config.findPackages(dependency);
|
|
|
|
for (auto &package : existingPackages) {
|
|
|
|
// skip if package not relevant
|
|
|
|
if (!isExistingPackageRelevant(dependency.name, package, packageBuildData, *destinationDb)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
dependencyExists = true;
|
|
|
|
}
|
|
|
|
if (dependencyExists) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// assume the dependency is provided by a package with the pkgbase of the dependency denotation
|
|
|
|
// FIXME: support split packages
|
|
|
|
m_buildDataByPackage[dependency.name] = std::move(packageBuildData);
|
|
|
|
dependencyAdded = true;
|
|
|
|
}
|
|
|
|
return dependencyAdded;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct BatchItem {
|
|
|
|
struct MissingDependency {
|
|
|
|
LibPkg::Dependency dependency;
|
|
|
|
|
|
|
|
LibPkg::Dependency requiredBy;
|
|
|
|
};
|
|
|
|
|
|
|
|
const std::string *const name;
|
|
|
|
const PackageBuildData *const buildData;
|
|
|
|
std::unordered_map<const BatchItem *, std::unordered_set<const BatchItem *>> neededItems;
|
|
|
|
LibPkg::DependencySet requiredDependencies;
|
|
|
|
LibPkg::DependencySet providedDependencies;
|
|
|
|
std::vector<MissingDependency> missingDependencies;
|
|
|
|
bool done = false;
|
|
|
|
|
|
|
|
void determineNeededItems(LibPkg::Config &config, const std::unordered_set<string> &requiredDbs, const LibPkg::Database *destinationDb,
|
|
|
|
unordered_map<string, BatchItem> &batchItems);
|
|
|
|
template <typename... DependencyParams>
|
|
|
|
bool addNeededItemForRequiredDependencyFromOtherItems(
|
|
|
|
const unordered_map<string, BatchItem> &otherItems, DependencyParams &&...requiredDependencyParams);
|
|
|
|
static bool addItemsToBatch(Batch ¤tBatch, BatchMap &items);
|
|
|
|
|
|
|
|
private:
|
|
|
|
void addNeededBatchItemsForPackage(LibPkg::Config &config, const std::unordered_set<string> &requiredDbs, const LibPkg::Database *destinationDb,
|
|
|
|
unordered_map<string, BatchItem> &batchItems, unordered_set<const LibPkg::Package *> &visitedPackages, LibPkg::Package &package);
|
|
|
|
};
|
|
|
|
|
|
|
|
template <typename... DependencyParams>
|
|
|
|
bool BatchItem::addNeededItemForRequiredDependencyFromOtherItems(
|
|
|
|
const unordered_map<string, BatchItem> &otherItems, DependencyParams &&...requiredDependencyParams)
|
|
|
|
{
|
|
|
|
// skip if the dependency of a package is provided by the package itself (can happen when dealing with split packages)
|
|
|
|
if (providedDependencies.provides(std::forward<DependencyParams>(requiredDependencyParams)...)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// go through all the items to find one which provides the dependency; continue to look for alternatives as well
|
|
|
|
const BatchItem *relevantItem = nullptr;
|
|
|
|
std::unordered_set<const BatchItem *> alternativeItems;
|
|
|
|
for (const auto &item : otherItems) {
|
|
|
|
if (&item.second == this) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (item.second.providedDependencies.provides(std::forward<DependencyParams>(requiredDependencyParams)...)) {
|
|
|
|
if (!relevantItem) {
|
|
|
|
relevantItem = &item.second;
|
|
|
|
} else {
|
|
|
|
alternativeItems.emplace(&item.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (relevantItem) {
|
|
|
|
// FIXME: actually, we need to handle the case when the item is already present as the alternative items might differ
|
|
|
|
neededItems.emplace(relevantItem, move(alternativeItems));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BatchItem::determineNeededItems(LibPkg::Config &config, const std::unordered_set<std::string> &requiredDbs,
|
|
|
|
const LibPkg::Database *destinationDb, unordered_map<string, BatchItem> &batchItems)
|
|
|
|
{
|
|
|
|
unordered_set<const LibPkg::Package *> visitedPackages;
|
|
|
|
|
|
|
|
for (const auto &require : requiredDependencies) {
|
|
|
|
// check whether its provided directly by some other item
|
|
|
|
if (addNeededItemForRequiredDependencyFromOtherItems(batchItems, require.first, require.second)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check by which other package the item is provided
|
|
|
|
// skip dependency if it is already present in one of the dbs
|
|
|
|
auto dependency = LibPkg::Dependency(require.first, require.second.version, require.second.mode);
|
|
|
|
auto foundPackage = false;
|
|
|
|
const auto existingPackages = config.findPackages(dependency);
|
|
|
|
for (const auto &package : existingPackages) {
|
2021-07-03 19:59:57 +02:00
|
|
|
// skip if package is not from any database available within that build
|
2021-01-25 00:24:31 +01:00
|
|
|
const auto *const db = std::get<LibPkg::Database *>(package.db);
|
|
|
|
if (requiredDbs.find(db->name) == requiredDbs.end()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (db->arch != destinationDb->arch) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
addNeededBatchItemsForPackage(config, requiredDbs, destinationDb, batchItems, visitedPackages, *package.pkg);
|
|
|
|
foundPackage = true;
|
|
|
|
}
|
|
|
|
if (!foundPackage) {
|
|
|
|
missingDependencies.emplace_back(MissingDependency{ .dependency = move(dependency), .requiredBy = LibPkg::Dependency(*name) });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BatchItem::addItemsToBatch(Batch ¤tBatch, BatchMap &items)
|
|
|
|
{
|
|
|
|
auto allDone = true;
|
|
|
|
|
|
|
|
for (auto &[packageName, batchItem] : items) {
|
|
|
|
// skip items which are already done
|
|
|
|
if (batchItem.done) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
allDone = false;
|
|
|
|
|
|
|
|
// skip if not all dependencies of the items are done
|
|
|
|
auto allDepsDone = true;
|
|
|
|
for (const auto &[neededItem, alternatives] : batchItem.neededItems) {
|
|
|
|
if (neededItem->done) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto oneAlternativeDone = false;
|
|
|
|
for (const auto &alternative : alternatives) {
|
|
|
|
if (alternative->done) {
|
|
|
|
oneAlternativeDone = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (oneAlternativeDone) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
allDepsDone = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!allDepsDone) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add item to current batch
|
|
|
|
currentBatch.emplace_back(&batchItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
return allDone;
|
|
|
|
}
|
|
|
|
|
|
|
|
void BatchItem::addNeededBatchItemsForPackage(LibPkg::Config &config, const std::unordered_set<string> &requiredDbs,
|
|
|
|
const LibPkg::Database *destinationDb, unordered_map<string, BatchItem> &batchItems, unordered_set<const LibPkg::Package *> &visitedPackages,
|
|
|
|
LibPkg::Package &package)
|
|
|
|
{
|
|
|
|
// stop if there's a cycle (not a critical error here; we've just seen enough; only cycles between packages we actually want to build are interesting)
|
|
|
|
const auto [i, notVisitedBefore] = visitedPackages.emplace(&package);
|
|
|
|
if (!notVisitedBefore) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check whether the dependencies of that package lead to another build item
|
|
|
|
for (const auto &dep : package.dependencies) {
|
|
|
|
// check whether the dependency is directly one of the build items
|
|
|
|
if (addNeededItemForRequiredDependencyFromOtherItems(batchItems, dep)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check other packages recursively
|
|
|
|
auto foundPackage = false;
|
|
|
|
const auto existingPackages = config.findPackages(dep);
|
2021-03-22 15:08:41 +01:00
|
|
|
for (const auto &existingPackage : existingPackages) {
|
2021-07-03 19:59:57 +02:00
|
|
|
// skip if package is not from any database available within that build
|
2021-03-22 15:08:41 +01:00
|
|
|
const auto *const db = std::get<LibPkg::Database *>(existingPackage.db);
|
2021-01-25 00:24:31 +01:00
|
|
|
if (requiredDbs.find(db->name) == requiredDbs.end()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (db->arch != destinationDb->arch) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-03-22 15:08:41 +01:00
|
|
|
addNeededBatchItemsForPackage(config, requiredDbs, destinationDb, batchItems, visitedPackages, *existingPackage.pkg);
|
2021-01-25 00:24:31 +01:00
|
|
|
foundPackage = true;
|
|
|
|
}
|
|
|
|
if (!foundPackage) {
|
|
|
|
missingDependencies.emplace_back(MissingDependency{ .dependency = dep, .requiredBy = LibPkg::Dependency(package.name) });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool sortBatch(const BatchItem *lhs, const BatchItem *rhs)
|
|
|
|
{
|
|
|
|
if (lhs->buildData->specifiedIndex != numeric_limits<size_t>::max() && rhs->buildData->specifiedIndex != numeric_limits<size_t>::max()) {
|
|
|
|
return lhs->buildData->specifiedIndex < rhs->buildData->specifiedIndex;
|
|
|
|
} else if (lhs->buildData->specifiedIndex != numeric_limits<size_t>::max()) {
|
|
|
|
return true;
|
|
|
|
} else if (rhs->buildData->specifiedIndex != numeric_limits<size_t>::max()) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return *lhs->name < *rhs->name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::bumpVersions()
|
|
|
|
{
|
|
|
|
// bump pkgrel or even epoch to ensure the new packages are actually considered newer by pacman when updating
|
|
|
|
if (m_keepPkgRelAndEpoch) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (const auto &[packageName, buildData] : m_buildDataByPackage) {
|
|
|
|
auto &existingVersionStr = buildData.existingVersion;
|
|
|
|
if (existingVersionStr.empty() && !m_forceBumpPackageVersion) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto existingVersion = existingVersionStr.empty() ? LibPkg::PackageVersion{} : LibPkg::PackageVersion::fromString(existingVersionStr);
|
|
|
|
LibPkg::PackageAmendment amendment;
|
|
|
|
LibPkg::PackageVersion newVersion;
|
|
|
|
for (const auto &package : buildData.packages) {
|
|
|
|
newVersion = LibPkg::PackageVersion::fromString(package->version);
|
|
|
|
if (existingVersionStr.empty()) {
|
|
|
|
existingVersion = newVersion;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
switch (newVersion.compare(existingVersion)) {
|
|
|
|
case LibPkg::PackageVersionComparison::Equal:
|
|
|
|
case LibPkg::PackageVersionComparison::PackageUpgradeOnly:
|
|
|
|
amendment.bumpDownstreamVersion = LibPkg::PackageAmendment::VersionBump::PackageVersion;
|
|
|
|
m_warnings.emplace_back("Bumping pkgrel of " % package->name % "; version " % existingVersionStr + " already exists");
|
|
|
|
break;
|
|
|
|
case LibPkg::PackageVersionComparison::SoftwareUpgrade:
|
|
|
|
if (package->decomposeName().isVcsPackage()) {
|
|
|
|
// skip bumping epoch of VCS packages; the pkgver is supposed to be adjusted in pkgver()
|
|
|
|
// note: Not skipping this in the pkgrel handling in the case above because when pkgver() returns a new version, pkgrel is
|
2021-07-03 19:59:57 +02:00
|
|
|
// reset automatically so there's no harm in bumping it and it might actually be needed if there's no no version.
|
2021-01-25 00:24:31 +01:00
|
|
|
m_warnings.emplace_back("Version of package " % package->name % " is" % package->version
|
|
|
|
% " which is older than existing version " % existingVersionStr
|
|
|
|
+ "; NOT bumping the epoch because it is a VCS package; be sure pkgver() actually yields a new version");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
amendment.bumpDownstreamVersion = LibPkg::PackageAmendment::VersionBump::Epoch;
|
|
|
|
m_warnings.emplace_back("Bumping epoch of " % package->name % "; its version " % package->version % " is older than existing version "
|
|
|
|
+ existingVersionStr);
|
|
|
|
goto breakLoop;
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
breakLoop:
|
|
|
|
if (m_forceBumpPackageVersion && amendment.isEmpty()) {
|
|
|
|
amendment.bumpDownstreamVersion = LibPkg::PackageAmendment::VersionBump::PackageVersion;
|
|
|
|
m_warnings.emplace_back("Bumping pkgrel of " % packageName + " (and its split packages); forcing pkgrel bump has been enabled");
|
|
|
|
}
|
|
|
|
if (!amendment.isEmpty()) {
|
|
|
|
auto amendedVersions = LibPkg::amendPkgbuild(buildData.sourceDirectory + "/PKGBUILD", existingVersion, amendment);
|
|
|
|
if (!amendedVersions.newEpoch.empty()) {
|
|
|
|
newVersion.epoch = std::move(amendedVersions.newEpoch);
|
|
|
|
}
|
|
|
|
if (!amendedVersions.newPkgRel.empty()) {
|
|
|
|
newVersion.package = std::move(amendedVersions.newPkgRel);
|
|
|
|
}
|
|
|
|
const auto newVersionStr = newVersion.toString();
|
|
|
|
for (const auto &package : buildData.packages) {
|
|
|
|
package->version = newVersionStr;
|
|
|
|
}
|
|
|
|
m_warnings.emplace_back("New version of " % packageName % " (and its split packages) is " + newVersionStr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::computeDependencies(WebClient::AurSnapshotQuerySession::ContainerType &&responses)
|
|
|
|
{
|
|
|
|
if (reportAbortedIfAborted()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find databases again
|
|
|
|
auto configReadLock = m_setup.config.lockToRead();
|
|
|
|
if (auto error = findDatabases(); !error.empty()) {
|
|
|
|
reportError(move(error));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// populate build data from responses and add further dependencies
|
|
|
|
auto furtherDependenciesNeeded = false;
|
|
|
|
auto sourcesMissing = false;
|
|
|
|
for (auto &response : responses) {
|
|
|
|
if (response.packageName.empty()) {
|
|
|
|
auto &buildData = m_buildDataByPackage["?"];
|
|
|
|
auto &error = buildData.error;
|
|
|
|
if (!error.empty()) {
|
|
|
|
error += '\n';
|
|
|
|
}
|
|
|
|
error = response.error.empty() ? "got response with no package name (internal error)" : move(response.error);
|
|
|
|
sourcesMissing = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto &buildData = m_buildDataByPackage[response.packageName];
|
|
|
|
buildData.sourceInfo = move(response.sourceInfo);
|
|
|
|
buildData.packages = move(response.packages);
|
|
|
|
buildData.error = move(response.error);
|
|
|
|
buildData.hasSource = buildData.sourceInfo && !buildData.packages.empty() && buildData.error.empty();
|
|
|
|
if (buildData.hasSource) {
|
|
|
|
const auto buildActionsWriteLock = m_setup.building.lockToWrite();
|
|
|
|
m_buildAction->artefacts.emplace_back(buildData.sourceDirectory + "/PKGBUILD"); // FIXME: add all files as artefacts
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (buildData.error.empty()) {
|
|
|
|
buildData.error = "no build data available";
|
|
|
|
}
|
|
|
|
sourcesMissing = true;
|
|
|
|
}
|
|
|
|
for (auto &response : responses) {
|
|
|
|
if (response.packageName.empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto &buildData = m_buildDataByPackage[response.packageName];
|
|
|
|
if (!buildData.hasSource) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
furtherDependenciesNeeded = pullFurtherDependencies(buildData.sourceInfo->makeDependencies) || furtherDependenciesNeeded;
|
|
|
|
furtherDependenciesNeeded = pullFurtherDependencies(buildData.sourceInfo->checkDependencies) || furtherDependenciesNeeded;
|
|
|
|
for (const auto &package : buildData.packages) {
|
|
|
|
furtherDependenciesNeeded = pullFurtherDependencies(package->dependencies) || furtherDependenciesNeeded;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
configReadLock.unlock();
|
|
|
|
|
|
|
|
if (reportAbortedIfAborted()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// pull missing dependencies if all sources could be retrieved so far
|
|
|
|
if (!sourcesMissing && furtherDependenciesNeeded) {
|
|
|
|
fetchMissingBuildData();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for errors
|
|
|
|
set<string> failedPackages;
|
|
|
|
for (const auto &buildData : m_buildDataByPackage) {
|
|
|
|
if (!buildData.second.error.empty()) {
|
|
|
|
failedPackages.emplace(buildData.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!failedPackages.empty()) {
|
|
|
|
auto errorMessage = "Unable to retrieve the following packages (see result data for details): " + joinStrings(failedPackages, " ");
|
|
|
|
m_buildAction->appendOutput(Phrases::ErrorMessage, errorMessage, '\n');
|
|
|
|
auto resultData = makeResultData(std::move(errorMessage));
|
|
|
|
auto buildActionWriteLock = m_setup.building.lockToWrite();
|
|
|
|
m_buildAction->resultData = std::move(resultData);
|
|
|
|
reportError();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bumpVersions();
|
|
|
|
|
|
|
|
if (reportAbortedIfAborted()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_keepOrder) {
|
|
|
|
deduceBatchesFromSpecifiedOrder();
|
|
|
|
} else {
|
|
|
|
computeBatches();
|
|
|
|
}
|
|
|
|
|
|
|
|
auto resultData = makeResultData();
|
|
|
|
auto buildActionWriteLock = m_setup.building.lockToWrite();
|
|
|
|
m_buildAction->resultData = std::move(resultData);
|
|
|
|
if (resultData.error.empty() && resultData.cyclicLeftovers.empty()) {
|
|
|
|
reportSuccess();
|
|
|
|
} else {
|
|
|
|
reportError();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unordered_map<std::string, BatchItem> PrepareBuild::prepareBatches()
|
|
|
|
{
|
|
|
|
std::unordered_map<std::string, BatchItem> batchItems;
|
|
|
|
for (const auto &[packageName, buildData] : m_buildDataByPackage) {
|
|
|
|
auto [i, newItem] = batchItems.try_emplace(packageName, BatchItem{ .name = &packageName, .buildData = &buildData });
|
|
|
|
auto &batchItem = i->second;
|
|
|
|
const auto &sourceInfo = buildData.sourceInfo;
|
|
|
|
for (const auto &package : buildData.packages) {
|
|
|
|
for (const auto &deps : { sourceInfo->makeDependencies, sourceInfo->checkDependencies, package->dependencies }) {
|
|
|
|
for (const auto &dep : deps) {
|
|
|
|
batchItem.requiredDependencies.add(dep, package);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto &dep : package->provides) {
|
|
|
|
batchItem.providedDependencies.add(dep, package);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return batchItems;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::deduceBatchesFromSpecifiedOrder()
|
|
|
|
{
|
|
|
|
m_buildAction->appendOutput(Phrases::InfoMessage, "Fetched sources; making batches for specified order ...\n"sv);
|
|
|
|
|
|
|
|
auto batchItems = prepareBatches();
|
|
|
|
BatchList batches;
|
|
|
|
for (auto &[packageName, batchItem] : batchItems) {
|
|
|
|
batches.emplace_back(std::vector<BatchItem *>{ &batchItem });
|
|
|
|
}
|
|
|
|
std::sort(batches.begin(), batches.end(), [](const Batch &lhs, const Batch &rhs) { return sortBatch(lhs.front(), rhs.front()); });
|
|
|
|
addBatchesToResult(std::move(batches), Batch());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::computeBatches()
|
|
|
|
{
|
|
|
|
m_buildAction->appendOutput(Phrases::InfoMessage, "Fetched sources; computing build batches ...\n"sv);
|
|
|
|
|
|
|
|
// prepare computing batches
|
|
|
|
auto batchItems = prepareBatches();
|
|
|
|
auto configReadLock2 = m_setup.config.lockToRead();
|
|
|
|
if (auto error = findDatabases(); !error.empty()) {
|
|
|
|
reportError(move(error));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add dependency relations to batch items
|
|
|
|
for (auto &batchItem : batchItems) {
|
|
|
|
batchItem.second.determineNeededItems(m_setup.config, m_requiredDbs, *m_destinationDbs.begin(), batchItems);
|
|
|
|
}
|
|
|
|
configReadLock2.unlock();
|
|
|
|
|
|
|
|
// FIXME: check for missing dependencies
|
|
|
|
|
|
|
|
// add batch items into batches
|
|
|
|
BatchList batches;
|
|
|
|
bool allDone;
|
|
|
|
size_t addedItems;
|
|
|
|
do {
|
|
|
|
if (reportAbortedIfAborted()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto &newBatch = batches.emplace_back();
|
|
|
|
allDone = BatchItem::addItemsToBatch(newBatch, batchItems);
|
|
|
|
addedItems = newBatch.size();
|
|
|
|
if (!addedItems) {
|
|
|
|
batches.pop_back();
|
|
|
|
}
|
|
|
|
for (auto &addedItem : newBatch) {
|
|
|
|
addedItem->done = true;
|
|
|
|
}
|
|
|
|
} while (!allDone && addedItems);
|
|
|
|
|
|
|
|
// check for cyclic leftovers
|
|
|
|
Batch cyclicLeftovers;
|
|
|
|
if (!allDone) {
|
|
|
|
for (auto &item : batchItems) {
|
|
|
|
if (!item.second.done) {
|
|
|
|
cyclicLeftovers.emplace_back(&item.second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!cyclicLeftovers.empty()) {
|
2021-07-03 19:59:57 +02:00
|
|
|
m_warnings.emplace_back("Dependency cycles have been detected. Add a bootstrap package to the package list to resolve this.");
|
2021-01-25 00:24:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort batches so the order in which packages have been specified is preserved within the batches
|
|
|
|
for (auto &batch : batches) {
|
|
|
|
std::sort(batch.begin(), batch.end(), sortBatch);
|
|
|
|
}
|
|
|
|
std::sort(cyclicLeftovers.begin(), cyclicLeftovers.end(), sortBatch);
|
|
|
|
|
|
|
|
// add batches to result
|
|
|
|
addBatchesToResult(std::move(batches), std::move(cyclicLeftovers));
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrepareBuild::addBatchesToResult(BatchList &&batches, Batch &&cyclicLeftovers)
|
|
|
|
{
|
|
|
|
const auto returnItemName = [](const BatchItem *item) { return *item->name; };
|
|
|
|
m_batches.reserve(batches.size());
|
|
|
|
std::transform(batches.cbegin(), batches.cend(), std::back_inserter(m_batches), [&returnItemName](const Batch &batch) {
|
|
|
|
std::vector<std::string> newBatch;
|
|
|
|
newBatch.reserve(batch.size());
|
|
|
|
std::transform(batch.cbegin(), batch.cend(), std::back_inserter(newBatch), returnItemName);
|
|
|
|
return newBatch;
|
|
|
|
});
|
|
|
|
m_cyclicLeftovers.reserve(cyclicLeftovers.size());
|
|
|
|
std::transform(cyclicLeftovers.cbegin(), cyclicLeftovers.cend(), std::back_inserter(m_cyclicLeftovers), returnItemName);
|
|
|
|
}
|
|
|
|
|
|
|
|
BuildPreparation PrepareBuild::makeResultData(std::string &&error)
|
|
|
|
{
|
|
|
|
auto resultData = BuildPreparation{
|
|
|
|
.buildData = std::move(m_buildDataByPackage),
|
|
|
|
.dbConfig = std::move(m_dbConfig),
|
|
|
|
.stagingDbConfig = std::move(m_stagingDbConfig),
|
|
|
|
.targetDb = std::move(m_targetDbName),
|
|
|
|
.targetArch = std::move(m_targetArch),
|
|
|
|
.stagingDb = std::move(m_stagingDbName),
|
|
|
|
.batches = std::move(m_batches),
|
|
|
|
.cyclicLeftovers = std::move(m_cyclicLeftovers),
|
|
|
|
.warnings = std::move(m_warnings),
|
|
|
|
.error = std::move(error),
|
|
|
|
.manuallyOrdered = m_keepOrder,
|
|
|
|
};
|
|
|
|
|
|
|
|
// write results into the working directory so the actual build can pick it up
|
|
|
|
try {
|
|
|
|
dumpJsonDocument(
|
|
|
|
[this, &resultData] {
|
|
|
|
const auto configLock = m_setup.config.lockToRead();
|
|
|
|
return resultData.toJsonDocument();
|
|
|
|
},
|
|
|
|
m_workingDirectory, buildPreparationFileName, DumpJsonExistingFileHandling::Backup);
|
|
|
|
} catch (const std::runtime_error &e) {
|
|
|
|
const auto what = string_view(e.what());
|
|
|
|
if (resultData.error.empty()) {
|
|
|
|
resultData.error = what;
|
|
|
|
} else {
|
|
|
|
resultData.warnings.emplace_back(what);
|
|
|
|
}
|
|
|
|
m_buildAction->appendOutput(Phrases::ErrorMessage, what, '\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
// write build progress skeletion (a BuildProgress with a default-initialized PackageBuildProgress for each package)
|
|
|
|
try {
|
|
|
|
BuildProgress progress;
|
|
|
|
try {
|
|
|
|
restoreJsonObject(progress, m_workingDirectory, buildProgressFileName, RestoreJsonExistingFileHandling::Skip);
|
|
|
|
} catch (const std::runtime_error &e) {
|
|
|
|
m_buildAction->appendOutput(Phrases::ErrorMessage, "Unable to read existing build-progress.json: ", e.what(), '\n');
|
|
|
|
}
|
|
|
|
if (dumpJsonDocument(
|
|
|
|
[this, &resultData, &progress] {
|
|
|
|
// reset database-specific fields as the database configuration might have changed
|
|
|
|
progress.targetDbFilePath.clear();
|
|
|
|
progress.targetRepoPath.clear();
|
|
|
|
progress.stagingDbFilePath.clear();
|
|
|
|
progress.stagingRepoPath.clear();
|
|
|
|
for (const auto &[packageName, buildData] : resultData.buildData) {
|
|
|
|
auto &buildProgress = progress.progressByPackage[packageName];
|
|
|
|
// reset the build progress if the PKGBUILD has been updated
|
|
|
|
if (!buildProgress.hasBeenAnyProgressMade()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (buildProgress.buildDirectory.empty()) {
|
2021-05-16 15:45:53 +02:00
|
|
|
buildProgress.resetProgress();
|
|
|
|
if (m_resetChrootSettings) {
|
|
|
|
buildProgress.resetChrootSettings();
|
|
|
|
}
|
2021-01-25 00:24:31 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const std::filesystem::path srcDirPkgbuild = buildData.sourceDirectory + "/PKGBUILD";
|
|
|
|
const std::filesystem::path buildDirPkgbuild = buildProgress.buildDirectory + "/PKGBUILD";
|
|
|
|
try {
|
|
|
|
if (!std::filesystem::exists(buildDirPkgbuild)
|
|
|
|
|| std::filesystem::last_write_time(srcDirPkgbuild) > std::filesystem::last_write_time(buildDirPkgbuild)) {
|
2021-05-16 15:45:53 +02:00
|
|
|
buildProgress.resetProgress();
|
|
|
|
if (m_resetChrootSettings) {
|
|
|
|
buildProgress.resetChrootSettings();
|
|
|
|
}
|
2021-01-25 00:24:31 +01:00
|
|
|
}
|
|
|
|
} catch (const std::filesystem::filesystem_error &e) {
|
|
|
|
m_buildAction->appendOutput(
|
|
|
|
Phrases::ErrorMessage, "Unable to determine whether PKGBUILD has been updated: ", e.what(), '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return progress.toJsonDocument();
|
|
|
|
},
|
|
|
|
m_workingDirectory, buildProgressFileName, DumpJsonExistingFileHandling::Backup)
|
|
|
|
.empty()) {
|
|
|
|
m_buildAction->appendOutput(Phrases::ErrorMessage, "Unable to write build-progress.json\n"sv);
|
|
|
|
}
|
|
|
|
} catch (const std::runtime_error &e) {
|
|
|
|
const auto what = string_view(e.what());
|
|
|
|
resultData.warnings.emplace_back(what);
|
|
|
|
m_buildAction->appendOutput(Phrases::ErrorMessage, what);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultData;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace LibRepoMgr
|