diff --git a/.ci/lint-python.sh b/.ci/lint-python.sh index edab8993a799..e1e9e306c883 100755 --- a/.ci/lint-python.sh +++ b/.ci/lint-python.sh @@ -2,11 +2,9 @@ set -e -E -u -o pipefail -# this can be re-enabled when this is fixed: -# https://github.com/tox-dev/filelock/issues/337 -# echo "running pre-commit checks" -# pre-commit run --all-files || exit 1 -# echo "done running pre-commit checks" +echo "running pre-commit checks" +pre-commit run --all-files || exit 1 +echo "done running pre-commit checks" echo "running mypy" mypy \ diff --git a/.ci/test.sh b/.ci/test.sh index c71c54ed906c..4f3de6a6f1ea 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -174,8 +174,7 @@ elif [[ $TASK == "bdist" ]]; then if [[ $ARCH == "x86_64" ]]; then PLATFORM="macosx_10_15_x86_64.macosx_11_6_x86_64.macosx_12_5_x86_64" else - echo "ERROR: macos wheels not supported yet on architecture '${ARCH}'" - exit 1 + PLATFORM="macosx_14_0_arm64" fi mv \ ./dist/tmp.whl \ diff --git a/.github/workflows/python_package.yml b/.github/workflows/python_package.yml index 83149a078cf6..035340e2ec1b 100644 --- a/.github/workflows/python_package.yml +++ b/.github/workflows/python_package.yml @@ -36,6 +36,10 @@ jobs: python_version: '3.8' - os: macos-13 task: if-else + python_version: '3.9' + - os: macos-14 + task: bdist + method: wheel python_version: '3.10' # We're currently skipping MPI jobs on macOS, see https://github.com/microsoft/LightGBM/pull/6425 # for further details. @@ -63,7 +67,11 @@ jobs: export TASK="${{ matrix.task }}" export METHOD="${{ matrix.method }}" export PYTHON_VERSION="${{ matrix.python_version }}" - if [[ "${{ matrix.os }}" == "macos-13" ]]; then + if [[ "${{ matrix.os }}" == "macos-14" ]]; then + # use clang when creating macOS release artifacts + export COMPILER="clang" + export OS_NAME="macos" + elif [[ "${{ matrix.os }}" == "macos-13" ]]; then export COMPILER="gcc" export OS_NAME="macos" elif [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then @@ -75,6 +83,12 @@ jobs: export PATH=${CONDA}/bin:${PATH} $GITHUB_WORKSPACE/.ci/setup.sh || exit 1 $GITHUB_WORKSPACE/.ci/test.sh || exit 1 + - name: upload wheels + if: ${{ matrix.method == 'wheel' && matrix.os == 'macos-14' }} + uses: actions/upload-artifact@v4 + with: + name: macosx-arm64-wheel + path: dist/*.whl test-latest-versions: name: Python - latest versions (ubuntu-latest) runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index d4eefbf3d840..3492289be078 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -707,6 +707,83 @@ if(__BUILD_FOR_PYTHON) set(CMAKE_INSTALL_PREFIX "lightgbm") endif() +# The macOS linker puts an absolute path to linked libraries in lib_lightgb.dylib. +# This block overrides that information for LightGBM's OpenMP dependency, to allow +# finding that library in more places. +# +# This reduces the risk of runtime issues resulting from multiple libomp.dylib being loaded. +# +if(APPLE AND USE_OPENMP) + # store path to libomp found at build time in a variable + get_target_property( + OpenMP_LIBRARY_LOCATION + OpenMP::OpenMP_CXX + INTERFACE_LINK_LIBRARIES + ) + # get just the filename of that path + # (to deal with the possibility that it might be 'libomp.dylib' or 'libgomp.dylib' or 'libiomp.dylib') + get_filename_component( + OpenMP_LIBRARY_NAME + ${OpenMP_LIBRARY_LOCATION} + NAME + ) + # get directory of that path + get_filename_component( + OpenMP_LIBRARY_DIR + ${OpenMP_LIBRARY_LOCATION} + DIRECTORY + ) + # get exact name of the library in a variable + get_target_property( + __LIB_LIGHTGBM_OUTPUT_NAME + _lightgbm + OUTPUT_NAME + ) + if(NOT __LIB_LIGHTGBM_OUTPUT_NAME) + set(__LIB_LIGHTGBM_OUTPUT_NAME "lib_lightgbm") + endif() + + if(CMAKE_SHARED_LIBRARY_SUFFIX_CXX) + set( + __LIB_LIGHTGBM_FILENAME "${__LIB_LIGHTGBM_OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX_CXX}" + CACHE INTERNAL "lightgbm shared library filename" + ) + else() + set( + __LIB_LIGHTGBM_FILENAME "${__LIB_LIGHTGBM_OUTPUT_NAME}.dylib" + CACHE INTERNAL "lightgbm shared library filename" + ) + endif() + + # Override the absolute path to OpenMP with a relative one using @rpath. + # + # This also ensures that if a libomp.dylib has already been loaded, it'll just use that. + add_custom_command( + TARGET _lightgbm + POST_BUILD + COMMAND + install_name_tool + -change + ${OpenMP_LIBRARY_LOCATION} + "@rpath/${OpenMP_LIBRARY_NAME}" + "${__LIB_LIGHTGBM_FILENAME}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Replacing hard-coded OpenMP install_name with '@rpath/${OpenMP_LIBRARY_NAME}'..." + ) + # add RPATH entries to ensure the loader looks in the following, in the following order: + # + # - /opt/homebrew/opt/libomp/lib (where 'brew install' / 'brew link' puts libomp.dylib) + # - ${OpenMP_LIBRARY_DIR} (wherever find_package(OpenMP) found OpenMP at build time) + # + set_target_properties( + _lightgbm + PROPERTIES + BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH "/opt/homebrew/opt/libomp/lib;${OpenMP_LIBRARY_DIR}" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() + install( TARGETS _lightgbm RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin