user2180977
user2180977

Reputation: 411

How to instruct cmake/automoc to find external header

I have a Qt widget C++ class that loads a ui file created in Qt Creator. The header and the source file for the class live in two separate directories. I have trouble instructing cmake/automoc to find the header for the class. cmake recognizes it needs to moc the C++ file but it cannot find the analogous header.

Is there something I can do to help cmake find the files?

Everything works fine if both the cpp and the header file are in the same directory. This only comes up when the headers are elsewhere.

My directory structure is

project
    src
        include
            Foo
                Bar.h
    lib
        Foo
            Bar.cpp
            forms
                Bar.ui           

In src/include/Foo/Bar.h I have:

// Bar.h
#include <QtWidgets/QWidget>

namespace Ui { class Bar; }

class Bar : public QWidget {
    Q_OBJECT
    ...
}

In src/Foo/Bar.cpp file:

#include "Foo/Bar.h"
#include "moc_Bar.cpp"
#include "ui_Bar.h"

My CMakeLists.txt in src/lib/Foo is set up as follows:

# there is a project() call at the root that defines PROJECT_SOURCE_DIR
set(PUBLIC_HEADERS_DIR ${PROJECT_SOURCE_DIR}/src/include)

# Pick up public library headers
include_directories(${PUBLIC_HEADERS_DIR})

# Pick up private headers in library dir    
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

# Set up Qt
set(CMAKE_AUTOMOC ON)                    
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Widgets REQUIRED) 
include_directories(${Qt5Core_INCLUDE_DIRS})
include_directories(${Qt5Gui_INCLUDE_DIRS})
include_directories(${Qt5Widgets_INCLUDE_DIRS})
add_definitions(${Qt5Widgets_DEFINITIONS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")

# Set up Qt forms/resources
qt5_wrap_ui(UI_OUT_FILES forms/Bar.ui)
qt5_add_resources(RESOURCE_FILE resources.qrc)

# Library cpp and header files
set(CORE_CPP_FILES Bar.cpp)
set(LIB_CPP_FILES ${LIB_CPP_FILES} ${CORE_CPP_FILES} ${UI_OUT_FILES} ${RESOURCE_FILE}) 
set(LIB_HEADER_FILES ${PUBLIC_HEADERS_DIR}/Foo/Bar.h)

# Build library
add_library(Foo SHARED ${LIB_CPP_FILES} ${LIB_HEADER_FILES})
target_link_libraries(Foo ${Qt5Widgets_LIBRARIES})

When I run cmake, I get the following error:

AUTOGEN: error: /automoc/src/lib/Foo/Bar.cpp The file includes the moc file "moc_Bar.cpp", but could not find header "Bar{.h,.hh,.h++,.hm,.hpp,.hxx,.in,.txx}" in /automoc/src/lib/Foo/

Upvotes: 11

Views: 5769

Answers (2)

FeRD
FeRD

Reputation: 2094

I find the workarounds for this can be simplified by just leaving AUTOMOC to its own devices. Here's what I do (which works for all of our supported CMake versions, currently 3.2...3.17):

  1. Remove the #include "moc_Bar.cpp" line(s) from file(s) Bar.cpp
  2. Add the external (to the current directory) header files as PRIVATE sources for the target:
    set(CMAKE_AUTOMOC True)
    target_sources(Foo PRIVATE
      ${CMAKE_SOURCE_DIR}/src/include/Foo/Bar.h
      ${CMAKE_SOURCE_DIR}/src/include/Foo/Baz.h)
    

AUTOMOC will create a Foo_autogen output directory when it MOCs the files in question. moc_Bar.cpp and moc_Baz.cpp will be created in a randomly-named ABDEADBEEF subdirectory, and a mocs_compilation.cpp file will be created with content like the following:

// This file is autogenerated. Changes will be overwritten.
#include "ABDEADBEEF/moc_Bar.cpp"
#include "ABDEADBEEF/moc_Baz.cpp"

That file gets compiled and linked in with the final output of the target.

With CMAKE_AUTOMOC globally set True, each target will also receive its own target_autogen directory, though nothing will be generated there in targets that don't have any MOC'able classes. Still, it may be better to set the AUTOMOC target property only on the target(s) that need it:

set_property(TARGET Foo PROPERTY AUTOMOC ON)

The AUTOMOC process can even be told to avoid scanning certain source files (headers, actually), to avoid it doing unnecessary work at build time. To do that, set the SKIP_AUTOMOC property directly on the relevant files:

set_property(SOURCE Bar.h PROPERTY SKIP_AUTOMOC ON)

That will prevent moc from being run in an attempt to generate a moc_Bar.cpp for that header, if one isn't needed. (moc will typically recognize that it isn't needed and quickly skip over the header anyway, but perhaps projects with very large headers or a large number of non-Qt headers might see some benefits from not scanning all of them unnecessarily.)

Upvotes: 0

paceholder
paceholder

Reputation: 1114

You have to wrap your header files manually. Put it into your CMakeLists.txt:

file(GLOB HEADERS_TO_MOC src/include/Foo/ *.h)

qt5_wrap_cpp(PROCESSED_MOCS                                                                                                                                                                                                                                                                    
             ${HEADERS_TO_MOC}                                                   
             TARGET Foo
             OPTIONS --no-notes) # Don't display a note for the headers which don't produce a moc_*.cpp

target_sources(Foo PRIVATE ${PROCESSED_MOCS}) # This adds generated moc cpps to target

Real example of this approach https://github.com/paceholder/nodeeditor/blob/master/CMakeLists.txt#L133

Upvotes: 3

Related Questions