Add script for change version. (#8443)

- Replace jvm regex replacement script with mvn command.
- Replace cmake script for python version with python script.
- Automate rest of the manual steps.

The script can handle dev branch, rc release, and formal release version.
This commit is contained in:
Jiaming Yuan 2022-11-24 00:06:39 +08:00 committed by GitHub
parent 5f1a6fca0d
commit 284dcf8d22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 175 additions and 85 deletions

View File

@ -1 +0,0 @@
@xgboost_VERSION_MAJOR@.@xgboost_VERSION_MINOR@.@xgboost_VERSION_PATCH@-dev

View File

@ -3,7 +3,4 @@ function (write_version)
configure_file( configure_file(
${xgboost_SOURCE_DIR}/cmake/version_config.h.in ${xgboost_SOURCE_DIR}/cmake/version_config.h.in
${xgboost_SOURCE_DIR}/include/xgboost/version_config.h @ONLY) ${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) endfunction (write_version)

View File

@ -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. 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. 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``.
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 <major>.<minor>.<patch>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 <major>.<minor>.<patch>-RC1
3. Commit the change, create a PR on GitHub on release branch. Port the bumped version to default branch, optionally with the postfix ``SNAPSHOT``. 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. 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. 5. Make a release on GitHub tag page, which might be done with previous step if the tag is created on GitHub.

View File

@ -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] <from_version> <to_version>" 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/(<artifactId>(xgboost-jvm.*|xgboost4j.*)<\/artifactId>\s+<version)>'$FROM_VERSION'(<\/version>)/\1>'$TO_VERSION'\3/g' {}" \;

View File

@ -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 <major>.<minor>.<patch>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
<major>.<minor>.<patch>-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)

View File

@ -6,10 +6,9 @@ from multiprocessing import Pool, cpu_count
from typing import Dict, Tuple from typing import Dict, Tuple
from pylint import epylint 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__))) 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 @record_time
@ -45,9 +44,9 @@ Please run the following command on your machine to address the formatting error
@record_time @record_time
@cd(PY_PACKAGE)
def run_mypy(rel_path: str) -> bool: def run_mypy(rel_path: str) -> bool:
with DirectoryExcursion(os.path.join(PROJECT_ROOT, "python-package")): path = os.path.join(ROOT, rel_path)
path = os.path.join(PROJECT_ROOT, rel_path)
ret = subprocess.run(["mypy", path]) ret = subprocess.run(["mypy", path])
if ret.returncode != 0: if ret.returncode != 0:
return False return False
@ -58,7 +57,7 @@ class PyLint:
"""A helper for running pylint, mostly copied from dmlc-core/scripts.""" """A helper for running pylint, mostly copied from dmlc-core/scripts."""
def __init__(self) -> None: 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_cats = set(["error", "warning", "convention", "refactor"])
self.pylint_opts = [ self.pylint_opts = [
"--extension-pkg-whitelist=numpy", "--extension-pkg-whitelist=numpy",
@ -147,9 +146,6 @@ def main(args: argparse.Namespace) -> None:
"tests/python/test_data_iterator.py", "tests/python/test_data_iterator.py",
"tests/python/test_quantile_dmatrix.py", "tests/python/test_quantile_dmatrix.py",
"tests/python-gpu/test_gpu_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/", "tests/test_distributed/test_with_spark/",
"tests/test_distributed/test_gpu_with_spark/", "tests/test_distributed/test_gpu_with_spark/",
# demo # demo
@ -157,6 +153,11 @@ def main(args: argparse.Namespace) -> None:
"demo/guide-python/cat_in_the_dat.py", "demo/guide-python/cat_in_the_dat.py",
"demo/guide-python/categorical.py", "demo/guide-python/categorical.py",
"demo/guide-python/spark_estimator_examples.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): if not all(black_results):
@ -195,14 +196,17 @@ def main(args: argparse.Namespace) -> None:
# tests # tests
"tests/python/test_data_iterator.py", "tests/python/test_data_iterator.py",
"tests/python-gpu/test_gpu_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_with_spark/test_data.py",
"tests/test_distributed/test_gpu_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", "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) sys.exit(-1)
if args.pylint == 1: if args.pylint == 1:

View File

@ -6,14 +6,7 @@ import subprocess
from pathlib import Path from pathlib import Path
from platform import system from platform import system
from test_utils import DirectoryExcursion, cd, print_time, record_time from test_utils import R_PACKAGE, ROOT, 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")
def get_mingw_bin() -> str: def get_mingw_bin() -> str:
@ -153,7 +146,7 @@ def check_rpackage(path: str) -> None:
raise ValueError("Suspicious NOTE.") raise ValueError("Suspicious NOTE.")
@cd(r_package) @cd(R_PACKAGE)
@record_time @record_time
def check_rmarkdown() -> None: def check_rmarkdown() -> None:
assert system() != "Windows", "Document test doesn't support Windows." 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 @record_time
def test_with_autotools() -> None: def test_with_autotools() -> None:
"""Windows only test. No `--as-cran` check, only unittests. We don't want to manage """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: else:
raise ValueError("Wrong compiler") raise ValueError("Wrong compiler")
with DirectoryExcursion(r_package): with DirectoryExcursion(R_PACKAGE):
subprocess.check_call( subprocess.check_call(
[ [
R, R,

View File

@ -70,3 +70,13 @@ def print_time() -> None:
"Elapsed:", "Elapsed:",
f"{v['total'].seconds} secs", 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")