Skip to content

Commit e3bcedd

Browse files
authored
[CI] Stop vendoring libomp.dylib in MacOS Python wheels (dmlc#10440) (dmlc#10448)
* [CI] Stop vendoring libomp.dylib in MacOS Python wheels (dmlc#10440) * [CI] [jvm-packages] Build libxgboost4j.dylib on M1 MacOS with OpenMP support (dmlc#10449)
1 parent ec2f56a commit e3bcedd

File tree

6 files changed

+153
-43
lines changed

6 files changed

+153
-43
lines changed

.github/workflows/python_wheels.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ jobs:
2828
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
2929
with:
3030
submodules: 'true'
31+
- name: Set up homebrew
32+
uses: Homebrew/actions/setup-homebrew@68fa6aeb1ccb0596d311f2b34ec74ec21ee68e54
33+
- name: Install libomp
34+
run: brew install libomp
3135
- uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3.0.4
3236
with:
3337
miniforge-variant: Mambaforge

CMakeLists.txt

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -234,28 +234,17 @@ endif()
234234

235235
find_package(Threads REQUIRED)
236236

237+
# -- OpenMP
238+
include(cmake/FindOpenMPMacOS.cmake)
237239
if(USE_OPENMP)
238240
if(APPLE)
239-
find_package(OpenMP)
240-
if(NOT OpenMP_FOUND)
241-
# Try again with extra path info; required for libomp 15+ from Homebrew
242-
execute_process(COMMAND brew --prefix libomp
243-
OUTPUT_VARIABLE HOMEBREW_LIBOMP_PREFIX
244-
OUTPUT_STRIP_TRAILING_WHITESPACE)
245-
set(OpenMP_C_FLAGS
246-
"-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PREFIX}/include")
247-
set(OpenMP_CXX_FLAGS
248-
"-Xpreprocessor -fopenmp -I${HOMEBREW_LIBOMP_PREFIX}/include")
249-
set(OpenMP_C_LIB_NAMES omp)
250-
set(OpenMP_CXX_LIB_NAMES omp)
251-
set(OpenMP_omp_LIBRARY ${HOMEBREW_LIBOMP_PREFIX}/lib/libomp.dylib)
252-
find_package(OpenMP REQUIRED)
253-
endif()
241+
find_openmp_macos()
254242
else()
255243
find_package(OpenMP REQUIRED)
256244
endif()
257245
endif()
258-
#Add for IBM i
246+
247+
# Add for IBM i
259248
if(${CMAKE_SYSTEM_NAME} MATCHES "OS400")
260249
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
261250
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> -X64 qc <TARGET> <OBJECTS>")
@@ -381,6 +370,10 @@ if(JVM_BINDINGS)
381370
xgboost_target_defs(xgboost4j)
382371
endif()
383372

373+
if(USE_OPENMP AND APPLE)
374+
patch_openmp_path_macos(xgboost libxgboost)
375+
endif()
376+
384377
if(KEEP_BUILD_ARTIFACTS_IN_BINARY_DIR)
385378
set_output_directory(xgboost ${xgboost_BINARY_DIR}/lib)
386379
else()

cmake/FindOpenMPMacOS.cmake

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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()

jvm-packages/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,8 @@ target_include_directories(xgboost4j
2525
${PROJECT_SOURCE_DIR}/rabit/include)
2626

2727
set_output_directory(xgboost4j ${PROJECT_SOURCE_DIR}/lib)
28+
29+
# MacOS: Patch libxgboost4j.dylib to use @rpath/libomp.dylib
30+
if(USE_OPENMP AND APPLE)
31+
patch_openmp_path_macos(xgboost4j libxgboost4j)
32+
endif()

tests/buildkite/test-macos-m1-clang11.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ set -x
1818
mkdir build
1919
pushd build
2020
export JAVA_HOME=$(/usr/libexec/java_home)
21-
cmake .. -GNinja -DJVM_BINDINGS=ON -DUSE_OPENMP=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15
21+
cmake .. -GNinja -DJVM_BINDINGS=ON -DUSE_OPENMP=ON -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15
2222
ninja -v
2323
popd
2424
rm -rf build
25+
otool -L lib/libxgboost.dylib
2526
set +x
2627

2728
echo "--- Upload Python wheel"

tests/ci_build/build_python_wheels.sh

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,19 @@ fi
1111
platform_id=$1
1212
commit_id=$2
1313

14-
# Bundle libomp 11.1.0 when targeting MacOS.
15-
# This is a workaround in order to prevent segfaults when running inside a Conda environment.
16-
# See https://github.com/dmlc/xgboost/issues/7039#issuecomment-1025125003 for more context.
17-
# The workaround is also used by the scikit-learn project.
1814
if [[ "$platform_id" == macosx_* ]]; then
19-
# Make sure to use a libomp version binary compatible with the oldest
20-
# supported version of the macos SDK as libomp will be vendored into the
21-
# XGBoost wheels for MacOS.
22-
2315
if [[ "$platform_id" == macosx_arm64 ]]; then
2416
# MacOS, Apple Silicon
25-
# arm64 builds must cross compile because CI is on x64
26-
# cibuildwheel will take care of cross-compilation.
2717
wheel_tag=macosx_12_0_arm64
2818
cpython_ver=39
2919
cibw_archs=arm64
3020
export MACOSX_DEPLOYMENT_TARGET=12.0
31-
#OPENMP_URL="https://anaconda.org/conda-forge/llvm-openmp/11.1.0/download/osx-arm64/llvm-openmp-11.1.0-hf3c4609_1.tar.bz2"
32-
OPENMP_URL="https://xgboost-ci-jenkins-artifacts.s3.us-west-2.amazonaws.com/llvm-openmp-11.1.0-hf3c4609_1-osx-arm64.tar.bz2"
3321
elif [[ "$platform_id" == macosx_x86_64 ]]; then
3422
# MacOS, Intel
3523
wheel_tag=macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64
3624
cpython_ver=39
3725
cibw_archs=x86_64
3826
export MACOSX_DEPLOYMENT_TARGET=10.15
39-
#OPENMP_URL="https://anaconda.org/conda-forge/llvm-openmp/11.1.0/download/osx-64/llvm-openmp-11.1.0-hda6cdc1_1.tar.bz2"
40-
OPENMP_URL="https://xgboost-ci-jenkins-artifacts.s3.us-west-2.amazonaws.com/llvm-openmp-11.1.0-hda6cdc1_1-osx-64.tar.bz2"
4127
else
4228
echo "Platform not supported: $platform_id"
4329
exit 3
@@ -48,26 +34,23 @@ if [[ "$platform_id" == macosx_* ]]; then
4834
export CIBW_ENVIRONMENT=${setup_env_var}
4935
export CIBW_TEST_SKIP='*-macosx_arm64'
5036
export CIBW_BUILD_VERBOSITY=3
51-
52-
mamba create -n build $OPENMP_URL
53-
PREFIX="$HOME/miniconda3/envs/build"
54-
55-
# Set up build flags for cibuildwheel
56-
# This is needed to bundle libomp lib we downloaded earlier
57-
export CC=/usr/bin/clang
58-
export CXX=/usr/bin/clang++
59-
export CPPFLAGS="$CPPFLAGS -Xpreprocessor -fopenmp"
60-
export CFLAGS="$CFLAGS -I$PREFIX/include"
61-
export CXXFLAGS="$CXXFLAGS -I$PREFIX/include"
62-
export LDFLAGS="$LDFLAGS -Wl,-rpath,$PREFIX/lib -L$PREFIX/lib -lomp"
6337
else
6438
echo "Platform not supported: $platform_id"
6539
exit 2
6640
fi
6741

42+
# Tell delocate-wheel to not vendor libomp.dylib into the wheel"
43+
export CIBW_REPAIR_WHEEL_COMMAND_MACOS="delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} --exclude libomp.dylib"
44+
6845
python -m pip install cibuildwheel
6946
python -m cibuildwheel python-package --output-dir wheelhouse
7047
python tests/ci_build/rename_whl.py \
7148
--wheel-path wheelhouse/*.whl \
7249
--commit-hash ${commit_id} \
7350
--platform-tag ${wheel_tag}
51+
52+
# List dependencies of libxgboost.dylib
53+
mkdir tmp
54+
unzip -j wheelhouse/xgboost-*.whl xgboost/lib/libxgboost.dylib -d tmp
55+
otool -L tmp/libxgboost.dylib
56+
rm -rf tmp

0 commit comments

Comments
 (0)