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

Side by Side Diff: releaseAutomation.py

Issue 29611593: Issue 5996 - Release consistent versions across WebExtensions (Closed) Base URL: https://codereview.adblockplus.org/29609559/
Patch Set: Addressing comments Created Nov. 28, 2017, 1:27 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « packagerChrome.py ('k') | tox.ini » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 re 9 import re
9 import codecs 10 import codecs
11 import logging
10 import subprocess 12 import subprocess
13 import sys
11 import tarfile 14 import tarfile
12 import json 15 import json
13 16
14 from packager import readMetadata, getDefaultFileName 17 from packager import readMetadata, getDefaultFileName, get_extension
18 from localeTools import read_locale_config
19
20 SOURCE_ARCHIVE = 'adblockplus-{}-source.tgz'
15 21
16 22
17 def get_dependencies(prefix, repos): 23 def get_dependencies(prefix, repos):
18 from ensure_dependencies import read_deps, safe_join 24 from ensure_dependencies import read_deps, safe_join
19 repo = repos[prefix] 25 repo = repos[prefix]
20 deps = read_deps(repo) 26 deps = read_deps(repo)
21 if deps: 27 if deps:
22 for subpath in deps: 28 for subpath in deps:
23 if subpath.startswith('_'): 29 if subpath.startswith('_'):
24 continue 30 continue
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
106 """Run repository-checks in order to bail out early if necessary""" 112 """Run repository-checks in order to bail out early if necessary"""
107 if repo_has_uncommitted(): 113 if repo_has_uncommitted():
108 return False 114 return False
109 if repo_has_incoming(*repo_paths): 115 if repo_has_incoming(*repo_paths):
110 return False 116 return False
111 if repo_has_outgoing(): 117 if repo_has_outgoing():
112 return continue_with_outgoing() 118 return continue_with_outgoing()
113 return True 119 return True
114 120
115 121
116 def run(baseDir, type, version, keyFile, downloadsRepo): 122 def compare_versions(a, b):
117 if not can_safely_release(baseDir, downloadsRepo): 123 """Compare two version numbers."""
118 print('Aborting release.') 124 a_digits = [int(v) for v in a.split('.')]
119 return 1 125 b_digits = [int(v) for v in b.split('.')]
120 126
121 if type == 'edge': 127 def safe_get(items, index):
122 import buildtools.packagerEdge as packager 128 return int(items[index]) if index < len(items) else 0
Wladimir Palant 2017/11/28 14:04:41 You don't need to call int() here, you converted t
tlucas 2017/11/28 14:21:15 Done.
123 elif type == 'chrome':
124 import buildtools.packagerChrome as packager
125 129
126 # Replace version number in metadata file "manually", ConfigParser will mess 130 for i in range(len(a_digits)):
Wladimir Palant 2017/11/28 14:04:41 This needs to be max(len(a_digits), len(b_digits))
tlucas 2017/11/28 14:21:15 Done.
127 # up the order of lines. 131 result = safe_get(a_digits, i) - safe_get(b_digits, i)
128 metadata = readMetadata(baseDir, type) 132 if result != 0:
129 with open(metadata.option_source('general', 'version'), 'r+b') as file: 133 return result
130 rawMetadata = file.read() 134 return 0
135
136
137 def release_combination_is_possible(version, platforms, base_dir):
138 """Determine whether a release for the given parameters is possible.
139
140 Examine existing tags in order to find either higher or matching versions.
141 The release is impossible if a) a higher version for a requested platform
142 exists, or if b) a matching version exists and the requested set of
143 platforms differs from what was already released.
144 """
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)
Wladimir Palant 2017/11/28 14:04:41 Technically speaking, "1" is a valid version numbe
tlucas 2017/11/28 14:21:15 Good point, Done.
156 tags = [
157 c for c in [
158 t.split('-') for t in
159 platform_tags.findall(subprocess.check_output(
160 ['hg', 'tags', '-R', base_dir, '-q']))
161 ] if compare_versions(c[0], version) >= 0
162 ]
163
164 for tag in tags:
165 if higher_tag_version(tag, version, platforms):
166 reason = ('The higher version {} has already been released for '
167 'the platforms {}.').format(tag[0], ', '.join(platforms))
168 return False, reason, None
169
170 if incomplete_platforms_for_version(tag, version, platforms):
171 reason = ('You have to re-release version {} for exactly all '
172 'of: {}').format(version, ', '.join(tag[1:]))
173 return False, reason, None
174
175 return (True, None,
176 any(compare_versions(tag[0], version) == 0 for tag in tags))
177
178
179 def update_metadata(metadata, version):
180 """Replace version number in metadata file "manually".
181
182 The ConfigParser would mess up the order of lines.
183 """
184 with open(metadata.option_source('general', 'version'), 'r+b') as fp:
185 rawMetadata = fp.read()
131 rawMetadata = re.sub( 186 rawMetadata = re.sub(
132 r'^(\s*version\s*=\s*).*', r'\g<1>%s' % version, 187 r'^(\s*version\s*=\s*).*', r'\g<1>%s' % version,
133 rawMetadata, flags=re.I | re.M 188 rawMetadata, flags=re.I | re.M
134 ) 189 )
135 190
136 file.seek(0) 191 fp.seek(0)
137 file.write(rawMetadata) 192 fp.write(rawMetadata)
138 file.truncate() 193 fp.truncate()
139 194
140 # Read extension name from locale data 195
141 default_locale_path = os.path.join('_locales', packager.defaultLocale, 196 def create_build(platform, base_dir, target_path, version, key_file=None):
197 """Create a build for the target platform and version."""
198 if platform == 'edge':
199 import buildtools.packagerEdge as packager
200 else:
201 import buildtools.packagerChrome as packager
202
203 metadata = readMetadata(base_dir, platform)
204 update_metadata(metadata, version)
205
206 build_path = os.path.join(
207 target_path,
208 getDefaultFileName(metadata, version,
209 get_extension(platform, key_file is not None))
210 )
211
212 packager.createBuild(base_dir, type=platform, outFile=build_path,
213 releaseBuild=True, keyFile=key_file)
214
215 return build_path
216
217
218 def release_commit(base_dir, extension_name, version, platforms):
219 """Create a release commit with a representative message."""
220 subprocess.check_call([
221 'hg', 'commit', '-R', base_dir, '-m',
222 'Noissue - Releasing {} {} for {}'.format(
223 extension_name, version,
224 ', '.join([p.capitalize() for p in platforms]))])
225
226
227 def release_tag(base_dir, tag_name, extension_name):
228 """Create a tag, along with a commit message for that tag."""
229 subprocess.check_call([
230 'hg', 'tag', '-R', base_dir, '-f', tag_name,
231 '-m', 'Noissue - Adding release tag for {} {}'.format(
232 extension_name, tag_name)])
233
234
235 def run(baseDir, platforms, version, keyFile, downloads_repo):
236 if not can_safely_release(baseDir, downloads_repo):
237 print('Aborting release.')
238 return 1
239
240 target_platforms = sorted(platforms)
241 release_identifier = '-'.join([version] + [p for p in target_platforms])
242
243 release_possible, reason, re_release = release_combination_is_possible(
244 version, platforms, baseDir)
245
246 if not release_possible:
247 logging.error(reason)
248 return 2
249
250 downloads = []
251 # Read extension name from first provided platform
252 locale_config = read_locale_config(
253 baseDir, target_platforms[0],
254 readMetadata(baseDir, target_platforms[0]))
255 default_locale_path = os.path.join(locale_config['base_path'],
256 locale_config['default_locale'],
142 'messages.json') 257 'messages.json')
143 with open(default_locale_path, 'r') as fp: 258 with open(default_locale_path, 'r') as fp:
144 extensionName = json.load(fp)['name']['message'] 259 extension_name = json.load(fp)['name']['message']
145 260
146 # Now commit the change and tag it 261 for platform in target_platforms:
147 subprocess.check_call(['hg', 'commit', '-R', baseDir, '-m', 'Releasing %s %s ' % (extensionName, version)]) 262 used_key_file = None
148 tag_name = version 263 if platform == 'chrome':
149 if type == 'edge': 264 # Currently, only chrome builds are provided by us as signed
150 tag_name = '{}-{}'.format(tag_name, type) 265 # packages. Create an unsigned package in base_dir which should be
151 subprocess.check_call(['hg', 'tag', '-R', baseDir, '-f', tag_name]) 266 # uploaded to the Chrome Web Store
267 create_build(platform, baseDir, baseDir, version)
268 used_key_file = keyFile
152 269
153 # Create a release build 270 downloads.append(
154 downloads = [] 271 create_build(platform, baseDir, downloads_repo, version,
155 if type == 'chrome': 272 used_key_file)
156 # Create both signed and unsigned Chrome builds (the latter for Chrome W eb Store). 273 )
157 buildPath = os.path.join(downloadsRepo, getDefaultFileName(metadata, ver sion, 'crx'))
158 packager.createBuild(baseDir, type=type, outFile=buildPath, releaseBuild =True, keyFile=keyFile)
159 downloads.append(buildPath)
160 274
161 buildPathUnsigned = os.path.join(baseDir, getDefaultFileName(metadata, v ersion, 'zip')) 275 # Only create one commit, one tag and one source archive for all
162 packager.createBuild(baseDir, type=type, outFile=buildPathUnsigned, rele aseBuild=True, keyFile=None) 276 # platforms
163 elif type == 'edge': 277 archive_path = os.path.join(
164 # We only offer the Edge extension for use through the Windows Store 278 downloads_repo,
165 buildPath = os.path.join(downloadsRepo, getDefaultFileName(metadata, ver sion, 'appx')) 279 'adblockplus-{}-source.tgz'.format(release_identifier),
166 packager.createBuild(baseDir, type=type, outFile=buildPath, releaseBuild =True) 280 )
167 downloads.append(buildPath) 281 create_sourcearchive(baseDir, archive_path)
282 downloads.append(archive_path)
283 if not re_release:
Wladimir Palant 2017/11/28 14:04:41 This might not be a good measure. What if the rele
tlucas 2017/11/28 14:21:15 Done. Replaced it with a try-except block, re-rais
284 release_commit(baseDir, extension_name, version, target_platforms)
168 285
169 # Create source archive 286 release_tag(baseDir, release_identifier, extension_name)
170 archivePath = os.path.splitext(buildPath)[0] + '-source.tgz'
171 create_sourcearchive(baseDir, archivePath)
172 downloads.append(archivePath)
173 287
174 # Now add the downloads and commit 288 # Now add the downloads and commit
175 subprocess.check_call(['hg', 'add', '-R', downloadsRepo] + downloads) 289 subprocess.check_call(['hg', 'add', '-R', downloads_repo] + downloads)
176 subprocess.check_call(['hg', 'commit', '-R', downloadsRepo, '-m', 'Releasing %s %s' % (extensionName, version)]) 290 release_commit(downloads_repo, extension_name, version, target_platforms)
177 291
178 # Push all changes 292 # Push all changes
179 subprocess.check_call(['hg', 'push', '-R', baseDir]) 293 subprocess.check_call(['hg', 'push', '-R', baseDir])
180 subprocess.check_call(['hg', 'push', '-R', downloadsRepo]) 294 subprocess.check_call(['hg', 'push', '-R', downloads_repo])
OLDNEW
« no previous file with comments | « packagerChrome.py ('k') | tox.ini » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld