Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CI] Build Python wheels for MacOS (x86_64 and arm64) #7621

Merged
merged 7 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ jobs:
submodules: 'true'
- name: Install system packages
run: |
# Use libomp 11.1.0: https://github.com/dmlc/xgboost/issues/7039
wget https://raw.githubusercontent.com/Homebrew/homebrew-core/679923b4eb48a8dc7ecc1f05d06063cd79b3fc00/Formula/libomp.rb -O $(find $(brew --repository) -name libomp.rb)
brew install ninja libomp
brew pin libomp
- name: Build gtest binary
run: |
mkdir build
Expand Down
34 changes: 6 additions & 28 deletions .github/workflows/python_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ jobs:
- name: Install osx system dependencies
if: matrix.os == 'macos-10.15'
run: |
# Use libomp 11.1.0: https://github.com/dmlc/xgboost/issues/7039
wget https://raw.githubusercontent.com/Homebrew/homebrew-core/679923b4eb48a8dc7ecc1f05d06063cd79b3fc00/Formula/libomp.rb -O $(find $(brew --repository) -name libomp.rb)
brew install ninja libomp
brew pin libomp
- name: Install Ubuntu system dependencies
if: matrix.os == 'ubuntu-latest'
run: |
Expand Down Expand Up @@ -119,14 +116,16 @@ jobs:
conda list

- name: Build XGBoost on macos
shell: bash -l {0}
run: |
wget https://raw.githubusercontent.com/Homebrew/homebrew-core/679923b4eb48a8dc7ecc1f05d06063cd79b3fc00/Formula/libomp.rb -O $(find $(brew --repository) -name libomp.rb)
brew install ninja libomp
brew pin libomp
brew install ninja

mkdir build
cd build
cmake .. -GNinja -DGOOGLE_TEST=ON -DUSE_DMLC_GTEST=ON
# Set prefix, to use OpenMP library from Conda env
# See https://github.com/dmlc/xgboost/issues/7039#issuecomment-1025038228
# to learn why we don't use libomp from Homebrew.
cmake .. -GNinja -DGOOGLE_TEST=ON -DUSE_DMLC_GTEST=ON -DCMAKE_PREFIX_PATH=$CONDA_PREFIX
ninja

- name: Install Python package
Expand All @@ -141,24 +140,3 @@ jobs:
shell: bash -l {0}
run: |
pytest -s -v ./tests/python

