blob: f2a74d7f55a9294162a6ce75776c0a525b1a0659 [file]
#!/usr/bin/env python3
import base64
import hashlib
import json
import pathlib
import re
import subprocess
import tarfile
import tempfile
import shutil
import sys
import urllib.parse
import urllib.request
# Config
BCR_REPO = 'bazelbuild/bazel-central-registry'
BCR_STAGING_REPO = 'rules-proto-grpc/bazel-central-registry'
CORE_MODULE_NAME = 'rules_proto_grpc'
HOMEPAGE = 'https://rules-proto-grpc.com'
MAINTAINER_EMAIL = 'adam@rules-proto-grpc.com'
MAINTAINER_NAME = 'Adam Liddell'
MAINTAINER_GH_USER = 'aaliddell'
SOURCE_REPO = 'rules-proto-grpc/rules_proto_grpc'
VERSION = input('Version number: ').strip()
VERSION_PLACEHOLDER = '0.0.0.rpg.version.placeholder'
# Find GitHub user ID
print('Finding GitHub user ID')
MAINTAINER_GH_USER_ID = int(json.loads(
urllib.request.urlopen(f'https://api.github.com/users/{MAINTAINER_GH_USER}').read()
)['id'])
# Sanity check
if not re.fullmatch(r"^([a-zA-Z0-9.]+)(?:-([a-zA-Z0-9.-]+))?(?:\+[a-zA-Z0-9.-]+)?$", VERSION):
print('Version is not valid')
sys.exit(1)
branch = subprocess.run(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'], check=True, capture_output=True
).stdout.strip().decode()
if branch != 'master':
print('Current branch is not master')
sys.exit(2)
existing_tags = subprocess.run(
['git', 'tag', '--list'], check=True, capture_output=True
).stdout.strip().decode().split('\n')
if VERSION in existing_tags:
print('Version conflicts with an existing tag')
sys.exit(3)
# Work under a temp directory
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_dir = pathlib.Path(tmp_dir)
print(f'Working under temporary directory: {tmp_dir}')
# Checkout clean copy under tmp dir
code_dir = tmp_dir / 'checkout'
print(f'Checking out code under: {code_dir}')
subprocess.run(['git', 'checkout-index', '--all', '--prefix', f'{code_dir}/'], check=True)
# Patch version identifier on modules
# This is hilariously inefficient but whatever...
version_bytes = VERSION.encode()
version_placeholder_bytes = VERSION_PLACEHOLDER.encode()
for dir_path, dir_names, file_names in (code_dir / 'modules').walk():
for file_name in file_names:
file_path = dir_path / file_name
file_data = file_path.read_bytes()
if version_placeholder_bytes in file_data:
file_path.write_bytes(file_data.replace(version_placeholder_bytes, version_bytes))
print(f'Patching version in {dir_path / file_name}')
# Find modules
modules = {
(
# Core module is not suffixed with _core
(CORE_MODULE_NAME + '_' + path.name)
if path.name != 'core' else CORE_MODULE_NAME
): path
for path in (code_dir / 'modules').glob('*')
if path.name != 'example_protos' # Don't publish internal examples as module
}
# Build assets
def _filter_tar_info(tar_info: tarfile.TarInfo) -> tarfile.TarInfo:
"""Strip local info from tar records."""
tar_info.mtime = 0
tar_info.uid = 0
tar_info.gid = 0
tar_info.uname = 'root'
tar_info.gname = 'root'
return tar_info
tmp_dist_dir = tmp_dir / 'dist'
tmp_dist_dir.mkdir()
module_dist_paths = {}
module_dist_hashes = {}
for module_name, module_path in modules.items():
module_dist_path = tmp_dist_dir / f'{module_name}-{VERSION}.tar.gz'
print(f'Bundling module {module_name} ({module_path}) to {module_dist_path}')
with tarfile.open(module_dist_path, 'x:gz', compresslevel=9, dereference=True) as tar_file:
tar_file.add(module_path, f'{module_name}-{VERSION}', filter=_filter_tar_info)
module_dist_paths[module_name] = module_dist_path
module_dist_hashes[module_name] = base64.b64encode(
hashlib.sha256(module_dist_path.read_bytes()).digest()
).decode()
# Copy assets
dist_dir = pathlib.Path('.') / 'dist' / str(VERSION)
dist_dir.mkdir(parents=True, exist_ok=True)
for module_name, module_dist_path in module_dist_paths.items():
print(f'Copying module dist to {dist_dir / module_dist_path.name}')
shutil.copy(module_dist_path, dist_dir / module_dist_path.name)
# Clone and update BCR repo, then create new branch
bcr_dir = tmp_dir / 'bcr'
bcr_branch_name = f'release/{VERSION}'
subprocess.run(['git', 'clone', f'https://github.com/{BCR_REPO}.git', str(bcr_dir)], check=True)
subprocess.run(['git', 'switch', '--create', bcr_branch_name], cwd=bcr_dir, check=True)
# Update BCR modules
for module_name, module_path in modules.items():
# Update module level files
bcr_mod_dir = bcr_dir / 'modules' / module_name
bcr_mod_dir.mkdir(exist_ok=True)
bcr_metadata_path = bcr_mod_dir / 'metadata.json'
bcr_metadata = {}
if bcr_metadata_path.exists():
bcr_metadata = json.loads(bcr_metadata_path.read_bytes())
bcr_metadata.update({
'homepage': HOMEPAGE,
'maintainers': [{
'email': MAINTAINER_EMAIL,
'github': MAINTAINER_GH_USER,
'name': MAINTAINER_NAME,
'github_user_id': MAINTAINER_GH_USER_ID,
}],
'repository': [
f'github:{SOURCE_REPO}',
],
'yanked_versions': {},
})
bcr_metadata.setdefault('versions', [])
bcr_metadata['versions'].append(VERSION)
bcr_metadata_path.write_text(json.dumps(bcr_metadata, indent=4) + '\n')
# Create module version level files
bcr_mod_version_dir = bcr_mod_dir / VERSION
bcr_mod_version_dir.mkdir(exist_ok=False)
shutil.copy(module_path / 'MODULE.bazel', bcr_mod_version_dir / 'MODULE.bazel')
(bcr_mod_version_dir / 'source.json').write_text(json.dumps({
'integrity': f'sha256-{module_dist_hashes[module_name]}',
'strip_prefix': f'{module_name}-{VERSION}',
'url': f'https://github.com/{SOURCE_REPO}/releases/download/{VERSION}/{module_name}-{VERSION}.tar.gz',
'patches': {},
'patch_strip': 0,
}, indent=4) + '\n')
(bcr_mod_version_dir / 'presubmit.yml').write_text(f"""matrix:
platform:
- ubuntu2204
- macos
#- windows # Blocked by https://github.com/bazelbuild/bazel/issues/18683
bazel:
- 7.x
- 8.x
tasks:
verify_targets:
name: Verify build targets
platform: ${{{{ platform }}}}
bazel: ${{{{ bazel }}}}
build_flags:
- "--cxxopt=-std=c++17"
- "--host_cxxopt=-std=c++17"
build_targets:
- "@{module_name}//..."
""")
# Stage and commit
commit_title = f'{CORE_MODULE_NAME}@{VERSION}'
subprocess.run(['git', 'add', '--all'], cwd=bcr_dir, check=True)
subprocess.run([
'git', 'commit',
'--message', commit_title,
], cwd=bcr_dir, check=True, env={
'GIT_AUTHOR_NAME': MAINTAINER_NAME,
'GIT_AUTHOR_EMAIL': MAINTAINER_EMAIL,
'GIT_COMMITTER_NAME': MAINTAINER_NAME,
'GIT_COMMITTER_EMAIL': MAINTAINER_EMAIL,
})
# Push BCR branch
subprocess.run([
'git', 'push', '--force', f'git@github.com:{BCR_STAGING_REPO}.git',
f'{bcr_branch_name}:{bcr_branch_name}',
], cwd=bcr_dir, check=True)
pr_body = f'''Release: https://github.com/{SOURCE_REPO}/releases/tag/{VERSION}
'''
print(f"""
Next steps:
- Tag the release with: git tag {VERSION}
- Push tag to GH: git push --tags
- Create a release at https://github.com/{SOURCE_REPO}/releases/new?tag={urllib.parse.quote_plus(VERSION)}&title={urllib.parse.quote_plus(VERSION)}
- Attach the files in {dist_dir.absolute()} to the release
- Create a PR on BCR at https://github.com/{BCR_REPO}/compare/main...{BCR_STAGING_REPO.replace('/', ':')}:{bcr_branch_name}?quick_pull=1&title={urllib.parse.quote_plus(commit_title)}&body={urllib.parse.quote_plus(pr_body)}
""")