import sys
import os
import shutil
+import glob
import subprocess
import re
import tarfile
import json
from ConfigParser import ConfigParser
from optparse import OptionParser, OptionError
+from distutils.version import LooseVersion
import logging
import common
import metadata
-from common import BuildException, VCSException, FDroidPopen, SilentPopen
+from common import FDroidException, BuildException, VCSException, FDroidPopen, SilentPopen
try:
import paramiko
is the stdout (and stderr) from vagrant
"""
p = FDroidPopen(['vagrant'] + params, cwd=cwd)
- return (p.returncode, p.stdout)
+ return (p.returncode, p.output)
def get_vagrant_sshinfo():
p = FDroidPopen(['VBoxManage', 'snapshot',
get_builder_vm_id(), 'list',
'--details'], cwd='builder')
- if 'fdroidclean' in p.stdout:
+ if 'fdroidclean' in p.output:
logging.info("...snapshot exists - resetting build server to "
"clean state")
retcode, output = vagrant(['status'], cwd='builder')
logging.info("...failed to reset to snapshot")
else:
logging.info("...snapshot doesn't exist - "
- "VBoxManage snapshot list:\n" + p.stdout)
+ "VBoxManage snapshot list:\n" + p.output)
# If we can't use the existing machine for any reason, make a
# new one from scratch.
p = FDroidPopen(['VBoxManage', 'snapshot', get_builder_vm_id(),
'list', '--details'],
cwd='builder')
- if 'fdroidclean' not in p.stdout:
+ if 'fdroidclean' not in p.output:
raise BuildException("Failed to take snapshot.")
return sshinfo
ftp.mkdir('extlib')
ftp.mkdir('srclib')
# Copy any extlibs that are required...
- if 'extlibs' in thisbuild:
+ if thisbuild['extlibs']:
ftp.chdir(homedir + '/build/extlib')
for lib in thisbuild['extlibs']:
lib = lib.strip()
ftp.chdir('..')
# Copy any srclibs that are required...
srclibpaths = []
- if 'srclibs' in thisbuild:
+ if thisbuild['srclibs']:
for lib in thisbuild['srclibs']:
srclibpaths.append(
common.getsrclib(lib, 'build/srclib', srclibpaths,
for root, dirs, files in os.walk(build_dir):
if 'build.gradle' in files:
path = os.path.join(root, 'build.gradle')
- logging.info("Adapting build.gradle at %s" % path)
+ logging.debug("Adapting build.gradle at %s" % path)
FDroidPopen(['sed', '-i',
r's@buildToolsVersion\([ =]*\)["\'][0-9\.]*["\']@buildToolsVersion\1"'
def build_local(app, thisbuild, vcs, build_dir, output_dir, srclib_dir, extlib_dir, tmp_dir, force, onserver):
"""Do a build locally."""
- if thisbuild.get('buildjni') not in (None, ['no']):
+ if thisbuild['buildjni'] and thisbuild['buildjni'] != ['no']:
if not config['ndk_path']:
logging.critical("$ANDROID_NDK is not set!")
sys.exit(3)
logging.info("Cleaning Gradle project...")
cmd = [config['gradle'], 'clean']
- if '@' in thisbuild['gradle']:
- gradle_dir = os.path.join(root_dir, thisbuild['gradle'].split('@', 1)[1])
- gradle_dir = os.path.normpath(gradle_dir)
- else:
- gradle_dir = root_dir
-
adapt_gradle(build_dir)
for name, number, libpath in srclibpaths:
adapt_gradle(libpath)
- p = FDroidPopen(cmd, cwd=gradle_dir)
+ p = FDroidPopen(cmd, cwd=root_dir)
elif thisbuild['type'] == 'kivy':
pass
if p is not None and p.returncode != 0:
raise BuildException("Error cleaning %s:%s" %
- (app['id'], thisbuild['version']), p.stdout)
+ (app['id'], thisbuild['version']), p.output)
- logging.info("Getting rid of Gradle wrapper binaries...")
for root, dirs, files in os.walk(build_dir):
# Don't remove possibly necessary 'gradle' dirs if 'gradlew' is not there
if 'gradlew' in files:
+ logging.debug("Getting rid of Gradle wrapper stuff in %s" % root)
os.remove(os.path.join(root, 'gradlew'))
if 'gradlew.bat' in files:
os.remove(os.path.join(root, 'gradlew.bat'))
f.write(manifestcontent)
# Run a build command if one is required...
- if 'build' in thisbuild:
+ if thisbuild['build']:
+ logging.info("Running 'build' commands in %s" % root_dir)
cmd = common.replace_config_vars(thisbuild['build'])
+
# Substitute source library paths into commands...
for name, number, libpath in srclibpaths:
libpath = os.path.relpath(libpath, root_dir)
cmd = cmd.replace('$$' + name + '$$', libpath)
- logging.info("Running 'build' commands in %s" % root_dir)
p = FDroidPopen(['bash', '-x', '-c', cmd], cwd=root_dir)
if p.returncode != 0:
raise BuildException("Error running build command for %s:%s" %
- (app['id'], thisbuild['version']), p.stdout)
+ (app['id'], thisbuild['version']), p.output)
# Build native stuff if required...
- if thisbuild.get('buildjni') not in (None, ['no']):
- logging.info("Building native libraries...")
- jni_components = thisbuild.get('buildjni')
+ if thisbuild['buildjni'] and thisbuild['buildjni'] != ['no']:
+ logging.info("Building the native code")
+ jni_components = thisbuild['buildjni']
+
if jni_components == ['yes']:
jni_components = ['']
cmd = [os.path.join(config['ndk_path'], "ndk-build"), "-j1"]
del manifest_text
p = FDroidPopen(cmd, cwd=os.path.join(root_dir, d))
if p.returncode != 0:
- raise BuildException("NDK build failed for %s:%s" % (app['id'], thisbuild['version']), p.stdout)
+ raise BuildException("NDK build failed for %s:%s" % (app['id'], thisbuild['version']), p.output)
p = None
# Build the release...
'-Dmaven.jar.sign.skip=true', '-Dmaven.test.skip=true',
'-Dandroid.sign.debug=false', '-Dandroid.release=true',
'package']
- if 'target' in thisbuild:
+ if thisbuild['target']:
target = thisbuild["target"].split('-')[1]
FDroidPopen(['sed', '-i',
's@<platform>[0-9]*</platform>@<platform>'
'pom.xml'],
cwd=maven_dir)
- if 'mvnflags' in thisbuild:
- mvncmd += thisbuild['mvnflags']
-
p = FDroidPopen(mvncmd, cwd=maven_dir)
bindir = os.path.join(root_dir, 'target')
elif thisbuild['type'] == 'gradle':
logging.info("Building Gradle project...")
- if '@' in thisbuild['gradle']:
- flavours = thisbuild['gradle'].split('@')[0].split(',')
- gradle_dir = thisbuild['gradle'].split('@')[1]
- gradle_dir = os.path.join(root_dir, gradle_dir)
- else:
- flavours = thisbuild['gradle'].split(',')
- gradle_dir = root_dir
+ flavours = thisbuild['gradle'].split(',')
if len(flavours) == 1 and flavours[0] in ['main', 'yes', '']:
flavours[0] = ''
commands = [config['gradle']]
- if 'preassemble' in thisbuild:
+ if thisbuild['preassemble']:
commands += thisbuild['preassemble'].split()
flavours_cmd = ''.join(flavours)
commands += ['assemble' + flavours_cmd + 'Release']
- p = FDroidPopen(commands, cwd=gradle_dir)
+ # Avoid having to use lintOptions.abortOnError false
+ if thisbuild['gradlepluginver'] >= LooseVersion('0.8'):
+ commands += ['-x', 'lintVital' + flavours_cmd + 'Release']
+
+ p = FDroidPopen(commands, cwd=root_dir)
elif thisbuild['type'] == 'ant':
logging.info("Building Ant project...")
cmd = ['ant']
- if 'antcommand' in thisbuild:
+ if thisbuild['antcommand']:
cmd += [thisbuild['antcommand']]
else:
cmd += ['release']
bindir = os.path.join(root_dir, 'bin')
if p is not None and p.returncode != 0:
- raise BuildException("Build failed for %s:%s" % (app['id'], thisbuild['version']), p.stdout)
+ raise BuildException("Build failed for %s:%s" % (app['id'], thisbuild['version']), p.output)
logging.info("Successfully built version " + thisbuild['version'] + ' of ' + app['id'])
if thisbuild['type'] == 'maven':
stdout_apk = '\n'.join([
- line for line in p.stdout.splitlines() if any(a in line for a in ('.apk', '.ap_'))])
+ line for line in p.output.splitlines() if any(a in line for a in ('.apk', '.ap_'))])
m = re.match(r".*^\[INFO\] .*apkbuilder.*/([^/]*)\.apk",
stdout_apk, re.S | re.M)
if not m:
src = 'python-for-android/dist/default/bin/{0}-{1}-release.apk'.format(
bconfig.get('app', 'title'), bconfig.get('app', 'version'))
elif thisbuild['type'] == 'gradle':
- basename = app['id']
- dd = build_dir
- if 'subdir' in thisbuild:
- dd = os.path.join(dd, thisbuild['subdir'])
- basename = os.path.basename(thisbuild['subdir'])
- if '@' in thisbuild['gradle']:
- dd = os.path.join(dd, thisbuild['gradle'].split('@')[1])
- basename = app['id']
- if len(flavours) == 1 and flavours[0] == '':
- name = '-'.join([basename, 'release', 'unsigned'])
+
+ if thisbuild['gradlepluginver'] >= LooseVersion('0.11'):
+ apks_dir = os.path.join(root_dir, 'build', 'outputs', 'apk')
else:
- name = '-'.join([basename, '-'.join(flavours), 'release', 'unsigned'])
- dd = os.path.normpath(dd)
- src = os.path.join(dd, 'build', 'apk', name + '.apk')
+ apks_dir = os.path.join(root_dir, 'build', 'apk')
+
+ apks = glob.glob(os.path.join(apks_dir, '*-release-unsigned.apk'))
+ if len(apks) > 1:
+ raise BuildException('More than one resulting apks found in %s' % apks_dir,
+ '\n'.join(apks))
+ if len(apks) < 1:
+ raise BuildException('Failed to find gradle output in %s' % apks_dir)
+ src = apks[0]
elif thisbuild['type'] == 'ant':
stdout_apk = '\n'.join([
- line for line in p.stdout.splitlines() if '.apk' in line])
+ line for line in p.output.splitlines() if '.apk' in line])
src = re.match(r".*^.*Creating (.+) for release.*$.*", stdout_apk,
re.S | re.M).group(1)
src = os.path.join(bindir, src)
# By way of a sanity check, make sure the version and version
# code in our new apk match what we expect...
- logging.info("Checking " + src)
+ logging.debug("Checking " + src)
if not os.path.exists(src):
raise BuildException("Unsigned apk is not at expected location of " + src)
- p = SilentPopen([os.path.join(config['sdk_path'], 'build-tools',
- config['build_tools'], 'aapt'),
- 'dump', 'badging', src])
+ p = SilentPopen([config['aapt'], 'dump', 'badging', src])
vercode = None
version = None
foundid = None
nativecode = None
- for line in p.stdout.splitlines():
+ for line in p.output.splitlines():
if line.startswith("package:"):
pat = re.compile(".*name='([a-zA-Z0-9._]*)'.*")
m = pat.match(line)
elif line.startswith("native-code:"):
nativecode = line[12:]
- if thisbuild.get('buildjni') is not None:
- if nativecode is None or "'" not in nativecode:
+ # 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 thisbuild['buildjni'] and thisbuild['buildjni'] != ['no']:
+ if nativecode is None:
raise BuildException("Native code should have been built but none was packaged")
if thisbuild['novcheck']:
vercode = thisbuild['vercode']
if os.path.exists(dest_also):
return False
- if 'disable' in thisbuild:
+ if thisbuild['disable']:
return False
- logging.info("Building version " + thisbuild['version'] + ' of ' + app['id'])
+ logging.info("Building version %s (%s) of %s" % (
+ thisbuild['version'], thisbuild['vercode'], app['id']))
if server:
# When using server mode, still keep a local cache of the repo, by
srclib_dir = os.path.join(build_dir, 'srclib')
extlib_dir = os.path.join(build_dir, 'extlib')
- # Get all apps...
+ # Read all app and srclib metadata
allapps = metadata.read_metadata(xref=not options.onserver)
apps = common.read_app_args(args, allapps, True)
len(app['Repo Type']) > 0 and len(app['builds']) > 0]
if len(apps) == 0:
- raise Exception("No apps to process.")
+ raise FDroidException("No apps to process.")
if options.latest:
for app in apps:
for build in reversed(app['builds']):
- if 'disable' in build:
+ if build['disable']:
continue
app['builds'] = [build]
break
logfile = open(os.path.join(log_dir, app['id'] + '.log'), 'a+')
logfile.write(str(be))
logfile.close()
- reason = str(be).split('\n', 1)[0] if options.verbose else str(be)
- print("Could not build app %s due to BuildException: %s" % (
- app['id'], reason))
+ print("Could not build app %s due to BuildException: %s" % (app['id'], be))
if options.stop:
sys.exit(1)
failed_apps[app['id']] = be
wikilog = be.get_wikitext()
except VCSException as vcse:
- print("VCS error while building app %s: %s" % (app['id'], vcse))
+ reason = str(vcse).split('\n', 1)[0] if options.verbose else str(vcse)
+ logging.error("VCS error while building app %s: %s" % (
+ app['id'], reason))
if options.stop:
sys.exit(1)
failed_apps[app['id']] = vcse
wikilog = str(vcse)
except Exception as e:
- print("Could not build app %s due to unknown error: %s" % (app['id'], traceback.format_exc()))
+ logging.error("Could not build app %s due to unknown error: %s" % (
+ app['id'], traceback.format_exc()))
if options.stop:
sys.exit(1)
failed_apps[app['id']] = e
txt = "Build completed at " + time.strftime("%Y-%m-%d %H:%M:%SZ", time.gmtime()) + "\n\n" + wikilog
newpage.save(txt, summary='Build log')
except:
- logging.info("Error while attempting to publish build log")
+ logging.error("Error while attempting to publish build log")
for app in build_succeeded:
logging.info("success: %s" % (app['id']))