Derek C.
Derek C.

Reputation: 1008

Error including "private" header with CMake

In my library I have seperated the public headers from the source by putting them into include and src. While using Cmake I have this for my library :

target_include_directories(${PROJECT_NAME}
    PRIVATE 
        "${CMAKE_CURRENT_SOURCE_DIR}/include/Thoth"
        "${CMAKE_CURRENT_SOURCE_DIR}/src"
    INTERFACE 
        "${CMAKE_CURRENT_SOURCE_DIR}/include"
)

The idea behind this is that I want the end-user (using the library) to only have the include directory so that they must include like so :

#include <Thoth/file.h>

But within the library I can omit the Thoth.
Now I've also including the src directory within the library because there are some private headers that live there.
This worked alright and I could include every file with the intended path.

When compiling an exe that used the library the library would compile successfully but it would fail on the exe itself.

This was due to the header found within the libraries src not being found. Now that file is not being included in the exe as it is private. But it came from the fact that the exe included a header that had the private header included.

I've played around with some of the settings of visibility and done my googling but I have not found the answer.
How do others deal with separating private and public headers.

Of course I could always use a relative path within the library but I'd rather avoid this seeing as :

#include "../src/private.h"

Is ugly.
Of course if that is the only way, so be it. Though I'd think others would have run into this and wanted a solution.

The other way I could think of is to just include the src folder publicly and trust the user to not include the private header but that would pollute the include path and is undesirably.

Full library CMake :

cmake_minimum_required(VERSION 3.13)

project(Thoth)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_library(${PROJECT_NAME}
    src/IndentData.cpp
    src/RenderComponent.cpp
    src/RenderElement.cpp
    src/RenderManager.cpp
)

target_include_directories(${PROJECT_NAME}
    PRIVATE 
        "${CMAKE_CURRENT_SOURCE_DIR}/include/Thoth"
        "${CMAKE_CURRENT_SOURCE_DIR}/src"
    INTERFACE 
        "${CMAKE_CURRENT_SOURCE_DIR}/include"
)

Full exe CMake :

cmake_minimum_required(VERSION 3.13)

project(myExe)

add_subdirectory(lib/Thoth)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(${PROJECT_NAME} src/main.cpp)

target_link_libraries(${PROJECT_NAME} PUBLIC Thoth)

As for the files, that's a little harder to show but the problem is that RenderComponent.hpp (within include/Thoth) includes IndentData.hpp (within src). So when the exe includes #include <Thoth/RenderComponent.hpp> it errors that the src dir of the library is not in the include path.

Upvotes: 3

Views: 2748

Answers (2)

Stewart
Stewart

Reputation: 5022

@sparik had a good answer. Here's a little more detail to elaborate on that.

If one of your public headers depends on a private header, that private header will need to be moved to the public location That's because when you deploy your library (binary + headers), the user shouldn't depend on having access to your source tree.

The other way I could think of is to just include the src folder publicly and trust the user to not include the private header but that would pollute the include path and is undesirably.

This is option 1. It's the simplest and not uncommon. If you look at the public include directory of a massive library like boost unit_test_framework, you'll see that things which are private (i.e. undocumented, subject to api breakages) are located in <boost/test/detail/...hpp> or <boost/test/impl/...ipp>

Option 2 would be to reduce the need for your public headers to include your private headers. That might be done by forward declaring everything. Storing references to those private classes instead of being composed of those private classes is one way to help you do that.

Upvotes: 3

sparik
sparik

Reputation: 1201

All the headers included by the user of the library, either directly or transitively, should be in /include.

If LibInterface.h includes LibInternal.h and the user includes LibInterface.h, then LibInternal.h should also be in /include, because the user includes LibInternal.h transitively.

If you can avoid including LibInternal.h being included in LibInterface.h, do it. Sometimes you cannot, because LibInterface.h may depend on something defined in LibInternal.h.

If you want to discourage the user from directly including LibInternal.h, you could put it in a /include/detail or something like that.

In some cases, you can use pimpl idiom to break dependency between your interface and implementation, but that has also its disadvantages, so it's a tradeoff.

Upvotes: 3

Related Questions