summaryrefslogtreecommitdiff
path: root/platform/android/scripts/release.py
blob: ce554d13629a0381f92bb92e6f4847def159f92c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
'''
Utility to schedule SDK builds on CircleCI.

Examples:

- Publish a snapshot from master (release.py uses the current branch)

	$ git branch
	* master
	  1234-fix-crash
	$ python release.py --stage snapshot

- Publish a snapshot from a feature branch (same as before, just switch branchs with git):

	$ git branch
	  master
	* 1234-fix-crash
	$ python release.py --stage snapshot

- Publish a beta from a pre-release branch:

	$ git branch
	  master
	* release-android-420-beta1
	$ python release.py --stage beta --version 4.2.0-beta.1

- Publish a beta from a release branch:

	$ git branch
	  master
	* release-android-420
	$ python release.py --stage final --version 4.2.0

TODO:

- Add a flag to wait until the release has been built (CircleCI) and published (Maven).

'''

import click
import json
import os
import requests
import subprocess

# Three stages, from less stable to more stable
ALLOWED_STAGES = ['snapshot', 'beta', 'final']

# Get the version from GRADLE_PROPERTIES_PATH below
CURRENT_VERSION_TAG = 'current'

# You can add your API token on https://circleci.com/account/api
CIRCLECI_API_TOKEN_ENV_VAR = 'CIRCLECI_API_TOKEN'

# In the future we might want to consider alpha, or rc.
ALLOWED_PRE_RELEASE = ['beta']

# We get the default version from here
MAPBOX_GL_ANDROID_SDK_PATH = '../MapboxGLAndroidSDK'
GRADLE_PROPERTIES_PATH = '%s/gradle.properties' % MAPBOX_GL_ANDROID_SDK_PATH
GRADLE_TOKEN = 'VERSION_NAME='
FABRIC_PROPERTIES_PATH = '%s/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties' % MAPBOX_GL_ANDROID_SDK_PATH
FABRIC_TOKEN = 'fabric-version='

# Triggers a new build, returns a summary of the build
URL_CIRCLECI = 'https://circleci.com/api/v1.1/project/github/mapbox/mapbox-gl-native/tree/'  # + :branch

# We support three parameters: stage, branch, and version
@click.command()
@click.option('--stage', default=ALLOWED_STAGES[0], type=click.Choice(ALLOWED_STAGES), prompt='Set stage', help='The release stage.')
@click.option('--version', default=CURRENT_VERSION_TAG, prompt='Set version', help='The version you want to publish. E.g: 4.2.0-SNAPSHOT, 4.2.0-beta.1, or 4.2.0. If you set the version to "%s", the script will default to the current SNAPSHOT version.' % CURRENT_VERSION_TAG)
def release(stage, version):
	# Validate params
	final_stage = validate_stage(stage=stage)
	final_branch = validate_branch(stage=final_stage)
	final_version = validate_version(stage=final_stage, branch=final_branch, version=version)

	# Get user confirmation
	click.echo('\n===== Build information =====')
	click.echo('- Stage: %s' % final_stage)
	click.echo('- Branch: %s' % final_branch)
	click.echo('- Version: %s' % final_version)
	click.confirm('\nDoes it look right?', abort=True)

	# Proceed
	if (final_stage == 'snapshot'):
		publish_snapshot(branch=final_branch, version=final_version)
	elif (final_stage == 'beta'):
		publish_beta(branch=final_branch, version=final_version)
	elif (final_stage == 'final'):
		publish_final(branch=final_branch, version=final_version)

def validate_stage(stage):
	if stage not in ALLOWED_STAGES:
		abort_with_message('Invalid stage: %s' % stage)
	return stage

def validate_branch(stage):
	branch = git_get_current_branch()
	if not branch:
		abort_with_message('The current folder is not a git repository.')
	if branch == 'master' and stage != 'snapshot':
		abort_with_message('You need to swtich to a release branch for a beta or a final release.')
	return branch

def validate_version(stage, branch, version):
	if stage == 'snapshot' and branch == 'master' and version != CURRENT_VERSION_TAG:
		abort_with_message('You cannot specify a custom version if you are building a snapshot from master.')

	if not version or version == CURRENT_VERSION_TAG:
		version = get_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN)

	if stage == 'snapshot':
		if not 'SNAPSHOT' in version:
			abort_with_message('Version should contain the word SNAPSHOT: %s' % version)
	elif stage == 'beta':
		if not ALLOWED_PRE_RELEASE[0] in version:
			abort_with_message('Version should contain the word %s: %s' % (ALLOWED_PRE_RELEASE[0], version))
	elif stage == 'final':
		if not version or 'SNAPSHOT' in version or ALLOWED_PRE_RELEASE[0] in version:
			abort_with_message('Version cannot be empty, or contain the words SNAPSHOT or %s: %s' % (ALLOWED_PRE_RELEASE[0], version))

	return version

def publish_snapshot(branch, version):
	click.echo('Publishing snapshot for branch: %s (version: %s).' % (branch, version))
	if branch != 'master':
		dirty_gradle = update_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN, version=version)
		if dirty_gradle:
			git_add(path=GRADLE_PROPERTIES_PATH)
			git_commit_and_push(branch=branch, version=version)
	do_circleci_request(branch=branch)

def publish_beta(branch, version):
	click.echo('Publishing beta from branch: %s (version: %s).' % (branch, version))
	dirty_gradle = update_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN, version=version)
	if dirty_gradle:
		git_add(path=GRADLE_PROPERTIES_PATH)
		git_commit_and_push(branch=branch, version=version)
	do_circleci_request(branch=branch)

def publish_final(branch, version):
	click.echo('Publishing final release from branch: %s (version: %s).' % (branch, version))
	dirty_gradle = update_current_version(file_path=GRADLE_PROPERTIES_PATH, file_var=GRADLE_TOKEN, version=version)
	if dirty_gradle:
		git_add(path=GRADLE_PROPERTIES_PATH)
	dirty_fabric = update_current_version(file_path=FABRIC_PROPERTIES_PATH, file_var=FABRIC_TOKEN, version=version)
	if dirty_fabric:
		git_add(path=FABRIC_PROPERTIES_PATH)
	if dirty_gradle or dirty_fabric:
		git_commit_and_push(branch=branch, version=version)
	do_circleci_request(branch=branch)

#
# Utils
#

def abort_with_message(message):
	click.echo(message)
	click.get_current_context().abort()

def execute_call(command):
	click.echo('Executing: %s' % command)
	result = subprocess.call(command, shell=True)
	if result != 0:
		abort_with_message('Command failed: %s' % command)

#
# CircleCI
#

def get_circleci_api_token():
	circleci_api_token = os.environ.get(CIRCLECI_API_TOKEN_ENV_VAR)
	if not circleci_api_token:
		abort_with_message('You need to set the CIRCLECI_API_TOKEN environment variable.')
	click.echo('Found CircleCI API token.')
	return circleci_api_token

def do_circleci_request(branch):
	url = URL_CIRCLECI + branch
	params = {'circle-token': get_circleci_api_token()}
	click.echo('CircleCI request to %s (params: %s)' % (url, json.dumps(params)))
	click.confirm('\nDo you want to start a build?', abort=True)

	r = requests.post(url, params=params)
	click.echo('- CircleCI response code: %s' % r.status_code)

#
# Git
#

def git_get_current_branch():
	return subprocess.check_output('git symbolic-ref --short HEAD'.split(' ')).strip()

def git_add(path):
	execute_call(command='git add %s' % path)

def git_commit_and_push(branch, version):
	message = '[android] [auto] Update properties to version %s in preparation for build.' % version
	commands = [
		'git commit -m "%s"' % message,
		'git push -u origin %s' % branch]
	for command in commands:
		execute_call(command=command)

#
# Read and update properties files
#

def get_current_version(file_path, file_var):
	click.echo('Getting current version from %s.' % file_path)
	with open(file_path, 'r') as f:
		for line in f:
			if line.startswith(file_var):
				version_name = line[len(file_var):].strip()
				click.echo('Current version is %s.' % version_name)
				return version_name
	return None

def update_current_version(file_path, file_var, version):
	dirty = False
	click.echo('Updating file to version %s: %s.' % (version, file_path))
	with open(file_path, 'r') as f:
		file_lines = f.readlines()
	for line_number in range(len(file_lines)):
		if file_lines[line_number].startswith(file_var):
			content_old = file_lines[line_number]
			content_new = '%s%s\n' % (file_var, version)
			if (content_old != content_new):
				click.echo('%s -> %s' % (content_old.strip(), content_new.strip()))
				file_lines[line_number] = content_new
				dirty = True
	if dirty:
		with open(file_path, 'w') as f:
			f.writelines(file_lines)
	else:
		click.echo('File already has the right version.')
	return dirty

if __name__ == '__main__':
	release()