#include "./mingwbundle.h" #include "./utilities.h" #include "./manager.h" #include "./config.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace ApplicationUtilities; namespace RepoIndex { using namespace Utilities; const char *prefix = "mingw-w64-"; size_t prefixLen = 10; IncludedPackage::IncludedPackage(const Package *package, bool dependencyOnly) : package(package), dependencyOnly(dependencyOnly) {} bool IncludedPackage::operator==(const Package *package) const { return this->package == package; } MingwBundle::MingwBundle(Manager &manager, const std::vector &packages, const std::vector &iconPackages, const std::vector &extraPackages, const std::vector &qtPlugins) : m_manager(manager), m_extraPackages(extraPackages), m_qtPlugins(qtPlugins) { cerr << shchar << "Resolving dependencies ..." << endl; string missing; // add mingw-w64 packages for(const char *pkgName : packages) { if(auto *pkg = manager.packageProviding(Dependency(QString::fromLocal8Bit(strncmp(pkgName, prefix, prefixLen) ? pkgName : (prefix + string(pkgName)).data())))) { if(find(m_packages.cbegin(), m_packages.cend(), pkg) == m_packages.cend()) { m_packages.emplace_back(pkg, false); addDependencies(pkg); } } else { missing.push_back(' '); missing.append(pkgName); } } // add additional icon packages for(const auto &pkgName : iconPackages) { if(auto *pkg = manager.packageProviding(Dependency(QString::fromLocal8Bit(pkgName)))) { if(find(m_packages.cbegin(), m_packages.cend(), pkg) == m_packages.cend()) { m_packages.emplace_back(pkg, false); } } else { missing.push_back(' '); missing.append(pkgName); } } if(!missing.empty()) { throw runtime_error("The following packages can not be found:" + missing); } else { cerr << shchar << "Adding the following packages:"; for(const IncludedPackage &pkg : m_packages) { cerr << shchar << ' ' << pkg.package->name().toLocal8Bit().data(); } cerr << shchar << endl; } } void MingwBundle::addDependencies(const Package *pkg) { string missing; for(const auto &dep : pkg->dependencies()) { if(dep.name.startsWith(QLatin1String("mingw-w64-"), Qt::CaseInsensitive)) { if(auto *pkg = m_manager.packageProviding(dep)) { if(find(m_packages.cbegin(), m_packages.cend(), pkg) == m_packages.cend()) { m_packages.emplace_back(pkg, true); addDependencies(pkg); } } else { missing.push_back(' '); missing.append(dep.name.toLocal8Bit()); } } } if(!missing.empty()) { throw runtime_error("The following dependencies of the " + string(pkg->name().toLocal8Bit().data()) + " package can not be resolved:" + missing); } } enum class RelevantFileType { Binary, Data, Translation, QtTranslation, QtPlugin, Plugin, SharedData, GLib2Data, GLib2Schemas, Theme, IconTheme }; enum class RelevantFileArch { x86_64, i686, Any }; struct RelevantFile { RelevantFile(const KArchiveFile *file, const KArchiveDirectory *containingDir, const RelevantFileType type, const RelevantFileArch arch, const QString &subDir = QString()) : name(file->name()), fileType(type), arch(arch), subDir(subDir) { if(file->symLinkTarget().isEmpty()) { data = file->data(); } else if(const auto *targetEntry = containingDir->entry(file->symLinkTarget())) { // Windows support for symlinks is not very well if(targetEntry->isFile()) { data = static_cast(targetEntry)->data(); } } } QString name; QByteArray data; RelevantFileType fileType; RelevantFileArch arch; QString subDir; }; struct PkgFileInfo { PkgFileInfo(const QString &name, const QString &path, bool dependencyOnly = false, const vector &qtPlugins = vector()) : name(name), path(path), dependencyOnly(dependencyOnly), qtPlugins(qtPlugins), failure(false) {} QString name; QString path; unique_ptr archive; list relevantFiles; bool dependencyOnly; const vector &qtPlugins; bool failure; }; void addEntries(PkgFileInfo &pkgFileInfo, RelevantFileType fileType, const KArchiveDirectory *dir, const QString &relPath = QString(), const QString &ext = QString(), RelevantFileArch relevantArch = RelevantFileArch::Any) { 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, ext, relevantArch); } else if(entry->isFile()) { if(ext.isEmpty() || entry->name().endsWith(ext)) { pkgFileInfo.relevantFiles.emplace_back(static_cast(entry), dir, fileType, relevantArch, newPath); } } } } } void getFiles(PkgFileInfo &pkgFileInfo) { pkgFileInfo.archive = make_unique(pkgFileInfo.path); if(pkgFileInfo.archive->open(QIODevice::ReadOnly)) { static const pair roots[] = { 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); for(const QString &entryName : binDir->entries()) { if((!pkgFileInfo.dependencyOnly && entryName.endsWith(QLatin1String(".exe"))) || entryName.indexOf(QLatin1String(".dll")) > 0) { if(const auto *entry = binDir->entry(entryName)) { // do only copy files and no symlinks if(entry->isFile() && entry->symLinkTarget().isEmpty()) { pkgFileInfo.relevantFiles.emplace_back(static_cast(entry), binDir, RelevantFileType::Binary, root.first); } } } } } // 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(QStringLiteral("qt")); if(qtEntry && qtEntry->isDirectory()) { const auto *qtDir = static_cast(qtEntry); const auto *pluginsEntry = qtDir->entry(QStringLiteral("plugins")); if(pluginsEntry && pluginsEntry->isDirectory()) { const auto *pluginsDir = static_cast(pluginsEntry); for(const auto &pluginCategory : pluginsDir->entries()) { const auto *categoryEntry = pluginsDir->entry(pluginCategory); if(categoryEntry && categoryEntry->isDirectory()) { const auto *categoryDir = static_cast(categoryEntry); for(const QString &entryName : categoryDir->entries()) { if(const auto *pluginEntry = categoryDir->entry(entryName)) { if(pluginEntry->isFile()) { if(entryName.endsWith(QLatin1String(".dll"))) { if(!pkgFileInfo.qtPlugins.empty()) { string pluginName = entryName.toLocal8Bit().data(); pluginName.resize(pluginName.size() - 4); if(find(pkgFileInfo.qtPlugins.cbegin(), pkgFileInfo.qtPlugins.cend(), pluginName) == pkgFileInfo.qtPlugins.cend()) { continue; } } pkgFileInfo.relevantFiles.emplace_back(static_cast(pluginEntry), categoryDir, RelevantFileType::QtPlugin, root.first, categoryDir->name()); } } } } } } } } // 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); } // share directory: translations, themes, icons, schemas const auto *shareEntry = rootDir->entry(QStringLiteral("share")); if(shareEntry && shareEntry->isDirectory()) { const auto *shareDir = static_cast(shareEntry); // Qt translations const auto *qtEntry = shareDir->entry(QStringLiteral("qt")); if(qtEntry && qtEntry->isDirectory()) { const auto *qtDir = static_cast(qtEntry); const auto *trEntry = qtDir->entry(QStringLiteral("translations")); if(trEntry && trEntry->isDirectory()) { const auto trDir = static_cast(trEntry); for(const auto &entryName : trDir->entries()) { if(entryName.endsWith(QLatin1String(".qm"))) { if(const auto *qmEntry = trDir->entry(entryName)) { if(qmEntry->isFile()) { pkgFileInfo.relevantFiles.emplace_back(static_cast(qmEntry), trDir, RelevantFileType::QtTranslation, root.first); } } } } } } // GTK themes 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); } } } // icon themes 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), QString(), QString(), root.first); } } } // glib2 stuff (need to compile glib2 schemes later) 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")); } } } // application specific stuff (Qt stuff is added separately, so skip Qt here) if(pkgFileInfo.name.compare(QLatin1String("qt"))) { const auto *appEntry = shareDir->entry(pkgFileInfo.name); if(appEntry && appEntry->isDirectory()) { addEntries(pkgFileInfo, RelevantFileType::SharedData, static_cast(appEntry), QString(), QString(), root.first); } } 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); for(const auto &themeName : iconsDir->entries()) { const auto *themeEntry = iconsDir->entry(themeName); if(themeEntry && themeEntry->isDirectory()) { addEntries(pkgFileInfo, RelevantFileType::IconTheme, static_cast(themeEntry)); } } } } else { if(pkgFileInfo.archive->device()) { cerr << shchar << "Error: Unable to open the archive " << pkgFileInfo.path.toLocal8Bit().data() << ": " << pkgFileInfo.archive->device()->errorString().toLocal8Bit().data() << endl; } else { cerr << shchar << "Error: Unable to open the archive " << pkgFileInfo.path.toLocal8Bit().data() << ": Device hasn't been created." << endl; } pkgFileInfo.failure = true; } } 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; if(targetFormat == "7z") { targetArchive = make_unique(targetPath); } else if(targetFormat == "zip") { targetArchive = make_unique(targetPath); } else if(ConversionUtilities::startsWith(targetFormat, "tar")) { targetArchive = make_unique(targetPath); } else { throw runtime_error("Specified archive format \"" + targetFormat + "\" is unknown."); } // add files to archive if(targetArchive->open(QIODevice::WriteOnly)) { // -> 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 (name, version, license info and upstream URL): var/lib/repoindex/packages.list\n" "More info: http://martchus.netai.net/page.php?name=programming")); // -> add package list if(!pkgList.isEmpty()) { targetArchive->writeFile(root + QStringLiteral("/var/lib/repoindex/packages.list"), pkgList); } // -> set default icon theme if(!indexFile.isEmpty()) { targetArchive->writeFile(root + QStringLiteral("/share/icons/default/index.theme"), indexFile); } // -> add relevant files from packages for(const auto &pkgFile : pkgFiles) { for(const RelevantFile &relevantFile : pkgFile.relevantFiles) { if(relevantFile.arch == RelevantFileArch::Any || relevantFile.arch == arch) { QString path; mode_t mode; switch(relevantFile.fileType) { case RelevantFileType::Binary: 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; break; case RelevantFileType::QtTranslation: path = root % QStringLiteral("/share/qt/translations/") % relevantFile.name; mode = 0100644; break; case RelevantFileType::QtPlugin: 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; break; } // write the file // disable symlinks: there is no decent support for symlinks under Windows //if(relevantFile.symlinkTarget.isEmpty()) { targetArchive->writeFile(path, relevantFile.data, mode); //} else { // targetArchive->writeSymLink(path, relevantFile.symlinkTarget, QString(), QString(), mode); //} } } } } else if(targetArchive->device()) { cerr << shchar << "Error: Unable to open target archive: " << targetArchive->device()->errorString().toLocal8Bit().data() << endl; } else { cerr << shchar << "Error: Unable to open target archive." << endl; } } void MingwBundle::createBundle(const string &targetDir, const string &targetName, const string &targetFormat, const string &defaultIconTheme) const { cerr << shchar << "Gathering relevant files ..." << endl; // get package files list pkgFiles; for(const IncludedPackage &includedPkg : m_packages) { const Package *pkg = includedPkg.package; QString pkgFile; if(!pkg->repository()->packagesDirectory().isEmpty()) { pkgFile = pkg->repository()->packagesDirectory() % QChar('/') % pkg->fileName(); } if(pkgFile.isEmpty() || !QFile::exists(pkgFile)) { if(!m_manager.pacmanCacheDir().isEmpty()) { pkgFile = m_manager.pacmanCacheDir() % QChar('/') % pkg->fileName(); } if(pkgFile.isEmpty() || !QFile::exists(pkgFile)) { throw runtime_error("The package file " + string(pkg->fileName().toLocal8Bit().data()) + " can't be found."); // TODO: download package from mirror } } // strip "mingw-w64-"-prefix and vcs-suffix (if present) QStringRef pkgName = pkg->name().startsWith(QLatin1String("mingw-w64-")) ? pkg->name().midRef(10) : QStringRef(&pkg->name()); if(pkgName.endsWith(QLatin1String("-svn")) || pkgName.endsWith(QLatin1String("-git"))) { pkgName = pkgName.mid(0, pkgName.length() - 4); } else if(pkgName.endsWith(QLatin1String("-hg"))) { pkgName = pkgName.mid(0, pkgName.length() - 3); } pkgFiles.emplace_back(pkgName.toString(), pkgFile, includedPkg.dependencyOnly, m_qtPlugins); } for(const char *pkgFileRawStr : m_extraPackages) { QString pkgFile = QString::fromLocal8Bit(pkgFileRawStr); if(QFile::exists(pkgFile)) { const auto pkg = make_unique(pkgFile); // do not catch the exception here pkgFiles.emplace_back(pkg->name().startsWith(QLatin1String("mingw-w64-")) ? pkg->name().mid(10) : pkg->name(), pkgFile, false, m_qtPlugins); } else { throw runtime_error("The specified extra package \"" + string(pkgFileRawStr) + "\" can't be found."); } } // get relevant files from packages QtConcurrent::blockingMap(pkgFiles, getFiles); // check whether all packages could be opened string failed; for(const auto &pkgFile : pkgFiles) { if(pkgFile.failure) { failed.push_back(' '); failed.append(pkgFile.path.toLocal8Bit().data()); } } if(!failed.empty()) { throw runtime_error("Unable to open the following package files:" + failed); } // make a list with package info to be included in the target archive QJsonArray pkgArray; for(const IncludedPackage &pkg : m_packages) { pkgArray << pkg.package->simpleInfo(); } QJsonDocument pkgList; pkgList.setArray(pkgArray); const QByteArray pkgListBytes = pkgList.toJson(); QByteArray indexFileBytes; if(!defaultIconTheme.empty()) { indexFileBytes.reserve(23 + defaultIconTheme.size()); indexFileBytes.append("[Icon Theme]\nInherits="); indexFileBytes.append(defaultIconTheme.data()); indexFileBytes.append('\n'); } // make target archive auto run1 = QtConcurrent::run(bind(makeArchive, ref(pkgFiles), ref(pkgListBytes), ref(indexFileBytes), RelevantFileArch::x86_64, QStringLiteral("x86_64-w64-mingw32"), ref(targetDir), ref(targetName), ref(targetFormat))); auto run2 = QtConcurrent::run(bind(makeArchive, ref(pkgFiles), ref(pkgListBytes), ref(indexFileBytes), RelevantFileArch::i686, QStringLiteral("i686-w64-mingw32"), ref(targetDir), ref(targetName), ref(targetFormat))); run1.waitForFinished(); run2.waitForFinished(); } } // namespace PackageManagement