panda3d/cmake/macros/PackageConfig.cmake
Sam Edwards d3ef7bf12d CMake: Avoid using continue()
This is, sadly, not added until CMake 3.4.

This commit avoids continue() by restructuring the loops so
that the conditions previously being used for continue() are
no longer needed. The alternative would've been to encase the
loop bodies massive ifs, but I find that to be a poor choice
for readability.
2019-06-25 03:34:51 -06:00

494 lines
15 KiB
CMake

# Filename: PackageConfig.cmake
#
# This module defines functions which find and configure libraries
# and packages for Panda3D.
#
# Assumes an attempt to find the package has already been made with
# find_package(). (i.e. relies on packagename_FOUND variable)
#
# The packages are added as imported/interface libraries in the PKG::
# namespace. If the package is not found (or disabled by the user),
# a dummy package will be created instead. Therefore, it is safe
# to link against the PKG::PACKAGENAME target unconditionally.
#
# Function: package_option
# Usage:
# package_option(package_name package_doc_string
# [DEFAULT ON | OFF]
# [IMPORTED_AS CMake::Imported::Target [...]]
# [FOUND_AS find_name]
# [LICENSE license])
#
# Examples:
# package_option(LIBNAME "Enables LIBNAME support." DEFAULT OFF)
#
# If no default is given, the default in normal
# builds is to enable all found third-party packages.
# In builds for redistribution, there is the additional requirement that
# the package be suitably-licensed.
#
# FOUND_AS indicates the name of the CMake find_package() module, which
# may differ from Panda3D's internal name for that package.
#
# IMPORTED_AS is used to indicate that the find_package() may have
# provided one or more IMPORTED targets, and that if at least one is
# found, the IMPORTED target(s) should be used instead of the
# variables provided by find_package()
#
#
# Function: package_status
# Usage:
# package_status(package_name "Package description" ["Config summary"])
#
# Examples:
# package_status(OpenAL "OpenAL Audio Output")
# package_status(ROCKET "Rocket" "without Python bindings")
#
#
# Function: show_packages
# Usage:
# show_packages()
#
# This prints the package usage report using the information provided in
# calls to package_status above.
#
set(_ALL_PACKAGE_OPTIONS CACHE INTERNAL "Internal variable")
#
# package_option
#
# In order to make sure no third-party licenses are inadvertently violated,
# this imposes a few rules regarding license:
# 1) If there is no license, no special restrictions.
# 2) If there is a license, but the build is not flagged for redistribution,
# no special restrictions.
# 3) If there is a license, and this is for redistribution, the package is
# forcibly defaulted off and must be explicitly enabled, unless the license
# matches a list of licenses suitable for redistribution.
#
function(package_option name)
# Parse the arguments.
set(command)
set(default)
set(found_as "${name}")
set(imported_as)
set(license "")
set(cache_string)
foreach(arg ${ARGN})
if(command STREQUAL "DEFAULT")
set(default "${arg}")
set(command)
elseif(command STREQUAL "FOUND_AS")
set(found_as "${arg}")
set(command)
elseif(command STREQUAL "LICENSE")
set(license "${arg}")
set(command)
elseif(arg STREQUAL "DEFAULT")
set(command "DEFAULT")
elseif(arg STREQUAL "FOUND_AS")
set(command "FOUND_AS")
elseif(arg STREQUAL "LICENSE")
set(command "LICENSE")
elseif(arg STREQUAL "IMPORTED_AS")
set(command "IMPORTED_AS")
elseif(command STREQUAL "IMPORTED_AS")
list(APPEND imported_as "${arg}")
else()
# Yes, a list, because semicolons can be in there, and
# that gets split up into multiple args, so we have to
# join it back together here.
list(APPEND cache_string "${arg}")
endif()
endforeach()
if(command AND NOT command STREQUAL "IMPORTED_AS")
message(SEND_ERROR "${command} in package_option takes an argument")
endif()
# If the default is not set, we set it.
if(NOT DEFINED default)
if(IS_DIST_BUILD)
# Accept things that don't have a configured license
if(license STREQUAL "")
set(default "${${found_as}_FOUND}")
else()
list(FIND PANDA_DIST_USE_LICENSES ${license} license_index)
# If the license isn't in the accept listed, don't use the package
if(${license_index} EQUAL "-1")
set(default OFF)
else()
set(default "${${found_as}_FOUND}")
endif()
endif()
elseif(IS_MINSIZE_BUILD)
set(default OFF)
else()
set(default "${${found_as}_FOUND}")
endif()
endif()
# If it was set by the user but not found, display an error.
if(HAVE_${name} AND NOT ${found_as}_FOUND)
message(SEND_ERROR "NOT FOUND: ${name}. Disable HAVE_${name} to continue.")
endif()
# Prevent the function from being called twice.
# This would indicate a cmake error.
if(";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
message(SEND_ERROR "package_option(${name}) was called twice.
This is a bug in the cmake build scripts.")
else()
list(APPEND _ALL_PACKAGE_OPTIONS "${name}")
set(_ALL_PACKAGE_OPTIONS "${_ALL_PACKAGE_OPTIONS}" CACHE INTERNAL "Internal variable")
endif()
set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
# Create the INTERFACE library used to depend on this package.
add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
# Explicitly record the package's include directories as system include
# directories. CMake does do this automatically for INTERFACE libraries, but
# it does it by discovering all transitive links first, then reading
# INTERFACE_INCLUDE_DIRECTORIES for those which are INTERFACE libraries. So,
# this would be broken for the metalib system (pre CMake 3.12) which doesn't
# "link" the object libraries.
if(CMAKE_VERSION VERSION_LESS "3.12")
set_target_properties(PKG::${name} PROPERTIES
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
"$<TARGET_PROPERTY:PKG::${name},INTERFACE_INCLUDE_DIRECTORIES>")
endif()
# Create the option, and if it actually is enabled, populate the INTERFACE
# library created above
option("HAVE_${name}" "${cache_string}" "${default}")
if(HAVE_${name})
set(use_variables ON)
# This is gross, but we actually want to hide package include directories
# from Interrogate to make sure it relies on parser-inc instead, so we'll
# use some generator expressions to do that.
set(_is_not_interface_lib
"$<NOT:$<STREQUAL:$<TARGET_PROPERTY:TYPE>,INTERFACE_LIBRARY>>")
set(_is_not_interrogate
"$<NOT:$<BOOL:$<${_is_not_interface_lib}:$<TARGET_PROPERTY:IS_INTERROGATE>>>>")
foreach(implib ${imported_as})
if(TARGET ${implib})
# We found one of the implibs, so we don't need to use variables
# (below) anymore
set(use_variables OFF)
# Hide it from Interrogate
target_link_libraries(PKG::${name} INTERFACE
"$<${_is_not_interrogate}:$<TARGET_NAME:${implib}>>")
endif()
endforeach(implib)
if(use_variables)
if(${found_as}_INCLUDE_DIRS)
set(includes ${${found_as}_INCLUDE_DIRS})
else()
set(includes "${${found_as}_INCLUDE_DIR}")
endif()
if(${found_as}_LIBRARIES)
set(libs ${${found_as}_LIBRARIES})
else()
set(libs "${${found_as}_LIBRARY}")
endif()
target_link_libraries(PKG::${name} INTERFACE ${libs})
# Hide it from Interrogate
set_target_properties(PKG::${name} PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "$<${_is_not_interrogate}:${includes}>")
endif()
endif()
endfunction(package_option)
set(_ALL_CONFIG_PACKAGES CACHE INTERNAL "Internal variable")
#
# package_status
#
function(package_status name desc)
set(note "")
foreach(arg ${ARGN})
set(note "${arg}")
endforeach()
if(NOT ";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
message(SEND_ERROR "package_status(${name}) was called before package_option(${name}).
This is a bug in the cmake build scripts.")
return()
endif()
if(";${_ALL_CONFIG_PACKAGES};" MATCHES ";${name};")
message(SEND_ERROR "package_status(${name}) was called twice.
This is a bug in the cmake build scripts.")
else()
list(APPEND _ALL_CONFIG_PACKAGES "${name}")
set(_ALL_CONFIG_PACKAGES "${_ALL_CONFIG_PACKAGES}" CACHE INTERNAL "Internal variable")
endif()
set(PANDA_PACKAGE_DESC_${name} "${desc}" PARENT_SCOPE)
set(PANDA_PACKAGE_NOTE_${name} "${note}" PARENT_SCOPE)
endfunction()
#
# show_packages
#
function(show_packages)
message("")
message("Configuring support for the following optional third-party packages:")
foreach(package ${_ALL_CONFIG_PACKAGES})
set(desc "${PANDA_PACKAGE_DESC_${package}}")
set(note "${PANDA_PACKAGE_NOTE_${package}}")
if(HAVE_${package})
if(NOT note STREQUAL "")
message("+ ${desc} (${note})")
else()
message("+ ${desc}")
endif()
else()
if(NOT ${package}_FOUND)
set(reason "not found")
elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
set(reason "not requested")
else()
set(reason "disabled")
endif()
message("- ${desc} (${reason})")
endif()
endforeach()
endfunction()
#
# export_packages(filename)
#
# Generates an includable CMake file that contains definitions for every PKG::
# package defined.
#
function(export_packages filename)
set(exports "# Exports for Panda3D PKG:: packages\n")
foreach(pkg ${_ALL_PACKAGE_OPTIONS})
string(APPEND exports "\n# Create imported target PKG::${pkg}\n")
string(APPEND exports "add_library(PKG::${pkg} INTERFACE IMPORTED)\n\n")
string(APPEND exports "set_target_properties(PKG::${pkg} PROPERTIES\n")
foreach(prop
INTERFACE_COMPILE_DEFINITIONS
INTERFACE_COMPILE_FEATURES
INTERFACE_COMPILE_OPTIONS
INTERFACE_INCLUDE_DIRECTORIES
INTERFACE_LINK_DEPENDS
INTERFACE_LINK_DIRECTORIES
INTERFACE_LINK_OPTIONS
INTERFACE_POSITION_INDEPENDENT_CODE
#INTERFACE_SYSTEM_INCLUDE_DIRECTORIES # Let the consumer dictate this
INTERFACE_SOURCES)
set(prop_ex "$<TARGET_PROPERTY:PKG::${pkg},${prop}>")
string(APPEND exports "$<$<BOOL:${prop_ex}>: ${prop} \"${prop_ex}\"\n>")
endforeach(prop)
# Ugh, INTERFACE_LINK_LIBRARIES isn't transitive. Fine. Take care of it
# by hand:
set(libraries)
set(stack "PKG::${pkg}")
set(history)
while(stack)
# Remove head item from stack
unset(head)
while(NOT DEFINED head)
if(NOT stack)
break()
endif()
list(GET stack 0 head)
list(REMOVE_AT stack 0)
# Don't visit anything twice
list(FIND history "${head}" _index)
if(_index GREATER -1)
unset(head)
endif()
endwhile()
if(head)
list(APPEND history "${head}")
else()
break()
endif()
# If head isn't a target, add it to `libraries`, else recurse
if(TARGET "${head}")
get_target_property(link_libs "${head}" INTERFACE_LINK_LIBRARIES)
if(link_libs)
list(APPEND stack ${link_libs})
endif()
get_target_property(type "${head}" TYPE)
if(NOT type STREQUAL "INTERFACE_LIBRARY")
get_target_property(imported_location "${head}" IMPORTED_LOCATION)
get_target_property(imported_implib "${head}" IMPORTED_IMPLIB)
if(imported_implib)
list(APPEND libraries ${imported_implib})
elseif(imported_location)
list(APPEND libraries ${imported_location})
endif()
get_target_property(configs "${head}" IMPORTED_CONFIGURATIONS)
if(configs AND NOT imported_location)
foreach(config ${configs})
get_target_property(imported_location "${head}" IMPORTED_LOCATION_${config})
# Prefer IMPORTED_IMPLIB where present
get_target_property(imported_implib "${head}" IMPORTED_IMPLIB_${config})
if(imported_implib)
set(imported_location "${imported_implib}")
endif()
if(imported_location)
if(configs MATCHES ".*;.*")
set(_bling "$<1:$>") # genex-escaped $
list(APPEND libraries "${_bling}<${_bling}<CONFIG:${config}>:${imported_location}>")
else()
list(APPEND libraries ${imported_location})
endif()
endif()
endforeach(config)
endif()
elseif(CMAKE_VERSION VERSION_GREATER "3.8")
# This is an INTERFACE_LIBRARY, and CMake is new enough to support
# IMPORTED_IMPLIB
get_target_property(imported_libname "${head}" IMPORTED_LIBNAME)
if(imported_libname)
list(APPEND libraries ${imported_libname})
endif()
endif()
elseif("${head}" MATCHES "\\$<TARGET_NAME:\([^>]+\)>")
string(REGEX REPLACE ".*\\$<TARGET_NAME:\([^>]+\)>.*" "\\1" match "${head}")
list(APPEND stack "${match}")
else()
list(APPEND libraries "${head}")
endif()
endwhile(stack)
string(APPEND exports " INTERFACE_LINK_LIBRARIES \"${libraries}\"\n")
string(APPEND exports ")\n")
endforeach(pkg)
file(GENERATE OUTPUT "${filename}" CONTENT "${exports}")
endfunction(export_packages)
#
# export_targets(set [NAMESPACE namespace] [COMPONENT component])
#
# Export targets in the export set named by "set"
#
# NAMESPACE overrides the namespace prefixed to the exported targets; it
# defaults to "Panda3D::[set]::" if no explicit override is given.
#
# COMPONENT overrides the install component for the generated .cmake file; it
# defaults to "[set]" if no explicit override is given.
#
function(export_targets set)
set(namespace "Panda3D::${set}::")
set(component "${set}")
set(keyword)
foreach(arg ${ARGN})
if(arg STREQUAL "NAMESPACE" OR
arg STREQUAL "COMPONENT")
set(keyword "${arg}")
elseif(keyword STREQUAL "NAMESPACE")
set(namespace "${arg}")
elseif(keyword STREQUAL "COMPONENT")
set(component "${arg}")
else()
message(FATAL_ERROR "export_targets() given unexpected arg: ${arg}")
endif()
endforeach(arg)
export(EXPORT "${set}" NAMESPACE "${namespace}"
FILE "${PROJECT_BINARY_DIR}/Panda3D${set}Targets.cmake")
install(EXPORT "${set}" NAMESPACE "${namespace}"
FILE "Panda3D${set}Targets.cmake"
COMPONENT "${component}" DESTINATION lib/cmake/Panda3D)
endfunction(export_targets)
#
# find_package
#
# This override is necessary because CMake's default behavior is to run
# find_package in MODULE mode, *then* in CONFIG mode. This is silly! CONFIG
# mode makes more sense to be done first, since any system config file will
# know vastly more about the package's configuration than a module can hope to
# guess.
#
macro(find_package name)
if(";${ARGN};" MATCHES ";(CONFIG|MODULE|NO_MODULE);")
# Caller explicitly asking for a certain mode; so be it.
_find_package(${ARGV})
else()
string(TOUPPER "${name}" __pkgname_upper)
# Try CONFIG
_find_package("${name}" CONFIG ${ARGN})
if(NOT ${name}_FOUND)
# CONFIG didn't work, fall back to MODULE
_find_package("${name}" MODULE ${ARGN})
else()
# Case-sensitivity
set(${__pkgname_upper}_FOUND 1)
endif()
endif()
endmacro(find_package)