cmake/libutils.cmake (395 lines of code) (raw):

# Copyright (c) 2015, 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/ODBC, 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 # # Library utilities: # # - convenient building of libraries from object library targets # - merging static libraries # get_filename_component(LIBUTILS_SCRIPT_DIR ${CMAKE_CURRENT_LIST_FILE} PATH) set(LIBUTILS_SCRIPT_DIR "${LIBUTILS_SCRIPT_DIR}/libutils") # # On MacOS we need install_name_tool to do rpath mangling (see below) # if(APPLE) find_program(INSTALL_NAME_TOOL install_name_tool) # If available, otool is used to show runtime dependencies for libraries we # build find_program(OTOOL otool) if(NOT INSTALL_NAME_TOOL) message(FATAL_ERROR "Could not find install_name_tool required to buld Connector/C++" ) endif() endif() # # Add interface link libraries to a target, even if this is object # library target. # function(lib_interface_link_libraries TARGET) get_target_property(target_type ${TARGET} TYPE) if(target_type STREQUAL "OBJECT_LIBRARY") set_property(TARGET ${TARGET} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${ARGN} ) else() target_link_libraries(${TARGET} INTERFACE ${ARGN}) endif() endfunction(lib_interface_link_libraries) function(lib_link_libraries TARGET) get_target_property(target_type ${TARGET} TYPE) if(target_type STREQUAL "OBJECT_LIBRARY") set_property(TARGET ${TARGET} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${ARGN} ) else() target_link_libraries(${TARGET} PRIVATE ${ARGN}) endif() endfunction(lib_link_libraries) # # Create static or shared library like add_library(). But library can be # built not only from source files but also from other libraries (object # or static). # # add_library_ex(TARGET TYPE # [<sources>] # [OBJECTS <object libs>] # [LIBS <static libs>] # ) # # If static library is created then specified <static libs> are merged # with the main library (so that resulting library does not depend on # them at compile time). # function(add_library_ex TARGET) set(type) set(srcs) set(objs) set(libs) list(GET ARGN 0 arg) list(REMOVE_AT ARGN 0) if(arg STREQUAL "STATIC") #message("- creating static library") set(type "STATIC") list(GET ARGN 0 arg) list(REMOVE_AT ARGN 0) endif() if(arg STREQUAL "SHARED") #message("- creating shared library") set(type "SHARED") list(GET ARGN 0 arg) list(REMOVE_AT ARGN 0) endif() # # Collect library sources, up to first OBJECTS or LIBS word # while(1) if(arg STREQUAL "OBJECTS" OR arg STREQUAL "LIBS") break() endif() #message("- processing source: ${arg}") list(APPEND srcs ${arg}) if(NOT ARGN) break() endif() list(GET ARGN 0 arg) list(REMOVE_AT ARGN 0) endwhile() # # Collect object libraries, if present # if(arg STREQUAL "OBJECTS") while(ARGN) list(GET ARGN 0 arg) list(REMOVE_AT ARGN 0) if(arg STREQUAL "LIBS") break() endif() #message("- processing object lib: ${arg}") list(APPEND objs ${arg}) endwhile() endif() # # Collect static libraries, if present # if(arg STREQUAL "LIBS") while(ARGN) list(GET ARGN 0 arg) list(REMOVE_AT ARGN 0) #message("- processing library: ${arg}") list(APPEND libs ${arg}) endwhile() endif() # # Propagate RPATH_MANGLE property from compound libraries to the target. # set(mangle_paths) foreach(xx ${objs} ${libs}) get_target_property(xx_mangle_paths ${xx} RPATH_MANGLE) #message("== checking mangle paths of ${xx}: ${xx_mangle_paths}") if(xx_mangle_paths) list(APPEND mangle_paths ${xx_mangle_paths}) endif() endforeach() if(mangle_paths) list(REMOVE_DUPLICATES mangle_paths) #message("== collected mangle paths: ${mangle_paths}") endif() # # We use different technology for building static library # from object libraries on Linux and on Windows or OSX. # # Because of the way we merge static libraries on Linux it is # important that all object files archived in a static library # have unique names. This might be not the case if we compose # the resulting library from several cmake object libraries. Two # different object libraries can have source file with the same # name and then the corresponding object files will also have # the same name. To go around this, instead of using object # libraries we use regular static libraries and then merge them # into output library. The static library merging infrastructure # ensures that object names are unique (see merge_archives.cmake.in). # # This is needed only for Linux builds. On Windows and OSX we have # tools for merging static libraries which can deal with non-unique # object names inside single library. On these platforms we prefer # directly using object libraries instead of merging output library # from static libs. # # # Add objects of each object library to the "sources" of the # output library. This is not done only if building static library # on Linux. # if(type STREQUAL "SHARED" OR MSVC OR APPLE) foreach(obj ${objs}) list(APPEND srcs $<TARGET_OBJECTS:${obj}>) endforeach() endif() add_library(${TARGET} ${type} ${srcs}) #message("- added ${type} library: ${TARGET}") if(mangle_paths) set_property(TARGET ${TARGET} PROPERTY RPATH_MANGLE ${mangle_paths}) endif() foreach(obj ${objs}) # If we are building static library on Linux, then for each object # library OOO a corresponding static library OOO_objs is created. # Then these static libraries are added to the list of libraries # that will be merged into the resulting library. if(type STREQUAL "STATIC" AND NOT MSVC AND NOT APPLE) message(- "adding static library: ${obj}_objs") add_library(${obj}_objs STATIC $<TARGET_OBJECTS:${obj}>) list(APPEND libs ${obj}_objs) add_dependencies(${TARGET} ${obj}_objs) endif() target_link_libraries(${TARGET} PRIVATE $<TARGET_PROPERTY:${obj},INTERFACE_LINK_LIBRARIES> ) target_include_directories(${TARGET} INTERFACE $<TARGET_PROPERTY:${obj},INTERFACE_INCLUDE_DIRECTORIES> ) endforeach() if(libs) if(${type} STREQUAL "STATIC") merge_static_libraries(${TARGET} ${libs}) add_dependencies(${TARGET} ${libs}) else() target_link_libraries(${TARGET} PRIVATE ${libs}) endif() endif() # # Perform rpath mangling on MacOS (see below). # if(${type} STREQUAL "SHARED" AND APPLE) mangle_osx_rpaths(${TARGET}) endif() endfunction(add_library_ex) # # Perform MacOS rptah mangling. # # For some libraries we depend on, such as openSSL, we want the link name # stored in our library to be of the form @rpath/<library name> instead of # being a fixed path to the location where such external dependencies were # found at build time. # # If target we build has MACOSX_RPATH and RPATH_MANGLE properties set, then # we perform rpath mangling for that target. The RPATH_MANGLE property is # a list of link names that should be mangled, that is, the directory prefix # of the link name should be replaced by @rpath. # # We use install_name_tool to change link names stored in the target. For more # information about @rpath see: <https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/RunpathDependentLibraries.html#//apple_ref/doc/uid/TP40008306-SW1> # function(mangle_osx_rpaths TARGET) if (NOT APPLE) return() endif() get_target_property(use_rpaths ${TARGET} MACOSX_RPATH) if (NOT use_rpaths) return() endif() #message("rpath mangling for target: ${TARGET}") set(paths_to_mangle) # Only OpenSSL 3 is regarded as secure at the moment. list(APPEND paths_to_mangle "libssl.3.dylib") list(APPEND paths_to_mangle "libcrypto.3.dylib") set(mangle_commands) foreach(path ${paths_to_mangle}) #message("magling path: ${path}") get_filename_component(lib_name "${path}" NAME) list(APPEND mangle_commands COMMAND ${INSTALL_NAME_TOOL} -change "\"${path}\"" "\"@rpath/${lib_name}\"" "\"$<TARGET_FILE:${TARGET}>\"" ) endforeach() if(ODBC_LIB_DIR) # Try to mangle iODBC and iODBCinst for compatibility with homebrew install foreach(iodbc_lib "libiodbc.dylib" "libiodbcinst.dylib") get_filename_component(IODBC_REALPATH "${ODBC_LIB_DIR}/${iodbc_lib}" REALPATH) if (IODBC_REALPATH) list(APPEND mangle_commands COMMAND ${INSTALL_NAME_TOOL} -change "${IODBC_REALPATH}" "@rpath/${iodbc_lib}" "\"$<TARGET_FILE:${TARGET}>\"") endif() endforeach() endif() # If otool is available, also list final dependencies after the mangling if(OTOOL) list(APPEND mangle_commands COMMAND ${OTOOL} -L "\"$<TARGET_FILE:${TARGET}>\"") endif() #message("mangle commands: ${mangle_commands}") # Invoke mangling commands as a POST_BUILD step for the target. add_custom_command(TARGET ${TARGET} POST_BUILD ${mangle_commands} COMMENT "runtime dependencies of ${TARGET}:" ) endfunction(mangle_osx_rpaths) # # Infrastructure for merging static libraries # =========================================== # # Static libraries that will be merged are prepared first. This is # done by prepere_for_merge() macro. During preparations the library # is copied to a known location (${MERGELIBS_DIR}) so that the Location # is not dependent on build configuraton. # # On Linux individual objects are also extracted from the library archive. # This is preliminary step for later merging of the object into single static # library. # # if(APPLE) # TODO: find libtool instead of assuming its location set(LIBTOOL_COMMAND /usr/bin/libtool CACHE INTERNAL "location of libtool") endif() # # Merge static libraries into a big static lib. The resulting library # should not not have dependencies on other static libraries. # We use it in MySQL to merge mysys,dbug,vio etc into mysqlclient # function(merge_static_libraries TARGET) set(mergedir ${CMAKE_CURRENT_BINARY_DIR}/libmerge_${TARGET}) if(MSVC) set(libs ${ARGN}) else() set(libs ${TARGET} ${ARGN}) endif() # # Prepare list of commands which copy each library file to a known location. # message("Merging static libraries into ${TARGET}:") set(copy_libs) foreach(lib ${libs}) message(" - ${lib}") if(NOT TARGET ${lib}) message(FATAL_ERROR "mergelibs: Trying to merge non target: ${lib}") endif() get_target_property(lib_type ${lib} TYPE) if(NOT lib_type STREQUAL "STATIC_LIBRARY") message(FATAL_ERROR "mergelibs: Trying to merge target which is not a static library: ${lib}") endif() list(APPEND copy_libs COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${lib}> ${mergedir}/${lib}/lib${CMAKE_STATIC_LIBRARY_SUFFIX} ) endforeach() # # Now merge all the libraries into one. # if(MSVC) # To merge libs, just pass them to lib.exe command line via # STATIC_LIBRARY_FLAGS property. set(LINKER_EXTRA_FLAGS "") foreach(lib ${ARGN}) set(LINKER_EXTRA_FLAGS "${LINKER_EXTRA_FLAGS} ${mergedir}/${lib}/lib.lib") endforeach() set_target_properties(${TARGET} PROPERTIES STATIC_LIBRARY_FLAGS "${LINKER_EXTRA_FLAGS}" ) # The PRE_BUILD command copies libraries into the predefined location # before main library is linked. add_custom_command(TARGET ${TARGET} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E remove_directory ${mergedir} COMMAND ${CMAKE_COMMAND} -E make_directory ${mergedir} ${copy_libs} COMMENT "Preparing libraries for merging into: ${TARGET}" ) else() # # Generate cmake script which handles merging of the libraries. # Apart from global cmake variables, script template uses the follwoing # additional variables: # # STATIC_LIBS -- static library targets to be merged # MERGELIBS_DIR -- location where libraries are prepared for merging # set(STATIC_LIBS ${libs}) set(MERGELIBS_DIR ${mergedir}) CONFIGURE_FILE( ${LIBUTILS_SCRIPT_DIR}/merge_archives.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/merge_archives_${TARGET}.cmake @ONLY ) # The POST_BUILD action on the merged library target copies merged # libraries to the predefined location and invokes the merge script. #message("- copy libs: ${copy_libs}") add_custom_command(TARGET ${TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E remove_directory ${mergedir} COMMAND ${CMAKE_COMMAND} -E make_directory ${mergedir} ${copy_libs} COMMAND ${CMAKE_COMMAND} -D TARGET_LOCATION=$<TARGET_FILE:${TARGET}> -P ${CMAKE_CURRENT_BINARY_DIR}/merge_archives_${TARGET}.cmake COMMENT "Merging libraries into: ${TARGET}" ) endif() endfunction() # An IMPORTED library can also be merged. function(add_imported_library target location) ADD_LIBRARY(${target} STATIC IMPORTED) SET_TARGET_PROPERTIES(${target} PROPERTIES IMPORTED_LOCATION ${location}) endfunction()