aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarkus Redeker <[email protected]>2024-09-30 16:41:30 +0200
committerMarkus Redeker <[email protected]>2024-10-24 12:41:04 +0000
commit090b540487c0d5e178a043f8df05d3bbc0b67bd5 (patch)
tree9ccda8fd82425ff3da58d7c68999b5e6a2e349dd
parenta5a8450edc5249c2694bc6836b177873ecb1132b (diff)
Extend Coco plugin for QMake/CMake project instrumentation
The Coco plugin now can also configure QMake and CMake projects for the use with Coco. (COCO-1782) * There is a new global preferences page to set the Coco directory. (But the plugin also searches automatically for the active Coco installation, so using the page should rarely be necessary.) * There is a project settings page where the code coverage can be configured, especially CoverageScanner options set. * Code coverage is enabled by changing the build settings of a project so that the build tool reads a special file at the beginning (cocoplugin.prf or cocoplugin.cmake), which replaces the normal compiler calls with calls of the Coco compiler wrappers. The CoverageScanner options are part of this file. * An additional, ficticious build step appears in the build menu of QMake and CMake projects, with a button to switch coverage on or off. * Added documentation * The class CMakeBuildSystem has been made publicly accessible and some member functions added so that the build settings chanbe chenged. [ChangeLog][coco] Added to the existing Coco plugin the ability to configure CMake and QMake projects for code coverage. Change-Id: I70a351b79b89bef3c25f81bb4b62ac59dc787645 Reviewed-by: Leena Miettinen <[email protected]> Reviewed-by: hjk <[email protected]>
-rw-r--r--doc/qtcreator/images/qtcreator-coco-buildstep.pngbin0 -> 6488 bytes
-rw-r--r--doc/qtcreator/images/qtcreator-coco-configpage.pngbin0 -> 16561 bytes
-rw-r--r--doc/qtcreator/images/qtcreator-coco.pngbin5550 -> 7353 bytes
-rw-r--r--doc/qtcreator/src/analyze/creator-coco.qdoc113
-rw-r--r--src/plugins/cmakeprojectmanager/builddirparameters.h6
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp17
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h12
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp5
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildsystem.h19
-rw-r--r--src/plugins/coco/CMakeLists.txt48
-rw-r--r--src/plugins/coco/Coco.json.in5
-rw-r--r--src/plugins/coco/coco.qbs45
-rw-r--r--src/plugins/coco/cocobuild/buildsettings.cpp100
-rw-r--r--src/plugins/coco/cocobuild/buildsettings.h67
-rw-r--r--src/plugins/coco/cocobuild/cmakemodificationfile.cpp91
-rw-r--r--src/plugins/coco/cocobuild/cmakemodificationfile.h32
-rw-r--r--src/plugins/coco/cocobuild/cocobuildstep.cpp126
-rw-r--r--src/plugins/coco/cocobuild/cocobuildstep.h59
-rw-r--r--src/plugins/coco/cocobuild/cococmakesettings.cpp167
-rw-r--r--src/plugins/coco/cocobuild/cococmakesettings.h51
-rw-r--r--src/plugins/coco/cocobuild/cocoprojectwidget.cpp336
-rw-r--r--src/plugins/coco/cocobuild/cocoprojectwidget.h81
-rw-r--r--src/plugins/coco/cocobuild/cocoqmakesettings.cpp188
-rw-r--r--src/plugins/coco/cocobuild/cocoqmakesettings.h56
-rw-r--r--src/plugins/coco/cocobuild/modificationfile.cpp73
-rw-r--r--src/plugins/coco/cocobuild/modificationfile.h51
-rw-r--r--src/plugins/coco/cocobuild/qmakefeaturefile.cpp105
-rw-r--r--src/plugins/coco/cocobuild/qmakefeaturefile.h34
-rw-r--r--src/plugins/coco/cocoplugin.cpp156
-rw-r--r--src/plugins/coco/cocoplugin.qrc10
-rw-r--r--src/plugins/coco/cocoplugin_global.h12
-rw-r--r--src/plugins/coco/cocopluginconstants.h23
-rw-r--r--src/plugins/coco/common.cpp30
-rw-r--r--src/plugins/coco/common.h14
-rw-r--r--src/plugins/coco/files/cocoplugin-clang.cmake8
-rw-r--r--src/plugins/coco/files/cocoplugin-gcc.cmake8
-rw-r--r--src/plugins/coco/files/cocoplugin-visualstudio.cmake8
-rw-r--r--src/plugins/coco/files/cocoplugin.cmake87
-rw-r--r--src/plugins/coco/files/cocoplugin.prf33
-rw-r--r--src/plugins/coco/images/SquishCoco_48x48.pngbin0 -> 594 bytes
-rw-r--r--src/plugins/coco/settings/cocoinstallation.cpp177
-rw-r--r--src/plugins/coco/settings/cocoinstallation.h39
-rw-r--r--src/plugins/coco/settings/cocoprojectsettingswidget.cpp40
-rw-r--r--src/plugins/coco/settings/cocoprojectsettingswidget.h28
-rw-r--r--src/plugins/coco/settings/globalsettings.cpp53
-rw-r--r--src/plugins/coco/settings/globalsettings.h13
-rw-r--r--src/plugins/coco/settings/globalsettingspage.cpp121
-rw-r--r--src/plugins/coco/settings/globalsettingspage.h59
48 files changed, 2727 insertions, 79 deletions
diff --git a/doc/qtcreator/images/qtcreator-coco-buildstep.png b/doc/qtcreator/images/qtcreator-coco-buildstep.png
new file mode 100644
index 00000000000..66b21eee1e7
--- /dev/null
+++ b/doc/qtcreator/images/qtcreator-coco-buildstep.png
Binary files differ
diff --git a/doc/qtcreator/images/qtcreator-coco-configpage.png b/doc/qtcreator/images/qtcreator-coco-configpage.png
new file mode 100644
index 00000000000..ac48dcbbd81
--- /dev/null
+++ b/doc/qtcreator/images/qtcreator-coco-configpage.png
Binary files differ
diff --git a/doc/qtcreator/images/qtcreator-coco.png b/doc/qtcreator/images/qtcreator-coco.png
index 851897ef2b3..841ba4f196f 100644
--- a/doc/qtcreator/images/qtcreator-coco.png
+++ b/doc/qtcreator/images/qtcreator-coco.png
Binary files differ
diff --git a/doc/qtcreator/src/analyze/creator-coco.qdoc b/doc/qtcreator/src/analyze/creator-coco.qdoc
index 4b1337e1238..3a9ab1c61ea 100644
--- a/doc/qtcreator/src/analyze/creator-coco.qdoc
+++ b/doc/qtcreator/src/analyze/creator-coco.qdoc
@@ -7,28 +7,119 @@
\ingroup creator-how-to-analyze
- \title Check code coverage
+ \title Measure and check code coverage
- With Coco CoverageBrowser, you can analyze the test coverage by loading an
- instrumentation database (a .csmes file), which was generated by Coco
- CoverageScanner. The experimental Coco plugin is currently supported only on
- Windows, with Coco version 6.0, or later.
+ With Coco, you can measure and the code coverage of tests. The Coco plugin
+ supports the setup of a project for code coverage and the display of the
+ coverage inside Qt Creator.
- To use the plugin, you must download and install Coco.
+ To use the plugin, you must download and install Coco version 6.0 or later.
\note Enable the Coco plugin to use it.
- \section1 Configure Coco
+ \section1 Configuring the plugin
+
+ There is a settings page at \preferences > \uicontrol Coco with which you
+ can set the directory in which Coco is installed. But in most cases, the default
+ settings need not to be changed.
+
+ \section1 Measure code coverage
+
+ With the Coco plugin, it is possible to set up code coverage easily for Qt Creator
+ projects that are built with QMake or CMake.
+
+ The general idea is that you take an existing build configuration, like "Debug",
+ clone it with a new name (like "DebugCoverage") and then use the plugin to
+ configure it for the use with Coco. Switching back and forth between coverage
+ and normal builds on the same build configuration is not recommended.
+
+ \section2 Components of the plugin
+
+ With the plugin enabled, C/C++ projects get
+ \list
+ \li A project settings menu at \uicontrol Projects > \uicontrol {Project settings}
+ > \uicontrol {Coco Code Coverage} with which you can enable and configure
+ code coverage.
+ \li In the Build Settings, an additional pseudo build step that shows whether
+ code coverage was enabled. There is also a button to directly disable or
+ enable code coverage from this build step.
+
+ \image qtcreator-coco-buildstep.png {Ficticious build step for code coverage}
+ \endlist
+
+ If code coverage is enabled, the plugin generates a \e {settings} file that is read
+ by the build tool before the other configuration files and which changes the
+ build process so that the Coco compiler wrappers are used instead of the original
+ compiler. The settings file is always located in the root directory of the
+ project sources. It also contains the coverage flags and possible overrides and
+ can be checked in into version control to preserve the settings.
+
+ \section2 The Project settings page
+
+ \image qtcreator-coco-configpage.png {Settings page}
+
+ The pages for QMake and CMake projects do not differ very much. They contain:
+ \list
+ \li A checkbox to enable and disable code coverage.
+ \li A field to enter code coverage options. There are no settings that are
+ needed to enable code coverage. Below the field are buttons:
+ \list
+ \li \uicontrol {Exclude file...} to exclude a file from instrumentation
+ more easily.
+ \li \uicontrol {Exclude directory...} to exclude a directory from
+ instrumentation more easily.
+ \li \uicontrol Override to open another entry field in which you can
+ enter additional commands at the end of the settings file. It is
+ meant for special cases in which the usual configuration flags are
+ not enough.
+ \endlist
+ \li A button \uicontrol Revert to reload the coverage settings from the current
+ settings file.
+ \li A button \uicontrol Save or \uicontrol {Save & Re-configure} to write
+ the settings to the settings file and reconfigure the project, if
+ necessary.
+ \li A list with the project build settings that were changed by the plugin.
+ \endlist
+
+ \section2 QMake projects
+
+ The settings file is \c {cocoplugin.prf}. It is a QMake "feature file".
+
+ For a command line build, \c {qmake} must be run with the additional options
+ \tt {CONFIG+=cocoplugin COCOPATH=\e{<Coco directory>}}. It is also necessary
+ to set the environment variable \c {QMAKEFEATURES} to the directory in which
+ \c {cocoplugin.prf} is located.
+
+ \section2 CMake projects
+
+ The settings file is \c {cocoplugin.cmake}. It is a CMake cache preload script.
+ Apart from this file, the "compiler files" \c {cocoplugin-gcc.cmake},
+ \c {cocoplugin-clang.cmake} and \c {cocoplugin-visualstudio.cmake} are created
+ in the same directory. They are needed for a command line build.
+
+ In a command-line build, run CMake in the form
+ "\tt {cmake \e{<other options>} -C\e{<project dir>}/cocoplugin-gcc.cmake}"
+ (if you are compiling with GCC). The file \c {cocoplugin-gcc.cmake} includes
+ then \c {cocoplugin.cmake}.
+
+ If you use a compiler different from GCC, clang or Visual Studio, one of the
+ compiler files must be modified for the new compiler.
+
+ \section1 Check code coverage
+
+ With the help of Coco CoverageBrowser, you can analyze the test coverage by
+ loading an instrumentation database (a \c .csmes file), which was generated by
+ Coco CoverageScanner.
+
+ \section2 Configure Coco
\list 1
\li Go to \uicontrol Analyze > \uicontrol {Squish Coco}.
\image qtcreator-coco.png {Coco CoverageBrowser and CSMes file}
- \li In \uicontrol {CoverageBrowser}, enter the path to the Coco
- coverage browser to use for analyzing a .csmes file.
\li In \uicontrol CSMes, select the instrumentation database to load.
\li Select \uicontrol Open to start CoverageBrowser.
\li In CoverageBrowser, go to \uicontrol File >
- \uicontrol {Load Execution Report} and select the .csexe for the
+ \uicontrol {Load Execution Report} and select the \c .csexe file for the
coverage scan.
\image coco-coveragebrowser-load-execution-report.png {Load Execution Report dialog}
\li To keep the execution report, clear
@@ -39,7 +130,7 @@
after the code in \uicontrol Edit mode. You can change the fonts and colors
used for different types of results.
- \section1 Changing Fonts and Colors
+ \section2 Changing Fonts and Colors
To change the default fonts and colors, go to \preferences >
\uicontrol {Text Editor} > \uicontrol {Font & Colors}.
diff --git a/src/plugins/cmakeprojectmanager/builddirparameters.h b/src/plugins/cmakeprojectmanager/builddirparameters.h
index b73993eb37c..fc5c812bbd8 100644
--- a/src/plugins/cmakeprojectmanager/builddirparameters.h
+++ b/src/plugins/cmakeprojectmanager/builddirparameters.h
@@ -19,9 +19,11 @@ namespace ProjectExplorer {
class Project;
}
-namespace CMakeProjectManager::Internal {
-
+namespace CMakeProjectManager {
class CMakeBuildSystem;
+}
+
+namespace CMakeProjectManager::Internal {
class BuildDirParameters
{
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
index 1c5e78980ab..c11aa6be18a 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
@@ -117,6 +117,8 @@ public:
void setError(const QString &message);
void setWarning(const QString &message);
+ void updateInitialCMakeArguments();
+
private:
void updateButtonState();
void updateAdvancedCheckBox();
@@ -135,7 +137,6 @@ private:
void batchEditConfiguration();
void reconfigureWithInitialParameters();
- void updateInitialCMakeArguments();
void kitCMakeConfiguration();
void updateConfigureDetailsWidgetsSummary(
const QStringList &configurationArguments = QStringList());
@@ -1838,7 +1839,19 @@ QString CMakeBuildSystem::warning() const
NamedWidget *CMakeBuildConfiguration::createConfigWidget()
{
- return new CMakeBuildSettingsWidget(this);
+ m_configWidget = new CMakeBuildSettingsWidget(this);
+ return m_configWidget;
+}
+
+void CMakeBuildConfiguration::updateInitialCMakeArguments()
+{
+ Q_ASSERT(m_configWidget);
+ m_configWidget->updateInitialCMakeArguments();
+}
+
+QStringList CMakeBuildConfiguration::initialCMakeOptions() const
+{
+ return initialCMakeArguments.allValues();
}
CMakeConfig CMakeBuildConfiguration::signingFlags() const
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h
index 8a7d2bace4d..398b42ffa50 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h
+++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h
@@ -14,10 +14,10 @@
namespace CMakeProjectManager {
class CMakeProject;
+class CMakeBuildSystem;
namespace Internal {
-class CMakeBuildSystem;
class CMakeBuildSettingsWidget;
class CMakeProjectImporter;
@@ -75,7 +75,7 @@ public:
void setRestrictedBuildTarget(const QString &buildTarget);
Utils::Environment configureEnvironment() const;
- Internal::CMakeBuildSystem *cmakeBuildSystem() const;
+ CMakeBuildSystem *cmakeBuildSystem() const;
QStringList additionalCMakeArguments() const;
void setAdditionalCMakeArguments(const QStringList &args);
@@ -90,6 +90,9 @@ public:
QtSupport::QmlDebuggingAspect qmlDebugging{this};
Internal::ConfigureEnvironmentAspect configureEnv{this, this};
+ void updateInitialCMakeArguments();
+ QStringList initialCMakeOptions() const;
+
signals:
void signingFlagsChanged();
void configureEnvironmentChanged();
@@ -105,11 +108,12 @@ private:
void setBuildPresetToBuildSteps(const ProjectExplorer::Target *target);
void filterConfigArgumentsFromAdditionalCMakeArguments();
- Internal::CMakeBuildSystem *m_buildSystem = nullptr;
+ CMakeBuildSystem *m_buildSystem = nullptr;
+ Internal::CMakeBuildSettingsWidget *m_configWidget = nullptr;
QStringList m_unrestrictedBuildTargets;
friend class Internal::CMakeBuildSettingsWidget;
- friend class Internal::CMakeBuildSystem;
+ friend class CMakeBuildSystem;
};
class CMAKE_EXPORT CMakeBuildConfigurationFactory
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
index 3547e96ed10..3d8d75eaa78 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
@@ -62,8 +62,9 @@
using namespace ProjectExplorer;
using namespace TextEditor;
using namespace Utils;
+using namespace CMakeProjectManager::Internal;
-namespace CMakeProjectManager::Internal {
+namespace CMakeProjectManager {
static Q_LOGGING_CATEGORY(cmakeBuildSystemLog, "qtc.cmake.buildsystem", QtWarningMsg);
@@ -2523,4 +2524,4 @@ ExtraCompiler *CMakeBuildSystem::findExtraCompiler(const ExtraCompilerFilter &fi
return Utils::findOrDefault(m_extraCompilers, filter);
}
-} // CMakeProjectManager::Internal
+} // CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h
index 2b55097852d..2b6b3f79f2f 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h
+++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h
@@ -29,13 +29,11 @@ namespace CMakeProjectManager {
class CMakeBuildConfiguration;
class CMakeProject;
-namespace Internal {
-
// --------------------------------------------------------------------
// CMakeBuildSystem:
// --------------------------------------------------------------------
-class CMakeBuildSystem final : public ProjectExplorer::BuildSystem
+class CMAKE_EXPORT CMakeBuildSystem final : public ProjectExplorer::BuildSystem
{
Q_OBJECT
@@ -157,7 +155,7 @@ private:
Utils::FilePaths *);
bool addTsFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths,
Utils::FilePaths *);
- bool renameFile(CMakeTargetNode *context,
+ bool renameFile(Internal::CMakeTargetNode *context,
const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath, bool &shouldRunCMake);
@@ -175,10 +173,10 @@ private:
};
void reparse(int reparseParameters);
QString reparseParametersString(int reparseFlags);
- void setParametersAndRequestParse(const BuildDirParameters &parameters,
+ void setParametersAndRequestParse(const Internal::BuildDirParameters &parameters,
const int reparseParameters);
- bool mustApplyConfigurationChangesArguments(const BuildDirParameters &parameters) const;
+ bool mustApplyConfigurationChangesArguments(const Internal::BuildDirParameters &parameters) const;
// State handling:
// Parser states:
@@ -208,7 +206,7 @@ private:
void wireUpConnections();
- void ensureBuildDirectory(const BuildDirParameters &parameters);
+ void ensureBuildDirectory(const Internal::BuildDirParameters &parameters);
void stopParsingAndClearState();
void becameDirty();
@@ -243,7 +241,7 @@ private:
ProjectExplorer::ProjectUpdater *m_cppCodeModelUpdater = nullptr;
QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers;
QList<CMakeBuildTarget> m_buildTargets;
- QSet<CMakeFileInfo> m_cmakeFiles;
+ QSet<Internal::CMakeFileInfo> m_cmakeFiles;
QHash<QString, Utils::Link> m_cmakeSymbolsHash;
QHash<QString, Utils::Link> m_dotCMakeFilesHash;
QHash<QString, Utils::Link> m_findPackagesFilesHash;
@@ -254,9 +252,9 @@ private:
QHash<QString, ProjectFileArgumentPosition> m_filesToBeRenamed;
// Parsing state:
- BuildDirParameters m_parameters;
+ Internal::BuildDirParameters m_parameters;
int m_reparseParameters = REPARSE_DEFAULT;
- FileApiReader m_reader;
+ Internal::FileApiReader m_reader;
mutable bool m_isHandlingError = false;
// CTest integration
@@ -271,5 +269,4 @@ private:
QString m_warning;
};
-} // namespace Internal
} // namespace CMakeProjectManager
diff --git a/src/plugins/coco/CMakeLists.txt b/src/plugins/coco/CMakeLists.txt
index bb5c5f165ac..71693fac7ad 100644
--- a/src/plugins/coco/CMakeLists.txt
+++ b/src/plugins/coco/CMakeLists.txt
@@ -1,7 +1,51 @@
add_qtc_plugin(Coco
- PLUGIN_DEPENDS Core LanguageClient
+ PLUGIN_DEPENDS
+ QtCreator::Core
+ QtCreator::LanguageClient
+ QtCreator::ProjectExplorer
+ QtCreator::QmakeProjectManager
+ DEPENDS
+ QtCreator::CMakeProjectManager
+ QtCreator::ExtensionSystem
SOURCES
- cocolanguageclient.cpp cocolanguageclient.h
+ Coco.json.in
+ cocobuild/buildsettings.cpp
+ cocobuild/buildsettings.h
+ cocobuild/cmakemodificationfile.cpp
+ cocobuild/cmakemodificationfile.h
+ cocobuild/cococmakesettings.cpp
+ cocobuild/cococmakesettings.h
+ cocobuild/cocobuildstep.cpp
+ cocobuild/cocobuildstep.h
+ cocobuild/cocoprojectwidget.cpp
+ cocobuild/cocoprojectwidget.h
+ cocobuild/modificationfile.cpp
+ cocobuild/modificationfile.h
+ cocobuild/qmakefeaturefile.cpp
+ cocobuild/qmakefeaturefile.h
+ cocobuild/cocoqmakesettings.cpp
+ cocobuild/cocoqmakesettings.h
+ cocolanguageclient.cpp
+ cocolanguageclient.h
cocoplugin.cpp
+ cocoplugin.qrc
+ cocoplugin_global.h
+ cocopluginconstants.h
cocotr.h
+ common.cpp
+ common.h
+ files/cocoplugin-clang.cmake
+ files/cocoplugin-gcc.cmake
+ files/cocoplugin-visualstudio.cmake
+ files/cocoplugin.cmake
+ files/cocoplugin.prf
+ images/SquishCoco_48x48.png
+ settings/cocoinstallation.cpp
+ settings/cocoinstallation.h
+ settings/cocoprojectsettingswidget.cpp
+ settings/cocoprojectsettingswidget.h
+ settings/globalsettings.cpp
+ settings/globalsettings.h
+ settings/globalsettingspage.cpp
+ settings/globalsettingspage.h
)
diff --git a/src/plugins/coco/Coco.json.in b/src/plugins/coco/Coco.json.in
index 3c1b411b0b3..e9eb090aa42 100644
--- a/src/plugins/coco/Coco.json.in
+++ b/src/plugins/coco/Coco.json.in
@@ -15,9 +15,10 @@
"",
"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html."
],
- "Description" : "Access the Coco CoverageBrowser.",
+ "Description" : "Configure Coco and access the results",
"LongDescription" : [
- "View the results from the Coco CoverageBrowser to make tests more efficient and complete."
+ "Configure CMake and QMake projects for code coverage.",
+ "Highlight the source code according to the measured coverage."
],
"Url" : "https://www.qt.io",
"DocumentationUrl" : "https://doc.qt.io/qtcreator/creator-coco.html",
diff --git a/src/plugins/coco/coco.qbs b/src/plugins/coco/coco.qbs
index 6e32634eaf4..abfe8e939f8 100644
--- a/src/plugins/coco/coco.qbs
+++ b/src/plugins/coco/coco.qbs
@@ -5,14 +5,57 @@ QtcPlugin {
Depends { name: "Core" }
Depends { name: "LanguageClient" }
+ Depends { name: "CMakeProjectManager" }
+ Depends { name: "ExtensionSystem" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "QmakeProjectManager" }
Depends { name: "TextEditor" }
+ Depends { name: "Utils" }
Depends { name: "Qt"; submodules: ["widgets"] }
files: [
- "cocoplugin.cpp",
+ "cocobuild/buildsettings.cpp",
+ "cocobuild/buildsettings.h",
+ "cocobuild/cmakemodificationfile.cpp",
+ "cocobuild/cmakemodificationfile.h",
+ "cocobuild/cocobuildstep.cpp",
+ "cocobuild/cocobuildstep.h",
+ "cocobuild/cococmakesettings.cpp",
+ "cocobuild/cococmakesettings.h",
+ "cocobuild/cocoprojectwidget.cpp",
+ "cocobuild/cocoprojectwidget.h",
+ "cocobuild/cocoprojectwidget.ui",
+ "cocobuild/cocoqmakesettings.cpp",
+ "cocobuild/cocoqmakesettings.h",
+ "cocobuild/modificationfile.cpp",
+ "cocobuild/modificationfile.h",
+ "cocobuild/qmakefeaturefile.cpp",
+ "cocobuild/qmakefeaturefile.h",
"cocolanguageclient.cpp",
"cocolanguageclient.h",
+ "cocoplugin.cpp",
+ "cocoplugin.qrc",
+ "cocoplugin_global.h",
+ "cocopluginconstants.h",
+ "cocotr.h",
+ "common.cpp",
+ "common.h",
+ "files/cocoplugin-clang.cmake",
+ "files/cocoplugin-gcc.cmake",
+ "files/cocoplugin-visualstudio.cmake",
+ "files/cocoplugin.cmake",
+ "files/cocoplugin.prf",
+ "images/SquishCoco_48x48.png",
+ "settings/cocoinstallation.cpp",
+ "settings/cocoinstallation.h",
+ "settings/cocoprojectsettingswidget.cpp",
+ "settings/cocoprojectsettingswidget.h",
+ "settings/globalsettings.cpp",
+ "settings/globalsettings.h",
+ "settings/globalsettingspage.cpp",
+ "settings/globalsettingspage.h",
+ "settings/globalsettingspage.ui",
]
}
diff --git a/src/plugins/coco/cocobuild/buildsettings.cpp b/src/plugins/coco/cocobuild/buildsettings.cpp
new file mode 100644
index 00000000000..c905b6806d1
--- /dev/null
+++ b/src/plugins/coco/cocobuild/buildsettings.cpp
@@ -0,0 +1,100 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "buildsettings.h"
+
+#include "cocobuildstep.h"
+#include "cococmakesettings.h"
+#include "cocoqmakesettings.h"
+#include "modificationfile.h"
+
+#include <projectexplorer/target.h>
+#include <cmakeprojectmanager/cmakeprojectconstants.h>
+#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
+
+namespace Coco::Internal {
+
+bool BuildSettings::supportsBuildConfig(const ProjectExplorer::BuildConfiguration &config)
+{
+ return config.id() == QmakeProjectManager::Constants::QMAKE_BC_ID
+ || config.id() == CMakeProjectManager::Constants::CMAKE_BUILDCONFIGURATION_ID;
+}
+
+BuildSettings *BuildSettings::createdFor(const ProjectExplorer::BuildConfiguration &config)
+{
+ if (config.id() == QmakeProjectManager::Constants::QMAKE_BC_ID)
+ return new CocoQMakeSettings{config.project()};
+ else if (config.id() == CMakeProjectManager::Constants::CMAKE_BUILDCONFIGURATION_ID)
+ return new CocoCMakeSettings{config.project()};
+ else
+ return nullptr;
+}
+
+BuildSettings::BuildSettings(ModificationFile &featureFile, ProjectExplorer::Project *project)
+ : m_featureFile{featureFile}
+ , m_project{*project}
+{
+ // Do not use m_featureFile in the constructor; it may not yet be valid.
+}
+
+void BuildSettings::connectToBuildStep(CocoBuildStep *step) const
+{
+ connect(
+ activeTarget(),
+ &ProjectExplorer::Target::buildSystemUpdated,
+ step,
+ &CocoBuildStep::buildSystemUpdated);
+}
+
+bool BuildSettings::enabled() const
+{
+ return m_enabled;
+}
+
+const QStringList &BuildSettings::options() const
+{
+ return m_featureFile.options();
+}
+
+const QStringList &BuildSettings::tweaks() const
+{
+ return m_featureFile.tweaks();
+}
+
+bool BuildSettings::hasTweaks() const
+{
+ return !m_featureFile.tweaks().isEmpty();
+}
+
+QString BuildSettings::featureFilenName() const
+{
+ return m_featureFile.fileName();
+}
+
+QString BuildSettings::featureFilePath() const
+{
+ return m_featureFile.nativePath();
+}
+
+void BuildSettings::provideFile()
+{
+ if (!m_featureFile.exists())
+ write("", "");
+}
+
+QString BuildSettings::tableRow(const QString &name, const QString &value) const
+{
+ return QString("<tr><td><b>%1</b></td><td>%2</td></tr>").arg(name, value);
+}
+
+void BuildSettings::setEnabled(bool enabled)
+{
+ m_enabled = enabled;
+}
+
+ProjectExplorer::Target *BuildSettings::activeTarget() const
+{
+ return m_project.activeTarget();
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/buildsettings.h b/src/plugins/coco/cocobuild/buildsettings.h
new file mode 100644
index 00000000000..10cf8e96925
--- /dev/null
+++ b/src/plugins/coco/cocobuild/buildsettings.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <QObject>
+#include <QStringList>
+
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+namespace ProjectExplorer {
+class BuildConfiguration;
+class Project;
+class Target;
+}
+
+namespace Coco::Internal {
+
+class CocoBuildStep;
+class CocoProjectWidget;
+class ModificationFile;
+
+class BuildSettings : public QObject
+{
+ Q_OBJECT
+public:
+ static bool supportsBuildConfig(const ProjectExplorer::BuildConfiguration &config);
+ static BuildSettings *createdFor(const ProjectExplorer::BuildConfiguration &config);
+
+ explicit BuildSettings(ModificationFile &featureFile, ProjectExplorer::Project *project);
+ virtual ~BuildSettings() {}
+
+ void connectToBuildStep(CocoBuildStep *step) const;
+ virtual void connectToProject(CocoProjectWidget *) const {}
+ virtual void read() = 0;
+ bool enabled() const;
+ virtual bool validSettings() const = 0;
+ virtual void setCoverage(bool on) = 0;
+
+ virtual QString saveButtonText() const = 0;
+ virtual void reconfigure() {};
+ virtual void stopReconfigure() {};
+ virtual bool needsReconfigure() const { return false; }
+
+ virtual QString configChanges() const = 0;
+
+ const QStringList &options() const;
+ const QStringList &tweaks() const;
+ virtual QString projectDirectory() const = 0;
+
+ bool hasTweaks() const;
+ QString featureFilenName() const;
+ QString featureFilePath() const;
+
+ virtual void write(const QString &options, const QString &tweaks) = 0;
+ void provideFile();
+
+protected:
+ QString tableRow(const QString &name, const QString &value) const;
+ void setEnabled(bool enabled);
+ ProjectExplorer::Target *activeTarget() const;
+
+private:
+ ModificationFile &m_featureFile;
+ ProjectExplorer::Project &m_project;
+ bool m_enabled = false;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cmakemodificationfile.cpp b/src/plugins/coco/cocobuild/cmakemodificationfile.cpp
new file mode 100644
index 00000000000..9813e92be0c
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cmakemodificationfile.cpp
@@ -0,0 +1,91 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cmakemodificationfile.h"
+
+#include "cocopluginconstants.h"
+
+#include <projectexplorer/project.h>
+#include <projectexplorer/target.h>
+#include <projectexplorer/buildconfiguration.h>
+
+namespace Coco::Internal {
+
+using namespace ProjectExplorer;
+
+static const char flagsSetting[] = "set(coverage_flags_list\n";
+static const char tweaksLine[] = "# User-supplied settings follow here:\n";
+
+CMakeModificationFile::CMakeModificationFile(Project *project)
+ : m_project{project}
+{}
+
+QString CMakeModificationFile::fileName() const
+{
+ return QString(Constants::PROFILE_NAME) + ".cmake";
+}
+
+void CMakeModificationFile::setProjectDirectory(const Utils::FilePath &projectDirectory)
+{
+ setFilePath(projectDirectory.pathAppended(fileName()));
+}
+
+QStringList CMakeModificationFile::defaultModificationFile() const
+{
+ return contentOf(":/cocoplugin/files/cocoplugin.cmake");
+}
+
+void CMakeModificationFile::read()
+{
+ clear();
+ QStringList file = currentModificationFile();
+
+ {
+ QStringList options;
+ int i = file.indexOf(flagsSetting);
+ if (i != -1) {
+ i++;
+ while (i < file.size() && !file[i].startsWith(')')) {
+ options += file[i].trimmed();
+ i++;
+ }
+ }
+ setOptions(options);
+ }
+ {
+ QStringList tweaks;
+ int i = file.indexOf(tweaksLine);
+ if (i != -1) {
+ i++;
+ while (i < file.size()) {
+ tweaks += file[i].chopped(1);
+ i++;
+ }
+ }
+ setTweaks(tweaks);
+ }
+}
+
+void CMakeModificationFile::write() const
+{
+ QFile out(nativePath());
+ out.open(QIODevice::WriteOnly | QIODevice::Text);
+
+ QTextStream outStream(&out);
+ for (QString &line : defaultModificationFile()) {
+ outStream << line;
+
+ if (line.startsWith(flagsSetting)) {
+ for (const QString &option : options()) {
+ QString line = " " + option + '\n';
+ outStream << line;
+ }
+ }
+ }
+ for (const QString &line : tweaks())
+ outStream << line << "\n";
+
+ out.close();
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cmakemodificationfile.h b/src/plugins/coco/cocobuild/cmakemodificationfile.h
new file mode 100644
index 00000000000..485c5fa4326
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cmakemodificationfile.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "modificationfile.h"
+
+namespace ProjectExplorer {
+class Project;
+}
+
+namespace Coco::Internal {
+
+class CMakeModificationFile : public ModificationFile
+{
+public:
+ CMakeModificationFile(ProjectExplorer::Project *project);
+
+ void read() override;
+ void write() const override;
+
+ QString fileName() const override;
+ void setProjectDirectory(const Utils::FilePath &projectDirectory) override;
+
+protected:
+ QStringList defaultModificationFile() const override;
+
+private:
+ ProjectExplorer::Project *m_project;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cocobuildstep.cpp b/src/plugins/coco/cocobuild/cocobuildstep.cpp
new file mode 100644
index 00000000000..6a71157e7b6
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cocobuildstep.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cocobuildstep.h"
+
+#include "cocopluginconstants.h"
+#include "cocotr.h"
+
+#include <cmakeprojectmanager/cmakeprojectconstants.h>
+#include <projectexplorer/projectexplorerconstants.h>
+#include <settings/cocoinstallation.h>
+#include <settings/globalsettingspage.h>
+#include <solutions/tasking/tasktree.h>
+#include <utils/layoutbuilder.h>
+#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
+
+#include <QPushButton>
+
+namespace Coco::Internal {
+
+using namespace ProjectExplorer;
+
+QMakeStepFactory::QMakeStepFactory()
+{
+ registerStep<CocoBuildStep>(Utils::Id{Constants::COCO_STEP_ID});
+ setSupportedProjectType(QmakeProjectManager::Constants::QMAKEPROJECT_ID);
+ setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
+ setRepeatable(false);
+}
+
+CMakeStepFactory::CMakeStepFactory()
+{
+ registerStep<CocoBuildStep>(Utils::Id{Constants::COCO_STEP_ID});
+ setSupportedProjectType(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
+ setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
+ setRepeatable(false);
+}
+
+CocoBuildStep *CocoBuildStep::create(BuildConfiguration *buildConfig)
+{
+ // The "new" command creates a small memory leak which we can tolerate.
+ return new CocoBuildStep(
+ new BuildStepList(buildConfig, Constants::COCO_STEP_ID), Utils::Id(Constants::COCO_STEP_ID));
+}
+
+CocoBuildStep::CocoBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id)
+ : BuildStep(bsl, id)
+ , m_reconfigureButton{new QPushButton}
+{}
+
+bool CocoBuildStep::init()
+{
+ return true;
+}
+
+void CocoBuildStep::buildSystemUpdated()
+{
+ updateDisplay();
+}
+
+void CocoBuildStep::onReconfigureButtonClicked()
+{
+ m_valid = !m_valid;
+
+ setSummaryText(Tr::tr("Coco Code Coverage: Reconfiguring..."));
+ m_reconfigureButton->setEnabled(false);
+ m_buildSettings->setCoverage(m_valid);
+ m_buildSettings->provideFile();
+ m_buildSettings->reconfigure();
+}
+
+QWidget *CocoBuildStep::createConfigWidget()
+{
+ connect(
+ m_reconfigureButton,
+ &QPushButton::clicked,
+ this,
+ &CocoBuildStep::onReconfigureButtonClicked);
+
+ Layouting::Form builder;
+ builder.addRow({m_reconfigureButton, new QLabel});
+ builder.setNoMargins();
+
+ return builder.emerge();
+}
+
+void CocoBuildStep::updateDisplay()
+{
+ CocoInstallation coco;
+ if (!coco.isValid()) {
+ setSummaryText("<i>" + Tr::tr("Coco Code Coverage: No working Coco installation") + "</i>");
+ m_reconfigureButton->setEnabled(false);
+ return;
+ }
+
+ m_valid = m_buildSettings->validSettings();
+
+ if (m_valid) {
+ setSummaryText("<b>" + Tr::tr("Coco Code Coverage: Enabled") + "</b>");
+ m_reconfigureButton->setText(Tr::tr("Disable Coverage"));
+ } else {
+ setSummaryText(Tr::tr("Coco Code Coverage: Disabled"));
+ m_reconfigureButton->setText(Tr::tr("Enable Coverage"));
+ }
+
+ m_reconfigureButton->setEnabled(true);
+}
+
+void CocoBuildStep::display(BuildConfiguration *buildConfig)
+{
+ Q_ASSERT( m_buildSettings.isNull() );
+
+ m_buildSettings = BuildSettings::createdFor(*buildConfig);
+ m_buildSettings->read();
+ m_buildSettings->connectToBuildStep(this);
+
+ setImmutable(true);
+ updateDisplay();
+}
+
+Tasking::GroupItem CocoBuildStep::runRecipe()
+{
+ return Tasking::GroupItem({});
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cocobuildstep.h b/src/plugins/coco/cocobuild/cocobuildstep.h
new file mode 100644
index 00000000000..15b48565fbc
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cocobuildstep.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "buildsettings.h"
+
+#include <projectexplorer/buildstep.h>
+#include <projectexplorer/buildsteplist.h>
+#include <projectexplorer/project.h>
+
+#include <QPointer>
+
+class QPushButton;
+
+namespace Coco::Internal {
+
+class QMakeStepFactory: public ProjectExplorer::BuildStepFactory
+{
+public:
+ QMakeStepFactory();
+};
+
+class CMakeStepFactory: public ProjectExplorer::BuildStepFactory
+{
+public:
+ CMakeStepFactory();
+};
+
+class CocoBuildStep : public ProjectExplorer::BuildStep
+{
+ Q_OBJECT
+public:
+ static CocoBuildStep *create(ProjectExplorer::BuildConfiguration *buildConfig);
+
+ CocoBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
+
+ bool init() override;
+ void display(ProjectExplorer::BuildConfiguration *buildConfig);
+
+public slots:
+ void buildSystemUpdated();
+
+private slots:
+ void onReconfigureButtonClicked();
+
+protected:
+ QWidget *createConfigWidget() override;
+
+private:
+ void updateDisplay();
+ Tasking::GroupItem runRecipe() override;
+
+ QPointer<BuildSettings> m_buildSettings;
+ bool m_valid;
+ QPushButton *m_reconfigureButton;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cococmakesettings.cpp b/src/plugins/coco/cocobuild/cococmakesettings.cpp
new file mode 100644
index 00000000000..ceb3ec8012a
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cococmakesettings.cpp
@@ -0,0 +1,167 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cococmakesettings.h"
+
+#include "cocobuild/cocoprojectwidget.h"
+#include "cocotr.h"
+#include "common.h"
+
+#include <cmakeprojectmanager/cmakebuildconfiguration.h>
+#include <cmakeprojectmanager/cmakebuildsystem.h>
+#include <projectexplorer/target.h>
+
+using namespace ProjectExplorer;
+using namespace CMakeProjectManager;
+
+namespace Coco::Internal {
+
+CocoCMakeSettings::CocoCMakeSettings(Project *project)
+ : BuildSettings{m_featureFile, project}
+ , m_featureFile{project}
+{}
+
+CocoCMakeSettings::~CocoCMakeSettings() {}
+
+void CocoCMakeSettings::connectToProject(CocoProjectWidget *parent) const
+{
+ connect(
+ activeTarget(), &Target::buildSystemUpdated, parent, &CocoProjectWidget::buildSystemUpdated);
+ connect(
+ qobject_cast<CMakeProjectManager::CMakeBuildSystem *>(activeTarget()->buildSystem()),
+ &CMakeProjectManager::CMakeBuildSystem::errorOccurred,
+ parent,
+ &CocoProjectWidget::configurationErrorOccurred);
+}
+
+void CocoCMakeSettings::read()
+{
+ setEnabled(false);
+ if (Target *target = activeTarget()) {
+ if ((m_buildConfig = qobject_cast<CMakeBuildConfiguration *>(
+ target->activeBuildConfiguration()))) {
+ m_featureFile.setProjectDirectory(m_buildConfig->project()->projectDirectory());
+ m_featureFile.read();
+ setEnabled(true);
+ }
+ }
+}
+
+QString CocoCMakeSettings::initialCacheOption() const
+{
+ return QString("-C%1").arg(m_featureFile.nativePath());
+}
+
+bool CocoCMakeSettings::hasInitialCacheOption(const QStringList &args) const
+{
+ for (int i = 0; i < args.length(); ++i) {
+ if (args[i] == "-C" && i + 1 < args.length() && args[i + 1] == m_featureFile.nativePath())
+ return true;
+
+ if (args[i] == initialCacheOption())
+ return true;
+ }
+
+ return false;
+}
+
+bool CocoCMakeSettings::validSettings() const
+{
+ return enabled() && m_featureFile.exists()
+ && hasInitialCacheOption(m_buildConfig->additionalCMakeArguments());
+}
+
+void CocoCMakeSettings::setCoverage(bool on)
+{
+ if (!enabled())
+ return;
+
+ auto values = m_buildConfig->initialCMakeOptions();
+ QStringList args = Utils::filtered(values, [&](const QString &option) {
+ return !(option.startsWith("-C") && option.endsWith(featureFilenName()));
+ });
+
+ if (on)
+ args << QString("-C%1").arg(m_featureFile.nativePath());
+
+ m_buildConfig->setInitialCMakeArguments(args);
+}
+
+QString CocoCMakeSettings::saveButtonText() const
+{
+ return Tr::tr("Save && Re-configure");
+}
+
+QString CocoCMakeSettings::configChanges() const
+{
+ return "<table><tbody>"
+ + tableRow("Additional CMake options: ", maybeQuote(initialCacheOption()))
+ + tableRow("Initial cache script: ", maybeQuote(featureFilePath())) + "</tbody></table>";
+}
+
+void CocoCMakeSettings::reconfigure()
+{
+ if (!enabled())
+ return;
+
+ m_buildConfig->cmakeBuildSystem()->clearCMakeCache();
+ m_buildConfig->updateInitialCMakeArguments();
+ m_buildConfig->cmakeBuildSystem()->runCMake();
+}
+
+void Coco::Internal::CocoCMakeSettings::stopReconfigure()
+{
+ if (enabled())
+ m_buildConfig->cmakeBuildSystem()->stopCMakeRun();
+}
+
+QString CocoCMakeSettings::projectDirectory() const
+{
+ if (enabled())
+ return m_buildConfig->project()->projectDirectory().path();
+ else
+ return "";
+}
+
+void CocoCMakeSettings::write(const QString &options, const QString &tweaks)
+{
+ m_featureFile.setOptions(options);
+ m_featureFile.setTweaks(tweaks);
+ m_featureFile.write();
+
+ writeToolchainFile(":/cocoplugin/files/cocoplugin-gcc.cmake");
+ writeToolchainFile(":/cocoplugin/files/cocoplugin-clang.cmake");
+ writeToolchainFile(":/cocoplugin/files/cocoplugin-visualstudio.cmake");
+}
+
+void CocoCMakeSettings::writeToolchainFile(const QString &internalPath)
+{
+ const Utils::FilePath projectDirectory = m_buildConfig->project()->projectDirectory();
+
+ QFile internalFile{internalPath};
+ internalFile.open(QIODeviceBase::ReadOnly);
+ const QByteArray internalContent = internalFile.readAll();
+
+ const QString fileName = Utils::FilePath::fromString(internalPath).fileName();
+ const Utils::FilePath toolchainPath{projectDirectory.pathAppended(fileName)};
+ const QString toolchainNative = toolchainPath.nativePath();
+
+ if (toolchainPath.exists()) {
+ QFile currentFile{toolchainNative};
+ currentFile.open(QIODeviceBase::ReadOnly);
+
+ QByteArray currentContent = currentFile.readAll();
+ if (internalContent == currentContent)
+ return;
+
+ logSilently(Tr::tr("Overwrite file %1").arg(maybeQuote(toolchainNative)));
+ } else
+ logSilently(Tr::tr("Write file %1").arg(maybeQuote(toolchainNative)));
+
+ QFile out{toolchainNative};
+ out.open(QIODeviceBase::WriteOnly);
+ out.write(internalContent);
+ out.close();
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cococmakesettings.h b/src/plugins/coco/cocobuild/cococmakesettings.h
new file mode 100644
index 00000000000..8535744b9f7
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cococmakesettings.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "cocobuild/buildsettings.h"
+#include "cocobuild/cmakemodificationfile.h"
+
+#include <QObject>
+#include <QStringList>
+
+namespace CMakeProjectManager {
+class CMakeBuildConfiguration;
+class CMakeConfig;
+}
+
+namespace Coco::Internal {
+
+class CocoProjectWidget;
+
+class CocoCMakeSettings : public BuildSettings
+{
+ Q_OBJECT
+public:
+ explicit CocoCMakeSettings(ProjectExplorer::Project *project);
+ ~CocoCMakeSettings() override;
+
+ void connectToProject(CocoProjectWidget *parent) const override;
+ void read() override;
+ bool validSettings() const override;
+ void setCoverage(bool on) override;
+
+ QString saveButtonText() const override;
+ QString configChanges() const override;
+ bool needsReconfigure() const override { return true; }
+ void reconfigure() override;
+ void stopReconfigure() override;
+
+ QString projectDirectory() const override;
+ void write(const QString &options, const QString &tweaks) override;
+
+private:
+ bool hasInitialCacheOption(const QStringList &args) const;
+ QString initialCacheOption() const;
+ void writeToolchainFile(const QString &internalPath);
+
+ CMakeProjectManager::CMakeBuildConfiguration *m_buildConfig;
+ CMakeModificationFile m_featureFile;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cocoprojectwidget.cpp b/src/plugins/coco/cocobuild/cocoprojectwidget.cpp
new file mode 100644
index 00000000000..7cf55610eba
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cocoprojectwidget.cpp
@@ -0,0 +1,336 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cocoprojectwidget.h"
+
+#include "buildsettings.h"
+#include "cocopluginconstants.h"
+#include "cocotr.h"
+#include "common.h"
+#include "settings/globalsettingspage.h"
+
+#include <coreplugin/icore.h>
+#include <projectexplorer/buildsystem.h>
+
+#include <QFileDialog>
+#include <QMessageBox>
+
+using namespace ProjectExplorer;
+using namespace Core;
+
+namespace Coco::Internal {
+
+CocoProjectWidget::CocoProjectWidget(Project *project, const BuildConfiguration &buildConfig)
+ : m_project{project}
+ , m_buildConfigurationName{buildConfig.displayName()}
+{
+ using namespace Layouting;
+ using namespace Utils;
+
+ m_configerrorLabel.setVisible(false);
+ m_configerrorLabel.setIconType(InfoLabel::Error);
+ Label docLink(
+ QString(
+ "<a href=\"https://doc.qt.io/coco/coveragescanner-command-line-arguments.html\">%1</a>")
+ .arg(Tr::tr("Documentation")));
+ docLink.setOpenExternalLinks(true);
+ m_optionEdit.setDisplayStyle(StringAspect::TextEditDisplay);
+ m_tweaksEdit.setDisplayStyle(StringAspect::TextEditDisplay);
+ m_revertButton.setText(Tr::tr("Revert"));
+ QFont bold;
+ bold.setBold(true);
+ m_saveButton.setFont(bold);
+
+ m_coverageGroupbox
+ = {groupChecker(m_coverageGroupBoxEnabled.groupChecker()),
+ Column{
+ Row{Tr::tr("CoverageScanner Options"), st, docLink},
+ m_optionEdit,
+ Row{PushButton{
+ text(Tr::tr("Exclude File...")),
+ onClicked([&] { onExcludeFileButtonClicked(); }, this)},
+ PushButton{
+ text(Tr::tr("Exclude Directory...")),
+ onClicked([&] { onExcludeDirButtonClicked(); }, this)},
+ m_tweaksButton,
+ st},
+ m_tweaksDescriptionLabel,
+ m_tweaksEdit,
+ Row{Tr::tr("These settings are stored in"), m_fileNameLabel, st},
+ Group{title(Tr::tr("Changed Build Settings")), Column{m_changesText}}}};
+
+ Column{
+ m_configerrorLabel,
+ m_coverageGroupbox,
+ Row{m_messageLabel, st, &m_revertButton, &m_saveButton}}
+ .attachTo(this);
+
+ m_buildSettings = BuildSettings::createdFor(buildConfig);
+ m_buildSettings->connectToProject(this);
+
+ readSelectionDir();
+ reloadSettings();
+
+ m_fileNameLabel.setValue(m_buildSettings->featureFilePath());
+ m_tweaksDescriptionLabel.setText(
+ Tr::tr("Code for the end of the file \"%1\" to override the built-in declarations."
+ " Only needed in special cases.")
+ .arg(m_buildSettings->featureFilenName()));
+ setTweaksVisible(m_buildSettings->hasTweaks());
+ clearMessageLabel();
+
+ connect(&m_coverageGroupBoxEnabled, &BoolAspect::changed, this, &CocoProjectWidget::onCoverageGroupBoxClicked);
+
+ connect(&m_optionEdit, &StringAspect::changed, this, &CocoProjectWidget::onTextChanged);
+ connect(&m_tweaksEdit, &StringAspect::changed, this, &CocoProjectWidget::onTextChanged);
+ m_tweaksButton.onClicked([&] { onTweaksButtonClicked(); }, this);
+
+ connect(&m_revertButton, &QPushButton::clicked, this, &CocoProjectWidget::onRevertButtonClicked);
+ connect(&m_saveButton, &QPushButton::clicked, this, &CocoProjectWidget::onSaveButtonClicked);
+
+ connect(GlobalSettingsPage::instance().widget(), &GlobalSettingsWidget::updateCocoDir, this, &CocoProjectWidget::reloadSettings);
+}
+
+// Read the build settings again and show them in the widget.
+void CocoProjectWidget::reloadSettings()
+{
+ m_buildSettings->read();
+ m_coverageGroupBoxEnabled.setValue(m_buildSettings->validSettings(), Utils::BaseAspect::BeQuiet);
+ m_coverageGroupbox.setTitle(
+ Tr::tr("Enable code coverage for build configuration \"%1\"").arg(m_buildConfigurationName));
+
+ m_optionEdit.setValue(m_buildSettings->options().join('\n'), Utils::BaseAspect::BeQuiet);
+ m_tweaksEdit.setValue(m_buildSettings->tweaks().join('\n'), Utils::BaseAspect::BeQuiet);
+
+ setState(configDone);
+ displayChanges();
+
+ const bool valid = m_coco.isValid();
+ m_configerrorLabel.setVisible(!valid);
+ if (!valid) {
+ m_configerrorLabel.setText(
+ Tr::tr("Coco is not installed correctly: \"%1\"").arg(m_coco.errorMessage()));
+ }
+}
+
+void CocoProjectWidget::showEvent(QShowEvent *event)
+{
+ Q_UNUSED(event)
+ reloadSettings();
+}
+
+void CocoProjectWidget::buildSystemUpdated(ProjectExplorer::BuildSystem *bs)
+{
+ QString newBuildConfigurationName = bs->buildConfiguration()->displayName();
+
+ if (m_buildConfigurationName != newBuildConfigurationName) {
+ m_buildConfigurationName = newBuildConfigurationName;
+ logSilently(Tr::tr("Build Configuration changed to %1.").arg(newBuildConfigurationName));
+ reloadSettings();
+ } else if (m_configState == configRunning)
+ setState(configDone);
+}
+
+void CocoProjectWidget::configurationErrorOccurred(const QString &error)
+{
+ Q_UNUSED(error)
+
+ if (m_configState == configEdited) {
+ setMessageLabel(Utils::InfoLabel::Information, Tr::tr("Re-configuring stopped by user."));
+ setState(configStopped);
+ } else {
+ // The variable error seems to contain no usable information.
+ setMessageLabel(
+ Utils::InfoLabel::Error,
+ Tr::tr("Error when configuring with \"%1\". "
+ "Check General Messages for more information.")
+ .arg(m_buildSettings->featureFilenName()));
+ setState(configDone);
+ }
+}
+
+void CocoProjectWidget::setState(ConfigurationState state)
+{
+ m_configState = state;
+
+ switch (m_configState) {
+ case configDone:
+ m_saveButton.setText(m_buildSettings->saveButtonText());
+ m_saveButton.setEnabled(false);
+ m_revertButton.setEnabled(false);
+ break;
+ case configEdited:
+ m_saveButton.setText(m_buildSettings->saveButtonText());
+ m_saveButton.setEnabled(true);
+ m_revertButton.setEnabled(true);
+ break;
+ case configRunning:
+ m_saveButton.setText(Tr::tr("Stop Re-configuring"));
+ m_saveButton.setEnabled(true);
+ m_revertButton.setEnabled(false);
+ break;
+ case configStopped:
+ m_saveButton.setText(Tr::tr("Re-configure"));
+ m_saveButton.setEnabled(true);
+ m_revertButton.setEnabled(false);
+ break;
+ }
+}
+
+void CocoProjectWidget::readSelectionDir()
+{
+ QVariantMap settings = m_project->namedSettings(Constants::SETTINGS_NAME_KEY).toMap();
+
+ if (settings.contains(Constants::SELECTION_DIR_KEY))
+ m_selectionDirectory = settings[Constants::SELECTION_DIR_KEY].toString();
+ else
+ m_selectionDirectory = m_buildSettings->projectDirectory();
+}
+
+void CocoProjectWidget::writeSelectionDir(const QString &path)
+{
+ m_selectionDirectory = path;
+
+ QVariantMap settings;
+ settings[Constants::SELECTION_DIR_KEY] = path;
+
+ m_project->setNamedSettings(Constants::SETTINGS_NAME_KEY, settings);
+}
+
+void CocoProjectWidget::setTweaksVisible(bool on)
+{
+ if (on)
+ m_tweaksButton.setText(Tr::tr("Override <<"));
+ else
+ m_tweaksButton.setText(Tr::tr("Override >>"));
+
+ m_tweaksDescriptionLabel.setVisible(on);
+ m_tweaksEdit.setVisible(on);
+}
+
+void CocoProjectWidget::setMessageLabel(const Utils::InfoLabel::InfoType type, const QString &text)
+{
+ m_messageLabel.setText(text);
+ m_messageLabel.setIconType(type);
+}
+
+void CocoProjectWidget::clearMessageLabel()
+{
+ m_messageLabel.setText("");
+ m_messageLabel.setIconType(Utils::InfoLabel::None);
+}
+
+void Internal::CocoProjectWidget::onCoverageGroupBoxClicked()
+{
+ bool checked = m_coverageGroupBoxEnabled();
+
+ displayChanges();
+
+ if (!checked) {
+ m_buildSettings->setCoverage(false);
+ setState(configEdited);
+ return;
+ }
+
+ if (!m_coco.isValid()) {
+ m_coverageGroupBoxEnabled.setValue(false, Utils::BaseAspect::BeQuiet);
+
+ QMessageBox box;
+ box.setIcon(QMessageBox::Critical);
+ box.setText(Tr::tr("The Coco installation path is not set correctly."));
+ box.addButton(QMessageBox::Cancel);
+ QPushButton *editButton = box.addButton(Tr::tr("Edit"), QMessageBox::AcceptRole);
+ box.exec();
+
+ if (box.clickedButton() == editButton)
+ Core::ICore::showOptionsDialog(Constants::COCO_SETTINGS_PAGE_ID);
+
+ m_coverageGroupBoxEnabled.setValue(m_coco.isValid(), Utils::BaseAspect::BeQuiet);
+ } else
+ m_buildSettings->setCoverage(checked);
+
+ setState(configEdited);
+}
+
+void CocoProjectWidget::onSaveButtonClicked()
+{
+ if (m_configState == configRunning) {
+ logSilently(Tr::tr("Stop re-configuring"));
+ m_buildSettings->stopReconfigure();
+ setState(configEdited);
+ return;
+ }
+
+ QString options = m_optionEdit();
+ QString tweaks = m_tweaksEdit();
+ clearMessageLabel();
+
+ logSilently(Tr::tr("Write file \"%1\"").arg(m_buildSettings->featureFilePath()));
+ m_buildSettings->write(options, tweaks);
+
+ if (m_buildSettings->needsReconfigure()) {
+ logSilently(Tr::tr("Re-configure"));
+ setState(configRunning);
+ m_buildSettings->reconfigure();
+ } else
+ setState(configDone);
+}
+
+void CocoProjectWidget::onRevertButtonClicked()
+{
+ clearMessageLabel();
+ logSilently(Tr::tr("Reload file \"%1\"").arg(m_buildSettings->featureFilePath()));
+ reloadSettings();
+}
+
+void CocoProjectWidget::onTextChanged()
+{
+ setState(configEdited);
+}
+
+void CocoProjectWidget::displayChanges()
+{
+ m_changesText.setValue(m_buildSettings->configChanges());
+}
+
+void CocoProjectWidget::addCocoOption(QString option)
+{
+ m_optionEdit.setValue(m_optionEdit() + "\n" + option);
+}
+
+void CocoProjectWidget::onExcludeFileButtonClicked()
+{
+ QString fileName = QFileDialog::getOpenFileName(
+ this, Tr::tr("File to Exclude from Instrumentation"), m_selectionDirectory);
+ if (fileName.isEmpty())
+ return;
+
+ const auto fileNameInfo = Utils::FilePath::fromString(fileName);
+ addCocoOption("--cs-exclude-file-abs-wildcard=" + maybeQuote("*/" + fileNameInfo.fileName()));
+
+ writeSelectionDir(fileNameInfo.path());
+}
+
+void CocoProjectWidget::onExcludeDirButtonClicked()
+{
+ QString path = QFileDialog::getExistingDirectory(
+ this, Tr::tr("Directory to Exclude from Instrumentation"), m_selectionDirectory);
+ if (path.isEmpty())
+ return;
+
+ const QString projectDir = m_buildSettings->projectDirectory();
+ if (path.startsWith(projectDir))
+ // Make it a relative path with "*/" at the beginnig.
+ path = "*/" + path.arg(path.mid(projectDir.size()));
+
+ addCocoOption("--cs-exclude-file-abs-wildcard=" + maybeQuote(path));
+
+ writeSelectionDir(path);
+}
+
+void Internal::CocoProjectWidget::onTweaksButtonClicked()
+{
+ setTweaksVisible(!m_tweaksEdit.isVisible());
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cocoprojectwidget.h b/src/plugins/coco/cocobuild/cocoprojectwidget.h
new file mode 100644
index 00000000000..b7f2cf4e12c
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cocoprojectwidget.h
@@ -0,0 +1,81 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "buildsettings.h"
+#include "projectexplorer/buildconfiguration.h"
+#include "settings/cocoinstallation.h"
+#include <utils/aspects.h>
+#include <utils/layoutbuilder.h>
+
+#include <QPointer>
+#include <QPushButton>
+#include <QWidget>
+
+namespace ProjectExplorer {
+class Project;
+}
+
+namespace Coco::Internal {
+
+class CocoProjectWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ enum ConfigurationState { configDone, configEdited, configRunning, configStopped };
+
+ explicit CocoProjectWidget(
+ ProjectExplorer::Project *project, const ProjectExplorer::BuildConfiguration &buildConfig);
+
+protected:
+ void showEvent(QShowEvent *event) override;
+
+public slots:
+ void buildSystemUpdated(ProjectExplorer::BuildSystem *bs);
+ void configurationErrorOccurred(const QString &error);
+
+private slots:
+ void onCoverageGroupBoxClicked();
+
+ void onSaveButtonClicked();
+ void onRevertButtonClicked();
+ void onExcludeFileButtonClicked();
+ void onExcludeDirButtonClicked();
+ void onTweaksButtonClicked();
+
+ void onTextChanged();
+
+private:
+ void displayChanges();
+ void reloadSettings();
+ void addCocoOption(QString option);
+ void setState(ConfigurationState state);
+ void readSelectionDir();
+ void writeSelectionDir(const QString &path);
+ void setTweaksVisible(bool on);
+ void setMessageLabel(const Utils::InfoLabel::InfoType type, const QString &text);
+ void clearMessageLabel();
+
+ Utils::TextDisplay m_configerrorLabel;
+ Utils::BoolAspect m_coverageGroupBoxEnabled;
+ Layouting::Group m_coverageGroupbox{};
+ Utils::StringAspect m_optionEdit;
+ Layouting::PushButton m_tweaksButton{};
+ Utils::TextDisplay m_tweaksDescriptionLabel;
+ Utils::StringAspect m_tweaksEdit;
+ Utils::StringAspect m_fileNameLabel;
+ Utils::TextDisplay m_messageLabel;
+ QPushButton m_revertButton;
+ QPushButton m_saveButton;
+ Utils::StringAspect m_changesText;
+
+ ProjectExplorer::Project *m_project;
+ QPointer<BuildSettings> m_buildSettings;
+ QString m_selectionDirectory;
+ ConfigurationState m_configState = configDone;
+ QString m_buildConfigurationName;
+ CocoInstallation m_coco;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cocoqmakesettings.cpp b/src/plugins/coco/cocobuild/cocoqmakesettings.cpp
new file mode 100644
index 00000000000..c86b53ed2f7
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cocoqmakesettings.cpp
@@ -0,0 +1,188 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cocoqmakesettings.h"
+
+#include "cocobuild/cocoprojectwidget.h"
+#include "cocopluginconstants.h"
+#include "cocotr.h"
+#include "common.h"
+
+#include <projectexplorer/buildsteplist.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/target.h>
+#include <qmakeprojectmanager/qmakebuildconfiguration.h>
+#include <qmakeprojectmanager/qmakestep.h>
+
+using namespace ProjectExplorer;
+
+namespace Coco::Internal {
+
+CocoQMakeSettings::CocoQMakeSettings(Project *project)
+ : BuildSettings{m_featureFile, project}
+{}
+
+CocoQMakeSettings::~CocoQMakeSettings() {}
+
+void CocoQMakeSettings::read()
+{
+ setEnabled(false);
+ if (Target *target = activeTarget()) {
+ if ((m_buildConfig = qobject_cast<QmakeProjectManager::QmakeBuildConfiguration*>(target->activeBuildConfiguration()))) {
+ if (BuildStepList *buildSteps = m_buildConfig->buildSteps()) {
+ if ((m_qmakeStep = buildSteps->firstOfType<QmakeProjectManager::QMakeStep>())) {
+ m_featureFile.setProjectDirectory(m_buildConfig->project()->projectDirectory());
+ m_featureFile.read();
+ setEnabled(true);
+ }
+ }
+ }
+ }
+}
+
+QString configAssignment()
+{
+ static const QString assignment = QString("CONFIG+=") + Constants::PROFILE_NAME;
+ return assignment;
+}
+
+static const char pathAssignmentPrefix[] = "COCOPATH=";
+static const char featuresVar[] = "QMAKEFEATURES";
+
+const QStringList CocoQMakeSettings::userArgumentList() const
+{
+ if (!enabled())
+ return {};
+
+ Utils::ProcessArgs::ConstArgIterator it{m_qmakeStep->userArguments.unexpandedArguments()};
+ QStringList result;
+
+ while (it.next()) {
+ if (it.isSimple())
+ result << it.value();
+ }
+
+ return result;
+}
+
+Utils::Environment CocoQMakeSettings::buildEnvironment() const
+{
+ if (!enabled())
+ return Utils::Environment();
+
+ Utils::Environment env = m_buildConfig->environment();
+ env.modify(m_buildConfig->userEnvironmentChanges());
+ return env;
+}
+
+void CocoQMakeSettings::setQMakeFeatures() const
+{
+ if (!enabled())
+ return;
+
+ Utils::Environment env = buildEnvironment();
+
+ const QString projectDir = m_buildConfig->project()->projectDirectory().nativePath();
+ if (env.value(featuresVar) != projectDir) {
+ // Bug in prependOrSet(): It does not recognize if QMAKEFEATURES contains a single path
+ // without a colon and then appends it twice.
+ env.prependOrSet(featuresVar, projectDir);
+ }
+
+ Utils::EnvironmentItems diff = m_buildConfig->baseEnvironment().diff(env);
+ m_buildConfig->setUserEnvironmentChanges(diff);
+}
+
+bool CocoQMakeSettings::environmentSet() const
+{
+ if (!enabled())
+ return true;
+
+ const Utils::Environment env = buildEnvironment();
+ const Utils::FilePath projectDir = m_buildConfig->project()->projectDirectory();
+ const QString nativeProjectDir = projectDir.nativePath();
+ return env.value(featuresVar) == nativeProjectDir
+ || env.value(featuresVar).startsWith(nativeProjectDir + projectDir.pathListSeparator());
+}
+
+bool CocoQMakeSettings::validSettings() const
+{
+ const bool configured = userArgumentList().contains(configAssignment());
+ return enabled() && configured && environmentSet() && m_featureFile.exists()
+ && cocoPathValid();
+}
+
+void CocoQMakeSettings::setCoverage(bool on)
+{
+ QString args = m_qmakeStep->userArguments.unexpandedArguments();
+ Utils::ProcessArgs::ArgIterator it{&args};
+
+ while (it.next()) {
+ if (it.isSimple()) {
+ const QString value = it.value();
+ if (value.startsWith(pathAssignmentPrefix) || value == configAssignment())
+ it.deleteArg();
+ }
+ }
+ if (on) {
+ it.appendArg(configAssignment());
+ it.appendArg(pathAssignment());
+
+ setQMakeFeatures();
+ m_featureFile.write();
+ }
+
+ m_qmakeStep->userArguments.setArguments(args);
+}
+
+QString CocoQMakeSettings::saveButtonText() const
+{
+ return Tr::tr("Save");
+}
+
+QString CocoQMakeSettings::configChanges() const
+{
+ return "<table><tbody>"
+ + tableRow(
+ "Additional qmake arguments: ",
+ maybeQuote(configAssignment()) + " " + maybeQuote(pathAssignment()))
+ + tableRow(
+ "Build environment: ", maybeQuote(QString(featuresVar) + "=" + projectDirectory()))
+ + tableRow("Feature File: ", maybeQuote(featureFilePath())) + "</tbody></table>";
+}
+
+QString CocoQMakeSettings::projectDirectory() const
+{
+ if (enabled())
+ return m_buildConfig->project()->projectDirectory().nativePath();
+ else
+ return "";
+}
+
+void CocoQMakeSettings::write(const QString &options, const QString &tweaks)
+{
+ m_featureFile.setOptions(options);
+ m_featureFile.setTweaks(tweaks);
+ m_featureFile.write();
+}
+
+QString CocoQMakeSettings::pathAssignment() const
+{
+ return pathAssignmentPrefix + m_coco.directory().toUserOutput();
+}
+
+bool CocoQMakeSettings::cocoPathValid() const
+{
+ Utils::ProcessArgs::ConstArgIterator it{m_qmakeStep->userArguments.unexpandedArguments()};
+
+ while (it.next()) {
+ if (it.isSimple()) {
+ const QString value = it.value();
+ if (value.startsWith(pathAssignmentPrefix) && value != pathAssignment())
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/cocoqmakesettings.h b/src/plugins/coco/cocobuild/cocoqmakesettings.h
new file mode 100644
index 00000000000..a081bb1e080
--- /dev/null
+++ b/src/plugins/coco/cocobuild/cocoqmakesettings.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "buildsettings.h"
+#include "qmakefeaturefile.h"
+#include "settings/cocoinstallation.h"
+
+#include <utils/commandline.h>
+#include <utils/environment.h>
+
+#include <QObject>
+#include <QStringList>
+
+namespace QmakeProjectManager {
+class QMakeStep;
+class QmakeBuildConfiguration;
+}
+
+namespace Coco::Internal {
+
+class CocoProjectWidget;
+
+class CocoQMakeSettings : public BuildSettings
+{
+ Q_OBJECT
+public:
+ explicit CocoQMakeSettings(ProjectExplorer::Project *project);
+ ~CocoQMakeSettings() override;
+
+ void read() override;
+ bool validSettings() const override;
+ void setCoverage(bool on) override;
+
+ QString saveButtonText() const override;
+ QString configChanges() const override;
+ QString projectDirectory() const override;
+ void write(const QString &options, const QString &tweaks) override;
+
+private:
+ bool environmentSet() const;
+ QString pathAssignment() const;
+ const QStringList userArgumentList() const;
+ Utils::Environment buildEnvironment() const;
+ void setQMakeFeatures() const;
+ bool cocoPathValid() const;
+
+ QmakeProjectManager::QmakeBuildConfiguration *m_buildConfig;
+ QmakeProjectManager::QMakeStep *m_qmakeStep;
+
+ QMakeFeatureFile m_featureFile;
+ CocoInstallation m_coco;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/modificationfile.cpp b/src/plugins/coco/cocobuild/modificationfile.cpp
new file mode 100644
index 00000000000..a8184021847
--- /dev/null
+++ b/src/plugins/coco/cocobuild/modificationfile.cpp
@@ -0,0 +1,73 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "modificationfile.h"
+
+namespace Coco::Internal {
+
+static void cutTail(QStringList &list)
+{
+ while (!list.isEmpty() && list.last().trimmed().isEmpty())
+ list.removeLast();
+}
+
+ModificationFile::ModificationFile() {}
+
+bool ModificationFile::exists() const
+{
+ return m_filePath.exists();
+}
+
+void ModificationFile::clear()
+{
+ m_options.clear();
+ m_tweaks.clear();
+}
+
+QStringList ModificationFile::contentOf(const Utils::FilePath &filePath) const
+{
+ QFile resource(filePath.nativePath());
+ resource.open(QIODevice::ReadOnly | QIODevice::Text);
+ QTextStream inStream(&resource);
+
+ QStringList result;
+ QString line;
+ while (inStream.readLineInto(&line))
+ result << line + '\n';
+ return result;
+}
+
+QStringList ModificationFile::currentModificationFile() const
+{
+ QStringList lines;
+ if (m_filePath.exists())
+ lines = contentOf(m_filePath);
+ else
+ lines = defaultModificationFile();
+
+ return lines;
+}
+
+void ModificationFile::setOptions(const QString &options)
+{
+ m_options = options.split('\n', Qt::SkipEmptyParts);
+}
+
+void ModificationFile::setOptions(const QStringList &options)
+{
+ m_options = options;
+}
+
+void ModificationFile::setTweaks(const QString &tweaks)
+{
+ m_tweaks = tweaks.split('\n', Qt::KeepEmptyParts);
+ cutTail(m_tweaks);
+}
+
+void ModificationFile::setTweaks(const QStringList &tweaks)
+{
+ m_tweaks = tweaks;
+ cutTail(m_tweaks);
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/modificationfile.h b/src/plugins/coco/cocobuild/modificationfile.h
new file mode 100644
index 00000000000..0f156ab2495
--- /dev/null
+++ b/src/plugins/coco/cocobuild/modificationfile.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/filepath.h>
+
+#include <QStringList>
+
+namespace Coco::Internal {
+
+class ModificationFile
+{
+public:
+ ModificationFile();
+
+ virtual void read() = 0;
+ virtual void write() const = 0;
+
+ virtual void setProjectDirectory(const Utils::FilePath &projectDirectory) = 0;
+
+ virtual QString fileName() const = 0;
+ QString nativePath() const { return m_filePath.nativePath(); }
+ bool exists() const;
+
+ const QStringList &options() const { return m_options; }
+ void setOptions(const QString &options);
+
+ const QStringList &tweaks() const { return m_tweaks; }
+ void setTweaks(const QString &tweaks);
+
+protected:
+ void clear();
+
+ virtual QStringList defaultModificationFile() const = 0;
+ QStringList contentOf(const Utils::FilePath &filePath) const;
+ QStringList currentModificationFile() const;
+
+ void setFilePath(const Utils::FilePath &path) { m_filePath = path; }
+
+ void setOptions(const QStringList &options);
+ void setTweaks(const QStringList &tweaks);
+
+private:
+ QStringList m_options;
+ QStringList m_tweaks;
+
+ Utils::FilePath m_filePath;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/qmakefeaturefile.cpp b/src/plugins/coco/cocobuild/qmakefeaturefile.cpp
new file mode 100644
index 00000000000..897cbaefa7b
--- /dev/null
+++ b/src/plugins/coco/cocobuild/qmakefeaturefile.cpp
@@ -0,0 +1,105 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmakefeaturefile.h"
+
+#include "cocopluginconstants.h"
+
+#include <QFile>
+#include <QRegularExpression>
+#include <QTextStream>
+
+namespace Coco::Internal {
+
+static const char assignment[] = "COVERAGE_OPTIONS = \\\n";
+static const char tweaksLine[] = "# User-supplied settings follow here:\n";
+
+static void cutTail(QStringList &list)
+{
+ while (!list.isEmpty() && list.last().trimmed().isEmpty())
+ list.removeLast();
+}
+
+QMakeFeatureFile::QMakeFeatureFile() {}
+
+QString QMakeFeatureFile::fileName() const
+{
+ return QString(Constants::PROFILE_NAME) + ".prf";
+}
+
+void QMakeFeatureFile::setProjectDirectory(const Utils::FilePath &projectDirectory)
+{
+ setFilePath(projectDirectory.pathAppended(fileName()));
+}
+
+QString QMakeFeatureFile::fromFileLine(const QString &line) const
+{
+ return line.chopped(2).trimmed().replace("\\\"", "\"");
+}
+
+QString QMakeFeatureFile::toFileLine(const QString &option) const
+{
+ QString line = option.trimmed().replace("\"", "\\\"");
+ return " " + line + " \\\n";
+}
+
+void QMakeFeatureFile::read()
+{
+ clear();
+ QStringList file = currentModificationFile();
+
+ {
+ QStringList options;
+ int i = file.indexOf(assignment);
+ if (i != -1) {
+ i++;
+ while (i < file.size() && file[i].endsWith("\\\n")) {
+ options += fromFileLine(file[i]);
+ i++;
+ }
+ }
+ setOptions(options);
+ }
+ {
+ QStringList tweaks;
+ int i = file.indexOf(tweaksLine);
+ if (i != -1) {
+ i++;
+ while (i < file.size()) {
+ tweaks += file[i].chopped(1);
+ i++;
+ }
+ }
+ setTweaks(tweaks);
+ }
+}
+
+void QMakeFeatureFile::write() const
+{
+ QFile out(nativePath());
+ out.open(QIODevice::WriteOnly | QIODevice::Text);
+
+ QTextStream outStream(&out);
+ for (QString &line : defaultModificationFile()) {
+ outStream << line;
+
+ if (line.startsWith(assignment)) {
+ for (const QString &option : options()) {
+ QString line = toFileLine(option);
+ if (!line.isEmpty())
+ outStream << line;
+ }
+ }
+ }
+ for (const QString &line : tweaks())
+ outStream << line << "\n";
+
+ out.close();
+}
+
+QStringList QMakeFeatureFile::defaultModificationFile() const
+{
+ return contentOf(":/cocoplugin/files/cocoplugin.prf");
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocobuild/qmakefeaturefile.h b/src/plugins/coco/cocobuild/qmakefeaturefile.h
new file mode 100644
index 00000000000..3c865a1a25d
--- /dev/null
+++ b/src/plugins/coco/cocobuild/qmakefeaturefile.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "modificationfile.h"
+
+#include <utils/fileutils.h>
+
+#include <QString>
+#include <QStringList>
+
+namespace Coco::Internal {
+
+class QMakeFeatureFile : public ModificationFile
+{
+public:
+ QMakeFeatureFile();
+
+ void setProjectDirectory(const Utils::FilePath &projectDirectory) override;
+ void read() override;
+ void write() const override;
+
+ QString fileName() const override;
+
+protected:
+ QStringList defaultModificationFile() const override;
+
+private:
+ QString fromFileLine(const QString &line) const;
+ QString toFileLine(const QString &option) const;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/cocoplugin.cpp b/src/plugins/coco/cocoplugin.cpp
index 89047b84249..13b0d690e90 100644
--- a/src/plugins/coco/cocoplugin.cpp
+++ b/src/plugins/coco/cocoplugin.cpp
@@ -1,17 +1,25 @@
-// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "cocobuild/cocobuildstep.h"
#include "cocolanguageclient.h"
+#include "cocopluginconstants.h"
#include "cocotr.h"
+#include "settings/cocoprojectsettingswidget.h"
+#include "settings/globalsettings.h"
+#include "settings/globalsettingspage.h"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectpanelfactory.h>
+#include <projectexplorer/target.h>
#include <debugger/analyzer/analyzerconstants.h>
#include <extensionsystem/iplugin.h>
-
#include <utils/environment.h>
#include <utils/fileutils.h>
#include <utils/pathchooser.h>
@@ -19,12 +27,17 @@
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
+#include <QMessageBox>
+#include <QPushButton>
using namespace Core;
using namespace Utils;
namespace Coco {
+using namespace ProjectExplorer;
+using namespace Internal;
+
class CocoPlugin final : public ExtensionSystem::IPlugin
{
Q_OBJECT
@@ -36,7 +49,7 @@ public:
// FIXME: Kill m_client?
}
- void initialize() final
+ void initLanguageServer()
{
ActionBuilder(this, "Coco.startCoco")
.setText("Squish Coco ...")
@@ -50,53 +63,112 @@ public:
m_client->shutdown();
m_client = nullptr;
- QDialog dialog(ICore::dialogParent());
- dialog.setModal(true);
- auto layout = new QFormLayout();
-
- const Environment env = Environment::systemEnvironment();
- const FilePath squishCocoPath = FilePath::fromUserInput(env.value("SQUISHCOCO"));
- const FilePath candidate = FilePath("coveragebrowser").searchInPath({squishCocoPath},
- FilePath::PrependToPath);
-
- PathChooser cocoChooser;
- if (!candidate.isEmpty())
- cocoChooser.setFilePath(candidate);
- cocoChooser.setExpectedKind(PathChooser::Command);
- cocoChooser.setPromptDialogTitle(Tr::tr("Select a Squish Coco CoverageBrowser Executable"));
-
- cocoChooser.setHistoryCompleter("Coco.CoverageBrowser.history", true);
- layout->addRow(Tr::tr("CoverageBrowser:"), &cocoChooser);
- PathChooser csmesChoser;
- csmesChoser.setHistoryCompleter("Coco.CSMes.history", true);
- csmesChoser.setExpectedKind(PathChooser::File);
- csmesChoser.setInitialBrowsePathBackup(FileUtils::homePath());
- csmesChoser.setPromptDialogFilter(Tr::tr("Coco instrumentation files (*.csmes)"));
- csmesChoser.setPromptDialogTitle(Tr::tr("Select a Squish Coco Instrumentation File"));
- layout->addRow(Tr::tr("CSMes:"), &csmesChoser);
- QDialogButtonBox buttons(QDialogButtonBox::Cancel | QDialogButtonBox::Open);
- layout->addItem(new QSpacerItem(0, 20, QSizePolicy::Expanding, QSizePolicy::MinimumExpanding));
- layout->addWidget(&buttons);
- dialog.setLayout(layout);
- dialog.resize(480, dialog.height());
-
- QObject::connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
- QObject::connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
-
- if (dialog.exec() == QDialog::Accepted) {
- const FilePath cocoPath = cocoChooser.filePath();
- const FilePath csmesPath = csmesChoser.filePath();
- if (cocoPath.isExecutableFile() && csmesPath.exists()) {
- m_client = new CocoLanguageClient(cocoPath, csmesPath);
- m_client->start();
+ CocoInstallation coco;
+ if (coco.isValid()) {
+ QDialog dialog(ICore::dialogParent());
+ dialog.setModal(true);
+ auto layout = new QFormLayout();
+
+ PathChooser csmesChoser;
+ csmesChoser.setHistoryCompleter("Coco.CSMes.history", true);
+ csmesChoser.setExpectedKind(PathChooser::File);
+ csmesChoser.setInitialBrowsePathBackup(PathChooser::homePath());
+ csmesChoser.setPromptDialogFilter(Tr::tr("Coco instrumentation files (*.csmes)"));
+ csmesChoser.setPromptDialogTitle(Tr::tr("Select a Squish Coco Instrumentation File"));
+ layout->addRow(Tr::tr("CSMes file:"), &csmesChoser);
+ QDialogButtonBox buttons(QDialogButtonBox::Cancel | QDialogButtonBox::Open);
+ layout->addWidget(&buttons);
+ dialog.setLayout(layout);
+ dialog.resize(480, dialog.height());
+
+ QObject::connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+ QObject::connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ const FilePath cocoPath = coco.coverageBrowserPath();
+ const FilePath csmesPath = csmesChoser.filePath();
+ if (cocoPath.isExecutableFile() && csmesPath.exists()) {
+ m_client = new CocoLanguageClient(cocoPath, csmesPath);
+ m_client->start();
+ }
}
+ } else {
+ QMessageBox msg;
+ msg.setText(Tr::tr("No valid CoverageScanner found."));
+ QPushButton *configButton = msg.addButton(Tr::tr("Configure"), QMessageBox::AcceptRole);
+ msg.setStandardButtons(QMessageBox::Cancel);
+ msg.exec();
+
+ if (msg.clickedButton() == configButton)
+ Core::ICore::showOptionsDialog(Constants::COCO_SETTINGS_PAGE_ID);
}
}
+ bool initialize(const QStringList &arguments, QString *errorString);
+ void addEntryToProjectSettings();
+
private:
+ static void addBuildStep(ProjectExplorer::Target *target);
+
+ QMakeStepFactory m_qmakeStepFactory;
+ CMakeStepFactory m_cmakeStepFactory;
+
CocoLanguageClient *m_client = nullptr;
};
+void CocoPlugin::addBuildStep(Target *target)
+{
+ for (BuildConfiguration *config : target->buildConfigurations()) {
+ if (BuildSettings::supportsBuildConfig(*config)) {
+ BuildStepList *steps = config->buildSteps();
+
+ if (!steps->contains(Constants::COCO_STEP_ID))
+ steps->insertStep(0, CocoBuildStep::create(config));
+
+ steps->firstOfType<CocoBuildStep>()->display(config);
+ }
+ }
+}
+
+bool CocoPlugin::initialize(const QStringList &arguments, QString *errorString)
+{
+ Q_UNUSED(arguments)
+ Q_UNUSED(errorString)
+
+ GlobalSettings::read();
+ GlobalSettingsPage::instance().widget();
+ addEntryToProjectSettings();
+
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, [&](Project *project) {
+ if (Target *target = project->activeTarget())
+ addBuildStep(target);
+
+ connect(project, &Project::addedTarget, this, [](Target *target) {
+ addBuildStep(target);
+ });
+ });
+
+ initLanguageServer();
+
+ return true;
+}
+
+void CocoPlugin::addEntryToProjectSettings()
+{
+ auto panelFactory = new ProjectPanelFactory;
+ panelFactory->setPriority(50);
+ panelFactory->setDisplayName(tr("Coco Code Coverage"));
+ panelFactory->setSupportsFunction([](Project *project) {
+ if (Target *target = project->activeTarget()) {
+ if (BuildConfiguration *abc = target->activeBuildConfiguration())
+ return BuildSettings::supportsBuildConfig(*abc);
+ }
+ return false;
+ });
+ panelFactory->setCreateWidgetFunction(
+ [](Project *project) { return new CocoProjectSettingsWidget(project); });
+}
+
} // namespace Coco
#include "cocoplugin.moc"
diff --git a/src/plugins/coco/cocoplugin.qrc b/src/plugins/coco/cocoplugin.qrc
new file mode 100644
index 00000000000..bb52bda91b1
--- /dev/null
+++ b/src/plugins/coco/cocoplugin.qrc
@@ -0,0 +1,10 @@
+<RCC>
+ <qresource prefix="/cocoplugin">
+ <file>images/SquishCoco_48x48.png</file>
+ <file>files/cocoplugin.prf</file>
+ <file>files/cocoplugin.cmake</file>
+ <file>files/cocoplugin-clang.cmake</file>
+ <file>files/cocoplugin-gcc.cmake</file>
+ <file>files/cocoplugin-visualstudio.cmake</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/coco/cocoplugin_global.h b/src/plugins/coco/cocoplugin_global.h
new file mode 100644
index 00000000000..b89de4ccaea
--- /dev/null
+++ b/src/plugins/coco/cocoplugin_global.h
@@ -0,0 +1,12 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QtGlobal>
+
+#if defined(COCO_LIBRARY)
+# define COCOPLUGINSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define COCOPLUGINSHARED_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/src/plugins/coco/cocopluginconstants.h b/src/plugins/coco/cocopluginconstants.h
new file mode 100644
index 00000000000..8fd36ee3bce
--- /dev/null
+++ b/src/plugins/coco/cocopluginconstants.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace Coco {
+namespace Constants {
+
+const char ACTION_ID[] = "coco.Action";
+const char MENU_ID[] = "coco.Menu";
+const char COCO_STEP_ID[] = "Cocoplugin.BuildStep";
+
+const char PROFILE_NAME[] = "cocoplugin"; // Name of the Coco profile file
+
+const char COCO_SETTINGS_GROUP[] = "Coco";
+const char COCO_SETTINGS_PAGE_ID[] = "A.CocoOptions";
+
+// Project settings
+const char SETTINGS_NAME_KEY[] = "CocoProjectSettings";
+const char SELECTION_DIR_KEY[] = "SelectionDir";
+
+} // namespace Constants
+} // namespace Coco
diff --git a/src/plugins/coco/common.cpp b/src/plugins/coco/common.cpp
new file mode 100644
index 00000000000..e822e6eb7b7
--- /dev/null
+++ b/src/plugins/coco/common.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "common.h"
+
+#include "cocopluginconstants.h"
+
+#include <coreplugin/messagemanager.h>
+
+QString maybeQuote(const QString &str)
+{
+ if ((str.contains(' ') || str.contains('\t')) && !str.startsWith('"'))
+ return '"' + str + '"';
+ else
+ return str;
+}
+
+void logSilently(const QString &msg)
+{
+ static const QString prefix = QString{"[%1] "}.arg(Coco::Constants::PROFILE_NAME);
+
+ Core::MessageManager::writeSilently(prefix + msg);
+}
+
+void logFlashing(const QString &msg)
+{
+ static const QString prefix = QString{"[%1] "}.arg(Coco::Constants::PROFILE_NAME);
+
+ Core::MessageManager::writeFlashing(prefix + msg);
+}
diff --git a/src/plugins/coco/common.h b/src/plugins/coco/common.h
new file mode 100644
index 00000000000..fde75dc617e
--- /dev/null
+++ b/src/plugins/coco/common.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <QString>
+
+QString maybeQuote(const QString &str);
+
+void logSilently(const QString &msg);
+void logFlashing(const QString &msg);
+
+#endif // COMMON_H
diff --git a/src/plugins/coco/files/cocoplugin-clang.cmake b/src/plugins/coco/files/cocoplugin-clang.cmake
new file mode 100644
index 00000000000..697b87465e0
--- /dev/null
+++ b/src/plugins/coco/files/cocoplugin-clang.cmake
@@ -0,0 +1,8 @@
+# Created by the Coco Qt Creator plugin. Do not edit!
+
+set(CMAKE_C_COMPILER clang)
+set(CMAKE_CXX_COMPILER clang++)
+set(CMAKE_AR ar)
+set(CMAKE_LINKER clang)
+
+include(cocoplugin.cmake)
diff --git a/src/plugins/coco/files/cocoplugin-gcc.cmake b/src/plugins/coco/files/cocoplugin-gcc.cmake
new file mode 100644
index 00000000000..1271c3115a3
--- /dev/null
+++ b/src/plugins/coco/files/cocoplugin-gcc.cmake
@@ -0,0 +1,8 @@
+# Created by the Coco Qt Creator plugin. Do not edit!
+
+set(CMAKE_C_COMPILER gcc)
+set(CMAKE_CXX_COMPILER g++)
+set(CMAKE_AR ar)
+set(CMAKE_LINKER gcc)
+
+include(cocoplugin.cmake)
diff --git a/src/plugins/coco/files/cocoplugin-visualstudio.cmake b/src/plugins/coco/files/cocoplugin-visualstudio.cmake
new file mode 100644
index 00000000000..d72f74db882
--- /dev/null
+++ b/src/plugins/coco/files/cocoplugin-visualstudio.cmake
@@ -0,0 +1,8 @@
+# Created by the Coco Qt Creator plugin. Do not edit!
+
+set(CMAKE_C_COMPILER cl)
+set(CMAKE_CXX_COMPILER cl)
+set(CMAKE_AR lib)
+set(CMAKE_LINKER link)
+
+include(cocoplugin.cmake)
diff --git a/src/plugins/coco/files/cocoplugin.cmake b/src/plugins/coco/files/cocoplugin.cmake
new file mode 100644
index 00000000000..78ea51de860
--- /dev/null
+++ b/src/plugins/coco/files/cocoplugin.cmake
@@ -0,0 +1,87 @@
+# Created by the Coco Qt Creator plugin. Do not edit!
+
+set(coverage_flags_list
+)
+list(JOIN coverage_flags_list " " coverage_flags)
+
+foreach(var IN ITEMS CMAKE_C_COMPILER CMAKE_CXX_COMPILER)
+ if(NOT DEFINED ${var})
+ message(FATAL_ERROR "Variable ${var} must be defined.")
+ endif()
+endforeach()
+
+set(CMAKE_C_FLAGS_INIT "${coverage_flags}"
+ CACHE STRING "Coverage flags for the C compiler." FORCE)
+set(CMAKE_CXX_FLAGS_INIT "${coverage_flags}"
+ CACHE STRING "Coverage flags for the C++ compiler." FORCE)
+set(CMAKE_EXE_LINKER_FLAGS_INIT "${coverage_flags}"
+ CACHE STRING "Coverage flags for the linker." FORCE)
+set(CMAKE_SHARED_LINKER_FLAGS_INIT "${coverage_flags}"
+ CACHE STRING "Coverage flags to link shared libraries." FORCE)
+set(CMAKE_STATIC_LINKER_FLAGS_INIT "${coverage_flags}"
+ CACHE STRING "Coverage flags to link static libraries." FORCE)
+
+if (DEFINED ENV{SQUISHCOCO})
+ set(cocopath $ENV{SQUISHCOCO})
+else()
+ find_file(cocopath SquishCoco
+ PATHS "$ENV{HOME}" /opt/ "/Applications"
+ REQUIRED
+ NO_DEFAULT_PATH
+ )
+endif()
+
+if(CMAKE_HOST_APPLE)
+ set(wrapperdir "${cocopath}/")
+elseif(CMAKE_HOST_UNIX)
+ set(wrapperdir "${cocopath}/bin/")
+elseif(MINGW)
+ set(wrapperdir "${cocopath}\\bin\\")
+else()
+ set(wrapperdir "${cocopath}\\" )
+endif()
+
+get_filename_component(c_compiler ${CMAKE_C_COMPILER} NAME)
+find_program(code_coverage_c_compiler cs${c_compiler}
+ PATHS ${wrapperdir}
+ REQUIRED NO_DEFAULT_PATH)
+set(CMAKE_C_COMPILER "${code_coverage_c_compiler}"
+ CACHE FILEPATH "CoverageScanner wrapper for C compiler" FORCE)
+
+get_filename_component(cxx_compiler ${CMAKE_CXX_COMPILER} NAME)
+find_program(code_coverage_cxx_compiler cs${cxx_compiler}
+ PATHS ${wrapperdir}
+ REQUIRED NO_DEFAULT_PATH)
+set(CMAKE_CXX_COMPILER "${code_coverage_cxx_compiler}"
+ CACHE FILEPATH "CoverageScanner wrapper for C++ compiler" FORCE)
+
+if(DEFINED CMAKE_LINKER)
+ get_filename_component(linker_prog ${CMAKE_LINKER} NAME)
+ find_program(code_coverage_linker cs${linker_prog}
+ PATHS ${wrapperdir}
+ REQUIRED NO_DEFAULT_PATH)
+ set(CMAKE_LINKER "${code_coverage_linker}"
+ CACHE FILEPATH "CoverageScanner wrapper for linker" FORCE)
+elseif(${c_compiler} STREQUAL "cl.exe") # special case for Visual Studio
+ find_program(code_coverage_linker "cslink.exe"
+ PATHS ${wrapperdir}
+ REQUIRED NO_DEFAULT_PATH)
+ set(CMAKE_LINKER "${code_coverage_linker}"
+ CACHE FILEPATH "CoverageScanner wrapper for linker" FORCE)
+endif()
+
+if(DEFINED CMAKE_AR)
+ get_filename_component(ar_prog ${CMAKE_AR} NAME)
+ find_program(code_coverage_ar cs${ar_prog}
+ PATHS ${wrapperdir}
+ REQUIRED NO_DEFAULT_PATH)
+ set(CMAKE_AR "${code_coverage_ar}"
+ CACHE FILEPATH "CoverageScanner wrapper for ar" FORCE)
+endif()
+
+mark_as_advanced(
+ cocopath
+ code_coverage_c_compiler code_coverage_cxx_compiler code_coverage_linker code_coverage_ar
+)
+
+# User-supplied settings follow here:
diff --git a/src/plugins/coco/files/cocoplugin.prf b/src/plugins/coco/files/cocoplugin.prf
new file mode 100644
index 00000000000..b68c2c671da
--- /dev/null
+++ b/src/plugins/coco/files/cocoplugin.prf
@@ -0,0 +1,33 @@
+# Created by the Coco plugin. Do not edit!
+
+COVERAGE_OPTIONS = \
+
+defineReplace(toCoco) {
+ cmd = $$1
+ path = $$take_first(cmd)
+ prog = $$basename(path)
+
+ return(cs$$prog $$cmd)
+}
+
+isEmpty(COCOPATH): error(The variable COCOPATH must be set)
+
+macos: wrapperdir = $$COCOPATH
+else: unix: wrapperdir = $$COCOPATH/bin
+else: win32: {
+ win32-arm-msvc*|win32-x86-msvc*: wrapperdir = $$COCOPATH/visualstudio
+ else: win32-arm64-msvc*|win32-x64-msvc*: wrapperdir = $$COCOPATH/visualstudio_x64
+ else: wrapperdir = $$COCOPATH
+}
+
+QMAKE_CFLAGS += $$COVERAGE_OPTIONS
+QMAKE_CXXFLAGS += $$COVERAGE_OPTIONS
+QMAKE_LFLAGS += $$COVERAGE_OPTIONS
+
+QMAKE_AR = $$wrapperdir/$$toCoco($$QMAKE_AR)
+QMAKE_CC = $$wrapperdir/$$toCoco($$QMAKE_CC)
+QMAKE_CXX = $$wrapperdir/$$toCoco($$QMAKE_CXX)
+QMAKE_LINK = $$wrapperdir/$$toCoco($$QMAKE_LINK)
+QMAKE_LINK_SHLIB_CMD = $$wrapperdir/$$toCoco($$QMAKE_LINK_SHLIB_CMD)
+
+# User-supplied settings follow here:
diff --git a/src/plugins/coco/images/SquishCoco_48x48.png b/src/plugins/coco/images/SquishCoco_48x48.png
new file mode 100644
index 00000000000..74a68a1727c
--- /dev/null
+++ b/src/plugins/coco/images/SquishCoco_48x48.png
Binary files differ
diff --git a/src/plugins/coco/settings/cocoinstallation.cpp b/src/plugins/coco/settings/cocoinstallation.cpp
new file mode 100644
index 00000000000..77d466eed3e
--- /dev/null
+++ b/src/plugins/coco/settings/cocoinstallation.cpp
@@ -0,0 +1,177 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cocoinstallation.h"
+
+#include "cocotr.h"
+#include "common.h"
+#include "globalsettings.h"
+
+#include <utils/fileutils.h>
+#include <utils/hostosinfo.h>
+
+#include <QProcess>
+#include <QStandardPaths>
+#include <QRegularExpression>
+
+namespace Coco::Internal {
+
+struct CocoInstallationPrivate
+{
+ Utils::FilePath cocoPath;
+ bool isValid = false;
+ QString errorMessage = Tr::tr("Error: Coco installation directory not set. (This can't happen.)");
+};
+
+CocoInstallationPrivate *CocoInstallation::d = nullptr;
+
+CocoInstallation::CocoInstallation()
+{
+ if (!d)
+ d = new CocoInstallationPrivate;
+}
+
+Utils::FilePath CocoInstallation::directory() const
+{
+ return d->cocoPath;
+}
+
+Utils::FilePath CocoInstallation::coverageBrowserPath() const
+{
+ QString browserPath;
+
+ if (Utils::HostOsInfo::isAnyUnixHost() || Utils::HostOsInfo::isMacHost())
+ browserPath = "bin/coveragebrowser";
+ else
+ browserPath = "coveragebrowser.exe";
+
+ return d->cocoPath.resolvePath(browserPath);
+}
+
+void CocoInstallation::setDirectory(const Utils::FilePath &dir)
+{
+ if (isCocoDirectory(dir)) {
+ d->cocoPath = dir;
+ d->isValid = true;
+ d->errorMessage = "";
+ verifyCocoDirectory();
+ }
+ else {
+ d->cocoPath = Utils::FilePath();
+ d->isValid = false;
+ d->errorMessage
+ = Tr::tr("Error: Coco installation directory not found at \"%1\".").arg(dir.nativePath());
+ }
+}
+
+Utils::FilePath CocoInstallation::coverageScannerPath(const Utils::FilePath &cocoDir) const
+{
+ QString scannerPath;
+
+ if (Utils::HostOsInfo::isAnyUnixHost() || Utils::HostOsInfo::isMacHost())
+ scannerPath = "bin/coveragescanner";
+ else
+ scannerPath = "coveragescanner.exe";
+
+ return cocoDir.resolvePath(scannerPath);
+}
+
+bool CocoInstallation::isCocoDirectory(const Utils::FilePath &cocoDir) const
+{
+ return coverageScannerPath(cocoDir).exists();
+}
+
+void CocoInstallation::logError(const QString &msg)
+{
+ logFlashing(msg);
+ d->isValid = false;
+ d->errorMessage = msg;
+}
+
+bool CocoInstallation::verifyCocoDirectory()
+{
+ QString coveragescanner = coverageScannerPath(d->cocoPath).nativePath();
+
+ QProcess proc;
+ proc.setProgram(coveragescanner);
+ proc.setArguments({"--cs-help"});
+ proc.start();
+
+ if (!proc.waitForStarted()) {
+ logError(Tr::tr("Error: Coveragescanner at \"%1\" did not start.").arg(coveragescanner));
+ return false;
+ }
+
+ if (!proc.waitForFinished()) {
+ logError(Tr::tr("Error: Coveragescanner at \"%1\" did not finish.").arg(coveragescanner));
+ return false;
+ }
+
+ QString result = QString::fromLatin1(proc.readAll());
+ static const QRegularExpression linebreak("\n|\r\n|\r");
+ QStringList lines = result.split(linebreak, Qt::SkipEmptyParts);
+
+ const qsizetype n = lines.size();
+ if (n >= 2 && lines[n - 2].startsWith("Version:") && lines[n - 1].startsWith("Date:")) {
+ logSilently(Tr::tr("Valid CoverageScanner found at \"%1\":").arg(coveragescanner));
+ logSilently(" " + lines[n - 2]);
+ logSilently(" " + lines[n - 1]);
+ return true;
+ } else {
+ logError(
+ Tr::tr("Error: Coveragescanner at \"%1\" did not run correctly.").arg(coveragescanner));
+ for (const QString &l : lines) {
+ logSilently(l);
+ }
+ return false;
+ }
+}
+
+bool CocoInstallation::isValid() const
+{
+ return d->isValid;
+}
+
+QString CocoInstallation::errorMessage() const
+{
+ return d->errorMessage;
+}
+
+void CocoInstallation::tryPath(const QString &path)
+{
+ if (d->isValid)
+ return;
+
+ const auto fpath = Utils::FilePath::fromString(path);
+ const QString nativePath = fpath.nativePath();
+ if (isCocoDirectory(fpath)) {
+ logSilently(Tr::tr("Found Coco directory \"%1\".").arg(nativePath));
+ setDirectory(fpath);
+ GlobalSettings::save();
+ } else
+ logSilently(Tr::tr("Checked Coco directory \"%1\".").arg(nativePath));
+}
+
+QString CocoInstallation::envVar(const QString &var) const
+{
+ return QProcessEnvironment::systemEnvironment().value(var);
+}
+
+void CocoInstallation::findDefaultDirectory()
+{
+ if (Utils::HostOsInfo::isMacHost())
+ tryPath("/Applications/SquishCoco");
+ else if (Utils::HostOsInfo::isAnyUnixHost()) {
+ tryPath((Utils::FileUtils::homePath() / "SquishCoco").nativePath());
+ tryPath("/opt/SquishCoco");
+ } else {
+ tryPath(envVar("SQUISHCOCO"));
+ QStringList homeDirs = QStandardPaths::standardLocations(QStandardPaths::HomeLocation);
+ if (!homeDirs.isEmpty())
+ tryPath(homeDirs[0] + "/squishcoco");
+ tryPath(envVar("ProgramFiles") + "\\squishcoco");
+ tryPath(envVar("ProgramFiles(x86)") + "\\squishcoco");
+ }
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/cocoinstallation.h b/src/plugins/coco/settings/cocoinstallation.h
new file mode 100644
index 00000000000..73d83bf704d
--- /dev/null
+++ b/src/plugins/coco/settings/cocoinstallation.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/filepath.h>
+
+class QString;
+
+namespace Coco::Internal {
+
+struct CocoInstallationPrivate;
+
+// Borg pattern: There are many instances of this class, but all are the same.
+class CocoInstallation
+{
+public:
+ CocoInstallation();
+
+ Utils::FilePath directory() const;
+ Utils::FilePath coverageBrowserPath() const;
+ void setDirectory(const Utils::FilePath &dir);
+ void findDefaultDirectory();
+
+ bool isValid() const;
+ QString errorMessage() const;
+
+private:
+ Utils::FilePath coverageScannerPath(const Utils::FilePath &cocoDir) const;
+ void logError(const QString &msg);
+ bool isCocoDirectory(const Utils::FilePath &cocoDir) const;
+ bool verifyCocoDirectory();
+ void tryPath(const QString &path);
+ QString envVar(const QString &var) const;
+
+ static CocoInstallationPrivate *d;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/cocoprojectsettingswidget.cpp b/src/plugins/coco/settings/cocoprojectsettingswidget.cpp
new file mode 100644
index 00000000000..8e6f10cc5ec
--- /dev/null
+++ b/src/plugins/coco/settings/cocoprojectsettingswidget.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cocoprojectsettingswidget.h"
+
+#include "cocobuild/cocoprojectwidget.h"
+#include "cocopluginconstants.h"
+
+#include <cmakeprojectmanager/cmakeprojectconstants.h>
+#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/target.h>
+#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
+
+#include <QDebug>
+
+namespace Coco::Internal {
+
+CocoProjectSettingsWidget::CocoProjectSettingsWidget(ProjectExplorer::Project *project)
+ : m_layout{new QVBoxLayout}
+{
+ setUseGlobalSettingsCheckBoxVisible(false);
+ setGlobalSettingsId(Constants::COCO_SETTINGS_PAGE_ID);
+
+ if (auto *target = project->activeTarget()) {
+ auto abc = target->activeBuildConfiguration();
+
+ if (abc->id() == QmakeProjectManager::Constants::QMAKE_BC_ID
+ || abc->id() == CMakeProjectManager::Constants::CMAKE_BUILDCONFIGURATION_ID)
+ m_layout->addWidget(new CocoProjectWidget(project, *abc));
+ }
+ setLayout(m_layout);
+}
+
+CocoProjectSettingsWidget::~CocoProjectSettingsWidget()
+{
+ delete m_layout;
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/cocoprojectsettingswidget.h b/src/plugins/coco/settings/cocoprojectsettingswidget.h
new file mode 100644
index 00000000000..c201a55ece7
--- /dev/null
+++ b/src/plugins/coco/settings/cocoprojectsettingswidget.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <projectexplorer/projectsettingswidget.h>
+
+#include <QVBoxLayout>
+
+namespace ProjectExplorer {
+class Project;
+}
+
+namespace Coco::Internal {
+
+class CocoProjectSettingsWidget : public ProjectExplorer::ProjectSettingsWidget
+{
+ Q_OBJECT
+
+public:
+ explicit CocoProjectSettingsWidget(ProjectExplorer::Project *project);
+ ~CocoProjectSettingsWidget();
+
+private:
+ QVBoxLayout *m_layout;
+};
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/globalsettings.cpp b/src/plugins/coco/settings/globalsettings.cpp
new file mode 100644
index 00000000000..594355964ee
--- /dev/null
+++ b/src/plugins/coco/settings/globalsettings.cpp
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "globalsettings.h"
+
+#include "cocoinstallation.h"
+#include "cocopluginconstants.h"
+
+#include <coreplugin/icore.h>
+#include <utils/filepath.h>
+
+#include <QProcess>
+#include <QSettings>
+
+namespace Coco::Internal {
+namespace GlobalSettings {
+
+static const char DIRECTORY[] = "CocoDirectory";
+
+void read()
+{
+ CocoInstallation coco;
+ bool directoryInSettings = false;
+
+ Utils::QtcSettings *s = Core::ICore::settings();
+ s->beginGroup(Constants::COCO_SETTINGS_GROUP);
+ const QStringList keys = s->allKeys();
+ for (const QString &keyString : keys) {
+ Utils::Key key(keyString.toLatin1());
+ if (key == DIRECTORY) {
+ coco.setDirectory(Utils::FilePath::fromUserInput(s->value(key).toString()));
+ directoryInSettings = true;
+ } else
+ s->remove(key);
+ }
+ s->endGroup();
+
+ if (!directoryInSettings)
+ coco.findDefaultDirectory();
+
+ GlobalSettings::save();
+}
+
+void save()
+{
+ Utils::QtcSettings *s = Core::ICore::settings();
+ s->beginGroup(Constants::COCO_SETTINGS_GROUP);
+ s->setValue(DIRECTORY, CocoInstallation().directory().toUserOutput());
+ s->endGroup();
+}
+
+} // namespace GlobalSettings
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/globalsettings.h b/src/plugins/coco/settings/globalsettings.h
new file mode 100644
index 00000000000..e57f7e790f3
--- /dev/null
+++ b/src/plugins/coco/settings/globalsettings.h
@@ -0,0 +1,13 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace Coco::Internal {
+namespace GlobalSettings {
+
+void read();
+void save();
+
+} // namespace GlobalSettings
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/globalsettingspage.cpp b/src/plugins/coco/settings/globalsettingspage.cpp
new file mode 100644
index 00000000000..b4eda76d0ba
--- /dev/null
+++ b/src/plugins/coco/settings/globalsettingspage.cpp
@@ -0,0 +1,121 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "globalsettingspage.h"
+
+#include "cocoinstallation.h"
+#include "cocopluginconstants.h"
+#include "cocotr.h"
+#include "globalsettings.h"
+
+#include <utils/fancylineedit.h>
+#include <utils/layoutbuilder.h>
+
+namespace Coco::Internal {
+
+GlobalSettingsWidget::GlobalSettingsWidget(QFrame *parent)
+ : QFrame(parent)
+{
+ m_cocoPathAspect.setDefaultPathValue(m_coco.directory());
+ m_cocoPathAspect.setExpectedKind(Utils::PathChooser::ExistingDirectory);
+ m_cocoPathAspect.setPromptDialogTitle(Tr::tr("Coco Installation Directory"));
+
+ connect(
+ &m_cocoPathAspect,
+ &Utils::FilePathAspect::changed,
+ this,
+ &GlobalSettingsWidget::onCocoPathChanged);
+
+ using namespace Layouting;
+ Form{
+ Column{
+ Row{Tr::tr("Coco Directory"), m_cocoPathAspect},
+ Row{m_messageLabel}}
+ }.attachTo(this);
+}
+
+void GlobalSettingsWidget::onCocoPathChanged()
+{
+ if (!verifyCocoDirectory(m_cocoPathAspect()))
+ m_cocoPathAspect.setValue(m_previousCocoDir, Utils::BaseAspect::BeQuiet);
+}
+
+bool GlobalSettingsWidget::verifyCocoDirectory(const Utils::FilePath &cocoDir)
+{
+ m_coco.setDirectory(cocoDir);
+ m_messageLabel.setText(m_coco.errorMessage());
+ if (m_coco.isValid())
+ m_messageLabel.setIconType(Utils::InfoLabel::None);
+ else
+ m_messageLabel.setIconType(Utils::InfoLabel::Error);
+ return m_coco.isValid();
+}
+
+void GlobalSettingsWidget::apply()
+{
+ if (!verifyCocoDirectory(widgetCocoDir()))
+ return;
+
+ m_coco.setDirectory(widgetCocoDir());
+ GlobalSettings::save();
+
+ emit updateCocoDir();
+}
+
+void GlobalSettingsWidget::cancel()
+{
+ m_coco.setDirectory(m_previousCocoDir);
+}
+
+void GlobalSettingsWidget::setVisible(bool visible)
+{
+ QFrame::setVisible(visible);
+ m_previousCocoDir = m_coco.directory();
+}
+
+Utils::FilePath GlobalSettingsWidget::widgetCocoDir() const
+{
+ return Utils::FilePath::fromUserInput(m_cocoPathAspect.value());
+}
+
+GlobalSettingsPage::GlobalSettingsPage()
+ : m_widget(nullptr)
+{
+ setId(Constants::COCO_SETTINGS_PAGE_ID);
+ setDisplayName(QCoreApplication::translate("Coco", "Coco"));
+ setCategory("I.Coco"); // Category I contains also the C++ settings.
+ setDisplayCategory(QCoreApplication::translate("Coco", "Coco"));
+ setCategoryIconPath(":/cocoplugin/images/SquishCoco_48x48.png");
+}
+
+GlobalSettingsPage &GlobalSettingsPage::instance()
+{
+ static GlobalSettingsPage instance;
+ return instance;
+}
+
+GlobalSettingsWidget *GlobalSettingsPage::widget()
+{
+ if (!m_widget)
+ m_widget = new GlobalSettingsWidget;
+ return m_widget;
+}
+
+void GlobalSettingsPage::apply()
+{
+ if (m_widget)
+ m_widget->apply();
+}
+
+void GlobalSettingsPage::cancel()
+{
+ if (m_widget)
+ m_widget->cancel();
+}
+
+void GlobalSettingsPage::finish()
+{
+ delete m_widget;
+}
+
+} // namespace Coco::Internal
diff --git a/src/plugins/coco/settings/globalsettingspage.h b/src/plugins/coco/settings/globalsettingspage.h
new file mode 100644
index 00000000000..e55d6881b86
--- /dev/null
+++ b/src/plugins/coco/settings/globalsettingspage.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "cocoinstallation.h"
+
+#include <coreplugin/dialogs/ioptionspage.h>
+
+#include <QPointer>
+
+namespace Coco::Internal {
+
+class GlobalSettingsWidget : public QFrame
+{
+ Q_OBJECT
+
+public:
+ GlobalSettingsWidget(QFrame *parent = nullptr);
+
+ void apply();
+ void cancel();
+
+signals:
+ void updateCocoDir();
+
+public slots:
+ void setVisible(bool visible) override;
+
+private:
+ void onCocoPathChanged();
+
+ Utils::FilePath widgetCocoDir() const;
+ bool verifyCocoDirectory(const Utils::FilePath &cocoDir);
+
+ Utils::FilePathAspect m_cocoPathAspect;
+ Utils::TextDisplay m_messageLabel;
+
+ CocoInstallation m_coco;
+ Utils::FilePath m_previousCocoDir;
+};
+
+class GlobalSettingsPage : public Core::IOptionsPage
+{
+public:
+ static GlobalSettingsPage &instance();
+
+ GlobalSettingsWidget *widget() override;
+ void apply() override;
+ void cancel() override;
+ void finish() override;
+
+private:
+ GlobalSettingsPage();
+
+ QPointer<GlobalSettingsWidget> m_widget;
+};
+
+} // namespace Coco::Internal