File indexing completed on 2024-05-19 15:46:01
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"