Skip to content

Commit 560def4

Browse files
committed
Add release script
1 parent f722b25 commit 560def4

File tree

3 files changed

+196
-0
lines changed

3 files changed

+196
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ js/requirements/yarn-error.log
1313
.vscode
1414

1515
/docs/build
16+
/dist

Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ rulegen:
66
rm available_tests.txt;
77

88

9+
# Publish a release
10+
.PHONY: release
11+
release:
12+
./tools/make_release.py
13+
14+
915
# Build docs locally
1016
.PHONY: docs
1117
docs:

tools/make_release.py

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env python3
2+
3+
import base64
4+
import hashlib
5+
import json
6+
import pathlib
7+
import re
8+
import subprocess
9+
import tarfile
10+
import tempfile
11+
import shutil
12+
import sys
13+
14+
# Config
15+
BCR_REPO = 'bazelbuild/bazel-central-registry'
16+
BCR_STAGING_REPO = 'rules-proto-grpc/bazel-central-registry'
17+
CORE_MODULE_NAME = 'rules_proto_grpc'
18+
HOMEPAGE = 'https://rules-proto-grpc.com'
19+
MAINTAINER_EMAIL = '[email protected]'
20+
MAINTAINER_NAME = 'Adam Liddell'
21+
MAINTAINER_GITHUB = 'aaliddell'
22+
SOURCE_REPO = 'rules-proto-grpc/rules_proto_grpc'
23+
VERSION = '5.0.0-alpha1'
24+
VERSION_PLACEHOLDER = '0.0.0.rpg.version.placeholder'
25+
26+
27+
# Sanity check
28+
if not re.fullmatch(r"^([a-zA-Z0-9.]+)(?:-([a-zA-Z0-9.-]+))?(?:\+[a-zA-Z0-9.-]+)?$", VERSION):
29+
print('Version is not valid')
30+
sys.exit(1)
31+
32+
branch = subprocess.run(
33+
['git', 'rev-parse', '--abbrev-ref', 'HEAD'], check=True, capture_output=True
34+
).stdout.strip().decode()
35+
if branch != 'master':
36+
print('Current branch is not master')
37+
sys.exit(2)
38+
39+
40+
# Work under a temp directory
41+
with tempfile.TemporaryDirectory() as tmp_dir:
42+
tmp_dir = pathlib.Path(tmp_dir)
43+
print(f'Working under temporary directory: {tmp_dir}')
44+
45+
# Checkout clean copy under tmp dir
46+
code_dir = tmp_dir / 'checkout'
47+
print(f'Checking out code under: {code_dir}')
48+
subprocess.run(['git', 'checkout-index', '--all', '--prefix', f'{code_dir}/'], check=True)
49+
50+
# Patch version identifier on modules
51+
# This is hilariously inefficient but whatever...
52+
version_bytes = VERSION.encode()
53+
version_placeholder_bytes = VERSION_PLACEHOLDER.encode()
54+
for dir_path, dir_names, file_names in (code_dir / 'modules').walk():
55+
for file_name in file_names:
56+
file_path = dir_path / file_name
57+
file_data = file_path.read_bytes()
58+
if version_placeholder_bytes in file_data:
59+
file_path.write_bytes(file_data.replace(version_placeholder_bytes, version_bytes))
60+
print(f'Patching version in {dir_path / file_name}')
61+
62+
# Find modules
63+
modules = {
64+
(
65+
# Core module is not suffixed with _core
66+
(CORE_MODULE_NAME + '_' + path.name)
67+
if path.name != 'core' else CORE_MODULE_NAME
68+
): path
69+
for path in (code_dir / 'modules').glob('*')
70+
if path.name != 'example_protos' # Don't publish internal examples as module
71+
}
72+
73+
# Build assets
74+
def _filter_tar_info(tar_info: tarfile.TarInfo) -> tarfile.TarInfo:
75+
"""Strip local info from tar records."""
76+
tar_info.mtime = 0
77+
tar_info.uid = 0
78+
tar_info.gid = 0
79+
tar_info.uname = 'root'
80+
tar_info.gname = 'root'
81+
return tar_info
82+
83+
tmp_dist_dir = tmp_dir / 'dist'
84+
tmp_dist_dir.mkdir()
85+
module_dist_paths = {}
86+
module_dist_hashes = {}
87+
for module_name, module_path in modules.items():
88+
module_dist_path = tmp_dist_dir / f'{module_name}-{VERSION}.tar.gz'
89+
print(f'Bundling module {module_name} ({module_path}) to {module_dist_path}')
90+
with tarfile.open(module_dist_path, 'x:gz', compresslevel=9, dereference=True) as tar_file:
91+
tar_file.add(module_path, f'{module_name}-{VERSION}', filter=_filter_tar_info)
92+
93+
module_dist_paths[module_name] = module_dist_path
94+
module_dist_hashes[module_name] = base64.b64encode(
95+
hashlib.sha256(module_dist_path.read_bytes()).digest()
96+
).decode()
97+
98+
# Copy assets
99+
dist_dir = pathlib.Path('.') / 'dist' / str(VERSION)
100+
dist_dir.mkdir(parents=True, exist_ok=True)
101+
for module_name, module_dist_path in module_dist_paths.items():
102+
print(f'Copying module dist to {dist_dir / module_dist_path.name}')
103+
shutil.copy(module_dist_path, dist_dir / module_dist_path.name)
104+
105+
# Clone and update BCR repo, then create new branch
106+
bcr_dir = tmp_dir / 'bcr'
107+
bcr_branch_name = f'release/{VERSION}'
108+
subprocess.run(['git', 'clone', f'https://github.com/{BCR_REPO}.git', str(bcr_dir)], check=True)
109+
subprocess.run(['git', 'switch', '--create', bcr_branch_name], cwd=bcr_dir, check=True)
110+
111+
# Update BCR modules
112+
for module_name, module_path in modules.items():
113+
# Update module level files
114+
bcr_mod_dir = bcr_dir / 'modules' / module_name
115+
bcr_mod_dir.mkdir(exist_ok=True)
116+
117+
bcr_metadata_path = bcr_mod_dir / 'metadata.json'
118+
bcr_metadata = {}
119+
if bcr_metadata_path.exists():
120+
bcr_metadata = json.loads(bcr_metadata_path.read_bytes())
121+
122+
bcr_metadata.update({
123+
'homepage': HOMEPAGE,
124+
'maintainers': [{
125+
'email': MAINTAINER_EMAIL,
126+
'github': MAINTAINER_GITHUB,
127+
'name': MAINTAINER_NAME,
128+
}],
129+
'repository': [
130+
f'github:{SOURCE_REPO}',
131+
],
132+
'yanked_versions': {},
133+
})
134+
bcr_metadata.setdefault('versions', [])
135+
bcr_metadata['versions'].append(VERSION)
136+
137+
bcr_metadata_path.write_text(json.dumps(bcr_metadata, indent=4) + '\n')
138+
139+
# Create module version level files
140+
bcr_mod_version_dir = bcr_mod_dir / VERSION
141+
bcr_mod_version_dir.mkdir(exist_ok=False)
142+
143+
shutil.copy(module_path / 'MODULE.bazel', bcr_mod_version_dir / 'MODULE.bazel')
144+
(bcr_mod_version_dir / 'source.json').write_text(json.dumps({
145+
'integrity': f'sha256-{module_dist_hashes[module_name]}',
146+
'strip_prefix': f'{module_name}-{VERSION}',
147+
'url': f'https://github.com/{SOURCE_REPO}/releases/download/{VERSION}/{module_name}-{VERSION}.tar.gz',
148+
'patches': {},
149+
'patch_strip': 0,
150+
}, indent=4) + '\n')
151+
152+
(bcr_mod_version_dir / 'presubmit.yml').write_text(f"""matrix:
153+
platform:
154+
- centos7
155+
- debian10
156+
- ubuntu2004
157+
- macos
158+
- windows
159+
tasks:
160+
verify_targets:
161+
name: Verify build targets
162+
platform: ${{{{ platform }}}}
163+
build_targets:
164+
- '@{module_name}//...'
165+
""")
166+
167+
# Stage and commit
168+
subprocess.run(['git', 'add', '--all'], cwd=bcr_dir, check=True)
169+
subprocess.run([
170+
'git', 'commit',
171+
'--author', f'{MAINTAINER_NAME} <{MAINTAINER_EMAIL}>',
172+
'--message', f'Add {CORE_MODULE_NAME} {VERSION}',
173+
], cwd=bcr_dir, check=True)
174+
175+
# Push BCR branch
176+
subprocess.run([
177+
'git', 'push', '--force', f'[email protected]:{BCR_STAGING_REPO}.git',
178+
f'{bcr_branch_name}:{bcr_branch_name}',
179+
], cwd=bcr_dir, check=True)
180+
181+
print(f"""
182+
Next steps:
183+
- Tag the release with: git tag {VERSION}
184+
- Push tag to GH: git push --tags
185+
- Create a release at https://github.com/{SOURCE_REPO}/releases/new using tag {VERSION}
186+
- Attach the files in {dist_dir.absolute()} to the release
187+
- Create a PR on BCR at https://github.com/{BCR_REPO}/compare/main...{BCR_STAGING_REPO.replace('/', ':')}:{bcr_branch_name}?expand=1
188+
189+
""")

0 commit comments

Comments
 (0)