diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
index b524e0b04af..157160c33f6 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
@@ -267,140 +267,178 @@ static QString newFilesForFunction(const std::string &cmakeFunction,
return relativeFilePaths(filePaths).join(" ");
}
+static std::optional cmakeFileForBuildKey(const QString &buildKey,
+ const QList &targets)
+{
+ auto target = Utils::findOrDefault(targets,
+ [buildKey](const CMakeBuildTarget &target) {
+ return target.title == buildKey;
+ });
+ if (target.backtrace.isEmpty()) {
+ qCCritical(cmakeBuildSystemLog) << "target.backtrace for" << buildKey << "is empty."
+ << "The location where to add the files is unknown.";
+ return std::nullopt;
+ }
+ return std::make_optional(Link(target.backtrace.last().path, target.backtrace.last().line));
+}
+
+static std::optional getUncachedCMakeListFile(const FilePath &targetCMakeFile)
+{
+ // Have a fresh look at the CMake file, not relying on a cached value
+ Core::DocumentManager::saveModifiedDocumentSilently(
+ Core::DocumentModel::documentForFilePath(targetCMakeFile));
+ expected_str fileContent = targetCMakeFile.fileContents();
+ cmListFile cmakeListFile;
+ std::string errorString;
+ if (fileContent) {
+ fileContent = fileContent->replace("\r\n", "\n");
+ if (!cmakeListFile.ParseString(fileContent->toStdString(),
+ targetCMakeFile.fileName().toStdString(),
+ errorString)) {
+ qCCritical(cmakeBuildSystemLog).noquote() << targetCMakeFile.toUserOutput()
+ << "failed to parse! Error:"
+ << QString::fromStdString(errorString);
+ return std::nullopt;
+ }
+ }
+ return std::make_optional(cmakeListFile);
+}
+
+static std::optional findFunction(
+ const cmListFile &cmakeListFile, std::function pred)
+{
+ auto function = std::find_if(cmakeListFile.Functions.begin(), cmakeListFile.Functions.end(),
+ pred);
+ if (function == cmakeListFile.Functions.end())
+ return std::nullopt;
+ return std::make_optional(*function);
+}
+
+struct SnippetAndLocation
+{
+ QString snippet;
+ long line = -1;
+ long column = -1;
+};
+
+static SnippetAndLocation generateSnippetAndLocationForSources(
+ const QString &newSourceFiles,
+ const cmListFile &cmakeListFile,
+ const cmListFileFunction &function,
+ const QString &targetName)
+{
+ static QSet knownFunctions{"add_executable",
+ "add_library",
+ "qt_add_executable",
+ "qt_add_library",
+ "qt6_add_executable",
+ "qt6_add_library",
+ "qt_add_qml_module",
+ "qt6_add_qml_module"};
+ SnippetAndLocation result;
+ int extraChars = 0;
+ auto afterFunctionLastArgument =
+ [&result, &extraChars, newSourceFiles](const auto &f) {
+ auto lastArgument = f.Arguments().back();
+ result.line = lastArgument.Line;
+ result.column = lastArgument.Column + static_cast(lastArgument.Value.size()) - 1;
+ result.snippet = QString("\n%1").arg(newSourceFiles);
+ // Take into consideration the quotes
+ if (lastArgument.Delim == cmListFileArgument::Quoted)
+ extraChars = 2;
+ };
+ if (knownFunctions.contains(function.LowerCaseName())) {
+ afterFunctionLastArgument(function);
+ } else {
+ const std::string target_name = targetName.toStdString();
+ auto targetSources = [target_name](const auto &func) {
+ return func.LowerCaseName() == "target_sources"
+ && func.Arguments().size() && func.Arguments().front().Value == target_name;
+ };
+ std::optional targetSourcesFunc = findFunction(cmakeListFile,
+ targetSources);
+ if (!targetSourcesFunc.has_value()) {
+ result.line = function.LineEnd() + 1;
+ result.column = 0;
+ result.snippet = QString("\ntarget_sources(%1\n PRIVATE\n %2\n)\n")
+ .arg(targetName)
+ .arg(newSourceFiles);
+ } else {
+ afterFunctionLastArgument(*targetSourcesFunc);
+ }
+ }
+ if (extraChars)
+ result.line += extraChars;
+ return result;
+}
+static expected_str insertSnippetSilently(const FilePath &cmakeFile,
+ const SnippetAndLocation &snippetLocation)
+{
+ BaseTextEditor *editor = qobject_cast(
+ Core::EditorManager::openEditorAt({cmakeFile,
+ int(snippetLocation.line),
+ int(snippetLocation.column)},
+ Constants::CMAKE_EDITOR_ID,
+ Core::EditorManager::DoNotMakeVisible));
+ if (!editor) {
+ return make_unexpected("BaseTextEditor cannot be obtained for " + cmakeFile.toUserOutput()
+ + ":" + QString::number(snippetLocation.line) + ":"
+ + QString::number(snippetLocation.column));
+ }
+ editor->insert(snippetLocation.snippet);
+ editor->editorWidget()->autoIndent();
+ if (!Core::DocumentManager::saveDocument(editor->document()))
+ return make_unexpected("Changes to " + cmakeFile.toUserOutput() + " could not be saved.");
+ return true;
+}
+
bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded)
{
if (notAdded)
*notAdded = filePaths;
if (auto n = dynamic_cast(context)) {
const QString targetName = n->buildKey();
- auto target = Utils::findOrDefault(buildTargets(),
- [targetName](const CMakeBuildTarget &target) {
- return target.title == targetName;
- });
-
- if (target.backtrace.isEmpty()) {
- qCCritical(cmakeBuildSystemLog) << "target.backtrace for" << targetName << "is empty. "
- << "The location where to add the files is unknown.";
+ const std::optional cmakeFile = cmakeFileForBuildKey(targetName, buildTargets());
+ if (!cmakeFile)
return false;
- }
- const FilePath targetCMakeFile = target.backtrace.last().path;
- const int targetDefinitionLine = target.backtrace.last().line;
+ const FilePath targetCMakeFile = cmakeFile->targetFilePath;
+ const int targetDefinitionLine = cmakeFile->targetLine;
- // Have a fresh look at the CMake file, not relying on a cached value
- Core::DocumentManager::saveModifiedDocumentSilently(
- Core::DocumentModel::documentForFilePath(targetCMakeFile));
- expected_str fileContent = targetCMakeFile.fileContents();
- cmListFile cmakeListFile;
- std::string errorString;
- if (fileContent) {
- fileContent = fileContent->replace("\r\n", "\n");
- if (!cmakeListFile.ParseString(fileContent->toStdString(),
- targetCMakeFile.fileName().toStdString(),
- errorString)) {
- qCCritical(cmakeBuildSystemLog).noquote()
- << targetCMakeFile.path() << "failed to parse! Error:"
- << QString::fromStdString(errorString);
- return false;
- }
- }
+ std::optional cmakeListFile = getUncachedCMakeListFile(targetCMakeFile);
+ if (!cmakeListFile)
+ return false;
- auto function = std::find_if(cmakeListFile.Functions.begin(),
- cmakeListFile.Functions.end(),
- [targetDefinitionLine](const auto &func) {
- return func.Line() == targetDefinitionLine;
- });
-
- if (function == cmakeListFile.Functions.end()) {
+ std::optional function
+ = findFunction(*cmakeListFile, [targetDefinitionLine](const auto &func) {
+ return func.Line() == targetDefinitionLine;
+ });
+ if (!function.has_value()) {
qCCritical(cmakeBuildSystemLog) << "Function that defined the target" << targetName
<< "could not be found at" << targetDefinitionLine;
return false;
}
+ const std::string target_name = targetName.toStdString();
+ auto qtAddModule = [target_name](const auto &func) {
+ return (func.LowerCaseName() == "qt_add_qml_module"
+ || func.LowerCaseName() == "qt6_add_qml_module")
+ && func.Arguments().front().Value == target_name;
+ };
// Special case: when qt_add_executable and qt_add_qml_module use the same target name
// then qt_add_qml_module function should be used
- const std::string target_name = targetName.toStdString();
- auto add_qml_module_func
- = std::find_if(cmakeListFile.Functions.begin(),
- cmakeListFile.Functions.end(),
- [target_name](const auto &func) {
- return (func.LowerCaseName() == "qt_add_qml_module"
- || func.LowerCaseName() == "qt6_add_qml_module")
- && func.Arguments().front().Value == target_name;
- });
- if (add_qml_module_func != cmakeListFile.Functions.end())
- function = add_qml_module_func;
+ if (auto preferred = findFunction(*cmakeListFile, qtAddModule); !preferred.has_value())
+ function = preferred;
const QString newSourceFiles = newFilesForFunction(function->LowerCaseName(),
filePaths,
n->filePath().canonicalPath());
- static QSet knownFunctions{"add_executable",
- "add_library",
- "qt_add_executable",
- "qt_add_library",
- "qt6_add_executable",
- "qt6_add_library",
- "qt_add_qml_module",
- "qt6_add_qml_module"};
-
- int line = 0;
- int column = 0;
- int extraChars = 0;
- QString snippet;
-
- auto afterFunctionLastArgument =
- [&line, &column, &snippet, &extraChars, newSourceFiles](const auto &f) {
- auto lastArgument = f->Arguments().back();
-
- line = lastArgument.Line;
- column = lastArgument.Column + static_cast(lastArgument.Value.size()) - 1;
- snippet = QString("\n%1").arg(newSourceFiles);
-
- // Take into consideration the quotes
- if (lastArgument.Delim == cmListFileArgument::Quoted)
- extraChars = 2;
- };
-
- if (knownFunctions.contains(function->LowerCaseName())) {
- afterFunctionLastArgument(function);
- } else {
- auto targetSourcesFunc = std::find_if(cmakeListFile.Functions.begin(),
- cmakeListFile.Functions.end(),
- [target_name](const auto &func) {
- return func.LowerCaseName()
- == "target_sources"
- && func.Arguments().front().Value
- == target_name;
- });
-
- if (targetSourcesFunc == cmakeListFile.Functions.end()) {
- line = function->LineEnd() + 1;
- column = 0;
- snippet = QString("\ntarget_sources(%1\n PRIVATE\n %2\n)\n")
- .arg(targetName)
- .arg(newSourceFiles);
- } else {
- afterFunctionLastArgument(targetSourcesFunc);
- }
- }
-
- BaseTextEditor *editor = qobject_cast(
- Core::EditorManager::openEditorAt({targetCMakeFile, line, column + extraChars},
- Constants::CMAKE_EDITOR_ID,
- Core::EditorManager::DoNotMakeVisible));
- if (!editor) {
- qCCritical(cmakeBuildSystemLog).noquote()
- << "BaseTextEditor cannot be obtained for" << targetCMakeFile.path() << line
- << int(column + extraChars);
- return false;
- }
-
- editor->insert(snippet);
- editor->editorWidget()->autoIndent();
- if (!Core::DocumentManager::saveDocument(editor->document())) {
- qCCritical(cmakeBuildSystemLog).noquote()
- << "Changes to" << targetCMakeFile.path() << "could not be saved.";
+ const SnippetAndLocation snippetLocation = generateSnippetAndLocationForSources(
+ newSourceFiles, *cmakeListFile, *function, targetName);
+ expected_str inserted = insertSnippetSilently(targetCMakeFile, snippetLocation);
+ if (!inserted) {
+ qCCritical(cmakeBuildSystemLog) << inserted.error();
return false;
}
@@ -415,58 +453,41 @@ bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FileP
std::optional
CMakeBuildSystem::projectFileArgumentPosition(const QString &targetName, const QString &fileName)
{
- auto target = Utils::findOrDefault(buildTargets(), [targetName](const CMakeBuildTarget &target) {
- return target.title == targetName;
- });
-
- if (target.backtrace.isEmpty())
+ const std::optional cmakeFile = cmakeFileForBuildKey(targetName, buildTargets());
+ if (!cmakeFile)
return std::nullopt;
- const FilePath targetCMakeFile = target.backtrace.last().path;
+ const FilePath targetCMakeFile = cmakeFile->targetFilePath;
+ const int targetDefinitionLine = cmakeFile->targetLine;
- // Have a fresh look at the CMake file, not relying on a cached value
- Core::DocumentManager::saveModifiedDocumentSilently(
- Core::DocumentModel::documentForFilePath(targetCMakeFile));
- expected_str fileContent = targetCMakeFile.fileContents();
- cmListFile cmakeListFile;
- std::string errorString;
- if (fileContent) {
- fileContent = fileContent->replace("\r\n", "\n");
- if (!cmakeListFile.ParseString(fileContent->toStdString(),
- targetCMakeFile.fileName().toStdString(),
- errorString))
- return std::nullopt;
+ std::optional cmakeListFile = getUncachedCMakeListFile(targetCMakeFile);
+ if (!cmakeListFile)
+ return std::nullopt;
+
+ std::optional function
+ = findFunction(*cmakeListFile, [targetDefinitionLine](const auto &func) {
+ return func.Line() == targetDefinitionLine;
+ });
+ if (!function.has_value()) {
+ qCCritical(cmakeBuildSystemLog) << "Function that defined the target" << targetName
+ << "could not be found at" << targetDefinitionLine;
+ return std::nullopt;
}
- const int targetDefinitionLine = target.backtrace.last().line;
-
- auto function = std::find_if(cmakeListFile.Functions.begin(),
- cmakeListFile.Functions.end(),
- [targetDefinitionLine](const auto &func) {
- return func.Line() == targetDefinitionLine;
- });
-
const std::string target_name = targetName.toStdString();
- auto targetSourcesFunc = std::find_if(cmakeListFile.Functions.begin(),
- cmakeListFile.Functions.end(),
- [target_name](const auto &func) {
- return func.LowerCaseName() == "target_sources"
- && func.Arguments().size() > 1
- && func.Arguments().front().Value
- == target_name;
- });
- auto addQmlModuleFunc = std::find_if(cmakeListFile.Functions.begin(),
- cmakeListFile.Functions.end(),
- [target_name](const auto &func) {
- return (func.LowerCaseName() == "qt_add_qml_module"
- || func.LowerCaseName() == "qt6_add_qml_module")
- && func.Arguments().size() > 1
- && func.Arguments().front().Value
- == target_name;
- });
+ auto targetSourcesFunc = findFunction(*cmakeListFile, [target_name](const auto &func) {
+ return func.LowerCaseName() == "target_sources" && func.Arguments().size() > 1
+ && func.Arguments().front().Value == target_name;
+ });
+
+ auto addQmlModuleFunc = findFunction(*cmakeListFile, [target_name](const auto &func) {
+ return (func.LowerCaseName() == "qt_add_qml_module"
+ || func.LowerCaseName() == "qt6_add_qml_module")
+ && func.Arguments().size() > 1 && func.Arguments().front().Value == target_name;
+ });
for (const auto &func : {function, targetSourcesFunc, addQmlModuleFunc}) {
- if (func == cmakeListFile.Functions.end())
+ if (!func.has_value())
continue;
auto filePathArgument = Utils::findOrDefault(func->Arguments(),
[file_name = fileName.toStdString()](
@@ -479,7 +500,7 @@ CMakeBuildSystem::projectFileArgumentPosition(const QString &targetName, const Q
} else {
// Check if the filename is part of globbing variable result
const auto globFunctions = std::get<0>(
- Utils::partition(cmakeListFile.Functions, [](const auto &f) {
+ Utils::partition(cmakeListFile->Functions, [](const auto &f) {
return f.LowerCaseName() == "file" && f.Arguments().size() > 2
&& (f.Arguments().front().Value == "GLOB"
|| f.Arguments().front().Value == "GLOB_RECURSE");
@@ -503,7 +524,7 @@ CMakeBuildSystem::projectFileArgumentPosition(const QString &targetName, const Q
// Check if the filename is part of a variable set by the user
const auto setFunctions = std::get<0>(
- Utils::partition(cmakeListFile.Functions, [](const auto &f) {
+ Utils::partition(cmakeListFile->Functions, [](const auto &f) {
return f.LowerCaseName() == "set" && f.Arguments().size() > 1;
}));