QmlProjectManager: New project structure support for CMakeGenerator

- Add writer interface in order to support the current and the new
  project structure in parallel. Using the new one if
  qdsVersion is >= 4.5
- Separated templates for the new generator from the old one
- Add file name validity check
- Generate files in the folder src and cmake if they do not exist yet.
  Only re-generate files in src/autogen.
- Add action to enable or disable the cmake-generator
- Add function that checks if a resource file is within the
  project folder but not part of the project

Change-Id: I3d75dbee1043ed28e6126cf0b2c83994cb70ed45
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Knud Dollereder
2024-03-22 11:38:14 +01:00
parent beb9fc2fde
commit 3721620799
23 changed files with 1255 additions and 367 deletions

View File

@@ -50,6 +50,9 @@ extend_qtc_plugin(QmlProjectManager
generatecmakelists.cpp generatecmakelists.h generatecmakelists.cpp generatecmakelists.h
generatecmakelistsconstants.h generatecmakelistsconstants.h
cmakegenerator.cpp cmakegenerator.h cmakegenerator.cpp cmakegenerator.h
cmakewriter.cpp cmakewriter.h
cmakewriterv0.cpp cmakewriterv0.h
cmakewriterv1.cpp cmakewriterv1.h
boilerplate.qrc boilerplate.qrc
) )

View File

