CMakePM: Implement BuildSystem::addDependencies

This can be triggered via C++ QuickFix or the new C++ class wizard.

Change-Id: I93439fb823a2e5c359a3f6b310c1cd642201d1a1
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Cristian Adam
2025-04-23 18:40:14 +02:00
parent f3f56edbad
commit d284974274
11 changed files with 358 additions and 0 deletions

View File

@@ -50,3 +50,11 @@ add_qtc_plugin(CMakeProjectManager
3rdparty/cmake/cmListFileCache.h 3rdparty/cmake/cmListFileCache.h
3rdparty/rstparser/rstparser.cc 3rdparty/rstparser/rstparser.h 3rdparty/rstparser/rstparser.cc 3rdparty/rstparser/rstparser.h
) )
file(GLOB_RECURSE test_cases RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} testcases/*)
qtc_add_resources(CMakeProjectManager "testcases"
CONDITION WITH_TESTS
PREFIX "/cmakeprojectmanager"
BASE "."
FILES ${test_cases}
)

View File

@@ -62,6 +62,11 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QPushButton> #include <QPushButton>
#ifdef WITH_TESTS
#include <cppeditor/cpptoolstestcase.h>
#include <QTest>
#endif
using namespace ProjectExplorer; using namespace ProjectExplorer;
using namespace TextEditor; using namespace TextEditor;
using namespace Utils; using namespace Utils;
@@ -1077,6 +1082,178 @@ void CMakeBuildSystem::buildNamedTarget(const QString &target)
CMakeProjectManager::Internal::buildTarget(this, target); CMakeProjectManager::Internal::buildTarget(this, target);
} }
static Result<bool> insertDependencies(
const QString &targetName,
const FilePath &targetCMakeFile,
int targetDefinitionLine,
const QStringList &dependencies,
int qtMajorVersion)
{
std::optional<cmListFile> cmakeListFile = getUncachedCMakeListFile(targetCMakeFile);
if (!cmakeListFile)
return ResultError("Failed to read " + targetCMakeFile.toUserOutput());
std::optional<cmListFileFunction> function
= findFunction(*cmakeListFile, [targetDefinitionLine](const auto &func) {
return func.Line() == targetDefinitionLine;
});
if (!function.has_value())
return ResultError(QString("Failed to locate the target defining function at %1").arg(targetDefinitionLine));
const int targetDefinitionLastLine = function->LineEnd();
//
// find_package
//
const QString qtPackage = QString("Qt%1").arg(qtMajorVersion);
function = findFunction(
*cmakeListFile,
[qtPackage](const auto &func) {
return func.LowerCaseName() == "find_package" && func.Arguments().size() > 0
&& func.Arguments()[0].Value == qtPackage;
},
/* reverse = */ true);
const QString findComponents = transform(dependencies, [](const QString &dep) {
QTC_ASSERT(dep.size() > 3, return dep);
return dep.mid(3);
}).join(" ");
QString snippet = QString("find_package(%1 REQUIRED COMPONENTS %2)\n%3")
.arg(qtPackage)
.arg(findComponents)
.arg(!function ? QString("\n") : QString(""));
int insertionLine = function ? function->LineEnd() + 1 : targetDefinitionLine;
Result<bool> inserted = insertSnippetSilently(targetCMakeFile, {snippet, insertionLine, 0});
if (!inserted)
return inserted;
const int insertedFindPackageOffset = 2;
//
// target_link_libraries
//
cmakeListFile = getUncachedCMakeListFile(targetCMakeFile);
function = findFunction(
*cmakeListFile,
[targetName](const auto &func) {
return func.LowerCaseName() == "target_link_libraries" && func.Arguments().size() > 0
&& func.Arguments()[0].Value == targetName;
},
/* reverse = */ true);
const QString targetPrefix = QString("Qt%1::").arg(qtMajorVersion);
const QString linkLibraries
= transform(dependencies, [targetPrefix](const QString &dep) -> QString {
QTC_ASSERT(dep.size() > 3, return targetPrefix + dep);
return targetPrefix + dep.mid(3);
}).join(" ");
snippet = QString("%1target_link_libraries(%2 PRIVATE %3)\n")
.arg(!function ? QString("\n") : QString(""))
.arg(targetName)
.arg(linkLibraries);
insertionLine = (function ? function->LineEnd()
: targetDefinitionLastLine + insertedFindPackageOffset)
+ 1;
return insertSnippetSilently(targetCMakeFile, {snippet, insertionLine, 0});
}
bool CMakeBuildSystem::addDependencies(
ProjectExplorer::Node *context, const QStringList &dependencies)
{
if (auto n = dynamic_cast<CMakeTargetNode *>(context)) {
const QString targetName = n->buildKey();
const std::optional<Link> cmakeFile = cmakeFileForBuildKey(targetName, buildTargets());
if (!cmakeFile)
return false;
int qtMajorVersion = 6;
if (auto qt = m_findPackagesFilesHash.value("Qt5Core"); qt.hasValidTarget())
qtMajorVersion = 5;
Result<bool> inserted = insertDependencies(
targetName,
cmakeFile->targetFilePath,
cmakeFile->targetLine,
dependencies,
qtMajorVersion);
if (!inserted) {
qCCritical(cmakeBuildSystemLog) << inserted.error();
return false;
}
return true;
}
return BuildSystem::addDependencies(context, dependencies);
}
#ifdef WITH_TESTS
class AddDependenciesTest final : public QObject
{
Q_OBJECT
private slots:
void test()
{
const auto projectDir = std::make_unique<CppEditor::Tests::TemporaryCopiedDir>(
":/cmakeprojectmanager/testcases/adddependencies");
QVERIFY(insertDependencies(
"HelloQt",
projectDir->filePath().pathAppended("existing_qt5.cmake"),
18,
{"Qt.Concurrent"},
5));
QVERIFY(insertDependencies(
"HelloQt",
projectDir->filePath().pathAppended("existing_qt6.cmake"),
8,
{"Qt.Concurrent"},
6));
QVERIFY(insertDependencies(
"HelloCpp",
projectDir->filePath().pathAppended("no_qt6.cmake"),
8,
{"Qt.Concurrent"},
6));
// Compare files.
static const QString suffix = "_expected.cmake";
const FileFilter filter({"*" + suffix}, QDir::Files);
const FilePaths expectedDocuments = projectDir->filePath().dirEntries(filter);
QVERIFY(!expectedDocuments.isEmpty());
for (const FilePath &expected : expectedDocuments) {
const FilePath actual = expected.parentDir().pathAppended(
expected.fileName().chopped(suffix.length()) + ".cmake");
QVERIFY(actual.exists());
const auto actualContents = actual.fileContents();
QVERIFY(actualContents);
const auto expectedContents = expected.fileContents();
const QByteArrayList actualLines = actualContents->split('\n');
const QByteArrayList expectedLines = expectedContents->split('\n');
if (actualLines.size() != expectedLines.size()) {
qDebug().noquote().nospace() << "---\n" << *expectedContents << "EOF";
qDebug().noquote().nospace() << "+++\n" << *actualContents << "EOF";
}
QCOMPARE(actualLines.size(), expectedLines.size());
for (int i = 0; i < actualLines.size(); ++i) {
const QByteArray actualLine = actualLines.at(i);
const QByteArray expectedLine = expectedLines.at(i);
if (actualLine != expectedLine)
qDebug() << "Unexpected content in line" << (i + 1) << "of file"
<< actual.fileName();
QCOMPARE(actualLine, expectedLine);
}
}
}
};
QObject *createAddDependenciesTest()
{
return new AddDependenciesTest;
}
#endif
FilePaths CMakeBuildSystem::filesGeneratedFrom(const FilePath &sourceFile) const FilePaths CMakeBuildSystem::filesGeneratedFrom(const FilePath &sourceFile) const
{ {
FilePath project = projectDirectory(); FilePath project = projectDirectory();
@@ -2608,3 +2785,7 @@ ExtraCompiler *CMakeBuildSystem::findExtraCompiler(const ExtraCompilerFilter &fi
} }
} // CMakeProjectManager::Internal } // CMakeProjectManager::Internal
#ifdef WITH_TESTS
#include <cmakebuildsystem.moc>
#endif

