-
Notifications
You must be signed in to change notification settings - Fork 146
/
tasks.py
266 lines (219 loc) · 7.56 KB
/
tasks.py
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
import os
import tempfile
import itertools
import shutil
import re
from invoke import task, run
from subprocess import call
import semver
VERSION_TEMPLATE = '''
# This file is generated programatically.
# Version of the Python package
__version__ = '{version_string}'
# Version of the JS client. This must match the version field in package.json.
CLIENT_VERSION = '{version_string}'
'''
RELEASE_NOTES_TEMPLATE = '''# Write the release notes here
# Delete the version title to cancel
Version {version_string}
{underline}
'''
GMAPS_DIR = os.path.dirname(os.path.realpath(__file__))
@task(help={'version': 'Version number to release'})
def prerelease(ctx, version):
'''
Release a pre-release version
Running this task will:
- Bump the version number
- Push a release to pypi and npm
'''
set_pyversion(version)
set_jsversion(version)
release_python_sdist()
os.chdir(os.path.join(GMAPS_DIR, 'js'))
try:
run('npm publish')
finally:
os.chdir(GMAPS_DIR)
@task(help={'version': 'Version number to release'})
def release(ctx, version):
'''
Release a new version
Running this task will:
- Prompt the user for a changelog and write it to
the release notes
- Commit the release notes
- Bump the version number
- Push a release to pypi and npm
'''
release_notes_lines = get_release_notes(version)
if release_notes_lines is None:
print('No release notes: exiting')
exit()
update_release_notes(version, release_notes_lines)
with open('changelog.tmp', 'w') as f:
f.writelines(release_notes_lines)
run('git add docs/source/release_notes.rst')
run('git commit -m "Add release notes for version {}"'.format(version))
set_pyversion(version)
set_jsversion(version)
release_python_sdist()
os.chdir(os.path.join(GMAPS_DIR, 'js'))
try:
run('npm publish')
finally:
os.chdir(GMAPS_DIR)
@task(help={
'version': 'Version number to finalize. Must be '
'the same version number that was used in the release.'
})
def postrelease(ctx, version):
'''
Finalise the release
Running this task will:
- commit the version changes to source control
- tag the commit
- push changes to master
'''
run('git add gmaps/_version.py')
run('git add js/package.json')
run('git commit -m "Bump version to {}"'.format(version))
run('git tag -a v{} -F changelog.tmp'.format(version))
run('git push origin master --tags')
new_version = semver.bump_patch(version) + '-dev'
set_pyversion(new_version)
set_jsversion(new_version)
run('git add gmaps/_version.py')
run('git add js/package.json')
run('git commit -m "Back to dev"')
run('git push origin master')
@task(help={
'version': 'Version number to finalize. Must be '
'the same version number that was used in the release.'
})
def release_conda(ctx, version):
"""
Open a PR for the new release in conda-forge.
"""
sha256 = get_file_sha256('dist/gmaps-{}.tar.gz'.format(version))
print('Release SHA256 hash: {}'.format(sha256))
tempdir = tempfile.mkdtemp()
try:
print('Cloning gmaps-feedstock to {}'.format(tempdir))
os.chdir(tempdir)
run('git clone [email protected]:pbugnion/gmaps-feedstock.git')
os.chdir('gmaps-feedstock')
run('git remote add upstream [email protected]:conda-forge/gmaps-feedstock.git')
run('git pull --rebase upstream master')
branch_name = 'release-version-{}'.format(version)
run('git checkout -b {}'.format(branch_name))
update_conda_recipe(version, sha256)
run('git add recipe/meta.yaml')
run('git commit -m "Release version {}"'.format(version))
run('git push origin {}'.format(branch_name))
print('URL: https://github.com/conda-forge/gmaps-feedstock')
finally:
print('Deleting temporary directory {}'.format(tempdir))
shutil.rmtree(tempdir)
def update_release_notes(version, new_lines):
release_notes_path = os.path.join(
GMAPS_DIR, 'docs', 'source', 'release_notes.rst')
with open(release_notes_path) as f:
current_release_notes_lines = f.readlines()
current_release_notes_lines = itertools.dropwhile(
lambda line: 'Version' not in line,
current_release_notes_lines
)
new_release_notes_lines = [
'\n',
'Release notes\n',
'-------------\n',
'\n'
] + new_lines + list(current_release_notes_lines)
with open(release_notes_path, 'w') as f:
f.writelines(new_release_notes_lines)
def release_python_sdist():
run('rm -f dist/*')
run('python setup.py sdist')
run('twine upload dist/*')
def get_release_notes(version):
version = normalize_version(version)
underline = '=' * len('Version {}'.format(version))
initial_message = RELEASE_NOTES_TEMPLATE.format(
version_string=version, underline=underline)
lines = open_editor(initial_message)
non_commented_lines = [line for line in lines if not line.startswith('#')]
changelog = ''.join(non_commented_lines)
if version in changelog:
if not non_commented_lines[-1].isspace():
non_commented_lines.append('\n')
return non_commented_lines
else:
return None
def set_pyversion(version):
version = normalize_version(version)
with open(os.path.join(GMAPS_DIR, 'gmaps', '_version.py'), 'w') as f:
f.write(VERSION_TEMPLATE.format(version_string=version))
def set_jsversion(version):
version = normalize_version(version)
package_json_path = os.path.join(GMAPS_DIR, 'js', 'package.json')
with open(package_json_path) as f:
package_json = f.readlines()
for iline, line in enumerate(package_json):
if '"version"' in line:
package_json[iline] = ' "version": "{}",\n'.format(version)
with open(package_json_path, 'w') as f:
f.writelines(package_json)
def update_conda_recipe(version, sha256):
with open('recipe/meta.yaml') as f:
lines = [line.rstrip() for line in f]
updated_lines = replace_line(
lines,
'set version = ',
'{{% set version = "{}" %}}'.format(version)
)
updated_lines = replace_line(
updated_lines,
'set sha256 = ',
'{{% set sha256 = "{}" %}}'.format(sha256)
)
with open('recipe/meta.yaml', 'w') as f:
f.writelines([line + '\n' for line in updated_lines])
def get_file_sha256(path):
sha256_output = run(
'shasum -a 256 {}'.format(path),
hide=True
)
if not sha256_output.ok:
print('Error getting sha256.')
print(sha256_output.stdout)
exit()
else:
sha256 = sha256_output.stdout.split(' ', 1)[0]
return sha256
def open_editor(initial_message):
editor = os.environ.get('EDITOR', 'vim')
tmp = tempfile.NamedTemporaryFile(suffix='.tmp')
fname = tmp.name
with open(fname, 'w') as f:
f.write(initial_message)
f.flush()
call([editor, fname], close_fds=True)
with open(fname, 'r') as f:
lines = f.readlines()
return lines
def replace_line(lines, regexp, new_line):
"""
Replace a line that matches 'regexp'
"""
for (iline, line) in enumerate(lines):
match_result = re.search(regexp, line)
if match_result is not None:
break
updated_lines = lines[:]
updated_lines[iline] = new_line
return updated_lines
def normalize_version(version):
version_info = semver.parse_version_info(version)
version_string = str(version_info)
return version_string