chiark / gitweb /
Merge branch 'plural' into 'master'
[fdroidserver.git] / fdroidserver / publish.py
index f0089d862e9f619d9c10390a055dd8ef1a8eecc1..b15c12a8553e93e031a9bdc12ac84dcd269dc8fd 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # publish.py - part of the FDroid server tools
 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
 
 import sys
 import os
+import re
 import shutil
-import md5
 import glob
+import hashlib
 from argparse import ArgumentParser
 import logging
+from gettext import ngettext
 
-import common
-import metadata
-from common import FDroidPopen, SdkToolsPopen, BuildException
+from . import _
+from . import common
+from . import metadata
+from .common import FDroidPopen, SdkToolsPopen
+from .exception import BuildException
 
 config = None
 options = None
@@ -42,41 +45,40 @@ def main():
     parser = ArgumentParser(usage="%(prog)s [options] "
                             "[APPID[:VERCODE] [APPID[:VERCODE] ...]]")
     common.setup_global_opts(parser)
-    parser.add_argument("appid", nargs='*', help="app-id with optional versioncode in the form APPID[:VERCODE]")
+    parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]"))
+    metadata.add_metadata_arguments(parser)
     options = parser.parse_args()
+    metadata.warnings_action = options.W
 
     config = common.read_config(options)
 
     if not ('jarsigner' in config and 'keytool' in config):
-        logging.critical('Java JDK not found! Install in standard location or set java_paths!')
+        logging.critical(_('Java JDK not found! Install in standard location or set java_paths!'))
         sys.exit(1)
 
     log_dir = 'logs'
     if not os.path.isdir(log_dir):
-        logging.info("Creating log directory")
+        logging.info(_("Creating log directory"))
         os.makedirs(log_dir)
 
     tmp_dir = 'tmp'
     if not os.path.isdir(tmp_dir):
-        logging.info("Creating temporary directory")
+        logging.info(_("Creating temporary directory"))
         os.makedirs(tmp_dir)
 
     output_dir = 'repo'
     if not os.path.isdir(output_dir):
-        logging.info("Creating output directory")
+        logging.info(_("Creating output directory"))
         os.makedirs(output_dir)
 
     unsigned_dir = 'unsigned'
     if not os.path.isdir(unsigned_dir):
-        logging.warning("No unsigned directory - nothing to do")
+        logging.warning(_("No unsigned directory - nothing to do"))
         sys.exit(1)
 
-    for f in [config['keystorepassfile'],
-              config['keystore'],
-              config['keypassfile']]:
-        if not os.path.exists(f):
-            logging.error("Config error - missing '{0}'".format(f))
-            sys.exit(1)
+    if not os.path.exists(config['keystore']):
+        logging.error("Config error - missing '{0}'".format(config['keystore']))
+        sys.exit(1)
 
     # It was suggested at
     #    https://dev.guardianproject.info/projects/bazaar/wiki/FDroid_Audit
@@ -91,20 +93,21 @@ def main():
     vercodes = common.read_pkg_args(options.appid, True)
     allaliases = []
     for appid in allapps:
-        m = md5.new()
-        m.update(appid)
+        m = hashlib.md5()
+        m.update(appid.encode('utf-8'))
         keyalias = m.hexdigest()[:8]
         if keyalias in allaliases:
-            logging.error("There is a keyalias collision - publishing halted")
+            logging.error(_("There is a keyalias collision - publishing halted"))
             sys.exit(1)
         allaliases.append(keyalias)
-    logging.info("{0} apps, {0} key aliases".format(len(allapps),
-                                                    len(allaliases)))
+    logging.info(ngettext('{0} app, {1} key aliases',
+                          '{0} apps, {1} key aliases', len(allapps)).format(len(allapps), len(allaliases)))
 
-    # Process any apks that are waiting to be signed...
-    for apkfile in sorted(glob.glob(os.path.join(unsigned_dir, '*.apk'))):
+    # Process any APKs or ZIPs that are waiting to be signed...
+    for apkfile in sorted(glob.glob(os.path.join(unsigned_dir, '*.apk'))
+                          + glob.glob(os.path.join(unsigned_dir, '*.zip'))):
 
-        appid, vercode = common.apknameinfo(apkfile)
+        appid, vercode = common.publishednameinfo(apkfile)
         apkfilename = os.path.basename(apkfile)
         if vercodes and appid not in vercodes:
             continue
@@ -121,14 +124,14 @@ def main():
             sys.exit(1)
         app = allapps[appid]
 
