diff --git a/dev/release-pypi.py b/dev/release-pypi.py new file mode 100644 index 000000000..4e14c36dd --- /dev/null +++ b/dev/release-pypi.py @@ -0,0 +1,127 @@ +"""Simple script for downloading and checking pypi release wheels. + +tqdm, sh are required to run this script. +""" +from urllib.request import urlretrieve +import argparse +from typing import List +from sh.contrib import git +from distutils import version +import subprocess +import tqdm +import os + +# The package building is managed by Jenkins CI. +PREFIX = "https://s3-us-west-2.amazonaws.com/xgboost-nightly-builds/release_" +DIST = os.path.join(os.path.curdir, "python-package", "dist") + +pbar = None + + +def show_progress(block_num, block_size, total_size): + "Show file download progress." + global pbar + if pbar is None: + pbar = tqdm.tqdm(total=total_size / 1024, unit="kB") + + downloaded = block_num * block_size + if downloaded < total_size: + pbar.update(block_size / 1024) + else: + pbar.close() + pbar = None + + +def retrieve(url, filename=None): + return urlretrieve(url, filename, reporthook=show_progress) + + +def lastest_hash() -> str: + "Get latest commit hash." + ret = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True) + assert ret.returncode == 0, "Failed to get lastest commit hash." + commit_hash = ret.stdout.decode("utf-8").strip() + return commit_hash + + +def download_wheels( + platforms: List[str], + dir_URL: str, + src_filename_prefix: str, + target_filename_prefix: str, +) -> None: + """Download all binary wheels. dir_URL is the URL for remote directory storing the release + wheels + + """ + + filenames = [] + for platform in platforms: + src_wheel = src_filename_prefix + platform + ".whl" + url = dir_URL + src_wheel + + target_wheel = target_filename_prefix + platform + ".whl" + filename = os.path.join(DIST, target_wheel) + filenames.append(filename) + print("Downloading from:", url, "to:", filename) + retrieve(url=url, filename=filename) + ret = subprocess.run(["twine", "check", filename], capture_output=True) + assert ret.returncode == 0, "Failed twine check" + stderr = ret.stderr.decode("utf-8") + stdout = ret.stdout.decode("utf-8") + assert stderr.find("warning") == -1, "Unresolved warnings:\n" + stderr + assert stdout.find("warning") == -1, "Unresolved warnings:\n" + stdout + + +def check_path(): + root = os.path.abspath(os.path.curdir) + assert os.path.basename(root) == "xgboost", "Must be run on project root." + + +def main(args: argparse.Namespace) -> None: + check_path() + + rel = version.StrictVersion(args.release) + platforms = [ + "win_amd64", + "manylinux2010_x86_64", + "manylinux2014_aarch64", + "macosx_10_14_x86_64.macosx_10_15_x86_64.macosx_11_0_x86_64", + ] + print("Release:", rel) + major, minor, patch = rel.version + branch = "release_" + str(major) + "." + str(minor) + ".0" + git.clean("-xdf") + git.checkout(branch) + git.pull("origin", branch) + git.submodule("update") + commit_hash = lastest_hash() + + dir_URL = PREFIX + str(major) + "." + str(minor) + ".0" + "/" + src_filename_prefix = "xgboost-" + args.release + "%2B" + commit_hash + "-py3-none-" + target_filename_prefix = "xgboost-" + args.release + "-py3-none-" + + if not os.path.exists(DIST): + os.mkdir(DIST) + + filenames = download_wheels( + platforms, dir_URL, src_filename_prefix, target_filename_prefix + ) + print("List of downloaded wheels:", filenames) + print( + """ +Following steps should be done manually: +- Generate source package by running `python setup.py sdist`. +- Upload pypi package by `python3 -m twine upload dist/` for all wheels. +- Check the uploaded files on `https://pypi.org/project/xgboost//#files` and `pip + install xgboost==` """ + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--release", type=str, required=True, help="Version tag, e.g. '1.3.2'." + ) + args = parser.parse_args() + main(args) diff --git a/doc/contrib/release.rst b/doc/contrib/release.rst index 7980bcdee..ae3e378b9 100644 --- a/doc/contrib/release.rst +++ b/doc/contrib/release.rst @@ -11,3 +11,20 @@ Starting from XGBoost 1.0.0, each XGBoost release will be versioned as [MAJOR].[ * MAJOR: We gurantee the API compatibility across releases with the same major version number. We expect to have a 1+ years development period for a new MAJOR release version. * FEATURE: We ship new features, improvements and bug fixes through feature releases. The cycle length of a feature is decided by the size of feature roadmap. The roadmap is decided right after the previous release. * MAINTENANCE: Maintenance version only contains bug fixes. This type of release only occurs when we found significant correctness and/or performance bugs and barrier for users to upgrade to a new version of XGBoost smoothly. + + +Making a Release +----------------- + +1. Create an issue for the release, noting the estimated date and expected features or major fixes, pin that issue. +2. Bump release version. + 1. Modify ``CMakeLists.txt`` source tree, run CMake. + 2. Modify ``DESCRIPTION`` in R-package. + 3. Run ``change_version.sh`` in ``jvm-packages/dev`` +3. Commit the change, create a PR on github on release branch. Port the bumped version to default branch, optionally with the postfix ``SNAPSHOT``. +4. Create a tag on release branch, either on github or locally. +5. Make a release on github tag page, which might be done with previous step if the tag is created on github. +6. Submit pip, cran and maven packages. + - pip package is maintained by [Hyunsu Cho](http://hyunsu-cho.io/) and [Jiaming Yuan](https://github.com/trivialfis). There's a helper script for downloading pre-built wheels on ``xgboost/dev/release-pypi.py`` along with simple instructions for using ``twine``. + - cran package is maintained by [Tong He](https://github.com/hetong007). + - maven packageis maintained by [Nan Zhu](https://github.com/CodingCat). diff --git a/python-package/setup.py b/python-package/setup.py index 62440669e..d1bcc42a0 100644 --- a/python-package/setup.py +++ b/python-package/setup.py @@ -305,6 +305,7 @@ if __name__ == '__main__': description="XGBoost Python Package", long_description=open(os.path.join(CURRENT_DIR, 'README.rst'), encoding='utf-8').read(), + long_description_content_type="text/x-rst", install_requires=[ 'numpy', 'scipy',