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, 2:17 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 items[index] if index < len(items) else 0
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(max(len(a_digits), len(b_digits))):
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)
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_output([
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 stderr=subprocess.STDOUT)
226
227
228 def release_tag(base_dir, tag_name, extension_name):
229 """Create a tag, along with a commit message for that tag."""
230 subprocess.check_call([
231 'hg', 'tag', '-R', base_dir, '-f', tag_name,
232 '-m', 'Noissue - Adding release tag for {} {}'.format(
233 extension_name, tag_name)])
234
235
236 def run(baseDir, platforms, version, keyFile, downloads_repo):
237 if not can_safely_release(baseDir, downloads_repo):
238 print('Aborting release.')
239 return 1
240
241 target_platforms = sorted(platforms)
242 release_identifier = '-'.join([version] + [p for p in target_platforms])
243
244 release_possible, reason, re_release = release_combination_is_possible(
245 version, platforms, baseDir)
246
247 if not release_possible:
248 logging.error(reason)
249 return 2
250
251 downloads = []
252 # Read extension name from first provided platform
253 locale_config = read_locale_config(
254 baseDir, target_platforms[0],
255 readMetadata(baseDir, target_platforms[0]))
256 default_locale_path = os.path.join(locale_config['base_path'],
257 locale_config['default_locale'],
142 'messages.json') 258 'messages.json')
143 with open(default_locale_path, 'r') as fp: 259 with open(default_locale_path, 'r') as fp:
144 extensionName = json.load(fp)['name']['message'] 260 extension_name = json.load(fp)['name']['message']
145 261
146 # Now commit the change and tag it 262 for platform in target_platforms:
147 subprocess.check_call(['hg', 'commit', '-R', baseDir, '-m', 'Releasing %s %s ' % (extensionName, version)]) 263 used_key_file = None
148 tag_name = version 264 if platform == 'chrome':
149 if type == 'edge': 265 # Currently, only chrome builds are provided by us as signed
150 tag_name = '{}-{}'.format(tag_name, type) 266 # packages. Create an unsigned package in base_dir which should be
151 subprocess.check_call(['hg', 'tag', '-R', baseDir, '-f', tag_name]) 267 # uploaded to the Chrome Web Store
268 create_build(platform, baseDir, baseDir, version)
269 used_key_file = keyFile
152 270
153 # Create a release build 271 downloads.append(
154 downloads = [] 272 create_build(platform, baseDir, downloads_repo, version,
155 if type == 'chrome': 273 used_key_file)
156 # Create both signed and unsigned Chrome builds (the latter for Chrome W eb Store). 274 )
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 275
161 buildPathUnsigned = os.path.join(baseDir, getDefaultFileName(metadata, v ersion, 'zip')) 276 # 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) 277 # platforms
163 elif type == 'edge': 278 archive_path = os.path.join(
164 # We only offer the Edge extension for use through the Windows Store 279 downloads_repo,
165 buildPath = os.path.join(downloadsRepo, getDefaultFileName(metadata, ver sion, 'appx')) 280 'adblockplus-{}-source.tgz'.format(release_identifier),
166 packager.createBuild(baseDir, type=type, outFile=buildPath, releaseBuild =True) 281 )
167 downloads.append(buildPath) 282 create_sourcearchive(baseDir, archive_path)
283 downloads.append(archive_path)
284 try:
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
168 289
169 # Create source archive 290 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 291
174 # Now add the downloads and commit 292 # Now add the downloads and commit
175 subprocess.check_call(['hg', 'add', '-R', downloadsRepo] + downloads) 293 subprocess.check_call(['hg', 'add', '-R', downloads_repo] + downloads)
176 subprocess.check_call(['hg', 'commit', '-R', downloadsRepo, '-m', 'Releasing %s %s' % (extensionName, version)]) 294 release_commit(downloads_repo, extension_name, version, target_platforms)
177 295
178 # Push all changes 296 # Push all changes
179 subprocess.check_call(['hg', 'push', '-R', baseDir]) 297 subprocess.check_call(['hg', 'push', '-R', baseDir])
180 subprocess.check_call(['hg', 'push', '-R', downloadsRepo]) 298 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