xo-cmake: sub-module build w/out relying on cmake external-project

This commit is contained in:
Roland Conybeare 2023-10-22 14:29:03 -04:00
commit 35820ef0da
3 changed files with 296 additions and 38 deletions

View file

@ -12,8 +12,8 @@ set(XO_PROJECT_NAME xo_macros)
install(
FILES
"cmake/xo_cxx.cmake"
"cmake/code-coverage.cmake"
"cmake/xo_macros/xo_cxx.cmake"
"cmake/xo_macros/code-coverage.cmake"
PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
DESTINATION share/cmake/${XO_PROJECT_NAME}
DESTINATION share/cmake/xo_macros
)

View file

@ -1,5 +1,21 @@
macro(xo_toplevel_compile_options)
define_property(
TARGET
PROPERTY xo_deps
BRIEF_DOCS "transitive completion of xo-dependencies for a target. Used with XO_SUBMODULE_BUILD"
)
define_property(
TARGET
PROPERTY xo_srcdir
BRIEF_DOCS "snapshot of PROJECT_SOURCE_DIR asof when this target introduced"
)
define_property(
TARGET
PROPERTY xo_bindir
BRIEF_DOCS "snapshot of PROJECT_BINARY_DIR asof when this target introduced"
)
if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 20)
endif()
@ -47,7 +63,11 @@ macro(xo_toplevel_compile_options)
endif()
endmacro()
macro(xo_include_headeronly_options2 target)
# e.g.
# - xo_target = xo_pyutil
# - nxo_target = pyutil
#
macro(xo_include_headeronly_options5 target nxo_target)
# ----------------------------------------------------------------
# PROJECT_SOURCE_DIR:
# so we can for example write
@ -64,12 +84,12 @@ macro(xo_include_headeronly_options2 target)
target_include_directories(
${target} INTERFACE
$<INSTALL_INTERFACE:include>
$<INSTALL_INTERFACE:include/xo/${target}>
$<INSTALL_INTERFACE:include/xo/${nxo_target}>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> # e.g. for #include "indentlog/scope.hpp"
#$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/${target}> # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED]
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/xo/${target}> # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/xo/${nxo_target}> # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect
#$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include> # e.g. for generated .hpp files
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/xo/${target}> # e.g. for generated .hpp files
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/xo/${nxo_target}> # e.g. for generated .hpp files
)
# ----------------------------------------------------------------
@ -83,12 +103,26 @@ macro(xo_include_headeronly_options2 target)
endif()
endmacro()
macro(xo_include_headeronly_options2 target)
xo_include_headeronly_options5(${target} ${target})
endmacro()
# ----------------------------------------------------------------
# use this to introduce a shared library.
# - has symlink-enabled .hpp install
#
macro(xo_add_shared_library4 target projectTargets targetversion soversion sources)
add_library(${target} SHARED ${sources})
set_property(
TARGET ${target}
PROPERTY xo_deps "${target}")
set_property(
TARGET ${target}
PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR})
set_property(
TARGET ${target}
PROPERTY xo_bindir ${PROJECT_BINARY_DIR})
foreach(arg IN ITEMS ${ARGN})
#message("target=${target}; arg=${arg}")
@ -114,7 +148,7 @@ endmacro()
# OBSOLETE. prefer xo_add_shared_library4()
#
macro(xo_add_shared_library3 target projectTargets targetversion soversion sources)
message(WARNING "obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()")
message(WARNING "${target}: obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()")
add_library(${target} SHARED ${sources})
foreach(arg IN ITEMS ${ARGN})
@ -142,7 +176,7 @@ endmacro()
# OBSOLETE. prefer xo_add_shared_library3()
#
macro(xo_add_shared_library target targetversion soversion sources)
message(WARNING "obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()")
message(WARNING "${target}: obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()")
add_library(${target} SHARED ${sources})
foreach(arg IN ITEMS ${ARGN})
@ -169,9 +203,41 @@ endmacro()
# ----------------------------------------------------------------
# use this for a header-only library
#
macro(xo_add_headeronly_library target)
# e.g.
# - target=xo_pyutil cmake target name for this library
# - nxo_target=pyutil directory for this target under include/xo
#
macro(xo_add_headeronly_library5 target nxo_target)
add_library(${target} INTERFACE)
xo_include_headeronly_options2(${target})
set_property(
TARGET ${target}
PROPERTY xo_deps "${target}")
set_property(
TARGET ${target}
PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR})
set_property(
TARGET ${target}
PROPERTY xo_bindir ${PROJECT_BINARY_DIR})
xo_include_headeronly_options5(${target} ${nxo_target})
endmacro()
macro(xo_add_headeronly_library target)
xo_add_headeronly_library5(${target} ${target})
endmacro()
# ----------------------------------------------------------------
#
# in submodule build each xo codebases cmake files are incorporated
# directly (via add_subdirectory()) into a single umbrella build.
#
# In such case we don't use find_package for xo dependencies
#
macro(xo_establish_submodule_build)
if(NOT DEFINED XO_SUBMODULE_BUILD)
set(XO_SUBMODULE_BUILD False)
endif()
endmacro()
# ----------------------------------------------------------------
@ -179,6 +245,10 @@ endmacro()
# do not use for header-only subsystems; see xo_include_headeronly_options2()
#
macro(xo_include_options2 target)
xo_establish_submodule_build()
#message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}")
# ----------------------------------------------------------------
# PROJECT_SOURCE_DIR:
# so we can for example write
@ -414,6 +484,134 @@ macro(xo_export_cmake_config projectname projectversion projecttargets)
)
endmacro()
# ----------------------------------------------------------------
# helper macro for xo_dependency_helper() see below
#
macro(xo_dependency_helper1 target visibility dep_include_subdir)
target_include_directories(${target} ${visibility}
$<BUILD_INTERFACE:${XO_UMBRELLA_SOURCE_DIR}/${dep_include_subdir}>)
target_include_directories(${target} ${visibility}
$<BUILD_INTERFACE:${XO_UMBRELLA_BINARY_DIR}/${dep_include_subdir}>)
# note: header directories owned by dep get added to target.
# however, header directories _used_ by dep don't get automatically added to target,
# (for example if referred to from a dep-owned .hpp file)
#
endmacro()
# ----------------------------------------------------------------
# dependency helper when depending on an xo codebase
#
# Two flavors:
# - depended-on codebase separately built+installed.
# In this case behaves like any other best-practice cmake dep:
# use find_package() to rely on install .cmake config files
# in PREFIX/lib/cmake
# - depended-on codebase in same umbrella source tree as codebase
# invoking this helper.
# 1. XO_UMBRELLA_SOURCE_DIR gives top of umbrella source tree
# 2. depended-on codebase foo in one of
# XO_UMBRELLA_SOURCE_DIR/repo/{foo, xo-foo}
#
# target: cmake target for which to supply a dependency
# visibility: INTERFACE|PUBLIC
# - INTERFACE must be used when supplying a dependency to a header-only target
# dep: cmake target on which to depend (e.g. xo_pyutil)
# nxo_dep: cmake target without any xo_ prefix. (e.g. pyutil)
#
macro(xo_dependency_helper target visibility dep)
string(REGEX REPLACE "^xo_" "" _nxo_dep ${dep})
string(REGEX REPLACE "^xo-" "" _nxo_dep ${_nxo_dep})
xo_establish_submodule_build()
if(XO_SUBMODULE_BUILD)
if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/xo-${_nxo_dep})
xo_dependency_helper1(${target} ${visibility} repo/xo-${_nxo_dep}/include)
endif()
if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/${_nxo_dep})
xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include)
endif()
else()
find_package(${dep} CONFIG REQUIRED)
endif()
if (XO_SUBMODULE_BUILD)
get_target_property(_tmp ${target} LINK_LIBRARIES)
message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}")
get_target_property(_tmp ${dep} LINK_LIBRARIES)
message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}")
get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES)
message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}")
get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES)
message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}")
# add INCLUDE_DIRECTORIES from ${dep} to ${target}.
#
# 1. we only need this for a submodule build
# 1a. cmake automatically adds ${dep}'s directly-set include directories
# (i.e. attached via target_include_directories(${dep} ..) etc.
# 1b. furthermore, cmake adds transitive deps when we use generated
# cmake support
# 1c. cmake doesn't seem automatically to arrange transitive include directories
# when we have a chain of target_link_libraries() calls.
# (NOTE: it does incorporate include paths from direct dependencies)
# 2. because of 1a, need to exclude ${dep}'s own dirs here (to avoid too-long include path)
#
get_target_property(_depsrcdir ${dep} xo_srcdir)
get_target_property(_depbindir ${dep} xo_bindir)
get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES)
if(_tmp)
message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}")
foreach(dir ${_tmp})
# want to add these to compile line for $target}.
# this will happen automatically for ${dep}'s own directories;
# those will have been added by
# xo_include_options2() / xo_include_headeronly_options2()
# however we also need directories for ${dep}'s transitive dependencies
#
if(${dir} MATCHES "BUILD_INTERFACE")
message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}")
if(${dir} MATCHES ${_depsrcdir})
#message(" skip ${dir}")
elseif(${dir} MATCHES ${_depbindir})
#message(" skip ${dir}")
else()
message(" KEEP ${dir}")
target_include_directories(${target} ${visibility} ${dir})
endif()
endif()
# set_property(
# TARGET ${target}
# APPEND
# PROPERTY INCLUDE_DIRECTORIES ${dir})
endforeach()
get_target_property(_tmp ${target} INTERFACE_INCLUDE_DIRECTORIES)
list(REMOVE_DUPLICATES _tmp)
set_property(
TARGET ${target}
PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${_tmp})
#message("xo_dependency_helper: ${target}.INCLUDE_DIRECTORIES: ${_tmp}")
endif()
endif()
# want ${target}.xo_deps to contain union of previous value, and ${dep}.xo_deps
set_property(
TARGET ${target}
APPEND
PROPERTY xo_deps ${dep})
get_target_property(_tmp ${target} xo_deps)
list(REMOVE_DUPLICATES _tmp)
set_property(
TARGET ${target}
PROPERTY xo_deps ${_tmp})
#list(REMOVE_DUPLICATES _xo_dependency_tmp1)
endmacro()
# ----------------------------------------------------------------
#
# dependency on an xo library (including header-only libraries)
@ -429,37 +627,64 @@ endmacro()
# dep: name of required dependency, e.g. indentlog
#
macro(xo_dependency target dep)
find_package(${dep} CONFIG REQUIRED)
xo_establish_submodule_build()
#message("xo_dependency: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}")
#message("xo_dependency: XO_UMBRELLA_SOURCE_DIR=${XO_UMBRELLA_SOURCE_DIR}")
#message("xo_dependency: PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}")
xo_dependency_helper(${target} PUBLIC ${dep})
message("----------------------------------------------------------------")
#message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}")
#message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}")
get_target_property(_tmp ${target} xo_deps)
message("xo_dependency: ${target}.xo_deps=${_tmp}")
#get_target_property(_tmp ${target} INCLUDE_DIRECTORIES)
#message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}")
target_link_libraries(${target} PUBLIC ${dep})
#target_link_libraries(${target} ${dep})
#get_target_property(_tmp ${target} INCLUDE_DIRECTORIES)
#message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} after target_link_libraries with ${dep}")
#get_target_property(_tmp ${dep} INCLUDE_DIRECTORIES)
#message("xo_dependency: ${dep}.INCLUDE_DIRECTORIES=${_tmp}")
#get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES)
#message("xo_dependency: ${dep}.INTERFACE_INCLUDE_DIRECTORIES=${_tmp}")
message("----------------------------------------------------------------")
endmacro()
# dependency of a header-only library on another header-only library
#
macro(xo_headeronly_dependency target dep)
find_package(${dep} CONFIG REQUIRED)
# Conflict here between PUBLIC and INTERFACE
#
# PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target};
# i.e. will appear in property ${target}.INCLUDE_DIRECTORIES
#
# INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)).
# otherwise get error:
# INTERFACE library can only be used with the INTERFACE keyword of
# target_link_libraries
# Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property
# (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only.
#
# Workaround by copying property explicity, which we do below
#
target_link_libraries(${target} INTERFACE ${dep})
xo_establish_submodule_build()
# get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES)
# set_property(
# TARGET ${target}
# APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp})
xo_dependency_helper(${target} INTERFACE ${dep})
# Conflict here between PUBLIC and INTERFACE
#
# PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target};
# i.e. will appear in property ${target}.INCLUDE_DIRECTORIES
#
# INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)).
# otherwise get error:
# INTERFACE library can only be used with the INTERFACE keyword of
# target_link_libraries
# Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property
# (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only.
#
# Workaround by copying property explicity, which we do below
#
target_link_libraries(${target} INTERFACE ${dep})
# get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES)
# set_property(
# TARGET ${target}
# APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp})
endmacro()
# dependency on namespaced target
# dependency on external (non-xo) namespaced target
# e.g.
# add_library(foo ..) or add_executable(foo ...)
# then
@ -471,6 +696,13 @@ endmacro()
macro(xo_external_target_dependency target pkg pkgtarget)
find_package(${pkg} CONFIG REQUIRED)
target_link_libraries(${target} PUBLIC ${pkgtarget})
#target_link_libraries(${target} ${pkgtarget})
endmacro()
# dependency on external (non-xo) target
#
macro(xo_external_dependency target pkg)
xo_external_target_dependency(${target} ${pkg} ${target})
endmacro()
# dependency on target provided from this codebase.
@ -575,6 +807,16 @@ macro(xo_pybind11_library target projectTargets source_files)
#
pybind11_add_module(${target} MODULE ${source_files})
set_property(
TARGET ${target}
PROPERTY xo_deps "${target}")
set_property(
TARGET ${target}
PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR})
set_property(
TARGET ${target}
PROPERTY xo_bindir ${PROJECT_BINARY_DIR})
xo_pybind11_link_flags()
xo_include_options2(${target})
# don't want to symlink include tree, because lives in build dir.
@ -613,10 +855,26 @@ endmacro()
# -- conceivably true if libfoo.so has RUNPATH etc.
#
macro(xo_pybind11_dependency target dep)
find_package(${dep} CONFIG REQUIRED)
xo_establish_submodule_build()
xo_dependency_helper(${target} PUBLIC ${dep})
# clobber secondary dependencies, as discussed above
set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "")
if(XO_SUBMODULE_BUILD)
# ok to keep dep libraries on link line in submodule build
message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES")
else()
message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES")
set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "")
endif()
# now that secondary deps are gone, attach to target pybind11 library
# skip xo_dependency() here, that would repeat the find_package() expansion
target_link_libraries(${target} PUBLIC ${dep})
endmacro()
# use when one xo pybind library imports another.
# for example
# pyprintjson -> pyreflect
#
macro(xo_pybind11_header_dependency target dep)
xo_dependency_helper(${target} PUBLIC ${dep})
endmacro()