File indexing completed on 2024-05-19 04:41:19

0001 /*
0002     SPDX-FileCopyrightText: 2019 Daniel Mensinger <daniel@mensinger-ka.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mesonintrospectjob.h"
0008 
0009 #include "mesonconfig.h"
0010 #include "mesonmanager.h"
0011 #include "mesonoptions.h"
0012 #include <debug.h>
0013 
0014 #include <KLocalizedString>
0015 #include <KProcess>
0016 
0017 #include <QDir>
0018 #include <QJsonArray>
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 #include <QtConcurrentRun>
0022 
0023 using namespace Meson;
0024 using namespace KDevelop;
0025 
0026 MesonIntrospectJob::MesonIntrospectJob(KDevelop::IProject* project, QVector<MesonIntrospectJob::Type> types,
0027                                        MesonIntrospectJob::Mode mode, QObject* parent)
0028     : KJob(parent)
0029     , m_types(types)
0030     , m_mode(mode)
0031     , m_project(project)
0032 {
0033     Q_ASSERT(project);
0034 
0035     if (mode == MESON_FILE) {
0036         // Since we are parsing the meson file in this mode, no build directory
0037         // is required and we have to fake a build directory
0038         m_buildDir.buildDir = project->path();
0039         auto* bsm = project->buildSystemManager();
0040         auto* manager = dynamic_cast<MesonManager*>(bsm);
0041         if (manager) {
0042             m_buildDir.mesonExecutable = manager->findMeson();
0043         }
0044     } else {
0045         m_buildDir = Meson::currentBuildDir(project);
0046     }
0047 
0048     m_projectPath = project->path();
0049     connect(&m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MesonIntrospectJob::finished);
0050 }
0051 
0052 MesonIntrospectJob::MesonIntrospectJob(KDevelop::IProject* project, KDevelop::Path meson,
0053                                        QVector<MesonIntrospectJob::Type> types, QObject* parent)
0054     : KJob(parent)
0055     , m_types(types)
0056     , m_mode(MESON_FILE)
0057     , m_project(project)
0058 {
0059     Q_ASSERT(project);
0060 
0061     // Since we are parsing the meson file in this mode, no build directory
0062     // is required and we have to fake a build directory
0063     m_projectPath = project->path();
0064     m_buildDir.buildDir = m_projectPath;
0065     m_buildDir.mesonExecutable = meson;
0066     connect(&m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MesonIntrospectJob::finished);
0067 }
0068 
0069 MesonIntrospectJob::MesonIntrospectJob(KDevelop::IProject* project, Meson::BuildDir buildDir,
0070                                        QVector<MesonIntrospectJob::Type> types, MesonIntrospectJob::Mode mode,
0071                                        QObject* parent)
0072     : KJob(parent)
0073     , m_types(types)
0074     , m_mode(mode)
0075     , m_buildDir(buildDir)
0076     , m_project(project)
0077 {
0078     Q_ASSERT(project);
0079     m_projectPath = project->path();
0080     connect(&m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MesonIntrospectJob::finished);
0081 }
0082 
0083 QString MesonIntrospectJob::getTypeString(MesonIntrospectJob::Type type) const
0084 {
0085     switch (type) {
0086     case BENCHMARKS:
0087         return QStringLiteral("benchmarks");
0088     case BUILDOPTIONS:
0089         return QStringLiteral("buildoptions");
0090     case BUILDSYSTEM_FILES:
0091         return QStringLiteral("buildsystem_files");
0092     case DEPENDENCIES:
0093         return QStringLiteral("dependencies");
0094     case INSTALLED:
0095         return QStringLiteral("installed");
0096     case PROJECTINFO:
0097         return QStringLiteral("projectinfo");
0098     case TARGETS:
0099         return QStringLiteral("targets");
0100     case TESTS:
0101         return QStringLiteral("tests");
0102     }
0103 
0104     return QStringLiteral("error");
0105 }
0106 
0107 QString MesonIntrospectJob::importJSONFile(const BuildDir& buildDir, MesonIntrospectJob::Type type, QJsonObject* out)
0108 {
0109     QString typeStr = getTypeString(type);
0110     QString fileName = QStringLiteral("intro-") + typeStr + QStringLiteral(".json");
0111     QString infoDir = buildDir.buildDir.toLocalFile() + QStringLiteral("/") + QStringLiteral("meson-info");
0112     QFile introFile(infoDir + QStringLiteral("/") + fileName);
0113 
0114     if (!introFile.exists()) {
0115         return i18n("Introspection file '%1' does not exist", QFileInfo(introFile).canonicalFilePath());
0116     }
0117 
0118     if (!introFile.open(QFile::ReadOnly | QFile::Text)) {
0119         return i18n("Failed to open introspection file '%1'", QFileInfo(introFile).canonicalFilePath());
0120     }
0121 
0122     QJsonParseError error;
0123     QJsonDocument doc = QJsonDocument::fromJson(introFile.readAll(), &error);
0124     if (error.error) {
0125         return i18n("In %1:%2: %3", QFileInfo(introFile).canonicalFilePath(), error.offset, error.errorString());
0126     }
0127 
0128     if (doc.isArray()) {
0129         (*out)[typeStr] = doc.array();
0130     } else if (doc.isObject()) {
0131         (*out)[typeStr] = doc.object();
0132     } else {
0133         return i18n("The introspection file '%1' contains neither an array nor an object",
0134                     QFileInfo(introFile).canonicalFilePath());
0135     }
0136 
0137     return QString();
0138 }
0139 
0140 QString MesonIntrospectJob::importMesonAPI(const BuildDir& buildDir, MesonIntrospectJob::Type type, QJsonObject* out)
0141 {
0142     QString typeStr = getTypeString(type);
0143     QString option = QStringLiteral("--") + typeStr;
0144     option.replace(QLatin1Char('_'), QLatin1Char('-'));
0145 
0146     KProcess proc(this);
0147     proc.setWorkingDirectory(m_projectPath.toLocalFile());
0148     proc.setOutputChannelMode(KProcess::SeparateChannels);
0149     proc.setProgram(buildDir.mesonExecutable.toLocalFile());
0150     proc << QStringLiteral("introspect") << option << QStringLiteral("meson.build");
0151 
0152     int ret = proc.execute();
0153     if (ret != 0) {
0154         return i18n("%1 returned %2", proc.program().join(QLatin1Char(' ')), ret);
0155     }
0156 
0157     QJsonParseError error;
0158     QJsonDocument doc = QJsonDocument::fromJson(proc.readAll(), &error);
0159     if (error.error) {
0160         return i18n("JSON parser error: %1", error.errorString());
0161     }
0162 
0163     if (doc.isArray()) {
0164         (*out)[typeStr] = doc.array();
0165     } else if (doc.isObject()) {
0166         (*out)[typeStr] = doc.object();
0167     } else {
0168         return i18n("The introspection output of '%1' contains neither an array nor an object",
0169                     proc.program().join(QLatin1Char(' ')));
0170     }
0171 
0172     return QString();
0173 }
0174 
0175 QString MesonIntrospectJob::import(BuildDir buildDir)
0176 {
0177     QJsonObject rawData;
0178 
0179     // First load the complete JSON data
0180     for (auto i : m_types) {
0181         QString err;
0182         switch (m_mode) {
0183         case BUILD_DIR:
0184             err = importJSONFile(buildDir, i, &rawData);
0185             break;
0186         case MESON_FILE:
0187             err = importMesonAPI(buildDir, i, &rawData);
0188             break;
0189         }
0190 
0191         if (!err.isEmpty()) {
0192             qCWarning(KDEV_Meson) << "MINTRO: " << err;
0193             setError(true);
0194             setErrorText(err);
0195             return err;
0196         }
0197     }
0198 
0199     auto buildOptionsJSON = rawData[QStringLiteral("buildoptions")];
0200     if (buildOptionsJSON.isArray()) {
0201         m_res_options = std::make_shared<MesonOptions>(buildOptionsJSON.toArray());
0202         if (m_res_options) {
0203             qCDebug(KDEV_Meson) << "MINTRO: Imported " << m_res_options->options().size() << " buildoptions";
0204         } else {
0205             qCWarning(KDEV_Meson) << "MINTRO: Failed to parse buildoptions";
0206         }
0207     }
0208 
0209     auto projectInfoJSON = rawData[QStringLiteral("projectinfo")];
0210     if (projectInfoJSON.isObject()) {
0211         m_res_projectInfo = std::make_shared<MesonProjectInfo>(projectInfoJSON.toObject());
0212         if (!m_res_projectInfo) {
0213             qCWarning(KDEV_Meson) << "MINTRO: Failed to parse projectinfo";
0214         }
0215     }
0216 
0217     auto targetsJSON = rawData[QStringLiteral("targets")];
0218     if (targetsJSON.isArray()) {
0219         m_res_targets = std::make_shared<MesonTargets>(targetsJSON.toArray());
0220     }
0221 
0222     auto testsJSON = rawData[QStringLiteral("tests")];
0223     if (testsJSON.isArray()) {
0224         m_res_tests = std::make_shared<MesonTestSuites>(testsJSON.toArray(), m_project);
0225         if (m_res_tests) {
0226             qCDebug(KDEV_Meson) << "MINTRO: Imported " << m_res_tests->testSuites().size() << " test suites";
0227         } else {
0228             qCWarning(KDEV_Meson) << "MINTRO: Failed to parse tests";
0229         }
0230     }
0231 
0232     return QString();
0233 }
0234 
0235 void MesonIntrospectJob::start()
0236 {
0237     qCDebug(KDEV_Meson) << "MINTRO: Starting meson introspection job";
0238     if (!m_buildDir.isValid()) {
0239         qCWarning(KDEV_Meson) << "The current build directory is invalid";
0240         setError(true);
0241         setErrorText(i18n("The current build directory is invalid"));
0242         emitResult();
0243         return;
0244     }
0245 
0246     auto future = QtConcurrent::run(this, &MesonIntrospectJob::import, m_buildDir);
0247     m_futureWatcher.setFuture(future);
0248 }
0249 
0250 void MesonIntrospectJob::finished()
0251 {
0252     qCDebug(KDEV_Meson) << "MINTRO: Meson introspection job finished";
0253     emitResult();
0254 }
0255 
0256 bool MesonIntrospectJob::doKill()
0257 {
0258     if (m_futureWatcher.isRunning()) {
0259         m_futureWatcher.cancel();
0260     }
0261     return true;
0262 }
0263 
0264 MesonOptsPtr MesonIntrospectJob::buildOptions()
0265 {
0266     return m_res_options;
0267 }
0268 
0269 MesonProjectInfoPtr MesonIntrospectJob::projectInfo()
0270 {
0271     return m_res_projectInfo;
0272 }
0273 
0274 MesonTargetsPtr MesonIntrospectJob::targets()
0275 {
0276     return m_res_targets;
0277 }
0278 
0279 MesonTestSuitesPtr MesonIntrospectJob::tests()
0280 {
0281     return m_res_tests;
0282 }
0283 
0284 #include "moc_mesonintrospectjob.cpp"