10

I wasn't sure what to search for for this one. So excuse me if this is simple. But let me outline the scenario and see what answers are out there.

Let's say I have a library which defines a structure like this:

struct Example {
    int a;
#if B_ENABLED
    int b;
#endif
};

This header gets installed as a part of the library's installation as a whole. My question here is that if my library defines B_ENABLED it will have a structure with these two variables included. However if my application does not define this as well. Then it will interpret the header as defining a struct with only one member.

Is the best way to handle this just to generate some kind of "options" header which would include all of the #defines that were specified in the library build?

My library builds with CMAKE. So a CMAKE solution for this is extra credit =D.

anoneironaut
  • 1,778
  • 16
  • 28
  • do you know if the library was compiled with or without B_ENABLED defined? – tristan Dec 30 '13 at 04:43
  • I am definitely late to the party and this is not really going in the `cmake` direction, however: it seems that whoever uses your library should just "agree" to whatever options the library was compiled with. That is, it is not for the clients of the library to actually set those options anyway. In which case.. and if C++ is an option, have you had a look over the private implementation idiom? You can just hide away the definitions and present the client with a stable interface. To me it seems that the client should not care about those options. – Alexandru N. Onea Apr 07 '21 at 11:00

1 Answers1

21

Solution #1 (configure + install)

Include config.hpp file in your header file(i.e. foo.hpp):

#ifndef FOO_HPP_
#define FOO_HPP_

#include "config.hpp" // FOO_DEBUG

class Foo {
 public:
  int result() const;

 private:
  int a_;
#ifdef FOO_DEBUG
  int b_;
#endif // FOO_DEBUG
};

#endif // FOO_HPP_

config.hpp is output of configure_file command:

configure_file(config.hpp.in "${PROJECT_BINARY_DIR}/config/config.hpp")
include_directories("${PROJECT_BINARY_DIR}/config")
install(FILES Foo.hpp "${PROJECT_BINARY_DIR}/config/config.hpp" DESTINATION include)

input file config.hpp.in use special cmakedefine directive:

#ifndef CONFIG_HPP_
#define CONFIG_HPP_

#cmakedefine FOO_DEBUG

#endif // CONFIG_HPP_

Note that when you use installed library in other project:

  • you still need to specify include directories for library
  • if your library have dependencies you need to link them manually
  • you can't have 2 config files (Debug/Release)

Solution #2 (export/import target, recommended)

install(EXPORT ...) command can hold all information about using library (aka usage requirements: including definitions, linked library, configuration etc):

add_library(Foo Foo.cpp Foo.hpp)

# Target which used Foo will be compiled with this definitions
target_compile_definitions(Foo PUBLIC $<$<CONFIG:Release>:FOO_DEBUG=0>)
target_compile_definitions(Foo PUBLIC $<$<CONFIG:Debug>:FOO_DEBUG=1>)

# This directory will be used as include
target_include_directories(Foo INTERFACE "${CMAKE_INSTALL_PREFIX}/include")

# This library will be linked
target_link_libraries(Foo PUBLIC pthread)

# Regular install
install(FILES Foo.hpp DESTINATION include)

# Install with export set
install(TARGETS Foo DESTINATION lib EXPORT FooTargets)
install(EXPORT FooTargets DESTINATION lib/cmake/Foo)

Installing such project will produce files (CMAKE_DEBUG_POSTFIX is d):

include/Foo.hpp
lib/libFoo.a
lib/libFood.a
lib/cmake/Foo/FooTargets-debug.cmake
lib/cmake/Foo/FooTargets-release.cmake
lib/cmake/Foo/FooTargets.cmake

Include FooTargets.cmake file to import installed library to project. For example using find_package command (need config, see configure_package_config_file):

add_executable(prog main.cpp)
find_package(Foo REQUIRED) # import Foo
target_link_libraries(prog Foo)

Note that:

  • path to include/Foo.hpp automatically added to compiler options
  • dependend library pthread is automatically added to prog linker option
  • definition FOO_DEBUG=0 added to Release build type
  • definition FOO_DEBUG=1 added to Debug build type

Rationale

So excuse me if this is simple

It is not (:

The root of the problem is ODR (C++ Standard 2011, 3.2 [basic.def.ord], p.3):

Every program shall contain exactly one definition of every non-inline function
or variable that is odr-used in that program; no diagnostic required. The
definition can appear explicitly in the program, it can be found in the
standard or a user-defined library

IMHO good general solution still not exists. Using CMake with imported configuration can partially helps a little bit, but in some cases you still will get linker errors (for example if you use library compiled with gcc, which linked to libstdcxx by default, and try to link it to project with clang compiler, which linked to libcxx). Some of this problems (not all, still) can be solved using toolchain files. See examples.

Related

John
  • 2,963
  • 11
  • 33
  • Solution #1 is the recommended CMake solution. If necessary, see also the "@ONLY" argument to configure_file. – zjm555 Dec 31 '13 at 01:46
  • 1
    There's more wip docs on this topic here: http://www.cmake.org/cmake/help/git-next/manual/cmake-buildsystem.7.html#build-specification-and-usage-requirements – steveire Jan 02 '14 at 15:26
  • It's for 3.0, yes. I just wrote it in December :), but most of it applies to earlier CMake versions too. – steveire Jan 02 '14 at 23:17
  • 1
    @zjm555, I don't see why #1 would be preferred, can you cite that? #2 allows a library to define itself, and it's dependencies. Module mode seems typically provided from cmake to help locate libraries that AREN'T self-descriptive. And the worst part is that if the Config mode and Module mode export different targets, which is the authority?! – johnb003 Jun 22 '18 at 16:25