Files
qt-creator/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp
Jarek Kobus 866a658b42 Get rid of unneeded includes of RunControl
Removed also some other includes that were marked
with yellow triangles. In some cases includes of
runcontrol.h were substituted with other includes,
like qtcassert.h.

Change-Id: Ica40f68198c7f8f70e6047acb2eddc8ef017e43d
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2022-10-25 09:49:13 +00:00

607 lines
21 KiB
C++

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "generatecmakelists.h"
#include "generatecmakelistsconstants.h"
#include "cmakegeneratordialog.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <qmlprojectmanager/qmlproject.h>
#include <qmlprojectmanager/qmlprojectmanagerconstants.h>
#include <qmlprojectmanager/qmlmainfileaspect.h>
#include <utils/fileutils.h>
#include <QAction>
#include <QMessageBox>
#include <QtConcurrent>
#include <QRegularExpression>
#include <QStringList>
#include <QTextStream>
using namespace Utils;
using namespace QmlProjectManager::GenerateCmake::Constants;
namespace QmlProjectManager {
namespace GenerateCmake {
bool operator==(const GeneratableFile &left, const GeneratableFile &right)
{
return (left.filePath == right.filePath && left.content == right.content);
}
enum ProjectDirectoryError {
NoError = 0,
MissingContentDir = 1<<1,
MissingImportDir = 1<<2,
MissingAssetDir = 1<<3,
MissingAssetImportDir = 1<<4,
MissingCppDir = 1<<5,
MissingMainCMake = 1<<6,
MissingMainQml = 1<<7,
MissingAppMainQml = 1<<8,
MissingQmlModules = 1<<9,
MissingMainCpp = 1<<10,
MissingMainCppHeader = 1<<11,
MissingEnvHeader = 1<<12
};
const QString MENU_ITEM_GENERATE = QCoreApplication::translate("QmlDesigner::GenerateCmake",
"Generate CMake Build Files");
void generateMenuEntry(QObject *parent)
{
Core::ActionContainer *menu =
Core::ActionManager::actionContainer(Core::Constants::M_FILE);
auto action = new QAction(MENU_ITEM_GENERATE, parent);
QObject::connect(action, &QAction::triggered, GenerateCmake::onGenerateCmakeLists);
Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateCMakeLists");
menu->addAction(cmd, Core::Constants::G_FILE_EXPORT);
action->setEnabled(false);
QObject::connect(ProjectExplorer::SessionManager::instance(),
&ProjectExplorer::SessionManager::startupProjectChanged,
[action]() {
auto qmlProject = qobject_cast<QmlProject *>(
ProjectExplorer::SessionManager::startupProject());
action->setEnabled(qmlProject != nullptr);
});
}
void onGenerateCmakeLists()
{
FilePath rootDir = ProjectExplorer::SessionManager::startupProject()->projectDirectory();
int projectDirErrors = isProjectCorrectlyFormed(rootDir);
if (projectDirErrors != NoError) {
showProjectDirErrorDialog(projectDirErrors);
if (isErrorFatal(projectDirErrors))
return;
}
CmakeFileGenerator cmakeGen;
cmakeGen.prepare(rootDir);
FilePaths allFiles;
for (const GeneratableFile &file: cmakeGen.fileQueue().queuedFiles())
allFiles.append(file.filePath);
CmakeGeneratorDialog dialog(rootDir, allFiles);
if (dialog.exec()) {
FilePaths confirmedFiles = dialog.getFilePaths();
cmakeGen.filterFileQueue(confirmedFiles);
cmakeGen.execute();
}
}
bool isErrorFatal(int error)
{
if (error & MissingContentDir ||
error & MissingImportDir ||
error & MissingCppDir ||
error & MissingAppMainQml)
return true;
return false;
}
int isProjectCorrectlyFormed(const FilePath &rootDir)
{
int errors = NoError;
if (!rootDir.pathAppended(DIRNAME_CONTENT).exists())
errors |= MissingContentDir;
if (!rootDir.pathAppended(DIRNAME_CONTENT).pathAppended(FILENAME_APPMAINQML).exists())
errors |= MissingAppMainQml;
if (!rootDir.pathAppended(DIRNAME_IMPORT).exists())
errors |= MissingImportDir;
if (!rootDir.pathAppended(DIRNAME_ASSETIMPORT).exists())
errors |= MissingAssetImportDir;
if (!rootDir.pathAppended(DIRNAME_CPP).exists())
errors |= MissingCppDir;
if (!rootDir.pathAppended(DIRNAME_CPP).pathAppended(FILENAME_MAINCPP).exists())
errors |= MissingMainCpp;
if (!rootDir.pathAppended(DIRNAME_CPP).pathAppended(FILENAME_MAINCPP_HEADER).exists())
errors |= MissingMainCppHeader;
if (!rootDir.pathAppended(DIRNAME_CPP).pathAppended(FILENAME_ENV_HEADER).exists())
errors |= MissingEnvHeader;
if (!rootDir.pathAppended(FILENAME_CMAKELISTS).exists())
errors |= MissingMainCMake;
if (!rootDir.pathAppended(FILENAME_MODULES).exists())
errors |= MissingQmlModules;
if (!rootDir.pathAppended(FILENAME_MAINQML).exists())
errors |= MissingMainQml;
return errors;
}
const QString WARNING_MISSING_STRUCTURE_FATAL = QCoreApplication::translate("QmlDesigner::GenerateCmake",
"The project is not properly structured for automatically generating CMake files.\n\nAborting process.\n\nThe following files or directories are missing:\n\n%1");
//const QString WARNING_MISSING_STRUCTURE_NONFATAL = QCoreApplication::translate("QmlDesigner::GenerateCmake",
// "The project is not properly structured for automatically generating CMake files.\n\nThe following files or directories are missing and may be created:\n\n%1");
const QString WARNING_TITLE_FATAL = QCoreApplication::translate("QmlDesigner::GenerateCmake",
"Cannot Generate CMake Files");
//const QString WARNING_TITLE_NONFATAL = QCoreApplication::translate("QmlDesigner::GenerateCmake",
// "Problems with Generating CMake Files");
void showProjectDirErrorDialog(int error)
{
bool isFatal = isErrorFatal(error);
if (isFatal) {
QString fatalList;
if (error & MissingContentDir)
fatalList.append(QString(DIRNAME_CONTENT) + "\n");
if (error & MissingAppMainQml)
fatalList.append(QString(DIRNAME_CONTENT)
+ QDir::separator()
+ QString(FILENAME_APPMAINQML)
+ "\n");
if (error & MissingCppDir)
fatalList.append(QString(DIRNAME_CPP) + "\n");
if (error & MissingImportDir)
fatalList.append(QString(DIRNAME_IMPORT) + "\n");
QMessageBox::critical(nullptr,
WARNING_TITLE_FATAL,
WARNING_MISSING_STRUCTURE_FATAL.arg(fatalList));
}
}
bool FileQueue::queueFile(const FilePath &filePath, const QString &fileContent)
{
GeneratableFile file;
file.filePath = filePath;
file.content = fileContent;
file.fileExists = filePath.exists();
m_queuedFiles.append(file);
return true;
}
const QVector<GeneratableFile> FileQueue::queuedFiles() const
{
return m_queuedFiles;
}
bool FileQueue::writeQueuedFiles()
{
for (GeneratableFile &file: m_queuedFiles)
if (!writeFile(file))
return false;
return true;
}
bool FileQueue::writeFile(const GeneratableFile &file)
{
QFile fileHandle(file.filePath.toString());
fileHandle.open(QIODevice::WriteOnly);
QTextStream stream(&fileHandle);
stream << file.content;
fileHandle.close();
return true;
}
void FileQueue::filterFiles(const Utils::FilePaths keepFiles)
{
QtConcurrent::blockingFilter(m_queuedFiles, [keepFiles](const GeneratableFile &qf) {
return keepFiles.contains(qf.filePath);
});
}
QString readTemplate(const QString &templatePath)
{
QFile templatefile(templatePath);
templatefile.open(QIODevice::ReadOnly);
QTextStream stream(&templatefile);
QString content = stream.readAll();
templatefile.close();
return content;
}
const QString projectEnvironmentVariable(const QString &key)
{
QString value = {};
auto *target = ProjectExplorer::SessionManager::startupProject()->activeTarget();
if (target && target->buildSystem()) {
auto buildSystem = qobject_cast<QmlProjectManager::QmlBuildSystem *>(target->buildSystem());
if (buildSystem) {
auto envItems = buildSystem->environment();
auto confEnv = std::find_if(envItems.begin(), envItems.end(),
[key](NameValueItem &item){return item.name == key;});
if (confEnv != envItems.end())
value = confEnv->value;
}
}
return value;
}
const QDir::Filters FILES_ONLY = QDir::Files;
const QDir::Filters DIRS_ONLY = QDir::Dirs|QDir::Readable|QDir::NoDotAndDotDot;
const char MAIN_CMAKEFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincmakelists.tpl";
const char QMLMODULES_FILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmodules.tpl";
bool CmakeFileGenerator::prepare(const FilePath &rootDir, bool checkFileBelongs)
{
m_checkFileIsInProject = checkFileBelongs;
FilePath contentDir = rootDir.pathAppended(DIRNAME_CONTENT);
FilePath importDir = rootDir.pathAppended(DIRNAME_IMPORT);
FilePath assetImportDir = rootDir.pathAppended(DIRNAME_ASSETIMPORT);
generateModuleCmake(contentDir);
generateImportCmake(importDir);
generateImportCmake(assetImportDir);
generateMainCmake(rootDir);
generateEntryPointFiles(rootDir);
return true;
}
const FileQueue CmakeFileGenerator::fileQueue() const
{
return m_fileQueue;
}
void CmakeFileGenerator::filterFileQueue(const Utils::FilePaths &keepFiles)
{
m_fileQueue.filterFiles(keepFiles);
}
bool CmakeFileGenerator::execute()
{
return m_fileQueue.writeQueuedFiles();
}
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";
void CmakeFileGenerator::generateMainCmake(const FilePath &rootDir)
{
//TODO startupProject() may be a terrible way to try to get "current project". It's not necessarily the same thing at all.
QString projectName = ProjectExplorer::SessionManager::startupProject()->displayName();
QString appName = projectName + "App";
QString fileSection = "";
const QString qtcontrolsConfFile = GenerateCmake::projectEnvironmentVariable(ENV_VARIABLE_CONTROLCONF);
if (!qtcontrolsConfFile.isEmpty())
fileSection = QString(" FILES\n %1").arg(qtcontrolsConfFile);
QString cmakeFileContent = GenerateCmake::readTemplate(MAIN_CMAKEFILE_TEMPLATE_PATH)
.arg(appName)
.arg(fileSection);
queueCmakeFile(rootDir, cmakeFileContent);
QString subdirIncludes;
subdirIncludes.append(QString(ADD_SUBDIR).arg(DIRNAME_CONTENT));
subdirIncludes.append(QString(ADD_SUBDIR).arg(DIRNAME_IMPORT));
if (rootDir.pathAppended(DIRNAME_ASSETIMPORT).exists())
subdirIncludes.append(QString(ADD_SUBDIR).arg(DIRNAME_ASSETIMPORT));
QString modulesAsPlugins;
for (const QString &moduleName : m_moduleNames)
modulesAsPlugins.append(" " + moduleName + "plugin\n");
QString moduleFileContent = GenerateCmake::readTemplate(QMLMODULES_FILE_TEMPLATE_PATH)
.arg(appName)
.arg(subdirIncludes)
.arg(modulesAsPlugins);
m_fileQueue.queueFile(rootDir.pathAppended(FILENAME_MODULES), moduleFileContent);
}
void CmakeFileGenerator::generateImportCmake(const FilePath &dir, const QString &modulePrefix)
{
if (!dir.exists())
return;
QString fileContent;
fileContent.append(DO_NOT_EDIT_FILE_COMMENT);
FilePaths subDirs = dir.dirEntries(DIRS_ONLY);
for (FilePath &subDir : subDirs) {
if (isDirBlacklisted(subDir))
continue;
if (getDirectoryTreeQmls(subDir).isEmpty() && getDirectoryTreeResources(subDir).isEmpty())
continue;
fileContent.append(QString(ADD_SUBDIR).arg(subDir.fileName()));
QString prefix = modulePrefix.isEmpty() ?
modulePrefix % subDir.fileName() :
QString(modulePrefix + '.') + subDir.fileName();
if (getDirectoryQmls(subDir).isEmpty()) {
generateImportCmake(subDir, prefix);
} else {
generateModuleCmake(subDir, prefix);
}
}
queueCmakeFile(dir, fileContent);
}
const char MODULEFILE_PROPERTY_SINGLETON[] = "QT_QML_SINGLETON_TYPE";
const char MODULEFILE_PROPERTY_SET[] = "set_source_files_properties(%1\n PROPERTIES\n %2 %3\n)\n\n";
const char MODULEFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmodulecmakelists.tpl";
void CmakeFileGenerator::generateModuleCmake(const FilePath &dir, const QString &uri)
{
QString fileTemplate = GenerateCmake::readTemplate(MODULEFILE_TEMPLATE_PATH);
QString singletonContent;
FilePaths qmldirFileList = dir.dirEntries({QStringList(FILENAME_QMLDIR), FILES_ONLY});
if (!qmldirFileList.isEmpty()) {
QStringList singletons = getSingletonsFromQmldirFile(qmldirFileList.first());
for (QString &singleton : singletons) {
singletonContent.append(QString(MODULEFILE_PROPERTY_SET).arg(singleton).arg(MODULEFILE_PROPERTY_SINGLETON).arg("true"));
}
}
QStringList qmlFileList = getDirectoryTreeQmls(dir);
QString qmlFiles;
for (QString &qmlFile : qmlFileList)
qmlFiles.append(QString(" %1\n").arg(qmlFile));
QStringList resourceFileList = getDirectoryTreeResources(dir);
QString resourceFiles;
for (QString &resourceFile : resourceFileList)
resourceFiles.append(QString(" %1\n").arg(resourceFile));
QString moduleContent;
if (!qmlFiles.isEmpty()) {
moduleContent.append(QString(" QML_FILES\n%1").arg(qmlFiles));
}
if (!resourceFiles.isEmpty()) {
moduleContent.append(QString(" RESOURCES\n%1").arg(resourceFiles));
}
QString moduleUri = uri.isEmpty() ?
dir.fileName() :
uri;
QString moduleName = QString(moduleUri).replace('.', '_');
m_moduleNames.append(moduleName);
QString fileContent;
fileContent.append(fileTemplate.arg(singletonContent, moduleName, moduleUri, moduleContent));
queueCmakeFile(dir, fileContent);
}
QStringList CmakeFileGenerator::getSingletonsFromQmldirFile(const FilePath &filePath)
{
QStringList singletons;
QFile f(filePath.toString());
f.open(QIODevice::ReadOnly);
QTextStream stream(&f);
while (!stream.atEnd()) {
QString line = stream.readLine();
if (line.startsWith("singleton", Qt::CaseInsensitive)) {
QStringList tokenizedLine = line.split(QRegularExpression("\\s+"));
QString fileName = tokenizedLine.last();
if (fileName.endsWith(".qml", Qt::CaseInsensitive)) {
singletons.append(fileName);
}
}
}
f.close();
return singletons;
}
QStringList CmakeFileGenerator::getDirectoryQmls(const FilePath &dir)
{
QStringList moduleFiles;
const QStringList qmlFilesOnly(FILENAME_FILTER_QML);
FilePaths allFiles = dir.dirEntries({qmlFilesOnly, FILES_ONLY});
for (FilePath &file : allFiles) {
if (includeFile(file)) {
moduleFiles.append(file.fileName());
}
}
return moduleFiles;
}
QStringList CmakeFileGenerator::getDirectoryResources(const FilePath &dir)
{
QStringList moduleFiles;
FilePaths allFiles = dir.dirEntries(FILES_ONLY);
for (FilePath &file : allFiles) {
if (!file.fileName().endsWith(".qml", Qt::CaseInsensitive) &&
includeFile(file)) {
moduleFiles.append(file.fileName());
}
}
return moduleFiles;
}
QStringList CmakeFileGenerator::getDirectoryTreeQmls(const FilePath &dir)
{
QStringList qmlFileList;
qmlFileList.append(getDirectoryQmls(dir));
FilePaths subDirsList = dir.dirEntries(DIRS_ONLY);
for (FilePath &subDir : subDirsList) {
if (isDirBlacklisted(subDir))
continue;
QStringList subDirQmlFiles = getDirectoryTreeQmls(subDir);
for (QString &qmlFile : subDirQmlFiles) {
qmlFileList.append(subDir.fileName().append('/').append(qmlFile));
}
}
return qmlFileList;
}
QStringList CmakeFileGenerator::getDirectoryTreeResources(const FilePath &dir)
{
QStringList resourceFileList;
resourceFileList.append(getDirectoryResources(dir));
FilePaths subDirsList = dir.dirEntries(DIRS_ONLY);
for (FilePath &subDir : subDirsList) {
if (isDirBlacklisted(subDir))
continue;
QStringList subDirResources = getDirectoryTreeResources(subDir);
for (QString &resource : subDirResources) {
resourceFileList.append(subDir.fileName().append('/').append(resource));
}
}
return resourceFileList;
}
void CmakeFileGenerator::queueCmakeFile(const FilePath &dir, const QString &content)
{
FilePath filePath = dir.pathAppended(FILENAME_CMAKELISTS);
m_fileQueue.queueFile(filePath, content);
}
bool CmakeFileGenerator::isFileBlacklisted(const QString &fileName)
{
return (!fileName.compare(FILENAME_QMLDIR) ||
!fileName.compare(FILENAME_CMAKELISTS));
}
bool CmakeFileGenerator::isDirBlacklisted(const FilePath &dir)
{
return (!dir.fileName().compare(DIRNAME_DESIGNER));
}
bool CmakeFileGenerator::includeFile(const FilePath &filePath)
{
if (m_checkFileIsInProject) {
ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
if (!project->isKnownFile(filePath))
return false;
}
return !isFileBlacklisted(filePath.fileName());
}
bool CmakeFileGenerator::generateEntryPointFiles(const FilePath &dir)
{
const QString qtcontrolsConf = GenerateCmake::projectEnvironmentVariable(ENV_VARIABLE_CONTROLCONF);
if (!qtcontrolsConf.isEmpty())
m_resourceFileLocations.append(qtcontrolsConf);
bool cppOk = generateMainCpp(dir);
bool qmlOk = generateMainQml(dir);
return cppOk && qmlOk;
}
const char MAIN_CPPFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincpp.tpl";
const char MAIN_CPPFILE_HEADER_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincppheader.tpl";
const char MAIN_CPPFILE_HEADER_PLUGIN_LINE[] = "Q_IMPORT_QML_PLUGIN(%1)\n";
const char ENV_HEADER_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectenvheader.tpl";
const char ENV_HEADER_VARIABLE_LINE[] = " qputenv(\"%1\", \"%2\");\n";
bool CmakeFileGenerator::generateMainCpp(const FilePath &dir)
{
FilePath srcDir = dir.pathAppended(DIRNAME_CPP);
QString cppContent = GenerateCmake::readTemplate(MAIN_CPPFILE_TEMPLATE_PATH);
FilePath cppFilePath = srcDir.pathAppended(FILENAME_MAINCPP);
bool cppOk = m_fileQueue.queueFile(cppFilePath, cppContent);
QString modulesAsPlugins;
for (const QString &moduleName : m_moduleNames)
modulesAsPlugins.append(
QString(MAIN_CPPFILE_HEADER_PLUGIN_LINE).arg(moduleName + "Plugin"));
QString headerContent = GenerateCmake::readTemplate(MAIN_CPPFILE_HEADER_TEMPLATE_PATH)
.arg(modulesAsPlugins);
FilePath headerFilePath = srcDir.pathAppended(FILENAME_MAINCPP_HEADER);
bool pluginHeaderOk = m_fileQueue.queueFile(headerFilePath, headerContent);
bool envHeaderOk = true;
QString environment;
auto *target = ProjectExplorer::SessionManager::startupProject()->activeTarget();
if (target && target->buildSystem()) {
auto buildSystem = qobject_cast<QmlProjectManager::QmlBuildSystem *>(target->buildSystem());
if (buildSystem) {
for (EnvironmentItem &envItem : buildSystem->environment()) {
QString key = envItem.name;
QString value = envItem.value;
if (isFileResource(value))
value.prepend(":/");
environment.append(QString(ENV_HEADER_VARIABLE_LINE).arg(key).arg(value));
}
QString envHeaderContent = GenerateCmake::readTemplate(ENV_HEADER_TEMPLATE_PATH)
.arg(environment);
FilePath envHeaderPath = srcDir.pathAppended(FILENAME_ENV_HEADER);
envHeaderOk = m_fileQueue.queueFile(envHeaderPath, envHeaderContent);
}
}
return cppOk && pluginHeaderOk && envHeaderOk;
}
const char MAIN_QMLFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmainqml.tpl";
bool CmakeFileGenerator::generateMainQml(const FilePath &dir)
{
QString content = GenerateCmake::readTemplate(MAIN_QMLFILE_TEMPLATE_PATH);
FilePath filePath = dir.pathAppended(FILENAME_MAINQML);
return m_fileQueue.queueFile(filePath, content);
}
bool CmakeFileGenerator::isFileResource(const QString &relativeFilePath)
{
if (m_resourceFileLocations.contains(relativeFilePath))
return true;
return false;
}
} //GenerateCmake
} //QmlProjectManager