allow arbitrary build products, not only APKs

This makes it so that the final build product can be specified in output=
and it'll work no matter if its an APK or not.  This was developed around
the case of building the OTA update.zip for the Privileged Extension. It
should work for any build process in theory but it has not yet been tested.

https://gitlab.com/fdroid/privileged-extension/issues/9
This commit is contained in:
Hans-Christoph Steiner 2016-10-31 19:53:55 +01:00
parent 8ecff5bd61
commit 84e09cd2a2
4 changed files with 66 additions and 54 deletions

View file

@ -454,6 +454,54 @@ def capitalize_intact(string):
return string[0].upper() + string[1:] return string[0].upper() + string[1:]
def get_metadata_from_apk(app, build, apkfile):
"""get the required metadata from the built APK"""
p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
vercode = None
version = None
foundid = None
nativecode = None
for line in p.output.splitlines():
if line.startswith("package:"):
pat = re.compile(".*name='([a-zA-Z0-9._]*)'.*")
m = pat.match(line)
if m:
foundid = m.group(1)
pat = re.compile(".*versionCode='([0-9]*)'.*")
m = pat.match(line)
if m:
vercode = m.group(1)
pat = re.compile(".*versionName='([^']*)'.*")
m = pat.match(line)
if m:
version = m.group(1)
elif line.startswith("native-code:"):
nativecode = line[12:]
# Ignore empty strings or any kind of space/newline chars that we don't
# care about
if nativecode is not None:
nativecode = nativecode.strip()
nativecode = None if not nativecode else nativecode
if build.buildjni and build.buildjni != ['no']:
if nativecode is None:
raise BuildException("Native code should have been built but none was packaged")
if build.novcheck:
vercode = build.vercode
version = build.version
if not version or not vercode:
raise BuildException("Could not find version information in build in output")
if not foundid:
raise BuildException("Could not find package ID in output")
if foundid != app.id:
raise BuildException("Wrong package ID - build " + foundid + " but expected " + app.id)
return vercode, version
def build_local(app, build, vcs, build_dir, output_dir, srclib_dir, extlib_dir, tmp_dir, force, onserver, refresh): def build_local(app, build, vcs, build_dir, output_dir, srclib_dir, extlib_dir, tmp_dir, force, onserver, refresh):
"""Do a build locally.""" """Do a build locally."""
@ -809,7 +857,7 @@ def build_local(app, build, vcs, build_dir, output_dir, srclib_dir, extlib_dir,
src = os.path.normpath(apks[0]) src = os.path.normpath(apks[0])
# Make sure it's not debuggable... # Make sure it's not debuggable...
if common.isApkDebuggable(src, config): if common.isApkAndDebuggable(src, config):
raise BuildException("APK is debuggable") raise BuildException("APK is debuggable")
# By way of a sanity check, make sure the version and version # By way of a sanity check, make sure the version and version
@ -818,56 +866,17 @@ def build_local(app, build, vcs, build_dir, output_dir, srclib_dir, extlib_dir,
if not os.path.exists(src): if not os.path.exists(src):
raise BuildException("Unsigned apk is not at expected location of " + src) raise BuildException("Unsigned apk is not at expected location of " + src)
p = SdkToolsPopen(['aapt', 'dump', 'badging', src], output=False) if common.get_file_extension(src) == 'apk':
vercode, version = get_metadata_from_apk(app, build, src)
vercode = None if (version != build.version or vercode != build.vercode):
version = None raise BuildException(("Unexpected version/version code in output;"
foundid = None " APK: '%s' / '%s', "
nativecode = None " Expected: '%s' / '%s'")
for line in p.output.splitlines(): % (version, str(vercode), build.version,
if line.startswith("package:"): str(build.vercode)))
pat = re.compile(".*name='([a-zA-Z0-9._]*)'.*") else:
m = pat.match(line)
if m:
foundid = m.group(1)
pat = re.compile(".*versionCode='([0-9]*)'.*")
m = pat.match(line)
if m:
vercode = m.group(1)
pat = re.compile(".*versionName='([^']*)'.*")
m = pat.match(line)
if m:
version = m.group(1)
elif line.startswith("native-code:"):
nativecode = line[12:]
# Ignore empty strings or any kind of space/newline chars that we don't
# care about
if nativecode is not None:
nativecode = nativecode.strip()
nativecode = None if not nativecode else nativecode
if build.buildjni and build.buildjni != ['no']:
if nativecode is None:
raise BuildException("Native code should have been built but none was packaged")
if build.novcheck:
vercode = build.vercode vercode = build.vercode
version = build.version version = build.version
if not version or not vercode:
raise BuildException("Could not find version information in build in output")
if not foundid:
raise BuildException("Could not find package ID in output")
if foundid != app.id:
raise BuildException("Wrong package ID - build " + foundid + " but expected " + app.id)
if (version != build.version or
vercode != build.vercode):
raise BuildException(("Unexpected version/version code in output;"
" APK: '%s' / '%s', "
" Expected: '%s' / '%s'")
% (version, str(vercode), build.version,
str(build.vercode))
)
# Add information for 'fdroid verify' to be able to reproduce the build # Add information for 'fdroid verify' to be able to reproduce the build
# environment. # environment.

View file

@ -1618,11 +1618,14 @@ def get_file_extension(filename):
return os.path.splitext(filename)[1].lower()[1:] return os.path.splitext(filename)[1].lower()[1:]
def isApkDebuggable(apkfile, config): def isApkAndDebuggable(apkfile, config):
"""Returns True if the given apk file is debuggable """Returns True if the given file is an APK and is debuggable
:param apkfile: full path to the apk to check""" :param apkfile: full path to the apk to check"""
if get_file_extension(apkfile) != 'apk':
return False
p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'], p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'],
output=False) output=False)
if p.returncode != 0: if p.returncode != 0:

View file

@ -739,7 +739,7 @@ def scan_apks(apkcache, repodir, knownapks, use_date_from_apk=False):
apk['minSdkVersion'] = 1 apk['minSdkVersion'] = 1
# Check for debuggable apks... # Check for debuggable apks...
if common.isApkDebuggable(apkfile, config): if common.isApkAndDebuggable(apkfile, config):
logging.warning('{0} is set to android:debuggable="true"'.format(apkfile)) logging.warning('{0} is set to android:debuggable="true"'.format(apkfile))
# Get the signature (or md5 of, to be precise)... # Get the signature (or md5 of, to be precise)...

View file

@ -74,7 +74,7 @@ class CommonTest(unittest.TestCase):
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')) testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk'))
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')) testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk'))
for apkfile in testfiles: for apkfile in testfiles:
debuggable = fdroidserver.common.isApkDebuggable(apkfile, config) debuggable = fdroidserver.common.isApkAndDebuggable(apkfile, config)
self.assertTrue(debuggable, self.assertTrue(debuggable,
"debuggable APK state was not properly parsed!") "debuggable APK state was not properly parsed!")
# these are set NOT debuggable # these are set NOT debuggable
@ -82,7 +82,7 @@ class CommonTest(unittest.TestCase):
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-release.apk')) testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-release.apk'))
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-release-unsigned.apk')) testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip-release-unsigned.apk'))
for apkfile in testfiles: for apkfile in testfiles:
debuggable = fdroidserver.common.isApkDebuggable(apkfile, config) debuggable = fdroidserver.common.isApkAndDebuggable(apkfile, config)
self.assertFalse(debuggable, self.assertFalse(debuggable,
"debuggable APK state was not properly parsed!") "debuggable APK state was not properly parsed!")