From 06a6549075eb765221bc62be954981d301828d07 Mon Sep 17 00:00:00 2001 From: Tapani Mattila Date: Wed, 17 Nov 2021 12:18:35 +0200 Subject: [PATCH] CMake generator: Add sanity checks Task-number: QDS-5410 Change-Id: I8f3cd130d7f5bfac3656e8d006661a81a5412abd Reviewed-by: Thomas Hartmann Reviewed-by: Leena Miettinen --- .../qmldesigner/generatecmakelists.cpp | 167 +++++++++++++++--- src/plugins/qmldesigner/generatecmakelists.h | 4 + 2 files changed, 151 insertions(+), 20 deletions(-) diff --git a/src/plugins/qmldesigner/generatecmakelists.cpp b/src/plugins/qmldesigner/generatecmakelists.cpp index 24faa267397..9a52f9f2d58 100644 --- a/src/plugins/qmldesigner/generatecmakelists.cpp +++ b/src/plugins/qmldesigner/generatecmakelists.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -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 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); } diff --git a/src/plugins/qmldesigner/generatecmakelists.h b/src/plugins/qmldesigner/generatecmakelists.h index b9f225e9465..794610a53f8 100644 --- a/src/plugins/qmldesigner/generatecmakelists.h +++ b/src/plugins/qmldesigner/generatecmakelists.h @@ -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);