| 302 | } |
| 303 | |
| 304 | CMakeProjectData parseReplyIndexFile(const ReplyIndex& replyIndex, const Path& sourceDirectory, |
| 305 | const Path& buildDirectory) |
| 306 | { |
| 307 | const auto reply = replyIndex.data.value(QLatin1String("reply")).toObject(); |
| 308 | const auto clientKDevelop = reply.value(QLatin1String("client-kdevelop")).toObject(); |
| 309 | const auto query = clientKDevelop.value(QLatin1String("query.json")).toObject(); |
| 310 | const auto responses = query.value(QLatin1String("responses")).toArray(); |
| 311 | const auto replyDir = toReplyDir(buildDirectory.toLocalFile()); |
| 312 | |
| 313 | StringInterner stringInterner; |
| 314 | PathInterner sourcePathInterner(toCanonical(sourceDirectory)); |
| 315 | PathInterner buildPathInterner(buildDirectory); |
| 316 | |
| 317 | CMakeProjectData codeModel; |
| 318 | QSet<Path> cmakeFiles; |
| 319 | |
| 320 | bool isOutdated = true; // consider the data outdated if the cmakeFiles object is absent or replyIndex is outdated |
| 321 | for (const auto& responseValue : responses) { |
| 322 | const auto response = responseValue.toObject(); |
| 323 | const auto kind = response.value(QLatin1String("kind")); |
| 324 | const auto jsonFile = response.value(QLatin1String("jsonFile")).toString(); |
| 325 | const auto jsonFilePath = replyDir.absoluteFilePath(jsonFile); |
| 326 | if (kind == QLatin1String("codemodel")) { |
| 327 | codeModel = parseCodeModel(parseFile(jsonFilePath), replyDir, |
| 328 | stringInterner, sourcePathInterner, buildPathInterner); |
| 329 | if (!codeModel.compilationData.isValid) { |
| 330 | break; // skip to printing a warning and the early return under the loop |
| 331 | } |
| 332 | } else if (kind == QLatin1String("cmakeFiles")) { |
| 333 | QDateTime lastModifiedCMakeFile; |
| 334 | cmakeFiles = parseCMakeFiles(parseFile(jsonFilePath), sourcePathInterner, &lastModifiedCMakeFile); |
| 335 | qCDebug(CMAKE) << PrintLastModified{"source CMake file", lastModifiedCMakeFile} << "- within" |
| 336 | << buildDirectory; |
| 337 | if (!replyIndex.isOutdated()) { |
| 338 | // KDevelop always writes the API client query file shortly before running the CMake configure step. |
| 339 | // Use its last modified timestamp as the time when the last CMake configure step started. This |
| 340 | // timestamp is normally a slight underestimate. However, when the user runs the CMake configure step |
| 341 | // manually while KDevelop is not running, the API client query file is not touched. Up-to-date project |
| 342 | // data can be marked outdated here in this case. KDevelop would reconfigure CMake needlessly then. |
| 343 | // TODO: determine when the last CMake configure step started more reliably, even if KDevelop is not |
| 344 | // running at the time. KDevelop always writes the same API client query, so the API reply written by |
| 345 | // CMake during the generate step is correct even if KDevelop does not rewrite the query file. If a |
| 346 | // future KDevelop version changes its API client query, the query file contents can be read and |
| 347 | // compared to determine whether the API reply is correct. |
| 348 | isOutdated = replyIndex.queryLastModified < lastModifiedCMakeFile; |
| 349 | if (isOutdated) { |
| 350 | qCDebug(CMAKE) << "API client query file is out of date (last modified before a source CMake file)"; |
| 351 | } |
| 352 | } |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | if (!codeModel.compilationData.isValid) { |
| 357 | qCWarning(CMAKE) << "failed to find code model in reply index" << sourceDirectory << buildDirectory |
| 358 | << replyIndex.data; |
| 359 | return {}; |
| 360 | } |
| 361 | |