View File

@@ -68,6 +68,8 @@ public:
Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final; Utils::FilePaths filesGeneratedFrom(const Utils::FilePath &sourceFile) const final;
bool addDependencies(ProjectExplorer::Node *context, const QStringList &dependencies) final;
// Actions: // Actions:
void runCMake(); void runCMake();
void runCMakeAndScanProjectTree(); void runCMakeAndScanProjectTree();
@@ -269,5 +271,9 @@ private:
QString m_warning; QString m_warning;
}; };
#ifdef WITH_TESTS
QObject *createAddDependenciesTest();
#endif
} // namespace Internal } // namespace Internal
} // namespace CMakeProjectManager } // namespace CMakeProjectManager

View File

@@ -108,4 +108,10 @@ QtcPlugin {
"rstparser/rstparser.h" "rstparser/rstparser.h"
] ]
} }
QtcTestFiles {
name: "test data"
files: "testcases/**/*"
fileTags: qtc.withPluginTests ? ["qt.core.resource_data"] : []
}
} }

View File

@@ -80,6 +80,7 @@ class CMakeProjectPlugin final : public ExtensionSystem::IPlugin
addTestCreator(createCMakeOutputParserTest); addTestCreator(createCMakeOutputParserTest);
addTestCreator(createCMakeAutogenParserTest); addTestCreator(createCMakeAutogenParserTest);
addTestCreator(createCMakeProjectImporterTest); addTestCreator(createCMakeProjectImporterTest);
addTestCreator(createAddDependenciesTest);
#endif #endif
FileIconProvider::registerIconOverlayForSuffix(Constants::Icons::FILE_OVERLAY, "cmake"); FileIconProvider::registerIconOverlayForSuffix(Constants::Icons::FILE_OVERLAY, "cmake");

