From d8be2491a5f5cfdc512f63c766a550dd43694063 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Thu, 13 Apr 2023 12:52:39 +0200 Subject: [PATCH] CMakePM: Add new / existing source files to project This will add the new added source files (.cpp, .h, .qrc, .ui) to the corresponding CMake source file as last arguments for known CMake functions like add_executable, add_library as well for the Qt counterprarts qt_add_executable or qt_add_library. For custom functions the code will insert a target_sources() call. Subsequent calls will add the files to the last target_sources. The previous copy to clipboard mechanism and settings have been removed. Fixes: QTCREATORBUG-26006 Fixes: QTCREATORBUG-27213 Fixes: QTCREATORBUG-28493 Fixes: QTCREATORBUG-29006 Change-Id: Ia6e075e4e5718e4106c1236673d469139611a677 Reviewed-by: hjk --- .../cmakeprojectmanager/cmakebuildsystem.cpp | 180 ++++++++++-------- .../cmakeprojectmanager/cmakebuildsystem.h | 1 + .../cmakespecificsettings.cpp | 14 -- .../cmakespecificsettings.h | 7 - .../fileapidataextractor.cpp | 15 ++ .../fileapidataextractor.h | 2 + .../cmakeprojectmanager/fileapireader.cpp | 7 + .../cmakeprojectmanager/fileapireader.h | 1 + .../hello-widgets/CMakeLists.txt | 32 ++++ .../hello-widgets/README.md | 4 + .../hello-widgets/main.cpp | 15 ++ .../hello-widgets/mainwindow.cpp | 20 ++ .../hello-widgets/mainwindow.h | 28 +++ .../hello-widgets/mainwindow.ui | 22 +++ .../hello-widgets/my_add_executable.cmake | 5 + .../hello-widgets/myclass.cpp | 10 + .../hello-widgets/myclass.h | 16 ++ 17 files changed, 283 insertions(+), 96 deletions(-) create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/CMakeLists.txt create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/README.md create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/main.cpp create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.cpp create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.h create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/mainwindow.ui create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/my_add_executable.cmake create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/myclass.cpp create mode 100644 tests/manual/cmakeprojectmanager/hello-widgets/myclass.h 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