| # 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. |
| |
| |
| import argparse |
| import logging |
| import os |
| import re |
| import shutil |
| import subprocess |
| |
| logging.basicConfig(level=logging.INFO) |
| logger = logging.getLogger(__name__) |
| |
| PROJECT_ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../") |
| |
| |
| def prepare(v: str): |
| """Create a new release branch""" |
| logger.info("Start to prepare release branch for version %s", v) |
| _check_release_version(v) |
| os.chdir(PROJECT_ROOT_DIR) |
| branch = f"releases-{v}" |
| try: |
| subprocess.check_call(f"git checkout -b {branch}", shell=True) |
| bump_version(version=v, l="all") |
| subprocess.check_call("git add -u", shell=True) |
| subprocess.check_call(f"git commit -m 'prepare release for {v}'", shell=True) |
| except BaseException: |
| logger.exception("Prepare branch failed") |
| subprocess.check_call(f"git checkout - && git branch -D {branch}", shell=True) |
| raise |
| |
| |
| def build(v: str): |
| """version format: 0.5.1""" |
| logger.info("Start to prepare release artifacts for version %s", v) |
| _check_release_version(v) |
| os.chdir(PROJECT_ROOT_DIR) |
| if os.path.exists("dist"): |
| shutil.rmtree("dist") |
| os.mkdir("dist") |
| branch = f"releases-{v}" |
| # Check if branch exists, if not create it |
| result = subprocess.run( |
| f"git show-ref --verify --quiet refs/heads/{branch}", |
| shell=True, |
| capture_output=True, |
| ) |
| if result.returncode == 0: |
| # Branch exists, checkout |
| subprocess.check_call(f"git checkout {branch}", shell=True) |
| else: |
| # Branch doesn't exist, create it |
| subprocess.check_call(f"git checkout -b {branch}", shell=True) |
| src_tar = f"apache-fory-{v}-src.tar.gz" |
| _check_all_committed() |
| _strip_unnecessary_license() |
| subprocess.check_call( |
| "git add LICENSE && git commit -m 'remove benchmark from license'", shell=True |
| ) |
| subprocess.check_call( |
| f"git archive --format=tar.gz " |
| f"--output=dist/{src_tar} " |
| f"--prefix=apache-fory-{v}-src/ {branch}", |
| shell=True, |
| ) |
| subprocess.check_call("git reset --hard HEAD~", shell=True) |
| os.chdir("dist") |
| logger.info("Start to generate signature") |
| subprocess.check_call( |
| f"gpg --armor --output {src_tar}.asc --detach-sig {src_tar}", shell=True |
| ) |
| subprocess.check_call(f"sha512sum {src_tar} >{src_tar}.sha512", shell=True) |
| verify(v) |
| |
| |
| def _check_release_version(v: str): |
| assert v |
| if "rc" in v: |
| raise ValueError( |
| "RC should only be contained in tag and svn directory, not in code" |
| ) |
| |
| |
| def _check_all_committed(): |
| proc = subprocess.run("git diff --quiet", capture_output=True, shell=True) |
| result = proc.returncode |
| if result != 0: |
| raise Exception( |
| f"There are some uncommitted files: {proc.stdout}, please commit it." |
| ) |
| |
| |
| def _strip_unnecessary_license(): |
| with open("LICENSE", "r") as f: |
| lines = f.readlines() |
| new_lines = [] |
| line_number = 0 |
| while line_number < len(lines): |
| line = lines[line_number] |
| if "fast-serialization" in line: |
| line_number += 4 |
| elif "benchmark" in line: # strip license in benchmark |
| line_number += 1 |
| else: |
| new_lines.append(line) |
| line_number += 1 |
| text = "".join(new_lines) |
| if lines != new_lines: |
| with open("LICENSE", "w") as f: |
| f.write(text) |
| |
| |
| def verify(v): |
| src_tar = f"apache-fory-{v}-src.tar.gz" |
| subprocess.check_call(f"gpg --verify {src_tar}.asc {src_tar}", shell=True) |
| logger.info("Verified signature") |
| subprocess.check_call(f"sha512sum --check {src_tar}.sha512", shell=True) |
| logger.info("Verified checksum successfully") |
| |
| |
| def bump_version(**kwargs): |
| new_version = kwargs["version"] |
| langs = kwargs["l"] |
| if langs == "all": |
| langs = ["java", "python", "javascript", "scala", "rust", "kotlin"] |
| else: |
| langs = langs.split(",") |
| for lang in langs: |
| if lang == "java": |
| bump_java_version(new_version) |
| elif lang == "scala": |
| _bump_version("scala", "build.sbt", new_version, _update_scala_version) |
| elif lang == "kotlin": |
| _bump_version("kotlin", "pom.xml", new_version, _update_kotlin_version) |
| elif lang == "rust": |
| _bump_version("rust", "Cargo.toml", new_version, _update_rust_version) |
| elif lang == "python": |
| _bump_version( |
| "python/pyfory", "__init__.py", new_version, _update_python_version |
| ) |
| elif lang == "javascript": |
| _bump_version( |
| "javascript/packages/fory", |
| "package.json", |
| new_version, |
| _update_js_version, |
| ) |
| _bump_version( |
| "javascript/packages/hps", |
| "package.json", |
| new_version, |
| _update_js_version, |
| ) |
| else: |
| raise NotImplementedError(f"Unsupported {lang}") |
| |
| |
| def _bump_version(path, file, new_version, func): |
| os.chdir(os.path.join(PROJECT_ROOT_DIR, path)) |
| with open(file, "r") as f: |
| lines = f.readlines() |
| lines = func(lines, new_version) or lines |
| text = "".join(lines) |
| with open(file, "w") as f: |
| f.write(text) |
| |
| |
| def bump_java_version(new_version): |
| for p in [ |
| "integration_tests/graalvm_tests", |
| "integration_tests/jdk_compatibility_tests", |
| "integration_tests/jpms_tests", |
| "integration_tests/latest_jdk_tests", |
| "integration_tests/latest_jdk_tests", |
| "java/benchmark", |
| "java/fory-core", |
| "java/fory-format", |
| "java/fory-simd", |
| "java/fory-extensions", |
| "java/fory-test-core", |
| "java/fory-testsuite", |
| ]: |
| _bump_version(p, "pom.xml", new_version, _update_pom_parent_version) |
| # mvn versions:set too slow |
| # os.chdir(os.path.join(PROJECT_ROOT_DIR, "java")) |
| # subprocess.check_output( |
| # f"mvn versions:set -DnewVersion={new_version}", |
| # shell=True, |
| # universal_newlines=True, |
| # ) |
| _bump_version("java", "pom.xml", new_version, _update_parent_pom_version) |
| |
| |
| def _update_pom_parent_version(lines, new_version): |
| start_index, end_index = -1, -1 |
| for i, line in enumerate(lines): |
| if "<parent>" in line: |
| start_index = i |
| if "</parent>" in line: |
| end_index = i |
| break |
| assert start_index != -1 |
| assert end_index != -1 |
| for line_number in range(start_index, end_index): |
| line = lines[line_number] |
| if "version" in line: |
| line = re.sub( |
| r"(<version>)[^<>]+(</version>)", r"\g<1>" + new_version + r"\2", line |
| ) |
| lines[line_number] = line |
| |
| |
| def _update_scala_version(lines, v): |
| for index, line in enumerate(lines): |
| if "foryVersion = " in line: |
| lines[index] = f'val foryVersion = "{v}"\n' |
| break |
| return lines |
| |
| |
| def _update_kotlin_version(lines, v): |
| return _update_pom_version(lines, v, "<artifactId>fory-kotlin</artifactId>") |
| |
| |
| def _update_parent_pom_version(lines, v): |
| return _update_pom_version(lines, v, "<packaging>pom</packaging>") |
| |
| |
| def _update_pom_version(lines, v, prev): |
| target_index = -1 |
| for index, line in enumerate(lines): |
| if prev in line: |
| target_index = index + 1 |
| break |
| current_version_line = lines[target_index] |
| # Find the start and end of the version number |
| start = current_version_line.index("<version>") + len("<version>") |
| end = current_version_line.index("</version>") |
| # Replace the version number |
| updated_version_line = current_version_line[:start] + v + current_version_line[end:] |
| lines[target_index] = updated_version_line |
| return lines |
| |
| |
| def _update_rust_version(lines, v): |
| for index, line in enumerate(lines): |
| if "version = " in line: |
| lines[index] = f'version = "{v}"\n' |
| break |
| return lines |
| |
| |
| def _update_python_version(lines, v: str): |
| for index, line in enumerate(lines): |
| if "__version__ = " in line: |
| v = v.replace("-alpha", "a") |
| v = v.replace("-beta", "b") |
| v = v.replace("-rc", "rc") |
| v = v.replace("-", "") |
| lines[index] = f'__version__ = "{v}"\n' |
| break |
| |
| |
| def _update_js_version(lines, v: str): |
| for index, line in enumerate(lines): |
| if "version" in line: |
| # "version": "0.5.9-beta" |
| for x in ["-alpha", "-beta", "-rc"]: |
| if x in v and v.split(x)[-1].isdigit(): |
| v = v.replace(x, x + ".") |
| lines[index] = f' "version": "{v}",\n' |
| break |
| |
| |
| def _parse_args(): |
| parser = argparse.ArgumentParser( |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter |
| ) |
| parser.set_defaults(func=parser.print_help) |
| subparsers = parser.add_subparsers() |
| bump_version_parser = subparsers.add_parser( |
| "bump_version", |
| description="Bump version", |
| ) |
| bump_version_parser.add_argument("-version", type=str, help="new version") |
| bump_version_parser.add_argument("-l", type=str, help="language") |
| bump_version_parser.set_defaults(func=bump_version) |
| |
| prepare_parser = subparsers.add_parser( |
| "prepare", |
| description="Prepare release branch", |
| ) |
| prepare_parser.add_argument("-v", type=str, help="new version") |
| prepare_parser.set_defaults(func=prepare) |
| |
| release_parser = subparsers.add_parser( |
| "build", |
| description="Build release artifacts", |
| ) |
| release_parser.add_argument("-v", type=str, help="new version") |
| release_parser.set_defaults(func=build) |
| |
| verify_parser = subparsers.add_parser( |
| "verify", |
| description="Verify release artifacts", |
| ) |
| verify_parser.add_argument("-v", type=str, help="new version") |
| verify_parser.set_defaults(func=verify) |
| |
| args = parser.parse_args() |
| arg_dict = dict(vars(args)) |
| del arg_dict["func"] |
| print(arg_dict) |
| args.func(**arg_dict) |
| |
| |
| if __name__ == "__main__": |
| _parse_args() |