diff options
author | Markus Redeker <[email protected]> | 2024-09-30 16:41:30 +0200 |
---|---|---|
committer | Markus Redeker <[email protected]> | 2024-10-24 12:41:04 +0000 |
commit | 090b540487c0d5e178a043f8df05d3bbc0b67bd5 (patch) | |
tree | 9ccda8fd82425ff3da58d7c68999b5e6a2e349dd | |
parent | a5a8450edc5249c2694bc6836b177873ecb1132b (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]>
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 Binary files differnew file mode 100644 index 00000000000..66b21eee1e7 --- /dev/null +++ b/doc/qtcreator/images/qtcreator-coco-buildstep.png diff --git a/doc/qtcreator/images/qtcreator-coco-configpage.png b/doc/qtcreator/images/qtcreator-coco-configpage.png Binary files differnew file mode 100644 index 00000000000..ac48dcbbd81 --- /dev/null +++ b/doc/qtcreator/images/qtcreator-coco-configpage.png diff --git a/doc/qtcreator/images/qtcreator-coco.png b/doc/qtcreator/images/qtcreator-coco.png Binary files differindex 851897ef2b3..841ba4f196f 100644 --- a/doc/qtcreator/images/qtcreator-coco.png +++ b/doc/qtcreator/images/qtcreator-coco.png 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 ¶meters, + void setParametersAndRequestParse(const Internal::BuildDirParameters ¶meters, const int reparseParameters); - bool mustApplyConfigurationChangesArguments(const BuildDirParameters ¶meters) const; + bool mustApplyConfigurationChangesArguments(const Internal::BuildDirParameters ¶meters) const; // State handling: // Parser states: @@ -208,7 +206,7 @@ private: void wireUpConnections(); - void ensureBuildDirectory(const BuildDirParameters ¶meters); + void ensureBuildDirectory(const Internal::BuildDirParameters ¶meters); 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 Binary files differnew file mode 100644 index 00000000000..74a68a1727c --- /dev/null +++ b/src/plugins/coco/images/SquishCoco_48x48.png 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 |