/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "plugingenerator.h" #include "pluginoptions.h" #include #include #include #include #include #include #include #include #include #include static QString headerGuard(const QString &header) { return header.toUpper().replace(QRegularExpression("[^A-Z0-9]+"), QString("_")); } namespace QmakeProjectManager { namespace Internal { struct ProjectContents { QString tmpl; QString library; QString headers; QString sources; }; // Create a binary icon file static inline Core::GeneratedFile generateIconFile(const Utils::FilePath &source, const QString &target, QString *errorMessage) { // Read out source Utils::FileReader reader; if (!reader.fetch(source, errorMessage)) return Core::GeneratedFile(); Core::GeneratedFile rc(target); rc.setBinaryContents(reader.data()); rc.setBinary(true); return rc; } static QString qt4PluginExport(const QString &pluginName, const QString &pluginClassName) { return QLatin1String("#if QT_VERSION < 0x050000\nQ_EXPORT_PLUGIN2(") + pluginName + QLatin1String(", ") + pluginClassName + QLatin1String(")\n#endif // QT_VERSION < 0x050000"); } static QString qt5PluginMetaData(const QString &interfaceName) { return QLatin1String("#if QT_VERSION >= 0x050000\n Q_PLUGIN_METADATA(IID \"org.qt-project.Qt.") + interfaceName + QLatin1String("\")\n#endif // QT_VERSION >= 0x050000"); } QList PluginGenerator::generatePlugin(const GenerationParameters& p, const PluginOptions &options, QString *errorMessage) { const QChar slash = QLatin1Char('/'); const QChar blank = QLatin1Char(' '); QList rc; QString baseDir = p.path; baseDir += slash; baseDir += p.fileName; const QString slashLessBaseDir = baseDir; baseDir += slash; QSet widgetLibraries; QSet widgetProjects; QMap widgetProjectContents; QString pluginIncludes; QString pluginAdditions; QString pluginHeaders; QString pluginSources; QSet pluginIcons; SubstitutionMap sm; // First create the widget wrappers (plugins) and - if requested - skeletons // for the widgets. const int widgetCount = options.widgetOptions.size(); for (int i = 0; i < widgetCount; i++) { const PluginOptions::WidgetOptions &wo = options.widgetOptions.at(i); sm.clear(); sm.insert(QLatin1String("SINGLE_INCLUDE_GUARD"), headerGuard(wo.pluginHeaderFile)); sm.insert(QLatin1String("PLUGIN_CLASS"), wo.pluginClassName); sm.insert(QLatin1String("SINGLE_PLUGIN_METADATA"), options.widgetOptions.count() == 1 ? qt5PluginMetaData(QLatin1String("QDesignerCustomWidgetInterface")) : QString()); const QString pluginHeaderContents = processTemplate(p.templatePath + QLatin1String("/tpl_single.h"), sm, errorMessage); if (pluginHeaderContents.isEmpty()) return QList(); Core::GeneratedFile pluginHeader(baseDir + wo.pluginHeaderFile); pluginHeader.setContents(CppEditor::AbstractEditorSupport::licenseTemplate(wo.pluginHeaderFile, wo.pluginClassName) + pluginHeaderContents); rc.push_back(pluginHeader); sm.remove(QLatin1String("SINGLE_INCLUDE_GUARD")); sm.insert(QLatin1String("PLUGIN_HEADER"), wo.pluginHeaderFile); sm.insert(QLatin1String("WIDGET_CLASS"), wo.widgetClassName); sm.insert(QLatin1String("WIDGET_HEADER"), wo.widgetHeaderFile); sm.insert(QLatin1String("WIDGET_GROUP"), wo.group); QString iconResource; if (!wo.iconFile.isEmpty()) { iconResource = QLatin1String("QLatin1String(\":/"); iconResource += Utils::FilePath::fromString(wo.iconFile).fileName(); iconResource += QLatin1String("\")"); } sm.insert(QLatin1String("WIDGET_ICON"),iconResource); sm.insert(QLatin1String("WIDGET_TOOLTIP"), cStringQuote(wo.toolTip)); sm.insert(QLatin1String("WIDGET_WHATSTHIS"), cStringQuote(wo.whatsThis)); sm.insert(QLatin1String("WIDGET_ISCONTAINER"), wo.isContainer ? QLatin1String("true") : QLatin1String("false")); sm.insert(QLatin1String("WIDGET_DOMXML"), cStringQuote(wo.domXml)); sm.insert(QLatin1String("SINGLE_PLUGIN_EXPORT"), options.widgetOptions.count() == 1 ? qt4PluginExport(options.pluginName, wo.pluginClassName) : QString()); const QString pluginSourceContents = processTemplate(p.templatePath + QLatin1String("/tpl_single.cpp"), sm, errorMessage); if (pluginSourceContents.isEmpty()) return QList(); Core::GeneratedFile pluginSource(baseDir + wo.pluginSourceFile); pluginSource.setContents(CppEditor::AbstractEditorSupport::licenseTemplate(wo.pluginSourceFile, wo.pluginClassName) + pluginSourceContents); if (i == 0 && widgetCount == 1) // Open first widget unless collection pluginSource.setAttributes(Core::GeneratedFile::OpenEditorAttribute); rc.push_back(pluginSource); if (wo.sourceType == PluginOptions::WidgetOptions::LinkLibrary) widgetLibraries.insert(QLatin1String("-l") + wo.widgetLibrary); else widgetProjects.insert(QLatin1String("include(") + wo.widgetProjectFile + QLatin1Char(')')); pluginIncludes += QLatin1String("#include \"") + wo.pluginHeaderFile + QLatin1String("\"\n"); pluginAdditions += QLatin1String(" m_widgets.append(new ") + wo.pluginClassName + QLatin1String("(this));\n"); pluginHeaders += QLatin1Char(' ') + wo.pluginHeaderFile; pluginSources += QLatin1Char(' ') + wo.pluginSourceFile; if (!wo.iconFile.isEmpty()) pluginIcons.insert(wo.iconFile); if (wo.createSkeleton) { ProjectContents &pc = widgetProjectContents[wo.widgetProjectFile]; if (pc.headers.isEmpty()) { if (wo.sourceType == PluginOptions::WidgetOptions::LinkLibrary) { pc.library = wo.widgetLibrary; pc.tmpl = p.templatePath + QLatin1String("/tpl_widget_lib.pro"); } else { pc.tmpl = p.templatePath + QLatin1String("/tpl_widget_include.pri"); } widgetProjectContents.insert(wo.widgetProjectFile, pc); } else { if (pc.library != wo.widgetLibrary) { *errorMessage = tr("Creating multiple widget libraries (%1, %2) in one project (%3) is not supported.") .arg(pc.library, wo.widgetLibrary, wo.widgetProjectFile); return QList(); } } pc.headers += blank + wo.widgetHeaderFile; pc.sources += blank + wo.widgetSourceFile; sm.clear(); sm.insert(QLatin1String("WIDGET_INCLUDE_GUARD"), headerGuard(wo.widgetHeaderFile)); sm.insert(QLatin1String("WIDGET_BASE_CLASS"), wo.widgetBaseClassName); sm.insert(QLatin1String("WIDGET_CLASS"), wo.widgetClassName); const QString widgetHeaderContents = processTemplate(p.templatePath + QLatin1String("/tpl_widget.h"), sm, errorMessage); if (widgetHeaderContents.isEmpty()) return QList(); Core::GeneratedFile widgetHeader(baseDir + wo.widgetHeaderFile); widgetHeader.setContents(CppEditor::AbstractEditorSupport::licenseTemplate(wo.widgetHeaderFile, wo.widgetClassName) + widgetHeaderContents); rc.push_back(widgetHeader); sm.remove(QLatin1String("WIDGET_INCLUDE_GUARD")); sm.insert(QLatin1String("WIDGET_HEADER"), wo.widgetHeaderFile); const QString widgetSourceContents = processTemplate(p.templatePath + QLatin1String("/tpl_widget.cpp"), sm, errorMessage); if (widgetSourceContents.isEmpty()) return QList(); Core::GeneratedFile widgetSource(baseDir + wo.widgetSourceFile); widgetSource.setContents(CppEditor::AbstractEditorSupport::licenseTemplate(wo.widgetSourceFile, wo.widgetClassName) + widgetSourceContents); rc.push_back(widgetSource); } } // Then create the project files for the widget skeletons. // These might create widgetLibraries or be included into the plugin's project. QMap::const_iterator it = widgetProjectContents.constBegin(); const QMap::const_iterator end = widgetProjectContents.constEnd(); for (; it != end; ++it) { const ProjectContents &pc = it.value(); sm.clear(); sm.insert(QLatin1String("WIDGET_HEADERS"), pc.headers); sm.insert(QLatin1String("WIDGET_SOURCES"), pc.sources); if (!pc.library.isEmpty()) sm.insert(QLatin1String("WIDGET_LIBRARY"), pc.library); const QString widgetPriContents = processTemplate(pc.tmpl, sm, errorMessage); if (widgetPriContents.isEmpty()) return QList(); Core::GeneratedFile widgetPri(baseDir + it.key()); widgetPri.setContents(widgetPriContents); rc.push_back(widgetPri); } // Create the sources for the collection if necessary. if (widgetCount > 1) { sm.clear(); sm.insert(QLatin1String("COLLECTION_INCLUDE_GUARD"), headerGuard(options.collectionHeaderFile)); sm.insert(QLatin1String("COLLECTION_PLUGIN_CLASS"), options.collectionClassName); sm.insert(QLatin1String("COLLECTION_PLUGIN_METADATA"), qt5PluginMetaData(QLatin1String("QDesignerCustomWidgetCollectionInterface"))); const QString collectionHeaderContents = processTemplate(p.templatePath + QLatin1String("/tpl_collection.h"), sm, errorMessage); if (collectionHeaderContents.isEmpty()) return QList(); Core::GeneratedFile collectionHeader(baseDir + options.collectionHeaderFile); collectionHeader.setContents(CppEditor::AbstractEditorSupport::licenseTemplate(options.collectionHeaderFile, options.collectionClassName) + collectionHeaderContents); rc.push_back(collectionHeader); sm.remove(QLatin1String("COLLECTION_INCLUDE_GUARD")); sm.insert(QLatin1String("PLUGIN_INCLUDES"), pluginIncludes + QLatin1String("#include \"") + options.collectionHeaderFile + QLatin1String("\"")); sm.insert(QLatin1String("PLUGIN_ADDITIONS"), pluginAdditions); sm.insert(QLatin1String("COLLECTION_PLUGIN_EXPORT"), qt4PluginExport(options.pluginName, options.collectionClassName)); const QString collectionSourceFileContents = processTemplate(p.templatePath + QLatin1String("/tpl_collection.cpp"), sm, errorMessage); if (collectionSourceFileContents.isEmpty()) return QList(); Core::GeneratedFile collectionSource(baseDir + options.collectionSourceFile); collectionSource.setContents(CppEditor::AbstractEditorSupport::licenseTemplate(options.collectionSourceFile, options.collectionClassName) + collectionSourceFileContents); collectionSource.setAttributes(Core::GeneratedFile::OpenEditorAttribute); rc.push_back(collectionSource); pluginHeaders += blank + options.collectionHeaderFile; pluginSources += blank + options.collectionSourceFile; } // Copy icons that are not in the plugin source base directory yet (that is, // probably all), add them to the resource file QString iconFiles; foreach (QString icon, pluginIcons) { const QFileInfo qfi(icon); if (qfi.dir() != slashLessBaseDir) { const QString newIcon = baseDir + qfi.fileName(); const Core::GeneratedFile iconFile = generateIconFile(Utils::FilePath::fromFileInfo(qfi), newIcon, errorMessage); if (iconFile.path().isEmpty()) return QList(); rc.push_back(iconFile); icon = qfi.fileName(); } iconFiles += QLatin1String(" ") + icon + QLatin1String("\n"); } // Create the resource file with the icons. sm.clear(); sm.insert(QLatin1String("ICON_FILES"), iconFiles); const QString resourceFileContents = processTemplate(p.templatePath + QLatin1String("/tpl_resources.qrc"), sm, errorMessage); if (resourceFileContents.isEmpty()) return QList(); Core::GeneratedFile resourceFile(baseDir + options.resourceFile); resourceFile.setContents(resourceFileContents); rc.push_back(resourceFile); // Finally create the project for the plugin itself. sm.clear(); sm.insert(QLatin1String("PLUGIN_NAME"), options.pluginName); sm.insert(QLatin1String("PLUGIN_HEADERS"), pluginHeaders); sm.insert(QLatin1String("PLUGIN_SOURCES"), pluginSources); sm.insert(QLatin1String("PLUGIN_RESOURCES"), options.resourceFile); sm.insert(QLatin1String("WIDGET_LIBS"), QStringList(Utils::toList(widgetLibraries)).join(blank)); sm.insert(QLatin1String("INCLUSIONS"), QStringList(Utils::toList(widgetProjects)).join(QLatin1Char('\n'))); const QString proFileContents = processTemplate(p.templatePath + QLatin1String("/tpl_plugin.pro"), sm, errorMessage); if (proFileContents.isEmpty()) return QList(); Core::GeneratedFile proFile(baseDir + p.fileName + QLatin1String(".pro")); proFile.setContents(proFileContents); proFile.setAttributes(Core::GeneratedFile::OpenProjectAttribute); rc.push_back(proFile); return rc; } QString PluginGenerator::processTemplate(const QString &tmpl, const SubstitutionMap &substMap, QString *errorMessage) { Utils::FileReader reader; if (!reader.fetch(Utils::FilePath::fromString(tmpl), errorMessage)) return QString(); QString cont = QString::fromUtf8(reader.data()); // Expander needed to handle extra variable "Cpp:PragmaOnce" Utils::MacroExpander *expander = Utils::globalMacroExpander(); QString errMsg; cont = Utils::TemplateEngine::processText(expander, cont, &errMsg); if (!errMsg.isEmpty()) { qWarning("Error processing custom plugin file: %s\nFile:\n%s", qPrintable(errMsg), qPrintable(cont)); errorMessage = &errMsg; return QString(); } const QChar atChar = QLatin1Char('@'); int offset = 0; for (;;) { const int start = cont.indexOf(atChar, offset); if (start < 0) break; const int end = cont.indexOf(atChar, start + 1); Q_ASSERT(end); const QString keyword = cont.mid(start + 1, end - start - 1); const QString replacement = substMap.value(keyword); cont.replace(start, end - start + 1, replacement); offset = start + replacement.length(); } return cont; } QString PluginGenerator::cStringQuote(QString s) { s.replace(QLatin1Char('\\'), QLatin1String("\\\\")); s.replace(QLatin1Char('"'), QLatin1String("\\\"")); s.replace(QLatin1Char('\t'), QLatin1String("\\t")); s.replace(QLatin1Char('\n'), QLatin1String("\\n")); return s; } } }