11

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/

user2180977
  • 401
  • 3
  • 14
  • A lot of stuff in your code not needed (e.g. there is no need in `find_package(Qt5Core)` if you're already using `find_package(Qt5Widget)`). But I think the error is that you need to include `ui_Bar.h` but not `*.cpp` files. See [example](https://github.com/forexample/qt-cmake/tree/master/TextFinder) –  Feb 13 '15 at 12:26
  • That was just a mistake when I was retyping the code from the actual test case I built to reproduce the problem. The real code includes the header, not the cpp. Thanks for catching it; I corrected the question. – user2180977 Feb 13 '15 at 16:17
  • BTW, I tried using some of the other auto capabilities (which I wasn't aware of) such as autouic by doing set(CMAKE_AUTOUIC ON), but that failed miserably, too since it only looks for the ui file in the same directory where Bar.cpp lives; it cannot figure out that it's in a separate directory (forms). – user2180977 Feb 14 '15 at 05:36
  • Yep, I've tried to move form to other directory and it failed to compile. Looks like it designed so that all files must be in the same directory. –  Feb 14 '15 at 08:18
  • Seems like it is a limitation in the current CMake automoc code, as a workaround you could add a custom target to call `moc src/include/Foo -o src/lib/moc_Foo.cpp`. There does not seem to be any option you can pass to moc with [AUTOMOC_MOC_OPTIONS](http://www.cmake.org/cmake/help/v3.0/prop_tgt/AUTOMOC_MOC_OPTIONS.html#prop_tgt:AUTOMOC_MOC_OPTIONS) and [CMAKE_AUTOMOC_RELAXED_MODE](http://www.cmake.org/cmake/help/v3.0/variable/CMAKE_AUTOMOC_RELAXED_MODE.html#variable:CMAKE_AUTOMOC_RELAXED_MODE) doesn' work either. Looks like the only solution is filing a CMake bugreport. – ar31 Feb 20 '15 at 00:33
  • I'm having the same problem, I'll try to use `qt5_wrap_ui` and `qt5_wrap_cpp` to use explicitly defined files... Please let me know if you've found a solution in the meantime. – MOnsDaR Jan 16 '16 at 14:44
  • Having the same problem. Its document "The moc command line will consume the COMPILE_DEFINITIONS and INCLUDE_DIRECTORIES target properties from the target it is being invoked for, and for the appropriate build configuration." – tom Jan 19 '16 at 08:01

2 Answers2

3

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

paceholder
  • 1,064
  • 1
  • 10
  • 21
  • _Maybe_ that would work in the OTHER direction, but if (as you say) `src/include` is the _public include directory_, then presumably it's where the headers would be installed from. Installing a header that contains nothing but a relative include for another (non-installed) header would create a very broken install. – FeRD Feb 19 '20 at 01:51
0

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.)

FeRD
  • 1,699
  • 15
  • 24
  • But what if I don't want to remove "moc_Bar.cpp"? It speeds up my builds by up to 40%. – Hedede May 05 '22 at 07:19
  • @Hedede Not sure I follow. I only suggested removing the **`#include`** for the moc'd file, because CMake will do it automatically (it just picks an internal location for the file itself). But the `moc_Bar.cpp` file itself should be autogenerated during the build, so there's nothing to remove. You're not committing pre-generated `moc_*.cpp` source files to your project, are you? That strikes me as a risky move: What if they were generated for a different Qt version than the one the build is using? Moc'd sources are really meant to be generated for each build. – FeRD May 10 '22 at 10:09
  • I'm having trouble imagining a project where running `moc` would add 40% to the build time, unless it's trivially small and we're talking about the difference between 10 seconds and 14 seconds or whatever. If it's a larger project, and `moc` is really that much of a time suck, perhaps it's running on lots of headers it doesn't need to? If that's the case, you could set the `AUTOMOC` property for only the _source files_ (headers) that **need** to be `moc`'d, instead of the target, to better constrain how much work `moc` tries to do during the build. – FeRD May 10 '22 at 10:12
  • Actually, my previous comment is slightly incorrect. I've added the details on skipping unnecessary `moc` runs to the end of my answer; follow those instead of what I wrote in the comment. – FeRD May 10 '22 at 10:44
  • 1
    The 40% figure is for qmake, which invokes the c++ compiler for every moc file. The codebase in question is in the process of transitioning from qmake to CMake. Now I see, CMake does a smarter thing including all `moc`s into a single `mocs_compilation.cpp`. So I'll just remove the `#include moc_*.cpp`. I'll also try your suggestion with `SKIP_AUTOMOC` to see how much speedup it gives. – Hedede May 10 '22 at 21:54
  • Yeah, there's a reason (well, plenty of reasons) why Qt themselves are leading the charge away from `qmake`; that insane 40% figure sounds like one more of them! – FeRD May 14 '22 at 14:35