diff options
Diffstat (limited to 'testing/mozharness/scripts/mobile_partner_repack.py')
-rwxr-xr-x | testing/mozharness/scripts/mobile_partner_repack.py | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/testing/mozharness/scripts/mobile_partner_repack.py b/testing/mozharness/scripts/mobile_partner_repack.py new file mode 100755 index 000000000..8d99f825a --- /dev/null +++ b/testing/mozharness/scripts/mobile_partner_repack.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python +# ***** BEGIN LICENSE BLOCK ***** +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +# ***** END LICENSE BLOCK ***** +"""mobile_partner_repack.py + +""" + +from copy import deepcopy +import os +import sys + +# load modules from parent dir +sys.path.insert(1, os.path.dirname(sys.path[0])) + +from mozharness.base.errors import ZipErrorList +from mozharness.base.log import FATAL +from mozharness.base.transfer import TransferMixin +from mozharness.base.vcs.vcsbase import MercurialScript +from mozharness.mozilla.l10n.locales import LocalesMixin +from mozharness.mozilla.release import ReleaseMixin +from mozharness.mozilla.signing import MobileSigningMixin + +SUPPORTED_PLATFORMS = ["android"] + + +# MobilePartnerRepack {{{1 +class MobilePartnerRepack(LocalesMixin, ReleaseMixin, MobileSigningMixin, + TransferMixin, MercurialScript): + config_options = [[ + ['--locale', ], + {"action": "extend", + "dest": "locales", + "type": "string", + "help": "Specify the locale(s) to repack" + } + ], [ + ['--partner', ], + {"action": "extend", + "dest": "partners", + "type": "string", + "help": "Specify the partner(s) to repack" + } + ], [ + ['--locales-file', ], + {"action": "store", + "dest": "locales_file", + "type": "string", + "help": "Specify a json file to determine which locales to repack" + } + ], [ + ['--tag-override', ], + {"action": "store", + "dest": "tag_override", + "type": "string", + "help": "Override the tags set for all repos" + } + ], [ + ['--platform', ], + {"action": "extend", + "dest": "platforms", + "type": "choice", + "choices": SUPPORTED_PLATFORMS, + "help": "Specify the platform(s) to repack" + } + ], [ + ['--user-repo-override', ], + {"action": "store", + "dest": "user_repo_override", + "type": "string", + "help": "Override the user repo path for all repos" + } + ], [ + ['--release-config-file', ], + {"action": "store", + "dest": "release_config_file", + "type": "string", + "help": "Specify the release config file to use" + } + ], [ + ['--version', ], + {"action": "store", + "dest": "version", + "type": "string", + "help": "Specify the current version" + } + ], [ + ['--buildnum', ], + {"action": "store", + "dest": "buildnum", + "type": "int", + "default": 1, + "metavar": "INT", + "help": "Specify the current release build num (e.g. build1, build2)" + } + ]] + + def __init__(self, require_config_file=True): + self.release_config = {} + LocalesMixin.__init__(self) + MercurialScript.__init__( + self, + config_options=self.config_options, + all_actions=[ + "passphrase", + "clobber", + "pull", + "download", + "repack", + "upload-unsigned-bits", + "sign", + "upload-signed-bits", + "summary", + ], + require_config_file=require_config_file + ) + + # Helper methods {{{2 + def add_failure(self, platform, locale, **kwargs): + s = "%s:%s" % (platform, locale) + if 'message' in kwargs: + kwargs['message'] = kwargs['message'] % {'platform': platform, 'locale': locale} + super(MobilePartnerRepack, self).add_failure(s, **kwargs) + + def query_failure(self, platform, locale): + s = "%s:%s" % (platform, locale) + return super(MobilePartnerRepack, self).query_failure(s) + + # Actions {{{2 + + def pull(self): + c = self.config + dirs = self.query_abs_dirs() + repos = [] + replace_dict = {} + if c.get("user_repo_override"): + replace_dict['user_repo_override'] = c['user_repo_override'] + # deepcopy() needed because of self.config lock bug :( + for repo_dict in deepcopy(c['repos']): + repo_dict['repo'] = repo_dict['repo'] % replace_dict + repos.append(repo_dict) + else: + repos = c['repos'] + self.vcs_checkout_repos(repos, parent_dir=dirs['abs_work_dir'], + tag_override=c.get('tag_override')) + + def download(self): + c = self.config + rc = self.query_release_config() + dirs = self.query_abs_dirs() + locales = self.query_locales() + replace_dict = { + 'buildnum': rc['buildnum'], + 'version': rc['version'], + } + success_count = total_count = 0 + for platform in c['platforms']: + base_installer_name = c['installer_base_names'][platform] + base_url = c['download_base_url'] + '/' + \ + c['download_unsigned_base_subdir'] + '/' + \ + base_installer_name + replace_dict['platform'] = platform + for locale in locales: + replace_dict['locale'] = locale + url = base_url % replace_dict + installer_name = base_installer_name % replace_dict + parent_dir = '%s/original/%s/%s' % (dirs['abs_work_dir'], + platform, locale) + file_path = '%s/%s' % (parent_dir, installer_name) + self.mkdir_p(parent_dir) + total_count += 1 + if not self.download_file(url, file_path): + self.add_failure(platform, locale, + message="Unable to download %(platform)s:%(locale)s installer!") + else: + success_count += 1 + self.summarize_success_count(success_count, total_count, + message="Downloaded %d of %d installers successfully.") + + def _repack_apk(self, partner, orig_path, repack_path): + """ Repack the apk with a partner update channel. + Returns True for success, None for failure + """ + dirs = self.query_abs_dirs() + zip_bin = self.query_exe("zip") + unzip_bin = self.query_exe("unzip") + file_name = os.path.basename(orig_path) + tmp_dir = os.path.join(dirs['abs_work_dir'], 'tmp') + tmp_file = os.path.join(tmp_dir, file_name) + tmp_prefs_dir = os.path.join(tmp_dir, 'defaults', 'pref') + # Error checking for each step. + # Ignoring the mkdir_p()s since the subsequent copyfile()s will + # error out if unsuccessful. + if self.rmtree(tmp_dir): + return + self.mkdir_p(tmp_prefs_dir) + if self.copyfile(orig_path, tmp_file): + return + if self.write_to_file(os.path.join(tmp_prefs_dir, 'partner.js'), + 'pref("app.partner.%s", "%s");' % (partner, partner) + ) is None: + return + if self.run_command([unzip_bin, '-q', file_name, 'omni.ja'], + error_list=ZipErrorList, + return_type='num_errors', + cwd=tmp_dir): + self.error("Can't extract omni.ja from %s!" % file_name) + return + if self.run_command([zip_bin, '-9r', 'omni.ja', + 'defaults/pref/partner.js'], + error_list=ZipErrorList, + return_type='num_errors', + cwd=tmp_dir): + self.error("Can't add partner.js to omni.ja!") + return + if self.run_command([zip_bin, '-9r', file_name, 'omni.ja'], + error_list=ZipErrorList, + return_type='num_errors', + cwd=tmp_dir): + self.error("Can't re-add omni.ja to %s!" % file_name) + return + if self.unsign_apk(tmp_file): + return + repack_dir = os.path.dirname(repack_path) + self.mkdir_p(repack_dir) + if self.copyfile(tmp_file, repack_path): + return + return True + + def repack(self): + c = self.config + rc = self.query_release_config() + dirs = self.query_abs_dirs() + locales = self.query_locales() + success_count = total_count = 0 + for platform in c['platforms']: + for locale in locales: + installer_name = c['installer_base_names'][platform] % {'version': rc['version'], 'locale': locale} + if self.query_failure(platform, locale): + self.warning("%s:%s had previous issues; skipping!" % (platform, locale)) + continue + original_path = '%s/original/%s/%s/%s' % (dirs['abs_work_dir'], platform, locale, installer_name) + for partner in c['partner_config'].keys(): + repack_path = '%s/unsigned/partner-repacks/%s/%s/%s/%s' % (dirs['abs_work_dir'], partner, platform, locale, installer_name) + total_count += 1 + if self._repack_apk(partner, original_path, repack_path): + success_count += 1 + else: + self.add_failure(platform, locale, + message="Unable to repack %(platform)s:%(locale)s installer!") + self.summarize_success_count(success_count, total_count, + message="Repacked %d of %d installers successfully.") + + def _upload(self, dir_name="unsigned/partner-repacks"): + c = self.config + dirs = self.query_abs_dirs() + local_path = os.path.join(dirs['abs_work_dir'], dir_name) + rc = self.query_release_config() + replace_dict = { + 'buildnum': rc['buildnum'], + 'version': rc['version'], + } + remote_path = '%s/%s' % (c['ftp_upload_base_dir'] % replace_dict, dir_name) + if self.rsync_upload_directory(local_path, c['ftp_ssh_key'], + c['ftp_user'], c['ftp_server'], + remote_path): + self.return_code += 1 + + def upload_unsigned_bits(self): + self._upload() + + # passphrase() in AndroidSigningMixin + # verify_passphrases() in AndroidSigningMixin + + def preflight_sign(self): + if 'passphrase' not in self.actions: + self.passphrase() + self.verify_passphrases() + + def sign(self): + c = self.config + rc = self.query_release_config() + dirs = self.query_abs_dirs() + locales = self.query_locales() + success_count = total_count = 0 + for platform in c['platforms']: + for locale in locales: + installer_name = c['installer_base_names'][platform] % {'version': rc['version'], 'locale': locale} + if self.query_failure(platform, locale): + self.warning("%s:%s had previous issues; skipping!" % (platform, locale)) + continue + for partner in c['partner_config'].keys(): + unsigned_path = '%s/unsigned/partner-repacks/%s/%s/%s/%s' % (dirs['abs_work_dir'], partner, platform, locale, installer_name) + signed_dir = '%s/partner-repacks/%s/%s/%s' % (dirs['abs_work_dir'], partner, platform, locale) + signed_path = "%s/%s" % (signed_dir, installer_name) + total_count += 1 + self.info("Signing %s %s." % (platform, locale)) + if not os.path.exists(unsigned_path): + self.error("Missing apk %s!" % unsigned_path) + continue + if self.sign_apk(unsigned_path, c['keystore'], + self.store_passphrase, self.key_passphrase, + c['key_alias']) != 0: + self.add_summary("Unable to sign %s:%s apk!" % (platform, locale), level=FATAL) + else: + self.mkdir_p(signed_dir) + if self.align_apk(unsigned_path, signed_path): + self.add_failure(platform, locale, + message="Unable to align %(platform)s%(locale)s apk!") + self.rmtree(signed_dir) + else: + success_count += 1 + self.summarize_success_count(success_count, total_count, + message="Signed %d of %d apks successfully.") + + # TODO verify signatures. + + def upload_signed_bits(self): + self._upload(dir_name="partner-repacks") + + +# main {{{1 +if __name__ == '__main__': + mobile_partner_repack = MobilePartnerRepack() + mobile_partner_repack.run_and_exit() |