diff --git a/alpm/mingwbundle.cpp b/alpm/mingwbundle.cpp index 49f3dd6..b6f948e 100644 --- a/alpm/mingwbundle.cpp +++ b/alpm/mingwbundle.cpp @@ -91,9 +91,15 @@ void MingwBundle::addDependencies(const Package *pkg) enum class RelevantFileType { Binary, + Data, Translation, QtTranslation, QtPlugin, + Plugin, + SharedData, + GLib2Data, + GLib2Schemas, + Theme, IconTheme, ConfigFile }; @@ -122,6 +128,7 @@ struct RelevantFile } } } + QString name; QByteArray data; RelevantFileType fileType; @@ -136,6 +143,7 @@ struct PkgFileInfo path(path), failure(false) {} + QString name; QString path; unique_ptr archive; @@ -143,15 +151,17 @@ struct PkgFileInfo bool failure; }; -void addEntries(PkgFileInfo &pkgFileInfo, RelevantFileType fileType, const KArchiveDirectory *dir, const QString &relPath = QString()) +void addEntries(PkgFileInfo &pkgFileInfo, RelevantFileType fileType, const KArchiveDirectory *dir, const QString &relPath = QString(), const QString &ext = QString(), RelevantFileArch relevantArch = RelevantFileArch::Any) { - QString newPath = relPath.isEmpty() ? dir->name() : relPath % QChar('/') % dir->name(); + const QString newPath = relPath.isEmpty() ? dir->name() : relPath % QChar('/') % dir->name(); for(const auto &entryName : dir->entries()) { if(auto *entry = dir->entry(entryName)) { if(entry->isDirectory()) { - addEntries(pkgFileInfo, fileType, static_cast(entry), newPath); + addEntries(pkgFileInfo, fileType, static_cast(entry), newPath, ext, relevantArch); } else if(entry->isFile()) { - pkgFileInfo.relevantFiles.emplace_back(static_cast(entry), dir, fileType, RelevantFileArch::Any, newPath); + if(ext.isEmpty() || entry->name().endsWith(ext)) { + pkgFileInfo.relevantFiles.emplace_back(static_cast(entry), dir, fileType, relevantArch, newPath); + } } } } @@ -165,10 +175,14 @@ void getFiles(PkgFileInfo &pkgFileInfo) make_pair(RelevantFileArch::x86_64, QStringLiteral("/usr/x86_64-w64-mingw32")), make_pair(RelevantFileArch::i686, QStringLiteral("/usr/i686-w64-mingw32")) }; + // add files for each architecture separately for(const auto &root : roots) { + // get root entry const auto *rootEntry = pkgFileInfo.archive->directory()->entry(root.second); if(rootEntry && rootEntry->isDirectory()) { const auto *rootDir = static_cast(rootEntry); + + // binary directory: *.exe and *.dll files required const auto *binEntry = rootDir->entry(QStringLiteral("bin")); if(binEntry && binEntry->isDirectory()) { const auto *binDir = static_cast(binEntry); @@ -182,8 +196,11 @@ void getFiles(PkgFileInfo &pkgFileInfo) } } } + + // library directory: Qt, GTK and application specific libs/plugins required const auto *libEntry = rootDir->entry(QStringLiteral("lib")); if(libEntry && libEntry->isDirectory()) { + // Qt 5 plugins const auto *libDir = static_cast(libEntry); const auto *qtEntry = libDir->entry("qt"); if(qtEntry && qtEntry->isDirectory()) { @@ -206,7 +223,38 @@ void getFiles(PkgFileInfo &pkgFileInfo) } } } + // Qt 4 plugins + qtEntry = libDir->entry(QStringLiteral("qt4")); + if(qtEntry && qtEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::Plugin, static_cast(qtEntry), QString(), QString(), root.first); + } + + // GTK plugins + const auto *gtkEntry = libDir->entry(QStringLiteral("gtk-2.0")); + if(gtkEntry && gtkEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::Plugin, static_cast(gtkEntry), QString(), QStringLiteral(".dll"), root.first); + } + gtkEntry = libDir->entry(QStringLiteral("gtk-3.0")); + if(gtkEntry && gtkEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::Plugin, static_cast(gtkEntry), QString(), QStringLiteral(".dll"), root.first); + } + + // app specific libs/plugins + const auto *evinceEntry = libDir->entry(pkgFileInfo.name); + if(evinceEntry && evinceEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::Plugin, static_cast(evinceEntry), QString(), QStringLiteral(".dll"), root.first); + // required by evince + addEntries(pkgFileInfo, RelevantFileType::Plugin, static_cast(evinceEntry), QString(), QStringLiteral(".evince-backend"), root.first); + } } + + // data directory: required by geany + const auto *dataEntry = rootDir->entry(QStringLiteral("data")); + if(dataEntry && dataEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::Data, static_cast(dataEntry), QString(), QString(), root.first); + } + + // shared directory: translations, themes, icons, schemas const auto *shareEntry = rootDir->entry(QStringLiteral("share")); if(shareEntry && shareEntry->isDirectory()) { const auto *shareDir = static_cast(shareEntry); @@ -227,16 +275,46 @@ void getFiles(PkgFileInfo &pkgFileInfo) } } } + + const auto *themesEntry = shareDir->entry(QStringLiteral("themes")); + if(themesEntry && themesEntry->isDirectory()) { + const auto *themesDir = static_cast(themesEntry); + for(const auto &themeName : themesDir->entries()) { + const auto *themeEntry = themesDir->entry(themeName); + if(themeEntry && themeEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::Theme, static_cast(themeEntry), QString(), QString(), root.first); + } + } + } + const auto *iconsEntry = shareDir->entry(QStringLiteral("icons")); if(iconsEntry && iconsEntry->isDirectory()) { const auto *iconsDir = static_cast(iconsEntry); for(const auto &themeName : iconsDir->entries()) { const auto *themeEntry = iconsDir->entry(themeName); if(themeEntry && themeEntry->isDirectory()) { - addEntries(pkgFileInfo, RelevantFileType::IconTheme, static_cast(themeEntry)); + addEntries(pkgFileInfo, RelevantFileType::IconTheme, static_cast(themeEntry), QString(), QString(), root.first); } } } + + const auto *glib2Entry = shareDir->entry(QStringLiteral("glib-2.0")); + if(glib2Entry && glib2Entry->isDirectory()) { + const auto *glib2Dir = static_cast(glib2Entry); + for(const auto &glib2SubEntryName : glib2Dir->entries()) { + const auto *glib2SubEntry = glib2Dir->entry(glib2SubEntryName); + if(glib2SubEntry->isDirectory()) { + if(glib2SubEntry->name() == QLatin1String("schemas")) { + addEntries(pkgFileInfo, RelevantFileType::GLib2Schemas, static_cast(glib2SubEntry), QStringLiteral("glib-2.0"), QString(), root.first); + } else { + addEntries(pkgFileInfo, RelevantFileType::GLib2Data, static_cast(glib2SubEntry), QStringLiteral("glib-2.0"), QString(), root.first); + } + } else if(glib2SubEntry->isFile()) { + pkgFileInfo.relevantFiles.emplace_back(static_cast(glib2SubEntry), glib2Dir, RelevantFileType::GLib2Data, root.first, QStringLiteral("glib-2.0")); + } + } + } + if(pkgFileInfo.name.compare(QLatin1String("qt"))) { const auto *appEntry = shareDir->entry(pkgFileInfo.name); if(appEntry && appEntry->isDirectory()) { @@ -285,9 +363,16 @@ void getFiles(PkgFileInfo &pkgFileInfo) } } + + const auto *localeEntry = shareDir->entry(QStringLiteral("locale")); + if(localeEntry && localeEntry->isDirectory()) { + addEntries(pkgFileInfo, RelevantFileType::SharedData, static_cast(localeEntry), QString(), QString(), root.first); + } } } } + + // icons from regular package const auto *iconsEntry = pkgFileInfo.archive->directory()->entry(QStringLiteral("usr/share/icons")); if(iconsEntry && iconsEntry->isDirectory()) { const auto *iconsDir = static_cast(iconsEntry); @@ -305,6 +390,7 @@ void getFiles(PkgFileInfo &pkgFileInfo) void makeArchive(const list &pkgFiles, const QByteArray &pkgList, const QByteArray &indexFile, RelevantFileArch arch, const QString &root, const string &targetDir, const string &targetName, const string &targetFormat) { + // prepare archive QString targetPath = qstr(targetDir) % QChar('/') % root % QChar('-') % qstr(targetName) % QChar('.') % qstr(targetFormat); cerr << shchar << "Making archive \"" << targetPath.toLocal8Bit().data() << "\" ..." << endl; unique_ptr targetArchive; @@ -317,20 +403,63 @@ void makeArchive(const list &pkgFiles, const QByteArray &pkgList, c } else { throw runtime_error("Specified archive format \"" + targetFormat + "\" is unknown."); } + + // add files to archive if(targetArchive->open(QIODevice::WriteOnly)) { - // add note + // -> compile glib2 schemas and add + vector glib2SchemaFiles; + glib2SchemaFiles.reserve(16); + for(const auto &pkgFile : pkgFiles) { + for(const RelevantFile &relevantFile : pkgFile.relevantFiles) { + if(relevantFile.arch == RelevantFileArch::Any || relevantFile.arch == arch) { + switch(relevantFile.fileType) { + case RelevantFileType::GLib2Schemas: + glib2SchemaFiles.push_back(&relevantFile); + break; + default: + ; + } + } + } + } + if(!glib2SchemaFiles.empty()) { + QTemporaryDir tempDir; + if(tempDir.isValid()) { + for(const RelevantFile *schemaFile : glib2SchemaFiles) { + QFile file(tempDir.path() % QChar('/') % schemaFile->name); + if(!file.open(QFile::WriteOnly) || !file.write(schemaFile->data)) { + cerr << shchar << "Unable to create glib2 schema file \"" << schemaFile->name.toLocal8Bit().data() << "\"." << endl; + } + } + QProcess compiler; + compiler.setProcessChannelMode(QProcess::ForwardedChannels); + compiler.start(QStringLiteral("glib-compile-schemas"), QStringList() << tempDir.path()); + compiler.waitForFinished(); + QFile compiledSchemas(tempDir.path() + QStringLiteral("/gschemas.compiled")); + if(compiledSchemas.open(QFile::ReadOnly)) { + targetArchive->writeFile(root + QStringLiteral("/share/glib-2.0/schemas/gschemas.compiled"), compiledSchemas.readAll()); + } else { + cerr << shchar << "Unable to compile glib2 schemas: Compiled schemas (gschemas.compiled) not found." << endl; + } + + } else { + cerr << shchar << "Unable to compile glib2 schemas: Can't create temp dir." << endl; + } + } + + // -> add note targetArchive->writeFile(root + QStringLiteral("/note.txt"), QByteArray("This archive has been created with Martchus' repository indexing tool.\n" "List of included packages: var/lib/repoindex/packages.list\n" "More info: http://martchus.netai.net/page.php?name=programming")); - // add package list + // -> add package list if(!pkgList.isEmpty()) { targetArchive->writeFile(root + QStringLiteral("/var/lib/repoindex/packages.list"), pkgList); } - // set default icon theme + // -> set default icon theme if(!indexFile.isEmpty()) { targetArchive->writeFile(root + QStringLiteral("/share/icons/default/index.theme"), indexFile); } - // add relevant files from packages + // -> add relevant files from packages for(const auto &pkgFile : pkgFiles) { for(const RelevantFile &relevantFile : pkgFile.relevantFiles) { if(relevantFile.arch == RelevantFileArch::Any || relevantFile.arch == arch) { @@ -341,6 +470,10 @@ void makeArchive(const list &pkgFiles, const QByteArray &pkgList, c path = root % QStringLiteral("/bin/") % relevantFile.name; mode = 0100755; break; + case RelevantFileType::Data: + path = root % QStringLiteral("/") % relevantFile.subDir % QChar('/') % relevantFile.name; + mode = 0100644; + break; case RelevantFileType::Translation: path = root % QStringLiteral("/share/") % pkgFile.name % QStringLiteral("/translations/") % relevantFile.name; mode = 0100644; @@ -353,6 +486,20 @@ void makeArchive(const list &pkgFiles, const QByteArray &pkgList, c path = root % QStringLiteral("/bin/") % relevantFile.subDir % QChar('/') % relevantFile.name; mode = 0100755; break; + case RelevantFileType::Plugin: + path = root % QStringLiteral("/lib/") % relevantFile.subDir % QChar('/') % relevantFile.name; + mode = 0100755; + break; + case RelevantFileType::SharedData: + case RelevantFileType::GLib2Data: + case RelevantFileType::GLib2Schemas: + path = root % QStringLiteral("/share/") % relevantFile.subDir % QChar('/') % relevantFile.name; + mode = 0100644; + break; + case RelevantFileType::Theme: + path = root % QStringLiteral("/share/themes/") % relevantFile.subDir % QChar('/') % relevantFile.name; + mode = 0100644; + break; case RelevantFileType::IconTheme: path = root % QStringLiteral("/share/icons/") % relevantFile.subDir % QChar('/') % relevantFile.name; mode = 0100644; diff --git a/alpm/package.cpp b/alpm/package.cpp index cfaebc9..1abba91 100644 --- a/alpm/package.cpp +++ b/alpm/package.cpp @@ -123,6 +123,9 @@ void Package::computeRequiredBy(Manager &manager) m_requiredByComputed = true; } +/*! + * \brief Checks whether the specified \a name and the specified \a version match the specified \a dependency. + */ bool Package::matches(const QString &name, const QString &version, const Dependency &dependency) { if(name == dependency.name) { @@ -147,6 +150,25 @@ bool Package::matches(const QString &name, const QString &version, const Depende return false; } +/*! + * \brief Checks whether the package matches the specified \a dependency. + */ +bool Package::matches(const Dependency &dependency) +{ + // check whether package matches "directly" + if(matches(name(), version(), dependency)) { + return true; + } else { + // check whether at least one of the provides matches + for(const auto &provide : provides()) { + if(matches(provide.name, provide.version, dependency)) { + return true; + } + } + return false; + } +} + /*! * \cond */ @@ -663,6 +685,24 @@ Dependency::Dependency(const QString &dependency) } } +QString Dependency::toString() const +{ + switch(mode) { + case ALPM_DEP_MOD_EQ: + return name % QChar('=') % version; + case ALPM_DEP_MOD_GE: + return name % QChar('>') % QChar('=') % version; + case ALPM_DEP_MOD_LE: + return name % QChar('<') % QChar('=') % version; + case ALPM_DEP_MOD_GT: + return name % QChar('>') % version; + case ALPM_DEP_MOD_LT: + return name % QChar('<') % version; + default: + return name; + } +} + /*! * \brief Returns a JSON object for the current instance. */ @@ -690,6 +730,8 @@ QJsonObject Dependency::toJson() const case ALPM_DEP_MOD_LT: obj.insert(QStringLiteral("mod"), QStringLiteral("lt")); break; + default: + ; } if(!description.isEmpty()) { obj.insert(QStringLiteral("desc"), description); diff --git a/alpm/package.h b/alpm/package.h index c88656e..8304748 100644 --- a/alpm/package.h +++ b/alpm/package.h @@ -81,6 +81,9 @@ public: explicit Dependency(const QString &name, const QString &version, _alpm_depmod_t mode = ALPM_DEP_MOD_ANY, const QString &description = QString()); explicit Dependency(const QString &dependency); + bool operator ==(const Dependency &other) const; + + QString toString() const; QJsonObject toJson() const; QString name; @@ -96,6 +99,16 @@ inline Dependency::Dependency(const QString &name, const QString &version, _alpm description(description) {} +inline bool Dependency::operator ==(const Dependency &other) const +{ + return other.name == name && other.description == description && other.mode == mode; +} + +inline uint qHash(const Dependency &dependency, uint seed) +{ + return qHash(dependency.name, seed) ^ qHash(dependency.version, seed) ^ qHash(dependency.mode, seed) ^ qHash(dependency.description, seed); +} + class Manager; class Package @@ -160,6 +173,7 @@ public: ChronoUtilities::DateTime lastModified() const; const QString &tarUrl() const; const std::map &sourceFiles() const; + QList *> allDependencies() const; // version comparsion PackageVersionComparsion compareVersion(const Package *syncPackage) const; @@ -651,6 +665,14 @@ inline const std::map &Package::sourceFiles() const return m_sourceFiles; } +/*! + * \brief Returns all dependencies. + */ +inline QList *> Package::allDependencies() const +{ + return {&dependencies(), &makeDependencies(), &checkDependencies()}; +} + /*! * \brief Compares the version of the package with the specified sync package. */ @@ -667,14 +689,6 @@ inline PackageVersionComparsion Package::compareVersion(const Dependency &depend return PackageVersion(version()).compare(PackageVersion(dependency.version)); } -/*! - * \brief Checks whether the package matches the specified \a dependency. - */ -inline bool Package::matches(const Dependency &dependency) -{ - return matches(name(), version(), dependency); -} - } #endif // ALPM_PACKAGE_H diff --git a/alpm/repository.cpp b/alpm/repository.cpp index 7e8a4b9..7b0f0b3 100644 --- a/alpm/repository.cpp +++ b/alpm/repository.cpp @@ -160,13 +160,6 @@ const Package *Repository::packageProviding(const Dependency &dependency) const if(entry.second->matches(dependency)) { // check whether package matches "directly" return entry.second.get(); - } else { - // check whether at least one of the provides matches - for(const auto &provide : entry.second->provides()) { - if(Package::matches(provide.name, provide.version, dependency)) { - return entry.second.get(); - } - } } } return nullptr; @@ -182,13 +175,6 @@ Package *Repository::packageProviding(const Dependency &dependency) if(entry.second->matches(dependency)) { // check whether package matches "directly" return entry.second.get(); - } else { - // check whether at least one of the provides matches - for(auto &provide : entry.second->provides()) { - if(Package::matches(provide.name, provide.version, dependency)) { - return entry.second.get(); - } - } } } return nullptr; @@ -202,16 +188,7 @@ QList Repository::packagesProviding(const Dependency &dependenc QList res; for(const auto &entry : m_packages) { if(entry.second->matches(dependency)) { - // check whether package matches "directly" res << entry.second.get(); - } else { - // check whether at least one of the provides matches - for(const auto &provide : entry.second->provides()) { - if(Package::matches(provide.name, provide.version, dependency)) { - res << entry.second.get(); - break; - } - } } } return res; diff --git a/alpm/resolvebuildorder.cpp b/alpm/resolvebuildorder.cpp index 55c6893..97091d5 100644 --- a/alpm/resolvebuildorder.cpp +++ b/alpm/resolvebuildorder.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -18,14 +19,17 @@ 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(QString name, bool onlyDependency = false, const QList &deps = QList()); + TaskInfo(const QString &name, bool onlyDependency = false, const QSet &deps = QSet()); const QString &name() const; void setName(const QString &name); - const QList deps() const; + const QSet deps() const; void addDep(TaskInfo *dep); bool isDone() const; bool isVisited() const; @@ -33,23 +37,29 @@ public: void setIsOnlyDependency(bool isOnlyDependency); bool isPackageRequested() const; void setPackageRequested(); + QSet requiredFor() const; + void addRequiredFor(const Dependency &dependency); void add(QList &results); Package *associatedPackage() const; - void associatePackage(Package *package); + 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; - QList m_deps; + QSet m_deps; bool m_done; bool m_visited; bool m_onlyDep; bool m_pkgRequested; + QSet m_requiredFor; Package *m_associatedPackage; }; -inline TaskInfo::TaskInfo(QString name, bool onlyDependency, const QList &deps) : +/*! + * \brief Constructs a new task. + */ +inline TaskInfo::TaskInfo(const QString &name, bool onlyDependency, const QSet &deps) : m_name(name), m_deps(deps), m_done(false), @@ -59,6 +69,13 @@ inline TaskInfo::TaskInfo(QString name, bool onlyDependency, const QList TaskInfo::deps() const +/*! + * \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 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 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()) { @@ -133,18 +209,57 @@ void TaskInfo::add(QList &results) } } +/*! + * \brief Returns the package associated via associatePackage(). + */ inline Package *TaskInfo::associatedPackage() const { return m_associatedPackage; } -void TaskInfo::associatePackage(Package *package) +/*! + * \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) { @@ -152,6 +267,10 @@ void TaskInfo::addAll(const QList &tasks, QList &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) { @@ -162,104 +281,53 @@ TaskInfo *TaskInfo::find(const QList &tasks, const QString &name) return nullptr; } -template -class DestroyList -{ -public: - DestroyList(ListType &list) : - m_list(list) - {} +/*! + * \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 + */ - ~DestroyList() - { - qDeleteAll(m_list); - } - -private: - ListType &m_list; -}; - -BuildOrderResolver::BuildOrderResolver(Manager &manager, const StringVector &packages, bool addSourceOnlyDeps) : +/*! + * \brief Creates a new BuildOrderResolver using the specified \a manager. + */ +BuildOrderResolver::BuildOrderResolver(Manager &manager, bool addSourceOnlyDeps) : m_manager(manager), m_finder(nullptr), - m_addSourceOnlyDeps(addSourceOnlyDeps) + m_addSourceOnlyDeps(addSourceOnlyDeps), + m_hasFinished(false) +{} + +/*! + * \brief Adds the specified \a errorMessage. + */ +void BuildOrderResolver::addError(const QString &errorMessage) { - cerr << shchar << "Getting package information ..." << endl; - m_tasks.clear(); - m_tasks.reserve(packages.size() * 2); - // add a task for each specified package - for(const auto &pkgName : packages) { - m_tasks << new TaskInfo(QString::fromLocal8Bit(pkgName.data())); + if(!m_errorMessage.isEmpty()) { + m_errorMessage.append(QStringLiteral(" \u2192 ")); } + m_errorMessage.append(errorMessage); } +/*! + * \brief Destroys the object. + */ BuildOrderResolver::~BuildOrderResolver() { qDeleteAll(m_tasks); } -void BuildOrderResolver::cli() -{ - if(m_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)); - findDependencies(); -} - +/*! + * \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) { - addDependenciesToTask(m_tasks.at(i)); - } - // request dependencies to be requested - requestDependenciesToBeRequested(); -} - -QStringList BuildOrderResolver::resultNames() -{ - QStringList names; - names.reserve(m_results.size()); - for(const auto *res : m_results) { - names << res->name(); - } - return names; -} - -void BuildOrderResolver::printRelevantPackages() -{ - cerr << shchar << "Relevant packages:"; - for(const auto *task : m_tasks) { - cerr << ' ' << task->name().toLocal8Bit().data(); - } - cerr << endl; -} - -void BuildOrderResolver::printResults() -{ - if(useShSyntax) { - Utilities::printBashArray(cout, "REPOINDEX_RESULTS", resultNames()); - } else { - Utilities::printValues(cout, "Results", resultNames()); - } -} - -void BuildOrderResolver::resolve() -{ - try { - m_results.clear(); - m_results.reserve(m_tasks.size()); - TaskInfo::addAll(m_tasks, m_results); - emit resolvingFinished(); - } catch(const TaskInfo &cyclic) { - emit resolvingFailed(QStringLiteral("Can't resolve build order; the package ") % cyclic.name() % QStringLiteral(" is a cyclic dependency.")); - } -} - -void BuildOrderResolver::addRequestedPackages() { // find specified packages and their dependencies for(int i = 0, size = m_tasks.size(); i != size; ++i) { @@ -271,6 +339,71 @@ void BuildOrderResolver::addRequestedPackages() 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) { Package *pkg; @@ -283,7 +416,7 @@ Package *BuildOrderResolver::findPackageForDependency(const Dependency &dependen } /*! - * \brief Finds the package for the specified \a task and then adds dependencies. + * \brief Finds the package for the specified \a task and then adds dependencies recursively. */ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task) { @@ -294,17 +427,31 @@ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task) } Dependency dep(task->name(), QString()); if(const auto pkg = findPackageForDependency(dep)) { - task->associatePackage(pkg); - if(m_addSourceOnlyDeps && pkg->repository()->isSourceOnly()) { - task->setIsOnlyDependency(false); + task->addRequiredFor(dep); + if(task->associatePackage(pkg)) { + if(m_addSourceOnlyDeps && pkg->repository()->isSourceOnly()) { + task->setIsOnlyDependency(false); + } + // add dependencies to task + 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; } - // add dependencies to task - addDependenciesToTask(task, pkg->dependencies()); } else if(!task->isPackageRequested()) { task->setPackageRequested(); m_dependenciesToBeRequested << dep; } else { - emit resolvingFailed(QStringLiteral("The specified package \"") % task->name() % QStringLiteral("\" could not be found.")); + addError(QStringLiteral("The specified package \"") % task->name() % QStringLiteral("\" could not be found.")); + m_hasFinished = true; + emit resolvingFailed(errorMessage()); return false; } return true; @@ -312,38 +459,53 @@ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task) /*! * \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) +bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task, const QList *> &dependencies) { - for(auto &dep : dependencies) { - const auto depPkg = findPackageForDependency(dep); - const QString taskName = depPkg ? depPkg->name() : dep.name; - auto *depTask = TaskInfo::find(m_tasks, taskName); - if(depTask) { - // we've already added a task for this dependency - // -> add dependency task to the dependencies of "parent" task - task->addDep(depTask); - } else { - // create new task - m_tasks << (depTask = new TaskInfo(taskName, true)); - // adds dependency task to the dependencies of "parent" task - task->addDep(depTask); - if(depPkg) { - depTask->associatePackage(depPkg); - if(m_addSourceOnlyDeps && depPkg->repository()->isSourceOnly()) { - depTask->setIsOnlyDependency(false); - } - // add dependencies of the dependency - addDependenciesToTask(depTask, depPkg->dependencies()); - } - } - if(!depPkg) { - if(!depTask->isPackageRequested()) { - depTask->setPackageRequested(); - m_dependenciesToBeRequested << dep; + for(auto *deps : dependencies) { + for(auto &dep : *deps) { + auto *depPkg = findPackageForDependency(dep); + const QString taskName = depPkg ? depPkg->name() : dep.name; + auto *depTask = TaskInfo::find(m_tasks, dep.name); + bool newTask; + if(depTask) { + // we've already added a task for this dependency + newTask = false; } else { - emit resolvingFailed(QStringLiteral("The specified package \"") % task->name() % QStringLiteral("\" could not be found.")); - return false; + // 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(m_addSourceOnlyDeps && depPkg->repository() && depPkg->repository()->isSourceOnly()) { + depTask->setIsOnlyDependency(false); + } + if(newTask) { + // add dependencies of the dependency + 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; + } + } } } } @@ -375,10 +537,10 @@ void BuildOrderResolver::requestDependenciesToBeRequested() // add results if(m_finder->areAllResultsAvailable()) { // results are immediately available (already cached) - addRequestedPackages(); + findDependencies(); } else { // need to request actually - connect(m_finder.get(), &PackageFinder::resultsAvailable, this, &BuildOrderResolver::addRequestedPackages); + connect(m_finder.get(), &PackageFinder::resultsAvailable, this, &BuildOrderResolver::findDependencies); } } else { @@ -387,5 +549,50 @@ void BuildOrderResolver::requestDependenciesToBeRequested() } } +/*! + * \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 StringVector &packages, bool addSourceOnlyDeps) : + BuildOrderResolver(manager, addSourceOnlyDeps) +{ + cerr << shchar << "Getting package information ..." << endl; + tasks().clear(); + tasks().reserve(packages.size() * 2); + // add a task for each specified package + for(const auto &pkgName : packages) { + tasks() << new TaskInfo(QString::fromLocal8Bit(pkgName.data())); + } +} + +/*! + * \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 diff --git a/alpm/resolvebuildorder.h b/alpm/resolvebuildorder.h index ec811a3..dc9f854 100644 --- a/alpm/resolvebuildorder.h +++ b/alpm/resolvebuildorder.h @@ -21,15 +21,14 @@ class BuildOrderResolver : public QObject { Q_OBJECT public: - BuildOrderResolver(Manager &manager, const ApplicationUtilities::StringVector &packages, bool addSourceOnlyDeps = false); ~BuildOrderResolver(); - void cli(); - - void findDependencies(); QStringList resultNames(); + bool hasFinished() const; + const QString &errorMessage() const; public slots: + void findDependencies(); void printRelevantPackages(); void printResults(); void resolve(); @@ -39,13 +38,16 @@ signals: void resolvingFinished(); void resolvingFailed(const QString &message); -private slots: - void addRequestedPackages(); +protected: + BuildOrderResolver(Manager &manager, bool addSourceOnlyDeps = false); + Manager &manager(); + QList &tasks(); private: + void addError(const QString &errorMessage); Package *findPackageForDependency(const Dependency &dependency); bool addDependenciesToTask(TaskInfo *task); - bool addDependenciesToTask(TaskInfo *task, const QList &dependencies); + bool addDependenciesToTask(TaskInfo *task, const QList *> &dependencies); void requestDependenciesToBeRequested(); Manager &m_manager; @@ -54,6 +56,49 @@ private: std::unique_ptr m_finder; QList m_results; bool m_addSourceOnlyDeps; + bool m_hasFinished; + QString m_errorMessage; +}; + +/*! + * \brief Returns whether adding dependencies/resolving the build order has been finished. + * \remarks Also true when an error occured. In this case errorMessage() is not empty. + */ +inline bool BuildOrderResolver::hasFinished() const +{ + return m_hasFinished; +} + +/*! + * \brief Returns the error message if an error occured. + */ +inline const QString &BuildOrderResolver::errorMessage() const +{ + return m_errorMessage; +} + +/*! + * \brief Returns the manager used to find packages. + */ +inline Manager &BuildOrderResolver::manager() +{ + return m_manager; +} + +/*! + * \brief Returns the tasks. + */ +inline QList &BuildOrderResolver::tasks() +{ + return m_tasks; +} + +class BuildOrderResolverCli : public BuildOrderResolver +{ + Q_OBJECT +public: + BuildOrderResolverCli(Manager &manager, const ApplicationUtilities::StringVector &packages, bool addSourceOnlyDeps = false); + int exec(); }; } // namespace PackageManagement diff --git a/main.cpp b/main.cpp index d1d29fd..ea84a66 100644 --- a/main.cpp +++ b/main.cpp @@ -77,12 +77,8 @@ int main(int argc, char *argv[]) // run Qt loop return application.exec(); } else if(configArgs.buildOrderArg.isPresent()) { - BuildOrderResolver resolver(manager, configArgs.buildOrderArg.values(), configArgs.addSourceOnlyDeps.isPresent()); - resolver.cli(); - QObject::connect(&resolver, &BuildOrderResolver::resolvingFinished, &application, &QCoreApplication::quit); - QObject::connect(&resolver, &BuildOrderResolver::resolvingFailed, &application, &QCoreApplication::quit); - // run Qt loop - return application.exec(); + BuildOrderResolverCli resolver(manager, configArgs.buildOrderArg.values(), configArgs.addSourceOnlyDeps.isPresent()); + return resolver.exec(); //BuildOrderResolver::printResults(resolver.resolve(configArgs.buildOrderArg.values())); } else if(configArgs.mingwBundleArg.isPresent()) { MingwBundle bundle(manager, configArgs.mingwBundleArg.values(), configArgs.iconThemesArg.values(), configArgs.extraPackagesArg.values()); diff --git a/repoindex.pro b/repoindex.pro index 099cdec..88a20d4 100644 --- a/repoindex.pro +++ b/repoindex.pro @@ -4,7 +4,7 @@ appname = "Repository Index" appauthor = Martchus appurl = "https://github.com/$${appauthor}/$${projectname}" QMAKE_TARGET_DESCRIPTION = "Provides a web interface to browse Arch Linux package repositories." -VERSION = 1.0.0 +VERSION = 0.0.1 # include ../../common.pri when building as part of a subdirs project; otherwise include general.pri !include(../../common.pri) { diff --git a/web/css/core.css b/web/css/core.css index 7b6e94b..0d032db 100644 --- a/web/css/core.css +++ b/web/css/core.css @@ -50,7 +50,7 @@ span.glyphicon { background-color: #f9f9db!important; cursor: pointer; } -#info_container th, #info_container td { +.info-container th, .info-container td { border: none!important; font-size: 90%; padding: 5px; @@ -95,10 +95,10 @@ span.glyphicon { /* * Package info */ -#info_container { +.info-container { margin-left: 5px; } -#info_container { +.info-container { float: none; width: 100%; } @@ -110,6 +110,13 @@ span.glyphicon { .file-tree ul ul { margin-left: 5px; } +#multi_package_info_container > div { + float: left; + width: 350px; +} +#packages_info { + clear: both; +} /* * Tree diff --git a/web/index.html b/web/index.html index 436defb..c762c0a 100644 --- a/web/index.html +++ b/web/index.html @@ -44,7 +44,7 @@ - +