View File

@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.1.0)
project(HelloQt VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(CMAKE_VERSION VERSION_LESS "3.7.0")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()
find_package(Qt5 COMPONENTS Widgets REQUIRED)
add_executable(HelloQt
mainwindow.ui
mainwindow.cpp
main.cpp
resources.qrc
)
target_link_libraries(HelloQt Qt5::Widgets)

View File

@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.1.0)
project(HelloQt VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(CMAKE_VERSION VERSION_LESS "3.7.0")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()
find_package(Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt5 REQUIRED COMPONENTS Concurrent)
add_executable(HelloQt
mainwindow.ui
mainwindow.cpp
main.cpp
resources.qrc
)
target_link_libraries(HelloQt Qt5::Widgets)
target_link_libraries(HelloQt PRIVATE Qt5::Concurrent)

View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.19)
project(HelloQt LANGUAGES CXX)
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets)
qt_standard_project_setup()
qt_add_executable(HelloQt
WIN32 MACOSX_BUNDLE
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
target_link_libraries(HelloQt
PRIVATE
Qt::Core
Qt::Widgets
)
include(GNUInstallDirs)
install(TARGETS HelloQt
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_app_script(
TARGET HelloQt
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})

View File

@@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.19)
project(HelloQt LANGUAGES CXX)
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets)
find_package(Qt6 REQUIRED COMPONENTS Concurrent)
qt_standard_project_setup()
qt_add_executable(HelloQt
WIN32 MACOSX_BUNDLE
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
target_link_libraries(HelloQt
PRIVATE
Qt::Core
Qt::Widgets
)
target_link_libraries(HelloQt PRIVATE Qt6::Concurrent)
include(GNUInstallDirs)
install(TARGETS HelloQt
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_app_script(
TARGET HelloQt
OUTPUT_SCRIPT deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})

View File

@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.16)
project(HelloCpp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(HelloCpp main.cpp)
include(GNUInstallDirs)
install(TARGETS HelloCpp
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.16)
project(HelloCpp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Concurrent)
add_executable(HelloCpp main.cpp)
target_link_libraries(HelloCpp PRIVATE Qt6::Concurrent)
include(GNUInstallDirs)
install(TARGETS HelloCpp
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)