[CMake] Preventing multiple definitions of metaObject methods

Stephen Morris s-morris at n-eos.com
Mon Sep 16 06:56:47 EDT 2019


-----Original Message-----
From: Kyle Edwards <kyle.edwards at kitware.com> 
Sent: 13 September 2019 16:54
To: Stephen Morris <s-morris at n-eos.com>; cmake at cmake.org
Subject: Re: [CMake] Preventing multiple definitions of metaObject methods

>Stephen,
>Could you post a minimally reproducible example with CMakeLists.txt and accompanying source code? I am currently working on a Qt-based >project with static libraries and have not encountered this issue.
>Kyle

It appears that the problem was due to a misunderstanding about how the PRIVATE keyword works in tergat_sources. I'd assumed that it should be used to identify the 'public headers' of a library (i.e. headers that should be included by the consumers of that library. It seems that this isn't the case, and even 'public' headers should be defined with the PRIVATE keyword.

Here are the simple demonstration files I wrote, four in all:

==== myTest.h ====

#ifndef MYTEST_HDR
#define MYTEST_HDR

class myTestWindow : public QMainWindow
{
    Q_OBJECT
public:
    myTestWindow()
    virtual ~myTestWindow();

signals:
    void readyToDisplay();

private slots:
    void showTitle();
};

#endif

==== myTest.cpp ====

#include <QtCore>
#include <QtWidgets>
#include "myTest.h"

myTestWindow::myTestWindow() : QMainWindow(nullptr)
{
    // Send the signal when ready to display
    (QTimer::singleShot(0, this, &myTestWindow::showTitle));
}

myTestWindow::~myTestWindow()
{}

void myTestWindow::showTitle()
{
    setWindowTitle(tr("This is a test window"));
}

#include "moc_myTest.cpp"

==== main.cpp ====

#include <QtCore>
#include <QtWidgets>

#include "myTest.h"

int main(int argc, char *argv[])
{
    QApplication * app = new QApplication (argc, argv);
    myTestWindow * win = new myTestWindow();
    win->show();
    app->exec();
    return 0;
}

==== CMakeLists.txt ====

cmake_minimum_required (VERSION 3.14)
project(myTest LANGUAGES CXX)

find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED PATHS "C:\\Qt\\5.12.3\\msvc2017_64\\lib\\cmake\\Qt5" NO_DEFAULT_PATH)
set (CMAKE_AUTOMOC ON)
set (CMAKE_AUTORCC ON)
set (CMAKE_AUTOUIC ON)

add_library(myTest_Lib STATIC)
target_sources(myTest_Lib PRIVATE myTest.cpp)
target_sources(myTest_Lib PUBLIC myTest.h)
target_link_libraries(myTest_Lib PUBLIC Qt5::Core Qt5::Widgets)
set_target_properties(myTest_Lib PROPERTIES OUTPUT_NAME myTest)

add_executable(myTest_Exe main.cpp)
target_include_directories(myTest_Exe PRIVATE ${CMAKE_LIST_DIR})
target_link_libraries(myTest_Exe PRIVATE myTest_Lib)
set_target_properties(myTest_Exe PROPERTIES OUTPUT_NAME myTest VS_DEBUGGER_ENVIRONMENT  "PATH=C:\\Qt\\5.12.3\\msvc2017_64\\bin;%PATH%")	


Compiling and running them as given here, with the line "target_sources(myTest_Lib PUBLIC myTest.h)" in the MakeLists.txt file, my library compiles cleanly but I get an error saying "'QMainWindow': base class undefined" when trying to compile the executable application. This is because the compiler encounters the Q_OBJECT macro in myTest.h, generates a new moc_myTest.cpp, then because it can't see that the original moc_myTest.cpp was compiled within the library it includes it in its own mocs_compilation.cpp file. The error occurs when trying to compile this file, since moc_myTest.cpp does not include <QMainWindow> anywhere.

If I try to fix this by adding the line "#include <QtWidgets/QMainWindow>" just above the class declaration in myTest.h, then the error changes to a bunch of 'multiple definition' errors, as I reported in my original question on Friday.

However, if I just change  "target_sources(myTest_Lib PUBLIC myTest.h)" to  "target_sources(myTest_Lib PRIVATE myTest.h)", then all the problems go away; it doesn't even matter whether I leave the redundant '#include' in myTest.h, everything compiles cleanly either way.

It seems that the matter of static vs. shared libraries in my initial question was a red herring - in my previous work with shared libraries I haven't used either the PUBLIC or PRIVATE keywords, but merely supplied add_library with a list of undifferentiated source files. This problem turns out to have been entirely a matter of the use of the PUBLIC keyword. I have obviously been using it incorrectly, though this leads me to wonder what its purpose is, if not for this.  


More information about the CMake mailing list