Skip to content

Commit db90c81

Browse files
authored
introduce the release.py script for MAS (#317)
1 parent 083b5c0 commit db90c81

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

scripts/release.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
'''
2+
Utility to schedule MAS builds in Bitrise.
3+
4+
Examples:
5+
6+
- Publish a snapshot from master (release.py uses the current branch)
7+
8+
$ git branch
9+
* master
10+
1234-fix-crash
11+
$ python release.py --stage snapshot
12+
13+
- Publish a snapshot from a feature branch (same as before, just switch branchs with git):
14+
15+
$ git branch
16+
master
17+
* 1234-fix-crash
18+
$ python release.py --stage snapshot
19+
20+
- Publish a beta from a pre-release branch:
21+
22+
$ git branch
23+
master
24+
* release-android-420-beta1
25+
$ python release.py --stage beta --version 2.0.0-beta.1
26+
27+
- Publish a beta from a release branch:
28+
29+
$ git branch
30+
master
31+
* release-android-420
32+
$ python release.py --stage final --version 2.0.0
33+
34+
TODO:
35+
36+
- Add a flag to wait until the release has been built (Bitrise) and published (Maven).
37+
38+
'''
39+
40+
import click
41+
import json
42+
import os
43+
import requests
44+
import subprocess
45+
46+
# Three stages, from less stable to more stable
47+
ALLOWED_STAGES = ['snapshot', 'beta', 'final']
48+
49+
# Get the version from GRADLE_PROPERTIES_PATH below
50+
CURRENT_VERSION_TAG = 'current'
51+
52+
# You can find the API token in https://www.bitrise.io/app/a7eea7d04be1e2e5#/code -> API token
53+
BITRISE_API_TOKEN_ENV_VAR = 'BITRISE_API_TOKEN_MAS'
54+
55+
# In the future we might want to consider alpha, or rc.
56+
ALLOWED_PRE_RELEASE = ['beta']
57+
58+
# We get the default version from here
59+
MAPBOX_GL_ANDROID_SDK_PATH = '../mapbox'
60+
GRADLE_PROPERTIES_PATH = '%s/gradle.properties' % MAPBOX_GL_ANDROID_SDK_PATH
61+
GRADLE_TOKEN = 'VERSION_NAME='
62+
63+
# Bitrise
64+
URL_BITRISE = 'https://www.bitrise.io/app/a7eea7d04be1e2e5/build/start.json'
65+
66+
# We support three parameters: stage, branch, and version
67+
@click.command()
68+
@click.option('--stage', default=ALLOWED_STAGES[0], type=click.Choice(ALLOWED_STAGES), prompt='Set stage', help='The release stage.')
69+
@click.option('--version', default=CURRENT_VERSION_TAG, prompt='Set version', help='The version you want to publish. E.g: 2.0.0-SNAPSHOT, 2.0.0-beta.1, or 2.0.0. If you set the version to "%s", the script will default to the current SNAPSHOT version.' % CURRENT_VERSION_TAG)
70+
def release(stage, version):
71+
# Validate params
72+
final_stage = validate_stage(stage=stage)
73+
final_branch = validate_branch(stage=final_stage)
74+
final_version = validate_version(stage=final_stage, branch=final_branch, version=version)
75+
76+
# Get user confirmation
77+
click.echo('\n===== Build information =====')
78+
click.echo('- Stage: %s' % final_stage)
79+
click.echo('- Branch: %s' % final_branch)
80+
click.echo('- Version: %s' % final_version)
81+
click.confirm('\nDoes it look right?', abort=True)
82+
83+
# Proceed
84+
if (final_stage == 'snapshot'):
85+
publish_snapshot(branch=final_branch, version=final_version)
86+
elif (final_stage == 'beta'):
87+
publish_beta(branch=final_branch, version=final_version)
88+
elif (final_stage == 'final'):
89+
publish_final(branch=final_branch, version=final_version)
90+
91+
def validate_stage(stage):
92+
if stage not in ALLOWED_STAGES:
93+
abort_with_message('Invalid stage: %s' % stage)
94+
return stage
95+
96+
def validate_branch(stage):
97+
branch = git_get_current_branch()
98+
if not branch:
99+
abort_with_message('The current folder is not a git repository.')
100+
if branch == 'master' and stage != 'snapshot':
101+
abort_with_message('You need to swtich to a release branch for a beta or a final release.')
102+
return branch
103+
104+
def validate_version(stage, branch, version):
105+
if stage == 'snapshot' and branch == 'master' and version != CURRENT_VERSION_TAG:
106+
abort_with_message('You cannot specify a custom version if you are building a snapshot from master.')
107+
108+
if not version or version == CURRENT_VERSION_TAG:
109+
version = get_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN)
110+
111+
if stage == 'snapshot':
112+
if not 'SNAPSHOT' in version:
113+
abort_with_message('Version should contain the word SNAPSHOT: %s' % version)
114+
elif stage == 'beta':
115+
if not ALLOWED_PRE_RELEASE[0] in version:
116+
abort_with_message('Version should contain the word %s: %s' % (ALLOWED_PRE_RELEASE[0], version))
117+
elif stage == 'final':
118+
if not version or 'SNAPSHOT' in version or ALLOWED_PRE_RELEASE[0] in version:
119+
abort_with_message('Version cannot be empty, or contain the words SNAPSHOT or %s: %s' % (ALLOWED_PRE_RELEASE[0], version))
120+
121+
return version
122+
123+
def publish_snapshot(branch, version):
124+
click.echo('Publishing snapshot for branch: %s (version: %s).' % (branch, version))
125+
if branch != 'master':
126+
dirty_gradle = update_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN, version=version)
127+
if dirty_gradle:
128+
git_add(path=GRADLE_PROPERTIES_PATH)
129+
git_commit_and_push(branch=branch, version=version)
130+
do_bitrise_request(build_params={'branch': branch, 'workflow_id': 'scheduled'})
131+
132+
def publish_beta(branch, version):
133+
click.echo('Publishing beta from branch: %s (version: %s).' % (branch, version))
134+
dirty_gradle = update_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN, version=version)
135+
if dirty_gradle:
136+
git_add(path=GRADLE_PROPERTIES_PATH)
137+
git_commit_and_push(branch=branch, version=version)
138+
do_bitrise_request(build_params={'branch': branch, 'workflow_id': 'scheduled'})
139+
140+
def publish_final(branch, version):
141+
click.echo('Publishing final release from branch: %s (version: %s).' % (branch, version))
142+
dirty_gradle = update_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN, version=version)
143+
if dirty_gradle:
144+
git_add(path=GRADLE_PROPERTIES_PATH)
145+
git_commit_and_push(branch=branch, version=version)
146+
do_bitrise_request(build_params={'branch': branch, 'workflow_id': 'scheduled'})
147+
148+
#
149+
# Utils
150+
#
151+
152+
def abort_with_message(message):
153+
click.echo(message)
154+
click.get_current_context().abort()
155+
156+
def execute_call(command):
157+
click.echo('Executing: %s' % command)
158+
result = subprocess.call(command, shell=True)
159+
if result != 0:
160+
abort_with_message('Command failed: %s' % command)
161+
162+
#
163+
# Bitrise
164+
#
165+
166+
def get_bitrise_api_token():
167+
bitrise_api_token = os.environ.get(BITRISE_API_TOKEN_ENV_VAR)
168+
if not bitrise_api_token:
169+
abort_with_message('You need to set the BITRISE_API_TOKEN_MAS environment variable.')
170+
click.echo('Found Bitrise API token.')
171+
return bitrise_api_token
172+
173+
def do_bitrise_request(build_params):
174+
data = {
175+
'hook_info': {'type': 'bitrise', 'api_token': get_bitrise_api_token()},
176+
'build_params' : build_params}
177+
click.echo('Bitrise request data: %s' % json.dumps(data))
178+
click.confirm('\nDo you want to start a build?', abort=True)
179+
180+
r = requests.post(URL_BITRISE, data=json.dumps(data))
181+
click.echo('- Bitrise response code: %s' % r.status_code)
182+
click.echo('- Bitrise response content: %s' % r.text)
183+
184+
#
185+
# Git
186+
#
187+
188+
def git_get_current_branch():
189+
return subprocess.check_output('git symbolic-ref --short HEAD'.split(' ')).strip()
190+
191+
def git_add(path):
192+
execute_call(command='git add %s' % path)
193+
194+
def git_commit_and_push(branch, version):
195+
message = '[android] [auto] Update properties to version %s in preparation for build.' % version
196+
commands = [
197+
'git commit -m "%s"' % message,
198+
'git push -u origin %s' % branch]
199+
for command in commands:
200+
execute_call(command=command)
201+
202+
#
203+
# Read and update properties files
204+
#
205+
206+
def get_current_version(file_path, file_var):
207+
click.echo('Getting current version from %s.' % file_path)
208+
with open(file_path, 'r') as f:
209+
for line in f:
210+
if line.startswith(file_var):
211+
version_name = line[len(file_var):].strip()
212+
click.echo('Current version is %s.' % version_name)
213+
return version_name
214+
return None
215+
216+
def update_current_version(file_path, file_var, version):
217+
dirty = False
218+
click.echo('Updating file to version %s: %s.' % (version, file_path))
219+
with open(file_path, 'r') as f:
220+
file_lines = f.readlines()
221+
for line_number in range(len(file_lines)):
222+
if file_lines[line_number].startswith(file_var):
223+
content_old = file_lines[line_number]
224+
content_new = '%s%s\n' % (file_var, version)
225+
if (content_old != content_new):
226+
click.echo('%s -> %s' % (content_old.strip(), content_new.strip()))
227+
file_lines[line_number] = content_new
228+
dirty = True
229+
if dirty:
230+
with open(file_path, 'w') as f:
231+
f.writelines(file_lines)
232+
else:
233+
click.echo('File already has the right version.')
234+
return dirty
235+
236+
if __name__ == '__main__':
237+
release()

0 commit comments

Comments
 (0)