如何指导cmake/automoc查找外部头文件

11

我有一个 Qt widget C++ 类,可以加载在 Qt Creator 中创建的 ui 文件。该类的头文件和源文件存储在两个不同的目录中。我无法指示 cmake/automoc 找到该类的头文件。cmake 可以识别需要 moc 该 C++ 文件,但找不到相应的头文件。

是否有什么方法可以帮助 cmake 找到这些文件?

如果 cpp 和头文件在同一个目录中,一切都正常运行。只有当头文件在其他位置时才会出现此问题。

我的目录结构是

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

在 src/include/Foo/Bar.h 文件中,我有以下代码:

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

namespace Ui { class Bar; }

class Bar : public QWidget {
    Q_OBJECT
    ...
}

在 src/Foo/Bar.cpp 文件中:

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

我在src/lib/Foo目录下的CMakeLists.txt文件设置如下:

# 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})
当我运行cmake时,出现以下错误:
AUTOGEN:错误:/ automoc / src / lib / Foo / Bar.cpp文件包括moc文件“moc_Bar.cpp”,但在/ automoc / src / lib / Foo /中找不到标题为“Bar{.h,.hh,.h++,.hm,.hpp,.hxx,.in,.txx}”的头文件。

你的代码中有很多不必要的东西(例如,如果你已经使用了find_package(Qt5Widget),那么就没有必要再使用find_package(Qt5Core))。但我认为错误在于你需要包含ui_Bar.h而不是*.cpp文件。请参考示例 - user2288008
这只是我从实际测试案例中复制代码时犯的一个错误。真正的代码包括头文件,而不是 cpp 文件。谢谢你指出来;我已经更正了问题。 - user2180977
顺便说一句,我尝试使用其他自动功能(我不知道的)例如通过执行set(CMAKE_AUTOUIC ON)来使用autouic,但是它也失败了,因为它只在Bar.cpp所在的同一目录中查找ui文件;它无法确定它在一个单独的目录(forms)中。 - user2180977
我遇到了同样的问题,我将尝试使用qt5_wrap_uiqt5_wrap_cpp来使用显式定义的文件...如果您在此期间找到解决方案,请告诉我。 - MOnsDaR
遇到了同样的问题。它的文档中写道:“moc命令行将使用被调用目标的COMPILE_DEFINITIONS和INCLUDE_DIRECTORIES目标属性,并针对适当的构建配置进行处理。” - tom
显示剩余2条评论
2个回答

3

您需要手动包装头文件。 将其放入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

这种方法的真实例子请参见https://github.com/paceholder/nodeeditor/blob/master/CMakeLists.txt#L133


也许在另一个方向上会起作用,但是如果(正如您所说)src/include是公共包含目录,则可以推断出这是头文件的安装位置。安装一个仅包含另一个(未安装)头文件的相对包含的头文件将创建一个非常破损的安装。 - FeRD

0

我发现这个问题的解决方法可以通过让 AUTOMOC 自行处理来简化。以下是我的做法(适用于我们支持的所有 CMake 版本,目前为 3.2...3.17):

  1. 文件Bar.cpp中删除#include "moc_Bar.cpp"行。
  2. 外部(不在当前目录中)头文件添加为PRIVATE目标源:
    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在MOC文件时会创建一个Foo_autogen输出目录。 moc_Bar.cppmoc_Baz.cpp将被创建在一个随机命名的ABDEADBEEF子目录中,并且将创建一个mocs_compilation.cpp文件,其内容如下:

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

该文件会被编译,并与目标的最终输出链接。

如果全局设置了CMAKE_AUTOMOCTrue,每个目标也会收到自己的target_autogen目录,但在没有MOC类的目标中不会生成任何内容。尽管如此,在只对需要它的目标设置AUTOMOC目标属性可能更好:

set_property(TARGET Foo PROPERTY AUTOMOC ON)

AUTOMOC 进程甚至可以被告知避免扫描某些源文件(实际上是头文件),以避免在构建时进行不必要的工作。为此,请直接在相关文件上设置 SKIP_AUTOMOC 属性:

set_property(SOURCE Bar.h PROPERTY SKIP_AUTOMOC ON)

这将防止 moc 运行,试图为该头文件生成一个 moc_Bar.cpp,如果不需要的话。(moc 通常会认识到它不需要并快速跳过头文件,但也许具有非常大的头文件或大量非 Qt 头文件的项目可能会从不必要地扫描所有这些文件中受益。)


但是如果我不想删除“moc_Bar.cpp”呢?它可以将我的构建速度提高多达40%。 - Hedede
@Hedede 不太确定你的意思。我只是建议删除 moc 文件的 **#include**,因为 CMake 会自动处理它(它只是选择一个内部位置来存储文件本身)。但是 moc_Bar.cpp 文件本身应该在构建过程中自动生成,所以没有什么需要删除的。你不会将预生成的 moc_*.cpp 源文件提交到项目中吧?这让我感到有些冒险:如果它们是为不同的 Qt 版本生成的,而构建使用的是另一个版本,那怎么办?Moc 的源代码确实是为每个构建生成的。 - FeRD
我很难想象一个项目中运行“moc”会增加40%的构建时间,除非它非常小并且我们在10秒和14秒之间讨论差异。如果这是一个更大的项目,并且“moc”确实需要那么多时间,也许它正在运行许多不必要的头文件?如果是这种情况,您可以为只有需要进行“moc”的源文件(头文件)设置“AUTOMOC”属性,而不是目标文件,以更好地限制“moc”在构建期间尝试执行的工作量。 - FeRD
实际上,我之前的评论有些不正确。我已经在我的答案末尾添加了有关跳过不必要的“moc”运行的详细信息;请按照那些做,而不是我在评论中写的内容。 - FeRD
1
40%的数字是针对qmake的,它为每个moc文件调用c++编译器。所讨论的代码库正在从qmake过渡到CMake。现在我明白了,CMake做了一个更聪明的事情,将所有的moc包含在一个单独的mocs_compilation.cpp中。所以我将删除#include moc_*.cpp。我也会尝试您的建议,使用SKIP_AUTOMOC来看看它能提供多少加速。 - Hedede
是的,Qt自己正在引领人们远离qmake的原因(嗯,有很多原因);那个不可思议的40%的数字听起来就像其中的一个原因! - FeRD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接