|
| 1 | +# Find OpenMP library on MacOS |
| 2 | +# Automatically handle locating libomp from the Homebrew package manager |
| 3 | + |
| 4 | +# lint_cmake: -package/consistency |
| 5 | + |
| 6 | +macro(find_openmp_macos) |
| 7 | + if(NOT APPLE) |
| 8 | + message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}() must only be used on MacOS") |
| 9 | + endif() |
| 10 | + find_package(OpenMP) |
| 11 | + if(NOT OpenMP_FOUND) |
| 12 | + # Try again with extra path info. This step is required for libomp 15+ from Homebrew, |
| 13 | + # as libomp 15.0+ from brew is keg-only |
| 14 | + # See https://github.com/Homebrew/homebrew-core/issues/112107#issuecomment-1278042927. |
| 15 | + execute_process(COMMAND brew --prefix libomp |
| 16 | + OUTPUT_VARIABLE HOMEBREW_LIBOMP_PREFIX |
| 17 | + OUTPUT_STRIP_TRAILING_WHITESPACE) |
| 18 | + set(OpenMP_C_FLAGS |
| 19 | + "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PREFIX}/include") |
| 20 | + set(OpenMP_CXX_FLAGS |
| 21 | + "-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PREFIX}/include") |
| 22 | + set(OpenMP_C_LIB_NAMES omp) |
| 23 | + set(OpenMP_CXX_LIB_NAMES omp) |
| 24 | + set(OpenMP_omp_LIBRARY ${HOMEBREW_LIBOMP_PREFIX}/lib/libomp.dylib) |
| 25 | + find_package(OpenMP REQUIRED) |
| 26 | + endif() |
| 27 | +endmacro() |
| 28 | + |
| 29 | +# Patch libxgboost.dylib so that it depends on @rpath/libomp.dylib instead of |
| 30 | +# /opt/homebrew/opt/libomp/lib/libomp.dylib or other hard-coded paths. |
| 31 | +# Doing so enables XGBoost to interoperate with multiple kinds of OpenMP |
| 32 | +# libraries. See https://github.com/microsoft/LightGBM/pull/6391 for detailed |
| 33 | +# explanation. Adapted from https://github.com/microsoft/LightGBM/pull/6391 |
| 34 | +# by James Lamb. |
| 35 | +# MacOS only. |
| 36 | +function(patch_openmp_path_macos target target_default_output_name) |
| 37 | + if(NOT APPLE) |
| 38 | + message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}() must only be used on MacOS") |
| 39 | + endif() |
| 40 | + # Get path to libomp found at build time |
| 41 | + get_target_property( |
| 42 | + __OpenMP_LIBRARY_LOCATION |
| 43 | + OpenMP::OpenMP_CXX |
| 44 | + INTERFACE_LINK_LIBRARIES |
| 45 | + ) |
| 46 | + # Get the base name of the OpenMP lib |
| 47 | + # Usually: libomp.dylib, libgomp.dylib, or libiomp.dylib |
| 48 | + get_filename_component( |
| 49 | + __OpenMP_LIBRARY_NAME |
| 50 | + ${__OpenMP_LIBRARY_LOCATION} |
| 51 | + NAME |
| 52 | + ) |
| 53 | + # Get the directory containing the OpenMP lib |
| 54 | + get_filename_component( |
| 55 | + __OpenMP_LIBRARY_DIR |
| 56 | + ${__OpenMP_LIBRARY_LOCATION} |
| 57 | + DIRECTORY |
| 58 | + ) |
| 59 | + # Get the name of the XGBoost lib, e.g. libxgboost |
| 60 | + get_target_property( |
| 61 | + __LIBXGBOOST_OUTPUT_NAME |
| 62 | + ${target} |
| 63 | + OUTPUT_NAME |
| 64 | + ) |
| 65 | + if(NOT __LIBXGBOOST_OUTPUT_NAME) |
| 66 | + set(__LIBXGBOOST_OUTPUT_NAME "${target_default_output_name}") |
| 67 | + endif() |
| 68 | + |
| 69 | + # Get the file name of the XGBoost lib, e.g. libxgboost.dylib |
| 70 | + if(CMAKE_SHARED_LIBRARY_SUFFIX_CXX) |
| 71 | + set( |
| 72 | + __LIBXGBOOST_FILENAME_${target} "${__LIBXGBOOST_OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX_CXX}" |
| 73 | + CACHE INTERNAL "Shared library filename ${target}" |
| 74 | + ) |
| 75 | + else() |
| 76 | + set( |
| 77 | + __LIBXGBOOST_FILENAME_${target} "${__LIBXGBOOST_OUTPUT_NAME}.dylib" |
| 78 | + CACHE INTERNAL "Shared library filename ${target}" |
| 79 | + ) |
| 80 | + endif() |
| 81 | + |
| 82 | + message(STATUS "Creating shared lib for target ${target}: ${__LIBXGBOOST_FILENAME_${target}}") |
| 83 | + |
| 84 | + # Override the absolute path to OpenMP with a relative one using @rpath. |
| 85 | + # |
| 86 | + # This also ensures that if a libomp.dylib has already been loaded, it'll just use that. |
| 87 | + if(KEEP_BUILD_ARTIFACTS_IN_BINARY_DIR) |
| 88 | + set(__LIB_DIR ${xgboost_BINARY_DIR}/lib) |
| 89 | + else() |
| 90 | + set(__LIB_DIR ${xgboost_SOURCE_DIR}/lib) |
| 91 | + endif() |
| 92 | + add_custom_command( |
| 93 | + TARGET ${target} |
| 94 | + POST_BUILD |
| 95 | + COMMAND |
| 96 | + install_name_tool |
| 97 | + -change |
| 98 | + ${__OpenMP_LIBRARY_LOCATION} |
| 99 | + "@rpath/${__OpenMP_LIBRARY_NAME}" |
| 100 | + "${__LIBXGBOOST_FILENAME_${target}}" |
| 101 | + WORKING_DIRECTORY ${__LIB_DIR} |
| 102 | + ) |
| 103 | + message(STATUS |
| 104 | + "${__LIBXGBOOST_FILENAME_${target}}: " |
| 105 | + "Replacing hard-coded OpenMP install_name with '@rpath/${__OpenMP_LIBRARY_NAME}'..." |
| 106 | + ) |
| 107 | + # Add RPATH entries to ensure the loader looks in the following, in the following order: |
| 108 | + # |
| 109 | + # - /opt/homebrew/opt/libomp/lib (where 'brew install' / 'brew link' puts libomp.dylib) |
| 110 | + # - ${__OpenMP_LIBRARY_DIR} (wherever find_package(OpenMP) found OpenMP at build time) |
| 111 | + # |
| 112 | + # Note: This list will only be used if libomp.dylib isn't already loaded into memory. |
| 113 | + # So Conda users will likely use ${CONDA_PREFIX}/libomp.dylib |
| 114 | + execute_process(COMMAND brew --prefix libomp |
| 115 | + OUTPUT_VARIABLE HOMEBREW_LIBOMP_PREFIX |
| 116 | + OUTPUT_STRIP_TRAILING_WHITESPACE) |
| 117 | + set_target_properties( |
| 118 | + ${target} |
| 119 | + PROPERTIES |
| 120 | + BUILD_WITH_INSTALL_RPATH TRUE |
| 121 | + INSTALL_RPATH "${HOMEBREW_LIBOMP_PREFIX}/lib;${__OpenMP_LIBRARY_DIR}" |
| 122 | + INSTALL_RPATH_USE_LINK_PATH FALSE |
| 123 | + ) |
| 124 | +endfunction() |
0 commit comments