CMakePM: Extract functions for later reuse

Change-Id: Id933c545328300398a2fa38fa82e0c593f501406
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
Christian Stenger
2023-11-23 23:08:36 +01:00
parent f708e200f9
commit 938a230dd0

View File

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