This commit is contained in:
Daniel Martí 2014-12-14 15:18:30 +01:00
commit 1a0831b5c0
12 changed files with 271 additions and 104 deletions

View file

@ -9,7 +9,7 @@
# Override the path to the Android NDK, $ANDROID_NDK by default # Override the path to the Android NDK, $ANDROID_NDK by default
# ndk_path = "/path/to/android-ndk" # ndk_path = "/path/to/android-ndk"
# Build tools version to be used # Build tools version to be used
build_tools = "21.1.2" # build_tools = "21.1.2"
# Command for running Ant # Command for running Ant
# ant = "/path/to/ant" # ant = "/path/to/ant"

View file

@ -35,7 +35,7 @@ import logging
import common import common
import metadata import metadata
from common import FDroidException, BuildException, VCSException, FDroidPopen, SilentPopen from common import FDroidException, BuildException, VCSException, FDroidPopen, SdkToolsPopen
try: try:
import paramiko import paramiko
@ -754,7 +754,7 @@ def build_local(app, thisbuild, vcs, build_dir, output_dir, srclib_dir, extlib_d
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 = SilentPopen([config['aapt'], 'dump', 'badging', src]) p = SdkToolsPopen(['aapt', 'dump', 'badging', src])
vercode = None vercode = None
version = None version = None

View file

@ -130,38 +130,6 @@ def read_config(opts, config_file='config.py'):
fill_config_defaults(config) fill_config_defaults(config)
if not test_sdk_exists(config):
sys.exit(3)
if not test_build_tools_exists(config):
sys.exit(3)
bin_paths = {
'aapt': [
os.path.join(config['sdk_path'], 'build-tools', config['build_tools'], 'aapt'),
],
'zipalign': [
os.path.join(config['sdk_path'], 'tools', 'zipalign'),
os.path.join(config['sdk_path'], 'build-tools', config['build_tools'], 'zipalign'),
],
'android': [
os.path.join(config['sdk_path'], 'tools', 'android'),
],
'adb': [
os.path.join(config['sdk_path'], 'platform-tools', 'adb'),
],
}
for b, paths in bin_paths.items():
config[b] = None
for path in paths:
if os.path.isfile(path):
config[b] = path
break
if config[b] is None:
logging.warn("Could not find %s in any of the following paths:\n%s" % (
b, '\n'.join(paths)))
# There is no standard, so just set up the most common environment # There is no standard, so just set up the most common environment
# variables # variables
env = os.environ env = os.environ
@ -197,7 +165,45 @@ def read_config(opts, config_file='config.py'):
return config return config
def find_sdk_tools_cmd(cmd):
'''find a working path to a tool from the Android SDK'''
tooldirs = []
if config is not None and 'sdk_path' in config and os.path.exists(config['sdk_path']):
# try to find a working path to this command, in all the recent possible paths
if 'build_tools' in config:
build_tools = os.path.join(config['sdk_path'], 'build-tools')
# if 'build_tools' was manually set and exists, check only that one
configed_build_tools = os.path.join(build_tools, config['build_tools'])
if os.path.exists(configed_build_tools):
tooldirs.append(configed_build_tools)
else:
# no configed version, so hunt known paths for it
for f in sorted(os.listdir(build_tools), reverse=True):
if os.path.isdir(os.path.join(build_tools, f)):
tooldirs.append(os.path.join(build_tools, f))
tooldirs.append(build_tools)
sdk_tools = os.path.join(config['sdk_path'], 'tools')
if os.path.exists(sdk_tools):
tooldirs.append(sdk_tools)
sdk_platform_tools = os.path.join(config['sdk_path'], 'platform-tools')
if os.path.exists(sdk_platform_tools):
tooldirs.append(sdk_platform_tools)
tooldirs.append('/usr/bin')
for d in tooldirs:
if os.path.isfile(os.path.join(d, cmd)):
return os.path.join(d, cmd)
# did not find the command, exit with error message
ensure_build_tools_exists(config)
def test_sdk_exists(thisconfig): def test_sdk_exists(thisconfig):
if 'sdk_path' not in thisconfig:
if 'aapt' in thisconfig and os.path.isfile(thisconfig['aapt']):
return True
else:
logging.error("'sdk_path' not set in config.py!")
return False
if thisconfig['sdk_path'] == default_config['sdk_path']: if thisconfig['sdk_path'] == default_config['sdk_path']:
logging.error('No Android SDK found!') logging.error('No Android SDK found!')
logging.error('You can use ANDROID_HOME to set the path to your SDK, i.e.:') logging.error('You can use ANDROID_HOME to set the path to your SDK, i.e.:')
@ -217,16 +223,15 @@ def test_sdk_exists(thisconfig):
return True return True
def test_build_tools_exists(thisconfig): def ensure_build_tools_exists(thisconfig):
if not test_sdk_exists(thisconfig): if not test_sdk_exists(thisconfig):
return False sys.exit(3)
build_tools = os.path.join(thisconfig['sdk_path'], 'build-tools') build_tools = os.path.join(thisconfig['sdk_path'], 'build-tools')
versioned_build_tools = os.path.join(build_tools, thisconfig['build_tools']) versioned_build_tools = os.path.join(build_tools, thisconfig['build_tools'])
if not os.path.isdir(versioned_build_tools): if not os.path.isdir(versioned_build_tools):
logging.critical('Android Build Tools path "' logging.critical('Android Build Tools path "'
+ versioned_build_tools + '" does not exist!') + versioned_build_tools + '" does not exist!')
return False sys.exit(3)
return True
def write_password_file(pwtype, password=None): def write_password_file(pwtype, password=None):
@ -1350,8 +1355,8 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
# Generate (or update) the ant build file, build.xml... # Generate (or update) the ant build file, build.xml...
if build['update'] and build['update'] != ['no'] and build['type'] == 'ant': if build['update'] and build['update'] != ['no'] and build['type'] == 'ant':
parms = [config['android'], 'update', 'lib-project'] parms = ['android', 'update', 'lib-project']
lparms = [config['android'], 'update', 'project'] lparms = ['android', 'update', 'project']
if build['target']: if build['target']:
parms += ['-t', build['target']] parms += ['-t', build['target']]
@ -1369,7 +1374,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
else: else:
logging.debug("Updating subproject %s" % d) logging.debug("Updating subproject %s" % d)
cmd = lparms + ['-p', d] cmd = lparms + ['-p', d]
p = FDroidPopen(cmd, cwd=root_dir) p = SdkToolsPopen(cmd, cwd=root_dir)
# Check to see whether an error was returned without a proper exit # Check to see whether an error was returned without a proper exit
# code (this is the case for the 'no target set or target invalid' # code (this is the case for the 'no target set or target invalid'
# error) # error)
@ -1600,9 +1605,7 @@ def isApkDebuggable(apkfile, config):
:param apkfile: full path to the apk to check""" :param apkfile: full path to the apk to check"""
p = SilentPopen([os.path.join(config['sdk_path'], 'build-tools', p = SdkToolsPopen(['aapt', 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'])
config['build_tools'], 'aapt'),
'dump', 'xmltree', apkfile, 'AndroidManifest.xml'])
if p.returncode != 0: if p.returncode != 0:
logging.critical("Failed to get apk manifest information") logging.critical("Failed to get apk manifest information")
sys.exit(1) sys.exit(1)
@ -1641,6 +1644,14 @@ class PopenResult:
output = '' output = ''
def SdkToolsPopen(commands, cwd=None, shell=False):
cmd = commands[0]
if cmd not in config:
config[cmd] = find_sdk_tools_cmd(commands[0])
return FDroidPopen([config[cmd]] + commands[1:],
cwd=cwd, shell=shell, output=False)
def SilentPopen(commands, cwd=None, shell=False): def SilentPopen(commands, cwd=None, shell=False):
return FDroidPopen(commands, cwd=cwd, shell=shell, output=False) return FDroidPopen(commands, cwd=cwd, shell=shell, output=False)

View file

@ -124,6 +124,7 @@ def main():
prefix = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) prefix = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
examplesdir = prefix + '/examples' examplesdir = prefix + '/examples'
aapt = None
fdroiddir = os.getcwd() fdroiddir = os.getcwd()
test_config = dict() test_config = dict()
common.fill_config_defaults(test_config) common.fill_config_defaults(test_config)
@ -133,6 +134,13 @@ def main():
if options.android_home is not None: if options.android_home is not None:
test_config['sdk_path'] = options.android_home test_config['sdk_path'] = options.android_home
elif not common.test_sdk_exists(test_config): elif not common.test_sdk_exists(test_config):
if os.path.isfile('/usr/bin/aapt'):
# remove sdk_path and build_tools, they are not required
test_config.pop('sdk_path', None)
test_config.pop('build_tools', None)
# make sure at least aapt is found, since this can't do anything without it
test_config['aapt'] = common.find_sdk_tools_cmd('aapt')
else:
# if neither --android-home nor the default sdk_path exist, prompt the user # if neither --android-home nor the default sdk_path exist, prompt the user
default_sdk_path = '/opt/android-sdk' default_sdk_path = '/opt/android-sdk'
while not options.no_prompt: while not options.no_prompt:
@ -162,12 +170,14 @@ def main():
# "$ANDROID_HOME" may be used if the env var is set up correctly. # "$ANDROID_HOME" may be used if the env var is set up correctly.
# If android_home is not None, the path given from the command line # If android_home is not None, the path given from the command line
# will be directly written in the config. # will be directly written in the config.
if 'sdk_path' in test_config:
write_to_config(test_config, 'sdk_path', options.android_home) write_to_config(test_config, 'sdk_path', options.android_home)
else: else:
logging.warn('Looks like this is already an F-Droid repo, cowardly refusing to overwrite it...') logging.warn('Looks like this is already an F-Droid repo, cowardly refusing to overwrite it...')
logging.info('Try running `fdroid init` in an empty directory.') logging.info('Try running `fdroid init` in an empty directory.')
sys.exit() sys.exit()
if not 'aapt' in test_config or not os.path.isfile(test_config['aapt']):
# try to find a working aapt, in all the recent possible paths # try to find a working aapt, in all the recent possible paths
build_tools = os.path.join(test_config['sdk_path'], 'build-tools') build_tools = os.path.join(test_config['sdk_path'], 'build-tools')
aaptdirs = [] aaptdirs = []
@ -188,8 +198,7 @@ def main():
else: else:
test_config['build_tools'] = dirname test_config['build_tools'] = dirname
write_to_config(test_config, 'build_tools') write_to_config(test_config, 'build_tools')
if not common.test_build_tools_exists(test_config): common.ensure_build_tools_exists(test_config)
sys.exit(3)
# now that we have a local config.py, read configuration... # now that we have a local config.py, read configuration...
config = common.read_config(options) config = common.read_config(options)
@ -275,6 +284,7 @@ def main():
logging.info('Built repo based in "' + fdroiddir + '"') logging.info('Built repo based in "' + fdroiddir + '"')
logging.info('with this config:') logging.info('with this config:')
logging.info(' Android SDK:\t\t\t' + config['sdk_path']) logging.info(' Android SDK:\t\t\t' + config['sdk_path'])
if aapt:
logging.info(' Android SDK Build Tools:\t' + os.path.dirname(aapt)) logging.info(' Android SDK Build Tools:\t' + os.path.dirname(aapt))
logging.info(' Android NDK (optional):\t' + ndk_path) logging.info(' Android NDK (optional):\t' + ndk_path)
logging.info(' Keystore for signing key:\t' + keystore) logging.info(' Keystore for signing key:\t' + keystore)

View file

@ -25,14 +25,14 @@ from optparse import OptionParser, OptionError
import logging import logging
import common import common
from common import FDroidPopen, FDroidException from common import SdkToolsPopen, FDroidException
options = None options = None
config = None config = None
def devices(): def devices():
p = FDroidPopen([config['adb'], "devices"]) p = SdkToolsPopen(['adb', "devices"])
if p.returncode != 0: if p.returncode != 0:
raise FDroidException("An error occured when finding devices: %s" % p.output) raise FDroidException("An error occured when finding devices: %s" % p.output)
lines = p.output.splitlines() lines = p.output.splitlines()
@ -103,7 +103,7 @@ def main():
logging.info("Installing %s..." % apk) logging.info("Installing %s..." % apk)
for dev in devs: for dev in devs:
logging.info("Installing %s on %s..." % (apk, dev)) logging.info("Installing %s on %s..." % (apk, dev))
p = FDroidPopen([config['adb'], "-s", dev, "install", apk]) p = SdkToolsPopen(['adb', "-s", dev, "install", apk])
fail = "" fail = ""
for line in p.output.splitlines(): for line in p.output.splitlines():
if line.startswith("Failure"): if line.startswith("Failure"):

View file

@ -28,7 +28,7 @@ import logging
import common import common
import metadata import metadata
from common import FDroidPopen, BuildException from common import FDroidPopen, SdkToolsPopen, BuildException
config = None config = None
options = None options = None
@ -213,7 +213,7 @@ def main():
raise BuildException("Failed to sign application") raise BuildException("Failed to sign application")
# Zipalign it... # Zipalign it...
p = FDroidPopen([config['zipalign'], '-v', '4', apkfile, p = SdkToolsPopen(['zipalign', '-v', '4', apkfile,
os.path.join(output_dir, apkfilename)]) os.path.join(output_dir, apkfilename)])
if p.returncode != 0: if p.returncode != 0:
raise BuildException("Failed to align application") raise BuildException("Failed to align application")

View file

@ -39,7 +39,7 @@ import logging
import common import common
import metadata import metadata
from common import FDroidPopen, SilentPopen from common import FDroidPopen, SdkToolsPopen
from metadata import MetaDataException from metadata import MetaDataException
@ -436,7 +436,7 @@ def scan_apks(apps, apkcache, repodir, knownapks):
thisinfo['features'] = set() thisinfo['features'] = set()
thisinfo['icons_src'] = {} thisinfo['icons_src'] = {}
thisinfo['icons'] = {} thisinfo['icons'] = {}
p = SilentPopen([config['aapt'], 'dump', 'badging', apkfile]) p = SdkToolsPopen(['aapt', 'dump', 'badging', apkfile])
if p.returncode != 0: if p.returncode != 0:
if options.delete_unknown: if options.delete_unknown:
if os.path.exists(apkfile): if os.path.exists(apkfile):

95
tests/common.TestCase Executable file
View file

@ -0,0 +1,95 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# http://www.drdobbs.com/testing/unit-testing-with-python/240165163
import inspect
import optparse
import os
import sys
import unittest
localmodule = os.path.realpath(os.path.join(
os.path.dirname(inspect.getfile(inspect.currentframe())),
'..'))
print('localmodule: ' + localmodule)
if localmodule not in sys.path:
sys.path.insert(0,localmodule)
import fdroidserver.common
class CommonTest(unittest.TestCase):
'''fdroidserver/common.py'''
def _set_build_tools(self):
build_tools = os.path.join(fdroidserver.common.config['sdk_path'], 'build-tools')
if os.path.exists(build_tools):
fdroidserver.common.config['build_tools'] = ''
for f in sorted(os.listdir(build_tools), reverse=True):
versioned = os.path.join(build_tools, f)
if os.path.isdir(versioned) \
and os.path.isfile(os.path.join(versioned, 'aapt')):
fdroidserver.common.config['build_tools'] = versioned
break
return True
else:
print 'no build-tools found: ' + build_tools
return False
def _find_all(self):
for cmd in ('aapt', 'adb', 'android', 'zipalign'):
path = fdroidserver.common.find_sdk_tools_cmd(cmd)
if path is not None:
self.assertTrue(os.path.exists(path))
self.assertTrue(os.path.isfile(path))
def test_find_sdk_tools_cmd(self):
fdroidserver.common.config = dict()
# TODO add this once everything works without sdk_path set in config
#self._find_all()
sdk_path = os.getenv('ANDROID_HOME')
if os.path.exists(sdk_path):
fdroidserver.common.config['sdk_path'] = sdk_path
if os.path.exists('/usr/bin/aapt'):
# this test only works when /usr/bin/aapt is installed
self._find_all()
build_tools = os.path.join(sdk_path, 'build-tools')
if self._set_build_tools():
self._find_all()
else:
print 'no build-tools found: ' + build_tools
def testIsApkDebuggable(self):
config = dict()
config['sdk_path'] = os.getenv('ANDROID_HOME')
fdroidserver.common.config = config
self._set_build_tools();
config['aapt'] = fdroidserver.common.find_sdk_tools_cmd('aapt')
# these are set debuggable
testfiles = []
testfiles.append(os.path.join(os.path.dirname(__file__), 'urzip.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'))
for apkfile in testfiles:
debuggable = fdroidserver.common.isApkDebuggable(apkfile, config)
self.assertTrue(debuggable,
"debuggable APK state was not properly parsed!")
# these are set NOT debuggable
testfiles = []
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'))
for apkfile in testfiles:
debuggable = fdroidserver.common.isApkDebuggable(apkfile, config)
self.assertFalse(debuggable,
"debuggable APK state was not properly parsed!")
if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("-v", "--verbose", action="store_true", default=False,
help="Spew out even more information than normal")
(fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
newSuite = unittest.TestSuite()
newSuite.addTest(unittest.makeSuite(CommonTest))
unittest.main()

46
tests/install.TestCase Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# http://www.drdobbs.com/testing/unit-testing-with-python/240165163
import inspect
import optparse
import os
import sys
import unittest
localmodule = os.path.realpath(os.path.join(
os.path.dirname(inspect.getfile(inspect.currentframe())),
'..'))
print('localmodule: ' + localmodule)
if localmodule not in sys.path:
sys.path.insert(0,localmodule)
import fdroidserver.common
import fdroidserver.install
class InstallTest(unittest.TestCase):
'''fdroidserver/install.py'''
def test_devices(self):
config = dict()
config['sdk_path'] = os.getenv('ANDROID_HOME')
fdroidserver.common.config = config
config['adb'] = fdroidserver.common.find_sdk_tools_cmd('adb')
self.assertTrue(os.path.exists(config['adb']))
self.assertTrue(os.path.isfile(config['adb']))
devices = fdroidserver.install.devices()
self.assertIsInstance(devices, list, 'install.devices() did not return a list!')
for device in devices:
self.assertIsInstance(device, basestring)
if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("-v", "--verbose", action="store_true", default=False,
help="Spew out even more information than normal")
(fdroidserver.install.options, args) = parser.parse_args(['--verbose'])
newSuite = unittest.TestSuite()
newSuite.addTest(unittest.makeSuite(InstallTest))
unittest.main()

View file

@ -97,8 +97,9 @@ echo_header "test python getsig replacement"
cd $WORKSPACE/tests/getsig cd $WORKSPACE/tests/getsig
./make.sh ./make.sh
cd $WORKSPACE/tests for testcase in $WORKSPACE/tests/*.TestCase; do
./update.TestCase $testcase
done
#------------------------------------------------------------------------------# #------------------------------------------------------------------------------#
@ -208,6 +209,9 @@ $fdroid init --keystore $KEYSTORE --android-home $FAKE_ANDROID_HOME
#------------------------------------------------------------------------------# #------------------------------------------------------------------------------#
echo_header "check that 'fdroid init' fails when build-tools cannot be found" echo_header "check that 'fdroid init' fails when build-tools cannot be found"
if [ -e /usr/bin/aapt ]; then
echo "/usr/bin/aapt exists, not running test"
else
REPOROOT=`create_test_dir` REPOROOT=`create_test_dir`
FAKE_ANDROID_HOME=`create_test_dir` FAKE_ANDROID_HOME=`create_test_dir`
create_fake_android_home $FAKE_ANDROID_HOME create_fake_android_home $FAKE_ANDROID_HOME
@ -218,6 +222,7 @@ set +e
$fdroid init --keystore $KEYSTORE --android-home $FAKE_ANDROID_HOME $fdroid init --keystore $KEYSTORE --android-home $FAKE_ANDROID_HOME
[ $? -eq 0 ] && exit 1 [ $? -eq 0 ] && exit 1
set -e set -e
fi
#------------------------------------------------------------------------------# #------------------------------------------------------------------------------#

Binary file not shown.

BIN
tests/urzip-release.apk Normal file

Binary file not shown.