I needed to set some global data (logging handler, base path, etc) across all libraries/targets (internally managed). A manual system of InitLib1 which calls all InitLibX of its dependencies would work, but this seemed error prone to me.
So I came up with the following system
InitLib.hpp
#include <memory>
namespace Setup
{
// Data used for initialization not relevant for this question
struct Data;
/** A type intended to map libraries onto the cpp type system, for use as template parameters */
struct LibraryName {
char const value[ 128 ];
};
/* DO NOT expose this function directly */
template< LibraryName paLibraryName = CURRENT_LIBRARY_CMAKE >
void Initialize( std::shared_ptr< Data > inData );
/* DO NOT expose this function directly */
template< LibraryName paLibraryName = CURRENT_LIBRARY_CMAKE >
Data const & GetData();
}
InitLib.cpp
#include "InitLib.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
static std::shared_ptr< Setup::Data > gData;
namespace
{
template< auto... Ts >
void InitializeDependencies( std::shared_ptr< Setup::Data > inData )
{
( ( Setup::Initialize< Ts >( inData ) ), ... );
}
}
template<>
CURRENT_LIBRARY_EXPORT_SYMBOL_CMAKE void
Setup::Initialize< CURRENT_LIBRARY >( std::shared_ptr< Setup::Data > inData )
{
std::string libraryName = CURRENT_LIBRARY_CMAKE.value;
if( inData == nullptr ) {
std::cerr << "Library " << libraryName << " must be initialized with actual data\n";
std::abort();
}
if( gData != nullptr and gData != inData ) {
std::cout << "Library " << libraryName << " is being initialized with mismatched data\n";
std::abort();
}
if( gData == inData ) return; // library already initialized
gData = inData;
InitializeDependencies< CURRENT_LIBRARY_DEPENDENCIES_CMAKE >( std::move( inData ) );
};
template<>
Setup::Data const &
Setup::GetData< CURRENT_LIBRARY_CMAKE >()
{
return *gData;
}
CMakeLists.txt
add_library( "Setup_InitLib" INTERFACE )
target_sources(
"Setup_InitLib"
INTERFACE
FILE_SET "HEADERS"
BASE_DIRS "${CURRENT_INCLUDE_FOLDER}"
FILES "InitLib.hpp"
)
target_sources(
"Setup_InitLib"
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Source/InitLib.cpp>"
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/src/Setup/Source/InitLib.cpp>"
)
set_property(
TARGET "Setup_InitLib"
APPEND PROPERTY "TRANSITIVE_LINK_PROPERTIES" "SETUP_LIBRARY_ID"
)
target_compile_definitions(
"Setup_InitLib"
INTERFACE
"CURRENT_LIBRARY_DEPENDENCIES_CMAKE=$<JOIN:$<TARGET_PROPERTY:SETUP_LIBRARY_ID>,$<COMMA>>"
)
function(add_library_init_code in_target in_export_symbol)
target_link_libraries("${in_target}" PRIVATE "Setup_InitLib")
set_property(TARGET "${in_target}" PROPERTY "SETUP_LIBRARY_NAME" "${in_target}")
set(local_library_id "Setup::LibraryName{\"$<TARGET_PROPERTY:${in_target},SETUP_LIBRARY_NAME>\"}")
set_property(TARGET "${in_target}" PROPERTY "INTERFACE_SETUP_LIBRARY_ID" "${local_library_id}")
set_property(
TARGET "${in_target}"
APPEND PROPERTY "EXPORT_PROPERTIES" "SETUP_LIBRARY_NAME;SETUP_LIBRARY_ID"
)
target_compile_definitions(
"${in_target}"
PRIVATE
"CURRENT_LIBRARY_EXPORT_SYMBOL_CMAKE=${in_export_symbol}"
"CURRENT_LIBRARY_CMAKE=${local_library_id}"
)
source_group("Inherited Sources" FILES "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/Source/InitLib.cpp")
endfunction()
Usage:
in CMake: just add add_library_init_code after defining you target.
in Cpp: any top level starting code should just call Setup::Initialize()
afterwards each library can access the same data using Setup::GetData().
Questions:
- Any way of making gData const?
- General remarks
- Weird edge cases