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,140 +267,178 @@ static QString newFilesForFunction(const std::string &cmakeFunction,
return relativeFilePaths(filePaths).join(" "); return relativeFilePaths(filePaths).join(" ");
} }
static std::optional<Link> cmakeFileForBuildKey(const QString &buildKey,
const QList<CMakeBuildTarget> &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<cmListFile> 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<QByteArray> 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<cmListFileFunction> findFunction(
const cmListFile &cmakeListFile, std::function<bool(const cmListFileFunction &)> 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<std::string> 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<int>(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<cmListFileFunction> 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<bool> insertSnippetSilently(const FilePath &cmakeFile,
const SnippetAndLocation &snippetLocation)
{
BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(
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) bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded)
{ {
if (notAdded) if (notAdded)
*notAdded = filePaths; *notAdded = filePaths;
if (auto n = dynamic_cast<CMakeTargetNode *>(context)) { if (auto n = dynamic_cast<CMakeTargetNode *>(context)) {
const QString targetName = n->buildKey(); const QString targetName = n->buildKey();
auto target = Utils::findOrDefault(buildTargets(), const std::optional<Link> cmakeFile = cmakeFileForBuildKey(targetName, buildTargets());
[targetName](const CMakeBuildTarget &target) { if (!cmakeFile)
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.";
return false; return false;
}
const FilePath targetCMakeFile = target.backtrace.last().path; const FilePath targetCMakeFile = cmakeFile->targetFilePath;
const int targetDefinitionLine = target.backtrace.last().line; 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 false;
expected_str<QByteArray> 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;
}
}
auto function = std::find_if(cmakeListFile.Functions.begin(), std::optional<cmListFileFunction> function
cmakeListFile.Functions.end(), = findFunction(*cmakeListFile, [targetDefinitionLine](const auto &func) {
[targetDefinitionLine](const auto &func) { return func.Line() == targetDefinitionLine;
return func.Line() == targetDefinitionLine; });
}); if (!function.has_value()) {
if (function == cmakeListFile.Functions.end()) {
qCCritical(cmakeBuildSystemLog) << "Function that defined the target" << targetName qCCritical(cmakeBuildSystemLog) << "Function that defined the target" << targetName
<< "could not be found at" << targetDefinitionLine; << "could not be found at" << targetDefinitionLine;
return false; 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 // 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 // then qt_add_qml_module function should be used
const std::string target_name = targetName.toStdString(); if (auto preferred = findFunction(*cmakeListFile, qtAddModule); !preferred.has_value())
auto add_qml_module_func function = preferred;
= 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(), const QString newSourceFiles = newFilesForFunction(function->LowerCaseName(),
filePaths, filePaths,
n->filePath().canonicalPath()); n->filePath().canonicalPath());
static QSet<std::string> knownFunctions{"add_executable", const SnippetAndLocation snippetLocation = generateSnippetAndLocationForSources(
"add_library", newSourceFiles, *cmakeListFile, *function, targetName);
"qt_add_executable", expected_str<bool> inserted = insertSnippetSilently(targetCMakeFile, snippetLocation);
"qt_add_library", if (!inserted) {
"qt6_add_executable", qCCritical(cmakeBuildSystemLog) << inserted.error();
"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<int>(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<BaseTextEditor *>(
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.";
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 auto addQmlModuleFunc = findFunction(*cmakeListFile, [target_name](const auto &func) {
== target_name; return (func.LowerCaseName() == "qt_add_qml_module"
}); || func.LowerCaseName() == "qt6_add_qml_module")
auto addQmlModuleFunc = std::find_if(cmakeListFile.Functions.begin(), && func.Arguments().size() > 1 && func.Arguments().front().Value == target_name;
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;
});
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;
})); }));