@@ -93,6 +93,7 @@ QmlBuildSystem::QmlBuildSystem(Target *target)
connect(target->project(), &Project::projectFileIsDirty, this, [this] { connect(target->project(), &Project::projectFileIsDirty, this, [this] {
refresh(RefreshOptions::Project); refresh(RefreshOptions::Project);
m_cmakeGen->initialize(qmlProject()); m_cmakeGen->initialize(qmlProject());
m_cmakeGen->updateMenuAction();
updateMcuBuildStep(project()->activeTarget(), qtForMCUs()); updateMcuBuildStep(project()->activeTarget(), qtForMCUs());
}); });
@@ -501,6 +502,17 @@ void QmlBuildSystem::setPrimaryLanguage(QString language)
m_projectItem->setPrimaryLanguage(language); m_projectItem->setPrimaryLanguage(language);
} }
bool QmlBuildSystem::enableCMakeGeneration() const
{
return m_projectItem->enableCMakeGeneration();
}
void QmlBuildSystem::setEnableCMakeGeneration(bool enable)
{
if (enable != enableCMakeGeneration())
m_projectItem->setEnableCMakeGeneration(enable);
}
void QmlBuildSystem::refreshFiles(const QSet<QString> & /*added*/, const QSet<QString> &removed) void QmlBuildSystem::refreshFiles(const QSet<QString> & /*added*/, const QSet<QString> &removed)
{ {
if (m_blockFilesUpdate) { if (m_blockFilesUpdate) {

View File

@@ -83,6 +83,9 @@ public:
QString primaryLanguage() const; QString primaryLanguage() const;
void setPrimaryLanguage(QString language); void setPrimaryLanguage(QString language);
bool enableCMakeGeneration() const;
void setEnableCMakeGeneration(bool enable);
bool forceFreeType() const; bool forceFreeType() const;
bool widgetApp() const; bool widgetApp() const;

View File

@@ -1,8 +1,17 @@
<RCC> <RCC>
<qresource prefix="/templates">
<file alias="cmakeroot_v0">templates/cmakeroot_v0.tpl</file>
<file alias="cmakeroot_v1">templates/cmakeroot_v1.tpl</file>
<file alias="main_cpp_v0">templates/main_cpp_v0.tpl</file>
<file alias="main_cpp_v1">templates/main_cpp_v1.tpl</file>
<file alias="cmakemodule_v1">templates/cmakemodule_v1.tpl</file>
<file alias="insight">templates/insight.tpl</file>
<file alias="qmlcomponents">templates/qmlcomponents.tpl</file>
<file alias="environment_h">templates/environment_h.tpl</file>
<file alias="import_qml_components_h">templates/import_qml_components_h.tpl</file>
</qresource>
<qresource prefix="/boilerplatetemplates"> <qresource prefix="/boilerplatetemplates">
<file>gencmakeroot.tpl</file>
<file>gencmakemodule.tpl</file>
<file>gencmakeheadercomponents.tpl</file>
<file>qmlprojectmaincpp.tpl</file> <file>qmlprojectmaincpp.tpl</file>
<file>qmlprojectmaincppheader.tpl</file> <file>qmlprojectmaincppheader.tpl</file>
<file>qmlprojectenvheader.tpl</file> <file>qmlprojectenvheader.tpl</file>

View File

@@ -2,13 +2,22 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "cmakegenerator.h" #include "cmakegenerator.h"
#include "generatecmakelistsconstants.h"
#include "qmlprojectmanager/qmlproject.h"
#include "qmlprojectmanager/qmlprojectconstants.h"
#include "qmlprojectmanager/qmlprojectmanagertr.h"
#include "projectexplorer/projectmanager.h" #include "projectexplorer/projectmanager.h"
#include "projectexplorer/projectnodes.h" #include "projectexplorer/projectnodes.h"
#include "qmlprojectmanager/qmlproject.h"
#include "utils/filenamevalidatinglineedit.h"
#include "coreplugin/actionmanager/actionmanager.h"
#include "coreplugin/actionmanager/actioncontainer.h"
#include <QDirIterator>
#include <QRegularExpression> #include <QRegularExpression>
#include <QMenu>
#include <set> #include <set>
@@ -16,30 +25,63 @@ namespace QmlProjectManager {
namespace GenerateCmake { namespace GenerateCmake {
const char TEMPLATE_CMAKELISTS_ROOT[] = ":/boilerplatetemplates/gencmakeroot.tpl"; void CMakeGenerator::createMenuAction(QObject *parent)
const char TEMPLATE_CMAKELISTS_MODULE[] = ":/boilerplatetemplates/gencmakemodule.tpl"; {
Core::ActionContainer *fileMenu = Core::ActionManager::actionContainer(
Core::Constants::M_FILE);
Core::ActionContainer *exportMenu = Core::ActionManager::createMenu(
QmlProjectManager::Constants::EXPORT_MENU);
const char TEMPLATE_SOURCE_MAIN[] = ":/boilerplatetemplates/qmlprojectmaincpp.tpl"; exportMenu->menu()->setTitle(Tr::tr("Export Project"));
const char TEMPLATE_HEADER_IMPORT_COMPS[] = ":/boilerplatetemplates/gencmakeheadercomponents.tpl"; exportMenu->appendGroup(QmlProjectManager::Constants::G_EXPORT_GENERATE);
const char TEMPLATE_HEADER_IMPORT_PLUGINS[] = ":/boilerplatetemplates/qmlprojectmaincppheader.tpl"; fileMenu->addMenu(exportMenu, Core::Constants::G_FILE_EXPORT);
const char TEMPLATE_HEADER_ENVIRONMENT[] = ":/boilerplatetemplates/qmlprojectenvheader.tpl";
const char DO_NOT_EDIT_FILE_COMMENT[] auto action = new QAction(Tr::tr("Enable Automatic CMake Generation"), parent);
= "### This file is automatically generated by Qt Design Studio.\n" action->setEnabled(false);
"### Do not change\n\n"; action->setCheckable(true);
const char TEMPLATE_BIG_RESOURCES[] = R"( Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.EnableCMakeGeneration");
qt6_add_resources(%1 %2 exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE);
BIG_RESOURCES
PREFIX "%3"
VERSION 1.0
FILES %4
))";
const char TEMPLATE_LINK_LIBRARIES[] = R"( QObject::connect(
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ProjectExplorer::ProjectManager::instance(),
%3 &ProjectExplorer::ProjectManager::startupProjectChanged,
))"; [action]() {
if (auto buildSystem = QmlBuildSystem::getStartupBuildSystem()) {
action->setEnabled(!buildSystem->qtForMCUs());
action->setChecked(buildSystem->enableCMakeGeneration());
}
}
);
QObject::connect(action, &QAction::toggled, [](bool checked) {
if (auto buildSystem = QmlBuildSystem::getStartupBuildSystem())
buildSystem->setEnableCMakeGeneration(checked);
});
}
void CMakeGenerator::logIssue(const QString &text)
{
// TODO: Use Issues panel as soon as it is usable in DS.
qDebug() << text;
}
void CMakeGenerator::updateMenuAction()
{
QTC_ASSERT(buildSystem(), return);
Core::Command *cmd = Core::ActionManager::command("QmlProject.EnableCMakeGeneration");
if (!cmd)
return;
QAction *action = cmd->action();
if (!action)
return;
bool enabled = buildSystem()->enableCMakeGeneration();
if (enabled != action->isChecked())
action->setChecked(enabled);
}
CMakeGenerator::CMakeGenerator(QmlBuildSystem *bs, QObject *parent) CMakeGenerator::CMakeGenerator(QmlBuildSystem *bs, QObject *parent)
: QObject(parent) : QObject(parent)
@@ -47,6 +89,44 @@ CMakeGenerator::CMakeGenerator(QmlBuildSystem *bs, QObject *parent)
, m_buildSystem(bs) , m_buildSystem(bs)
{} {}
const QmlProject *CMakeGenerator::qmlProject() const
{
if (m_buildSystem)
return m_buildSystem->qmlProject();
return nullptr;
}
const QmlBuildSystem *CMakeGenerator::buildSystem() const
{
return m_buildSystem;
}
bool CMakeGenerator::findFile(const Utils::FilePath& file) const
{
return findFile(m_root, file);
}
bool CMakeGenerator::isRootNode(const NodePtr &node) const
{
return node->name == "Main";
}
bool CMakeGenerator::hasChildModule(const NodePtr &node) const
{
for (const NodePtr &child : node->subdirs) {
if (child->type == Node::Type::Module)
return true;
if (hasChildModule(child))
return true;
}
return false;
}
QString CMakeGenerator::projectName() const
{
return m_projectName;
}
void CMakeGenerator::setEnabled(bool enabled) void CMakeGenerator::setEnabled(bool enabled)
{ {
m_enabled = enabled; m_enabled = enabled;
@@ -57,11 +137,11 @@ void CMakeGenerator::initialize(QmlProject *project)
if (!m_enabled) if (!m_enabled)
return; return;
m_srcs.clear();
m_moduleNames.clear(); m_moduleNames.clear();
m_writer = CMakeWriter::create(this);
m_root = std::make_shared<Node>(); m_root = std::make_shared<Node>();
m_root->module = true; m_root->type = Node::Type::App;
m_root->uri = QString("Main"); m_root->uri = QString("Main");
m_root->name = QString("Main"); m_root->name = QString("Main");
m_root->dir = project->rootProjectDirectory(); m_root->dir = project->rootProjectDirectory();
@@ -72,8 +152,10 @@ void CMakeGenerator::initialize(QmlProject *project)
parseNodeTree(m_root, rootProjectNode); parseNodeTree(m_root, rootProjectNode);
parseSourceTree(); parseSourceTree();
compareWithFileSystem(m_root);
createCMakeFiles(m_root); createCMakeFiles(m_root);
createEntryPoints(m_root); createSourceFiles();
} }
void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &removed) void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &removed)
@@ -81,6 +163,8 @@ void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &rem
if (!m_enabled) if (!m_enabled)
return; return;
QTC_ASSERT(m_writer, return);
std::set<NodePtr> dirtyModules; std::set<NodePtr> dirtyModules;
for (const QString &add : added) { for (const QString &add : added) {
const Utils::FilePath path = Utils::FilePath::fromString(add); const Utils::FilePath path = Utils::FilePath::fromString(add);
@@ -89,7 +173,8 @@ void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &rem
if (auto module = findModuleFor(node)) if (auto module = findModuleFor(node))
dirtyModules.insert(module); dirtyModules.insert(module);
} else { } else {
qDebug() << "CmakeGen: Failed to find Folder node " << path; QString text("Failed to find Folder for file: %1");
logIssue(text.arg(add));
} }
} }
@@ -103,282 +188,65 @@ void CMakeGenerator::update(const QSet<QString> &added, const QSet<QString> &rem
} }
for (auto module : dirtyModules) for (auto module : dirtyModules)
createModuleCMakeFile(module); m_writer->writeModuleCMakeFile(module, m_root);
} }
std::vector<Utils::FilePath> CMakeGenerator::files(const NodePtr &node, bool CMakeGenerator::isQml(const Utils::FilePath &path) const
const FileGetter &getter) const
{ {
std::vector<Utils::FilePath> out = getter(node); const QString suffix = path.suffix();
for (const CMakeGenerator::NodePtr &child : node->subdirs) { return suffix == "qml" || suffix == "ui.qml";
if (child->module)
continue;
auto childFiles = files(child, getter);
out.insert(out.end(), childFiles.begin(), childFiles.end());
}
return out;
} }
std::vector<Utils::FilePath> CMakeGenerator::qmlFiles(const NodePtr &node) const bool CMakeGenerator::isResource(const Utils::FilePath &path) const
{ {
return files(node, [](const NodePtr &n) { return n->files; }); static const QStringList suffixes = {
} "json", "mesh", "dae", "qad", "hints", "png", "hdr", "ttf", "jpg",
"jpeg", "js", "qsb", "frag", "frag.qsb", "vert", "vert.qsb", "svg",
std::vector<Utils::FilePath> CMakeGenerator::singletons(const NodePtr &node) const "ktx", "bmp", "gif", "webp", "tiff"};
{ return suffixes.contains(path.suffix(), Qt::CaseInsensitive);
return files(node, [](const NodePtr &n) { return n->singletons; });
}
std::vector<Utils::FilePath> CMakeGenerator::resources(const NodePtr &node) const
{
return files(node, [](const NodePtr &n) { return n->resources; });
}
std::vector<Utils::FilePath> CMakeGenerator::sources(const NodePtr &node) const
{
return files(node, [](const NodePtr &n) { return n->sources; });
} }
void CMakeGenerator::createCMakeFiles(const NodePtr &node) const void CMakeGenerator::createCMakeFiles(const NodePtr &node) const
{ {
if (isRootNode(node)) QTC_ASSERT(m_writer, return);
createMainCMakeFile(node);
if (node->module || hasChildModule(node)) if (isRootNode(node))
createModuleCMakeFile(node); m_writer->writeRootCMakeFile(node);
if (node->type == Node::Type::Module || (hasChildModule(node)))
m_writer->writeModuleCMakeFile(node, m_root);
for (const NodePtr &n : node->subdirs) for (const NodePtr &n : node->subdirs)
createCMakeFiles(n); createCMakeFiles(n);
} }
void CMakeGenerator::createMainCMakeFile(const NodePtr &node) const void CMakeGenerator::createSourceFiles() const
{ {
const QString appName = m_projectName + "App"; QTC_ASSERT(m_writer, return);
const QString qtcontrolsConfFile = makeEnvironmentVariable(Constants::ENV_VARIABLE_CONTROLCONF); NodePtr sourceNode = {};
for (const NodePtr &child : m_root->subdirs) {
QString fileSection = ""; if (child->name == m_writer->sourceDirName())
if (!qtcontrolsConfFile.isEmpty()) sourceNode = child;
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::createModuleCMakeFile(const NodePtr &node) const
{
Utils::FilePath writeToFile = node->dir.pathAppended("CMakeLists.txt");
if (!node->module && hasChildModule(node)) {
QString content(DO_NOT_EDIT_FILE_COMMENT);
content.append(makeSubdirectoriesBlock(node));
writeFile(writeToFile, content);
return;
} }
QString templatePrefix; if (sourceNode)
templatePrefix.append(makeSubdirectoriesBlock(node)); m_writer->writeSourceFiles(sourceNode, m_root);
templatePrefix.append(makeSingletonBlock(node));
auto [resources, bigResources] = makeResourcesBlocks(node);
QString moduleContent;
moduleContent.append(makeQmlFilesBlock(node));
moduleContent.append(resources);
QString templatePostfix;
templatePostfix.append(bigResources);
if (isRootNode(node)) {
writeToFile = node->dir.pathAppended("qmlModules");
QString pluginNames;
for (const QString &moduleName : m_moduleNames)
pluginNames.append(" " + moduleName + "plugin\n");
templatePostfix += QString::fromUtf8(TEMPLATE_LINK_LIBRARIES, -1).arg(pluginNames);
}
const QString fileTemplate = readTemplate(TEMPLATE_CMAKELISTS_MODULE);
const QString fileContent
= fileTemplate.arg(node->name, node->uri, templatePrefix, moduleContent, templatePostfix);
writeFile(writeToFile, 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::makeRelative(const NodePtr &node, const Utils::FilePath &path) const
{
const QString dir = node->dir.toString();
return "\"" + Utils::FilePath::calcRelativePath(path.toString(), dir) + "\"";
}
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::EnvironmentItem &item) { return item.name == key; });
if (confEnv != envItems.end())
value = confEnv->value;
}
return value;
}
QString CMakeGenerator::makeSingletonBlock(const NodePtr &node) const
{
const QString setProperties(
"set_source_files_properties(%1\n PROPERTIES\n %2 %3\n)\n\n");
QString str;
for (const Utils::FilePath &path : node->singletons)
str.append(setProperties.arg(path.fileName()).arg("QT_QML_SINGLETON_TYPE").arg("true"));
return str;
}
QString CMakeGenerator::makeSubdirectoriesBlock(const NodePtr &node) const
{
QString str;
for (const NodePtr &n : node->subdirs) {
if (n->module || hasChildModule(n))
str.append(QString("add_subdirectory(%1)\n").arg(n->dir.fileName()));
}
return str;
}
QString CMakeGenerator::makeQmlFilesBlock(const NodePtr &node) const
{
QString qmlFileContent;
for (const Utils::FilePath &path : qmlFiles(node))
qmlFileContent.append(QString(" %1\n").arg(makeRelative(node, path)));
if (isRootNode(node) && qmlFileContent.isEmpty())
qmlFileContent.append(QString(" %1\n").arg("\"main.qml\""));
QString str;
if (!qmlFileContent.isEmpty())
str.append(QString(" QML_FILES\n%1").arg(qmlFileContent));
return str;
}
std::tuple<QString, QString> CMakeGenerator::makeResourcesBlocks(const NodePtr &node) const
{
QString resourcesOut;
QString bigResourcesOut;
QString resourceFiles;
std::vector<QString> bigResources;
for (const Utils::FilePath &path : resources(node)) {
if (path.fileSize() > 5000000) {
bigResources.push_back(makeRelative(node, path));
continue;
}
resourceFiles.append(QString(" %1\n").arg(makeRelative(node, path)));
}
if (!resourceFiles.isEmpty())
resourcesOut.append(QString(" RESOURCES\n%1").arg(resourceFiles));
QString templatePostfix;
if (!bigResources.empty()) {
QString resourceContent;
for (const QString &res : bigResources)
resourceContent.append(QString("\n %1").arg(res));
const QString prefixPath = QString(node->uri).replace('.', '/');
const QString prefix = "/qt/qml/" + prefixPath;
const QString resourceName = node->name + "BigResource";
bigResourcesOut = QString::fromUtf8(TEMPLATE_BIG_RESOURCES, -1)
.arg(node->name, resourceName, prefix, resourceContent);
}
return {resourcesOut, bigResourcesOut};
}
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 void CMakeGenerator::readQmlDir(const Utils::FilePath &filePath, NodePtr &node) const
{ {
node->module = true; node->type = Node::Type::Module;
QFile f(filePath.toString()); QFile f(filePath.toString());
f.open(QIODevice::ReadOnly); f.open(QIODevice::ReadOnly);
QTextStream stream(&f); QTextStream stream(&f);
Utils::FilePath dir = filePath.parentDir(); Utils::FilePath dir = filePath.parentDir();
static const QRegularExpression whitespaceRegex("\\s+");
while (!stream.atEnd()) { while (!stream.atEnd()) {
const QString line = stream.readLine(); const QString line = stream.readLine();
const QStringList tokenizedLine = line.split(QRegularExpression("\\s+")); const QStringList tokenizedLine = line.split(whitespaceRegex);
const QString maybeFileName = tokenizedLine.last(); const QString maybeFileName = tokenizedLine.last();
if (tokenizedLine.first().compare("module", Qt::CaseInsensitive) == 0) { if (tokenizedLine.first().compare("module", Qt::CaseInsensitive) == 0) {
node->uri = tokenizedLine.last(); node->uri = tokenizedLine.last();
@@ -392,11 +260,11 @@ void CMakeGenerator::readQmlDir(const Utils::FilePath &filePath, NodePtr &node)
f.close(); f.close();
} }
CMakeGenerator::NodePtr CMakeGenerator::findModuleFor(const NodePtr &node) const NodePtr CMakeGenerator::findModuleFor(const NodePtr &node) const
{ {
NodePtr current = node; NodePtr current = node;
while (current->parent) { while (current->parent) {
if (current->module) if (current->type == Node::Type::Module)
return current; return current;
current = current->parent; current = current->parent;
@@ -404,7 +272,7 @@ CMakeGenerator::NodePtr CMakeGenerator::findModuleFor(const NodePtr &node) const
return nullptr; return nullptr;
} }
CMakeGenerator::NodePtr CMakeGenerator::findNode(NodePtr &node, const Utils::FilePath &path) const NodePtr CMakeGenerator::findNode(NodePtr &node, const Utils::FilePath &path) const
{ {
const Utils::FilePath parentDir = path.parentDir(); const Utils::FilePath parentDir = path.parentDir();
for (NodePtr &child : node->subdirs) { for (NodePtr &child : node->subdirs) {
@@ -416,8 +284,7 @@ CMakeGenerator::NodePtr CMakeGenerator::findNode(NodePtr &node, const Utils::Fil
return nullptr; return nullptr;
} }
CMakeGenerator::NodePtr CMakeGenerator::findOrCreateNode(NodePtr &node, NodePtr CMakeGenerator::findOrCreateNode(NodePtr &node, const Utils::FilePath &path) const
const Utils::FilePath &path) const
{ {
if (auto found = findNode(node, path)) if (auto found = findNode(node, path))
return found; return found;
@@ -442,14 +309,46 @@ CMakeGenerator::NodePtr CMakeGenerator::findOrCreateNode(NodePtr &node,
return last; return last;
} }
bool findFileWithGetter(const Utils::FilePath &file, const NodePtr &node, const FileGetter &getter)
{
for (const auto &f : getter(node)) {
if (f == file)
return true;
}
for (const auto &subdir : node->subdirs) {
if (findFileWithGetter(file, subdir, getter))
return true;
}
return false;
}
bool CMakeGenerator::findFile(const NodePtr &node, const Utils::FilePath &file) const
{
if (isResource(file)) {
return findFileWithGetter(file, node, [](const NodePtr &n) { return n->resources; });
} else if (isQml(file)) {
if (findFileWithGetter(file, node, [](const NodePtr &n) { return n->files; }))
return true;
else if (findFileWithGetter(file, node, [](const NodePtr &n) { return n->singletons; }))
return true;
}
return false;
}
void CMakeGenerator::insertFile(NodePtr &node, const Utils::FilePath &path) const void CMakeGenerator::insertFile(NodePtr &node, const Utils::FilePath &path) const
{ {
QString error;
if (!Utils::FileNameValidatingLineEdit::validateFileName(path.fileName(), false, &error)) {
QString text(path.path() + error);
logIssue(error);
}
if (path.fileName() == "qmldir") { if (path.fileName() == "qmldir") {
readQmlDir(path, node); readQmlDir(path, node);
} else if (path.suffix() == "qml" || path.suffix() == "ui.qml") {
node->files.push_back(path);
} else if (path.suffix() == "cpp") { } else if (path.suffix() == "cpp") {
node->sources.push_back(path); node->sources.push_back(path);
} else if (isQml(path)) {
node->files.push_back(path);
} else if (isResource(path)) { } else if (isResource(path)) {
node->resources.push_back(path); node->resources.push_back(path);
} }
@@ -458,12 +357,12 @@ void CMakeGenerator::insertFile(NodePtr &node, const Utils::FilePath &path) cons
void CMakeGenerator::removeFile(NodePtr &node, const Utils::FilePath &path) const void CMakeGenerator::removeFile(NodePtr &node, const Utils::FilePath &path) const
{ {
if (path.fileName() == "qmldir") { if (path.fileName() == "qmldir") {
node->module = false; node->type = Node::Type::Folder;
node->singletons.clear(); node->singletons.clear();
node->uri = ""; node->uri = "";
node->name = path.parentDir().fileName(); node->name = path.parentDir().fileName();
} else if (path.suffix() == "qml") { } else if (isQml(path)) {
auto iter = std::find(node->files.begin(), node->files.end(), path); auto iter = std::find(node->files.begin(), node->files.end(), path);
if (iter != node->files.end()) if (iter != node->files.end())
node->files.erase(iter); node->files.erase(iter);
@@ -474,33 +373,9 @@ void CMakeGenerator::removeFile(NodePtr &node, const Utils::FilePath &path) cons
} }
} }
bool CMakeGenerator::isRootNode(const NodePtr &node) const
{
return node->name == "Main";
}
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::printModules(const NodePtr &node) const void CMakeGenerator::printModules(const NodePtr &node) const
{ {
if (node->module) if (node->type == Node::Type::Module)
qDebug() << "Module: " << node->name; qDebug() << "Module: " << node->name;
for (const auto &child : node->subdirs) for (const auto &child : node->subdirs)
@@ -516,7 +391,27 @@ void CMakeGenerator::printNodeTree(const NodePtr &generatorNode, size_t indent)
return str; return str;
}; };
QString typeString;
switch (generatorNode->type)
{
case Node::Type::App:
typeString = "Node::Type::App";
break;
case Node::Type::Folder:
typeString = "Node::Type::Folder";
break;
case Node::Type::Module:
typeString = "Node::Type::Module";
break;
case Node::Type::Library:
typeString = "Node::Type::Library";
break;
default:
typeString = "Node::Type::Undefined";
}
qDebug() << addIndent(indent) << "GeneratorNode: " << generatorNode->name; qDebug() << addIndent(indent) << "GeneratorNode: " << generatorNode->name;
qDebug() << addIndent(indent) << "type: " << typeString;
qDebug() << addIndent(indent) << "directory: " << generatorNode->dir; qDebug() << addIndent(indent) << "directory: " << generatorNode->dir;
qDebug() << addIndent(indent) << "files: " << generatorNode->files; qDebug() << addIndent(indent) << "files: " << generatorNode->files;
qDebug() << addIndent(indent) << "singletons: " << generatorNode->singletons; qDebug() << addIndent(indent) << "singletons: " << generatorNode->singletons;
@@ -532,7 +427,7 @@ void CMakeGenerator::parseNodeTree(NodePtr &generatorNode,
{ {
for (const auto *childNode : folderNode->nodes()) { for (const auto *childNode : folderNode->nodes()) {
if (const auto *subFolderNode = childNode->asFolderNode()) { if (const auto *subFolderNode = childNode->asFolderNode()) {
CMakeGenerator::NodePtr childGeneratorNode = std::make_shared<Node>(); NodePtr childGeneratorNode = std::make_shared<Node>();
childGeneratorNode->parent = generatorNode; childGeneratorNode->parent = generatorNode;
childGeneratorNode->dir = subFolderNode->filePath(); childGeneratorNode->dir = subFolderNode->filePath();
childGeneratorNode->name = subFolderNode->displayName(); childGeneratorNode->name = subFolderNode->displayName();
@@ -544,25 +439,55 @@ void CMakeGenerator::parseNodeTree(NodePtr &generatorNode,
} }
} }
if (generatorNode->name == "content") if (m_writer)
generatorNode->module = true; m_writer->transformNode(generatorNode);
if (generatorNode->module) if (generatorNode->type == Node::Type::Module)
m_moduleNames.push_back(generatorNode->name); m_moduleNames.push_back(generatorNode->name);
} }
void CMakeGenerator::parseSourceTree() void CMakeGenerator::parseSourceTree()
{ {
m_srcs.clear(); QTC_ASSERT(m_writer, return);
const QString srcDir = m_root->dir.pathAppended(Constants::DIRNAME_CPP).path();
QDirIterator it(srcDir, QStringList({"*.cpp"}), QDir::Files, QDirIterator::Subdirectories); const Utils::FilePath srcDir = m_root->dir.pathAppended(m_writer->sourceDirName());
QDirIterator it(srcDir.path(), {"*.cpp"}, QDir::Files, QDirIterator::Subdirectories);
NodePtr srcNode = std::make_shared<Node>();
srcNode->parent = m_root;
srcNode->type = Node::Type::App;
srcNode->dir = srcDir;
srcNode->uri = srcDir.baseName();
srcNode->name = srcNode->uri;
while (it.hasNext()) { while (it.hasNext()) {
QString relative = Utils::FilePath::calcRelativePath(it.next(), m_root->dir.path()); auto next = it.next();
m_srcs.push_back(relative); srcNode->sources.push_back(Utils::FilePath::fromString(next));
} }
if (m_srcs.empty()) if (srcNode->sources.empty())
m_srcs.push_back("src/main.cpp"); srcNode->sources.push_back(srcDir.pathAppended("main.cpp"));
if (m_writer)
m_writer->transformNode(srcNode);
m_root->subdirs.push_back(srcNode);
}
void CMakeGenerator::compareWithFileSystem(const NodePtr &node) const
{
std::vector<Utils::FilePath> files;
QDirIterator iter(node->dir.path(), QDir::Files, QDirIterator::Subdirectories);
while (iter.hasNext()) {
auto next = Utils::FilePath::fromString(iter.next());
if (isResource(next) && !findFile(next))
files.push_back(next);
}
const QString text("File %1 is not part of the project");
for (const auto& file : files)
logIssue(text.arg(file.path()));
} }
} // namespace GenerateCmake } // namespace GenerateCmake

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once #pragma once
#include "cmakewriter.h"
#include "utils/filepath.h" #include "utils/filepath.h"
#include <QObject> #include <QObject>
@@ -22,80 +24,55 @@ class CMakeGenerator : public QObject
Q_OBJECT Q_OBJECT
public: public:
static void createMenuAction(QObject *parent);
static void logIssue(const QString &text);
CMakeGenerator(QmlBuildSystem *bs, QObject *parent = nullptr); CMakeGenerator(QmlBuildSystem *bs, QObject *parent = nullptr);
QString projectName() const;
const QmlProject *qmlProject() const;
const QmlBuildSystem *buildSystem() const;
bool findFile(const Utils::FilePath &file) const;
bool isRootNode(const NodePtr &node) const;
bool hasChildModule(const NodePtr &node) const;
void setEnabled(bool enabled); void setEnabled(bool enabled);
void initialize(QmlProject *project); void initialize(QmlProject *project);
void update(const QSet<QString> &added, const QSet<QString> &removed); void update(const QSet<QString> &added, const QSet<QString> &removed);
void updateMenuAction();
private: private:
struct Node bool isQml(const Utils::FilePath &path) const;
{ bool isResource(const Utils::FilePath &path) const;
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>;
using FileGetter = std::function<std::vector<Utils::FilePath>(const NodePtr &)>;
std::vector<Utils::FilePath> files(const NodePtr &node, const FileGetter &getter) const;
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 createCMakeFiles(const NodePtr &node) const;
void createMainCMakeFile(const NodePtr &node) const; void createSourceFiles() 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 makeRelative(const NodePtr &node, const Utils::FilePath &path) const;
QString makeEnvironmentVariable(const QString &key) const;
QString makeSingletonBlock(const NodePtr &node) const;
QString makeSubdirectoriesBlock(const NodePtr &node) const;
QString makeQmlFilesBlock(const NodePtr &node) const;
std::tuple<QString, QString> makeResourcesBlocks(const NodePtr &node) const;
QString readTemplate(const QString &templatePath) const;
void readQmlDir(const Utils::FilePath &filePath, NodePtr &node) const; void readQmlDir(const Utils::FilePath &filePath, NodePtr &node) const;
NodePtr findModuleFor(const NodePtr &node) const; NodePtr findModuleFor(const NodePtr &node) const;
NodePtr findNode(NodePtr &node, const Utils::FilePath &path) const; NodePtr findNode(NodePtr &node, const Utils::FilePath &path) const;
NodePtr findOrCreateNode(NodePtr &node, const Utils::FilePath &path) const; NodePtr findOrCreateNode(NodePtr &node, const Utils::FilePath &path) const;
bool findFile(const NodePtr &node, const Utils::FilePath &file) const;
void insertFile(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; void removeFile(NodePtr &node, const Utils::FilePath &path) const;
bool isRootNode(const NodePtr &node) const;
bool hasChildModule(const NodePtr &node) const;
bool isResource(const Utils::FilePath &path) const;
void printModules(const NodePtr &generatorNode) const; void printModules(const NodePtr &generatorNode) const;
void printNodeTree(const NodePtr &generatorNode, size_t indent = 0) const; void printNodeTree(const NodePtr &generatorNode, size_t indent = 0) const;
void parseNodeTree(NodePtr &generatorNode, const ProjectExplorer::FolderNode *folderNode); void parseNodeTree(NodePtr &generatorNode, const ProjectExplorer::FolderNode *folderNode);
void parseSourceTree(); void parseSourceTree();
void compareWithFileSystem(const NodePtr &node) const;
bool m_enabled = false; bool m_enabled = false;
CMakeWriter::Ptr m_writer = {};
QString m_projectName = {}; QString m_projectName = {};
NodePtr m_root = {}; NodePtr m_root = {};
QStringList m_srcs = {}; QStringList m_moduleNames = {};
std::vector<QString> m_moduleNames = {};
QmlBuildSystem *m_buildSystem = nullptr; QmlBuildSystem *m_buildSystem = nullptr;
}; };

View File

@@ -0,0 +1,259 @@
// 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 "cmakewriter.h"
#include "cmakegenerator.h"
#include "cmakewriterv0.h"
#include "cmakewriterv1.h"
#include "generatecmakelistsconstants.h"
#include "qmlprojectmanager/qmlproject.h"
#include "qmlprojectmanager/buildsystem/qmlbuildsystem.h"
#include "utils/namevalueitem.h"
#include <QFile>
#include <QTextStream>
namespace QmlProjectManager {
namespace GenerateCmake {
const char TEMPLATE_BIG_RESOURCES[] = R"(
qt6_add_resources(%1 %2
BIG_RESOURCES
PREFIX "%3"
VERSION 1.0
FILES %4
))";
CMakeWriter::Ptr CMakeWriter::create(CMakeGenerator *parent)
{
const QmlProject *project = parent->qmlProject();
QTC_ASSERT(project, return {});
const QmlBuildSystem *buildSystem = parent->buildSystem();
QTC_ASSERT(buildSystem, return {});
const QString versionString = buildSystem->versionDesignStudio();
bool ok = false;
if (float version = versionString.toFloat(&ok); ok && version > 4.4)
return std::make_unique<CMakeWriterV1>(parent);
return std::make_unique<CMakeWriterV0>(parent);
}
CMakeWriter::CMakeWriter(CMakeGenerator *parent)
: m_parent(parent)
{}
const CMakeGenerator *CMakeWriter::parent() const
{
return m_parent;
}
bool CMakeWriter::isPlugin(const NodePtr &node) const
{
if (node->type == Node::Type::Module)
return true;
return false;
}
QString CMakeWriter::sourceDirName() const
{
return Constants::DIRNAME_CPP;
}
void CMakeWriter::transformNode(NodePtr &) const
{}
std::vector<Utils::FilePath> CMakeWriter::files(const NodePtr &node, const FileGetter &getter) const
{
std::vector<Utils::FilePath> out = getter(node);
for (const NodePtr &child : node->subdirs) {
if (child->type == Node::Type::Module)
continue;
auto childFiles = files(child, getter);
out.insert(out.end(), childFiles.begin(), childFiles.end());
}
return out;
}
std::vector<Utils::FilePath> CMakeWriter::qmlFiles(const NodePtr &node) const
{
return files(node, [](const NodePtr &n) { return n->files; });
}
std::vector<Utils::FilePath> CMakeWriter::singletons(const NodePtr &node) const
{
return files(node, [](const NodePtr &n) { return n->singletons; });
}
std::vector<Utils::FilePath> CMakeWriter::resources(const NodePtr &node) const
{
return files(node, [](const NodePtr &n) { return n->resources; });
}
std::vector<Utils::FilePath> CMakeWriter::sources(const NodePtr &node) const
{
return files(node, [](const NodePtr &n) { return n->sources; });
}
std::vector<QString> CMakeWriter::plugins(const NodePtr &node) const
{
QTC_ASSERT(parent(), return {});
std::vector<QString> out;
collectPlugins(node, out);
return out;
}
QString CMakeWriter::getEnvironmentVariable(const QString &key) const
{
QTC_ASSERT(parent(), return {});
QString value;
if (m_parent->buildSystem()) {
auto envItems = m_parent->buildSystem()->environment();
auto confEnv = std::find_if(
envItems.begin(), envItems.end(), [key](const Utils::EnvironmentItem &item) {
return item.name == key;
});
if (confEnv != envItems.end())
value = confEnv->value;
}
return value;
}
QString CMakeWriter::makeRelative(const NodePtr &node, const Utils::FilePath &path) const
{
const QString dir = node->dir.toString();
return "\"" + Utils::FilePath::calcRelativePath(path.toString(), dir) + "\"";
}
QString CMakeWriter::makeQmlFilesBlock(const NodePtr &node) const
{
QTC_ASSERT(parent(), return {});
QString qmlFileContent;
for (const Utils::FilePath &path : qmlFiles(node))
qmlFileContent.append(QString("\t\t%1\n").arg(makeRelative(node, path)));
QString str;
if (!qmlFileContent.isEmpty())
str.append(QString("\tQML_FILES\n%1").arg(qmlFileContent));
return str;
}
QString CMakeWriter::makeSingletonBlock(const NodePtr &node) const
{
QString str;
const QString setProperties("set_source_files_properties(%1\n\tPROPERTIES\n\t\t%2 %3\n)\n\n");
for (const Utils::FilePath &path : node->singletons)
str.append(setProperties.arg(path.fileName()).arg("QT_QML_SINGLETON_TYPE").arg("true"));
return str;
}
QString CMakeWriter::makeSubdirectoriesBlock(const NodePtr &node) const
{
QTC_ASSERT(parent(), return {});
QString str;
for (const NodePtr &n : node->subdirs) {
if (n->type == Node::Type::Module || n->type == Node::Type::Library
|| n->type == Node::Type::App || parent()->hasChildModule(n))
str.append(QString("add_subdirectory(%1)\n").arg(n->dir.fileName()));
}
return str;
}
QString CMakeWriter::makeSetEnvironmentFn() const
{
QTC_ASSERT(parent(), return {});
QTC_ASSERT(parent()->buildSystem(), return {});
const QmlBuildSystem *buildSystem = parent()->buildSystem();
const QString configFile = getEnvironmentVariable(Constants::ENV_VARIABLE_CONTROLCONF);
QString out("inline void set_qt_environment() {\n");
for (Utils::EnvironmentItem &envItem : buildSystem->environment()) {
QString key = envItem.name;
QString value = envItem.value;
if (value == configFile)
value.prepend(":/");
out.append(QString("\tqputenv(\"%1\", \"%2\");\n").arg(key).arg(value));
}
out.append("}");
return out;
}
std::tuple<QString, QString> CMakeWriter::makeResourcesBlocks(const NodePtr &node) const
{
QString resourcesOut;
QString bigResourcesOut;
QString resourceFiles;
std::vector<QString> bigResources;
for (const Utils::FilePath &path : resources(node)) {
if (path.fileSize() > 5000000) {
bigResources.push_back(makeRelative(node, path));
continue;
}
resourceFiles.append(QString("\t\t%1\n").arg(makeRelative(node, path)));
}
if (!resourceFiles.isEmpty())
resourcesOut.append(QString("\tRESOURCES\n%1").arg(resourceFiles));
QString templatePostfix;
if (!bigResources.empty()) {
QString resourceContent;
for (const QString &res : bigResources)
resourceContent.append(QString("\n %1").arg(res));
const QString prefixPath = QString(node->uri).replace('.', '/');
const QString prefix = "/qt/qml/" + prefixPath;
const QString resourceName = node->name + "BigResource";
bigResourcesOut = QString::fromUtf8(TEMPLATE_BIG_RESOURCES, -1)
.arg(node->name, resourceName, prefix, resourceContent);
}
return {resourcesOut, bigResourcesOut};
}
QString CMakeWriter::readTemplate(const QString &templatePath) const
{
QFile templatefile(templatePath);
templatefile.open(QIODevice::ReadOnly);
QTextStream stream(&templatefile);
QString content = stream.readAll();
templatefile.close();
return content;
}
void CMakeWriter::writeFile(const Utils::FilePath &path, const QString &content) const
{
QFile fileHandle(path.toString());
if (fileHandle.open(QIODevice::WriteOnly)) {
QTextStream stream(&fileHandle);
stream << content;
} else {
QString text("Failed to write file: %1");
CMakeGenerator::logIssue(text.arg(path.path()));
}
fileHandle.close();
}
void CMakeWriter::collectPlugins(const NodePtr &node, std::vector<QString> &out) const
{
if (isPlugin(node))
out.push_back(node->name);
for (const auto &child : node->subdirs)
collectPlugins(child, out);
}
} // End namespace GenerateCmake.
} // End namespace QmlProjectManager.

View File

@@ -0,0 +1,98 @@
// 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 <QString>
namespace QmlProjectManager {
class QmlProject;
class QmlBuildSystem;
namespace GenerateCmake {
struct Node
{
enum class Type {
App,
Module,
Library,
Folder,
};
std::shared_ptr<Node> parent = nullptr;
Type type = Type::Folder;
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>;
using FileGetter = std::function<std::vector<Utils::FilePath>(const NodePtr &)>;
class CMakeGenerator;
const char DO_NOT_EDIT_FILE[] =
"### This file is automatically generated by Qt Design Studio.\n"
"### Do not change\n\n";
const char TEMPLATE_LINK_LIBRARIES[] =
"target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE\n"
"%3"
")";
class CMakeWriter
{
public:
using Ptr = std::shared_ptr<CMakeWriter>;
static Ptr create(CMakeGenerator *parent);
CMakeWriter(CMakeGenerator *parent);
const CMakeGenerator *parent() const;
virtual bool isPlugin(const NodePtr &node) const;
virtual QString sourceDirName() const;
virtual void transformNode(NodePtr &) const;
virtual void writeRootCMakeFile(const NodePtr &node) const = 0;
virtual void writeModuleCMakeFile(const NodePtr &node, const NodePtr &root) const = 0;
virtual void writeSourceFiles(const NodePtr &node, const NodePtr &root) const = 0;
protected:
std::vector<Utils::FilePath> files(const NodePtr &node, const FileGetter &getter) const;
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;
std::vector<QString> plugins(const NodePtr &node) const;
QString getEnvironmentVariable(const QString &key) const;
QString makeRelative(const NodePtr &node, const Utils::FilePath &path) const;
QString makeQmlFilesBlock(const NodePtr &node) const;
QString makeSingletonBlock(const NodePtr &node) const;
QString makeSubdirectoriesBlock(const NodePtr &node) const;
QString makeSetEnvironmentFn() const;
std::tuple<QString, QString> makeResourcesBlocks(const NodePtr &node) const;
QString readTemplate(const QString &templatePath) const;
void writeFile(const Utils::FilePath &path, const QString &content) const;
private:
void collectPlugins(const NodePtr &node, std::vector<QString> &out) const;
const CMakeGenerator *m_parent = nullptr;
};
} // End namespace GenerateCmake.
} // End namespace QmlProjectManager.

View File

@@ -0,0 +1,178 @@
// 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 "cmakewriterv0.h"
#include "cmakegenerator.h"
#include "generatecmakelistsconstants.h"
namespace QmlProjectManager {
namespace GenerateCmake {
const char TEMPLATE_ADD_QML_MODULE[] = R"(
qt6_add_qml_module(%1
URI "%2"
VERSION 1.0
RESOURCE_PREFIX "/qt/qml"
%3))";
CMakeWriterV0::CMakeWriterV0(CMakeGenerator *parent)
: CMakeWriter(parent)
{}
bool CMakeWriterV0::isPlugin(const NodePtr &node) const
{
if (CMakeWriter::isPlugin(node))
return true;
if (node->type == Node::Type::App)
return !node->files.empty() || !node->singletons.empty() || !node->resources.empty();
return false;
}
void CMakeWriterV0::transformNode(NodePtr &node) const
{
QTC_ASSERT(parent(), return);
if (node->name == "src") {
node->type = Node::Type::Folder;
} else if (node->name == "content") {
node->type = Node::Type::Module;
} else if (node->type == Node::Type::App) {
Utils::FilePath path = node->dir.pathAppended("main.qml");
if (!path.exists()) {
QString text("Expected File %1 not found.");
CMakeGenerator::logIssue(text.arg(path.path()));
return;
}
if (!parent()->findFile(path))
node->files.push_back(path);
}
}
void CMakeWriterV0::writeRootCMakeFile(const NodePtr &node) const
{
QTC_ASSERT(parent(), return);
const Utils::FilePath insightPath = node->dir.pathAppended("insight");
if (!insightPath.exists()) {
const QString insightTemplate = readTemplate(":/templates/insight");
writeFile(insightPath, insightTemplate);
}
const Utils::FilePath componentPath = node->dir.pathAppended("qmlcomponents");
if (!componentPath.exists()) {
const QString compTemplate = readTemplate(":/templates/qmlcomponents");
writeFile(componentPath, compTemplate);
}
const QString appName = parent()->projectName() + "App";
const QString qtcontrolsConfFile = getEnvironmentVariable(Constants::ENV_VARIABLE_CONTROLCONF);
QString fileSection = "";
if (!qtcontrolsConfFile.isEmpty())
fileSection = QString("\tFILES\n\t\t%1").arg(qtcontrolsConfFile);
QStringList srcs;
for (const Utils::FilePath &path : sources(node))
srcs.push_back(makeRelative(node, path));
const QString fileTemplate = readTemplate(":/templates/cmakeroot_v0");
const QString fileContent = fileTemplate.arg(appName, srcs.join(" "), fileSection);
const Utils::FilePath cmakeFile = node->dir.pathAppended("CMakeLists.txt");
writeFile(cmakeFile, fileContent);
}
void CMakeWriterV0::writeModuleCMakeFile(const NodePtr &node, const NodePtr &root) const
{
QTC_ASSERT(parent(), return);
Utils::FilePath writeToFile = node->dir.pathAppended("CMakeLists.txt");
QString content(DO_NOT_EDIT_FILE);
if (node->type == Node::Type::Folder && parent()->hasChildModule(node)) {
content.append(makeSubdirectoriesBlock(node));
writeFile(writeToFile, content);
return;
}
content.append(makeSubdirectoriesBlock(node));
content.append("\n");
content.append(makeSingletonBlock(node));
QString qmlModulesContent;
qmlModulesContent.append(makeQmlFilesBlock(node));
auto [resources, bigResources] = makeResourcesBlocks(node);
qmlModulesContent.append(resources);
if (!qmlModulesContent.isEmpty()) {
const QString addLibraryTemplate("qt_add_library(%1 STATIC)");
const QString addModuleTemplate(TEMPLATE_ADD_QML_MODULE);
content.append(addLibraryTemplate.arg(node->name));
content.append(addModuleTemplate.arg(node->name, node->uri, qmlModulesContent));
content.append("\n\n");
}
content.append(bigResources);
if (node->type == Node::Type::App) {
writeToFile = node->dir.pathAppended("qmlModules");
QString pluginNames;
for (const QString &moduleName : plugins(root))
pluginNames.append("\t" + moduleName + "plugin\n");
if (!pluginNames.isEmpty())
content += QString::fromUtf8(TEMPLATE_LINK_LIBRARIES, -1).arg(pluginNames);
}
writeFile(writeToFile, content);
}
void CMakeWriterV0::writeSourceFiles(const NodePtr &node, const NodePtr &root) const
{
QTC_ASSERT(parent(), return);
const Utils::FilePath srcDir = node->dir;
if (!srcDir.exists()) {
srcDir.createDir();
const Utils::FilePath componentsHeaderPath = srcDir.pathAppended(
"import_qml_components_plugins.h");
const QString componentsHeaderContent = readTemplate(
":/templates/import_qml_components_h");
writeFile(componentsHeaderPath, componentsHeaderContent);
const Utils::FilePath cppFilePath = srcDir.pathAppended("main.cpp");
const QString cppContent = readTemplate(":/templates/main_cpp_v0");
writeFile(cppFilePath, cppContent);
}
QString fileHeader(
"/*\n"
" * This file is automatically generated by Qt Design Studio.\n"
" * Do not change\n"
"*/\n\n");
const Utils::FilePath envHeaderPath = srcDir.pathAppended("app_environment.h");
QString envHeaderContent(fileHeader);
envHeaderContent.append("#include <QGuiApplication>\n\n");
envHeaderContent.append(makeSetEnvironmentFn());
writeFile(envHeaderPath, envHeaderContent);
QString importPluginsContent;
for (const QString &module : plugins(root))
importPluginsContent.append(QString("Q_IMPORT_QML_PLUGIN(%1)\n").arg(module + "Plugin"));
QString importPluginsHeader(fileHeader);
importPluginsHeader.append("#include <QtQml/qqmlextensionplugin.h>\n\n");
importPluginsHeader.append(importPluginsContent);
const Utils::FilePath headerFilePath = srcDir.pathAppended("import_qml_plugins.h");
writeFile(headerFilePath, importPluginsHeader);
}
} // namespace GenerateCmake
} // namespace QmlProjectManager

View File

@@ -0,0 +1,25 @@
// 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 "cmakewriter.h"
namespace QmlProjectManager {
namespace GenerateCmake {
class CMakeWriterV0 final : public CMakeWriter
{
public:
CMakeWriterV0(CMakeGenerator *parent);
bool isPlugin(const NodePtr &node) const override;
void transformNode(NodePtr &node) const override;
void writeRootCMakeFile(const NodePtr &node) const override;
void writeModuleCMakeFile(const NodePtr &node, const NodePtr &root) const override;
void writeSourceFiles(const NodePtr &node, const NodePtr &root) const override;
};
} // namespace GenerateCmake
} // namespace QmlProjectManager

View File

@@ -0,0 +1,179 @@
// 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 "cmakewriterv1.h"
#include "cmakegenerator.h"
#include "generatecmakelistsconstants.h"
#include "qmlprojectmanager/buildsystem/qmlbuildsystem.h"
namespace QmlProjectManager {
namespace GenerateCmake {
const char TEMPLATE_SRC_CMAKELISTS[] = R"(
target_sources(${CMAKE_PROJECT_NAME} PUBLIC
%2)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Quick
Qt${QT_VERSION_MAJOR}::Qml))";
CMakeWriterV1::CMakeWriterV1(CMakeGenerator *parent)
: CMakeWriter(parent)
{}
QString CMakeWriterV1::sourceDirName() const
{
return "App";
}
void CMakeWriterV1::transformNode(NodePtr &node) const
{
QTC_ASSERT(parent(), return);
QString contentDir = parent()->projectName() + "Content";
if (node->name == contentDir)
node->type = Node::Type::Module;
}
void CMakeWriterV1::writeRootCMakeFile(const NodePtr &node) const
{
QTC_ASSERT(parent(), return);
const Utils::FilePath cmakeFolderPath = node->dir.pathAppended("cmake");
if (!cmakeFolderPath.exists())
cmakeFolderPath.createDir();
const Utils::FilePath insightPath = cmakeFolderPath.pathAppended("insight.cmake");
if (!insightPath.exists()) {
const QString insightTemplate = readTemplate(":/templates/insight");
writeFile(insightPath, insightTemplate);
}
const Utils::FilePath componentPath = cmakeFolderPath.pathAppended("qmlcomponents.cmake");
if (!componentPath.exists()) {
const QString compTemplate = readTemplate(":/templates/qmlcomponents");
writeFile(componentPath, compTemplate);
}
const Utils::FilePath file = node->dir.pathAppended("CMakeLists.txt");
const QString appName = parent()->projectName() + "App";
QString fileSection = "";
const QString configFile = getEnvironmentVariable(Constants::ENV_VARIABLE_CONTROLCONF);
if (!configFile.isEmpty())
fileSection = QString("\t\t%1").arg(configFile);
const QString fileTemplate = readTemplate(":/templates/cmakeroot_v1");
const QString fileContent = fileTemplate.arg(appName, fileSection);
writeFile(file, fileContent);
const Utils::FilePath userFile = node->dir.pathAppended("qds.cmake");
QString userFileContent(DO_NOT_EDIT_FILE);
userFileContent.append(makeSubdirectoriesBlock(node));
userFileContent.append("\n");
QString pluginNames;
std::vector<QString> plugs = plugins(node);
for (size_t i = 0; i < plugs.size(); ++i) {
pluginNames.append("\t" + plugs[i] + "plugin");
if (i != plugs.size() - 1)
pluginNames.append("\n");
}
QString linkLibrariesTemplate(
"target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE\n"
"%1)");
userFileContent.append(linkLibrariesTemplate.arg(pluginNames));
writeFile(userFile, userFileContent);
}
void CMakeWriterV1::writeModuleCMakeFile(const NodePtr &node, const NodePtr &) const
{
QTC_ASSERT(parent(), return);
if (node->type == Node::Type::App)
return;
Utils::FilePath writeToFile = node->dir.pathAppended("CMakeLists.txt");
if (node->type == Node::Type::Folder && parent()->hasChildModule(node)) {
QString content(DO_NOT_EDIT_FILE);
content.append(makeSubdirectoriesBlock(node));
writeFile(writeToFile, content);
return;
}
QString prefix;
prefix.append(makeSubdirectoriesBlock(node));
prefix.append(makeSingletonBlock(node));
auto [resources, bigResources] = makeResourcesBlocks(node);
QString moduleContent;
moduleContent.append(makeQmlFilesBlock(node));
moduleContent.append(resources);
QString postfix;
postfix.append(bigResources);
const QString fileTemplate = readTemplate(":/templates/cmakemodule_v1");
const QString fileContent = fileTemplate
.arg(node->name, node->uri, prefix, moduleContent, postfix);
writeFile(writeToFile, fileContent);
}
void CMakeWriterV1::writeSourceFiles(const NodePtr &node, const NodePtr &root) const
{
QTC_ASSERT(parent(), return);
QTC_ASSERT(parent()->buildSystem(), return);
const QmlBuildSystem *buildSystem = parent()->buildSystem();
const Utils::FilePath srcDir = node->dir;
if (!srcDir.exists())
srcDir.createDir();
const Utils::FilePath autogenDir = srcDir.pathAppended("autogen");
if (!autogenDir.exists())
autogenDir.createDir();
const Utils::FilePath mainCppPath = srcDir.pathAppended("main.cpp");
if (!mainCppPath.exists()) {
const QString cppContent = readTemplate(":/templates/main_cpp_v1");
writeFile(mainCppPath, cppContent);
}
const Utils::FilePath cmakePath = srcDir.pathAppended("CMakeLists.txt");
if (!cmakePath.exists()) {
std::vector<Utils::FilePath> sourcePaths = sources(node);
if (sourcePaths.empty())
sourcePaths.push_back(mainCppPath);
QString srcs = {};
for (const Utils::FilePath &src : sourcePaths)
srcs.append("\t" + makeRelative(node, src) + "\n");
QString fileTemplate = QString::fromUtf8(TEMPLATE_SRC_CMAKELISTS, -1).arg(srcs);
writeFile(cmakePath, fileTemplate);
}
const Utils::FilePath headerPath = autogenDir.pathAppended("environment.h");
QString environmentPrefix;
for (const QString &module : plugins(root))
environmentPrefix.append(QString("Q_IMPORT_QML_PLUGIN(%1)\n").arg(module + "Plugin"));
const QString mainFile("const char mainQmlFile[] = \"qrc:/qt/qml/%1\";");
environmentPrefix.append("\n");
environmentPrefix.append(mainFile.arg(buildSystem->mainFile()));
const QString environmentPostfix = makeSetEnvironmentFn();
const QString headerTemplate = readTemplate(":/templates/environment_h");
writeFile(headerPath, headerTemplate.arg(environmentPrefix, environmentPostfix));
}
} // namespace GenerateCmake
} // namespace QmlProjectManager

View File

@@ -0,0 +1,25 @@
// 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 "cmakewriter.h"
namespace QmlProjectManager {
namespace GenerateCmake {
class CMakeWriterV1 final : public CMakeWriter
{
public:
CMakeWriterV1(CMakeGenerator *parent);
QString sourceDirName() const override;
void transformNode(NodePtr &node) const override;
void writeRootCMakeFile(const NodePtr &node) const override;
void writeModuleCMakeFile(const NodePtr &node, const NodePtr &root) const override;
void writeSourceFiles(const NodePtr &node, const NodePtr &root) const override;
};
} // namespace GenerateCmake
} // namespace QmlProjectManager

View File

@@ -8,7 +8,6 @@ qt6_add_qml_module(%1
URI "%2" URI "%2"
VERSION 1.0 VERSION 1.0
RESOURCE_PREFIX "/qt/qml" RESOURCE_PREFIX "/qt/qml"
%4 %4)
)
%5 %5

View File

@@ -0,0 +1,46 @@
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)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
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(${CMAKE_PROJECT_NAME})
qt_add_resources(${CMAKE_PROJECT_NAME} "configuration"
PREFIX "/"
FILES
%2)
include(qds)
if (BUILD_QDS_COMPONENTS)
include(qmlcomponents OPTIONAL)
endif()
if (LINK_INSIGHT)
include(insight OPTIONAL)
endif ()
include(GNUInstallDirs)
install(TARGETS ${CMAKE_PROJECT_NAME}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View File

@@ -0,0 +1,24 @@
/*
* This file is automatically generated by Qt Design Studio.
* Do not change.
*/
#include <QGuiApplication>
#include "qqmlextensionplugin.h"
%1
#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
%2

View File

@@ -17,3 +17,4 @@ Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSimulatorPlugin)
Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSystemPlugin) Q_IMPORT_QML_PLUGIN(QtQuick_Studio_EventSystemPlugin)
#endif #endif

View File

@@ -0,0 +1,19 @@
### This file is automatically generated by Qt Design Studio.
### Do not change
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/qtinsight.conf)
if (QT_VERSION GREATER_EQUAL 6.5.0)
find_package(Qt6 REQUIRED COMPONENTS InsightTracker)
qt_add_resources(${CMAKE_PROJECT_NAME} "configuration"
PREFIX "/"
FILES
qtinsight.conf
)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
Qt6::InsightTracker
)
else()
message(WARNING "You need Qt 6.5.0 or newer to build the application.")
endif()
endif()

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "app_environment.h"
#include "import_qml_plugins.h"
int main(int argc, char *argv[])
{
set_qt_environment();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/qt/qml/Main/main.qml"_qs);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.addImportPath(QCoreApplication::applicationDirPath() + "/qml");
engine.addImportPath(":/");
engine.load(url);
if (engine.rootObjects().isEmpty()) {
return -1;
}
return app.exec();
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "autogen/environment.h"
int main(int argc, char *argv[])
{
set_qt_environment();
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(mainQmlFile);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.addImportPath(QCoreApplication::applicationDirPath() + "/qml");
engine.addImportPath(":/");
engine.load(url);
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}

View File

@@ -0,0 +1,34 @@
### This file is automatically generated by Qt Design Studio.
### Do not change
message("Building designer components.")
set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
include(FetchContent)
FetchContent_Declare(
ds
GIT_TAG qds-4.5
GIT_REPOSITORY https://code.qt.io/qt-labs/qtquickdesigner-components.git
)
FetchContent_GetProperties(ds)
FetchContent_Populate(ds)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
QuickStudioComponentsplugin
QuickStudioEffectsplugin
QuickStudioApplicationplugin
FlowViewplugin
QuickStudioLogicHelperplugin
QuickStudioMultiTextplugin
QuickStudioEventSimulatorplugin
QuickStudioEventSystemplugin
QuickStudioUtilsplugin
)
add_subdirectory(${ds_SOURCE_DIR} ${ds_BINARY_DIR})
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
BULD_QDS_COMPONENTS=true
)

View File

@@ -54,6 +54,10 @@ QtcPlugin {
"cmakegeneratordialog.cpp", "cmakegeneratordialog.h", "cmakegeneratordialog.cpp", "cmakegeneratordialog.h",
"cmakeprojectconverter.cpp", "cmakeprojectconverter.h", "cmakeprojectconverter.cpp", "cmakeprojectconverter.h",
"cmakeprojectconverterdialog.cpp", "cmakeprojectconverterdialog.h", "cmakeprojectconverterdialog.cpp", "cmakeprojectconverterdialog.h",
"cmakegenerator.cpp", "cmakegenerator.h",
"cmakewriter.cpp", "cmakewriter.h",
"cmakewriterv0.cpp", "cmakewriterv0.h",
"cmakewriterv1.cpp", "cmakewriterv1.h"
] ]
} }

View File

@@ -386,6 +386,8 @@ void QmlProjectPlugin::initialize()
mainUifileAction->setEnabled(buildSystem->mainUiFilePath() mainUifileAction->setEnabled(buildSystem->mainUiFilePath()
!= fileNode->filePath()); != fileNode->filePath());
}); });
GenerateCmake::CMakeGenerator::createMenuAction(this);
} }
GenerateCmake::generateMenuEntry(this); GenerateCmake::generateMenuEntry(this);