CMake generator: Add sanity checks

Task-number: QDS-5410
Change-Id: I8f3cd130d7f5bfac3656e8d006661a81a5412abd
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
Tapani Mattila
2021-11-17 12:18:35 +02:00
parent e68e2a3159
commit 06a6549075
2 changed files with 151 additions and 20 deletions

View File

@@ -40,6 +40,7 @@
#include <utils/fileutils.h>
#include <QAction>
#include <QMessageBox>
#include <QtConcurrent>
#include <QRegularExpression>
#include <QStringList>
@@ -56,13 +57,26 @@ bool operator==(const GeneratableFile &left, const GeneratableFile &right)
return (left.filePath == right.filePath && left.content == right.content);
}
enum ProjectDirectoryError {
NO_ERROR = 0,
MISSING_CONTENTDIR = 1<<1,
MISSING_IMPORTDIR = 1<<2,
MISSING_CPPDIR = 1<<3,
MISSING_MAINCMAKE = 1<<4,
MISSING_MAINQML = 1<<5,
MISSING_APPMAINQML = 1<<6,
MISSING_QMLMODULES = 1<<7,
MISSING_MAINCPP = 1<<8,
MISSING_MAINCPP_HEADER = 1<<9
};
QVector<GeneratableFile> queuedFiles;
void generateMenuEntry()
{
Core::ActionContainer *buildMenu =
Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT);
auto action = new QAction("Generate CMakeLists.txt files");
auto action = new QAction(QCoreApplication::tr("Generate CMakeLists.txt Files"));
QObject::connect(action, &QAction::triggered, GenerateCmake::onGenerateCmakeLists);
Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateCMakeLists");
buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN);
@@ -76,8 +90,16 @@ void generateMenuEntry()
void onGenerateCmakeLists()
{
queuedFiles.clear();
FilePath rootDir = ProjectExplorer::SessionManager::startupProject()->projectDirectory();
int projectDirErrors = isProjectCorrectlyFormed(rootDir);
if (projectDirErrors != NO_ERROR) {
showProjectDirErrorDialog(projectDirErrors);
if (isErrorFatal(projectDirErrors))
return;
}
queuedFiles.clear();
GenerateCmakeLists::generateCmakes(rootDir);
GenerateEntryPoints::generateMainCpp(rootDir);
GenerateEntryPoints::generateMainQml(rootDir);
@@ -85,6 +107,57 @@ void onGenerateCmakeLists()
writeQueuedFiles();
}
bool isErrorFatal(int error)
{
if (error & MISSING_CONTENTDIR ||
error & MISSING_IMPORTDIR ||
error & MISSING_CPPDIR ||
error & MISSING_APPMAINQML)
return true;
return false;
}
const char DIRNAME_CONTENT[] = "content";
const char DIRNAME_IMPORT[] = "imports";
const char DIRNAME_CPP[] = "src";
const char FILENAME_CMAKELISTS[] = "CMakeLists.txt";
const char FILENAME_APPMAINQML[] = "App.qml";
const char FILENAME_MAINQML[] = "main.qml";
const char FILENAME_MAINCPP[] = "main.cpp";
const char FILENAME_MAINCPP_HEADER[] = "import_qml_plugins.h";
const char FILENAME_MODULES[] = "qmlmodules";
int isProjectCorrectlyFormed(const FilePath &rootDir)
{
int errors = NO_ERROR;
if (!rootDir.pathAppended(DIRNAME_CONTENT).exists())
errors |= MISSING_CONTENTDIR;
if (!rootDir.pathAppended(DIRNAME_CONTENT).pathAppended(FILENAME_APPMAINQML).exists())
errors |= MISSING_APPMAINQML;
if (!rootDir.pathAppended(DIRNAME_IMPORT).exists())
errors |= MISSING_IMPORTDIR;
if (!rootDir.pathAppended(DIRNAME_CPP).exists())
errors |= MISSING_CPPDIR;
if (!rootDir.pathAppended(DIRNAME_CPP).pathAppended(FILENAME_MAINCPP).exists())
errors |= MISSING_MAINCPP;
if (!rootDir.pathAppended(DIRNAME_CPP).pathAppended(FILENAME_MAINCPP_HEADER).exists())
errors |= MISSING_MAINCPP_HEADER;
if (!rootDir.pathAppended(FILENAME_CMAKELISTS).exists())
errors |= MISSING_MAINCMAKE;
if (!rootDir.pathAppended(FILENAME_MODULES).exists())
errors |= MISSING_QMLMODULES;
if (!rootDir.pathAppended(FILENAME_MAINQML).exists())
errors |= MISSING_MAINQML;
return errors;
}
void removeUnconfirmedQueuedFiles(const Utils::FilePaths confirmedFiles)
{
QtConcurrent::blockingFilter(queuedFiles, [confirmedFiles](const GeneratableFile &qf) {
@@ -92,6 +165,65 @@ void removeUnconfirmedQueuedFiles(const Utils::FilePaths confirmedFiles)
});
}
const QString WARNING_MISSING_STRUCTURE_FATAL = QCoreApplication::tr(
"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::tr(
"The project is not properly structured for automatically generating CMake files.\n\nThe following files will be created:\n\n%1");
const QString WARNING_TITLE_FATAL = QCoreApplication::tr(
"Cannot Generate CMake Files");
const QString WARNING_TITLE_NONFATAL = QCoreApplication::tr(
"Problems with Generating CMake Files");
void showProjectDirErrorDialog(int error)
{
QString fatalList;
QString nonFatalList;
if (error & MISSING_CONTENTDIR)
fatalList.append(QString(DIRNAME_CONTENT) + "\n");
if (error & MISSING_APPMAINQML)
fatalList.append(QString(DIRNAME_CONTENT)
+ QDir::separator()
+ QString(FILENAME_APPMAINQML)
+ "\n");
if (error & MISSING_CPPDIR)
fatalList.append(QString(DIRNAME_CPP) + "\n");
if (error & MISSING_IMPORTDIR)
fatalList.append(QString(DIRNAME_IMPORT) + "\n");
if (error & MISSING_MAINCMAKE)
nonFatalList.append(QString(FILENAME_CMAKELISTS) + "\n");
if (error & MISSING_QMLMODULES)
nonFatalList.append(QString(FILENAME_MODULES) + "\n");
if (error & MISSING_MAINQML)
nonFatalList.append(QString(FILENAME_MAINQML) + "\n");
if (error & MISSING_MAINCPP)
nonFatalList.append(QString(DIRNAME_CPP)
+ QDir::separator()
+ QString(FILENAME_MAINCPP)
+ "\n");
if (error & MISSING_MAINCPP_HEADER)
nonFatalList.append(QString(DIRNAME_CPP)
+ QDir::separator()
+ QString(FILENAME_MAINCPP_HEADER)
+ "\n");
bool isFatal = isErrorFatal(error);
if (isFatal) {
QMessageBox::critical(nullptr,
WARNING_TITLE_FATAL,
WARNING_MISSING_STRUCTURE_FATAL.arg(fatalList + nonFatalList));
}
else {
QMessageBox::warning(nullptr,
WARNING_TITLE_NONFATAL,
WARNING_MISSING_STRUCTURE_NONFATAL.arg(nonFatalList));
}
}
bool showConfirmationDialog(const Utils::FilePath &rootDir)
{
Utils::FilePaths files;
@@ -114,6 +246,7 @@ bool queueFile(const FilePath &filePath, const QString &fileContent)
GeneratableFile file;
file.filePath = filePath;
file.content = fileContent;
file.fileExists = filePath.exists();
queuedFiles.append(file);
return true;
@@ -159,9 +292,10 @@ QStringList moduleNames;
const QDir::Filters FILES_ONLY = QDir::Files;
const QDir::Filters DIRS_ONLY = QDir::Dirs|QDir::Readable|QDir::NoDotAndDotDot;
const char CMAKEFILENAME[] = "CMakeLists.txt";
const char QMLDIRFILENAME[] = "qmldir";
const char MODULEFILENAME[] = "qmlmodules";
const char MAIN_CMAKEFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincmakelists.tpl";
const char QMLMODULES_FILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmodules.tpl";
bool generateCmakes(const FilePath &rootDir)
{
@@ -177,9 +311,6 @@ bool generateCmakes(const FilePath &rootDir)
return true;
}
const char MAIN_CMAKEFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincmakelists.tpl";
const char QMLMODULES_FILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmodules.tpl";
void 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.
@@ -194,7 +325,7 @@ void generateMainCmake(const FilePath &rootDir)
modulesAsPlugins.append(" " + moduleName + "plugin\n");
QString moduleFileContent = GenerateCmake::readTemplate(QMLMODULES_FILE_TEMPLATE_PATH).arg(appName).arg(modulesAsPlugins);
GenerateCmake::queueFile(rootDir.pathAppended(MODULEFILENAME), moduleFileContent);
GenerateCmake::queueFile(rootDir.pathAppended(GenerateCmake::FILENAME_MODULES), moduleFileContent);
}
const char DO_NOT_EDIT_FILE_COMMENT[] = "### This file is automatically generated by Qt Design Studio.\n### Do not change\n\n";
@@ -336,14 +467,14 @@ QStringList getDirectoryTreeResources(const FilePath &dir)
void queueCmakeFile(const FilePath &dir, const QString &content)
{
FilePath filePath = dir.pathAppended(CMAKEFILENAME);
FilePath filePath = dir.pathAppended(GenerateCmake::FILENAME_CMAKELISTS);
GenerateCmake::queueFile(filePath, content);
}
bool isFileBlacklisted(const QString &fileName)
{
return (!fileName.compare(QMLDIRFILENAME) ||
!fileName.compare(CMAKEFILENAME));
!fileName.compare(GenerateCmake::FILENAME_CMAKELISTS));
}
}
@@ -358,18 +489,15 @@ bool generateEntryPointFiles(const FilePath &dir)
}
const char MAIN_CPPFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincpp.tpl";
const char MAIN_CPPFILE_DIR[] = "src";
const char MAIN_CPPFILE_NAME[] = "main.cpp";
const char MAIN_CPPFILE_HEADER_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmaincppheader.tpl";
const char MAIN_CPPFILE_HEADER_NAME[] = "import_qml_plugins.h";
const char MAIN_CPPFILE_HEADER_PLUGIN_LINE[] = "Q_IMPORT_QML_PLUGIN(%1)\n";
bool generateMainCpp(const FilePath &dir)
{
FilePath srcDir = dir.pathAppended(MAIN_CPPFILE_DIR);
FilePath srcDir = dir.pathAppended(GenerateCmake::DIRNAME_CPP);
QString cppContent = GenerateCmake::readTemplate(MAIN_CPPFILE_TEMPLATE_PATH);
FilePath cppFilePath = srcDir.pathAppended(MAIN_CPPFILE_NAME);
FilePath cppFilePath = srcDir.pathAppended(GenerateCmake::FILENAME_MAINCPP);
bool cppOk = GenerateCmake::queueFile(cppFilePath, cppContent);
QString modulesAsPlugins;
@@ -379,19 +507,18 @@ bool generateMainCpp(const FilePath &dir)
QString headerContent = GenerateCmake::readTemplate(MAIN_CPPFILE_HEADER_TEMPLATE_PATH)
.arg(modulesAsPlugins);
FilePath headerFilePath = srcDir.pathAppended(MAIN_CPPFILE_HEADER_NAME);
FilePath headerFilePath = srcDir.pathAppended(GenerateCmake::FILENAME_MAINCPP_HEADER);
bool headerOk = GenerateCmake::queueFile(headerFilePath, headerContent);
return cppOk && headerOk;
}
const char MAIN_QMLFILE_PATH[] = ":/boilerplatetemplates/qmlprojectmainqml.tpl";
const char MAIN_QMLFILE_NAME[] = "main.qml";
const char MAIN_QMLFILE_TEMPLATE_PATH[] = ":/boilerplatetemplates/qmlprojectmainqml.tpl";
bool generateMainQml(const FilePath &dir)
{
QString content = GenerateCmake::readTemplate(MAIN_QMLFILE_PATH);
FilePath filePath = dir.pathAppended(MAIN_QMLFILE_NAME);
QString content = GenerateCmake::readTemplate(MAIN_QMLFILE_TEMPLATE_PATH);
FilePath filePath = dir.pathAppended(GenerateCmake::FILENAME_MAINQML);
return GenerateCmake::queueFile(filePath, content);
}

View File

@@ -34,13 +34,17 @@ namespace GenerateCmake {
struct GeneratableFile {
Utils::FilePath filePath;
QString content;
bool fileExists;
};
bool operator==(const GeneratableFile &left, const GeneratableFile &right);
void generateMenuEntry();
void onGenerateCmakeLists();
bool isErrorFatal(int error);
int isProjectCorrectlyFormed(const Utils::FilePath &rootDir);
void removeUnconfirmedQueuedFiles(const Utils::FilePaths confirmedFiles);
void showProjectDirErrorDialog(int error);
bool showConfirmationDialog(const Utils::FilePath &rootDir);
bool queueFile(const Utils::FilePath &filePath, const QString &fileContent);
bool writeFile(const GeneratableFile &file);