445 lines
21 KiB
C++
445 lines
21 KiB
C++
#include "./mingwbundle.h"
|
|
#include "./utilities.h"
|
|
#include "./manager.h"
|
|
#include "./config.h"
|
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
#include <c++utilities/misc/memory.h>
|
|
|
|
#include <KTar>
|
|
#include <K7Zip>
|
|
#include <KZip>
|
|
|
|
#include <QtConcurrent/QtConcurrent>
|
|
#include <QStringBuilder>
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QFile>
|
|
|
|
#include <string>
|
|
#include <iostream>
|
|
|
|
using namespace std;
|
|
|
|
namespace RepoIndex {
|
|
|
|
using namespace Utilities;
|
|
|
|
const string prefix("mingw-w64-");
|
|
|
|
MingwBundle::MingwBundle(Manager &manager, const ApplicationUtilities::StringVector &packages, const ApplicationUtilities::StringVector &iconPackages, const ApplicationUtilities::StringVector &extraPackages) :
|
|
m_manager(manager),
|
|
m_extraPackages(extraPackages)
|
|
{
|
|
cerr << shchar << "Resolving dependencies ..." << endl;
|
|
string missing;
|
|
// add mingw-w64 packages
|
|
for(const auto &pkgName : packages) {
|
|
bool found = false;
|
|
for(const auto &syncDb : manager.syncDatabases()) {
|
|
const Dependency dep(QString::fromLocal8Bit(ConversionUtilities::startsWith(pkgName, prefix) ? pkgName.data() : (prefix + pkgName).data()));
|
|
if(auto *pkg = syncDb.second->packageProviding(dep)) {
|
|
if(missing.empty()) {
|
|
decltype(m_packages)::value_type entry(syncDb.second.get(), pkg);
|
|
if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) {
|
|
m_packages.emplace_back(entry);
|
|
}
|
|
}
|
|
addDependencies(pkg);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
missing.push_back(' ');
|
|
missing.append(pkgName);
|
|
}
|
|
}
|
|
// add additional icon packages
|
|
for(const auto &pkgName : iconPackages) {
|
|
bool found = false;
|
|
for(const auto &syncDb : manager.syncDatabases()) {
|
|
if(auto *pkg = syncDb.second->packageByName(QString::fromLocal8Bit(pkgName.data()))) {
|
|
if(missing.empty()) {
|
|
decltype(m_packages)::value_type entry(syncDb.second.get(), pkg);
|
|
if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) {
|
|
m_packages.emplace_back(entry);
|
|
}
|
|
}
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
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 auto &pkg : m_packages) {
|
|
cerr << shchar << ' ' << pkg.second->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)) {
|
|
bool found = false;
|
|
for(const auto &syncDbEntry : m_manager.syncDatabases()) {
|
|
if(const auto *pkg = syncDbEntry.second->packageProviding(dep)) {
|
|
if(missing.empty()) {
|
|
decltype(m_packages)::value_type entry(syncDbEntry.second.get(), pkg);
|
|
if(find(m_packages.cbegin(), m_packages.cend(), entry) == m_packages.cend()) {
|
|
m_packages.emplace_back(entry);
|
|
}
|
|
}
|
|
addDependencies(pkg);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
missing.push_back(' ');
|
|
missing.append(dep.name.toLocal8Bit().data());
|
|
}
|
|
}
|
|
}
|
|
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,
|
|
Translation,
|
|
QtTranslation,
|
|
QtPlugin,
|
|
IconTheme,
|
|
ConfigFile
|
|
};
|
|
|
|
enum class RelevantFileArch
|
|
{
|
|
x86_64,
|
|
i686,
|
|
Any
|
|
};
|
|
|
|
struct RelevantFile
|
|
{
|
|
RelevantFile(const KArchiveFile *file, const RelevantFileType type, const RelevantFileArch arch, const QString &subDir = QString()) :
|
|
name(file->name()),
|
|
data(file->data()),
|
|
fileType(type),
|
|
arch(arch),
|
|
subDir(subDir)
|
|
{}
|
|
QString name;
|
|
QByteArray data;
|
|
RelevantFileType fileType;
|
|
RelevantFileArch arch;
|
|
QString subDir;
|
|
};
|
|
|
|
struct PkgFileInfo
|
|
{
|
|
PkgFileInfo(const QString &name, const QString &path) :
|
|
name(name),
|
|
path(path),
|
|
failure(false)
|
|
{}
|
|
QString name;
|
|
QString path;
|
|
unique_ptr<KTar> archive;
|
|
list<RelevantFile> relevantFiles;
|
|
bool failure;
|
|
};
|
|
|
|
void addEntries(PkgFileInfo &pkgFileInfo, RelevantFileType fileType, const KArchiveDirectory *dir, const QString &relPath = QString())
|
|
{
|
|
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<const KArchiveDirectory *>(entry), newPath);
|
|
} else if(entry->isFile()) {
|
|
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(entry), fileType, RelevantFileArch::Any, newPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void getFiles(PkgFileInfo &pkgFileInfo)
|
|
{
|
|
pkgFileInfo.archive = make_unique<KTar>(pkgFileInfo.path);
|
|
if(pkgFileInfo.archive->open(QIODevice::ReadOnly)) {
|
|
static const pair<RelevantFileArch, QString> roots[] = {
|
|
make_pair(RelevantFileArch::x86_64, QStringLiteral("/usr/x86_64-w64-mingw32")),
|
|
make_pair(RelevantFileArch::i686, QStringLiteral("/usr/i686-w64-mingw32"))
|
|
};
|
|
for(const auto &root : roots) {
|
|
const auto *rootEntry = pkgFileInfo.archive->directory()->entry(root.second);
|
|
if(rootEntry && rootEntry->isDirectory()) {
|
|
const auto *rootDir = static_cast<const KArchiveDirectory *>(rootEntry);
|
|
const auto *binEntry = rootDir->entry(QStringLiteral("bin"));
|
|
if(binEntry && binEntry->isDirectory()) {
|
|
const auto *binDir = static_cast<const KArchiveDirectory *>(binEntry);
|
|
for(const auto &entryName : binDir->entries()) {
|
|
if(entryName.endsWith(QLatin1String(".exe")) || entryName.endsWith(QLatin1String(".dll"))) {
|
|
if(const auto *entry = binDir->entry(entryName)) {
|
|
if(entry->isFile()) {
|
|
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(entry), RelevantFileType::Binary, root.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const auto *libEntry = rootDir->entry(QStringLiteral("lib"));
|
|
if(libEntry && libEntry->isDirectory()) {
|
|
const auto *libDir = static_cast<const KArchiveDirectory *>(libEntry);
|
|
const auto *qtEntry = libDir->entry("qt");
|
|
if(qtEntry && qtEntry->isDirectory()) {
|
|
const auto *qtDir = static_cast<const KArchiveDirectory *>(qtEntry);
|
|
const auto *pluginsEntry = qtDir->entry(QStringLiteral("plugins"));
|
|
if(pluginsEntry && pluginsEntry->isDirectory()) {
|
|
const auto *pluginsDir = static_cast<const KArchiveDirectory *>(pluginsEntry);
|
|
for(const auto &pluginCategory : pluginsDir->entries()) {
|
|
const auto *categoryEntry = pluginsDir->entry(pluginCategory);
|
|
if(categoryEntry && categoryEntry->isDirectory()) {
|
|
const auto *categoryDir = static_cast<const KArchiveDirectory *>(categoryEntry);
|
|
for(const auto &entryName : categoryDir->entries()) {
|
|
if(const auto *pluginEntry = categoryDir->entry(entryName)) {
|
|
if(pluginEntry->isFile()) {
|
|
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(pluginEntry), RelevantFileType::QtPlugin, root.first, categoryDir->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const auto *shareEntry = rootDir->entry(QStringLiteral("share"));
|
|
if(shareEntry && shareEntry->isDirectory()) {
|
|
const auto *shareDir = static_cast<const KArchiveDirectory *>(shareEntry);
|
|
const auto *qtEntry = shareDir->entry(QStringLiteral("qt"));
|
|
if(qtEntry && qtEntry->isDirectory()) {
|
|
const auto *qtDir = static_cast<const KArchiveDirectory *>(qtEntry);
|
|
const auto *trEntry = qtDir->entry(QStringLiteral("translations"));
|
|
if(trEntry && trEntry->isDirectory()) {
|
|
const auto trDir = static_cast<const KArchiveDirectory *>(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<const KArchiveFile *>(qmEntry), RelevantFileType::QtTranslation, root.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(pkgFileInfo.name.compare(QLatin1String("qt"))) {
|
|
const auto *appEntry = shareDir->entry(pkgFileInfo.name);
|
|
if(appEntry && appEntry->isDirectory()) {
|
|
const auto *appDir = static_cast<const KArchiveDirectory *>(appEntry);
|
|
for(const auto &entryName : appDir->entries()) {
|
|
const auto *entry = appDir->entry(entryName);
|
|
if(entry->isFile()) {
|
|
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(entry), RelevantFileType::ConfigFile, root.first);
|
|
} else {
|
|
const auto subDir = static_cast<const KArchiveDirectory *>(entry);
|
|
if(entryName == QLatin1String("translations")) {
|
|
for(const auto &entryName : subDir->entries()) {
|
|
if(entryName.endsWith(QLatin1String(".qm"))) {
|
|
if(const auto *qmEntry = subDir->entry(entryName)) {
|
|
if(qmEntry->isFile()) {
|
|
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(qmEntry), RelevantFileType::Translation, root.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
for(const auto &entryName : subDir->entries()) {
|
|
if(const auto *configEntry = subDir->entry(entryName)) {
|
|
if(configEntry->isFile()) {
|
|
pkgFileInfo.relevantFiles.emplace_back(static_cast<const KArchiveFile *>(configEntry), RelevantFileType::ConfigFile, root.first, subDir->name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
const auto *trEntry = appDir->entry(QStringLiteral("translations"));
|
|
if(trEntry && trEntry->isDirectory()) {
|
|
const auto trDir = static_cast<const KArchiveDirectory *>(trEntry);
|
|
for(const auto &entryName : trDir->entries()) {
|
|
if(entryName.endsWith(QLatin1String(".qm"))) {
|
|
if(const auto *qmEntry = trDir->entry(entryName)) {
|
|
if(qmEntry->isFile()) {
|
|
const auto *qmFile = static_cast<const KArchiveFile *>(qmEntry);
|
|
pkgFileInfo.relevantFiles.emplace_back(qmFile, RelevantFileType::Translation, root.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const auto *iconsEntry = pkgFileInfo.archive->directory()->entry(QStringLiteral("usr/share/icons"));
|
|
if(iconsEntry && iconsEntry->isDirectory()) {
|
|
const auto *iconsDir = static_cast<const KArchiveDirectory *>(iconsEntry);
|
|
for(const auto &themeName : iconsDir->entries()) {
|
|
const auto *themeEntry = iconsDir->entry(themeName);
|
|
if(themeEntry && themeEntry->isDirectory()) {
|
|
addEntries(pkgFileInfo, RelevantFileType::IconTheme, static_cast<const KArchiveDirectory *>(themeEntry));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
pkgFileInfo.failure = true;
|
|
}
|
|
}
|
|
|
|
void makeArchive(const list<PkgFileInfo> &pkgFiles, const QByteArray &pkgList, const QByteArray &indexFile, RelevantFileArch arch, const QString &root, const string &targetDir, const string &targetName, const string &targetFormat)
|
|
{
|
|
QString targetPath = qstr(targetDir) % QChar('/') % root % QChar('-') % qstr(targetName) % QChar('.') % qstr(targetFormat);
|
|
cerr << shchar << "Making archive \"" << targetPath.toLocal8Bit().data() << "\" ..." << endl;
|
|
unique_ptr<KArchive> targetArchive;
|
|
if(targetFormat == "7z") {
|
|
targetArchive = make_unique<K7Zip>(targetPath);
|
|
} else if(targetFormat == "zip") {
|
|
targetArchive = make_unique<KZip>(targetPath);
|
|
} else if(ConversionUtilities::startsWith<string>(targetFormat, "tar")) {
|
|
targetArchive = make_unique<KTar>(targetPath);
|
|
} else {
|
|
throw runtime_error("Specified archive format \"" + targetFormat + "\" is unknown.");
|
|
}
|
|
if(targetArchive->open(QIODevice::WriteOnly)) {
|
|
// add package list
|
|
if(!pkgList.isEmpty()) {
|
|
targetArchive->writeFile(root % QStringLiteral("/var/lib/repoindex/packages.list"), pkgList, 0100644);
|
|
}
|
|
// set default icon theme
|
|
if(!indexFile.isEmpty()) {
|
|
targetArchive->writeFile(root % QStringLiteral("/share/icons/default/index.theme"), indexFile, 0100644);
|
|
}
|
|
// add relevant files from packages
|
|
for(const auto &pkgFile : pkgFiles) {
|
|
for(const RelevantFile &relevantFile : pkgFile.relevantFiles) {
|
|
if(relevantFile.arch == RelevantFileArch::Any || relevantFile.arch == arch) {
|
|
switch(relevantFile.fileType) {
|
|
case RelevantFileType::Binary:
|
|
targetArchive->writeFile(root % QStringLiteral("/bin/") % relevantFile.name, relevantFile.data, 0100755);
|
|
break;
|
|
case RelevantFileType::Translation:
|
|
targetArchive->writeFile(root % QStringLiteral("/share/") % pkgFile.name % QStringLiteral("/translations/") % relevantFile.name, relevantFile.data, 0100644);
|
|
break;
|
|
case RelevantFileType::QtTranslation:
|
|
targetArchive->writeFile(root % QStringLiteral("/share/qt/translations/") % relevantFile.name, relevantFile.data, 0100644);
|
|
break;
|
|
case RelevantFileType::QtPlugin:
|
|
targetArchive->writeFile(root % QStringLiteral("/bin/") % relevantFile.subDir % QChar('/') % relevantFile.name, relevantFile.data, 0100755);
|
|
break;
|
|
case RelevantFileType::IconTheme:
|
|
targetArchive->writeFile(root % QStringLiteral("/share/icons/") % relevantFile.subDir % QChar('/') % relevantFile.name, relevantFile.data, 0100644);
|
|
break;
|
|
case RelevantFileType::ConfigFile:
|
|
if(relevantFile.subDir.isEmpty()) {
|
|
targetArchive->writeFile(root % QStringLiteral("/share/") % pkgFile.name % QChar('/') % relevantFile.name, relevantFile.data, 0100644);
|
|
} else {
|
|
targetArchive->writeFile(root % QStringLiteral("/share/") % pkgFile.name % QChar('/') % relevantFile.subDir % QChar('/') % relevantFile.name, relevantFile.data, 0100644);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} 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<PkgFileInfo> pkgFiles;
|
|
for(const auto &entry : m_packages) {
|
|
QString pkgFile;
|
|
if(!entry.first->packagesDirectory().isEmpty()) {
|
|
pkgFile = entry.first->packagesDirectory() % QChar('/') % entry.second->fileName();
|
|
}
|
|
if(pkgFile.isEmpty() || !QFile::exists(pkgFile)) {
|
|
if(!m_manager.pacmanCacheDir().isEmpty()) {
|
|
pkgFile = m_manager.pacmanCacheDir() % QChar('/') % entry.second->fileName();
|
|
}
|
|
if(pkgFile.isEmpty() || !QFile::exists(pkgFile)) {
|
|
throw runtime_error("The package file " + string(entry.second->fileName().toLocal8Bit().data()) + " can't be found.");
|
|
// TODO: download package from mirror
|
|
}
|
|
}
|
|
pkgFiles.emplace_back(entry.second->name().startsWith(QLatin1String("mingw-w64-")) ? entry.second->name().mid(10) : entry.second->name(), pkgFile);
|
|
}
|
|
for(const auto &pkgFileStdStr : m_extraPackages) {
|
|
QString pkgFile = QString::fromLocal8Bit(pkgFileStdStr.data());
|
|
if(QFile::exists(pkgFile)) {
|
|
const auto pkg = m_manager.packageFromFile(pkgFileStdStr.data()); // do not catch the exception here
|
|
pkgFiles.emplace_back(pkg->name().startsWith(QLatin1String("mingw-w64-")) ? pkg->name().mid(10) : pkg->name(), pkgFile);
|
|
} else {
|
|
throw runtime_error("The specified extra package \"" + pkgFileStdStr + "\" 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 auto &entry : m_packages) {
|
|
pkgArray << entry.second->basicInfo(true);
|
|
}
|
|
QJsonDocument pkgList;
|
|
pkgList.setArray(pkgArray);
|
|
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
|
|
|