Skip to content

Commit a5cd241

Browse files
authored
Replace setup.py with pyproject.toml (#9021)
* Create pyproject.toml * Implement a custom build backend (see below) in packager directory. Build logic from setup.py has been refactored and migrated into the new backend. * Tested: pip wheel . (build wheel), python -m build --sdist . (source distribution)
1 parent a7b3dd3 commit a5cd241

31 files changed

+698
-660
lines changed

.github/workflows/python_tests.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
run: |
6666
cd python-package
6767
python --version
68-
python setup.py sdist
68+
python -m build --sdist
6969
pip install -v ./dist/xgboost-*.tar.gz
7070
cd ..
7171
python -c 'import xgboost'
@@ -92,6 +92,9 @@ jobs:
9292
auto-update-conda: true
9393
python-version: ${{ matrix.python-version }}
9494
activate-environment: test
95+
- name: Install build
96+
run: |
97+
conda install -c conda-forge python-build
9598
- name: Display Conda env
9699
run: |
97100
conda info
@@ -100,7 +103,7 @@ jobs:
100103
run: |
101104
cd python-package
102105
python --version
103-
python setup.py sdist
106+
python -m build --sdist
104107
pip install -v ./dist/xgboost-*.tar.gz
105108
cd ..
106109
python -c 'import xgboost'
@@ -147,7 +150,7 @@ jobs:
147150
run: |
148151
cd python-package
149152
python --version
150-
python setup.py install
153+
pip install -v .
151154
152155
- name: Test Python package
153156
run: |
@@ -194,7 +197,7 @@ jobs:
194197
run: |
195198
cd python-package
196199
python --version
197-
python setup.py bdist_wheel --universal
200+
pip wheel -v . --wheel-dir dist/
198201
pip install ./dist/*.whl
199202
200203
- name: Test Python package
@@ -238,7 +241,7 @@ jobs:
238241
run: |
239242
cd python-package
240243
python --version
241-
python setup.py install
244+
pip install -v .
242245
243246
- name: Test Python package
244247
run: |

CMakeLists.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ option(USE_NVTX "Build with cuda profiling annotations. Developers only." OFF)
4747
set(NVTX_HEADER_DIR "" CACHE PATH "Path to the stand-alone nvtx header")
4848
option(RABIT_MOCK "Build rabit with mock" OFF)
4949
option(HIDE_CXX_SYMBOLS "Build shared library and hide all C++ symbols" OFF)
50+
option(KEEP_BUILD_ARTIFACTS_IN_BINARY_DIR "Output build artifacts in CMake binary dir" OFF)
5051
## CUDA
5152
option(USE_CUDA "Build with GPU acceleration" OFF)
5253
option(USE_NCCL "Build with NCCL to enable distributed GPU support." OFF)
@@ -268,8 +269,13 @@ if (JVM_BINDINGS)
268269
xgboost_target_defs(xgboost4j)
269270
endif (JVM_BINDINGS)
270271

271-
set_output_directory(runxgboost ${xgboost_SOURCE_DIR})
272-
set_output_directory(xgboost ${xgboost_SOURCE_DIR}/lib)
272+
if (KEEP_BUILD_ARTIFACTS_IN_BINARY_DIR)
273+
set_output_directory(runxgboost ${xgboost_BINARY_DIR})
274+
set_output_directory(xgboost ${xgboost_BINARY_DIR}/lib)
275+
else ()
276+
set_output_directory(runxgboost ${xgboost_SOURCE_DIR})
277+
set_output_directory(xgboost ${xgboost_SOURCE_DIR}/lib)
278+
endif ()
273279
# Ensure these two targets do not build simultaneously, as they produce outputs with conflicting names
274280
add_dependencies(xgboost runxgboost)
275281

dev/release-artifacts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def make_pysrc_wheel(release: str, outdir: str) -> None:
105105
os.mkdir(dist)
106106

107107
with DirectoryExcursion(os.path.join(ROOT, "python-package")):
108-
subprocess.check_call(["python", "setup.py", "sdist"])
108+
subprocess.check_call(["python", "-m", "build", "--sdist"])
109109
src = os.path.join(DIST, f"xgboost-{release}.tar.gz")
110110
subprocess.check_call(["twine", "check", src])
111111
shutil.move(src, os.path.join(dist, f"xgboost-{release}.tar.gz"))

doc/build.rst

Lines changed: 65 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ systems. If the instructions do not work for you, please feel free to ask quest
1212
Consider installing XGBoost from a pre-built binary, to avoid the trouble of building XGBoost from the source. Checkout :doc:`Installation Guide </install>`.
1313

1414
.. contents:: Contents
15+
:local:
1516

1617
.. _get_source:
1718

@@ -152,11 +153,11 @@ On Windows, run CMake as follows:
152153
153154
mkdir build
154155
cd build
155-
cmake .. -G"Visual Studio 14 2015 Win64" -DUSE_CUDA=ON
156+
cmake .. -G"Visual Studio 17 2022" -A x64 -DUSE_CUDA=ON
156157
157158
(Change the ``-G`` option appropriately if you have a different version of Visual Studio installed.)
158159

159-
The above cmake configuration run will create an ``xgboost.sln`` solution file in the build directory. Build this solution in release mode as a x64 build, either from Visual studio or from command line:
160+
The above cmake configuration run will create an ``xgboost.sln`` solution file in the build directory. Build this solution in Release mode, either from Visual studio or from command line:
160161

161162
.. code-block:: bash
162163
@@ -176,110 +177,103 @@ Building Python Package with Default Toolchains
176177
===============================================
177178
There are several ways to build and install the package from source:
178179

179-
1. Use Python setuptools directly
180+
1. Build C++ core with CMake first
180181

181-
The XGBoost Python package supports most of the setuptools commands, here is a list of tested commands:
182+
You can first build C++ library using CMake as described in :ref:`build_shared_lib`.
183+
After compilation, a shared library will appear in ``lib/`` directory.
184+
On Linux distributions, the shared library is ``lib/libxgboost.so``.
185+
The install script ``pip install .`` will reuse the shared library instead of compiling
186+
it from scratch, making it quite fast to run.
182187

183-
.. code-block:: bash
184-
185-
python setup.py install # Install the XGBoost to your current Python environment.
186-
python setup.py build # Build the Python package.
187-
python setup.py build_ext # Build only the C++ core.
188-
python setup.py sdist # Create a source distribution
189-
python setup.py bdist # Create a binary distribution
190-
python setup.py bdist_wheel # Create a binary distribution with wheel format
191-
192-
Running ``python setup.py install`` will compile XGBoost using default CMake flags. For
193-
passing additional compilation options, append the flags to the command. For example,
194-
to enable CUDA acceleration and NCCL (distributed GPU) support:
195-
196-
.. code-block:: bash
188+
.. code-block:: console
197189
198-
python setup.py install --use-cuda --use-nccl
190+
$ cd python-package/
191+
$ pip install . # Will re-use lib/libxgboost.so
199192
200-
Please refer to ``setup.py`` for a complete list of available options. Some other
201-
options used for development are only available for using CMake directly. See next
202-
section on how to use CMake with setuptools manually.
193+
2. Install the Python package directly
203194

204-
You can install the created distribution packages using pip. For example, after running
205-
``sdist`` setuptools command, a tar ball similar to ``xgboost-1.0.0.tar.gz`` will be
206-
created under the ``dist`` directory. Then you can install it by invoking the following
207-
command under ``dist`` directory:
195+
You can navigate to ``python-package/`` directory and install the Python package directly
196+
by running
208197

209-
.. code-block:: bash
198+
.. code-block:: console
210199
211-
# under python-package directory
212-
cd dist
213-
pip install ./xgboost-1.0.0.tar.gz
200+
$ cd python-package/
201+
$ pip install -v .
214202
203+
which will compile XGBoost's native (C++) code using default CMake flags.
204+
To enable additional compilation options, pass corresponding ``--config-settings``:
215205

216-
For details about these commands, please refer to the official document of `setuptools
217-
<https://setuptools.readthedocs.io/en/latest/>`_, or just Google "how to install Python
218-
package from source". XGBoost Python package follows the general convention.
219-
Setuptools is usually available with your Python distribution, if not you can install it
220-
via system command. For example on Debian or Ubuntu:
206+
.. code-block:: console
221207
222-
.. code-block:: bash
208+
$ pip install -v . --config-settings use_cuda=True --config-settings use_nccl=True
223209
224-
sudo apt-get install python-setuptools
210+
Use Pip 22.1 or later to use ``--config-settings`` option.
225211

212+
Here are the available options for ``--config-settings``:
226213

227-
For cleaning up the directory after running above commands, ``python setup.py clean`` is
228-
not sufficient. After copying out the build result, simply running ``git clean -xdf``
229-
under ``python-package`` is an efficient way to remove generated cache files. If you
230-
find weird behaviors in Python build or running linter, it might be caused by those
231-
cached files.
214+
.. literalinclude:: ../python-package/packager/build_config.py
215+
:language: python
216+
:start-at: @dataclasses.dataclass
217+
:end-before: def _set_config_setting(
232218

233-
For using develop command (editable installation), see next section.
219+
``use_system_libxgboost`` is a special option. See Item 4 below for
220+
detailed description.
234221

235-
.. code-block::
222+
.. note:: Verbose flag recommended
236223

237-
python setup.py develop # Create a editable installation.
238-
pip install -e . # Same as above, but carried out by pip.
224+
As ``pip install .`` will build C++ code, it will take a while to complete.
225+
To ensure that the build is progressing successfully, we suggest that
226+
you add the verbose flag (``-v``) when invoking ``pip install``.
239227

240228

241-
2. Build C++ core with CMake first
229+
3. Editable installation
242230

243-
This is mostly for C++ developers who don't want to go through the hooks in Python
244-
setuptools. You can build C++ library directly using CMake as described in above
245-
sections. After compilation, a shared object (or called dynamic linked library, jargon
246-
depending on your platform) will appear in XGBoost's source tree under ``lib/``
247-
directory. On Linux distributions it's ``lib/libxgboost.so``. From there all Python
248-
setuptools commands will reuse that shared object instead of compiling it again. This
249-
is especially convenient if you are using the editable installation, where the installed
250-
package is simply a link to the source tree. We can perform rapid testing during
251-
development. Here is a simple bash script does that:
231+
To further enable rapid development and iteration, we provide an **editable installation**.
232+
In an editable installation, the installed package is simply a symbolic link to your
233+
working copy of the XGBoost source code. So every changes you make to your source
234+
directory will be immediately visible to the Python interpreter. Here is how to
235+
install XGBoost as editable installation:
252236

253237
.. code-block:: bash
254238
255-
# Under xgboost source tree.
239+
# Under xgboost source directory
256240
mkdir build
257241
cd build
258-
cmake ..
259-
make -j$(nproc)
242+
# Build shared library libxgboost.so
243+
cmake .. -GNinja
244+
ninja
245+
# Install as editable installation
260246
cd ../python-package
261-
pip install -e . # or equivalently python setup.py develop
247+
pip install -e .
248+
249+
4. Use ``libxgboost.so`` on system path.
262250

263-
3. Use ``libxgboost.so`` on system path.
251+
This option is useful for package managers that wish to separately package
252+
``libxgboost.so`` and the XGBoost Python package. For example, Conda
253+
publishes ``libxgboost`` (for the shared library) and ``py-xgboost``
254+
(for the Python package).
264255

265-
This is for distributing xgboost in a language independent manner, where
266-
``libxgboost.so`` is separately packaged with Python package. Assuming `libxgboost.so`
267-
is already presented in system library path, which can be queried via:
256+
To use this option, first make sure that ``libxgboost.so`` exists in the system library path:
268257

269258
.. code-block:: python
270259
271260
import sys
272-
import os
273-
os.path.join(sys.prefix, 'lib')
261+
import pathlib
262+
libpath = pathlib.Path(sys.prefix).joinpath("lib", "libxgboost.so")
263+
assert libpath.exists()
274264
275-
Then one only needs to provide an user option when installing Python package to reuse the
276-
shared object in system path:
265+
Then pass ``use_system_libxgboost=True`` option to ``pip install``:
277266

278267
.. code-block:: bash
279268
280-
cd xgboost/python-package
281-
python setup.py install --use-system-libxgboost
269+
cd python-package
270+
pip install . --config-settings use_system_libxgboost=True
271+
272+
273+
.. note::
282274

275+
See :doc:`contrib/python_packaging` for instructions on packaging
276+
and distributing XGBoost as Python distributions.
283277

284278
.. _python_mingw:
285279

@@ -297,7 +291,7 @@ So you may want to build XGBoost with GCC own your own risk. This presents some
297291
2. ``-O3`` is OK.
298292
3. ``-mtune=native`` is also OK.
299293
4. Don't use ``-march=native`` gcc flag. Using it causes the Python interpreter to crash if the DLL was actually used.
300-
5. You may need to provide the lib with the runtime libs. If ``mingw32/bin`` is not in ``PATH``, build a wheel (``python setup.py bdist_wheel``), open it with an archiver and put the needed dlls to the directory where ``xgboost.dll`` is situated. Then you can install the wheel with ``pip``.
294+
5. You may need to provide the lib with the runtime libs. If ``mingw32/bin`` is not in ``PATH``, build a wheel (``pip wheel``), open it with an archiver and put the needed dlls to the directory where ``xgboost.dll`` is situated. Then you can install the wheel with ``pip``.
301295

302296
******************************
303297
Building R Package From Source

doc/contrib/ci.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ calls ``cibuildwheel`` to build the wheel. The ``cibuildwheel`` is a library tha
3535
suitable Python environment for each OS and processor target. Since we don't have Apple Silion
3636
machine in GitHub Actions, cross-compilation is needed; ``cibuildwheel`` takes care of the complex
3737
task of cross-compiling a Python wheel. (Note that ``cibuildwheel`` will call
38-
``setup.py bdist_wheel``. Since XGBoost has a native library component, ``setup.py`` contains
39-
a glue code to call CMake and a C++ compiler to build the native library on the fly.)
38+
``pip wheel``. Since XGBoost has a native library component, we created a customized build
39+
backend that hooks into ``pip``. The customized backend contains the glue code to compile the native
40+
library on the fly.)
4041

4142
*********************************************************
4243
Reproduce CI testing environments using Docker containers

doc/contrib/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Here are guidelines for contributing to various aspect of the XGBoost project:
2323
Community Guideline <community>
2424
donate
2525
coding_guide
26+
python_packaging
2627
unit_tests
2728
Docs and Examples <docs>
2829
git_guide

doc/contrib/python_packaging.rst

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
###########################################
2+
Notes on packaging XGBoost's Python package
3+
###########################################
4+
5+
6+
.. contents:: Contents
7+
:local:
8+
9+
.. _packaging_python_xgboost:
10+
11+
***************************************************
12+
How to build binary wheels and source distributions
13+
***************************************************
14+
15+
Wheels and source distributions (sdist for short) are the two main
16+
mechanisms for packaging and distributing Python packages.
17+
18+
* A **source distribution** (sdist) is a tarball (``.tar.gz`` extension) that
19+
contains the source code.
20+
* A **wheel** is a ZIP-compressed archive (with ``.whl`` extension)
21+
representing a *built* distribution. Unlike an sdist, a wheel can contain
22+
compiled components. The compiled components are compiled prior to distribution,
23+
making it more convenient for end-users to install a wheel. Wheels containing
24+
compiled components are referred to as **binary wheels**.
25+
26+
See `Python Packaging User Guide <https://packaging.python.org/en/latest/>`_
27+
to learn more about how Python packages in general are packaged and
28+
distributed.
29+
30+
For the remainder of this document, we will focus on packaging and
31+
distributing XGBoost.
32+
33+
Building sdists
34+
===============
35+
36+
In the case of XGBoost, an sdist contains both the Python code as well as
37+
the C++ code, so that the core part of XGBoost can be compiled into the
38+
shared libary ``libxgboost.so`` [#shared_lib_name]_.
39+
40+
You can obtain an sdist as follows:
41+
42+
.. code-block:: console
43+
44+
$ python -m build --sdist .
45+
46+
(You'll need to install the ``build`` package first:
47+
``pip install build`` or ``conda install python-build``.)
48+
49+
Running ``pip install`` with an sdist will launch CMake and a C++ compiler
50+
to compile the bundled C++ code into ``libxgboost.so``:
51+
52+
.. code-block:: console
53+
54+
$ pip install -v xgboost-2.0.0.tar.gz # Add -v to show build progress
55+
56+
Building binary wheels
57+
======================
58+
59+
You can also build a wheel as follows:
60+
61+
.. code-block:: console
62+
63+
$ pip wheel --no-deps -v .
64+
65+
Notably, the resulting wheel contains a copy of the shared library
66+
``libxgboost.so`` [#shared_lib_name]_. The wheel is a **binary wheel**,
67+
since it contains a compiled binary.
68+
69+
70+
Running ``pip install`` with the binary wheel will extract the content of
71+
the wheel into the current Python environment. Since the wheel already
72+
contains a pre-built copy of ``libxgboost.so``, it does not have to be
73+
built at the time of install. So ``pip install`` with the binary wheel
74+
completes quickly:
75+
76+
.. code-block:: console
77+
78+
$ pip install xgboost-2.0.0-py3-none-linux_x86_64.whl # Completes quickly
79+
80+
.. rubric:: Footnotes
81+
82+
.. [#shared_lib_name] The name of the shared library file will differ
83+
depending on the operating system in use. See :ref:`build_shared_lib`.

0 commit comments

Comments
 (0)