o.write(line)
if not placed:
o.write('android.library.reference.%d=%s\n' % (number, relpath))
+
+
+def compare_apks(apk1, apk2, tmp_dir):
+ """Compare two apks
+
+ Returns None if the apk content is the same (apart from the signing key),
+ otherwise a string describing what's different, or what went wrong when
+ trying to do the comparison.
+ """
+
+ thisdir = os.path.join(tmp_dir, 'this_apk')
+ thatdir = os.path.join(tmp_dir, 'that_apk')
+ for d in [thisdir, thatdir]:
+ if os.path.exists(d):
+ shutil.rmtree(d)
+ os.mkdir(d)
+
+ if subprocess.call(['jar', 'xf',
+ os.path.abspath(apk1)],
+ cwd=thisdir) != 0:
+ return("Failed to unpack " + apk1)
+ if subprocess.call(['jar', 'xf',
+ os.path.abspath(apk2)],
+ cwd=thatdir) != 0:
+ return("Failed to unpack " + apk2)
+
+ p = FDroidPopen(['diff', '-r', 'this_apk', 'that_apk'], cwd=tmp_dir,
+ output=False)
+ lines = p.output.splitlines()
+ if len(lines) != 1 or 'META-INF' not in lines[0]:
+ return("Unexpected diff output - " + p.output)
+
+ # If we get here, it seems like they're the same!
+ return None
+
+
+
continue
logging.info("Processing " + apkfile)
- # Figure out the key alias name we'll use. Only the first 8
- # characters are significant, so we'll use the first 8 from
- # the MD5 of the app's ID and hope there are no collisions.
- # If a collision does occur later, we're going to have to
- # come up with a new alogrithm, AND rename all existing keys
- # in the keystore!
- if appid in config['keyaliases']:
- # For this particular app, the key alias is overridden...
- keyalias = config['keyaliases'][appid]
- if keyalias.startswith('@'):
+ # There ought to be valid metadata for this app, otherwise why are we
+ # trying to publish it?
+ if not appid in allapps:
+ logging.error("Unexpected {0} found in unsigned directory"
+ .format(apkfilename))
+ sys.exit(1)
+ app = allapps[appid]
+
+ if 'Binaries' in app:
+
+ # 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.
+
+ # Need the version name for the version code...
+ versionname = None
+ for build in app['builds']:
+ if build['vercode'] == vercode:
+ versionname = build['version']
+ break
+ if not versionname:
+ logging.error("...no defined build for version code {0}"
+ .format(vercode))
+ continue
+
+ # Figure out where the developer's binary is supposed to come from...
+ url = app['Binaries']
+ url = url.replace('%v', versionname)
+ url = url.replace('%c', str(vercode))
+
+ # Grab the binary from where the developer publishes it...
+ logging.info("...retrieving " + url)
+ srcapk = os.path.join(tmp_dir, url.split('/')[-1])
+ p = FDroidPopen(['wget', '-nv', url], cwd=tmp_dir)
+ if p.returncode != 0 or not os.path.exists(srcapk):
+ logging.error("...failed to retrieve " + url +
+ " - publish skipped")
+ continue
+
+ # Compare our unsigned one with the downloaded one...
+ compare_result = common.compare_apks(srcapk, apkfile, tmp_dir)
+ if compare_result:
+ logging.error("...verfication failed - publish skipped : "
+ + compare_result)
+ continue
+
+ # Success! So move the downloaded file to the repo...
+ shutil.move(srcapk, os.path.join(output_dir, apkfilename))
+
+ else:
+
+ # It's a 'normal' app, i.e. we sign and publish it...
+
+ # Figure out the key alias name we'll use. Only the first 8
+ # characters are significant, so we'll use the first 8 from
+ # the MD5 of the app's ID and hope there are no collisions.
+ # If a collision does occur later, we're going to have to
+ # come up with a new alogrithm, AND rename all existing keys
+ # in the keystore!
+ if appid in config['keyaliases']:
+ # For this particular app, the key alias is overridden...
+ keyalias = config['keyaliases'][appid]
+ if keyalias.startswith('@'):
+ m = md5.new()
+ m.update(keyalias[1:])
+ keyalias = m.hexdigest()[:8]
+ else:
m = md5.new()
- m.update(keyalias[1:])
+ m.update(appid)
keyalias = m.hexdigest()[:8]
- else:
- m = md5.new()
- m.update(appid)
- keyalias = m.hexdigest()[:8]
- logging.info("Key alias: " + keyalias)
-
- # See if we already have a key for this application, and
- # if not generate one...
- p = FDroidPopen(['keytool', '-list',
- '-alias', keyalias, '-keystore', config['keystore'],
- '-storepass:file', config['keystorepassfile']])
- if p.returncode != 0:
- logging.info("Key does not exist - generating...")
- p = FDroidPopen(['keytool', '-genkey',
- '-keystore', config['keystore'],
- '-alias', keyalias,
- '-keyalg', 'RSA', '-keysize', '2048',
- '-validity', '10000',
+ logging.info("Key alias: " + keyalias)
+
+ # See if we already have a key for this application, and
+ # if not generate one...
+ p = FDroidPopen(['keytool', '-list',
+ '-alias', keyalias, '-keystore', config['keystore'],
+ '-storepass:file', config['keystorepassfile']])
+ if p.returncode != 0:
+ logging.info("Key does not exist - generating...")
+ p = FDroidPopen(['keytool', '-genkey',
+ '-keystore', config['keystore'],
+ '-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
+ if p.returncode != 0:
+ raise BuildException("Failed to generate key")
+
+ # Sign the application...
+ p = FDroidPopen(['jarsigner', '-keystore', config['keystore'],
'-storepass:file', config['keystorepassfile'],
- '-keypass:file', config['keypassfile'],
- '-dname', config['keydname']])
+ '-keypass:file', config['keypassfile'], '-sigalg',
+ 'MD5withRSA', '-digestalg', 'SHA1',
+ apkfile, keyalias])
# TODO keypass should be sent via stdin
if p.returncode != 0:
- raise BuildException("Failed to generate key")
-
- # Sign the application...
- p = FDroidPopen(['jarsigner', '-keystore', config['keystore'],
- '-storepass:file', config['keystorepassfile'],
- '-keypass:file', config['keypassfile'], '-sigalg',
- 'MD5withRSA', '-digestalg', 'SHA1',
- apkfile, keyalias])
- # TODO keypass should be sent via stdin
- if p.returncode != 0:
- raise BuildException("Failed to sign application")
-
- # Zipalign it...
- p = FDroidPopen([config['zipalign'], '-v', '4', apkfile,
- os.path.join(output_dir, apkfilename)])
- if p.returncode != 0:
- raise BuildException("Failed to align application")
- os.remove(apkfile)
+ raise BuildException("Failed to sign application")
+
+ # Zipalign it...
+ p = FDroidPopen([config['zipalign'], '-v', '4', apkfile,
+ os.path.join(output_dir, apkfilename)])
+ if p.returncode != 0:
+ raise BuildException("Failed to align application")
+ os.remove(apkfile)
# Move the source tarball into the output directory...
tarfilename = apkfilename[:-4] + '_src.tar.gz'
import sys
import os
-import shutil
-import subprocess
import glob
from optparse import OptionParser
import logging
os.remove(remoteapk)
url = 'https://f-droid.org/repo/' + apkfilename
logging.info("...retrieving " + url)
- p = FDroidPopen(['wget', url], cwd=tmp_dir)
+ p = FDroidPopen(['wget', '-nv', url], cwd=tmp_dir)
if p.returncode != 0:
raise FDroidException("Failed to get " + apkfilename)
- thisdir = os.path.join(tmp_dir, 'this_apk')
- thatdir = os.path.join(tmp_dir, 'that_apk')
- for d in [thisdir, thatdir]:
- if os.path.exists(d):
- shutil.rmtree(d)
- os.mkdir(d)
-
- if subprocess.call(['jar', 'xf',
- os.path.join("..", "..", unsigned_dir, apkfilename)],
- cwd=thisdir) != 0:
- raise FDroidException("Failed to unpack local build of " + apkfilename)
- if subprocess.call(['jar', 'xf',
- os.path.join("..", "..", remoteapk)],
- cwd=thatdir) != 0:
- raise FDroidException("Failed to unpack remote build of " + apkfilename)
-
- p = FDroidPopen(['diff', '-r', 'this_apk', 'that_apk'], cwd=tmp_dir)
- lines = p.output.splitlines()
- if len(lines) != 1 or 'META-INF' not in lines[0]:
- raise FDroidException("Unexpected diff output - " + p.output)
+ compare_result = common.compare_apks(
+ os.path.join(unsigned_dir, apkfilename),
+ remoteapk,
+ tmp_dir)
+ if compare_result:
+ raise FDroidException(compare_result)
logging.info("...successfully verified")
verified += 1