#include "./resolvebuildorder.h" #include "./packagefinder.h" #include "./manager.h" #include "./config.h" #include "./utilities.h" #include "./repository.h" #include #include #include #include #include using namespace std; using namespace ApplicationUtilities; namespace RepoIndex { /*! * \brief The TaskInfo class defines a build task and provides the topo-sort required to resolve the build order. */ class TaskInfo { public: TaskInfo(const QString &name, bool onlyDependency = false, const QSet &deps = QSet()); const QString &name() const; void setName(const QString &name); const QSet deps() const; void addDep(TaskInfo *dep); bool areAllDepsAdded() const; void setAllDepsAdded(); bool isDone() const; bool isVisited() const; bool isOnlyDependency() const; void setIsOnlyDependency(bool isOnlyDependency); bool isBinaryAvailable() const; void setBinaryAvailable(bool binaryAvailable); bool isPackageRequested() const; void setPackageRequested(); QSet requiredFor() const; void addRequiredFor(const Dependency &dependency); void add(QList &results); Package *associatedPackage() const; bool associatePackage(Package *package); static void addAll(const QList &tasks, QList &results); static TaskInfo *find(const QList &tasks, const QString &name); private: QString m_name; QSet m_deps; bool m_allDepsAdded; bool m_done; bool m_visited; bool m_onlyDep; bool m_binaryAvailable; bool m_pkgRequested; QSet m_requiredFor; Package *m_associatedPackage; }; /*! * \brief Constructs a new task. */ inline TaskInfo::TaskInfo(const QString &name, bool onlyDependency, const QSet &deps) : m_name(name), m_deps(deps), m_allDepsAdded(false), m_done(false), m_visited(false), m_onlyDep(onlyDependency), m_binaryAvailable(false), m_pkgRequested(false), m_associatedPackage(nullptr) {} /*! * \brief Returns the name of the task. * * This is usually the name which has been specified when constructing * the task. However, the name might be adjusted to the package name * when a package is associated. */ inline const QString &TaskInfo::name() const { return m_name; } inline void TaskInfo::setName(const QString &name) { m_name = name; } /*! * \brief Returns the dependencies added via addDep(). */ inline const QSet TaskInfo::deps() const { return m_deps; } /*! * \brief Adds another task as dependency. */ inline void TaskInfo::addDep(TaskInfo *dep) { m_deps << dep; } /*! * \brief Returns whether setAllDepsAdded() has been called for this task. */ bool TaskInfo::areAllDepsAdded() const { return m_allDepsAdded; } /*! * \brief Sets that all dependencies have been added for this task. */ void TaskInfo::setAllDepsAdded() { m_allDepsAdded = true; } /*! * \brief Returns whether the task already has been added to the resulting list of tasks. * \sa add() */ inline bool TaskInfo::isDone() const { return m_done; } /*! * \brief Returns whether the task has been visited. * \remarks Used to track circular dependencies (within add()). */ inline bool TaskInfo::isVisited() const { return m_visited; } /*! * \brief Returns whether the task is only a dependency and none of the packages to be build. * \remarks Circular dependencies between packages which are only dependencies can be ignored. */ inline bool TaskInfo::isOnlyDependency() const { return m_onlyDep; } /*! * \brief Sets whether the package is only a dependency. * \sa isOnlyDependency() */ inline void TaskInfo::setIsOnlyDependency(bool isOnlyDependency) { m_onlyDep = isOnlyDependency; } /*! * \brief Returns whether a binary for the task is already available (from previous build). * \remarks If a binary is available the resolver won't complain if this package is a * cyclic dependency. */ inline bool TaskInfo::isBinaryAvailable() const { return m_binaryAvailable; } /*! * \brief Sets whether a binary for the task is already available (from previous build). * \sa isBinaryAvailable() */ inline void TaskInfo::setBinaryAvailable(bool binaryAvailable) { m_binaryAvailable = binaryAvailable; } /*! * \brief Returns whether the package for this task has been requested yet. * * Used to avoid requesting the same package twice after the package * couldn't be found when requesting it the first time. */ inline bool TaskInfo::isPackageRequested() const { return m_pkgRequested; } /*! * \brief Sets whether the package has been requested. * * This is set just before a package for this task is requrested. */ inline void TaskInfo::setPackageRequested() { m_pkgRequested = true; } /*! * \brief Returns the dependencies the task has been added for. * \remarks * - The same task can be added for multiple dependencies, eg. * mingw-w64-extra-cmake-modules and mingw-w64-extra-cmake-modules=5.15.0. * - The dependencies must be set explicitely using addRequiredFor(). */ inline QSet TaskInfo::requiredFor() const { return m_requiredFor; } /*! * \brief Adds the specified \a dependency to the list of dependencies the task has been added for. * \sa requiredFor() */ inline void TaskInfo::addRequiredFor(const Dependency &dependency) { m_requiredFor << dependency; } /*! * \brief Adds the task to the specified list of results. Ensures that all dependencies are added before. * \throws If a circular dependency between the build tasks has been detected, the causing task is thrown. * \remarks Dependencies must be added via addDep() before calling this method. */ void TaskInfo::add(QList &results) { if(!isDone()) { if(isVisited()) { // cyclic dependency if(isOnlyDependency() || isBinaryAvailable()) { // if this is only a dependency (which we don't want to build) don't care about it // if there is already a binary (from previous build) don't care about it either return; } else { throw *this; } } else { m_visited = true; } for(auto *dep : deps()) { dep->add(results); } m_done = true; if(!isOnlyDependency()) { results << this; } } } /*! * \brief Returns the package associated via associatePackage(). */ inline Package *TaskInfo::associatedPackage() const { return m_associatedPackage; } /*! * \brief Associates the specified \a package with this build task. * * Does nothing if there is already a package assigned which matches all * dependencies the task has been added for. Returns true in this case. * * Fails if the specified \a package does not match all dependencies the task * has been added for. * * \return Returns whether either the currenlty assigned package or the specified * \a package matches all dependencies the task has been added for. */ bool TaskInfo::associatePackage(Package *package) { // check whether an appropriate package is already assigned // and whether the package matches all dependencies this task is required for bool assignmentRequired = !m_associatedPackage, assignmentPossible = true; for(const Dependency &dep : m_requiredFor) { if(!assignmentRequired && !m_associatedPackage->matches(dep)) { assignmentRequired = true; } if(!package->matches(dep)) { assignmentPossible = false; } } if(!assignmentRequired) { return true; } if(!assignmentPossible) { return false; } m_associatedPackage = package; // update the name to ensure we have the acutal package name and not just a "provides" name m_name = package->name(); return true; } /*! * \brief Adds the specified \a tasks and their dependencies to the specified list of results. Ensures that all * tasks are added in the right order. * \throws If a circular dependency between the build tasks has been detected, the causing task is thrown. * \remarks Dependencies must be added to the \a tasks via addDep() before calling this method. */ void TaskInfo::addAll(const QList &tasks, QList &results) { for(auto *task : tasks) { task->add(results); } } /*! * \brief Finds the task with the specified \a name in the specified list of \a tasks. * \remarks Does not search dependencies the list does not contain. */ TaskInfo *TaskInfo::find(const QList &tasks, const QString &name) { for(auto *task : tasks) { if(task->name() == name) { return task; } } return nullptr; } /*! * \class BuildOrderResolver * \brief The BuildOrderResolver class resolves the build order for a specified array of packages. * * Resolving the build order is done in the following steps: * 1. instantiation: initial tasks are added * 2. findDependencies(): dependencies for each task are added, some packages might have to be requested first * 3. if some packages must be requested: wait for requested packages, if requested packages are available, go to 2. * 4. all dependencies are added: signal ready() is emitted * 5. resolve(): build order is resolved, either resolvingFinished() or resolvingFailed() is emitted */ /*! * \brief Creates a new BuildOrderResolver using the specified \a manager. */ BuildOrderResolver::BuildOrderResolver(Manager &manager, bool addSourceOnlyDeps, bool requireSources) : m_manager(manager), m_finder(nullptr), m_addSourceOnlyDeps(addSourceOnlyDeps), m_requireSources(requireSources), m_hasFinished(false) {} /*! * \brief Adds the specified \a errorMessage. */ void BuildOrderResolver::addError(const QString &errorMessage) { if(!m_errorMessage.isEmpty()) { m_errorMessage.append(QStringLiteral(" \u2192 ")); } m_errorMessage.append(errorMessage); } /*! * \brief Destroys the object. */ BuildOrderResolver::~BuildOrderResolver() { qDeleteAll(m_tasks); } /*! * \brief Finds the dependencies of all packages. * * Also internally called after requested packages are available. */ void BuildOrderResolver::findDependencies() { // find specified packages and their dependencies for(int i = 0, size = m_tasks.size(); i != size; ++i) { if(!addDependenciesToTask(m_tasks[i])) { return; } } // request dependencies to be requested requestDependenciesToBeRequested(); } /*! * \brief Returns the resulting build order. * \remarks Build order must have been resolved yet. */ QStringList BuildOrderResolver::resultNames() { QStringList names; names.reserve(m_results.size()); for(const auto *res : m_results) { names << res->name(); } return names; } /*! * \brief Prints relevant packages. * \remarks All dependencies must have been found yet. */ void BuildOrderResolver::printRelevantPackages() { cerr << shchar << "Relevant packages:"; for(const auto *task : m_tasks) { cerr << ' ' << task->name().toLocal8Bit().data(); } cerr << endl; } /*! * \brief Prints the resulting build order. * \remarks Build order must have been resolved yet. */ void BuildOrderResolver::printResults() { if(useShSyntax) { Utilities::printBashArray(cout, "REPOINDEX_RESULTS", resultNames()); } else { Utilities::printValues(cout, "Results", resultNames()); } } /*! * \brief Resolves the build order. * \remarks All dependencies must have been found yet. * * Emits either resolvingFinished() or resolvingFailed() depending on whether * the operation succeeded. */ void BuildOrderResolver::resolve() { try { m_results.clear(); m_results.reserve(m_tasks.size()); TaskInfo::addAll(m_tasks, m_results); m_hasFinished = true; emit resolvingFinished(); } catch(const TaskInfo &cyclic) { addError(QStringLiteral("Can't resolve build order; the package ") % cyclic.name() % QStringLiteral(" is a cyclic dependency.")); m_hasFinished = true; emit resolvingFailed(m_errorMessage); } } /*! * \brief Returns the package for the specified \a dependency or nullptr if no package could be found. */ Package *BuildOrderResolver::findPackageForDependency(const Dependency &dependency, bool sourceRequired) { Package *pkg; if(!sourceRequired && (pkg = m_manager.packageProviding(dependency))) { return pkg; } else if(m_finder && (pkg = m_finder->packageProviding(dependency))) { return pkg; } return nullptr; } /*! * \brief Finds the package for the specified \a task and then adds dependencies recursively. */ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task) { // check whether dependencies have already been added if(task->areAllDepsAdded()) { // package associated -> dependencies already added return true; } Dependency dep(task->name(), QString()); if(const auto pkg = findPackageForDependency(dep, m_requireSources && !task->isOnlyDependency())) { task->addRequiredFor(dep); if(task->associatePackage(pkg)) { if(pkg->repository()->isSourceOnly()) { if(m_addSourceOnlyDeps) { task->setIsOnlyDependency(false); } } else { task->setBinaryAvailable(true); } // add dependencies to task task->setAllDepsAdded(); if(!addDependenciesToTask(task, pkg->allDependencies())) { addError(QStringLiteral("Can not add dependencies of the dependency \"") % dep.toString() % QStringLiteral("\".")); m_hasFinished = true; emit resolvingFailed(errorMessage()); return false; } } else { addError(QStringLiteral("Can not associate the task \"") % task->name() % QStringLiteral("\" with the package required by dependency \"") % dep.toString() % QStringLiteral("\".")); m_hasFinished = true; emit resolvingFailed(errorMessage()); return false; } } else if(!task->isPackageRequested()) { task->setPackageRequested(); m_dependenciesToBeRequested << dep; } else { addError(QStringLiteral("The specified package \"") % task->name() % QStringLiteral("\" could not be found.")); m_hasFinished = true; emit resolvingFailed(errorMessage()); return false; } return true; } /*! * \brief Finds packages for the the specified \a dependencies and adds them to the specified \a task. * \remarks Adds dependencies of the dependencies recursively. */ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task, const QList *> &dependencies) { for(auto *deps : dependencies) { for(auto &dep : *deps) { auto *depTask = TaskInfo::find(m_tasks, dep.name); auto *depPkg = findPackageForDependency(dep, m_requireSources && depTask && !depTask->isOnlyDependency()); const QString taskName = depPkg ? depPkg->name() : dep.name; bool newTask; if(depTask) { // we've already added a task for this dependency newTask = false; } else { // create new task m_tasks << (depTask = new TaskInfo(taskName, true)); newTask = true; } // add dependency task to the dependencies of "parent" task task->addDep(depTask); depTask->addRequiredFor(dep); if(depPkg) { if(depTask->associatePackage(depPkg)) { if(depPkg->repository()->isSourceOnly()) { if(m_addSourceOnlyDeps) { depTask->setIsOnlyDependency(false); } } else { depTask->setBinaryAvailable(true); } if(!depTask->areAllDepsAdded()) { // add dependencies of the dependency depTask->setAllDepsAdded(); if(!addDependenciesToTask(depTask, depPkg->allDependencies())) { addError(QStringLiteral("Can not add dependencies of the dependency \"") % dep.toString() % QStringLiteral("\".")); return false; } } } else { addError(QStringLiteral("Can not associate the task \"") % depTask->name() % QStringLiteral("\" with the package required by dependency \"") % dep.toString() % QStringLiteral("\".")); return false; } } else { if(!m_dependenciesToBeRequested.contains(dep)) { if(!depTask->isPackageRequested()) { depTask->setPackageRequested(); m_dependenciesToBeRequested << dep; } else { addError(QStringLiteral("The specified package \"") % dep.toString() % QStringLiteral("\" could not be found.")); return false; } } } } } return true; } /*! * \brief Requests dependencies to be requested. */ void BuildOrderResolver::requestDependenciesToBeRequested() { // check whether there are dependencies to be requested if(!m_dependenciesToBeRequested.isEmpty()) { // print dependencies to be requested if(m_manager.config().isVerbose()) { cerr << shchar << "Dependencies to be requested:"; for(const Dependency &dep : m_dependenciesToBeRequested) { cerr << ' ' << dep.name.toStdString(); } cerr << endl; } // do requests (using package finder) m_finder = make_unique(m_manager, m_dependenciesToBeRequested, true); // all dependencies requested -> clear dependencies to be requested m_dependenciesToBeRequested.clear(); // add results if(m_finder->areAllResultsAvailable()) { // results are immediately available (already cached) findDependencies(); } else { // need to request actually connect(m_finder.get(), &PackageFinder::resultsAvailable, this, &BuildOrderResolver::findDependencies); } } else { // there are no dependencies to be requested -> ready to resolve build order emit ready(); } } /*! * \class BuildOrderResolverCli * \brief The BuildOrderResolverCli class resolves the build order of the specified packages and prints the results stdout. */ /*! * \brief Creates a new BuildOrderResolverCli for the specified \a packages using the specified \a manager. */ BuildOrderResolverCli::BuildOrderResolverCli(Manager &manager, const std::vector &packages, bool addSourceOnlyDeps, bool requireSources) : BuildOrderResolver(manager, addSourceOnlyDeps, requireSources) { cerr << shchar << "Getting package information ..." << endl; tasks().clear(); tasks().reserve(packages.size() * 2); // add a task for each specified package for(const char *pkgName : packages) { tasks() << new TaskInfo(QString::fromLocal8Bit(pkgName)); } } /*! * \brief Determines dependencies, prints relevant packages, resolves build order, prints results/errors. * \remarks Does not return until everything is done. */ int BuildOrderResolverCli::exec() { if(manager().config().isVerbose()) { connect(this, &BuildOrderResolver::ready, this, &BuildOrderResolver::printRelevantPackages); } connect(this, &BuildOrderResolver::ready, this, &BuildOrderResolver::resolve); connect(this, &BuildOrderResolver::resolvingFinished, this, &BuildOrderResolver::printResults); connect(this, &BuildOrderResolver::resolvingFailed, static_cast(Utilities::printError)); QEventLoop loop; connect(this, &BuildOrderResolver::resolvingFinished, &loop, &QEventLoop::quit); connect(this, &BuildOrderResolver::resolvingFailed, &loop, &QEventLoop::quit); findDependencies(); if(hasFinished()) { // resolving might have been finished immidiately return errorMessage().isEmpty(); } else { // resolving not finished yet (deps must be requested) return loop.exec(); } } } // namespace PackageManagement