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 <riitta-leena.miettinen@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Markus Redeker
2024-09-30 16:41:30 +02:00
parent a5a8450edc
commit 090b540487
48 changed files with 2724 additions and 76 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

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

View File

@@ -19,9 +19,11 @@ namespace ProjectExplorer {
class Project;
}
namespace CMakeProjectManager::Internal {
namespace CMakeProjectManager {
class CMakeBuildSystem;
}
namespace CMakeProjectManager::Internal {
class BuildDirParameters
{

View File

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

View File

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

View File

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

View File

@@ -29,13 +29,11 @@ namespace CMakeProjectManager {
class CMakeBuildConfiguration;
class CMakeProject;
namespace Internal {
// --------------------------------------------------------------------
// CMakeBuildSystem:
// --------------------------------------------------------------------
class CMakeBuildSystem final : public ProjectExplorer::BuildSystem
class CMAKE_EXPORT CMakeBuildSystem final : public ProjectExplorer::BuildSystem
{
Q_OBJECT
@@ -157,7 +155,7 @@ private:
Utils::FilePaths *);
bool addTsFiles(ProjectExplorer::Node *context, const Utils::FilePaths &filePaths,
Utils::FilePaths *);
bool renameFile(CMakeTargetNode *context,
bool renameFile(Internal::CMakeTargetNode *context,
const Utils::FilePath &oldFilePath,
const Utils::FilePath &newFilePath, bool &shouldRunCMake);
@@ -175,10 +173,10 @@ private:
};
void reparse(int reparseParameters);
QString reparseParametersString(int reparseFlags);
void setParametersAndRequestParse(const BuildDirParameters &parameters,
void setParametersAndRequestParse(const Internal::BuildDirParameters &parameters,
const int reparseParameters);
bool mustApplyConfigurationChangesArguments(const BuildDirParameters &parameters) const;
bool mustApplyConfigurationChangesArguments(const Internal::BuildDirParameters &parameters) const;
// State handling:
// Parser states:
@@ -208,7 +206,7 @@ private:
void wireUpConnections();
void ensureBuildDirectory(const BuildDirParameters &parameters);
void ensureBuildDirectory(const Internal::BuildDirParameters &parameters);
void stopParsingAndClearState();
void becameDirty();
@@ -243,7 +241,7 @@ private:
ProjectExplorer::ProjectUpdater *m_cppCodeModelUpdater = nullptr;
QList<ProjectExplorer::ExtraCompiler *> m_extraCompilers;
QList<CMakeBuildTarget> m_buildTargets;
QSet<CMakeFileInfo> m_cmakeFiles;
QSet<Internal::CMakeFileInfo> m_cmakeFiles;
QHash<QString, Utils::Link> m_cmakeSymbolsHash;
QHash<QString, Utils::Link> m_dotCMakeFilesHash;
QHash<QString, Utils::Link> m_findPackagesFilesHash;
@@ -254,9 +252,9 @@ private:
QHash<QString, ProjectFileArgumentPosition> m_filesToBeRenamed;
// Parsing state:
BuildDirParameters m_parameters;
Internal::BuildDirParameters m_parameters;
int m_reparseParameters = REPARSE_DEFAULT;
FileApiReader m_reader;
Internal::FileApiReader m_reader;
mutable bool m_isHandlingError = false;
// CTest integration
@@ -271,5 +269,4 @@ private:
QString m_warning;
};
} // namespace Internal
} // namespace CMakeProjectManager

View File

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

View File

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

View File

@@ -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",
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
CocoInstallation coco;
if (coco.isValid()) {
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 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());
PathChooser cocoChooser;
if (!candidate.isEmpty())
cocoChooser.setFilePath(candidate);
cocoChooser.setExpectedKind(PathChooser::Command);
cocoChooser.setPromptDialogTitle(Tr::tr("Select a Squish Coco CoverageBrowser Executable"));
QObject::connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
QObject::connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
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();
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"

View File

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

View File

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

View File

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

View File

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

14
src/plugins/coco/common.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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