build: reuse common methods for getting metadata from APKs

This splits out the code that gets the list of native ABIs supported, then
uses the standard methods for the rest.
This commit is contained in:
Hans-Christoph Steiner 2018-09-17 22:44:53 +02:00
parent 487c4d02f3
commit 807bf3d26b
4 changed files with 109 additions and 79 deletions

View File

@ -42,7 +42,7 @@ from . import net
from . import metadata
from . import scanner
from . import vmtools
from .common import FDroidPopen, SdkToolsPopen
from .common import FDroidPopen
from .exception import FDroidException, BuildException, VCSException
try:
@ -323,92 +323,28 @@ def transform_first_char(string, method):
return method(string[0]) + string[1:]
def has_native_code(apkobj):
"""aapt checks if there are architecture folders under the lib/ folder
so we are simulating the same behaviour"""
arch_re = re.compile("^lib/(.*)/.*$")
arch = [file for file in apkobj.get_files() if arch_re.match(file)]
return False if not arch else True
def get_apk_metadata_aapt(apkfile):
"""aapt function to extract versionCode, versionName, packageName and nativecode"""
vercode = None
version = None
foundid = None
nativecode = None
p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile], output=False)
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:]
return vercode, version, foundid, nativecode
def get_apk_metadata_androguard(apkfile):
"""androguard function to extract versionCode, versionName, packageName and nativecode"""
try:
from androguard.core.bytecodes.apk import APK
apkobject = APK(apkfile)
except ImportError:
raise BuildException("androguard library is not installed and aapt binary not found")
except FileNotFoundError:
raise BuildException("Could not open apk file for metadata analysis")
if not apkobject.is_valid_APK():
raise BuildException("Invalid APK provided")
foundid = apkobject.get_package()
vercode = apkobject.get_androidversion_code()
version = apkobject.get_androidversion_name()
nativecode = has_native_code(apkobject)
return vercode, version, foundid, nativecode
def get_metadata_from_apk(app, build, apkfile):
"""get the required metadata from the built APK"""
"""get the required metadata from the built APK
if common.SdkToolsPopen(['aapt', 'version'], output=False):
vercode, version, foundid, nativecode = get_apk_metadata_aapt(apkfile)
else:
vercode, version, foundid, nativecode = get_apk_metadata_androguard(apkfile)
versionName is allowed to be a blank string, i.e. ''
"""
# 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
appid, versionCode, versionName = common.get_apk_id(apkfile)
native_code = common.get_native_code(apkfile)
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.buildjni and build.buildjni != ['no'] and not native_code:
raise BuildException("Native code should have been built but none was packaged")
if build.novcheck:
vercode = build.versionCode
version = build.versionName
if not version or not vercode:
versionCode = build.versionCode
versionName = build.versionName
if not versionCode or versionName is None:
raise BuildException("Could not find version information in build in output")
if not foundid:
if not appid:
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 appid != app.id:
raise BuildException("Wrong package ID - build " + appid + " but expected " + app.id)
return vercode, version
return versionCode, versionName
def build_local(app, build, vcs, build_dir, output_dir, log_dir, srclib_dir, extlib_dir, tmp_dir, force, onserver, refresh):

View File

@ -2128,6 +2128,19 @@ def get_apk_id_aapt(apkfile):
.format(apkfilename=apkfile))
def get_native_code(apkfile):
"""aapt checks if there are architecture folders under the lib/ folder
so we are simulating the same behaviour"""
arch_re = re.compile("^lib/(.*)/.*$")
archset = set()
with ZipFile(apkfile) as apk:
for filename in apk.namelist():
m = arch_re.match(filename)
if m:
archset.add(m.group(1))
return sorted(list(archset))
def get_minSdkVersion_aapt(apkfile):
"""Extract the minimum supported Android SDK from an APK using aapt

View File

@ -20,6 +20,7 @@ if localmodule not in sys.path:
import fdroidserver.build
import fdroidserver.common
import fdroidserver.metadata
class BuildTest(unittest.TestCase):
@ -71,6 +72,55 @@ class BuildTest(unittest.TestCase):
filedata = f.read()
self.assertIsNotNone(pattern.search(filedata))
def test_get_apk_metadata(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)
fdroidserver.common.config = config
fdroidserver.build.config = config
self._set_build_tools()
try:
config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt')
except fdroidserver.exception.FDroidException:
pass # aapt is not required if androguard is present
testcases = [
('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1', None),
('org.bitbucket.tickytacky.mirrormirror_1.apk', 'org.bitbucket.tickytacky.mirrormirror', '1', '1.0', None),
('org.bitbucket.tickytacky.mirrormirror_2.apk', 'org.bitbucket.tickytacky.mirrormirror', '2', '1.0.1', None),
('org.bitbucket.tickytacky.mirrormirror_3.apk', 'org.bitbucket.tickytacky.mirrormirror', '3', '1.0.2', None),
('org.bitbucket.tickytacky.mirrormirror_4.apk', 'org.bitbucket.tickytacky.mirrormirror', '4', '1.0.3', None),
('org.dyndns.fules.ck_20.apk', 'org.dyndns.fules.ck', '20', 'v1.6pre2',
['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64']),
('urzip.apk', 'info.guardianproject.urzip', '100', '0.1', None),
('urzip-badcert.apk', 'info.guardianproject.urzip', '100', '0.1', None),
('urzip-badsig.apk', 'info.guardianproject.urzip', '100', '0.1', None),
('urzip-release.apk', 'info.guardianproject.urzip', '100', '0.1', None),
('urzip-release-unsigned.apk', 'info.guardianproject.urzip', '100', '0.1', None),
('repo/com.politedroid_3.apk', 'com.politedroid', '3', '1.2', None),
('repo/com.politedroid_4.apk', 'com.politedroid', '4', '1.3', None),
('repo/com.politedroid_5.apk', 'com.politedroid', '5', '1.4', None),
('repo/com.politedroid_6.apk', 'com.politedroid', '6', '1.5', None),
('repo/duplicate.permisssions_9999999.apk', 'duplicate.permisssions', '9999999', '', None),
('repo/info.zwanenburg.caffeinetile_4.apk', 'info.zwanenburg.caffeinetile', '4', '1.3', None),
('repo/obb.main.oldversion_1444412523.apk', 'obb.main.oldversion', '1444412523', '0.1', None),
('repo/obb.mainpatch.current_1619_another-release-key.apk', 'obb.mainpatch.current', '1619', '0.1', None),
('repo/obb.mainpatch.current_1619.apk', 'obb.mainpatch.current', '1619', '0.1', None),
('repo/obb.main.twoversions_1101613.apk', 'obb.main.twoversions', '1101613', '0.1', None),
('repo/obb.main.twoversions_1101615.apk', 'obb.main.twoversions', '1101615', '0.1', None),
('repo/obb.main.twoversions_1101617.apk', 'obb.main.twoversions', '1101617', '0.1', None),
('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', 'info.guardianproject.urzip', '100', '0.1', None),
]
for apkfilename, appid, versionCode, versionName, nativecode in testcases:
app = fdroidserver.metadata.App()
app.id = appid
build = fdroidserver.metadata.Build()
build.buildjni = ['yes'] if nativecode else build.buildjni
build.versionCode = versionCode
build.versionName = versionName
vc, vn = fdroidserver.build.get_metadata_from_apk(app, build, apkfilename)
self.assertEqual(versionCode, vc)
self.assertEqual(versionName, vn)
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))

View File

@ -636,6 +636,37 @@ class CommonTest(unittest.TestCase):
else:
self.fail('could not parse aapt output: {}'.format(f))
def test_get_native_code(self):
testcases = [
('repo/obb.main.twoversions_1101613.apk', []),
('org.bitbucket.tickytacky.mirrormirror_1.apk', []),
('org.bitbucket.tickytacky.mirrormirror_2.apk', []),
('org.bitbucket.tickytacky.mirrormirror_3.apk', []),
('org.bitbucket.tickytacky.mirrormirror_4.apk', []),
('org.dyndns.fules.ck_20.apk', ['arm64-v8a', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'x86', 'x86_64']),
('urzip.apk', []),
('urzip-badcert.apk', []),
('urzip-badsig.apk', []),
('urzip-release.apk', []),
('urzip-release-unsigned.apk', []),
('repo/com.politedroid_3.apk', []),
('repo/com.politedroid_4.apk', []),
('repo/com.politedroid_5.apk', []),
('repo/com.politedroid_6.apk', []),
('repo/duplicate.permisssions_9999999.apk', []),
('repo/info.zwanenburg.caffeinetile_4.apk', []),
('repo/obb.main.oldversion_1444412523.apk', []),
('repo/obb.mainpatch.current_1619_another-release-key.apk', []),
('repo/obb.mainpatch.current_1619.apk', []),
('repo/obb.main.twoversions_1101613.apk', []),
('repo/obb.main.twoversions_1101615.apk', []),
('repo/obb.main.twoversions_1101617.apk', []),
('repo/urzip-; Рахма́, [rɐxˈmanʲɪnəf] سيرجي_رخمانينوف 谢·.apk', []),
]
for apkfilename, native_code in testcases:
nc = fdroidserver.common.get_native_code(apkfilename)
self.assertEqual(native_code, nc)
def test_get_minSdkVersion_aapt(self):
config = dict()
fdroidserver.common.fill_config_defaults(config)