diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 68b3cb20a6c..ce56c305af5 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include @@ -30,6 +32,9 @@ #include #include +#include +#include + #include #include #include @@ -51,74 +56,11 @@ #include using namespace ProjectExplorer; +using namespace TextEditor; using namespace Utils; namespace CMakeProjectManager::Internal { -static void copySourcePathsToClipboard(const FilePaths &srcPaths, const ProjectNode *node) -{ - QClipboard *clip = QGuiApplication::clipboard(); - - QString data = Utils::transform(srcPaths, [projDir = node->filePath()](const FilePath &path) { - return path.relativePathFrom(projDir).cleanPath().toString(); - }).join(" "); - clip->setText(data); -} - -static void noAutoAdditionNotify(const FilePaths &filePaths, const ProjectNode *node) -{ - const FilePaths srcPaths = Utils::filtered(filePaths, [](const FilePath &file) { - const auto mimeType = Utils::mimeTypeForFile(file).name(); - return mimeType == CppEditor::Constants::C_SOURCE_MIMETYPE || - mimeType == CppEditor::Constants::C_HEADER_MIMETYPE || - mimeType == CppEditor::Constants::CPP_SOURCE_MIMETYPE || - mimeType == CppEditor::Constants::CPP_HEADER_MIMETYPE || - mimeType == ProjectExplorer::Constants::FORM_MIMETYPE || - mimeType == ProjectExplorer::Constants::RESOURCE_MIMETYPE || - mimeType == ProjectExplorer::Constants::SCXML_MIMETYPE; - }); - - if (!srcPaths.empty()) { - auto settings = CMakeSpecificSettings::instance(); - switch (settings->afterAddFileSetting.value()) { - case AskUser: { - bool checkValue{false}; - QDialogButtonBox::StandardButton reply = CheckableMessageBox::question( - Core::ICore::dialogParent(), - Tr::tr("Copy to Clipboard?"), - Tr::tr("Files are not automatically added to the " - "CMakeLists.txt file of the CMake project." - "\nCopy the path to the source files to the clipboard?"), - "Remember My Choice", - &checkValue, - QDialogButtonBox::Yes | QDialogButtonBox::No, - QDialogButtonBox::Yes); - if (checkValue) { - if (QDialogButtonBox::Yes == reply) - settings->afterAddFileSetting.setValue(CopyFilePath); - else if (QDialogButtonBox::No == reply) - settings->afterAddFileSetting.setValue(NeverCopyFilePath); - - settings->writeSettings(Core::ICore::settings()); - } - - if (QDialogButtonBox::Yes == reply) - copySourcePathsToClipboard(srcPaths, node); - - break; - } - - case CopyFilePath: { - copySourcePathsToClipboard(srcPaths, node); - break; - } - - case NeverCopyFilePath: - break; - } - } -} - static Q_LOGGING_CATEGORY(cmakeBuildSystemLog, "qtc.cmake.buildsystem", QtWarningMsg); // -------------------------------------------------------------------- @@ -258,24 +200,110 @@ void CMakeBuildSystem::triggerParsing() bool CMakeBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const { if (dynamic_cast(context)) - return action == ProjectAction::AddNewFile; - - if (dynamic_cast(context)) - return action == ProjectAction::AddNewFile; + return action == ProjectAction::AddNewFile || action == ProjectAction::AddExistingFile; return BuildSystem::supportsAction(context, action, node); } bool CMakeBuildSystem::addFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded) { - if (auto n = dynamic_cast(context)) { - noAutoAdditionNotify(filePaths, n); - return true; // Return always true as autoadd is not supported! - } - if (auto n = dynamic_cast(context)) { - noAutoAdditionNotify(filePaths, n); - return true; // Return always true as autoadd is not supported! + const QString targetName = n->buildKey(); + auto target = Utils::findOrDefault(buildTargets(), + [targetName](const CMakeBuildTarget &target) { + return target.title == targetName; + }); + + if (target.backtrace.isEmpty()) { + *notAdded = filePaths; + return false; + } + const FilePath targetCMakeFile = target.backtrace.last().path; + const int targetDefinitionLine = target.backtrace.last().line; + + auto cmakeListFile + = Utils::findOrDefault(m_cmakeFiles, [targetCMakeFile](const CMakeFileInfo &info) { + return info.path == targetCMakeFile; + }).cmakeListFile; + + auto function = std::find_if(cmakeListFile.Functions.begin(), + cmakeListFile.Functions.end(), + [targetDefinitionLine](const auto &func) { + return func.Line() == targetDefinitionLine; + }); + + if (function == cmakeListFile.Functions.end()) { + *notAdded = filePaths; + return false; + } + + const QString newSourceFiles = Utils::transform(filePaths, + [projDir = n->filePath().canonicalPath()]( + const FilePath &path) { + return path.canonicalPath() + .relativePathFrom(projDir) + .cleanPath() + .toString(); + }) + .join(" "); + + static QSet knownFunctions{"add_executable", + "add_library", + "qt_add_executable", + "qt_add_library", + "qt6_add_executable", + "qt6_add_library"}; + + int line = 0; + int column = 0; + QString snippet; + + auto afterFunctionLastArgument = [&line, &column, &snippet, 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); + }; + + if (knownFunctions.contains(function->LowerCaseName())) { + afterFunctionLastArgument(function); + } else { + 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().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}, + Constants::CMAKE_EDITOR_ID, + Core::EditorManager::DoNotMakeVisible)); + if (!editor) { + *notAdded = filePaths; + return false; + } + + editor->insert(snippet); + editor->editorWidget()->autoIndent(); + Core::DocumentManager::saveDocument(editor->document()); + + return true; } return BuildSystem::addFiles(context, filePaths, notAdded); @@ -735,6 +763,8 @@ void CMakeBuildSystem::handleParsingSucceeded(bool restoredFromBackup) return result; }); m_buildTargets += m_reader.takeBuildTargets(errorMessage); + m_cmakeFiles = m_reader.takeCMakeFileInfos(errorMessage); + checkAndReportError(errorMessage); } diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index f3b26b8b4f6..eea894f5e92 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -199,6 +199,7 @@ private: CppEditor::CppProjectUpdater *m_cppCodeModelUpdater = nullptr; QList m_extraCompilers; QList m_buildTargets; + QSet m_cmakeFiles; // Parsing state: BuildDirParameters m_parameters; diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp index b29351d1752..d7c8775bd18 100644 --- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp +++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp @@ -30,16 +30,6 @@ CMakeSpecificSettings::CMakeSpecificSettings() autorunCMake.setToolTip(::CMakeProjectManager::Tr::tr( "Automatically run CMake after changes to CMake project files.")); - registerAspect(&afterAddFileSetting); - afterAddFileSetting.setSettingsKey("ProjectPopupSetting"); - afterAddFileSetting.setDefaultValue(AfterAddFileAction::AskUser); - afterAddFileSetting.addOption(::CMakeProjectManager::Tr::tr("Ask about copying file paths")); - afterAddFileSetting.addOption(::CMakeProjectManager::Tr::tr("Do not copy file paths")); - afterAddFileSetting.addOption(::CMakeProjectManager::Tr::tr("Copy file paths")); - afterAddFileSetting.setToolTip(::CMakeProjectManager::Tr::tr("Determines whether file paths are copied " - "to the clipboard for pasting to the CMakeLists.txt file when you " - "add new files to CMake projects.")); - registerAspect(&ninjaPath); ninjaPath.setSettingsKey("NinjaPath"); // never save this to the settings: @@ -95,10 +85,6 @@ CMakeSpecificSettingsPage::CMakeSpecificSettingsPage() CMakeSpecificSettings &s = *settings; using namespace Layouting; Column { - Group { - title(::CMakeProjectManager::Tr::tr("Adding Files")), - Column { s.afterAddFileSetting } - }, s.autorunCMake, s.packageManagerAutoSetup, s.askBeforeReConfigureInitialParams, diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h index d0b19bee426..830f1050c24 100644 --- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h +++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h @@ -9,12 +9,6 @@ namespace CMakeProjectManager::Internal { -enum AfterAddFileAction : int { - AskUser, - CopyFilePath, - NeverCopyFilePath -}; - class CMakeSpecificSettings final : public Utils::AspectContainer { public: @@ -23,7 +17,6 @@ public: static CMakeSpecificSettings *instance(); Utils::BoolAspect autorunCMake; - Utils::SelectionAspect afterAddFileSetting; Utils::StringAspect ninjaPath; Utils::BoolAspect packageManagerAutoSetup; Utils::BoolAspect askBeforeReConfigureInitialParams; diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp index 399ed7385c7..074ee26a671 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -27,6 +27,8 @@ using namespace CMakeProjectManager::Internal::FileApiDetails; namespace CMakeProjectManager::Internal { +static Q_LOGGING_CATEGORY(cmakeLogger, "qtc.cmake.fileApiExtractor", QtWarningMsg); + // -------------------------------------------------------------------- // Helpers: // -------------------------------------------------------------------- @@ -53,7 +55,20 @@ CMakeFileResult extractCMakeFilesData(const std::vector &cmakefil const int oldCount = result.cmakeFiles.count(); CMakeFileInfo absolute(info); absolute.path = sfn; + + expected_str fileContent = sfn.fileContents(); + std::string errorString; + if (fileContent) { + fileContent = fileContent->replace("\r\n", "\n"); + if (!absolute.cmakeListFile.ParseString(fileContent->toStdString(), + sfn.fileName().toStdString(), + errorString)) + qCWarning(cmakeLogger) + << "Failed to parse:" << sfn.path() << QString::fromLatin1(errorString); + } + result.cmakeFiles.insert(absolute); + if (oldCount < result.cmakeFiles.count()) { if (info.isCMake && !info.isCMakeListsDotTxt) { // Skip files that cmake considers to be part of the installation -- but include diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.h b/src/plugins/cmakeprojectmanager/fileapidataextractor.h index 04f4ecea43f..e4ed3aa778c 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.h +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.h @@ -5,6 +5,7 @@ #include "cmakebuildtarget.h" #include "cmakeprojectnodes.h" +#include "3rdparty/cmake/cmListFileCache.h" #include @@ -32,6 +33,7 @@ public: bool isCMakeListsDotTxt = false; bool isExternal = false; bool isGenerated = false; + cmListFile cmakeListFile; }; class FileApiQtcData diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp index 2d8d4c5345c..42fca8b06ac 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.cpp +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -179,6 +179,13 @@ QList FileApiReader::takeBuildTargets(QString &errorMessage){ return std::exchange(m_buildTargets, {}); } +QSet FileApiReader::takeCMakeFileInfos(QString &errorMessage) +{ + Q_UNUSED(errorMessage) + + return std::exchange(m_cmakeFiles, {}); +} + CMakeConfig FileApiReader::takeParsedConfiguration(QString &errorMessage) { if (m_lastCMakeExitCode != 0) diff --git a/src/plugins/cmakeprojectmanager/fileapireader.h b/src/plugins/cmakeprojectmanager/fileapireader.h index c854ce20400..cb90af8477d 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.h +++ b/src/plugins/cmakeprojectmanager/fileapireader.h @@ -46,6 +46,7 @@ public: QSet projectFilesToWatch() const; QList takeBuildTargets(QString &errorMessage); + QSet takeCMakeFileInfos(QString &errorMessage); CMakeConfig takeParsedConfiguration(QString &errorMessage); QString ctestPath() const; ProjectExplorer::RawProjectParts createRawProjectParts(QString &errorMessage); diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt b/tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt new file mode 100644 index 00000000000..4400c291ef0 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18) + +project(hello-widgets) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets) + +qt_add_executable(hello-widgets + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +target_link_libraries(hello-widgets PRIVATE Qt6::Widgets) + +include(my_add_executable.cmake) + +my_add_executable(hello-my-widgets + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +target_link_libraries(hello-my-widgets PRIVATE Qt6::Widgets) diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/README.md b/tests/manual/cmakeprojectmanager/hello-widgets/README.md new file mode 100644 index 00000000000..52da33ef8ac --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/README.md @@ -0,0 +1,4 @@ +Qt6 Widgets project to test adding new or existing files to a CMake project. + +The project uses both custom CMake API and normal Qt6 qt_add_executable API +function call. diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/main.cpp b/tests/manual/cmakeprojectmanager/hello-widgets/main.cpp new file mode 100644 index 00000000000..a0ccba3ce92 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/main.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" + +#include + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp new file mode 100644 index 00000000000..c36872995c3 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "./ui_mainwindow.h" + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h new file mode 100644 index 00000000000..35d90b0cfd3 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h @@ -0,0 +1,28 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + + + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow + +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui new file mode 100644 index 00000000000..b232854ba81 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui @@ -0,0 +1,22 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake b/tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake new file mode 100644 index 00000000000..dca664be47b --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake @@ -0,0 +1,5 @@ +function(my_add_executable targetName) + add_executable(${targetName} + ${ARGN} + ) +endfunction() diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp new file mode 100644 index 00000000000..ce14aae2ce5 --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "myclass.h" + +myclass::myclass() +{ + +} + diff --git a/tests/manual/cmakeprojectmanager/hello-widgets/myclass.h b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.h new file mode 100644 index 00000000000..98e0e47cb1c --- /dev/null +++ b/tests/manual/cmakeprojectmanager/hello-widgets/myclass.h @@ -0,0 +1,16 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MYCLASS_H +#define MYCLASS_H + + + + +class myclass +{ +public: + myclass(); +}; + +#endif // MYCLASS_H