diff --git a/cmake/Python_version.in b/cmake/Python_version.in deleted file mode 100644 index 67b48af46..000000000 --- a/cmake/Python_version.in +++ /dev/null @@ -1 +0,0 @@ -@xgboost_VERSION_MAJOR@.@xgboost_VERSION_MINOR@.@xgboost_VERSION_PATCH@-dev \ No newline at end of file diff --git a/cmake/Version.cmake b/cmake/Version.cmake index f38ce3ce3..ea8c081dc 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -3,7 +3,4 @@ function (write_version) configure_file( ${xgboost_SOURCE_DIR}/cmake/version_config.h.in ${xgboost_SOURCE_DIR}/include/xgboost/version_config.h @ONLY) - configure_file( - ${xgboost_SOURCE_DIR}/cmake/Python_version.in - ${xgboost_SOURCE_DIR}/python-package/xgboost/VERSION @ONLY) endfunction (write_version) diff --git a/doc/contrib/release.rst b/doc/contrib/release.rst index d43f20550..2799d8fe2 100644 --- a/doc/contrib/release.rst +++ b/doc/contrib/release.rst @@ -17,18 +17,7 @@ Making a Release ----------------- 1. Create an issue for the release, noting the estimated date and expected features or major fixes, pin that issue. -2. Create a release branch if this is a major release. Bump release version. - - 1. Modify ``CMakeLists.txt`` in source tree and ``cmake/Python_version.in`` if needed, run CMake. - - If this is a RC release, the Python version has the form ..rc1 - - 2. Modify ``DESCRIPTION`` and ``configure.ac`` in R-package. Run ``autoreconf``. - - 3. Run ``change_version.sh`` in ``jvm-packages/dev`` - - If this is a RC release, the version for JVM packages has the form ..-RC1 - +2. Create a release branch if this is a major release. Bump release version. There's a helper script ``tests/ci_build/change_version.py``. 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. diff --git a/jvm-packages/dev/change_version.sh b/jvm-packages/dev/change_version.sh deleted file mode 100755 index 1575a5142..000000000 --- a/jvm-packages/dev/change_version.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -# (Yizhi) This is mainly inspired by the script in apache/spark. -# I did some modifications to get it with our project. -# (Nan) Modified from MxNet -# -set -e - -if [[ ($# -ne 2) || ( $1 == "--help") || $1 == "-h" ]]; then - echo "Usage: $(basename $0) [-h|--help] " 1>&2 - exit 1 -fi - -FROM_VERSION=$1 -TO_VERSION=$2 - -sed_i() { - perl -p -000 -e "$1" "$2" > "$2.tmp" && mv "$2.tmp" "$2" -} - -export -f sed_i - -BASEDIR=$(dirname $0)/.. -find "$BASEDIR" -name 'pom.xml' -not -path '*target*' -print \ - -exec bash -c \ - "sed_i 's/((xgboost-jvm.*|xgboost4j.*)<\/artifactId>\s+'$FROM_VERSION'(<\/version>)/\1>'$TO_VERSION'\3/g' {}" \; diff --git a/tests/ci_build/change_version.py b/tests/ci_build/change_version.py new file mode 100644 index 000000000..79f454bba --- /dev/null +++ b/tests/ci_build/change_version.py @@ -0,0 +1,141 @@ +""" +1. Modify ``CMakeLists.txt`` in source tree and ``python-package/xgboost/VERSION`` if +needed, run CMake . + If this is a RC release, the Python version has the form ..rc1 +2. Modify ``DESCRIPTION`` and ``configure.ac`` in R-package. Run ``autoreconf``. +3. Run ``mvn`` in ``jvm-packages`` + If this is a RC release, the version for JVM packages has the form + ..-RC1 +""" +import argparse +import os +import re +import subprocess +import sys +import tempfile + +from test_utils import JVM_PACKAGES, PY_PACKAGE, R_PACKAGE, ROOT, cd + + +@cd(ROOT) +def cmake(major: int, minor: int, patch: int) -> None: + version = f"{major}.{minor}.{patch}" + with open("CMakeLists.txt", "r") as fd: + cmakelist = fd.read() + pattern = r"project\(xgboost LANGUAGES .* VERSION ([0-9]+\.[0-9]+\.[0-9]+)\)" + matched = re.search(pattern, cmakelist) + assert matched, "Couldn't find the version string in CMakeLists.txt." + print(matched.start(1), matched.end(1)) + cmakelist = cmakelist[: matched.start(1)] + version + cmakelist[matched.end(1) :] + with open("CMakeLists.txt", "w") as fd: + fd.write(cmakelist) + + with tempfile.TemporaryDirectory() as tmpdir: + subprocess.call(["cmake", "-S", ".", "-B", tmpdir]) + + +@cd(PY_PACKAGE) +def pypkg( + major: int, minor: int, patch: int, rc: int, is_rc: bool, is_dev: bool +) -> None: + version = f"{major}.{minor}.{patch}" + pyver_path = os.path.join("xgboost", "VERSION") + pyver = version + if is_rc: + pyver = pyver + f"rc{rc}" + if is_dev: + pyver = pyver + "-dev" + with open(pyver_path, "w") as fd: + fd.write(pyver) + + +@cd(R_PACKAGE) +def rpkg(major: int, minor: int, patch: int) -> None: + version = f"{major}.{minor}.{patch}.1" + # Version: 2.0.0.1 + desc_path = "DESCRIPTION" + with open(desc_path, "r") as fd: + description = fd.read() + pattern = r"Version:\ ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)" + matched = re.search(pattern, description) + assert matched, "Couldn't find version string in DESCRIPTION." + description = ( + description[: matched.start(1)] + version + description[matched.end(1) :] + ) + with open(desc_path, "w") as fd: + fd.write(description) + + config_path = "configure.ac" + # AC_INIT([xgboost],[2.0.0],[],[xgboost],[]) + version = f"{major}.{minor}.{patch}" + with open(config_path, "r") as fd: + config = fd.read() + pattern = ( + r"AC_INIT\(\[xgboost\],\[([0-9]+\.[0-9]+\.[0-9]+)\],\[\],\[xgboost\],\[\]\)" + ) + matched = re.search(pattern, config) + assert matched, "Couldn't find version string in configure.ac" + config = config[: matched.start(1)] + version + config[matched.end(1) :] + + with open(config_path, "w") as fd: + fd.write(config) + + subprocess.check_call(["autoreconf"]) + + +@cd(JVM_PACKAGES) +def jvmpkgs( + major: int, minor: int, patch: int, rc: int, is_rc: bool, is_dev: bool +) -> None: + version = f"{major}.{minor}.{patch}" + if is_dev: + version += "-SNAPSHOT" + if is_rc: + version += f"-RC{rc}" + subprocess.check_call(["mvn", "versions:set", f"-DnewVersion={version}"]) + + +@cd(ROOT) +def main(args: argparse.Namespace) -> None: + major = args.major + minor = args.minor + patch = args.patch + rc = args.rc + is_rc = args.is_rc == 1 + is_dev = args.is_dev == 1 + if is_rc and is_dev: + raise ValueError("It cannot be both a rc and a dev branch.") + if is_rc: + assert rc >= 1, "RC version starts from 1." + else: + assert rc == 0, "RC is not used." + + cmake(major, minor, patch) + pypkg(major, minor, patch, rc, is_rc, is_dev) + rpkg(major, minor, patch) + jvmpkgs(major, minor, patch, rc, is_rc, is_dev) + + print( + """ + +Please examine the changes and commit. Be aware that mvn might leave backup files in the +source tree. + +""" + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--major", type=int) + parser.add_argument("--minor", type=int) + parser.add_argument("--patch", type=int) + parser.add_argument("--rc", type=int, default=0) + parser.add_argument("--is-rc", type=int, choices=[0, 1]) + parser.add_argument("--is-dev", type=int, choices=[0, 1]) + args = parser.parse_args() + try: + main(args) + except Exception as e: + print("Error:", e, file=sys.stderr) + exit(-1) diff --git a/tests/ci_build/lint_python.py b/tests/ci_build/lint_python.py index da26b317c..f5390946c 100644 --- a/tests/ci_build/lint_python.py +++ b/tests/ci_build/lint_python.py @@ -6,10 +6,9 @@ from multiprocessing import Pool, cpu_count from typing import Dict, Tuple from pylint import epylint -from test_utils import DirectoryExcursion, print_time, record_time +from test_utils import PY_PACKAGE, ROOT, cd, print_time, record_time CURDIR = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) -PROJECT_ROOT = os.path.normpath(os.path.join(CURDIR, os.path.pardir, os.path.pardir)) @record_time @@ -45,20 +44,20 @@ Please run the following command on your machine to address the formatting error @record_time +@cd(PY_PACKAGE) def run_mypy(rel_path: str) -> bool: - with DirectoryExcursion(os.path.join(PROJECT_ROOT, "python-package")): - path = os.path.join(PROJECT_ROOT, rel_path) - ret = subprocess.run(["mypy", path]) - if ret.returncode != 0: - return False - return True + path = os.path.join(ROOT, rel_path) + ret = subprocess.run(["mypy", path]) + if ret.returncode != 0: + return False + return True class PyLint: """A helper for running pylint, mostly copied from dmlc-core/scripts.""" def __init__(self) -> None: - self.pypackage_root = os.path.join(PROJECT_ROOT, "python-package/") + self.pypackage_root = os.path.join(ROOT, "python-package/") self.pylint_cats = set(["error", "warning", "convention", "refactor"]) self.pylint_opts = [ "--extension-pkg-whitelist=numpy", @@ -147,9 +146,6 @@ def main(args: argparse.Namespace) -> None: "tests/python/test_data_iterator.py", "tests/python/test_quantile_dmatrix.py", "tests/python-gpu/test_gpu_data_iterator.py", - "tests/ci_build/lint_python.py", - "tests/ci_build/test_r_package.py", - "tests/ci_build/test_utils.py", "tests/test_distributed/test_with_spark/", "tests/test_distributed/test_gpu_with_spark/", # demo @@ -157,6 +153,11 @@ def main(args: argparse.Namespace) -> None: "demo/guide-python/cat_in_the_dat.py", "demo/guide-python/categorical.py", "demo/guide-python/spark_estimator_examples.py", + # CI + "tests/ci_build/lint_python.py", + "tests/ci_build/test_r_package.py", + "tests/ci_build/test_utils.py", + "tests/ci_build/change_version.py", ] ] if not all(black_results): @@ -195,14 +196,17 @@ def main(args: argparse.Namespace) -> None: # tests "tests/python/test_data_iterator.py", "tests/python-gpu/test_gpu_data_iterator.py", - "tests/ci_build/lint_python.py", - "tests/ci_build/test_r_package.py", - "tests/ci_build/test_utils.py", "tests/test_distributed/test_with_spark/test_data.py", "tests/test_distributed/test_gpu_with_spark/test_data.py", "tests/test_distributed/test_gpu_with_dask/test_gpu_with_dask.py", + # CI + "tests/ci_build/lint_python.py", + "tests/ci_build/test_r_package.py", + "tests/ci_build/test_utils.py", + "tests/ci_build/change_version.py", ] ): + subprocess.check_call(["mypy", "--version"]) sys.exit(-1) if args.pylint == 1: diff --git a/tests/ci_build/test_r_package.py b/tests/ci_build/test_r_package.py index ea166676d..663fd1749 100644 --- a/tests/ci_build/test_r_package.py +++ b/tests/ci_build/test_r_package.py @@ -6,14 +6,7 @@ import subprocess from pathlib import Path from platform import system -from test_utils import DirectoryExcursion, cd, print_time, record_time - -ROOT = os.path.normpath( - os.path.join( - os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir - ) -) -r_package = os.path.join(ROOT, "R-package") +from test_utils import R_PACKAGE, ROOT, DirectoryExcursion, cd, print_time, record_time def get_mingw_bin() -> str: @@ -153,7 +146,7 @@ def check_rpackage(path: str) -> None: raise ValueError("Suspicious NOTE.") -@cd(r_package) +@cd(R_PACKAGE) @record_time def check_rmarkdown() -> None: assert system() != "Windows", "Document test doesn't support Windows." @@ -171,7 +164,7 @@ def check_rmarkdown() -> None: ) -@cd(r_package) +@cd(R_PACKAGE) @record_time def test_with_autotools() -> None: """Windows only test. No `--as-cran` check, only unittests. We don't want to manage @@ -240,7 +233,7 @@ def test_with_cmake(args: argparse.Namespace) -> None: ) else: raise ValueError("Wrong compiler") - with DirectoryExcursion(r_package): + with DirectoryExcursion(R_PACKAGE): subprocess.check_call( [ R, diff --git a/tests/ci_build/test_utils.py b/tests/ci_build/test_utils.py index b44fe207a..d4fa02629 100644 --- a/tests/ci_build/test_utils.py +++ b/tests/ci_build/test_utils.py @@ -70,3 +70,13 @@ def print_time() -> None: "Elapsed:", f"{v['total'].seconds} secs", ) + + +ROOT = os.path.normpath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir + ) +) +R_PACKAGE = os.path.join(ROOT, "R-package") +JVM_PACKAGES = os.path.join(ROOT, "jvm-packages") +PY_PACKAGE = os.path.join(ROOT, "python-package")