forked from qt-creator/qt-creator
QmlProjectManager: Add new cmake generator
Automatic cmake generation can now be enabled by setting the qmlproject property enableCMakeGeneration to true Change-Id: I98523a9479d0cd812e43a9bd0b700120358260f6 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io> Reviewed-by: Tim Jenssen <tim.jenssen@qt.io> Reviewed-by: Burak Hancerli <burak.hancerli@qt.io>
This commit is contained in:
@@ -50,6 +50,7 @@ extend_qtc_plugin(QmlProjectManager
|
||||
cmakeprojectconverterdialog.cpp cmakeprojectconverterdialog.h
|
||||
generatecmakelists.cpp generatecmakelists.h
|
||||
generatecmakelistsconstants.h
|
||||
cmakegenerator.cpp cmakegenerator.h
|
||||
boilerplate.qrc
|
||||
)
|
||||
|
||||
@@ -57,7 +58,7 @@ add_qtc_library(QmlProjectManagerLib OBJECT
|
||||
CONDITION Qt6_VERSION VERSION_GREATER_EQUAL 6.4.3
|
||||
EXCLUDE_FROM_INSTALL
|
||||
DEPENDS
|
||||
QmlJS Utils
|
||||
QmlJS Utils ProjectExplorer
|
||||
INCLUDES
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/buildsystem
|
||||
|
||||
@@ -143,6 +143,7 @@ QString jsonToQmlProject(const QJsonObject &rootObject)
|
||||
appendString("mainFile", runConfig["mainFile"].toString());
|
||||
appendString("mainUiFile", runConfig["mainUiFile"].toString());
|
||||
appendString("targetDirectory", deploymentConfig["targetDirectory"].toString());
|
||||
appendBool("enableCMakeGeneration", deploymentConfig["enableCMakeGeneration"].toBool());
|
||||
appendBool("widgetApp", runConfig["widgetApp"].toBool());
|
||||
appendStringArray("importPaths", rootObject["importPaths"].toVariant().toStringList());
|
||||
appendBreak();
|
||||
@@ -282,7 +283,8 @@ QJsonObject qmlProjectTojson(const Utils::FilePath &projectFile)
|
||||
|| propName.contains("mainuifile", Qt::CaseInsensitive)
|
||||
|| propName.contains("forcefreetype", Qt::CaseInsensitive)) {
|
||||
currentObj = &runConfigObject;
|
||||
} else if (propName.contains("targetdirectory", Qt::CaseInsensitive)) {
|
||||
} else if (propName.contains("targetdirectory", Qt::CaseInsensitive)
|
||||
|| propName.contains("enableCMakeGeneration", Qt::CaseInsensitive)) {
|
||||
currentObj = &deploymentObject;
|
||||
} else if (propName.contains("qtformcus", Qt::CaseInsensitive)) {
|
||||
qtForMCUs = value.toBool();
|
||||
|
||||
@@ -422,4 +422,16 @@ void QmlProjectItem::insertAndUpdateProjectFile(const QString &key, const QJsonV
|
||||
m_projectFile.writeFileContents(Converters::jsonToQmlProject(m_project).toUtf8());
|
||||
}
|
||||
|
||||
bool QmlProjectItem::enableCMakeGeneration() const
|
||||
{
|
||||
return m_project["deployment"].toObject()["enableCMakeGeneration"].toBool();
|
||||
}
|
||||
|
||||
void QmlProjectItem::setEnableCMakeGeneration(bool enable)
|
||||
{
|
||||
QJsonObject obj = m_project["deployment"].toObject();
|
||||
obj["enableCMakeGeneration"] = enable;
|
||||
insertAndUpdateProjectFile("deployment", obj);
|
||||
}
|
||||
|
||||
} // namespace QmlProjectManager
|
||||
|
||||
@@ -88,6 +88,9 @@ public:
|
||||
|
||||
QJsonObject project() const;
|
||||
|
||||
bool enableCMakeGeneration() const;
|
||||
void setEnableCMakeGeneration(bool enable);
|
||||
|
||||
signals:
|
||||
void qmlFilesChanged(const QSet<QString> &, const QSet<QString> &);
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ void updateMcuBuildStep(Target *target, bool mcuEnabled)
|
||||
|
||||
QmlBuildSystem::QmlBuildSystem(Target *target)
|
||||
: BuildSystem(target)
|
||||
, m_cmakeGen(new GenerateCmake::CMakeGenerator(this, this))
|
||||
{
|
||||
// refresh first - project information is used e.g. to decide the default RC's
|
||||
refresh(RefreshOptions::Project);
|
||||
@@ -86,10 +87,12 @@ QmlBuildSystem::QmlBuildSystem(Target *target)
|
||||
|
||||
connect(target->project(), &Project::activeTargetChanged, this, [this](Target *target) {
|
||||
refresh(RefreshOptions::NoFileRefresh);
|
||||
m_cmakeGen->initialize(qmlProject());
|
||||
updateMcuBuildStep(target, qtForMCUs());
|
||||
});
|
||||
connect(target->project(), &Project::projectFileIsDirty, this, [this]() {
|
||||
refresh(RefreshOptions::Project);
|
||||
m_cmakeGen->initialize(qmlProject());
|
||||
updateMcuBuildStep(project()->activeTarget(), qtForMCUs());
|
||||
});
|
||||
|
||||
@@ -220,6 +223,13 @@ void QmlBuildSystem::initProjectItem()
|
||||
&QmlProjectItem::qmlFilesChanged,
|
||||
this,
|
||||
&QmlBuildSystem::refreshFiles);
|
||||
|
||||
connect(m_projectItem.get(),
|
||||
&QmlProjectItem::qmlFilesChanged,
|
||||
m_cmakeGen,
|
||||
&GenerateCmake::CMakeGenerator::update);
|
||||
|
||||
m_cmakeGen->setEnabled(m_projectItem->enableCMakeGeneration());
|
||||
}
|
||||
|
||||
void QmlBuildSystem::parseProjectFiles()
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "../qmlprojectmanager_global.h"
|
||||
#include <projectexplorer/buildsystem.h>
|
||||
|
||||
#include "qmlprojectmanager/cmakegen/cmakegenerator.h"
|
||||
|
||||
namespace QmlProjectManager {
|
||||
|
||||
class QmlProject;
|
||||
@@ -122,6 +124,8 @@ private:
|
||||
void registerMenuButtons();
|
||||
void updateDeploymentData();
|
||||
friend class FilesUpdateBlocker;
|
||||
|
||||
GenerateCmake::CMakeGenerator* m_cmakeGen;
|
||||
};
|
||||
|
||||
} // namespace QmlProjectManager
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<RCC>
|
||||
<qresource prefix="/boilerplatetemplates">
|
||||
<file>gencmakeroot.tpl</file>
|
||||
<file>gencmakemodule.tpl</file>
|
||||
<file>gencmakeheadercomponents.tpl</file>
|
||||
<file>qmlprojectmaincpp.tpl</file>
|
||||
<file>qmlprojectmaincppheader.tpl</file>
|
||||
<file>qmlprojectenvheader.tpl</file>
|
||||
|
||||
550
src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp
Normal file
550
src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.cpp
Normal file
@@ -0,0 +1,550 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "cmakegenerator.h"
|
||||
#include "generatecmakelistsconstants.h"
|
||||
|
||||
#include "projectexplorer/projectmanager.h"
|
||||
#include "projectexplorer/projectnodes.h"
|
||||
#include "qmlprojectmanager/qmlproject.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace QmlProjectManager {
|
||||
|
||||
namespace GenerateCmake {
|
||||
|
||||
const char TEMPLATE_CMAKELISTS_ROOT[] = ":/boilerplatetemplates/gencmakeroot.tpl";
|
||||
const char TEMPLATE_CMAKELISTS_MODULE[] = ":/boilerplatetemplates/gencmakemodule.tpl";
|
||||
|
||||
const char TEMPLATE_QMLMODULES[] = ":/boilerplatetemplates/qmlprojectmodules.tpl";
|
||||
const char TEMPLATE_SOURCE_MAIN[] = ":/boilerplatetemplates/qmlprojectmaincpp.tpl";
|
||||
const char TEMPLATE_HEADER_IMPORT_COMPS[] = ":/boilerplatetemplates/gencmakeheadercomponents.tpl";
|
||||
const char TEMPLATE_HEADER_IMPORT_PLUGINS[] = ":/boilerplatetemplates/qmlprojectmaincppheader.tpl";
|
||||
const char TEMPLATE_HEADER_ENVIRONMENT[] = ":/boilerplatetemplates/qmlprojectenvheader.tpl";
|
||||
|
||||
const char DO_NOT_EDIT_FILE_COMMENT[] =
|
||||
"### This file is automatically generated by Qt Design Studio.\n"
|
||||
"### Do not change\n\n";
|
||||
|
||||
const char ADD_SUBDIR[] = "add_subdirectory(%1)\n";
|
||||
|
||||
const char BIG_RESOURCE_TEMPLATE[] = R"(
|
||||
qt6_add_resources(%1 %2
|
||||
BIG_RESOURCES
|
||||
PREFIX "%3"
|
||||
VERSION 1.0
|
||||
FILES %4
|
||||
))";
|
||||
|
||||
CMakeGenerator::CMakeGenerator(QmlBuildSystem *bs, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_root(std::make_shared<Node>())
|
||||
, m_buildSystem(bs)
|
||||
{}
|
||||
|
||||
void CMakeGenerator::setEnabled(bool enabled)
|
||||
{
|
||||
m_enabled = enabled;
|
||||
}
|
||||
|
||||
void CMakeGenerator::initialize(QmlProject *project)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
m_srcs.clear();
|
||||
m_moduleNames.clear();
|
||||
|
||||
m_root = std::make_shared<Node>();
|
||||
m_root->name = QString("Root");
|
||||
m_root->dir = project->rootProjectDirectory();
|
||||
|
||||
m_projectName = project->displayName();
|
||||
|
||||
ProjectExplorer::ProjectNode *rootProjectNode = project->rootProjectNode();
|
||||
parseNodeTree(m_root, rootProjectNode);
|
||||
parseSourceTree();
|
||||
|
||||
createCMakeFiles(m_root);
|
||||
createEntryPoints(m_root);
|
||||
}
|
||||
|
||||
void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &removed)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
std::set<NodePtr> dirtyModules;
|
||||
for (const QString &add : added) {
|
||||
const Utils::FilePath path = Utils::FilePath::fromString(add);
|
||||
if (auto node = findOrCreateNode(m_root, path)) {
|
||||
insertFile(node, path);
|
||||
if (auto module = findModuleFor(node))
|
||||
dirtyModules.insert(module);
|
||||
} else {
|
||||
qDebug() << "CmakeGen: Failed to find Folder node " << path;
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &remove : removed) {
|
||||
const Utils::FilePath path = Utils::FilePath::fromString(remove);
|
||||
if (auto node = findNode(m_root, path)) {
|
||||
removeFile(node, path);
|
||||
if (auto module = findModuleFor(node))
|
||||
dirtyModules.insert(module);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto module : dirtyModules)
|
||||
createModuleCMakeFile(module);
|
||||
}
|
||||
|
||||
std::vector<Utils::FilePath> CMakeGenerator::qmlFiles(const NodePtr &node) const
|
||||
{
|
||||
std::vector<Utils::FilePath> out = node->files;
|
||||
for (const NodePtr &child : node->subdirs) {
|
||||
if (child->module)
|
||||
continue;
|
||||
|
||||
auto childFiles = qmlFiles(child);
|
||||
out.insert(out.end(), childFiles.begin(), childFiles.end());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Utils::FilePath> CMakeGenerator::singletons(const NodePtr &node) const
|
||||
{
|
||||
std::vector<Utils::FilePath> out = node->singletons;
|
||||
for (const NodePtr &child : node->subdirs) {
|
||||
if (child->module)
|
||||
continue;
|
||||
|
||||
auto childFiles = singletons(child);
|
||||
out.insert(out.end(), childFiles.begin(), childFiles.end());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Utils::FilePath> CMakeGenerator::resources(const NodePtr &node) const
|
||||
{
|
||||
std::vector<Utils::FilePath> out = node->resources;
|
||||
for (const NodePtr &child : node->subdirs) {
|
||||
if (child->module)
|
||||
continue;
|
||||
|
||||
auto childFiles = resources(child);
|
||||
out.insert(out.end(), childFiles.begin(), childFiles.end());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Utils::FilePath> CMakeGenerator::sources(const NodePtr &node) const
|
||||
{
|
||||
std::vector<Utils::FilePath> out = node->sources;
|
||||
for (const NodePtr &child : node->subdirs) {
|
||||
if (child->module)
|
||||
continue;
|
||||
|
||||
auto childFiles = sources(child);
|
||||
out.insert(out.end(), childFiles.begin(), childFiles.end());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void CMakeGenerator::createCMakeFiles(const NodePtr &node) const
|
||||
{
|
||||
if (node->name == "Root") {
|
||||
createMainCMakeFile(node);
|
||||
createQmlModuleFile(node);
|
||||
} else if (node->module || hasChildModule(node)) {
|
||||
createModuleCMakeFile(node);
|
||||
}
|
||||
for (const NodePtr &n : node->subdirs)
|
||||
createCMakeFiles(n);
|
||||
}
|
||||
|
||||
void CMakeGenerator::createMainCMakeFile(const NodePtr &node) const
|
||||
{
|
||||
const QString appName = m_projectName + "App";
|
||||
|
||||
const QString qtcontrolsConfFile = makeEnvironmentVariable(Constants::ENV_VARIABLE_CONTROLCONF);
|
||||
|
||||
QString fileSection = "";
|
||||
if (!qtcontrolsConfFile.isEmpty())
|
||||
fileSection = QString(" FILES\n %1").arg(qtcontrolsConfFile);
|
||||
|
||||
const QString fileTemplate = readTemplate(TEMPLATE_CMAKELISTS_ROOT);
|
||||
const QString fileContent = fileTemplate.arg(appName, m_srcs.join(" "), fileSection);
|
||||
|
||||
const Utils::FilePath file = node->dir.pathAppended("CMakeLists.txt");
|
||||
writeFile(file, fileContent);
|
||||
}
|
||||
|
||||
void CMakeGenerator::createQmlModuleFile(const NodePtr &node) const
|
||||
{
|
||||
const QString appName = m_projectName + "App";
|
||||
|
||||
QString subdirIncludes;
|
||||
for (const NodePtr &n : node->subdirs)
|
||||
subdirIncludes.append(QString(ADD_SUBDIR).arg(n->name));
|
||||
|
||||
QString modulesAsPlugins;
|
||||
for (const QString &moduleName : m_moduleNames)
|
||||
modulesAsPlugins.append(" " + moduleName + "plugin\n");
|
||||
|
||||
const QString fileTemplate = readTemplate(TEMPLATE_QMLMODULES);
|
||||
const QString fileContent = fileTemplate.arg(appName, subdirIncludes, modulesAsPlugins);
|
||||
|
||||
const Utils::FilePath file = node->dir.pathAppended("qmlModules");
|
||||
writeFile(file, fileContent);
|
||||
}
|
||||
|
||||
void CMakeGenerator::createModuleCMakeFile(const NodePtr &node) const
|
||||
{
|
||||
QString subDirContent;
|
||||
for (const NodePtr &n : node->subdirs) {
|
||||
if (n->module || hasChildModule(n))
|
||||
subDirContent.append(QString(ADD_SUBDIR).arg(n->dir.fileName()));
|
||||
}
|
||||
|
||||
QString content;
|
||||
if (!node->module && hasChildModule(node)) {
|
||||
content.append(DO_NOT_EDIT_FILE_COMMENT);
|
||||
content.append(subDirContent);
|
||||
Utils::FilePath file = node->dir.pathAppended("CMakeLists.txt");
|
||||
writeFile(file, content);
|
||||
return;
|
||||
}
|
||||
|
||||
auto makeRelative = [](const Utils::FilePath &base,
|
||||
const Utils::FilePath &converted) -> QString {
|
||||
return "\"" + Utils::FilePath::calcRelativePath(converted.toString(), base.toString()) + "\"";
|
||||
};
|
||||
|
||||
QString uri = node->uri;
|
||||
if (uri.isEmpty())
|
||||
uri = node->dir.baseName();
|
||||
|
||||
const QString setProperties(
|
||||
"set_source_files_properties(%1\n PROPERTIES\n %2 %3\n)\n\n");
|
||||
|
||||
for (const Utils::FilePath &path : node->singletons) {
|
||||
content.append(setProperties.arg(path.fileName()).arg("QT_QML_SINGLETON_TYPE").arg("true"));
|
||||
}
|
||||
|
||||
if (!subDirContent.isEmpty())
|
||||
content.append(subDirContent);
|
||||
|
||||
QString qmlFileContent;
|
||||
for (const Utils::FilePath &path : qmlFiles(node)) {
|
||||
qmlFileContent.append(QString(" %1\n").arg(makeRelative(node->dir, path)));
|
||||
}
|
||||
|
||||
QString moduleContent;
|
||||
if (!qmlFileContent.isEmpty())
|
||||
moduleContent.append(QString(" QML_FILES\n%1").arg(qmlFileContent));
|
||||
|
||||
std::vector<QString> bigResources;
|
||||
QString resourceFiles;
|
||||
for (const Utils::FilePath &path : resources(node)) {
|
||||
if (path.fileSize() > 5000000) {
|
||||
bigResources.push_back(makeRelative(node->dir, path));
|
||||
continue;
|
||||
}
|
||||
resourceFiles.append(QString(" %1\n").arg(makeRelative(node->dir, path)));
|
||||
}
|
||||
|
||||
if (!resourceFiles.isEmpty())
|
||||
moduleContent.append(QString(" RESOURCES\n%1").arg(resourceFiles));
|
||||
|
||||
QString bigResourceContent;
|
||||
if (!bigResources.empty()) {
|
||||
QString resourceContent;
|
||||
for (const QString &res : bigResources)
|
||||
resourceContent.append(QString("\n %1").arg(res));
|
||||
|
||||
const QString prefixPath = QString(uri).replace('.', '/');
|
||||
const QString prefix = "/qt/qml/" + prefixPath;
|
||||
const QString resourceName = node->name + "BigResource";
|
||||
|
||||
bigResourceContent = QString::fromUtf8(BIG_RESOURCE_TEMPLATE, -1)
|
||||
.arg(node->name, resourceName, prefix, resourceContent);
|
||||
}
|
||||
|
||||
const QString fileTemplate = readTemplate(TEMPLATE_CMAKELISTS_MODULE);
|
||||
const QString fileContent =
|
||||
fileTemplate.arg(content, node->name, uri, moduleContent, bigResourceContent);
|
||||
|
||||
const Utils::FilePath file = node->dir.pathAppended("CMakeLists.txt");
|
||||
writeFile(file, fileContent);
|
||||
}
|
||||
|
||||
void CMakeGenerator::createEntryPoints(const NodePtr &node) const
|
||||
{
|
||||
createMainCppFile(node);
|
||||
}
|
||||
|
||||
void CMakeGenerator::createMainCppFile(const NodePtr &node) const
|
||||
{
|
||||
const Utils::FilePath srcDir = node->dir.pathAppended(Constants::DIRNAME_CPP);
|
||||
if (!srcDir.exists()) {
|
||||
srcDir.createDir();
|
||||
|
||||
const Utils::FilePath componentsHeaderPath = srcDir.pathAppended(
|
||||
"import_qml_components_plugins.h");
|
||||
const QString componentsHeaderContent = readTemplate(TEMPLATE_HEADER_IMPORT_COMPS);
|
||||
writeFile(componentsHeaderPath, componentsHeaderContent);
|
||||
|
||||
const Utils::FilePath cppFilePath = srcDir.pathAppended("main.cpp");
|
||||
const QString cppContent = readTemplate(TEMPLATE_SOURCE_MAIN);
|
||||
writeFile(cppFilePath, cppContent);
|
||||
|
||||
const Utils::FilePath envHeaderPath = srcDir.pathAppended("app_environment.h");
|
||||
if (m_buildSystem) {
|
||||
QString environment;
|
||||
const QString qtcontrolsConfFile = makeEnvironmentVariable(
|
||||
Constants::ENV_VARIABLE_CONTROLCONF);
|
||||
for (Utils::EnvironmentItem &envItem : m_buildSystem->environment()) {
|
||||
QString key = envItem.name;
|
||||
QString value = envItem.value;
|
||||
if (value == qtcontrolsConfFile)
|
||||
value.prepend(":/");
|
||||
environment.append(QString(" qputenv(\"%1\", \"%2\");\n").arg(key).arg(value));
|
||||
}
|
||||
const QString envHeaderContent = readTemplate(TEMPLATE_HEADER_ENVIRONMENT).arg(environment);
|
||||
writeFile(envHeaderPath, envHeaderContent);
|
||||
}
|
||||
}
|
||||
|
||||
QString moduleContent;
|
||||
for (const QString &module : m_moduleNames)
|
||||
moduleContent.append(QString("Q_IMPORT_QML_PLUGIN(%1)\n").arg(module + "Plugin"));
|
||||
|
||||
const QString headerContent = readTemplate(TEMPLATE_HEADER_IMPORT_PLUGINS).arg(moduleContent);
|
||||
const Utils::FilePath headerFilePath = srcDir.pathAppended("import_qml_plugins.h");
|
||||
writeFile(headerFilePath, headerContent);
|
||||
}
|
||||
|
||||
void CMakeGenerator::writeFile(const Utils::FilePath &path, const QString &content) const
|
||||
{
|
||||
QFile fileHandle(path.toString());
|
||||
fileHandle.open(QIODevice::WriteOnly);
|
||||
QTextStream stream(&fileHandle);
|
||||
stream << content;
|
||||
fileHandle.close();
|
||||
}
|
||||
|
||||
QString CMakeGenerator::makeEnvironmentVariable(const QString &key) const
|
||||
{
|
||||
QString value = {};
|
||||
|
||||
if (m_buildSystem) {
|
||||
auto envItems = m_buildSystem->environment();
|
||||
auto confEnv = std::find_if(envItems.begin(),
|
||||
envItems.end(),
|
||||
[key](Utils::NameValueItem &item) { return item.name == key; });
|
||||
if (confEnv != envItems.end())
|
||||
value = confEnv->value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
QString CMakeGenerator::readTemplate(const QString &templatePath) const
|
||||
{
|
||||
QFile templatefile(templatePath);
|
||||
templatefile.open(QIODevice::ReadOnly);
|
||||
QTextStream stream(&templatefile);
|
||||
QString content = stream.readAll();
|
||||
templatefile.close();
|
||||
return content;
|
||||
}
|
||||
|
||||
void CMakeGenerator::readQmlDir(const Utils::FilePath &filePath, NodePtr &node) const
|
||||
{
|
||||
node->module = true;
|
||||
|
||||
QFile f(filePath.toString());
|
||||
f.open(QIODevice::ReadOnly);
|
||||
QTextStream stream(&f);
|
||||
|
||||
Utils::FilePath dir = filePath.parentDir();
|
||||
while (!stream.atEnd()) {
|
||||
const QString line = stream.readLine();
|
||||
const QStringList tokenizedLine = line.split(QRegularExpression("\\s+"));
|
||||
const QString maybeFileName = tokenizedLine.last();
|
||||
if (tokenizedLine.first().compare("module", Qt::CaseInsensitive) == 0) {
|
||||
node->uri = tokenizedLine.last();
|
||||
node->name = QString(node->uri).replace('.', '_');
|
||||
} else if (maybeFileName.endsWith(".qml", Qt::CaseInsensitive)) {
|
||||
Utils::FilePath tmp = dir.pathAppended(maybeFileName);
|
||||
if (tokenizedLine.first() == "singleton")
|
||||
node->singletons.push_back(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
f.close();
|
||||
}
|
||||
|
||||
CMakeGenerator::NodePtr CMakeGenerator::findModuleFor(const NodePtr &node) const
|
||||
{
|
||||
NodePtr current = node;
|
||||
while (current->parent) {
|
||||
if (current->module)
|
||||
return current;
|
||||
|
||||
current = current->parent;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CMakeGenerator::NodePtr CMakeGenerator::findNode(NodePtr &node, const Utils::FilePath &path) const
|
||||
{
|
||||
const Utils::FilePath parentDir = path.parentDir();
|
||||
for (NodePtr &child : node->subdirs) {
|
||||
if (child->dir == parentDir)
|
||||
return child;
|
||||
if (path.isChildOf(child->dir))
|
||||
return findNode(child, path);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CMakeGenerator::NodePtr CMakeGenerator::findOrCreateNode(NodePtr &node,
|
||||
const Utils::FilePath &path) const
|
||||
{
|
||||
if (auto found = findNode(node, path))
|
||||
return found;
|
||||
|
||||
if (!path.isChildOf(node->dir))
|
||||
return nullptr;
|
||||
|
||||
const Utils::FilePath parentDir = path.parentDir();
|
||||
const Utils::FilePath relative = parentDir.relativeChildPath(node->dir);
|
||||
const QChar separator = relative.pathComponentSeparator();
|
||||
const QList<QStringView> components = relative.pathView().split(separator);
|
||||
|
||||
NodePtr last = node;
|
||||
for (const auto &comp : components) {
|
||||
NodePtr newNode = std::make_shared<Node>();
|
||||
newNode->parent = last;
|
||||
newNode->name = comp.toString();
|
||||
newNode->dir = last->dir.pathAppended(comp.toString());
|
||||
last->subdirs.push_back(newNode);
|
||||
last = newNode;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
void CMakeGenerator::insertFile(NodePtr &node, const Utils::FilePath &path) const
|
||||
{
|
||||
if (path.fileName() == "qmldir") {
|
||||
readQmlDir(path, node);
|
||||
} else if (path.suffix() == "qml" || path.suffix() == "ui.qml") {
|
||||
node->files.push_back(path);
|
||||
} else if (path.suffix() == "cpp") {
|
||||
node->sources.push_back(path);
|
||||
} else if (isResource(path)) {
|
||||
node->resources.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
void CMakeGenerator::removeFile(NodePtr &node, const Utils::FilePath &path) const
|
||||
{
|
||||
if (path.fileName() == "qmldir") {
|
||||
node->module = false;
|
||||
node->singletons.clear();
|
||||
node->uri = "";
|
||||
node->name = path.parentDir().fileName();
|
||||
|
||||
} else if (path.suffix() == "qml") {
|
||||
auto iter = std::find(node->files.begin(), node->files.end(), path);
|
||||
if (iter != node->files.end())
|
||||
node->files.erase(iter);
|
||||
} else if (isResource(path)) {
|
||||
auto iter = std::find(node->resources.begin(), node->resources.end(), path);
|
||||
if (iter != node->resources.end())
|
||||
node->resources.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
bool CMakeGenerator::hasChildModule(const NodePtr &node) const
|
||||
{
|
||||
for (const NodePtr &child : node->subdirs) {
|
||||
if (child->module)
|
||||
return true;
|
||||
if (hasChildModule(child))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CMakeGenerator::isResource(const Utils::FilePath &path) const
|
||||
{
|
||||
static const QStringList suffixes = {
|
||||
"json", "mesh", "dae", "qad", "hints", "png", "hdr", "ttf", "jpg",
|
||||
"JPG", "js", "qsb", "frag", "frag.qsb", "vert", "vert.qsb", "svg", "ktx"};
|
||||
return suffixes.contains(path.suffix());
|
||||
}
|
||||
|
||||
void CMakeGenerator::printNodeTree(const NodePtr &generatorNode, size_t indent) const
|
||||
{
|
||||
auto addIndent = [](size_t level) -> QString {
|
||||
QString str;
|
||||
for (size_t i = 0; i < level; ++i)
|
||||
str += " ";
|
||||
return str;
|
||||
};
|
||||
|
||||
qDebug() << addIndent(indent) << "GeneratorNode: " << generatorNode->name;
|
||||
qDebug() << addIndent(indent) << "directory: " << generatorNode->dir;
|
||||
qDebug() << addIndent(indent) << "files: " << generatorNode->files;
|
||||
qDebug() << addIndent(indent) << "singletons: " << generatorNode->singletons;
|
||||
qDebug() << addIndent(indent) << "resources: " << generatorNode->resources;
|
||||
qDebug() << addIndent(indent) << "sources: " << generatorNode->sources;
|
||||
|
||||
for (const auto &child : generatorNode->subdirs)
|
||||
printNodeTree(child, indent + 1);
|
||||
}
|
||||
|
||||
void CMakeGenerator::parseNodeTree(NodePtr &generatorNode,
|
||||
const ProjectExplorer::FolderNode *folderNode)
|
||||
{
|
||||
for (const auto *childNode : folderNode->nodes()) {
|
||||
if (const auto *subFolderNode = childNode->asFolderNode()) {
|
||||
CMakeGenerator::NodePtr childGeneratorNode = std::make_shared<Node>();
|
||||
childGeneratorNode->parent = generatorNode;
|
||||
childGeneratorNode->name = subFolderNode->displayName();
|
||||
childGeneratorNode->dir = subFolderNode->filePath();
|
||||
parseNodeTree(childGeneratorNode, subFolderNode);
|
||||
generatorNode->subdirs.push_back(childGeneratorNode);
|
||||
} else if (auto *fileNode = childNode->asFileNode()) {
|
||||
insertFile(generatorNode, fileNode->filePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (generatorNode->name == "content")
|
||||
generatorNode->module = true;
|
||||
|
||||
if (generatorNode->module)
|
||||
m_moduleNames.push_back(generatorNode->name);
|
||||
}
|
||||
|
||||
void CMakeGenerator::parseSourceTree()
|
||||
{
|
||||
m_srcs.clear();
|
||||
const QString srcDir = m_root->dir.pathAppended(Constants::DIRNAME_CPP).path();
|
||||
QDirIterator it(srcDir, QStringList({"*.cpp"}), QDir::Files, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
QString relative = Utils::FilePath::calcRelativePath(it.next(), m_root->dir.path());
|
||||
m_srcs.push_back(relative);
|
||||
}
|
||||
|
||||
if (m_srcs.empty())
|
||||
m_srcs.push_back("src/main.cpp");
|
||||
}
|
||||
|
||||
} // namespace GenerateCmake
|
||||
} // namespace QmlProjectManager
|
||||
101
src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h
Normal file
101
src/plugins/qmlprojectmanager/cmakegen/cmakegenerator.h
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2023 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 <QObject>
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class FolderNode;
|
||||
}
|
||||
|
||||
namespace QmlProjectManager {
|
||||
|
||||
class QmlProject;
|
||||
class QmlBuildSystem;
|
||||
|
||||
namespace GenerateCmake {
|
||||
|
||||
// TODO:
|
||||
// - Create "module" for src dir
|
||||
// - Replace AppName in templates with ${CMAKE_PROJECT_NAME}
|
||||
// - Introduce Blacklist (designer)
|
||||
|
||||
class CMakeGenerator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CMakeGenerator(QmlBuildSystem *bs, QObject *parent = nullptr);
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
void initialize(QmlProject *project);
|
||||
|
||||
void update(const QSet<QString> &added, const QSet<QString> &removed);
|
||||
|
||||
private:
|
||||
struct Node
|
||||
{
|
||||
std::shared_ptr<Node> parent = nullptr;
|
||||
bool module = false;
|
||||
|
||||
QString uri;
|
||||
QString name;
|
||||
Utils::FilePath dir;
|
||||
|
||||
std::vector<std::shared_ptr<Node>> subdirs;
|
||||
std::vector<Utils::FilePath> files;
|
||||
std::vector<Utils::FilePath> singletons;
|
||||
std::vector<Utils::FilePath> resources;
|
||||
std::vector<Utils::FilePath> sources;
|
||||
};
|
||||
|
||||
using NodePtr = std::shared_ptr<Node>;
|
||||
|
||||
std::vector<Utils::FilePath> qmlFiles(const NodePtr &node) const;
|
||||
std::vector<Utils::FilePath> singletons(const NodePtr &node) const;
|
||||
std::vector<Utils::FilePath> resources(const NodePtr &node) const;
|
||||
std::vector<Utils::FilePath> sources(const NodePtr &node) const;
|
||||
|
||||
void createCMakeFiles(const NodePtr &node) const;
|
||||
|
||||
void createQmlModuleFile(const NodePtr &node) const;
|
||||
void createMainCMakeFile(const NodePtr &node) const;
|
||||
void createModuleCMakeFile(const NodePtr &node) const;
|
||||
|
||||
void createEntryPoints(const NodePtr &node) const;
|
||||
void createMainCppFile(const NodePtr &node) const;
|
||||
void writeFile(const Utils::FilePath &path, const QString &content) const;
|
||||
|
||||
QString makeEnvironmentVariable(const QString &key) const;
|
||||
|
||||
QString readTemplate(const QString &templatePath) const;
|
||||
void readQmlDir(const Utils::FilePath &filePath, NodePtr &node) const;
|
||||
|
||||
NodePtr findModuleFor(const NodePtr &node) const;
|
||||
NodePtr findNode(NodePtr &node, const Utils::FilePath &path) const;
|
||||
NodePtr findOrCreateNode(NodePtr &node, const Utils::FilePath &path) const;
|
||||
|
||||
void insertFile(NodePtr &node, const Utils::FilePath &path) const;
|
||||
void removeFile(NodePtr &node, const Utils::FilePath &path) const;
|
||||
|
||||
bool hasChildModule(const NodePtr &node) const;
|
||||
bool isResource(const Utils::FilePath &path) const;
|
||||
|
||||
void printNodeTree(const NodePtr &generatorNode, size_t indent = 0) const;
|
||||
|
||||
void parseNodeTree(NodePtr &generatorNode, const ProjectExplorer::FolderNode *folderNode);
|
||||
void parseSourceTree();
|
||||
|
||||
bool m_enabled = false;
|
||||
QString m_projectName = {};
|
||||
NodePtr m_root = {};
|
||||
QStringList m_srcs = {};
|
||||
std::vector<QString> m_moduleNames = {};
|
||||
QmlBuildSystem *m_buildSystem = nullptr;
|
||||
};
|
||||
|
||||
} // namespace GenerateCmake
|
||||
} // namespace QmlProjectManager
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* This file is automatically generated by Qt Design Studio.
|
||||
* Do not change.
|
||||
*/
|
||||
|
||||
#include "qqmlextensionplugin.h"
|
||||
|
||||
#ifdef BUILD_QDS_COMPONENTS
|
||||
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_ComponentsPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EffectsPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_ApplicationPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(FlowViewPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_LogicHelperPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_MultiTextPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSimulatorPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSystemPlugin)
|
||||
|
||||
#endif
|
||||
14
src/plugins/qmlprojectmanager/cmakegen/gencmakemodule.tpl
Normal file
14
src/plugins/qmlprojectmanager/cmakegen/gencmakemodule.tpl
Normal file
@@ -0,0 +1,14 @@
|
||||
### This file is automatically generated by Qt Design Studio.
|
||||
### Do not change
|
||||
|
||||
%1
|
||||
|
||||
qt_add_library(%2 STATIC)
|
||||
qt6_add_qml_module(%2
|
||||
URI "%3"
|
||||
VERSION 1.0
|
||||
RESOURCE_PREFIX "/qt/qml"
|
||||
%4
|
||||
)
|
||||
|
||||
%5
|
||||
52
src/plugins/qmlprojectmanager/cmakegen/gencmakeroot.tpl
Normal file
52
src/plugins/qmlprojectmanager/cmakegen/gencmakeroot.tpl
Normal file
@@ -0,0 +1,52 @@
|
||||
cmake_minimum_required(VERSION 3.21.1)
|
||||
|
||||
option(LINK_INSIGHT "Link Qt Insight Tracker library" ON)
|
||||
option(BUILD_QDS_COMPONENTS "Build design studio components" ON)
|
||||
|
||||
project(%1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)
|
||||
set(QML_IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY}
|
||||
CACHE STRING "Import paths for Qt Creator's code model"
|
||||
FORCE
|
||||
)
|
||||
|
||||
find_package(Qt6 6.2 REQUIRED COMPONENTS Core Gui Qml Quick)
|
||||
|
||||
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.3)
|
||||
qt_standard_project_setup()
|
||||
endif()
|
||||
|
||||
qt_add_executable(%1 %2)
|
||||
|
||||
qt_add_resources(%1 "configuration"
|
||||
PREFIX "/"
|
||||
%3
|
||||
)
|
||||
|
||||
target_link_libraries(%1 PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
Qt${QT_VERSION_MAJOR}::Qml
|
||||
)
|
||||
|
||||
if (BUILD_QDS_COMPONENTS)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/qmlcomponents OPTIONAL)
|
||||
endif()
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/qmlmodules)
|
||||
|
||||
if (LINK_INSIGHT)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/insight OPTIONAL)
|
||||
endif ()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS %1
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ Project {
|
||||
mainFile: "content/App.qml"
|
||||
mainUiFile: "content/Screen01.ui.qml"
|
||||
targetDirectory: "/opt/UntitledProject13"
|
||||
enableCMakeGeneration: false
|
||||
widgetApp: true
|
||||
importPaths: [ "imports","asset_imports" ]
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ Project {
|
||||
/* Required for deployment */
|
||||
targetDirectory: "/opt/UntitledProject13"
|
||||
|
||||
enableCMakeGeneration: false
|
||||
|
||||
qdsVersion: "4.0"
|
||||
|
||||
quickVersion: "6.2"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"deployment": {
|
||||
"enableCMakeGeneration": false,
|
||||
"targetDirectory": "/opt/UntitledProject13"
|
||||
},
|
||||
"environment": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import QmlProject
|
||||
Project {
|
||||
mainFile: "fileSelectors.qml"
|
||||
targetDirectory: "/opt/fileSelectors"
|
||||
enableCMakeGeneration: false
|
||||
widgetApp: false
|
||||
importPaths: [ "imports" ]
|
||||
|
||||
|
||||
@@ -44,4 +44,6 @@ Project {
|
||||
|
||||
/* Required for deployment */
|
||||
targetDirectory: "/opt/fileSelectors"
|
||||
|
||||
enableCMakeGeneration: false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"deployment": {
|
||||
"enableCMakeGeneration": false,
|
||||
"targetDirectory": "/opt/fileSelectors"
|
||||
},
|
||||
"environment": {
|
||||
|
||||
@@ -7,6 +7,7 @@ Project {
|
||||
mainFile: "content/App.qml"
|
||||
mainUiFile: "content/Screen01.ui.qml"
|
||||
targetDirectory: "/opt/UntitledProject13"
|
||||
enableCMakeGeneration: false
|
||||
widgetApp: true
|
||||
importPaths: [ "imports","asset_imports" ]
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ Project {
|
||||
/* Required for deployment */
|
||||
QDS.targetDirectory: "/opt/UntitledProject13"
|
||||
|
||||
QDS.enableCMakeGeneration: false
|
||||
|
||||
QDS.qdsVersion: "4.0"
|
||||
|
||||
QDS.quickVersion: "6.2"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"deployment": {
|
||||
"enableCMakeGeneration": false,
|
||||
"targetDirectory": "/opt/UntitledProject13"
|
||||
},
|
||||
"environment": {
|
||||
|
||||
@@ -6,6 +6,7 @@ import QmlProject
|
||||
Project {
|
||||
mainFile: "Main.qml"
|
||||
targetDirectory: "/opt/UntitledProject13"
|
||||
enableCMakeGeneration: false
|
||||
widgetApp: true
|
||||
importPaths: [ "imports","asset_imports" ]
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import QmlProject
|
||||
|
||||
Project {
|
||||
enableCMakeGeneration: false
|
||||
widgetApp: false
|
||||
|
||||
qt6Project: false
|
||||
|
||||
@@ -11,6 +11,7 @@ Project {
|
||||
|
||||
importPaths: [ ]
|
||||
targetDirectory: ""
|
||||
enableCMakeGeneration: false
|
||||
fileSelectors: [ ]
|
||||
|
||||
qdsVersion: ""
|
||||
|
||||
@@ -11,6 +11,7 @@ Project {
|
||||
|
||||
QDS.importPaths: [ "imports", "asset_imports" ]
|
||||
QDS.targetDirectory: "/opt/targetDirectory"
|
||||
QDS.enableCMakeGeneration: true
|
||||
QDS.fileSelectors: [ "WXGA", "darkTheme", "ShowIndicator"]
|
||||
|
||||
QDS.qdsVersion: "3.9"
|
||||
|
||||
@@ -11,6 +11,7 @@ Project {
|
||||
|
||||
importPaths: [ "imports", "asset_imports" ]
|
||||
targetDirectory: "/opt/targetDirectory"
|
||||
enableCMakeGeneration: true
|
||||
fileSelectors: [ "WXGA", "darkTheme", "ShowIndicator"]
|
||||
|
||||
qdsVersion: "3.9"
|
||||
|
||||
@@ -121,6 +121,13 @@ TEST_F(QmlProjectItem, get_with_qds_prefix_tar_get_with_qds_prefix_directory)
|
||||
ASSERT_THAT(targetDirectory, Eq("/opt/targetDirectory"));
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, get_with_qds_prefix_enable_cmake_generation)
|
||||
{
|
||||
auto enable = projectItemWithQdsPrefix->enableCMakeGeneration();
|
||||
|
||||
ASSERT_TRUE(enable);
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, get_with_qds_prefix_import_paths)
|
||||
{
|
||||
auto importPaths = projectItemWithQdsPrefix->importPaths();
|
||||
@@ -266,6 +273,13 @@ TEST_F(QmlProjectItem, get_without_qds_prefix_tar_get_without_qds_prefix_directo
|
||||
ASSERT_THAT(targetDirectory, Eq("/opt/targetDirectory"));
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, get_without_qds_prefix_enable_cmake_generation)
|
||||
{
|
||||
auto enable = projectItemWithoutQdsPrefix->enableCMakeGeneration();
|
||||
|
||||
ASSERT_TRUE(enable);
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, get_without_qds_prefix_import_paths)
|
||||
{
|
||||
auto importPaths = projectItemWithoutQdsPrefix->importPaths();
|
||||
@@ -413,6 +427,13 @@ TEST_F(QmlProjectItem, get_empty_tar_get_empty_directory)
|
||||
ASSERT_THAT(targetDirectory, IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, get_empty_enable_cmake_generation)
|
||||
{
|
||||
auto enable = projectItemEmpty->enableCMakeGeneration();
|
||||
|
||||
ASSERT_FALSE(enable);
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, get_empty_import_paths)
|
||||
{
|
||||
auto importPaths = projectItemEmpty->importPaths();
|
||||
@@ -677,6 +698,13 @@ TEST_F(QmlProjectItem, set_design_studio_version)
|
||||
ASSERT_EQ(projectItemSetters->versionDesignStudio(), "6");
|
||||
}
|
||||
|
||||
TEST_F(QmlProjectItem, set_enable_cmake_generation)
|
||||
{
|
||||
projectItemSetters->setEnableCMakeGeneration(true);
|
||||
|
||||
ASSERT_EQ(projectItemSetters->enableCMakeGeneration(), true);
|
||||
}
|
||||
|
||||
// TODO: We should move these 2 tests into the integration tests
|
||||
TEST_F(QmlProjectItem, test_file_filters)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user