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 <hjk@qt.io>
This commit is contained in:
Cristian Adam
2023-04-13 12:52:39 +02:00
parent d74c2369ee
commit d8be2491a5
17 changed files with 283 additions and 96 deletions

View File

@@ -17,6 +17,8 @@
#include <android/androidconstants.h>
#include <coreplugin/icore.h>
#include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
@@ -30,6 +32,9 @@
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <texteditor/texteditor.h>
#include <texteditor/textdocument.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qtsupport/qtcppkitinfo.h>
#include <qtsupport/qtkitinformation.h>
@@ -51,74 +56,11 @@
#include <QLoggingCategory>
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<CMakeTargetNode *>(context))
return action == ProjectAction::AddNewFile;
if (dynamic_cast<CMakeListsNode *>(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<CMakeProjectNode *>(context)) {
noAutoAdditionNotify(filePaths, n);
return true; // Return always true as autoadd is not supported!
if (auto n = dynamic_cast<CMakeTargetNode *>(context)) {
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;
}
if (auto n = dynamic_cast<CMakeTargetNode *>(context)) {
noAutoAdditionNotify(filePaths, n);
return true; // Return always true as autoadd is not supported!
const QString newSourceFiles = Utils::transform(filePaths,
[projDir = n->filePath().canonicalPath()](
const FilePath &path) {
return path.canonicalPath()
.relativePathFrom(projDir)
.cleanPath()
.toString();
})
.join(" ");
static QSet<std::string> 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<int>(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<BaseTextEditor *>(
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);
}

View File

@@ -199,6 +199,7 @@ private:
CppEditor::CppProjectUpdater *m_cppCodeModelUpdater = nullptr;
QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers;
QList<CMakeBuildTarget> m_buildTargets;
QSet<CMakeFileInfo> m_cmakeFiles;
// Parsing state:
BuildDirParameters m_parameters;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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<CMakeFileInfo> &cmakefil
const int oldCount = result.cmakeFiles.count();
CMakeFileInfo absolute(info);
absolute.path = sfn;
expected_str<QByteArray> 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

View File

@@ -5,6 +5,7 @@
#include "cmakebuildtarget.h"
#include "cmakeprojectnodes.h"
#include "3rdparty/cmake/cmListFileCache.h"
#include <projectexplorer/rawprojectpart.h>
@@ -32,6 +33,7 @@ public:
bool isCMakeListsDotTxt = false;
bool isExternal = false;
bool isGenerated = false;
cmListFile cmakeListFile;
};
class FileApiQtcData

View File

@@ -179,6 +179,13 @@ QList<CMakeBuildTarget> FileApiReader::takeBuildTargets(QString &errorMessage){
return std::exchange(m_buildTargets, {});
}
QSet<CMakeFileInfo> FileApiReader::takeCMakeFileInfos(QString &errorMessage)
{
Q_UNUSED(errorMessage)
return std::exchange(m_cmakeFiles, {});
}
CMakeConfig FileApiReader::takeParsedConfiguration(QString &errorMessage)
{
if (m_lastCMakeExitCode != 0)

View File

@@ -46,6 +46,7 @@ public:
QSet<Utils::FilePath> projectFilesToWatch() const;
QList<CMakeBuildTarget> takeBuildTargets(QString &errorMessage);
QSet<CMakeFileInfo> takeCMakeFileInfos(QString &errorMessage);
CMakeConfig takeParsedConfiguration(QString &errorMessage);
QString ctestPath() const;
ProjectExplorer::RawProjectParts createRawProjectParts(QString &errorMessage);

View File

@@ -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)

View File

@@ -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.

View File

@@ -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 <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@@ -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;
}

View File

@@ -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 <QMainWindow>
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

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar"/>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,5 @@
function(my_add_executable targetName)
add_executable(${targetName}
${ARGN}
)
endfunction()

View File

@@ -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()
{
}

View File

@@ -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