Files
geant4/cmake/Modules/G4DeveloperAPI.cmake
T
2026-03-19 16:51:22 +01:00

1529 lines
58 KiB
CMake

#.rst:
# G4DeveloperAPI
# --------------
#
# .. code-block::cmake
#
# include(G4DeveloperAPI)
#
# CMake functions and macros for declaring and working with build
# products of Geant4.
#
#-----------------------------------------------------------------
# License and Disclaimer
#
# The Geant4 software is copyright of the Copyright Holders of
# the Geant4 Collaboration. It is provided under the terms and
# conditions of the Geant4 Software License, included in the file
# LICENSE and available at http://cern.ch/geant4/license . These
# include a list of copyright holders.
#
# Neither the authors of this software system, nor their employing
# institutes,nor the agencies providing financial support for this
# work make any representation or warranty, express or implied,
# regarding this software system or assume any liability for its
# use. Please see the license in the file LICENSE and URL above
# for the full disclaimer and the limitation of liability.
#
# This code implementation is the result of the scientific and
# technical work of the GEANT4 collaboration.
# By using, copying, modifying or distributing the software (or
# any work based on the software) you agree to acknowledge its
# use in resulting scientific publications, and indicate your
# acceptance of all terms of the Geant4 Software license.
#
#-----------------------------------------------------------------
include_guard(DIRECTORY)
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
#.rst:
# Module Commands
# ^^^^^^^^^^^^^^^
#
# .. cmake:command:: geant4_add_module
#
# .. code-block:: cmake
#
# geant4_add_module(<name>
# PUBLIC_HEADERS header1 [header2 ...]
# PRIVATE_HEADERS header1 [header2 ..]
# [SOURCES source1 [source2 ...]])
#
# Add a Geant4 module called ``<name>`` to the project, composed
# of the source files listed in the ``PUBLIC_HEADERS``, ``PRIVATE_HEADERS``,
# and ``SOURCES`` arguments. The ``<name>`` must be unique within the project.
# The directory in which the module is added (i.e. ``CMAKE_CURRENT_LIST_DIR``
# for the CMake script in which ``geant4_add_module`` is called) must contain:
#
# * An ``include`` subdirectory for the public headers
# * A ``include/private`` subdirectory for the private headers
# * A ``src`` subdirectory for source files if the module provides these
#
# Any module with only ``PUBLIC_HEADERS`` has its ``IS_INTERFACE`` property
# set to ``TRUE``. This is for downstream use in composing and building end
# targets appropriately.
#
# The ``PUBLIC_HEADERS`` argument must list the headers comprising the
# public interface of the module. If a header is supplied as a relative path,
# this is interpreted as being relative to the ``include`` subdirectory of the module.
# Absolute paths may also be supplied, e.g. if headers are generated by the project.
#
# The ``PRIVATE_HEADERS`` argument must list the headers comprising any
# interfaces that are implementation details of the module. If a header is supplied
# as a relative path, this is interpreted as being relative to the ``include/private``
# subdirectory of the module. Absolute paths may also be supplied, e.g. if headers
# are generated by the project.
#
# The ``SOURCES`` argument should list any source files for the module.
# If a source is is supplied as a relative path, this is interpreted as being
# relative to the ``src`` subdirectory of the module. Absolute paths may
# also be supplied, e.g. if sources are generated by the project.
#
function(geant4_add_module _name)
__geant4_module_assert_not_exists(${_name})
set_property(GLOBAL APPEND PROPERTY GEANT4_DEFINED_MODULES ${_name})
cmake_parse_arguments(G4ADDMOD
""
""
"PUBLIC_HEADERS;PRIVATE_HEADERS;SOURCES"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4ADDMOD geant4_add_module)
# - Check required directory structure at definition point
# Headers always
if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/include")
message(FATAL_ERROR "Missing required 'include' subdirectory for module '${_name}' at '${CMAKE_CURRENT_LIST_DIR}'")
endif()
# Sources if defined
if(G4ADDMOD_SOURCES AND (NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/src"))
message(FATAL_ERROR "Missing required 'src' subdirectory for module '${_name}' at '${CMAKE_CURRENT_LIST_DIR}'")
endif()
# Record where we're defined so we can pop the file into IDEs
geant4_set_module_property(${_name} PROPERTY CMAKE_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}")
# Compose header/source lists
geant4_module_sources(${_name}
PUBLIC_HEADERS ${G4ADDMOD_PUBLIC_HEADERS}
PRIVATE_HEADERS ${G4ADDMOD_PRIVATE_HEADERS}
SOURCES ${G4ADDMOD_SOURCES})
# Set default usage requirements for this module, dependent on whether it is an INTERFACE or not
# TODO: It's a hard error if a module is INTERFACE and has PRIVATE_HEADERS
if(NOT G4ADDMOD_SOURCES)
geant4_module_include_directories(${_name} INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)
geant4_set_module_property(${_name} PROPERTY IS_INTERFACE TRUE)
else()
# Set the default include directory for this module
# Private header inclusions are, for now, dealt with at the target level (until we require CMake >=3.18)
# - See set_source_files_properties for details
geant4_module_include_directories(${_name} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)
# Some compile definitions are still transported via directory level
# "add_definitions" calls (primarily DLL exports)
# These, if they exist, are equivalent to PRIVATE compile defs for the module (and hence library)
# and hence are only added for compiled modules.
get_directory_property(__local_compile_defs COMPILE_DEFINITIONS)
geant4_module_compile_definitions(${_name} PRIVATE ${__local_compile_defs})
endif()
# Backward compatibility shim for direct usage of build directory
# Not all clients (esp. those using ROOT) may not fully support usage requirements
# and expect GEANT4_INCLUDE_DIRS to be complete
set_property(GLOBAL APPEND PROPERTY GEANT4_BUILDTREE_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/include")
endfunction()
#-----------------------------------------------------------------------
# .. cmake:command:: geant4_module_sources
#
# .. code-block:: cmake
#
# geant4_module_sources(<name>
# PUBLIC_HEADERS header1 [header2 ...]
# PRIVATE_HEADERS header1 [header2 ...]
# [SOURCES source1 [source2 ...]])
#
# Append sources to a Geant4 module called ``<name>``, composed of the source
# files listed in the ``PUBLIC_HEADERS`` and ``SOURCES`` arguments. The module
# ``name`` must have already been created by the ``geant4_add_module`` command.
#
# The requirements on the ``PUBLIC_HEADERS`` and ``SOURCES`` arguments as the same
# as for the ``geant4_add_module`` command. Intended for the use case of modules with
# optional sources dependent on a configuration flag.
#
function(geant4_module_sources _name)
__geant4_module_assert_exists(${_name})
cmake_parse_arguments(G4MODSRCS
""
""
"PUBLIC_HEADERS;PRIVATE_HEADERS;SOURCES"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4MODSRCS geant4_module_sources)
# Restrict calls to the same CMake file as the original add
# Ensures we don't mix up sources/get confused on paths given assumptions made
geant4_get_module_property(__location ${_name} CMAKE_LIST_FILE)
if(NOT ("${__location}" STREQUAL "${CMAKE_CURRENT_LIST_FILE}"))
message(FATAL_ERROR "adding sources to module '${_name}' in '${CMAKE_CURRENT_LIST_FILE}', but module was defined in '${__location}'")
endif()
# Append header/source lists to module
set(__tmp_HEADERS)
foreach(__elem ${G4MODSRCS_PUBLIC_HEADERS})
if(IS_ABSOLUTE "${__elem}")
list(APPEND __tmp_HEADERS "${__elem}")
else()
list(APPEND __tmp_HEADERS "${CMAKE_CURRENT_LIST_DIR}/include/${__elem}")
endif()
endforeach()
geant4_set_module_property(${_name} APPEND PROPERTY PUBLIC_HEADERS ${__tmp_HEADERS})
set(__tmp_HEADERS)
foreach(__elem ${G4MODSRCS_PRIVATE_HEADERS})
if(IS_ABSOLUTE "${__elem}")
list(APPEND __tmp_HEADERS "${__elem}")
else()
list(APPEND __tmp_HEADERS "${CMAKE_CURRENT_LIST_DIR}/include/private/${__elem}")
endif()
endforeach()
geant4_set_module_property(${_name} APPEND PROPERTY PRIVATE_HEADERS ${__tmp_HEADERS})
set(__tmp_SOURCES)
foreach(__elem ${G4MODSRCS_SOURCES})
if(IS_ABSOLUTE "${__elem}")
list(APPEND __tmp_SOURCES "${__elem}")
else()
list(APPEND __tmp_SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/${__elem}")
endif()
endforeach()
geant4_set_module_property(${_name} APPEND PROPERTY SOURCES ${__tmp_SOURCES})
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_module_include_directories
#
# .. code-block:: cmake
#
# geant4_module_include_directories(<module>
# [PUBLIC pub1 [pub2 ...]
# [PRIVATE pri1 [pri2 ...]
# [INTERFACE int1 [int2 ...])
#
# Add include directories to given module. If the module is an ``INTERFACE``,
# i.e. header-only, then only ``INTERFACE`` properties may be set.
#
function(geant4_module_include_directories _module)
__geant4_module_assert_exists(${_module})
cmake_parse_arguments(G4MODINCDIR
""
""
"PUBLIC;PRIVATE;INTERFACE"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4MODINCDIR geant4_module_include_directories)
# An interface module can only have interface requirements set
geant4_get_module_property(__is_interface ${_module} IS_INTERFACE)
if(__is_interface AND (G4MODINCDIR_PUBLIC OR G4MODINCDIR_PRIVATE))
message(FATAL_ERROR "geant4_module_include_directories: may only set INTERFACE properties on header-only module ${_module}")
endif()
foreach(_dir ${G4MODINCDIR_PUBLIC})
geant4_set_module_property(${_module} APPEND PROPERTY PUBLIC_INCLUDE_DIRECTORIES ${_dir})
endforeach()
foreach(_dir ${G4MODINCDIR_PRIVATE})
geant4_set_module_property(${_module} APPEND PROPERTY PRIVATE_INCLUDE_DIRECTORIES ${_dir})
endforeach()
foreach(_dir ${G4MODINCDIR_INTERFACE})
geant4_set_module_property(${_module} APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${_dir})
endforeach()
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_module_link_libraries
#
# .. code-block:: cmake
#
# geant4_module_link_libraries(<module>
# [PUBLIC pub1 [pub2 ...]
# [PRIVATE pri1 [pri2 ...]
# [INTERFACE int1 [int2 ...])
#
# Link ``<module>`` to given targets. If the module is an ``INTERFACE``,
# i.e. header-only, then only ``INTERFACE`` properties may be set.
#
function(geant4_module_link_libraries _module)
__geant4_module_assert_exists(${_module})
cmake_parse_arguments(G4MODLINKLIB
""
""
"PUBLIC;PRIVATE;INTERFACE"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4MODLINKLIB geant4_module_link_libraries)
# An interface module can only have interface requirements set
geant4_get_module_property(__is_interface ${_module} IS_INTERFACE)
if(__is_interface AND (G4MODLINKLIB_PUBLIC OR G4MODLINKLIB_PRIVATE))
message(FATAL_ERROR "geant4_module_link_libraries: may only set INTERFACE properties on header-only module ${_module}")
endif()
foreach(_lib ${G4MODLINKLIB_PUBLIC})
geant4_set_module_property(${_module} APPEND PROPERTY PUBLIC_LINK_LIBRARIES ${_lib})
endforeach()
foreach(_lib ${G4MODLINKLIB_PRIVATE})
geant4_set_module_property(${_module} APPEND PROPERTY PRIVATE_LINK_LIBRARIES ${_lib})
endforeach()
foreach(_lib ${G4MODLINKLIB_INTERFACE})
geant4_set_module_property(${_module} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${_lib})
endforeach()
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_module_compile_definitions
#
# .. code-block:: cmake
#
# geant4_module_compile_definitions(<module>
# [PUBLIC pub1 [pub2 ...]
# [PRIVATE pri1 [pri2 ...]
# [INTERFACE int1 [int2 ...])
#
# Add compile definitions for this module. If the module is an ``INTERFACE``,
# i.e. header-only, then only ``INTERFACE`` properties may be set.
#
# Use cases:
# 1. workarounds when a config header isn't suitable
#
# Needs care with DLLs if used for import/export declspecs
# Application of specific defs to single files not considered
# Expected that uses cases here minimal, and developers should
# in that case use set_source_files_properties or similar
# directly.
#
function(geant4_module_compile_definitions _module)
__geant4_module_assert_exists(${_module})
cmake_parse_arguments(G4MODCOMPILEDEF
""
""
"PUBLIC;PRIVATE;INTERFACE"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4MODCOMPILEDEF geant4_module_compile_definitions)
# An interface module can only have interface requirements set
geant4_get_module_property(__is_interface ${_module} IS_INTERFACE)
if(__is_interface AND (G4MODCOMPILEDEF_PUBLIC OR G4MODCOMPILEDEF_PRIVATE))
message(FATAL_ERROR "geant4_module_compile_definitions: may only set INTERFACE properties on header-only module ${_module}")
endif()
foreach(_def ${G4MODCOMPILEDEF_PUBLIC})
geant4_set_module_property(${_module} APPEND PROPERTY PUBLIC_COMPILE_DEFINITIONS ${_def})
endforeach()
foreach(_def ${G4MODCOMPILEDEF_PRIVATE})
geant4_set_module_property(${_module} APPEND PROPERTY PRIVATE_COMPILE_DEFINITIONS ${_def})
endforeach()
foreach(_def ${G4MODCOMPILEDEF_INTERFACE})
geant4_set_module_property(${_module} APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS ${_def})
endforeach()
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_get_modules
#
# .. code-block:: cmake
#
# geant4_get_modules(<result>)
#
# Store the list of currently defined modules in the variable ``<result>``.
#
function(geant4_get_modules _result)
get_property(__tmp GLOBAL PROPERTY GEANT4_DEFINED_MODULES)
set(${_result} ${__tmp} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_has_module
#
# .. code-block:: cmake
#
# geant4_has_module(<result> <name>)
#
# Set variable ``<result>`` to a boolean which will be true if the module
# ``<name>`` is defined.
#
function(geant4_has_module _result _name)
set(__exists FALSE)
geant4_get_modules(__tmp)
if(__tmp)
list(FIND __tmp ${_name} __index)
if(__index GREATER -1)
set(__exists TRUE)
endif()
endif()
set(${_result} ${__exists} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# Module Properties
# =================
#
# A Geant4 module stores its build and usage requirements in a series
# of properties:
#
# * ``PUBLIC_HEADERS``
# * ``PRIVATE_HEADERS``
# * ``SOURCES``
# * ``PRIVATE_COMPILE_DEFINITIONS``
# * ``PUBLIC_COMPILE_DEFINITIONS``
# * ``INTERFACE_COMPILE_DEFINITIONS``
# * ``PRIVATE_INCLUDE_DIRECTORIES``
# * ``PUBLIC_INCLUDE_DIRECTORIES``
# * ``INTERFACE_INCLUDE_DIRECTORIES``
# * ``PRIVATE_LINK_LIBRARIES``
# * ``PUBLIC_LINK_LIBRARIES``
# * ``INTERFACE_LINK_LIBRARIES``
# * ``PARENT_TARGET``
# * ``CMAKE_LIST_FILE``
# * ``GLOBAL_DEPENDENCIES``
# * ``IS_INTERFACE``
#
# The properties of a module may be queried and set using the following
# commands.
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_get_module_property
#
# .. code-block:: cmake
#
# geant4_get_module_property(<result> <module> <property>)
#
# Store value of property ``<property>`` for ``<module>`` in variable
# ``<result>``.
#
# If ``<property>`` is not a valid module property, a FATAL_ERROR is
# emitted.
#
function(geant4_get_module_property _output _module _propertyname)
__geant4_module_assert_exists(${_module})
__geant4_module_validate_property(${_propertyname})
get_property(__result GLOBAL PROPERTY ${_module}_${_propertyname})
set(${_output} ${__result} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_set_module_property
#
# .. code-block:: cmake
#
# geant4_set_module_property(<module>
# [APPEND | APPEND_STRING]
# PROPERTY <property> <value>)
#
# Set property ``<property>`` of module ``<module>`` to ``<value>``.
#
# If ``APPEND`` is supplied, ``<value>`` will be appended to any existing
# value for the property as a list.
#
# If ``APPEND_STRING`` is supplied, ``<value>`` will be appended to any existing
# value for the property as a string. This option is mutually exclusive with
# ``APPEND``.
#
# If ``<property>`` is not a valid module property, a FATAL_ERROR is
# emitted.
#
function(geant4_set_module_property _module)
__geant4_module_assert_exists(${_module})
cmake_parse_arguments(G4SMP
"APPEND;APPEND_STRING"
""
"PROPERTY"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4SMP geant4_set_module_property)
# Append/Append_string are mutually exclusive
if(G4SMP_APPEND AND G4SMP_APPEND_STRING)
message(FATAL_ERROR "geant4_set_module_property: cannot set both APPEND and APPEND_STRING")
elseif(G4SMP_APPEND)
set(G4SMP_APPEND_MODE "APPEND")
elseif(G4SMP_APPEND_MODE)
set(G4SMP_APPEND_MODE "APPEND_STRING")
endif()
# First element of PROPERTY list is prop name
list(GET G4SMP_PROPERTY 0 G4SMP_PROPERTY_NAME)
if(NOT G4SMP_PROPERTY_NAME)
message(FATAL_ERROR "geant4_set_module_property: Required PROPERTY argument is missing")
endif()
__geant4_module_validate_property(${G4SMP_PROPERTY_NAME})
# Remainder is arguments, so strip first element
list(REMOVE_AT G4SMP_PROPERTY 0)
set_property(GLOBAL ${G4SMP_APPEND_MODE} PROPERTY ${_module}_${G4SMP_PROPERTY_NAME} ${G4SMP_PROPERTY})
endfunction()
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
#.rst:
# Category Commands
# ^^^^^^^^^^^^^^^^^
#
# .. cmake:command:: geant4_add_category
#
# .. code-block:: cmake
#
# geant4_add_category(<name> [INTERFACE] [MODULES <module> [<module> ...]])
#
# Add a Geant4 category ``<name>`` to the project. It can be composed from modules
# supplied in the ``MODULES`` list, or the ``INTERFACE`` option can be used
# to create an alias to one or more other categories.
#
# Calling this function does not create an actual CMake library target.
# Because modules declare dependencies on modules rather than libraries, we
# defer creation of library targets to after creation of categories, which
# allows resolution of module <-> category use. Additionally, category specific
# actions such as install may be added.
#
function(geant4_add_category _name)
__geant4_category_assert_not_exists(${_name})
set_property(GLOBAL APPEND PROPERTY GEANT4_DEFINED_CATEGORIES ${_name})
cmake_parse_arguments(G4ADDCAT
""
""
"INTERFACE;MODULES"
${ARGN}
)
# INTERFACE/MODULES are mutually exclusive
if(G4ADDCAT_INTERFACE AND G4ADDCAT_MODULES)
message(FATAL_ERROR "geant4_add_category: INTERFACE and MODULES arguments are mutually exclusive")
endif()
# They must have value(s) supplied
if(G4ADDCAT_KEYWORDS_MISSING_VALUES MATCHES "INTERFACE")
message(FATAL_ERROR "geant4_add_category: Empty 'INTERFACE' argument")
endif()
if(G4ADDCAT_KEYWORDS_MISSING_VALUES MATCHES "MODULES")
message(FATAL_ERROR "geant4_add_category: Empty 'MODULES' argument")
endif()
# - Have an ALIAS based category
if(G4ADDCAT_INTERFACE)
geant4_set_category_property(${_name} PROPERTY IS_ALIAS TRUE)
geant4_category_interfaces(${_name} ${G4ADDCAT_INTERFACE})
return()
endif()
# - Otherwise, must be a MODULE based category
# Default to an interface module, geant4_category_modules will update this if needed
geant4_set_category_property(${_name} PROPERTY IS_INTERFACE TRUE)
# Compose Category from Modules
geant4_category_modules(${_name} ${G4ADDCAT_MODULES})
# As we do not create a physical target, store the script in which we're called
geant4_set_category_property(${_name} PROPERTY CMAKE_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}")
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_category_is_alias
#
# .. code-block:: cmake
#
# geant4_category_is_alias(<category> <outputvar>)
#
# Set ``outputvar`` to ``TRUE`` if ``<category>`` is an alias to one or
# more others.
#
function(geant4_category_is_alias _outvar _name)
__geant4_category_assert_exists(${_name})
geant4_get_category_property(_result ${_name} IS_ALIAS)
set(${_outvar} ${_result} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_category_modules
#
# .. code-block:: cmake
#
# geant4_category_modules(<category> <module> [<module> ...])
#
# Add modules to category ``<category>``
#
function(geant4_category_modules _name)
__geant4_category_assert_exists(${_name})
geant4_category_is_alias(check ${_name})
if(check)
message(FATAL_ERROR "geant4_category_modules: Trying to add modules to ALIAS-type category '${_name}'")
endif()
# ARGN must not be empty
if(NOT ARGN)
message(FATAL_ERROR "no modules given to add to to category '${_name}'")
endif()
# Need to track if any added module has source files. If so, must set IS_INTERFACE to false
# Compose Category from Modules, accounting for potential interface-only composition
geant4_get_category_property(__cat_is_interface ${_name} IS_INTERFACE)
foreach(__g4module ${ARGN})
# Module must not have been composed already (Might want to mark special case where module added twice into same category)
geant4_get_module_property(_parent ${__g4module} PARENT_TARGET)
if(_parent)
message(FATAL_ERROR "trying to compose category '${_name}' using module '${__g4module}' which is already composed into category '${_parent}'")
endif()
# Compose it
geant4_set_module_property(${__g4module} PROPERTY PARENT_TARGET ${_name})
geant4_set_category_property(${_name} APPEND PROPERTY MODULES ${__g4module})
geant4_get_module_property(_headers ${__g4module} PUBLIC_HEADERS)
geant4_set_category_property(${_name} APPEND PROPERTY PUBLIC_HEADERS ${_headers})
# Is this module interface only?
geant4_get_module_property(_is_interface ${__g4module} IS_INTERFACE)
if(NOT _is_interface)
set(__cat_is_interface FALSE)
endif()
endforeach()
geant4_set_category_property(${_name} PROPERTY IS_INTERFACE ${__cat_is_interface})
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_category_interfaces
#
# .. code-block:: cmake
#
# geant4_category_interfaces(<category> <cat> [<mat> ...]))
#
# Add categories to alias category ``<category>``
#
function(geant4_category_interfaces _name)
__geant4_category_assert_exists(${_name})
geant4_category_is_alias(check ${_name})
if(NOT check)
message(FATAL_ERROR "geant4_category_interfaces: Trying to add categories to non-ALIAS category '${_name}'")
endif()
# ARGN must not be empty
if(NOT ARGN)
message(FATAL_ERROR "no categories given to add to to category '${_name}'")
endif()
foreach(iface ${ARGN})
__geant4_category_assert_exists(${iface})
geant4_set_category_property(${_name} APPEND PROPERTY CATEGORIES ${iface})
endforeach()
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_get_categories
#
# .. code-block:: cmake
#
# geant4_get_categories(<result>)
#
# Store the list of currently defined categories in the variable ``<result>``.
#
function(geant4_get_categories _result)
get_property(__tmp GLOBAL PROPERTY GEANT4_DEFINED_CATEGORIES)
set(${_result} ${__tmp} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_has_category
#
# .. code-block:: cmake
#
# geant4_has_category(<result> <name>)
#
# Set variable ``<result>`` to a boolean which will be true if the category
# ``<name>`` is defined.
#
function(geant4_has_category _result _name)
set(__exists FALSE)
geant4_get_categories(__tmp)
if(__tmp)
list(FIND __tmp ${_name} __index)
if(__index GREATER -1)
set(__exists TRUE)
endif()
endif()
set(${_result} ${__exists} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# Category Properties
# ===================
#
# A Geant4 category stores its build and usage requirements in a series
# of properties:
#
# * ``CMAKE_LIST_FILE``
# * ``IS_INTERFACE``
# * ``MODULES``
# * ``PUBLIC_HEADERS``
# * ``IS_ALIAS``
# * ``CATEGORIES``
#
# The properties of a category may be queried and set using the following
# commands.
#
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_get_category_property
#
# .. code-block:: cmake
#
# geant4_get_category_property(<result> <category> <property>)
#
# Store value of property ``<property>`` for ``<module>`` in variable
# ``<result>``.
#
# If ``<property>`` is not a valid module property, a FATAL_ERROR is
# emitted.
#
function(geant4_get_category_property _output _category _propertyname)
__geant4_category_assert_exists(${_category})
__geant4_category_validate_property(${_propertyname})
get_property(__result GLOBAL PROPERTY GEANT4_CATEGORY_${_category}_${_propertyname})
set(${_output} ${__result} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_set_category_property
#
# .. code-block:: cmake
#
# geant4_set_category_property(<module>
# [APPEND | APPEND_STRING]
# PROPERTY <property> <value>)
#
# Set property ``<property>`` of category ``<category>`` to ``<value>``.
#
# If ``APPEND`` is supplied, ``<value>`` will be appended to any existing
# value for the property as a list.
#
# If ``APPEND_STRING`` is supplied, ``<value>`` will be appended to any existing
# value for the property as a string. This option is mutually exclusive with
# ``APPEND``.
#
# If ``<property>`` is not a valid category property, a FATAL_ERROR is
# emitted.
#
function(geant4_set_category_property _category)
__geant4_category_assert_exists(${_category})
cmake_parse_arguments(G4CMP
"APPEND;APPEND_STRING"
""
"PROPERTY"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4CMP geant4_set_category_property)
# Append/Append_string are mutually exclusive
if(G4CMP_APPEND AND G4CMP_APPEND_STRING)
message(FATAL_ERROR "geant4_set_category_property: cannot set both APPEND and APPEND_STRING")
elseif(G4CMP_APPEND)
set(G4CMP_APPEND_MODE "APPEND")
elseif(G4CMP_APPEND_MODE)
set(G4CMP_APPEND_MODE "APPEND_STRING")
endif()
# First element of PROPERTY list is prop name
list(GET G4CMP_PROPERTY 0 G4CMP_PROPERTY_NAME)
if(NOT G4CMP_PROPERTY_NAME)
message(FATAL_ERROR "geant4_set_category_property: Required PROPERTY argument is missing")
endif()
__geant4_category_validate_property(${G4CMP_PROPERTY_NAME})
# Remainder is arguments, so strip first element
list(REMOVE_AT G4CMP_PROPERTY 0)
set_property(GLOBAL ${G4CMP_APPEND_MODE} PROPERTY GEANT4_CATEGORY_${_category}_${G4CMP_PROPERTY_NAME} ${G4CMP_PROPERTY})
endfunction()
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
#.rst:
# External Library Commands
# ^^^^^^^^^^^^^^^^^^^^^^^^^
#
# Geant4 provides internal copies of libraries that are required to build
# the toolkit:
#
# - CLHEP
# - PTL
# - Expat
# - ZLIB
# - Tools
#
# Because these may have different build systems and source/header structures than
# Geant4, the following commands help adapt their build and use to:
#
# - Provide shared and/or static library targets using the same settings as the
# toolkit libraries (for example, C++ Standard)
# - Use of these targets as other Geant4 libraries to allow consistent full static/full
# shared linking
#
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_add_external_library
#
# .. code-block::
#
# geant4_add_external_library(NAME <name>
# SOURCES source1 [source2 ...])
#
# Maintained for building external targets because we try and reuse their
# upstream code/build layout as far as possible, but want to ensure the
# targets are compiled in same way as toolkit libraries.
#
function(geant4_add_external_library)
cmake_parse_arguments(G4GLOBLIB
""
"NAME"
"SOURCES"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4GLOBLIB geant4_add_external_library)
# Only supported for externals G4clhep/G4expat/G4zlib, so an error if used elsewhere
if(NOT (${G4GLOBLIB_NAME} MATCHES "G4clhep|G4expat|G4zlib"))
message(FATAL_ERROR "geant4_add_external_library called for '${G4GLOBLIB_NAME}' in '${CMAKE_CURRENT_LIST_DIR}'")
endif()
if(BUILD_SHARED_LIBS)
add_library(${G4GLOBLIB_NAME} SHARED ${G4GLOBLIB_SOURCES})
add_library(Geant4::${G4GLOBLIB_NAME} ALIAS ${G4GLOBLIB_NAME})
target_compile_features(${G4GLOBLIB_NAME} PUBLIC ${GEANT4_TARGET_COMPILE_FEATURES})
target_compile_definitions(${G4GLOBLIB_NAME} PUBLIC G4LIB_BUILD_DLL)
target_include_directories(${G4GLOBLIB_NAME}
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
)
set_target_properties(${G4GLOBLIB_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
install(TARGETS ${G4GLOBLIB_NAME}
EXPORT Geant4LibraryDepends
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Runtime
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
endif()
if(BUILD_STATIC_LIBS)
add_library(${G4GLOBLIB_NAME}-static STATIC ${G4GLOBLIB_SOURCES})
add_library(Geant4::${G4GLOBLIB_NAME}-static ALIAS ${G4GLOBLIB_NAME}-static)
target_compile_features(${G4GLOBLIB_NAME}-static PUBLIC ${GEANT4_TARGET_COMPILE_FEATURES})
target_include_directories(${G4GLOBLIB_NAME}-static
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
)
if(NOT WIN32)
set_target_properties(${G4GLOBLIB_NAME}-static PROPERTIES OUTPUT_NAME ${G4GLOBLIB_NAME})
endif()
install(TARGETS ${G4GLOBLIB_NAME}-static
EXPORT Geant4LibraryDepends
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Runtime
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
endif()
# As with a standard library, always add the local include/ directory to exported list
# for scripts
set_property(GLOBAL APPEND PROPERTY GEANT4_BUILDTREE_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/include")
# Needs to be in defined category list for static/shared to be linked correctly.
geant4_add_external_category(${G4GLOBLIB_NAME})
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_add_external_category
#
# .. code-block:: cmake
#
# geant4_add_external_category(<name>)
#
# Mark ``<name>`` as a target that supports the Geant4 target naming
# convention of ``<name>`` as the shared library and ``<name>-static``
# as the static. It allows Geant4 modules to link to targets built
# outside the module/category system and retain consistent shared-shared
# or static-static linking chains.
#
# If ``BUILD_SHARED_LIBS``(``BUILD_STATIC_LIBS``) is ``ON``, then ``<name>``(``<name>-static``)
# must correspond to a shared(static) library target that exists at
# the time this function is called.
#
function(geant4_add_external_category _name)
# Targets must have been defined!
if(BUILD_SHARED_LIBS AND (NOT TARGET ${_name}))
message(FATAL_ERROR "geant4_add_external_category: no target '${_name}' has been declared")
endif()
if(BUILD_STATIC_LIBS AND (NOT TARGET ${_name}-static))
message(FATAL_ERROR "geant4_add_external_category: no target '${_name}-static' has been declared")
endif()
set_property(GLOBAL APPEND PROPERTY GEANT4_EXTERNAL_CATEGORIES ${_name})
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: geant4_get_external_categories
#
# .. code-block:: cmake
#
# geant4_get_external_categories(<result>)
#
# Store the list of currently defined external_categories in the variable ``<result>``.
#
function(geant4_get_external_categories _result)
get_property(__tmp GLOBAL PROPERTY GEANT4_EXTERNAL_CATEGORIES)
set(${_result} ${__tmp} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
#.rst:
# Test Program Commands
# ^^^^^^^^^^^^^^^^^^^^^
# .. cmake:command:: geant4_test_link_libraries
#
# .. code-block:: cmake
#
# geant4_test_link_libraries(<target>
# [PUBLIC pub1 [pub2 ...]
# [PRIVATE pri1 [pri2 ...]
# [INTERFACE int1 [int2 ...])
function(geant4_test_link_libraries _target)
cmake_parse_arguments(G4TESTLINKLIB
""
""
"PUBLIC;PRIVATE;INTERFACE"
${ARGN}
)
__geant4_assert_no_unparsed_arguments(G4TESTLINKLIB geant4_test_link_libraries)
# Need defined libraries and externals to be able to resolve between static/shared
get_property(__g4definedlibraries GLOBAL PROPERTY GEANT4_DEFINED_CATEGORIES)
geant4_get_external_categories(__g4externalcategories)
list(APPEND __g4definedlibraries ${__g4externalcategories})
foreach(__prop PUBLIC PRIVATE INTERFACE)
__geant4_resolve_link_libraries(G4TESTLINKLIB_${__prop})
if(G4TESTLINKLIB_${__prop})
# Filter list for internal static targets
# NB: This only works assuming that the input target is an executable
# If we introduce test libraries, would need same treatment as for main libraries
if(BUILD_STATIC_LIBS AND NOT BUILD_SHARED_LIBS)
set(_g4linklibs )
foreach(_linklib ${G4TESTLINKLIB_${__prop}})
# If the linklib is a G4Library, change name to "name-static"
list(FIND __g4definedlibraries ${_linklib} _isg4lib)
if(_isg4lib GREATER -1)
list(APPEND _g4linklibs "${_linklib}-static")
else()
list(APPEND _g4linklibs "${_linklib}")
endif()
endforeach()
set(_linklibs ${_g4linklibs})
else()
set(_linklibs ${G4TESTLINKLIB_${__prop}})
endif()
target_link_libraries(${_target} ${__prop} ${_linklibs})
endif()
endforeach()
endfunction()
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# Composition Functions
#.rst:
# .. cmake:command:: geant4_compose_targets
#
# .. code-block:: cmake
#
# geant4_compose_targets()
#
# Create physical SHARED/STATIC library targets from the defined Geant4
# categories and modules.
#
# Can only be called once, and must be done so after all Geant4 libraries
# and modules are defined.
#
function(geant4_compose_targets)
get_property(__alreadyCalled GLOBAL PROPERTY GEANT4_COMPOSE_TARGETS_CALLED)
if(__alreadyCalled)
get_property(__callsite GLOBAL PROPERTY GEANT4_COMPOSE_TARGETS_LIST_FILE)
message(FATAL_ERROR "geant4_compose_targets already called from ${__callsite}")
endif()
# Check that every defined module is composed
geant4_get_modules(__g4definedmodules)
foreach(__module ${__g4definedmodules})
geant4_get_module_property(__iscomposed ${__module} PARENT_TARGET)
if(NOT __iscomposed)
message(FATAL_ERROR "Geant4 module '${__module}' is not composed into any category")
endif()
endforeach()
# - For each module
# 1. Write out files for
# 1.1. module -> used modules adjacency list for detecting module-module cycles
file(WRITE "${PROJECT_BINARY_DIR}/G4ModuleAdjacencyList.txt" "# Geant4 Module - Module Adjacencies\n")
# 1.2. module,location,public headers for developer query operations
# Basically need a dict of module : [headers, ...]
# - Two main queries to run
# - Given a module, what public headers does it provide?
# - Given a header, what module provides this?
# Also want something to check a module
# - Only really possible by parsing module headers (what it exposes publically)
# and sources (what it uses internally)
# Probaly just needs module : location
# - Checking of files is runtime operation, so will refind/check each time
# - Store set of build settings relevant to filtering headers in dependency checks
# - "Dumb" as simply reproduced as CMake list in last column of every row (de-dupped in script)
# - Settings are designed to overcome limitation of ifdef in parsing, so map
# to symbols in code
set(__gmc_build_settings )
if(GEANT4_BUILD_MULTITHREADED)
list(APPEND __gmc_build_settings "G4MULTITHREADED")
endif()
file(WRITE "${PROJECT_BINARY_DIR}/G4ModuleInterfaceMap.csv" "")
configure_file("${PROJECT_SOURCE_DIR}/cmake/Modules/geant4_module_check.py" "${PROJECT_BINARY_DIR}/geant4_module_check.py" COPYONLY)
# 2. Check it does not link to a composed library composed of N>1 modules
get_property(__g4definedlibraries GLOBAL PROPERTY GEANT4_DEFINED_CATEGORIES)
set(__g4disallowedlinks ${__g4definedlibraries})
list(REMOVE_ITEM __g4disallowedlinks ${__g4definedmodules})
foreach(__module ${__g4definedmodules})
geant4_get_module_property(__publicdeps ${__module} PUBLIC_LINK_LIBRARIES)
geant4_get_module_property(__privatedeps ${__module} PRIVATE_LINK_LIBRARIES)
geant4_get_module_property(__interfacedeps ${__module} INTERFACE_LINK_LIBRARIES)
# 1.1 Adjacency list - take all dependencies
set(__alldeps_l ${__publicdeps} ${__privatedeps} ${__interfacedeps})
list(JOIN __alldeps_l " " __alldeps)
file(APPEND "${PROJECT_BINARY_DIR}/G4ModuleAdjacencyList.txt" "${__module} ${__alldeps}\n")
# 1.2 Module interfaces, needs CMAKE, PUBLIC_HEADER
geant4_get_module_property(__listfile ${__module} CMAKE_LIST_FILE)
geant4_get_module_property(__publichdrs ${__module} PUBLIC_HEADERS)
geant4_get_module_property(__privatehdrs ${__module} PRIVATE_HEADERS)
geant4_get_module_property(__srcs ${__module} SOURCES)
geant4_get_module_property(__parent_target ${__module} PARENT_TARGET)
get_filename_component(__listdir "${__listfile}" DIRECTORY)
# Remove generated headers
list(TRANSFORM __publichdrs REPLACE "^${PROJECT_BINARY_DIR}/.*$" "")
list(TRANSFORM __publichdrs REPLACE "^/.*/" "")
list(TRANSFORM __privatehdrs REPLACE "^/.*/" "")
list(TRANSFORM __srcs REPLACE "^/.*/" "")
file(APPEND "${PROJECT_BINARY_DIR}/G4ModuleInterfaceMap.csv" "${__module},${__listdir},${__publichdrs},${__privatehdrs},${__srcs},${__publicdeps},${__privatedeps},${__interfacedeps},${__parent_target},${__gmc_build_settings}\n")
# 2. Check for disallowed links in each link type
foreach(__linktype IN ITEMS public private interface)
foreach(__link ${__${__linktype}deps})
if(__link IN_LIST __g4disallowedlinks)
geant4_get_category_property(__linktothese ${__link} MODULES)
string(REPLACE "${PROJECT_SOURCE_DIR}/" "" __badlistfile ${__listfile})
message(FATAL_ERROR "Geant4 module '${__module}' has a ${__linktype} link to composed category '${__link}'."
"It must link to one or more of its component modules instead:\n"
"${__linktothese}\n"
"in ${__badlistfile}\n")
endif()
endforeach()
endforeach()
endforeach()
# Process all defined libraries
set(__g4builtlibraries)
set(__g4public_headers)
foreach(__g4lib ${__g4definedlibraries})
if(BUILD_SHARED_LIBS)
__geant4_add_library(${__g4lib} SHARED)
list(APPEND __g4builtlibraries ${__g4lib})
endif()
if(BUILD_STATIC_LIBS)
__geant4_add_library(${__g4lib} STATIC)
list(APPEND __g4builtlibraries ${__g4lib}-static)
endif()
geant4_get_category_property(__headers ${__g4lib} PUBLIC_HEADERS)
list(APPEND __g4public_headers ${__headers})
endforeach()
#-----------------------------------------------------------------------
# TEMP INSTALL - do here purely to review exported links. Should be
# factored out into separate function later.
install(TARGETS ${__g4builtlibraries}
EXPORT Geant4LibraryDepends
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT Development
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT Runtime
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT Runtime
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}")
install(FILES ${__g4public_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}" COMPONENT Development)
set_property(GLOBAL PROPERTY GEANT4_COMPOSE_TARGETS_CALLED ON)
set_property(GLOBAL PROPERTY GEANT4_COMPOSE_TARGETS_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}")
endfunction()
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
#.rst:
# Internal Helper Commands
# ^^^^^^^^^^^^^^^^^^^^^^^^
#
# These macros and functions are for use in the implementation of the
# module and library functions. They should never be used directly
# in developer-level scripts.
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_assert_no_unparsed_arguments
#
# .. code-block:: cmake
#
# __geant4_assert_no_unparsed_arguments(<prefix> <function>)
#
# Emit a ``FATAL_ERROR`` if ``<prefix>_UNPARSED_ARGUMENTS`` is non-empty
# in ``<function>``
#
# This is a macro intended for use in G4DeveloperAPI functions that cannot
# have unparsed arguments to validate their input.
#
# From CMake 3.17, the ``<function>`` argument is no longer required and
# ``CMAKE_CURRENT_FUNCTION`` can be used
#
macro(__geant4_assert_no_unparsed_arguments _prefix _function)
if(${_prefix}_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "${_function} called with unparsed arguments: '${${_prefix}_UNPARSED_ARGUMENTS}'")
endif()
endmacro()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_module_assert_exists
#
# .. code-block:: cmake
#
# __geant4_module_assert_exists(<name>)
#
# Emit a ``FATAL_ERROR`` if the module ``<name>`` is not defined.
#
# This is a macro intended for use in G4DeveloperAPI functions when
# the existence of a module is required for further processing
#
macro(__geant4_module_assert_exists _module)
geant4_has_module(__geant4_module_assert_exists_tmp ${_module})
if(NOT __geant4_module_assert_exists_tmp)
message(FATAL_ERROR "Geant4 module '${_module}' has not been created")
endif()
endmacro()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_module_assert_not_exists
#
# .. code-block:: cmake
#
# __geant4_module_assert_not_exists(<name>)
#
# Emit a ``FATAL_ERROR`` if the module ``<name>`` is defined
#
# This is a macro intended for use in G4DeveloperAPI functions when
# the non-existence of a module is required for further processing
#
macro(__geant4_module_assert_not_exists _module)
geant4_has_module(__geant4_module_assert_not_exists_tmp ${_module})
if(__geant4_module_assert_not_exists_tmp)
geant4_get_module_property(__previous_cmake_list ${_module} CMAKE_LIST_FILE)
message(FATAL_ERROR "Geant4 module '${_module}' has already been created by call in '${__previous_cmake_list}'")
endif()
endmacro()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_module_validate_property
#
# .. code-block:: cmake
#
# __geant4_module_validate_property(<property>)
#
# Emit a ``FATAL_ERROR`` if the ``<property>`` is not one of the valid
# properties for a module:
#
# This is used internally by the property get/set functions.
#
function(__geant4_module_validate_property _property)
if(NOT (${_property} MATCHES "PUBLIC_HEADERS|PRIVATE_HEADERS|SOURCES|PRIVATE_COMPILE_DEFINITIONS|PUBLIC_COMPILE_DEFINITIONS|INTERFACE_COMPILE_DEFINITIONS|PRIVATE_INCLUDE_DIRECTORIES|PUBLIC_INCLUDE_DIRECTORIES|INTERFACE_INCLUDE_DIRECTORIES|PRIVATE_LINK_LIBRARIES|PUBLIC_LINK_LIBRARIES|INTERFACE_LINK_LIBRARIES|PARENT_TARGET|CMAKE_LIST_FILE|GLOBAL_DEPENDENCIES|IS_INTERFACE|AUTOMOC"))
message(FATAL_ERROR "Undefined property '${_property}'")
endif()
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_category_assert_exists
#
# .. code-block:: cmake
#
# __geant4_category_assert_exists(<name>)
#
# Emit a ``FATAL_ERROR`` if the category ``<name>`` is not defined.
#
# This is a macro intended for use in G4DeveloperAPI functions when
# the existence of a category is required for further processing
#
macro(__geant4_category_assert_exists _category)
geant4_has_category(__geant4_category_assert_exists_tmp ${_category})
if(NOT __geant4_category_assert_exists_tmp)
message(FATAL_ERROR "Geant4 category '${_category}' has not been created")
endif()
endmacro()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_category_assert_not_exists
#
# .. code-block:: cmake
#
# __geant4_module_category_not_exists(<name>)
#
# Emit a ``FATAL_ERROR`` if the category ``<name>`` is defined
#
# This is a macro intended for use in G4DeveloperAPI functions when
# the non-existence of a category is required for further processing
#
macro(__geant4_category_assert_not_exists _category)
geant4_has_category(__geant4_category_assert_not_exists_tmp ${_category})
if(__geant4_category_assert_not_exists_tmp)
geant4_get_category_property(__previous_cmake_list ${_category} CMAKE_LIST_FILE)
message(FATAL_ERROR "Geant4 category '${_category}' has already been created by call in '${__previous_cmake_list}'")
endif()
endmacro()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_category_validate_property
#
# .. code-block:: cmake
#
# __geant4_category_validate_property(<property>)
#
# Emit a ``FATAL_ERROR`` if the ``<property>`` is not one of the valid
# properties for a category.
#
# This is used internally by the property get/set functions.
#
function(__geant4_category_validate_property _property)
if(NOT (${_property} MATCHES "CMAKE_LIST_FILE|IS_INTERFACE|MODULES|PUBLIC_HEADERS|DEFINE_SYMBOL|IS_ALIAS|CATEGORIES"))
message(FATAL_ERROR "Undefined property '${_property}'")
endif()
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_category_reset
#
# .. code-block:: cmake
#
# __geant4_category_reset()
#
# Reset all existing categories to allow redefinition.
#
function(__geant4_category_reset)
# Reset parent targets of modules to allow recomposition
geant4_get_modules(__all_modules)
foreach(__mod ${__all_modules})
geant4_set_module_property(${__mod} PROPERTY PARENT_TARGET)
endforeach()
# Reset category properties, then categories
geant4_get_categories(__all_categories)
foreach(__cat ${__all_categories})
foreach(_prop CMAKE_LIST_FILE IS_INTERFACE MODULES PUBLIC_HEADERS DEFINE_SYMBOL IS_ALIAS CATEGORIES)
geant4_set_category_property(${__cat} PROPERTY ${_prop})
endforeach()
endforeach()
set_property(GLOBAL PROPERTY GEANT4_DEFINED_CATEGORIES)
endfunction()
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# Library Build functions
#
# Resolve a list of links
# These may include Geant4 modules as well as standard targets or other expressions
# Resolve Modules to PARENT_TARGET, removing duplicates
# Leave other links unchanged
function(__geant4_resolve_link_libraries _list)
set(_resolved_list )
foreach(__lib ${${_list}})
# If "library" is a module, resolve it to PARENT_TARGET
geant4_has_module(__is_module ${__lib})
if(__is_module)
geant4_get_module_property(__parent_lib ${__lib} PARENT_TARGET)
if(NOT __parent_lib)
message(FATAL_ERROR "Module '${__lib}' has no PARENT_TARGET set")
endif()
list(APPEND _resolved_list ${__parent_lib})
else()
list(APPEND _resolved_list ${__lib})
endif()
endforeach()
if(_resolved_list)
list(REMOVE_DUPLICATES _resolved_list)
endif()
set(${_list} ${_resolved_list} PARENT_SCOPE)
endfunction()
#-----------------------------------------------------------------------
#.rst:
# .. cmake:command:: __geant4_add_library
#
# .. code-block:: cmake
#
# __geant4_add_library(<libraryname> <mode>)
#
# Declare an actual CMake library target of type ``<mode>`` from a category
# ``<libraryname>``. The category must have been declared previously, and
# ``<mode>`` must be ``SHARED`` or ``STATIC``.
# In the special case that the category is header-only, ``<mode>`` is only
# used to determine the name of the created target: ``<libraryname>`` for
# ``SHARED``, and ``<libraryname>-static`` otherwise. This ensures consistent
# link chains of only shared or only static Geant4 libraries.
#
# This function is used internally by the ``geant4_compose_targets`` command
# and should not be called directly in developer build scripts.
#
function(__geant4_add_library _name _type)
if(NOT (${_type} MATCHES "SHARED|STATIC"))
message(FATAL_ERROR "Invalid library type '${_type}'")
endif()
# Check if the overall library is binary, header-only, or an alias (really an interface in CMake terms)
set(_lib_cmake_type ${_type})
geant4_get_category_property(_lib_is_interface ${_name} IS_INTERFACE)
geant4_get_category_property(_lib_is_alias ${_name} IS_ALIAS)
if(_lib_is_interface OR _lib_is_alias)
set(_lib_cmake_type "INTERFACE")
endif()
set(_target_name ${_name})
if(_type STREQUAL "STATIC")
set(_target_name ${_name}-static)
endif()
# - General target creation/properties
# Conditional on INTERFACE as CMake < 3.19 cannot create interface lib with sources
if(_lib_cmake_type STREQUAL "INTERFACE")
add_library(${_target_name} ${_lib_cmake_type})
else()
add_library(${_target_name} ${_lib_cmake_type} "")
endif()
# Alias for transparent use with imported targets
add_library(Geant4::${_target_name} ALIAS ${_target_name})
# - Determine what properties to process
if(_lib_cmake_type STREQUAL "INTERFACE")
target_compile_features(${_target_name} INTERFACE ${GEANT4_TARGET_COMPILE_FEATURES})
set(_props_to_process "INTERFACE")
# Shortcut: If this category is also an "alias", we just extract the link interface directly
if(_lib_is_alias)
geant4_get_category_property(iface ${_name} CATEGORIES)
if(_type STREQUAL "STATIC")
list(TRANSFORM iface APPEND "-static")
endif()
target_link_libraries(${_target_name} INTERFACE ${iface})
return()
endif()
else()
target_compile_features(${_target_name} PUBLIC ${GEANT4_TARGET_COMPILE_FEATURES})
set(_props_to_process "PUBLIC" "PRIVATE" "INTERFACE")
set(_promote_interface_to_public TRUE)
endif()
if(_lib_cmake_type STREQUAL "SHARED")
# G4LIB_BUILD_DLL is public as despite the name it indicates the shared/archive mode
# and clients must apply it when linking to the shared libs. The global
# category handles the exact import/export statements
target_compile_definitions(${_target_name} PUBLIC G4LIB_BUILD_DLL)
set_target_properties(${_target_name} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
# Check if we have a DEFINE_SYMBOL for this category
geant4_get_category_property(_lib_define_symbol ${_name} DEFINE_SYMBOL)
if(_lib_define_symbol)
set_target_properties(${_target_name} PROPERTIES DEFINE_SYMBOL ${_lib_define_symbol})
endif()
# MacOS
# Use '@rpath' in install names of libraries on macOS to provide relocatibility
# Add '@loader_path' to INSTALL_RPATH on macOS so that Geant4
# libraries self-locate each other whilst remaining relocatable
set_target_properties(${_target_name} PROPERTIES MACOSX_RPATH 1)
if(APPLE)
set_property(TARGET ${_target_name} APPEND PROPERTY INSTALL_RPATH "@loader_path")
endif()
endif()
if((_lib_cmake_type STREQUAL "STATIC") AND NOT WIN32)
set_target_properties(${_target_name} PROPERTIES OUTPUT_NAME ${_name})
endif()
# Get Modules to build into library, and list of defined interna/external libraries to resolve/link against
geant4_get_categories(__g4definedlibraries)
geant4_get_external_categories(__g4externallibraries)
list(APPEND __g4definedlibraries ${__g4externallibraries})
geant4_get_category_property(__g4modules ${_name} MODULES)
foreach(__g4mod ${__g4modules})
# - Process sources...
geant4_get_module_property(_headers ${__g4mod} PUBLIC_HEADERS)
geant4_get_module_property(_headers_private ${__g4mod} PRIVATE_HEADERS)
geant4_get_module_property(_srcs ${__g4mod} SOURCES)
geant4_get_module_property(_cmakescript ${__g4mod} CMAKE_LIST_FILE)
get_filename_component(_moddir "${_cmakescript}" DIRECTORY)
# - Group sources and scripts for IDEs
# NB: Seemingly has to be done at same level we define target.
# TODO: If lib name is same as mod name, don't group avoid extra
# folder layer.
source_group(${__g4mod}\\Headers FILES ${_headers} ${_headers_private})
source_group(${__g4mod}\\Sources FILES ${_srcs})
source_group(${__g4mod} FILES ${_cmakescript})
# - Add sources to target - PRIVATE, because consuming targets don't need them
target_sources(${_target_name} PRIVATE ${_headers} ${_headers_private} ${_srcs} ${_cmakescript})
# - Process usage properties
# Include dirs, compile definitions and libraries can be handled together
# Important to note that these promote PUBLIC/PRIVATE/INTERFACE
# from module level to library level. I.e. other modules in the
# library can see each other's PRIVATE include paths/compile defs.
# The only known way to fully wall things off is OBJECT libs,
# but then run into issues mentioned at start - linking and
# use in IDEs (though newer CMake versions should resolve these)
# This "promotion" is probably correct though - interfaces are
# at physical library level rather than module, and in Geant4
# all files must have globally unique names (no nested headers
# nor namespaces). Also, DLL export symbols may need this
# behaviour (esp. ALLOC_EXPORT).
# Only really an issue if header names/definitions aren't
# globally (in Geant4) unique. Or if a module is moved and hasn't
# declared its deps correctly (but then an error will occur
# anyway, and point is that libs are linked, not modules!)
# "Module" level really means "Source file" level, so same
# sets of rules should apply.
# Can use set_source_files_properties for module-level PRIVATE_HEADERS,
# but that's it
set_property(SOURCE ${_srcs} APPEND PROPERTY INCLUDE_DIRECTORIES "${_moddir}/include/private")
foreach(_prop ${_props_to_process})
# Further gotcha with INTERFACE modules here
# - If an interface module is composed into a non-interface module
# its headers are exposed correctly to *clients* of the library, but
# *not* the internals of the library! This being the case, we promote
# these properties to PUBLIC
set(_target_prop ${_prop})
if(_promote_interface_to_public AND (_prop STREQUAL "INTERFACE"))
set(_target_prop "PUBLIC")
endif()
geant4_get_module_property(_incdirs ${__g4mod} ${_prop}_INCLUDE_DIRECTORIES)
target_include_directories(${_target_name} ${_target_prop} ${_incdirs})
geant4_get_module_property(_defs ${__g4mod} ${_prop}_COMPILE_DEFINITIONS)
target_compile_definitions(${_target_name} ${_target_prop} ${_defs})
# Target linking requires additional processing to resolve
geant4_get_module_property(_linklibs ${__g4mod} ${_prop}_LINK_LIBRARIES)
__geant4_resolve_link_libraries(_linklibs)
if(_linklibs)
# Remove self-linking
list(REMOVE_ITEM _linklibs ${_name})
# Filter list for internal static targets
if(_lib_cmake_type STREQUAL "STATIC")
set(_g4linklibs )
foreach(_linklib ${_linklibs})
# If the linklib is a G4Library, change name to "name-static"
list(FIND __g4definedlibraries ${_linklib} _isg4lib)
if(_isg4lib GREATER -1)
list(APPEND _g4linklibs "${_linklib}-static")
else()
list(APPEND _g4linklibs "${_linklib}")
endif()
endforeach()
set(_linklibs ${_g4linklibs})
endif()
target_link_libraries(${_target_name} ${_target_prop} ${_linklibs})
endif()
endforeach()
# Apply any additional properties supported in modules to target
if(GEANT4_USE_QT)
geant4_get_module_property(_needs_moc ${__g4mod} AUTOMOC)
if(_needs_moc)
set_target_properties(${_target_name} PROPERTIES AUTOMOC ON)
endif()
endif()
endforeach()
# - Postprocess target properties to remove duplicates
# NB: This makes the assumption that there is no order dependence here (and any is considered a bug!)
# CMake will handle static link ordering internally
foreach(_link_prop IN ITEMS LINK_LIBRARIES INTERFACE_LINK_LIBRARIES INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES COMPILE_DEFINITIONS INTERFACE_COMPILE_DEFINITIONS)
get_target_property(__g4lib_link_libs ${_target_name} ${_link_prop})
if(__g4lib_link_libs)
list(SORT __g4lib_link_libs)
list(REMOVE_DUPLICATES __g4lib_link_libs)
set_property(TARGET ${_target_name} PROPERTY ${_link_prop} ${__g4lib_link_libs})
endif()
endforeach()
endfunction()