diff --git a/.taskcluster.yml b/.taskcluster.yml index 567995129..82b746762 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -60,6 +60,7 @@ tasks: - "queue:create-task:aws-provisioner-v1/github-worker" - "queue:scheduler-id:taskcluster-github" - "secrets:get:project/fenix/sentry" + - "secrets:get:project/fenix/preview-key-store" - "queue:route:index.project.fenix.android.preview-builds" payload: maxRunTime: 3600 @@ -74,16 +75,17 @@ tasks: && git checkout {{event.head.sha}} && python automation/taskcluster/get-secret.py -s project/fenix/sentry -k sentryDsn -f .sentry && ./gradlew --no-daemon clean assemble test detektCheck ktlint lint + && ./gradlew --no-daemon clean assembleRelease + && python automation/taskcluster/get-secret.py -s project/fenix/preview-key-store -k keyStoreFile -f .store --decode + && python automation/taskcluster/get-secret.py -s project/fenix/preview-key-store -k keyStorePassword -f .store_token + && python automation/taskcluster/get-secret.py -s project/fenix/preview-key-store -k keyPassword -f .key_token + && python automation/taskcluster/sign-builds.py --zipalign --path ./app/build/outputs/apk --store .store --store-token .store_token --key-alias preview-key --key-token .key_token --archive preview features: taskclusterProxy: true artifacts: - 'public/reports': - type: 'directory' - path: '/build/phoenix/build/reports' - expires: "{{ '1 week' | $fromNow }}" - 'public/app/reports': + 'public/preview': type: 'directory' - path: '/build/phoenix/app/build/reports' + path: '/build/phoenix/preview' expires: "{{ '1 week' | $fromNow }}" metadata: name: Fenix - Master build diff --git a/automation/taskcluster/sign-builds.py b/automation/taskcluster/sign-builds.py new file mode 100644 index 000000000..45af1f958 --- /dev/null +++ b/automation/taskcluster/sign-builds.py @@ -0,0 +1,80 @@ +# 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/. + +import argparse +import fnmatch +import glob +import os +import subprocess + +def collect_apks(path, pattern): + matches = [] + for root, dirnames, filenames in os.walk(path): + for filename in fnmatch.filter(filenames, pattern): + matches.append(os.path.join(root, filename)) + return matches + +def zipalign(path): + unsigned_apks = collect_apks(path, '*-unsigned.apk') + print "Found %d APK(s) to zipalign in %s" % (len(unsigned_apks), path) + for apk in unsigned_apks: + print "Zipaligning", apk + split = os.path.splitext(apk) + print subprocess.check_output(["zipalign", "-f", "-v", "-p", "4", apk, split[0] + "-aligned" + split[1]]) + +def sign(path, store, store_token, key_alias, key_token): + unsigned_apks = collect_apks(path, '*-aligned.apk') + print "Found %d APK(s) to sign in %s" % (len(unsigned_apks), path) + + for apk in unsigned_apks: + print "Signing", apk + print subprocess.check_output([ + "apksigner", "sign", + "--ks", store, + "--ks-key-alias", key_alias, + "--ks-pass", "file:%s" % store_token, + "--key-pass", "file:%s" % key_token, + "-v", + "--out", apk.replace('unsigned', 'signed'), apk]) + +def archive(path, archive): + artifacts_path = os.path.join(archive) + if not os.path.exists(artifacts_path): + os.makedirs(artifacts_path) + + signed_apks = collect_apks(path, '*-signed-*.apk') + print "Found %d APK(s) to archive in %s" % (len(signed_apks), path) + + for apk in signed_apks: + print "Verifying", apk + print subprocess.check_output(['apksigner', 'verify', apk]) + + print "Archiving", apk + os.rename(apk, artifacts_path + "/" + os.path.basename(apk)) + +def main(): + parser = argparse.ArgumentParser( + description='Zipaligns, signs and archives APKs') + parser.add_argument('--path', dest="path", action="store", help='Root path to search for APK files') + parser.add_argument('--zipalign', dest="zipalign", action="store_true", default=False, help='Zipaligns APKs before signing') + parser.add_argument('--archive', metavar="PATH", dest="archive", action="store", default=False, help='Path to save sign APKs to') + + parser.add_argument('--store', metavar="PATH", dest="store", action="store", help='Path to keystore') + parser.add_argument('--store-token', metavar="PATH", dest="store_token", action="store", help='Path to keystore password file') + parser.add_argument('--key-alias', metavar="ALIAS", dest="key_alias", action="store", help='Key alias') + parser.add_argument('--key-token', metavar="PATH", dest="key_token", action="store", help='Path to key password file') + + result = parser.parse_args() + + if result.zipalign: + zipalign(result.path) + + sign(result.path, result.store, result.store_token, result.key_alias, result.key_token) + + if result.archive: + archive(result.path, result.archive) + + +if __name__ == "__main__": + main()