| #!/usr/bin/env python3 |
| |
| # 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. |
| |
| from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, REMAINDER |
| from glob import glob |
| from os import makedirs |
| from pathlib import Path |
| import re |
| from subprocess import Popen, PIPE |
| import sys |
| from typing import List, Any, Optional, TextIO, Tuple |
| from shutil import which |
| |
| SEMVER_REGEX = re.compile( |
| r""" |
| ^ |
| (?P<major>0|[1-9]\d*) |
| \. |
| (?P<minor>0|[1-9]\d*) |
| \. |
| (?P<patch>0|[1-9]\d*) |
| (?:-(?P<prerelease> |
| (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) |
| (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))* |
| ))? |
| (?:\+(?P<build> |
| [0-9a-zA-Z-]+ |
| (?:\.[0-9a-zA-Z-]+)* |
| ))? |
| $ |
| """, |
| re.VERBOSE, |
| ) |
| |
| |
| def run(*args: str, msg: Optional[str] = None, verbose: bool = False, **kwargs: Any) -> Popen: |
| sys.stdout.flush() |
| if verbose: |
| print(f"$ {' '.join(args)}") |
| |
| p = Popen(args, **kwargs) |
| code = p.wait() |
| if code != 0: |
| err = f"\nfailed to run: {args}\nexit with code: {code}\n" |
| if msg: |
| err += f"error message: {msg}\n" |
| raise RuntimeError(err) |
| |
| return p |
| |
| |
| def run_pipe(*args: str, msg: Optional[str] = None, verbose: bool = False, **kwargs: Any) -> TextIO: |
| p = run(*args, msg=msg, verbose=verbose, stdout=PIPE, universal_newlines=True, **kwargs) |
| return p.stdout # type: ignore |
| |
| |
| def find_command(command: str, msg: Optional[str] = None) -> str: |
| return run_pipe("which", command, msg=msg).read().strip() |
| |
| |
| def write_version(release_version: str) -> str: |
| version = release_version.strip() |
| if SEMVER_REGEX.match(version) is None: |
| raise RuntimeError(f"Version should follow semver spec, got: {version}") |
| |
| with open('VERSION.txt', 'w+') as f: |
| f.write(version) |
| |
| return version |
| |
| |
| def package_source(release_version: str, release_candidate_number: Optional[int]) -> None: |
| # 0. Write input version to VERSION file |
| version = write_version(release_version) |
| |
| # 1. Git commit and tag |
| git = find_command('git', msg='git is required for source packaging') |
| run(git, 'commit', '-a', '-m', f'[source-release] prepare release apache-kvrocks-controller-{version}') |
| if release_candidate_number is None: |
| run(git, 'tag', '-a', f'v{version}', '-m', f'[source-release] copy for tag v{version}') |
| else: |
| run(git, 'tag', '-a', f'v{version}-rc{release_candidate_number}', '-m', f'[source-release] copy for tag v{version}-rc{release_candidate_number}') |
| |
| # 2. Create the source tarball |
| folder = f'apache-kvrocks-controller-src-{version}' |
| tarball = f'apache-kvrocks-controller-src-{version}.tar.gz' |
| run(git, 'archive', '--format=tar.gz', f'--output={tarball}', f'--prefix={folder}/', 'HEAD') |
| |
| # 3. GPG Sign |
| gpg = find_command('gpg', msg='gpg is required for source packaging') |
| run(gpg, '--detach-sign', '--armor', tarball) |
| |
| # 4. Generate sha512 checksum |
| shasum = find_command('shasum', msg='shasum is required for source packaging') |
| with open(f'{tarball}.sha512', 'w+') as f: |
| run(shasum, '-a', '512', tarball, stdout=f) |
| |
| |
| if __name__ == '__main__': |
| parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) |
| parser.set_defaults(func=parser.print_help) |
| |
| subparsers = parser.add_subparsers() |
| |
| parser_package = subparsers.add_parser( |
| 'package', |
| description="Package the source tarball or binary installer", |
| help="Package the source tarball or binary installer", |
| formatter_class=ArgumentDefaultsHelpFormatter, |
| ) |
| parser_package.set_defaults(func=parser_package.print_help) |
| parser_package_subparsers = parser_package.add_subparsers() |
| parser_package_source = parser_package_subparsers.add_parser( |
| 'source', |
| description="Package the source tarball", |
| help="Package the source tarball", |
| ) |
| parser_package_source.add_argument('-v', '--release-version', required=True, metavar='VERSION', |
| help='current releasing version') |
| parser_package_source.add_argument('-rc', '--release-candidate-number',required=False, type=int, help='current releasing candidate number') |
| parser_package_source.set_defaults(func=package_source) |
| |
| args = parser.parse_args() |
| |
| arg_dict = dict(vars(args)) |
| del arg_dict['func'] |
| args.func(**arg_dict) |