#include "./buildactionprivate.h" #include "../logging.h" #include "../serversetup.h" #include "../../libpkg/data/database.h" #include "../../libpkg/data/package.h" #include "../../libpkg/parser/utils.h" #include #include #include using namespace std; using namespace CppUtilities; using namespace CppUtilities::EscapeCodes; namespace LibRepoMgr { ReloadLibraryDependencies::ReloadLibraryDependencies(ServiceSetup &setup, const std::shared_ptr &buildAction) : InternalBuildAction(setup, buildAction) { } void ReloadLibraryDependencies::run() { // read configuration const auto flags = static_cast(m_buildAction->flags); m_force = flags & ReloadLibraryDependenciesFlags::ForceReload; const auto skipDependencies = flags & ReloadLibraryDependenciesFlags::SkipDependencies; auto &metaInfo = m_setup.building.metaInfo; auto metaInfoLock = metaInfo.lockToRead(); const auto &typeInfo = metaInfo.typeInfoForId(BuildActionType::ReloadLibraryDependencies); const auto packageExcludeRegexSetting = typeInfo.settings[static_cast(ReloadLibraryDependenciesSettings::PackageExcludeRegex)].param; metaInfoLock.unlock(); const auto &packageExcludeRegexValue = findSetting(packageExcludeRegexSetting); auto packageExcludeRegex = std::regex(); if (!packageExcludeRegexValue.empty()) { try { packageExcludeRegex = std::regex(packageExcludeRegexValue); } catch (const std::regex_error &e) { reportError(argsToString("configured package exclude regex is invalid: ", e.what())); return; } } // initialize m_remainingPackages = 0; auto configReadLock = init(BuildActionAccess::ReadConfig, RequiredDatabases::MaybeDestination, RequiredParameters::MaybePackages); if (std::holds_alternative(configReadLock)) { return; } // use cache directory from global configuration auto buildLock = m_setup.building.lockToRead(); m_cacheDir = m_setup.building.packageCacheDir + '/'; m_packageDownloadSizeLimit = m_setup.building.packageDownloadSizeLimit; buildLock.unlock(); // find relevant databases and packages m_buildAction->appendOutput(Phrases::SuccessMessage, "Finding relevant databases/packages ...\n"); m_relevantPackagesByDatabase.reserve(m_destinationDbs.empty() ? m_setup.config.databases.size() : m_destinationDbs.size()); auto relevantDbs = std::unordered_set(); auto relevantPkgs = std::unordered_map>(); auto missingDeps = LibPkg::DependencySet(); auto visitedPackages = std::unordered_set(); if (m_destinationDbs.empty()) { for (auto &db : m_setup.config.databases) { relevantDbs.emplace(&db); } } else { for (auto *const destinationDb : m_destinationDbs) { if (!relevantDbs.emplace(destinationDb).second || skipDependencies) { continue; } const auto databaseDependencyOrderRes = m_setup.config.computeDatabaseDependencyOrder(*destinationDb); if (std::holds_alternative(databaseDependencyOrderRes)) { m_messages.errors.emplace_back( destinationDb->name % ": unable to consider dependencies: " + std::get(databaseDependencyOrderRes)); continue; } auto &databaseDependencyOrder = std::get>(databaseDependencyOrderRes); for (auto *const destinationDbOrDependency : databaseDependencyOrder) { relevantDbs.emplace(destinationDbOrDependency); } } for (auto *const destinationDb : m_destinationDbs) { destinationDb->allPackages([&, this](LibPkg::StorageID, const std::shared_ptr &package) { m_setup.config.pullDependentPackages(package, relevantDbs, relevantPkgs, missingDeps, visitedPackages); return false; }); } } for (const auto &[dependencyName, dependencyDetail] : missingDeps) { auto packageNames = std::vector(); packageNames.reserve(dependencyDetail.relevantPackages.size()); for (const auto &package : dependencyDetail.relevantPackages) { packageNames.emplace_back(package->name); } m_messages.warnings.emplace_back( "dependency " % dependencyName % " missing, required by " + joinStrings(packageNames, ", ")); } for (auto *const db : relevantDbs) { const auto isDestinationDb = m_destinationDbs.empty() || m_destinationDbs.find(db) != m_destinationDbs.end(); auto &relevantDbInfo = m_relevantPackagesByDatabase.emplace_back(DatabaseToConsider{ .name = db->name, .arch = db->arch }); if (m_buildAction->packageNames.empty()) { db->allPackages([&](LibPkg::StorageID packageID, const std::shared_ptr &package) { // allow aborting the build action if (reportAbortedIfAborted()) { return true; } // skip if package should be excluded if (!packageExcludeRegexValue.empty() && std::regex_match(package->name, packageExcludeRegex)) { m_messages.notes.emplace_back(db->name % '/' % package->name + ": matches exclude regex"); return false; } return addRelevantPackage(packageID, package, db, isDestinationDb, relevantDbInfo, relevantPkgs); }); } else { for (const auto &packageDenotation : m_buildAction->packageNames) { const auto [dbName, dbArch, packageName] = LibPkg::Config::parsePackageDenotation(packageDenotation); if ((!dbName.empty() && dbName != db->name) || (!dbArch.empty() && dbArch != db->arch)) { continue; } auto [packageID, package] = db->findPackageWithID(std::string(packageName)); // FIXME: allow passing string_view here if (!package) { continue; } addRelevantPackage(packageID, package, db, isDestinationDb, relevantDbInfo, relevantPkgs); } } if (reportAbortedIfAborted()) { return; } } configReadLock = std::monostate{}; m_buildAction->appendOutput(Phrases::SubMessage, "Found ", m_remainingPackages.load(), "\n"); // add note about skipped packages if (m_skippingNote.tellp()) { m_skippingNote << ": not required by any destination DB, skipping download"; m_messages.notes.emplace_back(m_skippingNote.str()); m_skippingNote = std::stringstream(); } // stop here if no relevant packages were found if (m_relevantPackagesByDatabase.empty() || !m_remainingPackages) { conclude(); return; } downloadPackagesFromMirror(); } bool ReloadLibraryDependencies::addRelevantPackage(LibPkg::StorageID packageID, const std::shared_ptr &package, const LibPkg::Database *db, bool isDestinationDb, DatabaseToConsider &relevantDbInfo, std::unordered_map> &relevantPkgs) { // skip if the package info is missing (we need the binary package's file name here) const auto &packageInfo = package->packageInfo; if (!packageInfo) { m_messages.errors.emplace_back(db->name % '/' % package->name + ": no package info"); return false; } // skip the package if it is not part of the destination DB or required by a package of the destination DB if (!isDestinationDb && relevantPkgs.find(packageID) == relevantPkgs.end()) { if (m_skippingNote.tellp()) { m_skippingNote << ", "; } m_skippingNote << db->name << '/' << package->name; return false; } // find the package on disk; otherwise add an URL to download it from the configured mirror std::string path, url, cachePath; std::error_code ec; const auto &fileName = packageInfo->fileName; const auto &arch = package->arch; if (!db->localPkgDir.empty() && std::filesystem::file_size(cachePath = db->localPkgDir % '/' + fileName, ec) && !ec) { path = std::move(cachePath); } else if (std::filesystem::file_size(cachePath = m_cacheDir + fileName, ec) && !ec) { path = std::move(cachePath); } else if (std::filesystem::file_size(cachePath = m_cacheDir % arch % '/' + fileName, ec) && !ec) { path = std::move(cachePath); } else { for (const auto &possibleCachePath : m_setup.config.packageCacheDirs) { if (std::filesystem::file_size(path = possibleCachePath % '/' + fileName, ec) && !ec) { break; } path.clear(); } } if (path.empty() && !db->mirrors.empty()) { const auto &mirror = db->mirrors.front(); // just use the first mirror for now if (startsWith(mirror, "file:")) { std::error_code ecCanonical; const auto canonPath = std::filesystem::canonical(argsToString(std::string_view(mirror.data() + 5, mirror.size() - 5), '/', fileName), ecCanonical); if (!ecCanonical) { path = canonPath.string(); } } else { path = std::move(cachePath); url = mirror % (endsWith(mirror, "/") ? std::string() : "/") + fileName; } } if (path.empty()) { m_messages.errors.emplace_back(db->name % '/' % package->name + ": binary package not found and no mirror configured"); return false; } // skip if the package info has already been loaded from package contents and the present binary package is not newer auto lastModified = DateTime(); if (url.empty()) { lastModified = LibPkg::lastModified(path); if (!m_force && package->origin == LibPkg::PackageOrigin::PackageContents && package->timestamp >= lastModified) { m_messages.notes.emplace_back(db->name % '/' % package->name % ": skipping because \"" % path % "\" is newer (" % package->timestamp.toString() % " >= " % lastModified.toString() + ")\n"); return false; } } // add the full path to the binary package to relevant packages auto &relevantPkg = relevantDbInfo.packages.emplace_back(PackageToConsider{ .path = std::move(path), .url = std::move(url), .lastModified = lastModified }); // create a temporary package object to hold the info parsed from the .PKGINFO file relevantPkg.info.name = package->name; // -> assign certain fields which are used by addDepsAndProvidesFromOtherPackage() to check whether the packages are matching relevantPkg.info.version = package->version; relevantPkg.info.packageInfo = std::make_optional(); relevantPkg.info.packageInfo->buildDate = package->packageInfo->buildDate; // -> gather source info such as make and check dependencies as well relevantPkg.info.sourceInfo = std::make_optional(); ++m_remainingPackages; return false; } void LibRepoMgr::ReloadLibraryDependencies::downloadPackagesFromMirror() { // prepare caching data std::size_t packagesWhichNeedCaching = 0; for (const auto &db : m_relevantPackagesByDatabase) { for (const auto &pkg : db.packages) { if (!pkg.url.empty()) { auto &cachingData = m_cachingData[db.name][pkg.info.name]; cachingData.url = pkg.url; cachingData.destinationFilePath = pkg.path; ++packagesWhichNeedCaching; } } } // skip caching if not required if (!packagesWhichNeedCaching) { loadPackageInfoFromContents(); return; } // allow aborting the build action if (reportAbortedIfAborted()) { return; } m_buildAction->appendOutput(Phrases::SuccessMessage, "Downloading ", packagesWhichNeedCaching, " binary packages from mirror ...\n"); auto session = std::make_shared(m_cachingData, m_setup.building.ioContext, m_setup.webServer.sslContext, std::bind(&ReloadLibraryDependencies::loadPackageInfoFromContents, this)); session->aborted = &m_buildAction->aborted(); WebClient::cachePackages( m_buildAction->log(), std::move(session), m_packageDownloadSizeLimit ? std::make_optional(m_packageDownloadSizeLimit) : std::nullopt); } void ReloadLibraryDependencies::loadPackageInfoFromContents() { // allow aborting the build action if (reportAbortedIfAborted()) { return; } // load info from package contents utilizing hardware concurrency std::mutex nextPackageMutex, submitErrorMutex, submitWarningMutex; auto dbIterator = m_relevantPackagesByDatabase.begin(), dbEnd = m_relevantPackagesByDatabase.end(); auto pkgIterator = dbIterator->packages.begin(), pkgEnd = dbIterator->packages.end(); m_buildAction->appendOutput(Phrases::SuccessMessage, "Parsing ", m_remainingPackages.load(), " binary packages ...\n"); const auto processPackage = [this, &dbIterator, &dbEnd, &pkgIterator, &pkgEnd, &nextPackageMutex, &submitErrorMutex, &submitWarningMutex] { for (; !m_buildAction->isAborted();) { // get the next package std::unique_lock nextPackagelock(nextPackageMutex); while (pkgIterator == pkgEnd) { if (dbIterator == dbEnd || ++dbIterator == dbEnd) { return; } pkgIterator = dbIterator->packages.begin(); pkgEnd = dbIterator->packages.end(); } const auto ¤tDb = *dbIterator; auto ¤tPkg = *pkgIterator; // increment the current package ++pkgIterator; // allow other threads to get a package as well nextPackagelock.unlock(); // log progress m_buildAction->appendOutput( Phrases::InfoMessage, m_remainingPackages--, " packages remaining to parse, next package: ", currentPkg.path, '\n'); // check whether the package could be cached from the mirror and skip it with an error if not if (!currentPkg.url.empty()) { if (auto db = m_cachingData.find(currentDb.name); db != m_cachingData.end()) { if (auto pkg = db->second.find(currentPkg.info.name); pkg != db->second.end()) { auto &packageCachingInfo = pkg->second; if (!packageCachingInfo.error.empty()) { std::unique_lock submitErrorLock(submitErrorMutex); m_messages.errors.emplace_back(currentDb.name % '/' % currentPkg.info.name % ':' % ' ' + packageCachingInfo.error); continue; } } } } // extract the binary package's files try { auto dllsReferencedByImportLibs = std::set(); LibPkg::walkThroughArchive( currentPkg.path, &LibPkg::Package::isPkgInfoFileOrBinary, [¤tPkg, &dllsReferencedByImportLibs](std::string_view directoryPath, LibPkg::ArchiveFile &&file) { if (directoryPath.empty() && file.name == ".PKGINFO") { currentPkg.info.addInfoFromPkgInfoFile(file.content); return false; } currentPkg.info.addDepsAndProvidesFromContainedFile(directoryPath, file, dllsReferencedByImportLibs); return false; }, [¤tPkg](std::string_view directoryPath) { if (directoryPath.empty()) { return false; } currentPkg.info.addDepsAndProvidesFromContainedDirectory(directoryPath); return false; }); if (auto dllIssues = currentPkg.info.processDllsReferencedByImportLibs(std::move(dllsReferencedByImportLibs)); !dllIssues.empty()) { std::unique_lock submitWarningLock(submitWarningMutex); for (auto &issue : dllIssues) { m_messages.warnings.emplace_back(std::move(issue)); } } currentPkg.info.origin = LibPkg::PackageOrigin::PackageContents; } catch (const std::runtime_error &e) { std::unique_lock submitErrorLock(submitErrorMutex); m_messages.errors.emplace_back(currentDb.name % '/' % currentPkg.info.name % ':' % ' ' + e.what()); } } }; auto threads = std::vector(std::thread::hardware_concurrency() - 1); for (std::thread &t : threads) { t = std::thread(processPackage); } processPackage(); for (std::thread &t : threads) { t.join(); } // store the information in the database m_buildAction->appendOutput(Phrases::SuccessMessage, "Adding parsed information to databases ...\n"); std::size_t counter = 0; for (DatabaseToConsider &relevantDb : m_relevantPackagesByDatabase) { auto configWritelock = m_setup.config.lockToRead(); auto *const db = m_setup.config.findDatabase(relevantDb.name, relevantDb.arch); if (!db) { continue; // the whole database has been removed while we were loading package contents } auto updater = LibPkg::PackageUpdater(*db); for (PackageToConsider &package : relevantDb.packages) { // skip if package info could not be parsed from package contents if (package.info.origin != LibPkg::PackageOrigin::PackageContents) { continue; } // find the package in the database again const auto [packageID, existingPackage] = updater.findPackageWithID(package.info.name); if (!existingPackage) { continue; // the package has been removed while we were loading package contents } // add the dependencies/provides to the existing package if (!existingPackage->addDepsAndProvidesFromOtherPackage(package.info)) { continue; // the package does no longer match what's in the database } // update timestamp so we can skip this package on the next run if (existingPackage->timestamp < package.lastModified) { existingPackage->timestamp = package.lastModified; } // add the new dependencies on database-level updater.update(existingPackage); ++counter; } updater.commit(); } m_buildAction->appendOutput(Phrases::SuccessMessage, "Added dependency information for ", counter, " packages\n"); conclude(); } void ReloadLibraryDependencies::conclude() { if (reportAbortedIfAborted()) { return; } const auto result = m_messages.errors.empty() ? BuildActionResult::Success : BuildActionResult::Failure; const auto buildActionWriteLock = m_setup.building.lockToWrite(); m_buildAction->resultData = std::move(m_messages); reportResult(result); } } // namespace LibRepoMgr