CMake does not run moc for a Q_NAMESPACE in a shared library

I’m trying to register a namespace in QML from a shared library, but the compilation fails with an unresolved external symbol error:

failed (exit code 1120) with the following output:
main.cpp.obj : error LNK2019: unresolved external symbol "struct QMetaObject const EnumNamespace::staticMetaObject" (?staticMetaObject@EnumNamespace@@3UQMetaObject@@B) referenced in function "class QDebug __cdecl operator<<<enum EnumNamespace::MyEnum>(class QDebug,enum EnumNamespace::MyEnum)" (??$?6W4MyEnum@EnumNamespace@@@@YA?AVQDebug@@V0@W4MyEnum@EnumNamespace@@@Z)
StaticMetaObject.exe : fatal error LNK1120: 1 unresolved externals

I have searched the build directory for the moc file (moc_enum.cpp or similar) but I couldn’t find it. The only moc related file I could find was mocs_compilation.cpp:

// This file is autogenerated. Changes will be overwritten.
// No files found that require moc or the moc files are included
enum some_compilers { need_more_than_nothing };

Why is CMake not finding the file it needs to moc?
If instead of using CMake I use qmake, it compiles fine and the moc_enum.cpp file is present in the build directory.

Setup

  • Qt 5.12.10 & Qt Creator 10.0.0
  • CMake 3.23 (integrated in Qt Creator)
  • Visual Studio 2017 64bit compiler.

Code

Structure

staticmetaobject/
      |_ CMakeLists.txt
      |_ main.cpp
      |_ enum/
             |_ CMakeLists.txt
             |_ enum.h
             |_ enumLib.h

Main CmakeLists.txt

cmake_minimum_required(VERSION 3.23 FATAL_ERROR)

project(StaticMetaObject VERSION 1.0.0 LANGUAGES CXX DESCRIPTION "Static Meta Object Test")

set(BUILD_SHARED_LIBS ON)
set(CMAKE_DEBUG_POSTFIX d)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

find_package(QT NAMES Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Qml)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

add_subdirectory(enum)

add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PUBLIC main.cpp)
target_link_libraries(${PROJECT_NAME} enumLib Qt${QT_VERSION_MAJOR}::Qml)

main.cpp

#include "enum/enum.h"
#include <QCoreApplication>
#include <QDebug>
#include <QQmlEngine>

// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
int main(int argc, char * *argv)
{
    QCoreApplication app{argc, argv};
    qInfo() << EnumNamespace::TWO;

    // Comenting this line out doesn't solve the problem.
    qmlRegisterUncreatableMetaObject(EnumNamespace::staticMetaObject, "ABCDE", 1, 0, "EnumNamespace", "Error: only enums");

    return app.exec();
}

Enum lib

CMakeLists.txt

cmake_minimum_required(VERSION "${CMAKE_MINIMUM_REQUIRED_VERSION}" FATAL_ERROR)

set(LIB_NAME enumLib)
add_library(${LIB_NAME} SHARED)
set_target_properties(${LIB_NAME} PROPERTIES LINKER_LANGUAGE CXX)
target_sources(${LIB_NAME} PUBLIC enum.h enumLib.h)
target_compile_definitions(${LIB_NAME} PUBLIC ENUMLIB_BUILD)
target_link_libraries(${LIB_NAME} PUBLIC Qt${QT_VERSION_MAJOR}::Core)

enum.h

#ifndef ENUM_H
#define ENUM_H

#include "enumLib.h"
#include <QObject>

namespace EnumNamespace {
    ENUMLIB_EXPORT Q_NAMESPACE
    enum MyEnum : unsigned int
    {
        ZERO = 0,
        ONE,
        TWO
    };
    Q_ENUM_NS(MyEnum);
}

#endif // ENUM_H

enumLib.h

#ifndef ENUMLIB_H
#define ENUMLIB_H

#if defined(ENUMLIB_BUILD)
#define ENUMLIB_EXPORT Q_DECL_EXPORT
#else
#define ENUMLIB_EXPORT Q_DECL_IMPORT
#endif

#endif // ENUMLIB_H

Pro file

QT = core

CONFIG += c++17 cmdline

QT += core qml

DEFINES += ENUMLIB_BUILD

HEADERS += \
        enum/enum.h \
        enum/enum.h \
        enum/enumLib.h

