cdk/cmake/dependency.cmake (437 lines of code) (raw):
# Copyright (c) 2008, 2024, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0, as
# published by the Free Software Foundation.
#
# This program is designed to work with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation. The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have either included with
# the program or referenced in the documentation.
#
# Without limiting anything contained in the foregoing, this file,
# which is part of Connector/C++, is also subject to the
# Universal FOSS Exception, version 1.0, a copy of which can be found at
# https://oss.oracle.com/licenses/universal-foss-exception.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include(CMakeParseArguments)
include(ProcessorCount)
include(config_options)
##########################################################################
#
# find_dependency(XXX) command.
#
# Currently it is looking for DepFindXXX.cmake script that should perform
# all steps required to locate given dependency and make it available in
# the project. It is a layer on top of find_module().
#
function(find_dependency NAME)
# TODO: Fallback to find_module()
include(DepFind${NAME})
endfunction(find_dependency)
# Variables to forward from this build configuration to external
# build configuration
# TODO: complete the list
set(EXT_FWD
CMAKE_BUILD_TYPE
CMAKE_SYSTEM_NAME CMAKE_SYSTEM_VERSION
CMAKE_SYSTEM_PROCESSOR
CMAKE_C_COMPILER CMAKE_CXX_COMPILER
MSVC
WERROR
)
set(EXT_DIR ${CMAKE_CURRENT_LIST_DIR}/ext CACHE INTERNAL "external project utils location")
##########################################################################
#
# add_ext(name header [tgt_name tgt])
#
# Add an external dependency (such as a 3rd party library) with name `name`.
# There are two cases (where NNN is the specified name) :
#
# 1. Either location of the dependency is given using WTTH_NNN or related
# options (see below), or
#
# 2. Dependency is built from bundled sources located at extra/NNN in the source
# tree if no options are specified.
#
# In case 1 parameter `header` names a file to be located at the specified
# dependency location. In case 2 the `header` parameter is not used. Instead
# targets NNN-build/-rebuild are created that will be used to build the bundled
# sources before the dependency can be used. Invoking these (re)build targets
# is arranged later when `add_ext_targets()` is called (see below).
#
# Variable `NNN_FOUND` is set to true/false to indicate if dependency was
# correctly located/configurred.
#
# To use the dependency in the project some targets need to be created for it
# using `add_ext_targets()`.
#
# If `tgt_name` and `tgt` parameters are given then it is assumed that the
# dependency is a single library and the target is created for it already here
# by a call to `add_ext_targets(name LIBRARY tgt_name tgt)`.
#
function(add_ext NAME HEADER)
string(TOUPPER ${NAME} EXT_LIB)
# TODO: resolve WITH_X in case it is a path
add_config_option(WITH_${EXT_LIB} STRING "Enable, disable or point to ${EXT_LIB} installation.")
# TODO: use option aliases for WITH_X (need to port it from CDK)
add_config_option(${EXT_LIB}_DIR PATH ADVANCED
"Path to ${EXT_LIB} instalation dir."
)
add_config_option(${EXT_LIB}_ROOT_DIR PATH ADVANCED
"Path to ${EXT_LIB} instalation dir."
)
add_config_option(${EXT_LIB}_INCLUDE_DIR PATH ADVANCED
"Path to ${EXT_LIB} include directory."
)
add_config_option(${EXT_LIB}_LIB_DIR PATH ADVANCED
"Path to ${EXT_LIB} lib directory."
)
add_config_option(${EXT_LIB}_BIN_DIR PATH ADVANCED
"Path to ${EXT_LIB} bin directory."
)
add_config_option(${EXT_LIB}_LIBRARY STRING ADVANCED
"${EXT_LIB} library name"
)
#show_config_options()
# Handle non-path value of WITH_X option
if(DEFINED WITH_${EXT_LIB})
string(TOUPPER "${WITH_${EXT_LIB}}" with_val)
if(with_val MATCHES "^SYSTEM$")
set(use_system 1)
elseif(with_val MATCHES "^(YES|ON|1|TRUE)$")
set(use_bundled 1)
elseif(with_val MATCHES "^(NO|OFF|0|FALSE)")
message(FATAL_ERROR "Dependency ${EXT_LIB} is required.")
endif()
endif()
# Set X_ROOT_DIR if specified (monolithic install)
if(DEFINED WITH_${EXT_LIB})
if(DEFINED ${EXT_LIB}_ROOT_DIR)
message(FATAL_ERROR "Can't specify both WITH_${EXT_LIB} and ${EXT_LIB}_ROOT_DIR")
endif()
if(NOT use_system AND NOT use_bundled)
set(${EXT_LIB}_ROOT_DIR "${WITH_${EXT_LIB}}")
endif()
elseif(DEFINED ${EXT_LIB}_DIR)
if(DEFINED ${EXT_LIB}_ROOT_DIR)
message(FATAL_ERROR "Can't specify both ${NAME}_DIR and ${NAME}_ROOT_DIR")
endif()
set(${EXT_LIB}_ROOT_DIR "${${EXT_LIB}_DIR}")
endif()
if(DEFINED ${EXT_LIB}_ROOT_DIR)
set(${EXT_LIB}_ROOT_DIR "${${EXT_LIB}_ROOT_DIR}" PARENT_SCOPE)
#message(STATUS "!!! _ROOT_DIR: ${${EXT_LIB}_ROOT_DIR}")
if(
DEFINED ${EXT_LIB}_INCLUDE_DIR OR
DEFINED ${EXT_LIB}_LIB_DIR OR
DEFINED ${EXT_LIB}_LIBRARY
)
message(FATAL_ERROR "Can't specify both ${EXT_LIB} root dir and include/libs parameters")
endif()
endif()
set(${EXT_LIB}_FOUND "0" PARENT_SCOPE)
if(
use_bundled OR NOT(
use_system OR
DEFINED ${EXT_LIB}_ROOT_DIR OR
DEFINED ${EXT_LIB}_INCLUDE_DIR OR
DEFINED ${EXT_LIB}_LIB_DIR OR
DEFINED ${EXT_LIB}_LIBRARY
)
)
# Use the bundled sources.
add_ext_build_targets("${NAME}")
set(${EXT_LIB}_FOUND "1" PARENT_SCOPE)
else()
# Use external third party dependency
# if(DEFINED WITH_${EXT_LIB} AND NOT WITH_${EXT_LIB})
# return()
# endif()
# Let's find the header
if(DEFINED ${EXT_LIB}_ROOT_DIR)
set(find_opts PATHS ${${EXT_LIB}_ROOT_DIR}
PATH_SUFFIXES include
NO_DEFAULT_PATH
)
elseif(DEFINED ${EXT_LIB}_INCLUDE_DIR)
set(find_opts PATHS ${${EXT_LIB}_INCLUDE_DIR}
NO_DEFAULT_PATH
)
endif()
unset(${EXT_LIB}_INCLUDE_DIR CACHE)
message(STATUS "Looking for header using options: ${find_opts}")
find_path(${EXT_LIB}_INCLUDE_DIR
NAMES ${HEADER}
${find_opts}
REQUIRED
)
message(STATUS "${EXT_LIB} INCLUDES : ${${EXT_LIB}_INCLUDE_DIR}")
if(${EXT_LIB}_INCLUDE_DIR)
set(${EXT_LIB}_FOUND "1" PARENT_SCOPE)
endif()
endif()
if(DEFINED ${EXT_LIB}_LIBRARY AND NOT ${ARGC} GREATER 2)
# FIXME: Why this limitation?
message(FATAL_ERROR
"${EXT_LIB}_LIB_DIR can't be used on ${EXT_LIB} since it has multiple libs"
)
endif()
if(${ARGC} GREATER 2)
add_ext_targets(${NAME} LIBRARY ${ARGN})
endif()
endfunction(add_ext)
function(add_ext_build_targets NAME)
set(src_dir ${PROJECT_SOURCE_DIR}/extra/${NAME})
set(bin_dir ${CMAKE_CURRENT_BINARY_DIR}/${NAME})
file(MAKE_DIRECTORY ${bin_dir})
#
# Copy cmake cache and internal files to avoid expensive platform
# detection/checks (as external projects are built on the same platform
# as the main project)
#
if(COMMAND init_ext_build)
init_ext_build(${bin_dir})
endif()
set(cmake_opts)
message("== configuring external build of ${NAME}")
message("-- sources at: ${src_dir}")
message("-- generator: ${CMAKE_GENERATOR}")
# Note: if we initialized build location above, there is no need to pass
# toolset/platform options in cmake invocation (the information is
# already in the cache). In fact, in this situation cmake errors out if
# these options are passed.
if(NOT COMMAND init_ext_build)
if(CMAKE_GENERATOR_TOOLSET)
message("-- toolset: ${CMAKE_GENERATOR_TOOLSET}")
list(APPEND cmake_opts "-T ${CMAKE_GENERATOR_TOOLSET}")
endif()
if(CMAKE_GENERATOR_PLATFORM)
message("-- platform: ${CMAKE_GENERATOR_PLATFORM}")
list(APPEND cmake_opts "-A ${CMAKETO_GENERATOR_PLATFORM}")
endif()
endif()
foreach(var ${EXT_FWD})
if(${var})
message("-- option ${var}: ${${var}}")
list(APPEND cmake_opts -D${var}=${${var}})
endif()
endforeach()
message("-- ----")
execute_process(
COMMAND ${CMAKE_COMMAND}
-G ${CMAKE_GENERATOR} ${cmake_opts}
-Wno-dev --no-warn-unused-cli
${src_dir}
WORKING_DIRECTORY ${bin_dir}
RESULT_VARIABLE res
)
if(res)
message(FATAL_ERROR
"Failed to configure external build of ${NAME}: ${res}"
)
endif()
message("== done configuring external build of ${NAME}")
# Option to use parallel builds (much faster!)
# Note: ninja does that by default
set(build_opt)
ProcessorCount(prc_cnt)
if(prc_cnt AND NOT CMAKE_VERSION VERSION_LESS 3.12)
list(APPEND build_opt --parallel ${prc_cnt})
endif()
add_custom_target(${NAME}-build
COMMAND ${CMAKE_COMMAND}
-DBIN_DIR=${bin_dir}
-DCONFIG=$<CONFIG>
-DOPTS=${build_opt}
-P ${EXT_DIR}/ext-build.cmake
#COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG> ${build_opt}
#COMMAND ${CMAKE_COMMAND} -E touch ${bin_dir}/build.$<CONFIG>.stamp
#COMMENT "building ${NAME}"
WORKING_DIRECTORY ${bin_dir}
)
set_target_properties(${NAME}-build PROPERTIES FOLDER "Misc")
add_custom_target(${NAME}-rebuild
COMMAND ${CMAKE_COMMAND} --build . --config $<CONFIG> --clean-first ${build_opt}
COMMAND ${CMAKE_COMMAND} -E touch ${bin_dir}/build.$<CONFIG>.stamp
COMMENT "re-building ${NAME}"
WORKING_DIRECTORY ${bin_dir}
)
set_target_properties(${NAME}-rebuild PROPERTIES FOLDER "Misc")
add_dependencies(rebuild-ext ${NAME}-rebuild)
endfunction()
if(NOT TARGET rebuild-ext)
add_custom_target(rebuild-ext)
set_target_properties(rebuild-ext PROPERTIES FOLDER "Misc")
endif()
##########################################################################
#
# add_ext_targets(ext type1 name1 tgt1 type2 name2 tgt2 ...)
#
# Definestargets `ext::nameK` for an external dependency `ext` that was created
# with `add_ext(ext ...) `. The target can refer to a LIBRARY or EXECUTABLE,
# as given by the `typeK` parameter.
#
# If the external dependency is at locations specified using the corresponding
# WITH_X(or alternative) options then it refers to a library/executable with the
# name `nameK` found in these locations(and `tgtK` argument is ignored). For
# example `add_ext_targets(zlib LIBRARY z ...) ` will create target `ext::z`
# referring to library `libz` found in the external locations specified using
# WITH_ZLIB.
#
# If the dependency is built from bundled sources, then created target
# `ext::nameK` refers to the build target `tgtK` that should be exported to file
# `exports.cmake` by the bundled dependency build system. In the example of
# `add_ext_targets(zlib LIBRARY z ext_zlib) `, in case zlib is built from
# bundled sources the created `ext::z` target will correspond to the target
# `ext_zlib` exported by the bundled zlib build system.
#
# In the case of dependency built from bundled sources the `ext::nameK` target
# will trigger `build-ext` target when used (if it is defined) so that external
# build is performed before the imported libraries are used.
function(add_ext_targets EXT)
string(TOUPPER ${EXT} EXT_LIB)
set(ext_dir ${CMAKE_CURRENT_BINARY_DIR}/${EXT})
while(ARGN)
list(GET ARGN 0 type)
list(GET ARGN 1 name)
list(GET ARGN 2 tgt)
list(REMOVE_AT ARGN 0 1 2)
# If defined, will use external third party dependencies
if(NOT TARGET ${EXT}-build)
set(target ${name})
if(DEFINED ${EXT_LIB}_LIBRARY)
set(name ${${EXT_LIB}_LIBRARY})
endif()
if(type STREQUAL "LIBRARY")
add_ext_lib(${EXT_LIB} ${target} ${name})
elseif(type STREQUAL "EXECUTABLE")
add_ext_exec(${EXT_LIB} ${target} ${name})
endif()
else()
# otherwise, will use bundled code
if(NOT TARGET ${tgt} AND EXISTS ${ext_dir}/exports.cmake)
include(${ext_dir}/exports.cmake)
endif()
if(NOT TARGET ${tgt})
message(FATAL_ERROR "Could not import target ${tgt}")
endif()
get_target_property(configs ${tgt} IMPORTED_CONFIGURATIONS)
get_target_property(type ${tgt} TYPE)
if(type MATCHES "LIBRARY")
# message("importing library ${tgt} as ext::${name}")
add_library(ext::${name} INTERFACE IMPORTED GLOBAL)
target_link_libraries(ext::${name} INTERFACE ${tgt})
if(TARGET ${EXT}-build)
add_dependencies(ext::${name} ${EXT}-build)
endif()
# Touch imported locations which do not exist before external project
# is built. This is needed for ninja ...
if(CMAKE_GENERATOR MATCHES "Ninja")
foreach(conf ${configs})
get_target_property(loc ${tgt} IMPORTED_LOCATION_${conf})
if(loc)
execute_process(COMMAND ${CMAKE_COMMAND} -E touch ${loc})
endif()
endforeach(conf)
endif()
endif()
if(type MATCHES "EXECUTABLE")
# message("importing executable ${tgt} as ext::${name}")
add_executable(ext::${name} IMPORTED GLOBAL)
if(TARGET ${EXT}-build)
add_dependencies(ext::${name} ${EXT}-build)
endif()
foreach(conf ${configs})
get_target_property(loc ${tgt} IMPORTED_LOCATION_${conf})
if(loc)
set_target_properties(ext::${name}
PROPERTIES IMPORTED_LOCATION_${conf} ${loc}
)
endif()
endforeach(conf)
endif()
endif()
endwhile(ARGN)
endfunction(add_ext_targets)
##########################################################################
#
# add_ext_lib(EXT target name)
#
# Expects include defined on ${EXT}-include and creates target ext::{target}
# pointing to library with ${name} and located on ${EXT}_ROOT_DIR (suffixes: lib
# lib64 dll) or ${${EXT}_LIB_DIR}.
#
function(add_ext_lib EXT target name)
# Search for the library
if(DEFINED ${EXT}_ROOT_DIR)
set(suffix PATHS ${${EXT}_ROOT_DIR}
PATH_SUFFIXES lib lib64 dll
NO_DEFAULT_PATH
)
elseif(DEFINED ${EXT}_LIB_DIR)
set(suffix
PATHS ${${EXT}_LIB_DIR}
NO_DEFAULT_PATH
)
endif()
unset(library CACHE)
find_library(library
NAMES ${name} lib${name}
${suffix}
REQUIRED
)
message(STATUS "${EXT} LIBRARY : ${library}")
# if(NOT EXISTS ${${EXT}_include} OR NOT EXISTS ${library})
# message(FATAL_ERROR "Couldn't find ${EXT} library.")
# endif()
add_library(ext::${target} SHARED IMPORTED GLOBAL)
set_target_properties(ext::${target} PROPERTIES
IMPORTED_LOCATION ${library}
IMPORTED_IMPLIB ${library}
INTERFACE_INCLUDE_DIRECTORIES ${${EXT}_INCLUDE_DIR}
)
endfunction(add_ext_lib)
##########################################################################
#
# add_ext_exec(EXT target name)
# Creates target ext::{target} pointing to executable with ${name} and located
# on ${EXT}_ROOT_DIR (suffixes: bin) or ${${EXT}_BIN_DIR}.
#
function(add_ext_exec EXT target name)
# Search for the library
if(DEFINED ${EXT}_ROOT_DIR)
set(suffix PATHS ${${EXT}_ROOT_DIR}
PATH_SUFFIXES bin
NO_DEFAULT_PATH
)
elseif(DEFINED ${EXT}_BIN_DIR)
set(suffix PATHS ${${EXT_LIB}_BIN_DIR}
PATH_SUFFIXES bin
NO_DEFAULT_PATH
)
endif()
find_program(executable
NAMES ${name}
${suffix}
NO_CACHE
)
if(NOT executable)
message(FATAL_ERROR "Couldn't find ${name} executable.")
endif()
message(STATUS "${EXT} BINARY: ${executable}")
add_executable(ext::${target} IMPORTED GLOBAL)
set_target_properties(ext::${target} PROPERTIES
IMPORTED_LOCATION ${executable})
endfunction(add_ext_exec)