import glob
import subprocess
import re
+import resource
import tarfile
import traceback
import time
from configparser import ConfigParser
from argparse import ArgumentParser
import logging
+from gettext import ngettext
+from . import _
from . import common
from . import net
from . import metadata
# Helper to copy the contents of a directory to the server...
def send_dir(path):
- root = os.path.dirname(path)
- main = os.path.basename(path)
- ftp.mkdir(main)
- for r, d, f in os.walk(path):
- rr = os.path.relpath(r, root)
- ftp.chdir(rr)
- for dd in d:
- ftp.mkdir(dd)
- for ff in f:
- lfile = os.path.join(root, rr, ff)
- if not os.path.islink(lfile):
- ftp.put(lfile, ff)
- ftp.chmod(ff, os.stat(lfile).st_mode)
- for i in range(len(rr.split('/'))):
- ftp.chdir('..')
- ftp.chdir('..')
+ logging.debug("rsyncing " + path + " to " + ftp.getcwd())
+ subprocess.check_call(['rsync', '-rple',
+ 'ssh -o StrictHostKeyChecking=no' +
+ ' -o UserKnownHostsFile=/dev/null' +
+ ' -o LogLevel=FATAL' +
+ ' -o IdentitiesOnly=yes' +
+ ' -o PasswordAuthentication=no' +
+ ' -p ' + str(sshinfo['port']) +
+ ' -i ' + sshinfo['idfile'],
+ path,
+ sshinfo['user'] +
+ "@" + sshinfo['hostname'] +
+ ":" + ftp.getcwd()])
logging.info("Preparing server for build...")
serverpath = os.path.abspath(os.path.dirname(__file__))
ftp.mkdir(d)
ftp.chdir(d)
ftp.put(libsrc, lp[-1])
- for _ in lp[:-1]:
+ for _ignored in lp[:-1]:
ftp.chdir('..')
# Copy any srclibs that are required...
srclibpaths = []
cmdline += " %s:%s" % (app.id, build.versionCode)
chan.exec_command('bash --login -c "' + cmdline + '"')
- output = bytes()
- output += get_android_tools_version_log(build.ndk_path()).encode()
- while not chan.exit_status_ready():
- while chan.recv_ready():
- output += chan.recv(1024)
- time.sleep(0.1)
+ # Fetch build process output ...
+ try:
+ cmd_stdout = chan.makefile('rb', 1024)
+ output = bytes()
+ output += get_android_tools_version_log(build.ndk_path()).encode()
+ while not chan.exit_status_ready():
+ line = cmd_stdout.readline()
+ if line:
+ if options.verbose:
+ logging.debug("buildserver > " + str(line, 'utf-8').rstrip())
+ output += line
+ else:
+ time.sleep(0.05)
+ for line in cmd_stdout.readlines():
+ if options.verbose:
+ logging.debug("buildserver > " + str(line, 'utf-8').rstrip())
+ output += line
+ finally:
+ cmd_stdout.close()
+
+ # Check build process exit status ...
logging.info("...getting exit status")
returncode = chan.recv_exit_status()
- while True:
- get = chan.recv(1024)
- if len(get) == 0:
- break
- output += get
if returncode != 0:
raise BuildException(
"Build.py failed on server for {0}:{1}".format(
- app.id, build.versionName), str(output, 'utf-8'))
+ app.id, build.versionName), None if options.verbose else str(output, 'utf-8'))
# Retreive logs...
toolsversion_log = common.get_toolsversion_logname(app, build)
ftp.get(tarball, os.path.join(output_dir, tarball))
except Exception:
raise BuildException(
- "Build failed for %s:%s - missing output files".format(
- app.id, build.versionName), output)
+ "Build failed for {0}:{1} - missing output files".format(
+ app.id, build.versionName), None if options.verbose else str(output, 'utf-8'))
ftp.close()
finally:
if f in files:
os.remove(os.path.join(root, f))
- if 'build.gradle' in files:
+ if any(f in files for f in ['build.gradle', 'settings.gradle']):
# Even when running clean, gradle stores task/artifact caches in
# .gradle/ as binary files. To avoid overcomplicating the scanner,
# manually delete them, just like `gradle clean` should have removed
count = scanner.scan_source(build_dir, build)
if count > 0:
if force:
- logging.warn('Scanner found %d problems' % count)
+ logging.warning(ngettext('Scanner found {} problem',
+ 'Scanner found {} problems', count).format(count))
else:
- raise BuildException("Can't build due to %d errors while scanning" % count)
+ raise BuildException(ngettext(
+ "Can't build due to {} error while scanning",
+ "Can't build due to {} errors while scanning", count).format(count))
if not options.notarball:
# Build the source tarball right before we build the release...
elif omethod == 'gradle':
src = None
apk_dirs = [
+ # gradle plugin >= 3.0
os.path.join(root_dir, 'build', 'outputs', 'apk', 'release'),
+ # gradle plugin < 3.0 and >= 0.11
os.path.join(root_dir, 'build', 'outputs', 'apk'),
+ # really old path
os.path.join(root_dir, 'build', 'apk'),
]
+ # If we build with gradle flavours with gradle plugin >= 3.0 the apk will be in
+ # a subdirectory corresponding to the flavour command used, but with different
+ # capitalization.
if flavours_cmd:
apk_dirs.append(os.path.join(root_dir, 'build', 'outputs', 'apk', transform_first_char(flavours_cmd, str.lower), 'release'))
for apks_dir in apk_dirs:
parser = ArgumentParser(usage="%(prog)s [options] [APPID[:VERCODE] [APPID[:VERCODE] ...]]")
common.setup_global_opts(parser)
- parser.add_argument("appid", nargs='*', help="app-id with optional versionCode in the form APPID[:VERCODE]")
+ parser.add_argument("appid", nargs='*', help=_("applicationId with optional versionCode in the form APPID[:VERCODE]"))
parser.add_argument("-l", "--latest", action="store_true", default=False,
- help="Build only the latest version of each package")
+ help=_("Build only the latest version of each package"))
parser.add_argument("-s", "--stop", action="store_true", default=False,
- help="Make the build stop on exceptions")
+ help=_("Make the build stop on exceptions"))
parser.add_argument("-t", "--test", action="store_true", default=False,
- help="Test mode - put output in the tmp directory only, and always build, even if the output already exists.")
+ help=_("Test mode - put output in the tmp directory only, and always build, even if the output already exists."))
parser.add_argument("--server", action="store_true", default=False,
- help="Use build server")
+ help=_("Use build server"))
parser.add_argument("--resetserver", action="store_true", default=False,
- help="Reset and create a brand new build server, even if the existing one appears to be ok.")
+ help=_("Reset and create a brand new build server, even if the existing one appears to be ok."))
parser.add_argument("--on-server", dest="onserver", action="store_true", default=False,
- help="Specify that we're running on the build server")
+ help=_("Specify that we're running on the build server"))
parser.add_argument("--skip-scan", dest="skipscan", action="store_true", default=False,
- help="Skip scanning the source code for binaries and other problems")
+ help=_("Skip scanning the source code for binaries and other problems"))
parser.add_argument("--dscanner", action="store_true", default=False,
- help="Setup an emulator, install the apk on it and perform a drozer scan")
+ help=_("Setup an emulator, install the APK on it and perform a Drozer scan"))
parser.add_argument("--no-tarball", dest="notarball", action="store_true", default=False,
- help="Don't create a source tarball, useful when testing a build")
+ help=_("Don't create a source tarball, useful when testing a build"))
parser.add_argument("--no-refresh", dest="refresh", action="store_false", default=True,
- help="Don't refresh the repository, useful when testing a build with no internet connection")
+ help=_("Don't refresh the repository, useful when testing a build with no internet connection"))
parser.add_argument("-f", "--force", action="store_true", default=False,
- help="Force build of disabled apps, and carries on regardless of scan problems. Only allowed in test mode.")
+ help=_("Force build of disabled apps, and carries on regardless of scan problems. Only allowed in test mode."))
parser.add_argument("-a", "--all", action="store_true", default=False,
- help="Build all applications available")
+ help=_("Build all applications available"))
parser.add_argument("-w", "--wiki", default=False, action="store_true",
- help="Update the wiki")
+ help=_("Update the wiki"))
metadata.add_metadata_arguments(parser)
options = parser.parse_args()
metadata.warnings_action = options.W
# Read all app and srclib metadata
pkgs = common.read_pkg_args(options.appid, True)
- allapps = metadata.read_metadata(not options.onserver, pkgs)
+ allapps = metadata.read_metadata(not options.onserver, pkgs, sort_by_time=True)
apps = common.read_app_args(options.appid, allapps, True)
for appid, app in list(apps.items()):
if not apps:
raise FDroidException("No apps to process.")
+ # make sure enough open files are allowed to process everything
+ soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
+ if len(apps) > soft:
+ try:
+ soft = len(apps) * 2
+ if soft > hard:
+ soft = hard
+ resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
+ logging.debug(_('Set open file limit to {integer}')
+ .format(integer=soft))
+ except (OSError, ValueError) as e:
+ logging.warning(_('Setting open file limit failed: ') + str(e))
+
if options.latest:
for app in apps.values():
for build in reversed(app.builds):
vcs, build_dir = common.setup_vcs(app)
first = False
+ logging.info("Using %s" % vcs.clientversion())
logging.debug("Checking " + build.versionName)
if trybuild(app, build, build_dir, output_dir, log_dir,
also_check_dir, srclib_dir, extlib_dir,
logging.error("VCS error while building app %s: %s" % (
appid, reason))
if options.stop:
+ logging.debug("Error encoutered, stopping by user request.")
sys.exit(1)
failed_apps[appid] = vcse
wikilog = str(vcse)
f.write(str(e))
logging.error("Could not build app %s: %s" % (appid, e))
if options.stop:
+ logging.debug("Error encoutered, stopping by user request.")
sys.exit(1)
failed_apps[appid] = e
wikilog = e.get_wikitext()
logging.error("Could not build app %s due to unknown error: %s" % (
appid, traceback.format_exc()))
if options.stop:
+ logging.debug("Error encoutered, stopping by user request.")
sys.exit(1)
failed_apps[appid] = e
wikilog = str(e)
break
if not apk_path:
- raise Exception("No signed APK found at path: {0}".format(apk_path))
+ raise Exception("No signed APK found at path: {path}".format(path=apk_path))
if not os.path.isdir(repo_dir):
- exit(1)
+ logging.critical("directory does not exists '{path}'".format(path=repo_dir))
+ sys.exit(1)
logging.info("Performing Drozer scan on {0}.".format(app))
docker.perform_drozer_scan(apk_path, app.id, repo_dir)
logging.info("Cleaning up after ourselves.")
docker.clean()
- logging.info("Finished.")
+ logging.info(_("Finished"))
if len(build_succeeded) > 0:
- logging.info(str(len(build_succeeded)) + ' builds succeeded')
+ logging.info(ngettext("{} build succeeded",
+ "{} builds succeeded", len(build_succeeded)).format(len(build_succeeded)))
if len(failed_apps) > 0:
- logging.info(str(len(failed_apps)) + ' builds failed')
+ logging.info(ngettext("{} build failed",
+ "{} builds failed", len(failed_apps)).format(len(failed_apps)))
- sys.exit(0)
+ # hack to ensure this exits, even is some threads are still running
+ sys.stdout.flush()
+ sys.stderr.flush()
+ os._exit(0)
if __name__ == "__main__":