SOURCES += \
        main.cpp

Verbose CMake build log

19:08:34: Running steps for project StaticMetaObject...
19:08:34: Starting: "C:\Qt\Tools\CMake_64\bin\cmake.exe" --build C:/SRC/MyTests/StaticMetaObject/build_release --target all --verbose
[1/7 25.6/sec] cmd.exe /C "cd /D C:\SRC\MyTests\StaticMetaObject\build_release\enum && C:\Qt\Tools\CMake_64\bin\cmake.exe -E cmake_autogen C:/SRC/MyTests/StaticMetaObject/build_release/enum/CMakeFiles/enumLib_autogen.dir/AutogenInfo.json Release"
[2/7 8.7/sec] C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\HostX64\x64\cl.exe  /nologo /TP -DENUMLIB_BUILD -DQT_CORE_LIB -DQT_NO_DEBUG -DenumLib_EXPORTS -IC:\SRC\MyTests\StaticMetaObject\build_release\enum\enumLib_autogen\include -external:IC:\Qt\5.12.10\msvc2017_64\include -external:IC:\Qt\5.12.10\msvc2017_64\include\QtCore -external:IC:\Qt\5.12.10\msvc2017_64\.\mkspecs\win32-msvc -external:W0 /DWIN32 /D_WINDOWS /EHsc /O2 /Ob2 /DNDEBUG -MD -std:c++17 /showIncludes /Foenum\CMakeFiles\enumLib.dir\enumLib_autogen\mocs_compilation.cpp.obj /Fdenum\CMakeFiles\enumLib.dir\ /FS -c C:\SRC\MyTests\StaticMetaObject\build_release\enum\enumLib_autogen\mocs_compilation.cpp
[3/7 7.6/sec] cmd.exe /C "cmd.exe /C "C:\Qt\Tools\CMake_64\bin\cmake.exe -E __create_def C:\SRC\MyTests\StaticMetaObject\build_release\enum\CMakeFiles\enumLib.dir\.\exports.def C:\SRC\MyTests\StaticMetaObject\build_release\enum\CMakeFiles\enumLib.dir\.\exports.def.objs && cd C:\SRC\MyTests\StaticMetaObject\build_release" && C:\Qt\Tools\CMake_64\bin\cmake.exe -E vs_link_dll --intdir=enum\CMakeFiles\enumLib.dir --rc=C:\PROGRA~2\WI3CF2~1\10\bin\100190~1.0\x64\rc.exe --mt=C:\PROGRA~2\WI3CF2~1\10\bin\100190~1.0\x64\mt.exe --manifests  -- C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\Hostx64\x64\link.exe /nologo enum\CMakeFiles\enumLib.dir\enumLib_autogen\mocs_compilation.cpp.obj  /out:enum\enumLib.dll /implib:enum\enumLib.lib /pdb:enum\enumLib.pdb /dll /version:0.0 /machine:x64 /INCREMENTAL:NO  /DEF:enum\CMakeFiles\enumLib.dir\.\exports.def  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Core.lib  kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib  && cd ."
[4/7 9.2/sec] cmd.exe /C "cd /D C:\SRC\MyTests\StaticMetaObject\build_release && C:\Qt\Tools\CMake_64\bin\cmake.exe -E cmake_autogen C:/SRC/MyTests/StaticMetaObject/build_release/CMakeFiles/StaticMetaObject_autogen.dir/AutogenInfo.json Release"
[5/7 10.5/sec] C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\HostX64\x64\cl.exe  /nologo /TP -DENUMLIB_BUILD -DQT_CORE_LIB -DQT_NETWORK_LIB -DQT_NO_DEBUG -DQT_QML_LIB -IC:\SRC\MyTests\StaticMetaObject\build_release\StaticMetaObject_autogen\include -external:IC:\Qt\5.12.10\msvc2017_64\include -external:IC:\Qt\5.12.10\msvc2017_64\include\QtCore -external:IC:\Qt\5.12.10\msvc2017_64\.\mkspecs\win32-msvc -external:IC:\Qt\5.12.10\msvc2017_64\include\QtQml -external:IC:\Qt\5.12.10\msvc2017_64\include\QtNetwork -external:W0 /DWIN32 /D_WINDOWS /EHsc /O2 /Ob2 /DNDEBUG -MD -std:c++17 /showIncludes /FoCMakeFiles\StaticMetaObject.dir\StaticMetaObject_autogen\mocs_compilation.cpp.obj /FdCMakeFiles\StaticMetaObject.dir\ /FS -c C:\SRC\MyTests\StaticMetaObject\build_release\StaticMetaObject_autogen\mocs_compilation.cpp
[6/7 5.4/sec] C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\HostX64\x64\cl.exe  /nologo /TP -DENUMLIB_BUILD -DQT_CORE_LIB -DQT_NETWORK_LIB -DQT_NO_DEBUG -DQT_QML_LIB -IC:\SRC\MyTests\StaticMetaObject\build_release\StaticMetaObject_autogen\include -external:IC:\Qt\5.12.10\msvc2017_64\include -external:IC:\Qt\5.12.10\msvc2017_64\include\QtCore -external:IC:\Qt\5.12.10\msvc2017_64\.\mkspecs\win32-msvc -external:IC:\Qt\5.12.10\msvc2017_64\include\QtQml -external:IC:\Qt\5.12.10\msvc2017_64\include\QtNetwork -external:W0 /DWIN32 /D_WINDOWS /EHsc /O2 /Ob2 /DNDEBUG -MD -std:c++17 /showIncludes /FoCMakeFiles\StaticMetaObject.dir\main.cpp.obj /FdCMakeFiles\StaticMetaObject.dir\ /FS -c C:\SRC\MyTests\StaticMetaObject\src\main.cpp
[7/7 5.9/sec] cmd.exe /C "cd . && C:\Qt\Tools\CMake_64\bin\cmake.exe -E vs_link_exe --intdir=CMakeFiles\StaticMetaObject.dir --rc=C:\PROGRA~2\WI3CF2~1\10\bin\100190~1.0\x64\rc.exe --mt=C:\PROGRA~2\WI3CF2~1\10\bin\100190~1.0\x64\mt.exe --manifests  -- C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\Hostx64\x64\link.exe /nologo CMakeFiles\StaticMetaObject.dir\StaticMetaObject_autogen\mocs_compilation.cpp.obj CMakeFiles\StaticMetaObject.dir\main.cpp.obj  /out:StaticMetaObject.exe /implib:StaticMetaObject.lib /pdb:StaticMetaObject.pdb /version:0.0 /machine:x64 /INCREMENTAL:NO /subsystem:console  enum\enumLib.lib  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Qml.lib  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Network.lib  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Core.lib  kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib && cd ."
FAILED: StaticMetaObject.exe 
cmd.exe /C "cd . && C:\Qt\Tools\CMake_64\bin\cmake.exe -E vs_link_exe --intdir=CMakeFiles\StaticMetaObject.dir --rc=C:\PROGRA~2\WI3CF2~1\10\bin\100190~1.0\x64\rc.exe --mt=C:\PROGRA~2\WI3CF2~1\10\bin\100190~1.0\x64\mt.exe --manifests  -- C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\Hostx64\x64\link.exe /nologo CMakeFiles\StaticMetaObject.dir\StaticMetaObject_autogen\mocs_compilation.cpp.obj CMakeFiles\StaticMetaObject.dir\main.cpp.obj  /out:StaticMetaObject.exe /implib:StaticMetaObject.lib /pdb:StaticMetaObject.pdb /version:0.0 /machine:x64 /INCREMENTAL:NO /subsystem:console  enum\enumLib.lib  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Qml.lib  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Network.lib  C:\Qt\5.12.10\msvc2017_64\lib\Qt5Core.lib  kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib && cd ."
LINK: command "C:\PROGRA~2\MICROS~2\2019\COMMUN~1\VC\Tools\MSVC\1429~1.301\bin\Hostx64\x64\link.exe /nologo CMakeFiles\StaticMetaObject.dir\StaticMetaObject_autogen\mocs_compilation.cpp.obj CMakeFiles\StaticMetaObject.dir\main.cpp.obj /out:StaticMetaObject.exe /implib:StaticMetaObject.lib /pdb:StaticMetaObject.pdb /version:0.0 /machine:x64 /INCREMENTAL:NO /subsystem:console enum\enumLib.lib C:\Qt\5.12.10\msvc2017_64\lib\Qt5Qml.lib C:\Qt\5.12.10\msvc2017_64\lib\Qt5Network.lib C:\Qt\5.12.10\msvc2017_64\lib\Qt5Core.lib kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTFILE:StaticMetaObject.exe.manifest" failed (exit code 1120) with the following output:
main.cpp.obj : error LNK2019: unresolved external symbol "struct QMetaObject const EnumNamespace::staticMetaObject" (?staticMetaObject@EnumNamespace@@3UQMetaObject@@B) referenced in function "class QDebug __cdecl operator<<<enum EnumNamespace::MyEnum>(class QDebug,enum EnumNamespace::MyEnum)" (??$?6W4MyEnum@EnumNamespace@@@@YA?AVQDebug@@V0@W4MyEnum@EnumNamespace@@@Z)
StaticMetaObject.exe : fatal error LNK1120: 1 unresolved externals
ninja: build stopped: subcommand failed.
19:08:36: The process "C:\Qt\Tools\CMake_64\bin\cmake.exe" exited with code 1.
Error while building/deploying project StaticMetaObject (kit: Desktop Qt 5.12.10 MSVC2017 64bit)
When executing step "Build"
19:08:36: Elapsed time: 00:01.

