Issue
I've created a dummy Qt subdirs project just to test things out, and managed to get everything working as I wanted using qmake. Now I'm trying to convert the subdirs project to CMake using a separate (working) CMake subdirs style (non-Qt) project of mine and I'm running into trouble that I believe revolves around issues not generating the moc files during the build. When I attempt to build a shared library it fails linking with the error.
C:/Users/cance/dev/Qt/Tools/mingw1120_64/bin/../lib/gcc/x86_64-w64-mingw32/11.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: common-lib/CMakeFiles/common.dir/src/commonlib.cpp.obj:commonlib.cpp:(.rdata$.refptr._ZTV9CommonLib[.refptr._ZTV9CommonLib]+0x0): undefined reference to `vtable for CommonLib'
The qmake subdirs project had 5 nested projects, but for the purpose of figuring this out I've included just the one (common-lib) and the directory structure is as follows:
project/
├── CMakeLists.txt
├── cmake-common/
│ ├── cmake-lib-macros.cmake
│ └── CMakeLists.txt
└── common-lib/
├── CMakeLists.txt
├── common-config.cmake.in
├── include/
│ ├── commonlib.h
│ ├── common-lib_global.h
│ └── logger.h
└── src/
├── commonlib.cpp
└── logger.cpp
I'm using cmake-common
to include reusable macros to avoid the need to maximize my ability to reuse the CMake functionality as much as possible. The important section of the root CMakekLists.txt is as follows:
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_VERBOSE_MAKEFILE OFF)
set(CMAKE_COLOR_MAKEFILE ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Test)
find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS Widgets)
add_subdirectory(cmake-common)
add_subdirectory(common-lib)
cmake-lib-macros.cmake provides a function (macro?) through which to generate a source list for a library and add any required dependent libraries
function(configure_lib link_libs)
file(GLOB_RECURSE src_files CONFIGURE_DEPENDS src/*.cpp)
add_library(${component} SHARED ${src_files})
add_library(${project}::${component} ALIAS ${component})
target_include_directories(${component} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>)
set_target_properties(${component} PROPERTIES VERSION ${XTRACTCADQT_VERSION}
SOVERSION ${XTRACTCADQT_VERSION_MINOR})
target_link_libraries(${component} PUBLIC ${link_libs})
endfunction()
And finally the common-lib/CMakeLists.txt just simply takes advantage of the above function to do all of the heavy lifting
set(component common)
configure_lib("Qt::Core")
The problematic commonlib.cpp is an exceedingly barebones class, with just enough functionality to prove signal/slot capability is present.
// commonlib.h
#ifndef COMMONLIB_H
#define COMMONLIB_H
#include <QObject>
#include <QString>
#include "logger.h"
DEFINE_LOGGING_CATEGORY(mycat)
class CommonLib: public QObject {
Q_OBJECT // HERE
public:
CommonLib();
virtual ~CommonLib() = default;
QString giveMeSomething();
signals: // HERE
void somethingGiven(const QString &something); // HERE
public slots: // HERE
void somethingChanged(const QString &newThing);
private:
QString something;
};
#endif // COMMONLIB_H
// commonlib.cpp
#include "commonlib.h"
#include "logger.h"
#include <iostream>
INITIALIZE_LOGGING_CATEGORY(mycat, "abc.123")
CommonLib::CommonLib(): something("something")
{
}
QString CommonLib::giveMeSomething() {
qCDebug(mycat) << "DEBUG";
qCInfo(mycat) << "INFO";
qCWarning(mycat) << "WARNING";
qCCritical(mycat) << "CRITICAL";
emit somethingGiven(something); // HERE
return something;
}
void CommonLib::somethingChanged(const QString &newThing) {
something = newThing;
}
If I comment out the lines with the // HERE
comment, then it compiles and links properly. If even just the Q_OBJECT
line is present, then linker error is produced. So I'm fairly confident that the problem is as mentioned caused by not the Qt mocs not being generated, but I'm not sure what I'm missing to generate them. From what I can tell having the line set(CMAKE_AUTOMOC ON)
should be enough, but evidently not in my case...
For reference, the full compile output is as follows
[0/2 ?/sec] Re-checking globbed directories...
[1/5 9.4/sec] Automatic MOC and UIC for target common
[2/5 13.0/sec] Building CXX object common-lib/CMakeFiles/common.dir/common_autogen/mocs_compilation.cpp.obj
[3/5 2.4/sec] Building CXX object common-lib/CMakeFiles/common.dir/src/logger.cpp.obj
[4/5 3.1/sec] Building CXX object common-lib/CMakeFiles/common.dir/src/commonlib.cpp.obj
[5/5 3.6/sec] Linking CXX shared library common.dll
FAILED: common.dll common-lib/libcommon.dll.a
cmd.exe /C "cd . && C:\Users\cance\dev\Qt\Tools\mingw1120_64\bin\g++.exe -g -shared -o common.dll -Wl,--out-implib,common-lib\libcommon.dll.a -Wl,--major-image-version,0,--minor-image-version,0 common-lib/CMakeFiles/common.dir/common_autogen/mocs_compilation.cpp.obj common-lib/CMakeFiles/common.dir/src/commonlib.cpp.obj common-lib/CMakeFiles/common.dir/src/logger.cpp.obj C:/Users/cance/dev/Qt/6.5.1/mingw_64/lib/libQt6Core.a -lmpr -luserenv -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 && cd ."
C:/Users/cance/dev/Qt/Tools/mingw1120_64/bin/../lib/gcc/x86_64-w64-mingw32/11.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: common-lib/CMakeFiles/common.dir/src/commonlib.cpp.obj:commonlib.cpp:(.rdata$.refptr._ZTV9CommonLib[.refptr._ZTV9CommonLib]+0x0): undefined reference to `vtable for CommonLib'
collect2.exe: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
19:33:33: The process "C:\Users\cance\dev\Qt\Tools\CMake_64\bin\cmake.exe" exited with code 1.
Error while building/deploying project xTrackCadQt (kit: Desktop Qt 6.5.1 MinGW 64-bit)
When executing step "Build"
And I'm using Qt 6.5.1.
Solution
Finally figured it out. To mirror what I had in the other cmake project I organized the code such that all source was placed into a src
subdirectory and all headers in an include
subdirectory. With how the build worked this was fine for "normal" compilation (referencing the include directory), however insufficient for moc. The header files need to be included in the source list, meaning that the solution is to add the header files into the GLOB. While this shouldn't have any impact to compilation, it does mean that now moc can see them and thus generate the relevant code. For anyone interested the relevant update is in cmake-lib-macros.cmake
, specifically the second line.
function(configure_lib link_libs)
file(GLOB_RECURSE src_files CONFIGURE_DEPENDS include/*.h src/*.cpp)
add_library(${component} SHARED ${src_files})
add_library(${project}::${component} ALIAS ${component})
target_include_directories(${component} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>)
set_target_properties(${component} PROPERTIES VERSION ${XTRACTCADQT_VERSION}
SOVERSION ${XTRACTCADQT_VERSION_MINOR})
target_link_libraries(${component} PUBLIC ${link_libs})
endfunction()
Answered By - cancech Answer Checked By - Gilberto Lyons (WPSolving Admin)