From 35820ef0dadbe75f70617b7c0ee5108f499a1ea7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:29:03 -0400 Subject: [PATCH] xo-cmake: sub-module build w/out relying on cmake external-project --- CMakeLists.txt | 6 +- cmake/{ => xo_macros}/code-coverage.cmake | 0 cmake/{ => xo_macros}/xo_cxx.cmake | 322 +++++++++++++++++++--- 3 files changed, 293 insertions(+), 35 deletions(-) rename cmake/{ => xo_macros}/code-coverage.cmake (100%) rename cmake/{ => xo_macros}/xo_cxx.cmake (64%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c9aa85f..197416e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/cmake/code-coverage.cmake b/cmake/xo_macros/code-coverage.cmake similarity index 100% rename from cmake/code-coverage.cmake rename to cmake/xo_macros/code-coverage.cmake diff --git a/cmake/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake similarity index 64% rename from cmake/xo_cxx.cmake rename to cmake/xo_macros/xo_cxx.cmake index 54072862..2ab628be 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -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 $ - $ + $ $ # e.g. for #include "indentlog/scope.hpp" #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ # 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} + $) + target_include_directories(${target} ${visibility} + $) + + # 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()