Using the autogenerated exports file

Using the CMake generated exports file didn’t work either. The compilation fails with the same error.

cmake_minimum_required(VERSION "${CMAKE_MINIMUM_REQUIRED_VERSION}" FATAL_ERROR)

set(LIB_NAME enumLib)
add_library(${LIB_NAME} SHARED)
set_target_properties(${LIB_NAME} PROPERTIES LINKER_LANGUAGE CXX)
target_sources(${LIB_NAME} PUBLIC enum.h)

include(GenerateExportHeader)
generate_export_header(${LIB_NAME} EXPORT_MACRO_NAME ENUMLIB_EXPORT)

target_compile_definitions(${LIB_NAME} PUBLIC enumLib_EXPORTS)
target_include_directories(${LIB_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(${LIB_NAME} PUBLIC Qt${QT_VERSION_MAJOR}::Core)

This looks like the Q_NAMESPACE macro isn’t recognized by autogen as a trigger for “please moc this” (as your subject seems to find). There is the AUTOMOC_MACRO_NAMES target property you can set, but we should probably add Q_NAMESPACE to the default set. Could you please file an issue?

Hi Ben.
I believe there is no need to explicitly add Q_NAMESPACE to the list, as it should have been there by default. From the AUTOMOC_MACRO_NAMES documentation:

By default AUTOMOC_MACRO_NAMES is initialized from CMAKE_AUTOMOC_MACRO_NAMES.

And in the CMAKE_AUTOMOC_MACRO_NAMES docs:

The default value is Q_OBJECT;Q_GADGET;Q_NAMESPACE;Q_NAMESPACE_EXPORT.

I have tried to add Q_ENUM_NS and the export keyword we are using to the list, but I’m probably doing it wrong because I couldn’t make it work:

set_property(TARGET ${LIB_NAME} APPEND PROPERTY AUTOMOC_MACRO_NAMES "Q_ENUM_NS;ENUMLIB_EXPORT")

The same error appears when building the test project.

FYI, this is the value of the target’s AUTOMOC_MACRO_NAMES property after the update:
Q_OBJECT;Q_GADGET;Q_NAMESPACE;Q_NAMESPACE_EXPORT;Q_ENUM_NS;ENUMLIB_EXPORT

I wonder if putting ENUMLIB_EXPORT on the same line is confusing it? It certainly doesn’t belong in the AUTOMOC_MACRO_NAMES list though.

Well, that was it! If I put ENUMLIB_EXPORTand Q_NAMESPACE in different lines as you suggested, it works fine.
I haven’t opened any issue yet. Is it still necessary?

Thanks a lot, @ben.boeckel!

Yes, please. If at least for docs if we can’t fix it (it’s best to have a stakeholder involved to help get more information if needed).

Hi @ben.boeckel.
I can’t file the issue. GitLab says it has been identified as “spam”:

image

Blah. Can you post the text of your issue here and GitLab username? I’ll try and post it then.

Ok. I managed to do it! It’s the issue 24905.

related on Stack Overflow: visual studio 2017 - CMake does not run moc for a Q_NAMESPACE in a shared library - Stack Overflow