- name: Rename Python wheel
shell: bash -l {0}
run: |
TAG=macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64
python tests/ci_build/rename_whl.py python-package/dist/*.whl ${{ github.sha }} ${TAG}

- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/heads/release_')

- name: Upload Python wheel
shell: bash -l {0}
if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/heads/release_')
run: |
python -m awscli s3 cp python-package/dist/*.whl s3://xgboost-nightly-builds/${{ steps.extract_branch.outputs.branch }}/ --acl public-read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_IAM_S3_UPLOADER }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_IAM_S3_UPLOADER }}
57 changes: 57 additions & 0 deletions .github/workflows/python_wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: XGBoost-Python-Wheels

on: [push, pull_request]

jobs:
python-wheels:
name: Build wheel for ${{ matrix.platform_id }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-latest
python: 37
platform_id: macosx_x86_64
wheel_tag: macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64
- os: macos-latest
python: 38
platform_id: macosx_arm64
wheel_tag: macosx_12_0_arm64
steps:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Set env var for ARM64
shell: bash
run: echo "::set-output name=value::CIBW_TARGET_OSX_ARM64=1"
id: arm64_flag
if: matrix.platform_id == 'macosx_arm64'
- name: Build wheels
env:
CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }}
CIBW_ARCHS: all
CIBW_ENVIRONMENT: ${{ steps.arm64_flag.outputs.value }}
CIBW_TEST_SKIP: "*-macosx_arm64"
CIBW_BUILD_VERBOSITY: 3
run: bash tests/ci_build/build_python_wheels.sh

- name: Rename Python wheel
run: |
python tests/ci_build/rename_whl.py wheelhouse/*.whl ${{ github.sha }} ${{ matrix.wheel_tag }}
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/heads/release_')
- name: Upload Python wheel
if: github.ref == 'refs/heads/master' || contains(github.ref, 'refs/heads/release_')
run: |
python -m pip install awscli
python -m awscli s3 cp wheelhouse/*.whl s3://xgboost-nightly-builds/${{ steps.extract_branch.outputs.branch }}/ --acl public-read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_IAM_S3_UPLOADER }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_IAM_S3_UPLOADER }}
3 changes: 2 additions & 1 deletion dev/release-py-r.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def download_py_packages(major: int, minor: int, commit_hash: str):
"win_amd64",
"manylinux2014_x86_64",
"manylinux2014_aarch64",
"macosx_10_14_x86_64.macosx_10_15_x86_64.macosx_11_0_x86_64",
"macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64",
"macosx_12_0_arm64"
]

dir_URL = PREFIX + str(major) + "." + str(minor) + ".0" + "/"
Expand Down
12 changes: 12 additions & 0 deletions doc/contrib/ci.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ requests and every update to branches. A few tests however require manual activa
details about noLD. This is a requirement for keeping XGBoost on CRAN (the R package index).
To invoke this test suite for a particular pull request, simply add a review comment
``/gha run r-nold-test``. (Ordinary comment won't work. It needs to be a review comment.)

GitHub Actions is also used to build Python wheels targeting MacOS Intel and Apple Silicon. See
`.github/workflows/python_wheels.yml
<https://github.com/dmlc/xgboost/tree/master/.github/workflows/python_wheels.yml>`_. The
``python_wheels`` pipeline sets up environment variables prefixed ``CIBW_*`` to indicate the target
OS and processor. The pipeline then invokes the script ``build_python_wheels.sh``, which in turns
calls ``cibuildwheel`` to build the wheel. The ``cibuildwheel`` is a library that sets up a
suitable Python environment for each OS and processor target. Since we don't have Apple Silion
machine in GitHub Actions, cross-compilation is needed; ``cibuildwheel`` takes care of the complex
task of cross-compiling a Python wheel. (Note that ``cibuildwheel`` will call
``setup.py bdist_wheel``. Since XGBoost has a native library component, ``setup.py`` contains
a glue code to call CMake and a C++ compiler to build the native library on the fly.)
7 changes: 7 additions & 0 deletions python-package/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ def build(
continue
cmake_cmd.append('-D' + arg + '=' + value)

# Flag for cross-compiling for Apple Silicon
# We use environment variable because it's the only way to pass down custom flags
# through the cibuildwheel package, which otherwise calls `python setup.py bdist_wheel`
# command.
if 'CIBW_TARGET_OSX_ARM64' in os.environ:
hcho3 marked this conversation as resolved.
Show resolved Hide resolved
cmake_cmd.append("-DCMAKE_OSX_ARCHITECTURES=arm64")

self.logger.info('Run CMake command: %s', str(cmake_cmd))
subprocess.check_call(cmake_cmd, cwd=build_dir)

Expand Down
38 changes: 38 additions & 0 deletions tests/ci_build/build_python_wheels.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash

set -e
set -x

# Bundle libomp 11.1.0 when targeting MacOS.
# This is a workaround in order to prevent segfaults when running inside a Conda environment.
# See https://github.com/dmlc/xgboost/issues/7039#issuecomment-1025125003 for more context.
# The workaround is also used by the scikit-learn project.
if [[ "$RUNNER_OS" == "macOS" ]]; then
# Make sure to use a libomp version binary compatible with the oldest
# supported version of the macos SDK as libomp will be vendored into the
# XGBoost wheels for MacOS.

if [[ "$CIBW_BUILD" == *-macosx_arm64 ]]; then
# arm64 builds must cross compile because CI is on x64
# cibuildwheel will take care of cross-compilation.
export PYTHON_CROSSENV=1
export MACOSX_DEPLOYMENT_TARGET=12.0
OPENMP_URL="https://anaconda.org/conda-forge/llvm-openmp/11.1.0/download/osx-arm64/llvm-openmp-11.1.0-hf3c4609_1.tar.bz2"
else
export MACOSX_DEPLOYMENT_TARGET=10.13
OPENMP_URL="https://anaconda.org/conda-forge/llvm-openmp/11.1.0/download/osx-64/llvm-openmp-11.1.0-hda6cdc1_1.tar.bz2"
fi

sudo conda create -n build $OPENMP_URL
PREFIX="/usr/local/miniconda/envs/build"

export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
export CPPFLAGS="$CPPFLAGS -Xpreprocessor -fopenmp"
export CFLAGS="$CFLAGS -I$PREFIX/include"
export CXXFLAGS="$CXXFLAGS -I$PREFIX/include"
export LDFLAGS="$LDFLAGS -Wl,-rpath,$PREFIX/lib -L$PREFIX/lib -lomp"
fi

python -m pip install cibuildwheel
python -m cibuildwheel python-package --output-dir wheelhouse
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This command calls python.py bdist_wheel to generate the wheel.

1 change: 1 addition & 0 deletions tests/ci_build/conda_env/macos_cpu_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies:
- pylint
- numpy
- scipy
- llvm-openmp
- scikit-learn
- pandas
- matplotlib
Expand Down