Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fail to use Q_OBJECT Macro in CMake Project

I am having trouble with the meta Object Compiler of Qt in my CMake Project. A shared lib I am building contains the following code and employs the pimpl idiom. After invoking CMake and upon compilation I get

AUTOGEN: error: ~/tools/Project/gui/src/mainWindow.cpp: The file contains a Q_OBJECT macro, but does not include "mainWindow.moc" ! gui/CMakeFiles/gui_automoc.dir/build.make:57: recipe for target 'gui/CMakeFiles/gui_automoc' failed make[2]: *** [gui/CMakeFiles/gui_automoc] Error 1 CMakeFiles/Makefile2:234: recipe for target 'gui/CMakeFiles/gui_automoc.dir/all' failed

I dont get what I am doing wrong or whats the correct way to incorporate src files with the Q_OBJECT Macro in my project. Please help =/

gui/include/gui/mainWindow.hpp

#include <QMainWindow>
#include <string>


class MainWindow : public QMainWindow {
  class MainWindowImpl;

 public:
  MainWindow(QWidget* parent = nullptr);

 private:
  MainWindowImpl* pimpl_;
};

gui/src/mainWindow.cpp

#include "gui/mainWindow.hpp"

class MainWindow::MainWindowImpl : public QWidget{
 Q_OBJECT
  public:
   explicit MainWindowImpl(MainWindow *parent);

  private:
   MainWindow &parent_;
};

MainWindow::MainWindowImpl::MainWindowImpl(MainWindow *parent)
    : QWidget{parent}, parent_(*parent) {}

MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} {
    pimpl_ = new MainWindowImpl{this};
    setCentralWidget(pimpl_);
}

I compile the libray like so:

cmake_minimum_required(VERSION 3.5.1 FATAL_ERROR)
project(gui)

QT5_WRAP_CPP(MOC_Files
include/gui/mainWindow.hpp
)

add_library(${PROJECT_NAME}
  SHARED
   src/mainWindow.cpp
   ${MOC_Files}
)
add_library(gui::gui ALIAS ${PROJECT_NAME})

target_include_directories(${PROJECT_NAME} 
  PUBLIC 
   ${PROJECT_SOURCE_DIR}/include
)

set_target_properties(${PROJECT_NAME} PROPERTIES AUTOMOC TRUE)

target_link_libraries(${PROJECT_NAME}
  PUBLIC
   Qt5::Widgets
   Qt5::Core
   Qt5::Xml
   Qt5::OpenGL
   Qt5::Gui
)

install(TARGETS ${PROJECT_NAME} DESTINATION lib)

Now I want to link this lib against my executable

apps/main.cpp

#include <QApplication>
#include "gui/mainWindow.hpp"

int main(int argc, char *argv[]) {

QApplication app{argc, argv};

MainWindow gui{};
gui.show();

return app.exec();
}

with the following CMakelists.txt where I link against the gui lib

cmake_minimum_required (VERSION 3.5.1 FATAL_ERROR)
project (app)

add_executable(${PROJECT_NAME}
  main.cpp
)

target_include_directories(${PROJECT_NAME}
    PUBLIC ${PROJECT_BINARY_DIR}
)

target_link_libraries(${PROJECT_NAME}
  PRIVATE
   gui::gui
   Qt5::Widgets
   Qt5::Core
   Qt5::Xml
   Qt5::OpenGL
   Qt5::Gui
 )

 install(TARGETS ${PROJECT_NAME}
         DESTINATION bin)

my top-level CMakeLists of the project looks like the following

cmake_minimum_required (VERSION 3.5.1 FATAL_ERROR)
project(project)

set(CMAKE_INSTALL_DIR ${PROJECT_SOURCE_DIR}/obj)
set(CMAKE_INSTALL_PREFIX  ${CMAKE_INSTALL_DIR})
# add our local path to the runtime path
SET(CMAKE_INSTALL_RPATH "$ORIGIN:${CMAKE_INSTALL_PREFIX}/lib")
# also add the link paths to the runtime paths
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

find_package(Qt5 COMPONENTS Core Widgets Xml OpenGL Gui REQUIRED)

## --> Build libraries and applications  <--
add_subdirectory(gui)
add_subdirectory(apps)
like image 437
CD86 Avatar asked Nov 24 '17 10:11

CD86


1 Answers

It is a typical confusion about compiling Qt application with CMake. Basically there are two approaches to run moc preprocessor with CMake:

1. CMake's approach with AUTOMOC property.

It is very easy to use but has a couple of requirements which are mentioned in the documentation.

  • Make sure the property AUTOMOC is enabled for the target.

    set_target_properties(${PROJECT_NAME} PROPERTIES AUTOMOC TRUE)
    
  • If your .cpp file contains Q_OBJECT macro then you need to include the generated .moc file after the last qobject class (better at the end of the file). For this step you also need to enable CMAKE_INCLUDE_CURRENT_DIR but it is a general recommendation for any CMake+Qt build.

  • If your header file contains Q_OBJECT make sure CMake knows about it. The easiest way is to pass along with the source files:

    add_library(${PROJECT_NAME}
      SHARED
       include/mainWindow.hpp
       src/mainWindow.cpp
    )
    
  • And finally link all required Qt libraries.

    target_link_libraries(${PROJECT_NAME}
      PUBLIC
       Qt5::Widgets
       Qt5::Core
    )
    

So to fix your code in CMake's way:

gui/src/mainWindow.cpp:

#include "gui/mainWindow.hpp"

class MainWindow::MainWindowImpl : public QWidget{
 Q_OBJECT
  public:
   explicit MainWindowImpl(MainWindow *parent);

  private:
   MainWindow &parent_;
};

MainWindow::MainWindowImpl::MainWindowImpl(MainWindow *parent)
    : QWidget{parent}, parent_(*parent) {}

MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} {
    pimpl_ = new MainWindowImpl{this};
    setCentralWidget(pimpl_);
}

#include "mainWindow.moc"

gui/CMakeLists.txt:

project(gui)

set(CMAKE_INCLUDE_CURRENT_DIR YES)

add_library(${PROJECT_NAME}
  SHARED
  include/gui/mainWindow.hpp
  src/mainWindow.cpp
)
add_library(gui::gui ALIAS ${PROJECT_NAME})

target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)

set_target_properties(${PROJECT_NAME} PROPERTIES AUTOMOC TRUE)

target_link_libraries(${PROJECT_NAME}
  PUBLIC
   Qt5::Widgets
   Qt5::Core
)

2. Qt approach with QT5_WRAP_CPP

Here you simply need to "wrap" all your header files that have Q_OBJECT in them and add the result to the list of source files.

Or if you have a class in cpp file it gets tricky. The Q_OBJECT macro adds member functions to the class. The implementation of any class member function outside of the class body needs to know the class declaration. These implementations are inside the generated .moc file but they cannot see the declaration of the class. The easiest way to fix it would be to split your .cpp file into two:

gui/src/mainWindowImpl.hpp:

#pragma once
#include "gui/mainWindow.hpp"

class MainWindow::MainWindowImpl : public QWidget{
 Q_OBJECT
  public:
   explicit MainWindowImpl(MainWindow *parent);

  private:
   MainWindow &parent_;
};

gui/src/mainWindow.cpp:

#include "gui/mainWindow.hpp"
#include "mainWindowImpl.hpp"

MainWindow::MainWindowImpl::MainWindowImpl(MainWindow *parent)
    : QWidget{parent}, parent_(*parent) {}

MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} {
    pimpl_ = new MainWindowImpl{this};
    setCentralWidget(pimpl_);
}

And include the additional header in the QT5_WRAP_CPP:

gui/CMakeLists.txt:

project(gui)

QT5_WRAP_CPP(MOC_Files
  include/mainWindow.hpp
  src/mainWindowImpl.hpp
)

add_library(${PROJECT_NAME}
  SHARED
   src/mainWindow.cpp
   ${MOC_Files}
)
add_library(gui::gui ALIAS ${PROJECT_NAME})

target_include_directories(${PROJECT_NAME} 
  PUBLIC 
   ${PROJECT_SOURCE_DIR}/include
)

target_link_libraries(${PROJECT_NAME}
  PUBLIC
   Qt5::Widgets
   Qt5::Core
)

Note! Be careful with moc and classes that use complex syntax as there are limitations.

like image 70
Teivaz Avatar answered Oct 31 '22 16:10

Teivaz