-        if app.Binaries is not None:
+        if app.Binaries:
 
             # It's an app where we build from source, and verify the apk
             # contents against a developer's binary, and then publish their
             # version if everything checks out.
             # The binary should already have been retrieved during the build
             # process.
-            srcapk = apkfile + ".binary"
+            srcapk = re.sub(r'.apk$', '.binary.apk', apkfile)
 
             # Compare our unsigned one with the downloaded one...
             compare_result = common.verify_apks(srcapk, apkfile, tmp_dir)
@@ -142,6 +145,12 @@ def main():
             shutil.move(srcapk, os.path.join(output_dir, apkfilename))
             os.remove(apkfile)
 
+        elif apkfile.endswith('.zip'):
+
+            # OTA ZIPs built by fdroid do not need to be signed by jarsigner,
+            # just to be moved into place in the repo
+            shutil.move(apkfile, os.path.join(output_dir, apkfilename))
+
         else:
 
             # It's a 'normal' app, i.e. we sign and publish it...
@@ -156,20 +165,24 @@ def main():
                 # For this particular app, the key alias is overridden...
                 keyalias = config['keyaliases'][appid]
                 if keyalias.startswith('@'):
-                    m = md5.new()
-                    m.update(keyalias[1:])
+                    m = hashlib.md5()
+                    m.update(keyalias[1:].encode('utf-8'))
                     keyalias = m.hexdigest()[:8]
             else:
-                m = md5.new()
-                m.update(appid)
+                m = hashlib.md5()
+                m.update(appid.encode('utf-8'))
                 keyalias = m.hexdigest()[:8]
             logging.info("Key alias: " + keyalias)
 
             # See if we already have a key for this application, and
             # if not generate one...
+            env_vars = {
+                'FDROID_KEY_STORE_PASS': config['keystorepass'],
+                'FDROID_KEY_PASS': config['keypass'],
+            }
             p = FDroidPopen([config['keytool'], '-list',
                              '-alias', keyalias, '-keystore', config['keystore'],
-                             '-storepass:file', config['keystorepassfile']])
+                             '-storepass:env', 'FDROID_KEY_STORE_PASS'], envs=env_vars)
             if p.returncode != 0:
                 logging.info("Key does not exist - generating...")
                 p = FDroidPopen([config['keytool'], '-genkey',
@@ -177,28 +190,33 @@ def main():
                                  '-alias', keyalias,
                                  '-keyalg', 'RSA', '-keysize', '2048',
                                  '-validity', '10000',
-                                 '-storepass:file', config['keystorepassfile'],
-                                 '-keypass:file', config['keypassfile'],
-                                 '-dname', config['keydname']])
-                # TODO keypass should be sent via stdin
+                                 '-storepass:env', 'FDROID_KEY_STORE_PASS',
+                                 '-keypass:env', 'FDROID_KEY_PASS',
+                                 '-dname', config['keydname']], envs=env_vars)
                 if p.returncode != 0:
                     raise BuildException("Failed to generate key")
 
+            signed_apk_path = os.path.join(output_dir, apkfilename)
+            if os.path.exists(signed_apk_path):
+                raise BuildException("Refusing to sign '{0}' file exists in both "
+                                     "{1} and {2} folder.".format(apkfilename,
+                                                                  unsigned_dir,
+                                                                  output_dir))
+
             # Sign the application...
             p = FDroidPopen([config['jarsigner'], '-keystore', config['keystore'],
-                             '-storepass:file', config['keystorepassfile'],
-                             '-keypass:file', config['keypassfile'], '-sigalg',
+                             '-storepass:env', 'FDROID_KEY_STORE_PASS',
+                             '-keypass:env', 'FDROID_KEY_PASS', '-sigalg',
                              'SHA1withRSA', '-digestalg', 'SHA1',
-                             apkfile, keyalias])
-            # TODO keypass should be sent via stdin
+                             apkfile, keyalias], envs=env_vars)
             if p.returncode != 0:
-                raise BuildException("Failed to sign application")
+                raise BuildException(_("Failed to sign application"))
 
             # Zipalign it...
             p = SdkToolsPopen(['zipalign', '-v', '4', apkfile,
                                os.path.join(output_dir, apkfilename)])
             if p.returncode != 0:
-                raise BuildException("Failed to align application")
+                raise BuildException(_("Failed to align application"))
             os.remove(apkfile)
 
         # Move the source tarball into the output directory...