Update: Simplified problem
With CMake 3.16.8 or less, the following workflow was working, but 3.17+ deletes the Qt's TS files.
The idea is taken from Professional CMake, 7th Edition by Craig Scott
Requirements
cmake 3.17+, Qt 5.9+, ninja
Concept
To update the translation files for Qt, UPDATE_TRANSLATIONS can be enabled. When finished, disable it.
CMake 3.17+ deletes the updated TS files.
Adjust pathes in the script update_translation.sh to automate the workflow.
Steps to reproduce
git clone https://github.com/Macintron/QtTranslationDemo.git # or create files from below in QtTranslationDemo.
mkdir build # NOT in QtTranslationDemo
cd build
cmake -G Ninja -DCMAKE_PREFIX_PATH=[pathToQtDir/5.15.1/clang_64] ../QtTranslationDemo
ninja
./demo
Output should be:
Demo
de.qm: 85 bytes
Language: 'Deutsch'
greeting: ''
en.qm: 85 bytes
Language: 'English'
greeting: ''
now the problem starts:
cmake -DUPDATE_TRANSLATIONS=ON ../QtTranslationDemo
ninja demoTranslations
cmake -DUPDATE_TRANSLATIONS=OFF ../QtTranslationDemo
# new strings ("greeting") should haven been added to the ts files, but TS files are deleted instead!
Why are the TS files get deleted? Is this a bug in CMake 3.17+ or a bug in my workflow?
build info
The build directory must NOT be inside the sources.
Source files
CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(QtTranslationDemo LANGUAGES CXX)
find_package(Qt5
COMPONENTS
LinguistTools Core
REQUIRED
)
option(UPDATE_TRANSLATIONS "Enable rescanning sources to update .ts files" OFF)
set(_tsFiles en.ts de.ts)
if(UPDATE_TRANSLATIONS)
message("** parse sources for new translations")
qt5_create_translation(_qmFiles
main.cpp
${_tsFiles}
)
else()
message("** update qm files")
qt5_add_translation(_qmFiles
${_tsFiles}
)
endif()
add_custom_target(demoTranslations DEPENDS ${_qmFiles})
add_executable(demo
main.cpp
${_qmFiles}
)
target_link_libraries(demo
PUBLIC
Qt5::Core
)
main.cpp
#include <QTranslator>
#include <QDir>
#include <QStringList>
static const char* language = QT_TRANSLATE_NOOP("#default", "Language");
static const char* greeting = QT_TRANSLATE_NOOP("#default", "greeting"); // not in ts files now!
int main(int argc, char *argv[])
{
qDebug("Demo");
QStringList qms = QDir("./").entryList({"*.qm"});
for (const auto& qm: qms) {
qDebug("%s: %lld bytes", qPrintable(qm), QFileInfo("./" + qm).size());
QTranslator appTranslator;
if (appTranslator.load(qm, "./")) {
qDebug(" Language: '%s'", qPrintable(appTranslator.translate("#default", language)));
qDebug(" greeting: '%s'", qPrintable(appTranslator.translate("#default", greeting)));
} else {
qWarning("Failed to load %s", qPrintable(qm));
}
}
return 0;
}
en.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en" sourcelanguage="en">
<context>
<name>#default</name>
<message>
<source>Language</source>
<translation>English</translation>
</message>
</context>
</TS>
de.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de" sourcelanguage="en">
<context>
<name>#default</name>
<message>
<source>Language</source>
<translation>Deutsch</translation>
</message>
</context>
</TS>
update_translation.sh
#!/bin/sh
set -e
thisdir=$(dirname $0)
qt_root=/usr/local/Trolltech/Qt5.15/5.15.1/clang_64
#cmaketool=/usr/local/Cellar/cmake/3.16.8/bin/cmake
cmaketool=cmake
# easier setup
rm -Rf en.qm de.qm CMakeFiles CMakeCache.txt cmake_install.cmake build.ninja .ninja_* > /dev/null 2>&1
git checkout $thisdir/de.ts $thisdir/en.ts # restore files
$cmaketool --version
$cmaketool -G Ninja -DCMAKE_PREFIX_PATH=$qt_root ../QtTranslationDemo
ninja
./demo
rm -f demo
echo "Starting CMake with DUPDATE_TRANSLATIONS..."
$cmaketool -DUPDATE_TRANSLATIONS=ON $thisdir
echo
echo "Updating translation files..."
ninja demoTranslations
echo
echo "Restore CMake configuration..."
$cmaketool -DUPDATE_TRANSLATIONS=OFF $thisdir
ninja
./demo
exit 0
This was a generic CMake problem, caused by the cleandead feature. It should be fixed in CMake 3.19.2.
Thank you all for your contributions to solve the problem.
Related
I am trying to write a simple qml application with language translations using CMake and although the application runs, it never shows translations. I'm on a Linux platform with CMake version 3.22.2 and Qt version 3.15.2 Here is the directory structure:
├── CMakeLists.txt
└── src
├── CMakeLists.txt
├── main.cpp
├── silly.qrc
└── qml
├── silly.qml
└── translations
├── qml_de.ts
├── qml_en.ts
└── qml_fr.ts
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(silly VERSION 1.0.0)
add_subdirectory(src)
src/CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
find_package(Qt5 COMPONENTS Qml Quick LinguistTools REQUIRED)
set(TRANSLATIONS
qml/translations/qml_en.ts
qml/translations/qml_fr.ts
qml/translations/qml_de.ts
)
qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${TRANSLATIONS})
qt5_add_resources(QRC_RESOURCES "silly.qrc")
add_executable(silly main.cpp
"${QRC_RESOURCES}"
"${QM_FILES}"
)
target_compile_features(silly PUBLIC cxx_std_17)
set_target_properties(silly PROPERTIES AUTOMOC ON AUTORCC ON)
target_link_libraries(silly PRIVATE Qt5::Quick Qt5::Qml)
target_include_directories(silly
PUBLIC
$<INSTALL_INTERFACE:.>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
)
install(TARGETS silly)
src/main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.addImportPath("qrc:/");
engine.load(QUrl("qrc:/qml/silly.qml"));
return app.exec();
}
src/silly.qrc
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/qml">
<file alias="silly.qml">qml/silly.qml</file>
</qresource>
</RCC>
src/qml/silly.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
Button {
anchors.fill: parent
spacing: 20
text: qsTr("Hello")
}
}
src/qml/translations/qml_de.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>silly</name>
<message>
<location filename="../silly.qml" line="11"/>
<source>Hello</source>
<translation>Hallo</translation>
</message>
</context>
</TS>
src/qml/translations/qml_en.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>silly</name>
<message>
<location filename="../silly.qml" line="11"/>
<source>Hello</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
src/qml/translations/qml_fr.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="fr_FR">
<context>
<name>silly</name>
<message>
<location filename="../silly.qml" line="11"/>
<source>Hello</source>
<translation>Bonjour</translation>
</message>
</context>
</TS>
It builds and runs with no errors, but when I attempt to test with this:
LANGUAGE=fr src/silly I get a button that says "Hello" instead of a button that says "Bonjour". I've been trying to figure this out for hours which also leads me to the secondary question: how does one generally troubleshoot Qt language translations? This is my first time and I could find no documentation on that.
That's pretty badly documented stuff which works magically with ready-made example using dedicated qmake configuration parameter embed_translations. I advice you to take a look into the original example's build dir where .qm files and a special qrc file qmake_qmake_qm_files.qrc get generated.
You don't need to use QTranslator unless you want to support dynamic language switch. At startup, QML runtime automatically loads a translation file qml_<language_COUNTRY>.qm (qml_xx_XX.qm where xx is ISO639 and XX is optional ISO 3166 code) from the i18n subdirectory of the root QML file, based on the system language, if it finds one.
You need to get your .qm files to qrc:/qml/i18n/ folder because your main qml file is in qrc:/qml/.
With CMake you can do it as follows:
Add a new qrc file, e.g. cmake_qm_files.qrc to your project
<RCC>
<qresource prefix="/qml/i18n">
<file>qml_de.qm</file>
<file>qml_en.qm</file>
<file>qml_fr.qm</file>
</qresource>
</RCC>
Get CMake to copy the qrc file to binary dir where .qm files get created
configure_file(cmake_qm_files.qrc ${CMAKE_BINARY_DIR} COPYONLY)
Get qrc file resource compiled and embedded to your executable
add_executable(silly main.cpp
${QRC_RESOURCES}
${CMAKE_BINARY_DIR}/cmake_qm_files.qrc
)
I typically use QLocale for translation testing as follows:
QGuiApplication app(argc, argv);
QLocale systemLocale = QLocale::system();
QLocale::Language language = systemLocale.language();
QLocale::Country country = systemLocale.country();
qDebug() << "System locale language:" << language << ", country:" << country;
// TEST: change default locale by removing comments below
// language = QLocale::French;
// country = QLocale::France;
language = QLocale::English;
country = QLocale::Australia;
QLocale locale(language, country);
qDebug() << "Changing default locale to language:" << locale.language() << ", country:" << locale.country();
QLocale::setDefault(locale); // TEST: set default locale to something else than system locale
QQmlApplicationEngine engine;
I would like to migrate from 5.9 to 5.15 and I don't get to load svg from QML.
The original version, using qt provided by Ubuntu (5.9) did load svg from QML with
no extra steps. The new build uses a static Qt.
I use:
Ubuntu 18
In the house static build of Qt 5.15
CMake (3.18)
build Qt
./configure \
-confirm-license \
-release \
-static \
-no-pch \
-nomake tests \
-nomake examples \
-nomake tools \
-nomake examples \
-nomake tests \
-skip qtdoc \
-skip wayland \
-skip qtwebengine \
--prefix=/opt/Qt-5.15.0
CMake
cmake_minimum_required(VERSION 3.18)
project(test VERSION 0.0.0 LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt5 5.15
PATHS /opt/Qt-5.15.0
COMPONENTS
Core
Widgets
Qml
Svg
Quick
QuickControls2
Charts
Multimedia
LinguistTools
QmlImportScanner
Xml
REQUIRED QUIET
)
add_executable(test_qt
# c++ code files
main.cpp
# qml code files
qml/Main.qml
# resource files
qml/resources.qrc
)
target_include_directories(test_qt PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(test_qt
PRIVATE
Qt5::Widgets
Qt5::Qml
Qt5::Quick
Qt5::QuickControls2
Qt5::Svg
Qt5::Xml
Qt5::Gui
)
qt_import_qml_plugins(test_qt
INCLUDE
Qt5::QtQuick2Plugin
Qt5::QSvgPlugin
Qt5::QtQuickControls2Plugin
)
main.cpp
#include <QGuiApplication>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQmlContext>
int main(int argc, char *argv[]) {
int status = 0;
try {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl("qrc:/Main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::quit, &QGuiApplication::quit);
if (engine.rootObjects().isEmpty())
return -1;
status = app.exec();
} catch (std::exception &) {
return -1;
}
return status;
}
Main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
id: window
width: 850
height: 850
visible: true
color: "lightgray"
Image{
source: "image.svg"
}
}
I omit the resource file, but it lists qml, png and svg files.
When using PNG image is loaded successfully, but when using SVG the following error appears:
qrc:/Main.qml:13:5: QML Image: Error decoding: qrc:/image.svg: Unsupported image format
The current Qt build contains the following files related to SVG handling:
./lib/libQt5Svg.a
./lib/libQt5Svg.la
./lib/cmake/Qt5Svg
./lib/cmake/Qt5Svg/Qt5SvgConfigVersion.cmake
./lib/cmake/Qt5Svg/Qt5SvgConfig.cmake
./lib/cmake/Qt5Gui/Qt5Gui_QSvgIconPlugin.cmake
./lib/cmake/Qt5Gui/Qt5Gui_QSvgPlugin_Import.cpp
./lib/cmake/Qt5Gui/Qt5Gui_QSvgIconPlugin_Import.cpp
./lib/cmake/Qt5Gui/Qt5Gui_QSvgPlugin.cmake
./lib/pkgconfig/Qt5Svg.pc
./lib/libQt5Svg.prl
./include/QtSvg
./include/QtSvg/QGraphicsSvgItem
./include/QtSvg/5.15.0/QtSvg
./include/QtSvg/QSvgRenderer
./include/QtSvg/QSvgWidget
./include/QtSvg/QSvgGenerator
./include/QtSvg/QtSvg
./include/QtSvg/QtSvgVersion
./include/QtSvg/QtSvgDepends
EDIT
I added the missing Svg library dependency to the find_package command. The issue remains unsolved.
If I link against a shared build of the Qt libraries, this project works correctly.
The solution for me was in addition to including Qt5::Svg in find_package and target_link_libraries, eg:
find_package(Qt5 REQUIRED COMPONENTS Core Quick Qml Xml Charts Gui Svg QuickControls2)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick Qt5::Qml Qt5::Xml Qt5::Charts Qt5::Gui Qt5::Svg Qt5::QuickControls2)
To also include the following when statically linking Qt 5.15:
qt5_import_plugins(${PROJECT_NAME} INCLUDE Qt5::QSvgPlugin)
That resolved errors such as "QML ButtonIcon: Error decoding: qrc:/resources/images/add-white-36dp.svg: Unsupported image format"
You are using cmake, but in a qmake .pro project,using static linking, you
must add
QT += svg
Note that svg plugin depends of widgets:
"QT += svg" is the same that "QT += svg widgets"
In android/IOS is better skip svg images to reduce app size.
/opt/Qt/5.15.1/gcc_64/lib$ ldd libQt5Svg.so
linux-vdso.so.1 (0x00007fff7bff0000)
libQt5Widgets.so.5 => /opt/Qt/5.15.1/gcc_64/lib/./libQt5Widgets.so.5 (0x00007ff0cbf55000)
libQt5Gui.so.5 => /opt/Qt/5.15.1/gcc_64/lib/./libQt5Gui.so.5 (0x00007ff0cb624000)
You can try to add some C++ QTSVG code in your main.cpp ,to test linking
https://doc.qt.io/qt-5/qsvggenerator.html
I have a project that uses a server/client feature. My project layout is:
Project w/ subprojects
-server
--- some files (not important)
-client display
---Display.qml --> Main view
---ViewerForm.ui.qml --> UI Display to create the view
---Viewer.qml --> To run button clicks and javascript
I am able to run the server and client with no problems. Now, I would like to have the Viewer and ViewerForm to be available for different applications. For example:
Standalone client application (just the client display)
Imported into another application which may have several kinds of UI pages in which one is the client display.
How can I set this up so that I have only 1 Viewer project and it can be imported into different applications. Should this be a Qt Plugin? Module? I looked at the Qt documentation and I created a ViewerPlugin project as it's own project with the ViewerForm.ui.qml, Viewer.qml, several component *.ui.qml and *.qml files, and javascript files as shown below. When I build the ViewerPlugin it creates a Viewer folder with files: ViewerPlugin.dll, ViewerPlugin.exp, ViewerPlugin.lib, and qmldir. Then, I copy this folder into my client application and import the QML plugin in my Display.qml like import Viewer 1.0. But I get build errors that it can't find: CListView.qml and the other qml files.
viewerplugin.pro
TEMPLATE = lib
TARGET = ViewerPlugin
QT += qml quick
CONFIG += qt plugin c++11
DESTDIR = ../../imports/Viewer
TARGET = $$qtLibraryTarget($$TARGET)
uri = Viewer
# Input
SOURCES += \
viewerplugin_plugin.cpp \
viewer.cpp
HEADERS += \
viewerplugin_plugin.h \
viewer.h
DISTFILES += qmldir \
Viewer.qml \
ViewerForm.ui.qml \
components/CListView.qml \
components/CListViewForm.ui.qml \
components/CRangeSliderForm.ui.qml \
components/CSliderForm.ui.qml \
components/IconButtonForm.ui.qml \
components/PressAndHoldButton.qml \
components/TextButton.qml
RESOURCES += qml.qrc
!equals(_PRO_FILE_PWD_, $$OUT_PWD) {
copy_qmldir.target = $$OUT_PWD/qmldir
copy_qmldir.depends = $$_PRO_FILE_PWD_/qmldir
copy_qmldir.commands = $(COPY_FILE) "$$replace(copy_qmldir.depends, /, $$QMAKE_DIR_SEP)" "$$replace(copy_qmldir.target, /, $$QMAKE_DIR_SEP)"
QMAKE_EXTRA_TARGETS += copy_qmldir
PRE_TARGETDEPS += $$copy_qmldir.target
}
qmldir.files = qmldir
# Copy the qmldir file to the same folder as the plugin binary
cpqmldir.files = qmldir
cpqmldir.path = $$DESTDIR
COPIES += cpqmldir
qml.qrc
<RCC>
<qresource prefix="/">
<file alias="ViewerForm.ui.qml">ViewerForm.ui.qml</file>
</qresource>
<qresource prefix="/components">
<file alias="IconButtonForm.ui.qml">components/IconButtonForm.ui.qml</file>
<file alias="CRangeSliderForm.ui.qml">components/CRangeSliderForm.ui.qml</file>
<file alias="CSliderForm.ui.qml">components/CSliderForm.ui.qml</file>
<file alias="CListView.qml">components/CListView.qml</file>
<file alias="TextButton.qml">components/TextButton.qml</file>
</qresource>
<qresource prefix="/HTML5">
<file alias="index.html">HTML5/index.html</file>
<file alias="loader.css">HTML5/loader.css</file>
</qresource>
<qresource prefix="/resources">
<file alias="fontawesome-webfont.ttf">resources/fontawesome-webfont.ttf</file>
</qresource>
</RCC>
qmldir
module Viewer
CListView 1.0 CListView.qml
CListViewForm 1.0 CListViewForm.ui.qml
CRangeSliderForm 1.0 CRangeSliderForm.ui.qml
CSliderForm 1.0 CSliderForm.ui.qml
IconButtonForm 1.0 IconButtonForm.ui.qml
PressAndHoldButton 1.0 PressAndHoldButton.qml
TextButton 1.0 TextButton.qml
plugin ViewerPlugin
The components and HTML5 folders exist as described in the .pro file. The viewerplugin_plugin.h/cpp and viewer.h/cpp are the basic files created from the Qt5 wizard for QT Extensions to extend QQmlExtensionPlugin.
Below are the files that try to import the ViewerPlugin:
Client.pro
QT += quick qml serialport core webengine webchannel
CONFIG += c++11 qt
CONFIG += qtquickcompiler
RESOURCES += qml.qrc
!include($${top_srcdir}/common/common.pri) {
error("Couldn't find common.pri file")
}
!include($${top_srcdir}/qmake-target-platform.pri) {
error("Couldn't find qmake-target-platform.pri file")
}
!include($${top_srcdir}/qmake-destination-path.pri) {
error("Couldn't find qmake-destination-path.pri file")
}
SOURCES += \
main.cpp
DESTDIR = $${top_srcdir}/binaries/$$DESTINATION_PATH
OBJECTS_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.obj
MOC_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.moc
RCC_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.qrc
UI_DIR = $${top_srcdir}/build/$$DESTINATION_PATH/.ui
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Refer to the documentation for the
# deprecated API to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
DISTFILES += \
Display.qml
# ViewerPlugin needs to be copied to binaries executable directory
CONFIG += file_copies
COPIES += ViewerPlugin
ViewerPlugin.files = $$files($${Viewer}/*)
ViewerPlugin.path = $${DESTDIR}/Viewer
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = ${top_srcdir}
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
qml.qrc
<RCC>
<qresource prefix="/">
<file alias="Display.qml">Display.qml</file>
</qresource>
</RCC>
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtWebEngine>
#include <QObject>
#include <QMetaObject>
int main(int argc, char *argv[])
{
qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QtWebEngine::initialize();
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/Display.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
Display.qml
import Viewer 1.0
ApplicationWindow {
id: window
visibility: "Maximized"
visible: true
ViewerForm {
id: viewer
}
Looks like all I needed to do was update the qmldir file to add: Viewer 1.0 qrc:/Viewer.qml and it works.
I developed an application along the lines of the SQL Browser example provided with QT in the Demos and Examples section. My development machine is Windows XP (visual studio compiler was used) and the application works well on it. It is able to connect to an external database (MySQL), and I am able browse through tables. I used the QODBC driver for connections. However when I deploy the executable (with all required .dll files) in another computer without QT, it says that I need to provide for the database drivers. I read the documentation and realized that I need to build a PlugIn for QODBC drivers.
First I looked at an example Plugin (Echo Plugin Example) given at http://doc.qt.digia.com/4.6/tools-echoplugin.html. Then I followed the instructions in http://doc.qt.digia.com/4.6/sql-driver.html#qodbc.
cd %QTDIR%\src\plugins\sqldrivers\odbc
qmake odbc.pro
nmake
The above commands built qsqlodbc4.dll. However, I am not successful in developing a Plugin for my application. Here are my steps, and the compilation output:
Created project odbcplugin.pro (see script below) under directory /odbcpluginTest
TEMPLATE = subdirs
SUBDIRS = sqlbrowser \
odbc
CONFIG += release
# install
target.path = $$PWD
sources.path = $$PWD
sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS
INSTALLS += target sources
Created subdirectories: /odbc, /sqlbrowser, /plugins
Inside the directroy /odbcpluginTest /odbc/
(i). Copied odbc.pro and suitably modified the paths and file names (for example, a file originally named as main.cpp has been renamed as mainODBC.cpp to avoid confusing with the file named main.cpp inside /sqlbrowser). See the scripts below:
TEMPLATE = lib
INCLUDEPATH += ../sqlbrowser
SOURCES = mainODBC.cpp
TARGET = qsqlodbc
DESTDIR = ../plugins
CONFIG += release
include(qsql_odbc.pri)
include(qsqldriverbase.pri)
sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS
INSTALLS += target sources
(ii). The file odbcinterface.h that describes the plugin interface is included as a header in odbc.pro. However, it is actually placed inside the directory /sqlbrowser. Hence, the line INCLUDEPATH += ../sqlbrowser is included in the above script.
(iii). Also, copied all related project files (qsql_odbc.pri, qsqldriverbase.pri, qpluginbase.pri, qt_targets.pri). Suitably modified the paths in all project files (there may be mistakes in here).
(iv). The header (qsql_odbc.h) and source (qsql_odbc.cpp) files of qsql_odbc.pri have also been copied.
Inside the directory /odbcpluginTest /sqlbrowser/
(i). Copied sqlbrowser.pro and all related files.
(ii). Created the header file odbcinterface.h that describes the plugin interface (see below) and added it to the HEADERS in sqlbrowser.pro.
#ifndef ODBCINTERFACE_H
#define ODBCINTERFACE_H
#include <QString>
#include <QtSql/qsqldriver.h>
class OdbcInterface
{
public:
virtual ~OdbcInterface() {}
virtual QSqlDriver* create(const QString &) = 0;
virtual QStringList keys() const = 0;
};
QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(OdbcInterface,
"developed similar to com.trolltech.Plugin.EchoInterface/1.0");
QT_END_NAMESPACE
#endif // ODBCINTERFACE_H
iii. Also, modified the browser.h file by adding the lines
#include "odbcinterface.h"
private:
bool loadPlugin();
OdbcInterface *odbcInterface;
Public:
void TestCase1();
iv. Also, modified the browser.cpp file by adding the function definitions:
bool Browser::loadPlugin()
{
QDir pluginsDir(qApp->applicationDirPath());
#if defined(Q_OS_WIN)
pluginsDir.cdUp();
pluginsDir.cdUp();
#endif
pluginsDir.cd("plugins");
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *pluginI = pluginLoader.instance();
if (pluginI) {
odbcInterface = qobject_cast<OdbcInterface *>(pluginI);
if (odbcInterface)
return true;
}
}
return false;
}
void Browser::TestCase1()
{
loadPlugin();
QStringList list;
list = odbcInterface->keys();
QMessageBox msgBox;
if(list.length() >0)
{
msgBox.setText("Test1 success");
}
else
{
msgBox.setText("Test1 failure");
}
msgBox.exec();
}
Testing:
In browser.cpp file, the constructor Browser::Browser(QWidget *parent) was modified by appending a call to void Browser::TestCase1()
Compile Output:
15:09:18: Running build steps for project odbcplugin...
15:09:18: Configuration unchanged, skipping qmake step.
15:09:18: Starting: "C:\QtSDK\QtCreator\bin\jom.exe"
cd sqlbrowser\ && C:\QtSDK\QtCreator\bin\jom.exe -nologo -j 2 -f Makefile
C:\QtSDK\QtCreator\bin\jom.exe -nologo -j 2 -f Makefile.Debug
cd odbc\ && C:\QtSDK\QtCreator\bin\jom.exe -nologo -j 2 -f Makefile
C:\QtSDK\QtCreator\bin\jom.exe -nologo -j 2 -f Makefile.Release
link /LIBPATH:"c:\QtSDK\Desktop\Qt\4.8.1\msvc2010\lib" /NOLOGO /DYNAMICBASE /NXCOMPAT /INCREMENTAL:NO /DLL /MANIFEST /MANIFESTFILE:"release\qsqlodbc.intermediate.manifest" /VERSION:4.81 /OUT:..\plugins\qsqlodbc4.dll #C:\DOCUME~1\SHAINE~1\LOCALS~1\Temp\qsqlodbc4.dll.5076.0.jom
Creating library ..\plugins\qsqlodbc4.lib and object ..\plugins\qsqlodbc4.exp
mainODBC.obj : error LNK2001: unresolved external symbol "public: virtual struct QMetaObject const * __thiscall QODBCDriverPlugin::metaObject(void)const " (?metaObject#QODBCDriverPlugin##UBEPBUQMetaObject##XZ)
mainODBC.obj : error LNK2001: unresolved external symbol "public: virtual void * __thiscall QODBCDriverPlugin::qt_metacast(char const *)" (?qt_metacast#QODBCDriverPlugin##UAEPAXPBD#Z)
mainODBC.obj : error LNK2001: unresolved external symbol "public: virtual int __thiscall QODBCDriverPlugin::qt_metacall(enum QMetaObject::Call,int,void * *)" (?qt_metacall#QODBCDriverPlugin##UAEHW4Call#QMetaObject##HPAPAX#Z)
..\plugins\qsqlodbc4.dll : fatal error LNK1120: 3 unresolved externals
jom 1.0.6 - empower your cores
command failed with exit code 1120
command failed with exit code 2
command failed with exit code 2
15:09:19: The process "C:\QtSDK\QtCreator\bin\jom.exe" exited with code 2.
Error while building project odbcplugin (target: Desktop)
When executing build step 'Make'
After compiling a Qt application and a Qt plugin with the same flags (except -shared added for compiling the .dll) there is an error message at execution:
"The plugin 'Z:/bug_pyqt/plugin.dll' uses incompatible Qt library. Expected build key "Windows mingw release full-config", got "Windows mingw debug full-config""
Why this error message if both the main application and the plugin were compiled with the same flags?
They were compiled with mingw32-g++ on windows XP under cygwin with a hand-made makefile.
Compiling the main application with option "-g" (and plugin still without) makes things "work" and the error message disappear but what is the rationale?
File main.cpp to compile into a.out:
#include <QCoreApplication>
#include <QPluginLoader>
#include <QDebug>
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QPluginLoader loader("plugin.dll");
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);
loader.load();
if(!loader.isLoaded()) {
qDebug() << loader.errorString();
exit(1);
}
(void)loader.instance();
return app.exec();
}
File plugin.h:
#ifndef PLUGIN_H
#define PLUGIN_H
#include <QObject>
class Plugin : public QObject
{
Q_OBJECT
public:
Plugin();
~Plugin();
};
#endif
File plugin.cpp to compile into plugin.dll:
#include "plugin.h"
#include <QtPlugin>
Q_EXPORT_PLUGIN2(Plugin, Plugin)
Plugin::Plugin() { }
Plugin::~Plugin() { }
File Makefile:
MOC=/cygdrive/c/Qt/4.8.4/bin/moc
GCC=/cygdrive/c/MinGW/bin/mingw32-g++.exe
FLAGS=-Ic:/Qt/4.8.4/include -Ic:/Qt/4.8.4/include/QtCore -Lc:/Qt/4.8.4/lib -Lc:/Qt/4.8.4/bin -lQtCore4
LIBFLAGS=-shared
all:
$(MOC) plugin.h > plugin_moc.cpp
$(GCC) -o a.out main.cpp $(FLAGS)
$(GCC) -o plugin.dll $(LIBFLAGS) plugin_moc.cpp plugin.cpp $(FLAGS)
After investigation, here is a half answer
First off, the "build key" that is mentionned in the error message in defined as macro QT_BUILD_KEY in file QtCore/qconfig.h.
Here is a relevant extract of this file:
#if defined(__SYMBIAN32__)
# define QT_BUILD_KEY "Symbian full-config"
#else
# if !defined(QT_NO_DEBUG)
# if (defined(WIN64) || defined(_WIN64) || defined(__WIN64__))
# define QT_BUILD_KEY "Windows x64 mingw debug full-config"
# else
# define QT_BUILD_KEY "Windows mingw debug full-config"
# endif
# else
# if (defined(WIN64) || defined(_WIN64) || defined(__WIN64__))
# define QT_BUILD_KEY "Windows x64 mingw release full-config"
# else
# define QT_BUILD_KEY "Windows mingw release full-config"
# endif
# endif
#endif
From this we understand that we can force the build type of the plugin to be "release" by defining the macro QT_NO_DEBUG.
Adding "-DQT_NO_DEBUG" on the compile command for the plugin solves the issue.
This still does not explain why by default Qt_BUILD_KEY (or QT_NO_DEBUG) is different between the main app and the plugin.