| OLD | NEW | 
|---|
| 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  Loading... | 
| 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]) | 
| OLD | NEW | 
|---|