Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Delta Between Two Patch Sets: releaseAutomation.py

Issue 29611593: Issue 5996 - Release consistent versions across WebExtensions (Closed) Base URL: https://codereview.adblockplus.org/29609559/
Left Patch Set: Refactoring release decision Created Nov. 23, 2017, 11:57 a.m.
Right Patch Set: Addressing comments Created Nov. 28, 2017, 2:17 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « packagerChrome.py ('k') | tox.ini » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # This Source Code Form is subject to the terms of the Mozilla Public 1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this 2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 4
5 from __future__ import print_function 5 from __future__ import print_function
6 6
7 import os 7 import os
8 import operator 8 import operator
9 import re 9 import re
10 import codecs 10 import codecs
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
112 """Run repository-checks in order to bail out early if necessary""" 112 """Run repository-checks in order to bail out early if necessary"""
113 if repo_has_uncommitted(): 113 if repo_has_uncommitted():
114 return False 114 return False
115 if repo_has_incoming(*repo_paths): 115 if repo_has_incoming(*repo_paths):
116 return False 116 return False
117 if repo_has_outgoing(): 117 if repo_has_outgoing():
118 return continue_with_outgoing() 118 return continue_with_outgoing()
119 return True 119 return True
120 120
121 121
122 def version_is_op_to(value, orig, op): 122 def compare_versions(a, b):
Wladimir Palant 2017/11/28 11:01:16 For reference, sitescripts.extensions.utils has an
tlucas 2017/11/28 13:28:43 Done.
123 """Compare two version numbers (left to right) with a given operator. 123 """Compare two version numbers."""
124 124 a_digits = [int(v) for v in a.split('.')]
125 If a passed version contains non-numeric values, the comparison is always 125 b_digits = [int(v) for v in b.split('.')]
126 False. 126
127 """ 127 def safe_get(items, index):
128 def prefix_0(a, b): 128 return items[index] if index < len(items) else 0
129 target_len = max((len(a), len(b))) 129
130 a = '0' * (target_len - len(a)) + a 130 for i in range(max(len(a_digits), len(b_digits))):
131 b = '0' * (target_len - len(b)) + b 131 result = safe_get(a_digits, i) - safe_get(b_digits, i)
132 return a, b 132 if result != 0:
133 133 return result
134 try: 134 return 0
135 value_digits = [int(v) for v in value.split('.')]
136 orig_digits = [int(v) for v in orig.split('.')]
137
138 value_dec = ''
139 orig_dec = ''
140
141 if len(value_digits) < len(orig_digits):
142 value_digits += [0] * (len(orig_digits) - len(value_digits))
143 elif len(orig_digits) < len(value_digits):
144 orig_digits += [0] * (len(value_digits) - len(orig_digits))
145
146 for i in range(len(value_digits)):
147 val, orig = prefix_0(str(value_digits[i]), str(orig_digits[i]))
148 value_dec += val
149 orig_dec += orig
150
151 return op(int(value_dec), int(orig_dec))
152
153 except ValueError:
154 pass
Wladimir Palant 2017/11/28 11:01:16 How about only passing in tags that match r'^\d+(\
tlucas 2017/11/28 13:28:43 Done, respectively only passed tags of the form "1
155
156 return False
157 135
158 136
159 def release_combination_is_possible(version, platforms, base_dir): 137 def release_combination_is_possible(version, platforms, base_dir):
tlucas 2017/11/23 12:05:52 This docstring reflects what was discussed in IRC.
160 """Determine whether a release for the given parameters is possible. 138 """Determine whether a release for the given parameters is possible.
161 139
162 Examine existing tags in order to find either higher or matching versions. 140 Examine existing tags in order to find either higher or matching versions.
163 The release is impossible if a) a higher version for a requested platform 141 The release is impossible if a) a higher version for a requested platform
164 exists, or if b) a matching version exists and the requested set of 142 exists, or if b) a matching version exists and the requested set of
165 platforms differs from what was already released. 143 platforms differs from what was already released.
166 """ 144 """
167 considered_tags = [ 145 def higher_tag_version(tag, version, platforms):
146 return (compare_versions(tag[0], version) > 0 and
147 set(tag[1:]).intersection(platforms))
148
149 def incomplete_platforms_for_version(tag, version, platforms):
150 intersection = set(tag[1:]).intersection(platforms)
151 return (compare_versions(tag[0], version) == 0 and
152 intersection and set(platforms) != set(tag[1:]))
153
154 # only consider tags of the form "1.2[.x ...]-platform[-platform ...]
155 platform_tags = re.compile(r'^(\d+(?:(?:\.\d+)*)(?:-\w+)+)$', re.MULTILINE)
156 tags = [
168 c for c in [ 157 c for c in [
169 t.split('-') for t in 158 t.split('-') for t in
170 subprocess.check_output( 159 platform_tags.findall(subprocess.check_output(
171 ['hg', 'tags', '-R', base_dir, '-q']).split() 160 ['hg', 'tags', '-R', base_dir, '-q']))
172 ] if version_is_op_to(c[0], version, operator.ge) 161 ] if compare_versions(c[0], version) >= 0
173 ] 162 ]
174 163
175 for tag in considered_tags: 164 for tag in tags:
176 if version_is_op_to(tag[0], version, operator.eq): 165 if higher_tag_version(tag, version, platforms):
177 if set(tag[1:]) != set(platforms): 166 reason = ('The higher version {} has already been released for '
178 return False, ('You have to re-release version {} for exactly ' 167 'the platforms {}.').format(tag[0], ', '.join(platforms))
179 'all of {}').format(version, ', '.join(tag[1:])) 168 return False, reason, None
180 if version_is_op_to(tag[0], version, operator.gt): 169
181 intersect = set(tag[1:]).intersection(platforms) 170 if incomplete_platforms_for_version(tag, version, platforms):
182 if intersect: 171 reason = ('You have to re-release version {} for exactly all '
183 return False, ('The higher version {} has already been ' 172 'of: {}').format(version, ', '.join(tag[1:]))
184 'released for {}').format( 173 return False, reason, None
185 tag[0], ', '.join(intersect)) 174
186 175 return (True, None,
187 return True, None 176 any(compare_versions(tag[0], version) == 0 for tag in tags))
188 177
189 178
190 def update_metadata(metadata, version): 179 def update_metadata(metadata, version):
191 """Replace version number in metadata file "manually". 180 """Replace version number in metadata file "manually".
192 181
193 The ConfigParser would mess up the order of lines. 182 The ConfigParser would mess up the order of lines.
194 """ 183 """
195 with open(metadata.option_source('general', 'version'), 'r+b') as fp: 184 with open(metadata.option_source('general', 'version'), 'r+b') as fp:
196 rawMetadata = fp.read() 185 rawMetadata = fp.read()
197 rawMetadata = re.sub( 186 rawMetadata = re.sub(
(...skipping 23 matching lines...) Expand all
221 ) 210 )
222 211
223 packager.createBuild(base_dir, type=platform, outFile=build_path, 212 packager.createBuild(base_dir, type=platform, outFile=build_path,
224 releaseBuild=True, keyFile=key_file) 213 releaseBuild=True, keyFile=key_file)
225 214
226 return build_path 215 return build_path
227 216
228 217
229 def release_commit(base_dir, extension_name, version, platforms): 218 def release_commit(base_dir, extension_name, version, platforms):
230 """Create a release commit with a representative message.""" 219 """Create a release commit with a representative message."""
231 subprocess.check_call([ 220 subprocess.check_output([
232 'hg', 'commit', '-R', base_dir, '-m', 221 'hg', 'commit', '-R', base_dir, '-m',
233 'Noissue - Releasing {} {} for {}'.format( 222 'Noissue - Releasing {} {} for {}'.format(
234 extension_name, version, 223 extension_name, version,
235 ', '.join([p.capitalize() for p in platforms]))]) 224 ', '.join([p.capitalize() for p in platforms]))],
225 stderr=subprocess.STDOUT)
236 226
237 227
238 def release_tag(base_dir, tag_name, extension_name): 228 def release_tag(base_dir, tag_name, extension_name):
239 """Create a tag, along with a commit message for that tag.""" 229 """Create a tag, along with a commit message for that tag."""
240 subprocess.check_call([ 230 subprocess.check_call([
241 'hg', 'tag', '-R', base_dir, '-f', tag_name, 231 'hg', 'tag', '-R', base_dir, '-f', tag_name,
242 '-m', 'Noissue - Adding release tag for {} {}'.format( 232 '-m', 'Noissue - Adding release tag for {} {}'.format(
243 extension_name, tag_name)]) 233 extension_name, tag_name)])
244 234
245 235
246 def run(baseDir, platforms, version, keyFile, downloads_repo): 236 def run(baseDir, platforms, version, keyFile, downloads_repo):
247 if not can_safely_release(baseDir, downloads_repo): 237 if not can_safely_release(baseDir, downloads_repo):
248 print('Aborting release.') 238 print('Aborting release.')
249 return 1 239 return 1
250 240
251 target_platforms = sorted(platforms) 241 target_platforms = sorted(platforms)
252 release_identifier = '-'.join([version] + [p for p in target_platforms]) 242 release_identifier = '-'.join([version] + [p for p in target_platforms])
253 243
254 release_possible, reason = release_combination_is_possible( 244 release_possible, reason, re_release = release_combination_is_possible(
255 version, platforms, baseDir) 245 version, platforms, baseDir)
256 246
257 if not release_possible: 247 if not release_possible:
258 logging.error(reason) 248 logging.error(reason)
259 return 2 249 return 2
260 250
261 downloads = [] 251 downloads = []
262 # Read extension name from first provided platform 252 # Read extension name from first provided platform
263 locale_config = read_locale_config( 253 locale_config = read_locale_config(
264 baseDir, target_platforms[0], 254 baseDir, target_platforms[0],
(...skipping 19 matching lines...) Expand all
284 ) 274 )
285 275
286 # Only create one commit, one tag and one source archive for all 276 # Only create one commit, one tag and one source archive for all
287 # platforms 277 # platforms
288 archive_path = os.path.join( 278 archive_path = os.path.join(
289 downloads_repo, 279 downloads_repo,
290 'adblockplus-{}-source.tgz'.format(release_identifier), 280 'adblockplus-{}-source.tgz'.format(release_identifier),
291 ) 281 )
292 create_sourcearchive(baseDir, archive_path) 282 create_sourcearchive(baseDir, archive_path)
293 downloads.append(archive_path) 283 downloads.append(archive_path)
294 284 try:
295 release_commit(baseDir, extension_name, version, target_platforms) 285 release_commit(baseDir, extension_name, version, target_platforms)
286 except subprocess.CalledProcessError as e:
287 if not (re_release and 'nothing changed' in e.output):
288 raise
289
296 release_tag(baseDir, release_identifier, extension_name) 290 release_tag(baseDir, release_identifier, extension_name)
297 291
298 # Now add the downloads and commit 292 # Now add the downloads and commit
299 subprocess.check_call(['hg', 'add', '-R', downloads_repo] + downloads) 293 subprocess.check_call(['hg', 'add', '-R', downloads_repo] + downloads)
300 release_commit(downloads_repo, extension_name, version, target_platforms) 294 release_commit(downloads_repo, extension_name, version, target_platforms)
301 295
302 # Push all changes 296 # Push all changes
303 subprocess.check_call(['hg', 'push', '-R', baseDir]) 297 subprocess.check_call(['hg', 'push', '-R', baseDir])
304 subprocess.check_call(['hg', 'push', '-R', downloads_repo]) 298 subprocess.check_call(['hg', 'push', '-R', downloads_repo])
LEFTRIGHT

Powered by Google App Engine
This is Rietveld