From: relan Date: Sun, 3 Dec 2017 06:03:03 +0000 (+0000) Subject: Merge branch 'log_git' into 'master' X-Git-Tag: 1.0.0~53 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=24e1da1e9149a9ee46014e53d716023a923bc9ea;hp=bb591e236d0b2576cfd350a50c62cc2ffa64395a;p=fdroidserver.git Merge branch 'log_git' into 'master' build: log vcs tools version on every build attempt See merge request fdroid/fdroidserver!391 --- diff --git a/buildserver/provision-android-sdk b/buildserver/provision-android-sdk index b79c2e4a..7cd96e0c 100644 --- a/buildserver/provision-android-sdk +++ b/buildserver/provision-android-sdk @@ -74,12 +74,25 @@ y EOH mkdir -p $ANDROID_HOME/licenses/ + cat << EOF > $ANDROID_HOME/licenses/android-sdk-license 8933bad161af4178b1185d1a37fbf41ea5269c55 + d56f5187479451eabf01fb78af6dfcb131a6481e EOF -echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > $ANDROID_HOME/licenses/android-sdk-preview-license + +cat < $ANDROID_HOME/licenses/android-sdk-preview-license + +84831b9409646a918e30573bab4c9c91346d8abd +EOF + +cat < $ANDROID_HOME/licenses/android-sdk-preview-license-old +79120722343a6f314e0719f863036c702b0e6b2a + +84831b9409646a918e30573bab4c9c91346d8abd +EOF + echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.1" echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.1" echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2" diff --git a/completion/bash-completion b/completion/bash-completion index 8bdd333f..d18edd35 100644 --- a/completion/bash-completion +++ b/completion/bash-completion @@ -264,6 +264,12 @@ __complete_btlog() { __complete_options } +__complete_mirror() { + opts="-v" + lopts="--archive --output-dir" + __complete_options +} + __complete_nightly() { opts="-v -q" lopts="--show-secret-var" @@ -316,6 +322,7 @@ import \ init \ install \ lint \ +mirror \ nightly \ publish \ readmeta \ diff --git a/examples/config.py b/examples/config.py index 0551e1c0..f36d51d2 100644 --- a/examples/config.py +++ b/examples/config.py @@ -216,11 +216,12 @@ The repository of older versions of applications from the main demo repository. # sync_from_local_copy_dir = True -# To upload the repo to an Amazon S3 bucket using `fdroid server update`. -# Warning, this deletes and recreates the whole fdroid/ directory each -# time. This is based on apache-libcloud, which supports basically all cloud -# storage services, so it should be easy to port the fdroid server tools to -# any of them. +# To upload the repo to an Amazon S3 bucket using `fdroid server +# update`. Warning, this deletes and recreates the whole fdroid/ +# directory each time. This prefers s3cmd, but can also use +# apache-libcloud. To customize how s3cmd interacts with the cloud +# provider, create a 's3cfg' file next to this file (config.py), and +# those settings will be used instead of any 'aws' variable below. # # awsbucket = 'myawsfdroid' # awsaccesskeyid = 'SEE0CHAITHEIMAUR2USA' diff --git a/fdroid b/fdroid index a07a4ecf..f5e6c92b 100755 --- a/fdroid +++ b/fdroid @@ -48,6 +48,7 @@ commands = OrderedDict([ ("btlog", _("Update the binary transparency log for a URL")), ("signatures", _("Extract signatures from APKs")), ("nightly", _("Set up an app build for a nightly build repo")), + ("mirror", _("Download complete mirrors of small repos")), ]) diff --git a/fdroidserver/build.py b/fdroidserver/build.py index 0d0f687a..2bacd555 100644 --- a/fdroidserver/build.py +++ b/fdroidserver/build.py @@ -23,6 +23,7 @@ import shutil import glob import subprocess import re +import resource import tarfile import traceback import time @@ -1110,7 +1111,7 @@ def main(): # 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()): @@ -1120,6 +1121,19 @@ def main(): 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): @@ -1331,7 +1345,10 @@ def main(): 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__": diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 89c1a1d5..e7a1f1ec 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -40,7 +40,7 @@ import json import xml.etree.ElementTree as XMLElementTree from binascii import hexlify -from datetime import datetime +from datetime import datetime, timedelta from distutils.version import LooseVersion from queue import Queue from zipfile import ZipFile @@ -444,17 +444,16 @@ def get_local_metadata_files(): return glob.glob('.fdroid.[a-jl-z]*[a-rt-z]') -def read_pkg_args(args, allow_vercodes=False): +def read_pkg_args(appid_versionCode_pairs, allow_vercodes=False): """ - :param args: arguments in the form of multiple appid:[vc] strings + :param appids: arguments in the form of multiple appid:[vc] strings :returns: a dictionary with the set of vercodes specified for each package """ - vercodes = {} - if not args: + if not appid_versionCode_pairs: return vercodes - for p in args: + for p in appid_versionCode_pairs: if allow_vercodes and ':' in p: package, vercode = p.split(':') else: @@ -468,13 +467,17 @@ def read_pkg_args(args, allow_vercodes=False): return vercodes -def read_app_args(args, allapps, allow_vercodes=False): - """ - On top of what read_pkg_args does, this returns the whole app metadata, but - limiting the builds list to the builds matching the vercodes specified. +def read_app_args(appid_versionCode_pairs, allapps, allow_vercodes=False): + """Build a list of App instances for processing + + On top of what read_pkg_args does, this returns the whole app + metadata, but limiting the builds list to the builds matching the + appid_versionCode_pairs and vercodes specified. If no appid_versionCode_pairs are specified, then + all App and Build instances are returned. + """ - vercodes = read_pkg_args(args, allow_vercodes) + vercodes = read_pkg_args(appid_versionCode_pairs, allow_vercodes) if not vercodes: return allapps @@ -1299,27 +1302,58 @@ def parse_androidmanifests(paths, app): vercode = None package = None + flavour = "" + if app.builds and 'gradle' in app.builds[-1] and app.builds[-1].gradle: + flavour = app.builds[-1].gradle[-1] + if has_extension(path, 'gradle'): + # first try to get version name and code from correct flavour with open(path, 'r') as f: - for line in f: - if gradle_comment.match(line): - continue - # Grab first occurence of each to avoid running into - # alternative flavours and builds. - if not package: - matches = psearch_g(line) - if matches: - s = matches.group(2) - if app_matches_packagename(app, s): - package = s - if not version: - matches = vnsearch_g(line) - if matches: - version = matches.group(2) - if not vercode: - matches = vcsearch_g(line) - if matches: - vercode = matches.group(1) + buildfile = f.read() + + regex_string = r"" + flavour + ".*?}" + search = re.compile(regex_string, re.DOTALL) + result = search.search(buildfile) + + if result is not None: + resultgroup = result.group() + + if not package: + matches = psearch_g(resultgroup) + if matches: + s = matches.group(2) + if app_matches_packagename(app, s): + package = s + if not version: + matches = vnsearch_g(resultgroup) + if matches: + version = matches.group(2) + if not vercode: + matches = vcsearch_g(resultgroup) + if matches: + vercode = matches.group(1) + else: + # fall back to parse file line by line + with open(path, 'r') as f: + for line in f: + if gradle_comment.match(line): + continue + # Grab first occurence of each to avoid running into + # alternative flavours and builds. + if not package: + matches = psearch_g(line) + if matches: + s = matches.group(2) + if app_matches_packagename(app, s): + package = s + if not version: + matches = vnsearch_g(line) + if matches: + version = matches.group(2) + if not vercode: + matches = vcsearch_g(line) + if matches: + vercode = matches.group(1) else: try: xml = parse_xml(path) @@ -1716,6 +1750,23 @@ def natural_key(s): return [int(sp) if sp.isdigit() else sp for sp in re.split(r'(\d+)', s)] +def check_system_clock(dt_obj, path): + """Check if system clock is updated based on provided date + + If an APK has files newer than the system time, suggest updating + the system clock. This is useful for offline systems, used for + signing, which do not have another source of clock sync info. It + has to be more than 24 hours newer because ZIP/APK files do not + store timezone info + + """ + checkdt = dt_obj - timedelta(1) + if datetime.today() < checkdt: + logging.warning(_('System clock is older than date in {path}!').format(path=path) + + '\n' + _('Set clock to that time using:') + '\n' + + 'sudo date -s "' + str(dt_obj) + '"') + + class KnownApks: """permanent store of existing APKs with the date they were added @@ -1744,6 +1795,7 @@ class KnownApks: date = datetime.strptime(t[-1], '%Y-%m-%d') filename = line[0:line.rfind(appid) - 1] self.apks[filename] = (appid, date) + check_system_clock(date, self.path) self.changed = False def writeifchanged(self): @@ -1918,6 +1970,7 @@ def FDroidPopenBytes(commands, cwd=None, envs=None, output=True, stderr_to_stdou raise BuildException("OSError while trying to execute " + ' '.join(commands) + ': ' + str(e)) + # TODO are these AsynchronousFileReader threads always exiting? if not stderr_to_stdout and options.verbose: stderr_queue = Queue() stderr_reader = AsynchronousFileReader(p.stderr, stderr_queue) diff --git a/fdroidserver/init.py b/fdroidserver/init.py index 9fdb5836..9d03e0b9 100644 --- a/fdroidserver/init.py +++ b/fdroidserver/init.py @@ -178,6 +178,7 @@ def main(): + '" does not exist, creating a new keystore there.') common.write_to_config(test_config, 'keystore', keystore) repo_keyalias = None + keydname = None if options.repo_keyalias: repo_keyalias = options.repo_keyalias common.write_to_config(test_config, 'repo_keyalias', repo_keyalias) @@ -211,7 +212,16 @@ def main(): flags=re.MULTILINE) with open('opensc-fdroid.cfg', 'w') as f: f.write(opensc_fdroid) - elif not os.path.exists(keystore): + elif os.path.exists(keystore): + to_set = ['keystorepass', 'keypass', 'repo_keyalias', 'keydname'] + if repo_keyalias: + to_set.remove('repo_keyalias') + if keydname: + to_set.remove('keydname') + logging.warning('\n' + _('Using existing keystore "{path}"').format(path=keystore) + + '\n' + _('Now set these in config.py:') + ' ' + + ', '.join(to_set) + '\n') + else: password = common.genpassword() c = dict(test_config) c['keystorepass'] = password diff --git a/fdroidserver/metadata.py b/fdroidserver/metadata.py index d65eb81a..c29c1e4c 100644 --- a/fdroidserver/metadata.py +++ b/fdroidserver/metadata.py @@ -26,6 +26,7 @@ import logging import textwrap import io import yaml +from collections import OrderedDict # use libyaml if it is available try: from yaml import CLoader @@ -703,35 +704,50 @@ def read_srclibs(): srclibs[srclibname] = parse_srclib(metadatapath) -def read_metadata(xref=True, check_vcs=[]): - """ - Read all metadata. Returns a list of 'app' objects (which are dictionaries as - returned by the parse_txt_metadata function. +def read_metadata(xref=True, check_vcs=[], sort_by_time=False): + """Return a list of App instances sorted newest first + + This reads all of the metadata files in a 'data' repository, then + builds a list of App instances from those files. The list is + sorted based on creation time, newest first. Most of the time, + the newer files are the most interesting. + + If there are multiple metadata files for a single appid, then the first + file that is parsed wins over all the others, and the rest throw an + exception. So the original .txt format is parsed first, at least until + newer formats stabilize. check_vcs is the list of packageNames to check for .fdroid.yml in source + """ # Always read the srclibs before the apps, since they can use a srlib as # their source repository. read_srclibs() - apps = {} + apps = OrderedDict() for basedir in ('metadata', 'tmp'): if not os.path.exists(basedir): os.makedirs(basedir) - # If there are multiple metadata files for a single appid, then the first - # file that is parsed wins over all the others, and the rest throw an - # exception. So the original .txt format is parsed first, at least until - # newer formats stabilize. - - for metadatapath in sorted(glob.glob(os.path.join('metadata', '*.txt')) - + glob.glob(os.path.join('metadata', '*.json')) - + glob.glob(os.path.join('metadata', '*.yml')) - + glob.glob('.fdroid.txt') - + glob.glob('.fdroid.json') - + glob.glob('.fdroid.yml')): + metadatafiles = (glob.glob(os.path.join('metadata', '*.txt')) + + glob.glob(os.path.join('metadata', '*.json')) + + glob.glob(os.path.join('metadata', '*.yml')) + + glob.glob('.fdroid.txt') + + glob.glob('.fdroid.json') + + glob.glob('.fdroid.yml')) + + if sort_by_time: + entries = ((os.stat(path).st_mtime, path) for path in metadatafiles) + metadatafiles = [] + for _ignored, path in sorted(entries, reverse=True): + metadatafiles.append(path) + else: + # most things want the index alpha sorted for stability + metadatafiles = sorted(metadatafiles) + + for metadatapath in metadatafiles: packageName, _ignored = fdroidserver.common.get_extension(os.path.basename(metadatapath)) if packageName in apps: warn_or_exception(_("Found multiple metadata files for {appid}") diff --git a/fdroidserver/mirror.py b/fdroidserver/mirror.py new file mode 100644 index 00000000..0aa43722 --- /dev/null +++ b/fdroidserver/mirror.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 + +import ipaddress +import logging +import os +import posixpath +import socket +import subprocess +import sys +from argparse import ArgumentParser +import urllib.parse + +from . import _ +from . import common +from . import index +from . import update + +options = None + + +def _run_wget(path, urls): + if options.verbose: + verbose = '--verbose' + else: + verbose = '--no-verbose' + + if not urls: + return + logging.debug(_('Running wget in {path}').format(path=path)) + os.makedirs(path, exist_ok=True) + os.chdir(path) + urls_file = '.fdroid-mirror-wget-input-file' + with open(urls_file, 'w') as fp: + for url in urls: + fp.write(url.split('?')[0] + '\n') # wget puts query string in the filename + subprocess.call(['wget', verbose, '--continue', '--user-agent="fdroid mirror"', + '--input-file=' + urls_file]) + os.remove(urls_file) + + +def main(): + global options + + parser = ArgumentParser(usage=_("%(prog)s [options] url")) + common.setup_global_opts(parser) + parser.add_argument("url", nargs='?', + help=_('Base URL to mirror, can include the index signing key ' + + 'using the query string: ?fingerprint=')) + parser.add_argument("--archive", action='store_true', default=False, + help=_("Also mirror the full archive section")) + parser.add_argument("--output-dir", default=None, + help=_("The directory to write the mirror to")) + options = parser.parse_args() + + if options.url is None: + logging.error(_('A URL is required as an argument!') + '\n') + parser.print_help() + sys.exit(1) + + scheme, hostname, path, params, query, fragment = urllib.parse.urlparse(options.url) + fingerprint = urllib.parse.parse_qs(query).get('fingerprint') + + def _append_to_url_path(*args): + '''Append the list of path components to URL, keeping the rest the same''' + newpath = posixpath.join(path, *args) + return urllib.parse.urlunparse((scheme, hostname, newpath, params, query, fragment)) + + if fingerprint: + config = common.read_config(options) + if not ('jarsigner' in config or 'apksigner' in config): + logging.error(_('Java JDK not found! Install in standard location or set java_paths!')) + sys.exit(1) + + def _get_index(section, etag=None): + url = _append_to_url_path(section) + return index.download_repo_index(url, etag=etag) + else: + def _get_index(section, etag=None): + import io + import json + import zipfile + from . import net + url = _append_to_url_path(section, 'index-v1.jar') + content, etag = net.http_get(url) + with zipfile.ZipFile(io.BytesIO(content)) as zip: + jsoncontents = zip.open('index-v1.json').read() + data = json.loads(jsoncontents.decode('utf-8')) + return data, etag + + ip = None + try: + ip = ipaddress.ip_address(hostname) + except ValueError: + pass + if hostname == 'f-droid.org' \ + or (ip is not None and hostname in socket.gethostbyname_ex('f-droid.org')[2]): + print(_('ERROR: this command should never be used to mirror f-droid.org!\n' + 'A full mirror of f-droid.org requires more than 200GB.')) + sys.exit(1) + + path = path.rstrip('/') + if path.endswith('repo') or path.endswith('archive'): + logging.warning(_('Do not include "{path}" in URL!') + .format(path=path.split('/')[-1])) + elif not path.endswith('fdroid'): + logging.warning(_('{url} does not end with "fdroid", check the URL path!') + .format(url=options.url)) + + icondirs = ['icons', ] + for density in update.screen_densities: + icondirs.append('icons-' + density) + + if options.output_dir: + basedir = options.output_dir + else: + basedir = os.path.join(os.getcwd(), hostname, path.strip('/')) + os.makedirs(basedir, exist_ok=True) + + if options.archive: + sections = ('repo', 'archive') + else: + sections = ('repo', ) + + for section in sections: + sectiondir = os.path.join(basedir, section) + + data, etag = _get_index(section) + + os.makedirs(sectiondir, exist_ok=True) + os.chdir(sectiondir) + for icondir in icondirs: + os.makedirs(os.path.join(sectiondir, icondir), exist_ok=True) + + urls = [] + for packageName, packageList in data['packages'].items(): + for package in packageList: + to_fetch = [] + for k in ('apkName', 'srcname'): + if k in package: + to_fetch.append(package[k]) + elif k == 'apkName': + logging.error(_('{appid} is missing {name}') + .format(appid=package['packageName'], name=k)) + for f in to_fetch: + if not os.path.exists(f) \ + or (f.endswith('.apk') and os.path.getsize(f) != package['size']): + urls.append(_append_to_url_path(section, f)) + urls.append(_append_to_url_path(section, f + '.asc')) + _run_wget(sectiondir, urls) + + for app in data['apps']: + localized = app.get('localized') + if localized: + for locale, d in localized.items(): + urls = [] + components = (section, app['packageName'], locale) + for k in update.GRAPHIC_NAMES: + f = d.get(k) + if f: + filepath_tuple = components + (f, ) + urls.append(_append_to_url_path(*filepath_tuple)) + _run_wget(os.path.join(basedir, *components), urls) + for k in update.SCREENSHOT_DIRS: + urls = [] + filelist = d.get(k) + if filelist: + components = (section, app['packageName'], locale, k) + for f in filelist: + filepath_tuple = components + (f, ) + urls.append(_append_to_url_path(*filepath_tuple)) + _run_wget(os.path.join(basedir, *components), urls) + + urls = dict() + for app in data['apps']: + if 'icon' not in app: + logging.error(_('no "icon" in {appid}').format(appid=app['packageName'])) + continue + icon = app['icon'] + for icondir in icondirs: + url = _append_to_url_path(section, icondir, icon) + if icondir not in urls: + urls[icondir] = [] + urls[icondir].append(url) + + for icondir in icondirs: + _run_wget(os.path.join(basedir, section, icondir), urls[icondir]) + + +if __name__ == "__main__": + main() diff --git a/fdroidserver/nightly.py b/fdroidserver/nightly.py index 3834f79e..454616e4 100644 --- a/fdroidserver/nightly.py +++ b/fdroidserver/nightly.py @@ -155,6 +155,7 @@ def main(): repo_url = repo_base + '/repo' git_mirror_path = os.path.join(repo_basedir, 'git-mirror') git_mirror_repodir = os.path.join(git_mirror_path, 'fdroid', 'repo') + git_mirror_metadatadir = os.path.join(git_mirror_path, 'fdroid', 'metadata') if not os.path.isdir(git_mirror_repodir): logging.debug(_('cloning {url}').format(url=clone_url)) try: @@ -186,10 +187,10 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base, mirror_git_repo.git.add(all=True) mirror_git_repo.index.commit("update README") - icon_path = os.path.join(repo_basedir, 'icon.png') + icon_path = os.path.join(git_mirror_path, 'icon.png') try: import qrcode - img = qrcode.make('Some data here') + img = qrcode.make(repo_url) with open(icon_path, 'wb') as fp: fp.write(img) except Exception: @@ -197,9 +198,13 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base, shutil.copy(exampleicon, icon_path) mirror_git_repo.git.add(all=True) mirror_git_repo.index.commit("update repo/website icon") + shutil.copy(icon_path, repo_basedir) os.chdir(repo_basedir) - common.local_rsync(options, git_mirror_repodir + '/', 'repo/') + if os.path.isdir(git_mirror_repodir): + common.local_rsync(options, git_mirror_repodir + '/', 'repo/') + if os.path.isdir(git_mirror_metadatadir): + common.local_rsync(options, git_mirror_metadatadir + '/', 'metadata/') ssh_private_key_file = _ssh_key_from_debug_keystore() # this is needed for GitPython to find the SSH key @@ -254,7 +259,11 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base, except subprocess.CalledProcessError: pass - subprocess.check_call(['fdroid', 'update', '--rename-apks', '--verbose'], cwd=repo_basedir) + subprocess.check_call(['fdroid', 'update', '--rename-apks', '--create-metadata', '--verbose'], + cwd=repo_basedir) + common.local_rsync(options, repo_basedir + '/metadata/', git_mirror_metadatadir + '/') + mirror_git_repo.git.add(all=True) + mirror_git_repo.index.commit("update app metadata") try: subprocess.check_call(['fdroid', 'server', 'update', '--verbose'], cwd=repo_basedir) except subprocess.CalledProcessError: diff --git a/fdroidserver/server.py b/fdroidserver/server.py index 116c27f8..004a6068 100644 --- a/fdroidserver/server.py +++ b/fdroidserver/server.py @@ -38,6 +38,9 @@ options = None BINARY_TRANSPARENCY_DIR = 'binary_transparency' +AUTO_S3CFG = '.fdroid-server-update-s3cfg' +USER_S3CFG = 's3cfg' + def update_awsbucket(repo_section): ''' @@ -72,12 +75,17 @@ def update_awsbucket_s3cmd(repo_section): logging.debug(_('Using s3cmd to sync with: {url}') .format(url=config['awsbucket'])) - configfilename = '.s3cfg' - fd = os.open(configfilename, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600) - os.write(fd, '[default]\n'.encode('utf-8')) - os.write(fd, ('access_key = ' + config['awsaccesskeyid'] + '\n').encode('utf-8')) - os.write(fd, ('secret_key = ' + config['awssecretkey'] + '\n').encode('utf-8')) - os.close(fd) + if os.path.exists(USER_S3CFG): + logging.info(_('Using "{path}" for configuring s3cmd.').format(path=USER_S3CFG)) + configfilename = USER_S3CFG + else: + fd = os.open(AUTO_S3CFG, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600) + logging.debug(_('Creating "{path}" for configuring s3cmd.').format(path=AUTO_S3CFG)) + os.write(fd, '[default]\n'.encode('utf-8')) + os.write(fd, ('access_key = ' + config['awsaccesskeyid'] + '\n').encode('utf-8')) + os.write(fd, ('secret_key = ' + config['awssecretkey'] + '\n').encode('utf-8')) + os.close(fd) + configfilename = AUTO_S3CFG s3bucketurl = 's3://' + config['awsbucket'] s3cmd = [config['s3cmd'], '--config=' + configfilename] @@ -151,6 +159,10 @@ def update_awsbucket_libcloud(repo_section): _('To use awsbucket, awssecretkey and awsaccesskeyid must also be set in config.py!')) awsbucket = config['awsbucket'] + if os.path.exists(USER_S3CFG): + raise FDroidException(_('"{path}" exists but s3cmd is not installed!') + .format(path=USER_S3CFG)) + cls = get_driver(Provider.S3) driver = cls(config['awsaccesskeyid'], config['awssecretkey']) try: @@ -501,7 +513,7 @@ def upload_to_virustotal(repo_section, vt_apikey): with open(outputfilename, 'w') as fp: json.dump(response, fp, indent=2, sort_keys=True) - if response.get('positives') > 0: + if response.get('positives', 0) > 0: logging.warning(repofilename + ' has been flagged by virustotal ' + str(response['positives']) + ' times:' + '\n\t' + response['permalink']) diff --git a/fdroidserver/update.py b/fdroidserver/update.py index 2019063c..71c4e430 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -29,7 +29,7 @@ import zipfile import hashlib import pickle import time -from datetime import datetime, timedelta +from datetime import datetime from argparse import ArgumentParser import collections @@ -772,7 +772,8 @@ def insert_localized_app_metadata(apps): """ - sourcedirs = glob.glob(os.path.join('build', '[A-Za-z]*', 'fastlane', 'metadata', 'android', '[a-z][a-z]*')) + sourcedirs = glob.glob(os.path.join('build', '[A-Za-z]*', 'src', '[A-Za-z]*', 'fastlane', 'metadata', 'android', '[a-z][a-z]*')) + sourcedirs += glob.glob(os.path.join('build', '[A-Za-z]*', 'fastlane', 'metadata', 'android', '[a-z][a-z]*')) sourcedirs += glob.glob(os.path.join('build', '[A-Za-z]*', 'metadata', '[a-z][a-z]*')) sourcedirs += glob.glob(os.path.join('metadata', '[A-Za-z]*', '[a-z][a-z]*')) @@ -787,6 +788,17 @@ def insert_localized_app_metadata(apps): continue locale = segments[-1] destdir = os.path.join('repo', packageName, locale) + + # flavours specified in build receipt + build_flavours = "" + if apps[packageName] and 'builds' in apps[packageName] and len(apps[packageName].builds) > 0\ + and 'gradle' in apps[packageName].builds[-1]: + build_flavours = apps[packageName].builds[-1].gradle + + if len(segments) >= 5 and segments[4] == "fastlane" and segments[3] not in build_flavours: + logging.debug("ignoring due to wrong flavour") + continue + for f in files: if f in ('description.txt', 'full_description.txt'): _set_localized_text_entry(apps[packageName], locale, 'description', @@ -1297,22 +1309,11 @@ def process_apk(apkcache, apkfilename, repodir, knownapks, use_date_from_apk=Fal apkzip = zipfile.ZipFile(apkfile, 'r') - # if an APK has files newer than the system time, suggest updating - # the system clock. This is useful for offline systems, used for - # signing, which do not have another source of clock sync info. It - # has to be more than 24 hours newer because ZIP/APK files do not - # store timezone info manifest = apkzip.getinfo('AndroidManifest.xml') if manifest.date_time[1] == 0: # month can't be zero logging.debug(_('AndroidManifest.xml has no date')) else: - dt_obj = datetime(*manifest.date_time) - checkdt = dt_obj - timedelta(1) - if datetime.today() < checkdt: - logging.warning('System clock is older than manifest in: ' - + apkfilename - + '\nSet clock to that time using:\n' - + 'sudo date -s "' + str(dt_obj) + '"') + common.check_system_clock(datetime(*manifest.date_time), apkfilename) # extract icons from APK zip file iconfilename = "%s.%s.png" % (apk['packageName'], apk['versionCode']) diff --git a/fdroidserver/vmtools.py b/fdroidserver/vmtools.py index aae46c75..2142beb7 100644 --- a/fdroidserver/vmtools.py +++ b/fdroidserver/vmtools.py @@ -22,13 +22,14 @@ import os import math import json import tarfile -import time import shutil import subprocess import textwrap from .common import FDroidException from logging import getLogger +from fdroidserver import _ + logger = getLogger('fdroidserver-vmtools') @@ -192,8 +193,6 @@ class FDroidBuildVm(): def up(self, provision=True): try: self.vgrnt.up(provision=provision) - logger.info('...waiting a sec...') - time.sleep(10) self.srvuuid = self._vagrant_fetch_uuid() except subprocess.CalledProcessError as e: raise FDroidBuildVmException("could not bring up vm '%s'" % self.srvname) from e @@ -202,8 +201,6 @@ class FDroidBuildVm(): logger.info('suspending buildserver') try: self.vgrnt.suspend() - logger.info('...waiting a sec...') - time.sleep(10) except subprocess.CalledProcessError as e: raise FDroidBuildVmException("could not suspend vm '%s'" % self.srvname) from e @@ -350,16 +347,12 @@ class LibvirtBuildVm(FDroidBuildVm): # (eg. lookupByName only works on running VMs) try: _check_call(('virsh', '-c', 'qemu:///system', 'destroy', self.srvname)) - logger.info("...waiting a sec...") - time.sleep(10) except subprocess.CalledProcessError as e: logger.info("could not force libvirt domain '%s' off: %s", self.srvname, e) try: # libvirt python bindings do not support all flags required # for undefining domains correctly. _check_call(('virsh', '-c', 'qemu:///system', 'undefine', self.srvname, '--nvram', '--managed-save', '--remove-all-storage', '--snapshots-metadata')) - logger.info("...waiting a sec...") - time.sleep(10) except subprocess.CalledProcessError as e: logger.info("could not undefine libvirt domain '%s': %s", self.srvname, e) @@ -383,7 +376,9 @@ class LibvirtBuildVm(FDroidBuildVm): vol = storagePool.storageVolLookupByName(self.srvname + '.img') imagepath = vol.path() # TODO use a libvirt storage pool to ensure the img file is readable - _check_call(['sudo', '/bin/chmod', '-R', 'a+rX', '/var/lib/libvirt/images']) + if not os.access(imagepath, os.R_OK): + logger.warning(_('Cannot read "{path}"!').format(path=imagepath)) + _check_call(['sudo', '/bin/chmod', '-R', 'a+rX', '/var/lib/libvirt/images']) shutil.copy2(imagepath, 'box.img') _check_call(['qemu-img', 'rebase', '-p', '-b', '', 'box.img']) img_info_raw = _check_output(['qemu-img', 'info', '--output=json', 'box.img']) @@ -454,8 +449,6 @@ class LibvirtBuildVm(FDroidBuildVm): logger.info("creating snapshot '%s' for vm '%s'", snapshot_name, self.srvname) try: _check_call(['virsh', '-c', 'qemu:///system', 'snapshot-create-as', self.srvname, snapshot_name]) - logger.info('...waiting a sec...') - time.sleep(10) except subprocess.CalledProcessError as e: raise FDroidBuildVmException("could not cerate snapshot '%s' " "of libvirt vm '%s'" @@ -484,8 +477,6 @@ class LibvirtBuildVm(FDroidBuildVm): dom = self.conn.lookupByName(self.srvname) snap = dom.snapshotLookupByName(snapshot_name) dom.revertToSnapshot(snap) - logger.info('...waiting a sec...') - time.sleep(10) except libvirt.libvirtError as e: raise FDroidBuildVmException('could not revert domain \'%s\' to snapshot \'%s\'' % (self.srvname, snapshot_name)) from e @@ -501,8 +492,6 @@ class VirtualboxBuildVm(FDroidBuildVm): logger.info("creating snapshot '%s' for vm '%s'", snapshot_name, self.srvname) try: _check_call(['VBoxManage', 'snapshot', self.srvuuid, 'take', 'fdroidclean'], cwd=self.srvdir) - logger.info('...waiting a sec...') - time.sleep(10) except subprocess.CalledProcessError as e: raise FDroidBuildVmException('could not cerate snapshot ' 'of virtualbox vm %s' diff --git a/jenkins-build-all b/jenkins-build-all index 2abac58c..d41d920f 100755 --- a/jenkins-build-all +++ b/jenkins-build-all @@ -31,7 +31,6 @@ else echo "No virtualization is used." fi sudo /bin/chmod -R a+rX /var/lib/libvirt/images -ulimit -n 2048 echo 'maximum allowed number of open file descriptors: ' `ulimit -n` ls -ld /var/lib/libvirt/images ls -l /var/lib/libvirt/images || echo no access diff --git a/setup.py b/setup.py index 1552a636..a382b254 100755 --- a/setup.py +++ b/setup.py @@ -89,6 +89,7 @@ setup(name='fdroidserver', 'pyasn1-modules', 'python-vagrant', 'PyYAML', + 'qrcode', 'ruamel.yaml >= 0.13', 'requests >= 2.5.2, != 2.11.0, != 2.12.2, != 2.18.0', 'docker-py >= 1.9, < 2.0', diff --git a/tests/build.TestCase b/tests/build.TestCase index b595f3fa..c9ffe97c 100755 --- a/tests/build.TestCase +++ b/tests/build.TestCase @@ -3,6 +3,7 @@ # http://www.drdobbs.com/testing/unit-testing-with-python/240165163 import inspect +import logging import optparse import os import re @@ -46,22 +47,27 @@ class BuildTest(unittest.TestCase): self.assertTrue(os.path.exists(path)) self.assertTrue(os.path.isfile(path)) + def setUp(self): + logging.basicConfig(level=logging.DEBUG) + self.basedir = os.path.join(localmodule, 'tests') + self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles')) + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + os.chdir(self.basedir) + def test_force_gradle_build_tools(self): - testsbase = os.path.join(os.path.dirname(__file__), '..', '.testfiles') - if not os.path.exists(testsbase): - os.makedirs(testsbase) - testsdir = tempfile.mkdtemp(prefix='test_adapt_gradle', dir=testsbase) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) shutil.copytree(os.path.join(os.path.dirname(__file__), 'source-files'), - os.path.join(testsdir, 'source-files')) + os.path.join(testdir, 'source-files')) teststring = 'FAKE_VERSION_FOR_TESTING' - fdroidserver.build.force_gradle_build_tools(testsdir, teststring) + fdroidserver.build.force_gradle_build_tools(testdir, teststring) pattern = re.compile(bytes("buildToolsVersion[\s=]+'%s'\s+" % teststring, 'utf8')) for p in ('source-files/fdroid/fdroidclient/build.gradle', 'source-files/Zillode/syncthing-silk/build.gradle', 'source-files/open-keychain/open-keychain/build.gradle', 'source-files/osmandapp/osmand/build.gradle', 'source-files/open-keychain/open-keychain/OpenKeychain/build.gradle'): - with open(os.path.join(testsdir, p), 'rb') as f: + with open(os.path.join(testdir, p), 'rb') as f: filedata = f.read() self.assertIsNotNone(pattern.search(filedata)) diff --git a/tests/common.TestCase b/tests/common.TestCase index a5d52f75..dfb4380a 100755 --- a/tests/common.TestCase +++ b/tests/common.TestCase @@ -96,8 +96,8 @@ class CommonTest(unittest.TestCase): print('no build-tools found: ' + build_tools) def test_find_java_root_path(self): - tmptestsdir = tempfile.mkdtemp(prefix='test_find_java_root_path', dir=self.tmpdir) - os.chdir(tmptestsdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) + os.chdir(testdir) all_pathlists = [ ([ # Debian @@ -170,11 +170,11 @@ class CommonTest(unittest.TestCase): testint = 99999999 teststr = 'FAKE_STR_FOR_TESTING' - tmptestsdir = tempfile.mkdtemp(prefix='test_prepare_sources', dir=self.tmpdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) shutil.copytree(os.path.join(self.basedir, 'source-files'), - os.path.join(tmptestsdir, 'source-files')) + os.path.join(testdir, 'source-files')) - testdir = os.path.join(tmptestsdir, 'source-files', 'fdroid', 'fdroidclient') + fdroidclient_testdir = os.path.join(testdir, 'source-files', 'fdroid', 'fdroidclient') config = dict() config['sdk_path'] = os.getenv('ANDROID_HOME') @@ -201,13 +201,14 @@ class CommonTest(unittest.TestCase): def getsrclib(self): return None - fdroidserver.common.prepare_source(FakeVcs(), app, build, testdir, testdir, testdir) + fdroidserver.common.prepare_source(FakeVcs(), app, build, + fdroidclient_testdir, fdroidclient_testdir, fdroidclient_testdir) - with open(os.path.join(testdir, 'build.gradle'), 'r') as f: + with open(os.path.join(fdroidclient_testdir, 'build.gradle'), 'r') as f: filedata = f.read() self.assertIsNotNone(re.search("\s+compileSdkVersion %s\s+" % testint, filedata)) - with open(os.path.join(testdir, 'AndroidManifest.xml')) as f: + with open(os.path.join(fdroidclient_testdir, 'AndroidManifest.xml')) as f: filedata = f.read() self.assertIsNone(re.search('android:debuggable', filedata)) self.assertIsNotNone(re.search('android:versionName="%s"' % build.versionName, filedata)) @@ -215,7 +216,7 @@ class CommonTest(unittest.TestCase): def test_prepare_sources_refresh(self): packageName = 'org.fdroid.ci.test.app' - testdir = tempfile.mkdtemp(prefix='test_prepare_sources_refresh', dir=self.tmpdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) print('testdir', testdir) os.chdir(testdir) os.mkdir('build') @@ -262,7 +263,7 @@ class CommonTest(unittest.TestCase): fdroidserver.signindex.config = config sourcedir = os.path.join(self.basedir, 'signindex') - testsdir = tempfile.mkdtemp(prefix='test_signjar', dir=self.tmpdir) + testsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) for f in ('testy.jar', 'guardianproject.jar',): sourcefile = os.path.join(sourcedir, f) testfile = os.path.join(testsdir, f) @@ -291,7 +292,7 @@ class CommonTest(unittest.TestCase): sourceapk = os.path.join(self.basedir, 'urzip.apk') - testdir = tempfile.mkdtemp(prefix='test_verify_apks', dir=self.tmpdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) print('testdir', testdir) copyapk = os.path.join(testdir, 'urzip-copy.apk') @@ -491,6 +492,35 @@ class CommonTest(unittest.TestCase): sig = fdroidserver.common.metadata_find_developer_signature('org.smssecure.smssecure') self.assertEqual('b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868', sig) + def test_parse_androidmanifests(self): + source_files_dir = os.path.join(os.path.dirname(__file__), 'source-files') + app = fdroidserver.metadata.App() + app.id = 'org.fdroid.fdroid' + paths = [ + os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'AndroidManifest.xml'), + os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'build.gradle'), + ] + for path in paths: + self.assertTrue(os.path.isfile(path)) + self.assertEqual(('0.94-test', '940', 'org.fdroid.fdroid'), + fdroidserver.common.parse_androidmanifests(paths, app)) + + def test_parse_androidmanifests_with_flavor(self): + source_files_dir = os.path.join(os.path.dirname(__file__), 'source-files') + app = fdroidserver.metadata.App() + build = fdroidserver.metadata.Build() + build.gradle = ['devVersion'] + app.builds = [build] + app.id = 'org.fdroid.fdroid.dev' + paths = [ + os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'AndroidManifest.xml'), + os.path.join(source_files_dir, 'fdroid', 'fdroidclient', 'build.gradle'), + ] + for path in paths: + self.assertTrue(os.path.isfile(path)) + self.assertEqual(('0.95-dev', '949', 'org.fdroid.fdroid.dev'), + fdroidserver.common.parse_androidmanifests(paths, app)) + if __name__ == "__main__": parser = optparse.OptionParser() @@ -500,4 +530,4 @@ if __name__ == "__main__": newSuite = unittest.TestSuite() newSuite.addTest(unittest.makeSuite(CommonTest)) - unittest.main(failfast=True) + unittest.main(failfast=False) diff --git a/tests/import.TestCase b/tests/import.TestCase index a41028c9..76427c59 100755 --- a/tests/import.TestCase +++ b/tests/import.TestCase @@ -3,6 +3,7 @@ # http://www.drdobbs.com/testing/unit-testing-with-python/240165163 import inspect +import logging import optparse import os import requests @@ -24,8 +25,15 @@ import import_proxy class ImportTest(unittest.TestCase): '''fdroid import''' + def setUp(self): + logging.basicConfig(level=logging.DEBUG) + self.basedir = os.path.join(localmodule, 'tests') + self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles')) + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + os.chdir(self.basedir) + def test_import_gitlab(self): - os.chdir(os.path.dirname(__file__)) # FDroidPopen needs some config to work config = dict() fdroidserver.common.fill_config_defaults(config) diff --git a/tests/index.TestCase b/tests/index.TestCase index 4a7463fb..671370c3 100755 --- a/tests/index.TestCase +++ b/tests/index.TestCase @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import inspect +import logging import optparse import os import sys @@ -31,30 +32,33 @@ GP_FINGERPRINT = 'B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A61 class IndexTest(unittest.TestCase): def setUp(self): + logging.basicConfig(level=logging.DEBUG) + self.basedir = os.path.join(localmodule, 'tests') + self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles')) + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + os.chdir(self.basedir) + fdroidserver.common.config = None config = fdroidserver.common.read_config(fdroidserver.common.options) config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner') fdroidserver.common.config = config fdroidserver.signindex.config = config - @staticmethod - def test_verify_jar_signature_succeeds(): - basedir = os.path.dirname(__file__) - source_dir = os.path.join(basedir, 'signindex') + def test_verify_jar_signature_succeeds(self): + source_dir = os.path.join(self.basedir, 'signindex') for f in ('testy.jar', 'guardianproject.jar'): testfile = os.path.join(source_dir, f) fdroidserver.common.verify_jar_signature(testfile) def test_verify_jar_signature_fails(self): - basedir = os.path.dirname(__file__) - source_dir = os.path.join(basedir, 'signindex') + source_dir = os.path.join(self.basedir, 'signindex') testfile = os.path.join(source_dir, 'unsigned.jar') with self.assertRaises(fdroidserver.index.VerificationException): fdroidserver.common.verify_jar_signature(testfile) def test_get_public_key_from_jar_succeeds(self): - basedir = os.path.dirname(__file__) - source_dir = os.path.join(basedir, 'signindex') + source_dir = os.path.join(self.basedir, 'signindex') for f in ('testy.jar', 'guardianproject.jar'): testfile = os.path.join(source_dir, f) jar = zipfile.ZipFile(testfile) @@ -68,8 +72,7 @@ class IndexTest(unittest.TestCase): self.assertTrue(fingerprint == GP_FINGERPRINT) def test_get_public_key_from_jar_fails(self): - basedir = os.path.dirname(__file__) - source_dir = os.path.join(basedir, 'signindex') + source_dir = os.path.join(self.basedir, 'signindex') testfile = os.path.join(source_dir, 'unsigned.jar') jar = zipfile.ZipFile(testfile) with self.assertRaises(fdroidserver.index.VerificationException): @@ -226,9 +229,6 @@ class IndexTest(unittest.TestCase): if __name__ == "__main__": - if os.path.basename(os.getcwd()) != 'tests' and os.path.isdir('tests'): - os.chdir('tests') - parser = optparse.OptionParser() parser.add_option("-v", "--verbose", action="store_true", default=False, help="Spew out even more information than normal") diff --git a/tests/lint.TestCase b/tests/lint.TestCase index f2a411c5..433c0787 100755 --- a/tests/lint.TestCase +++ b/tests/lint.TestCase @@ -32,8 +32,7 @@ class LintTest(unittest.TestCase): self.assertTrue(fdroidserver.lint.check_for_unsupported_metadata_files()) tmpdir = os.path.join(localmodule, '.testfiles') - tmptestsdir = tempfile.mkdtemp(prefix='test_check_for_unsupported_metadata_files-', - dir=tmpdir) + tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=tmpdir) self.assertFalse(fdroidserver.lint.check_for_unsupported_metadata_files(tmptestsdir + '/')) shutil.copytree(os.path.join(localmodule, 'tests', 'metadata'), os.path.join(tmptestsdir, 'metadata'), diff --git a/tests/metadata.TestCase b/tests/metadata.TestCase index 306f1c46..247f6a89 100755 --- a/tests/metadata.TestCase +++ b/tests/metadata.TestCase @@ -2,9 +2,13 @@ # http://www.drdobbs.com/testing/unit-testing-with-python/240165163 +import glob import inspect +import logging import optparse import os +import random +import shutil import sys import unittest import yaml @@ -23,15 +27,20 @@ import fdroidserver.metadata class MetadataTest(unittest.TestCase): '''fdroidserver/metadata.py''' + def setUp(self): + logging.basicConfig(level=logging.DEBUG) + self.basedir = os.path.join(localmodule, 'tests') + self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles')) + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + os.chdir(self.basedir) + def test_read_metadata(self): def _build_yaml_representer(dumper, data): '''Creates a YAML representation of a Build instance''' return dumper.represent_dict(data) - testsdir = os.path.dirname(__file__) - os.chdir(testsdir) - self.maxDiff = None # these need to be set to prevent code running on None, only @@ -58,12 +67,7 @@ class MetadataTest(unittest.TestCase): # yaml.dump(frommeta, f, default_flow_style=False) def test_rewrite_yaml_fakeotaupdate(self): - - # setup/reset test dir if necessary and setup params - tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles') - if not os.path.exists(tmpdir): - os.makedirs(tmpdir) - testdir = tempfile.mkdtemp(prefix='test_rewrite_metadata_', dir=tmpdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) fdroidserver.common.config = {'accepted_formats': ['txt', 'yml']} # rewrite metadata @@ -79,12 +83,7 @@ class MetadataTest(unittest.TestCase): self.assertEqual(result.read(), orig.read()) def test_rewrite_yaml_fdroidclient(self): - - # setup/reset test dir if necessary and setup params - tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles') - if not os.path.exists(tmpdir): - os.makedirs(tmpdir) - testdir = tempfile.mkdtemp(prefix='test_rewrite_metadata_', dir=tmpdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) fdroidserver.common.config = {'accepted_formats': ['txt', 'yml']} # rewrite metadata @@ -100,12 +99,7 @@ class MetadataTest(unittest.TestCase): self.assertEqual(result.read(), orig.read()) def test_rewrite_yaml_special_build_params(self): - - # setup/reset test dir if necessary and setup params - tmpdir = os.path.join(os.path.dirname(__file__), '..', '.testfiles') - if not os.path.exists(tmpdir): - os.makedirs(tmpdir) - testdir = tempfile.mkdtemp(prefix='test_rewrite_metadata_', dir=tmpdir) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) fdroidserver.common.config = {'accepted_formats': ['txt', 'yml']} # rewrite metadata @@ -120,6 +114,31 @@ class MetadataTest(unittest.TestCase): self.maxDiff = None self.assertEqual(result.read(), orig.read()) + def test_read_metadata_sort_by_time(self): + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) + metadatadir = os.path.join(testdir, 'metadata') + os.makedirs(metadatadir) + fdroidserver.common.config = {'accepted_formats': ['txt']} + + randomlist = [] + randomapps = glob.glob(os.path.join(self.basedir, 'metadata', '*.txt')) + random.shuffle(randomapps) + i = 1 + for f in randomapps: + shutil.copy(f, metadatadir) + new = os.path.join(metadatadir, os.path.basename(f)) + stat = os.stat(new) + os.utime(new, (stat.st_ctime, stat.st_mtime + i)) + # prepend new item so newest is always first + randomlist = [os.path.basename(f)[:-4]] + randomlist + i += 1 + os.chdir(testdir) + allapps = fdroidserver.metadata.read_metadata(xref=True, sort_by_time=True) + allappids = [] + for appid, app in allapps.items(): + allappids.append(appid) + self.assertEqual(randomlist, allappids) + if __name__ == "__main__": parser = optparse.OptionParser() diff --git a/tests/publish.TestCase b/tests/publish.TestCase index 8e165608..e296a48a 100755 --- a/tests/publish.TestCase +++ b/tests/publish.TestCase @@ -11,6 +11,7 @@ # import inspect +import logging import optparse import os import sys @@ -32,6 +33,14 @@ from fdroidserver.exception import FDroidException class PublishTest(unittest.TestCase): '''fdroidserver/publish.py''' + def setUp(self): + logging.basicConfig(level=logging.DEBUG) + self.basedir = os.path.join(localmodule, 'tests') + self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles')) + if not os.path.exists(self.tmpdir): + os.makedirs(self.tmpdir) + os.chdir(self.basedir) + def test_key_alias(self): publish.config = {} self.assertEqual('a163ec9b', publish.key_alias('com.example.app')) @@ -77,39 +86,35 @@ class PublishTest(unittest.TestCase): 'com.example.anotherapp', 'org.org.org'] - with tempfile.TemporaryDirectory() as tmpdir: - orig_cwd = os.getcwd() - try: - os.chdir(tmpdir) - with open('config.py', 'w') as f: - pass - - publish.store_stats_fdroid_signing_key_fingerprints(appids, indent=2) - - self.maxDiff = None - expected = { - "com.example.anotherapp": { - "signer": "fa3f6a017541ee7fe797be084b1bcfbf92418a7589ef1f7fdeb46741b6d2e9c3" - }, - "com.example.app": { - "signer": "d34f678afbaa8f2fa6cc0edd6f0c2d1d2e2e9eb08bea521b24c740806016bff4" - }, - "org.org.org": { - "signer": "277655a6235bc6b0ef2d824396c51ba947f5ebc738c293d887e7083ff338af82" - }, - "org.test.testy": { - "signer": "6ae5355157a47ddcc3834a71f57f6fb5a8c2621c8e0dc739e9ddf59f865e497c" - } - } - self.assertEqual(expected, common.load_stats_fdroid_signing_key_fingerprints()) - - with open('config.py', 'r') as f: - self.assertEqual(textwrap.dedent('''\ - - repo_key_sha256 = "c58460800c7b250a619c30c13b07b7359a43e5af71a4352d86c58ae18c9f6d41" - '''), f.read()) - finally: - os.chdir(orig_cwd) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) + os.chdir(testdir) + with open('config.py', 'w') as f: + pass + + publish.store_stats_fdroid_signing_key_fingerprints(appids, indent=2) + + self.maxDiff = None + expected = { + "com.example.anotherapp": { + "signer": "fa3f6a017541ee7fe797be084b1bcfbf92418a7589ef1f7fdeb46741b6d2e9c3" + }, + "com.example.app": { + "signer": "d34f678afbaa8f2fa6cc0edd6f0c2d1d2e2e9eb08bea521b24c740806016bff4" + }, + "org.org.org": { + "signer": "277655a6235bc6b0ef2d824396c51ba947f5ebc738c293d887e7083ff338af82" + }, + "org.test.testy": { + "signer": "6ae5355157a47ddcc3834a71f57f6fb5a8c2621c8e0dc739e9ddf59f865e497c" + } + } + self.assertEqual(expected, common.load_stats_fdroid_signing_key_fingerprints()) + + with open('config.py', 'r') as f: + self.assertEqual(textwrap.dedent('''\ + + repo_key_sha256 = "c58460800c7b250a619c30c13b07b7359a43e5af71a4352d86c58ae18c9f6d41" + '''), f.read()) def test_store_and_load_fdroid_signing_key_fingerprints_with_missmatch(self): common.config = {} @@ -122,21 +127,14 @@ class PublishTest(unittest.TestCase): publish.config['repo_keyalias'] = 'repokey' publish.config['repo_key_sha256'] = 'bad bad bad bad bad bad bad bad bad bad bad bad' - with tempfile.TemporaryDirectory() as tmpdir: - orig_cwd = os.getcwd() - try: - os.chdir(tmpdir) - publish.store_stats_fdroid_signing_key_fingerprints({}, indent=2) - with self.assertRaises(FDroidException): - common.load_stats_fdroid_signing_key_fingerprints() - finally: - os.chdir(orig_cwd) + testdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=self.tmpdir) + os.chdir(testdir) + publish.store_stats_fdroid_signing_key_fingerprints({}, indent=2) + with self.assertRaises(FDroidException): + common.load_stats_fdroid_signing_key_fingerprints() if __name__ == "__main__": - if os.path.basename(os.getcwd()) != 'tests' and os.path.isdir('tests'): - os.chdir('tests') - parser = optparse.OptionParser() parser.add_option("-v", "--verbose", action="store_true", default=False, help="Spew out even more information than normal") diff --git a/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 00000000..f11ffa13 --- /dev/null +++ b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1 @@ +The copyleft libre software Nextcloud Android app, gives you access to all the files in your Nextcloud.\n\nFeatures:\n* Easy, modern interface, suited to the theme of your server\n* Upload files to your Nextcloud server\n* Share them with others\n* Keep your favorite files and folders synced\n* Search across all folders on your server\n* Auto Upload for photos and videos taken by your device\n* Keep up to date with notifications\n* Multi-account support\n* Secure access to your data with fingerprint or PIN\n* Integration with DAVdroid for easy setup of calendar & Contacts synchronization\n\nPlease report all issues at https://github.com/nextcloud/android/issues and discuss this app at https://help.nextcloud.com/c/clients/android\n\nNew to Nextcloud? Nextcloud is a private file sync & share and communication server. It is libre software, and you can host it yourself or pay a company to do it for you. That way, you are in control of your photos, your calendar and contact data, your documents and everything else.\n\nCheck out Nextcloud at https://nextcloud.com \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 00000000..69b7d99b --- /dev/null +++ b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +The Nextcloud Android app gives you access to all your files in your Nextcloud \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 00000000..9928b03a --- /dev/null +++ b/tests/source-files/com.nextcloud.client.dev/src/generic/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Nextcloud \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 00000000..8593fc4f --- /dev/null +++ b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1 @@ +The Open Source Nextcloud Android app allows you to access all your files on your Nextcloud.\nThis is a dev version of the official Nextcloud app and includes brand-new, untested features which might lead to instabilities and data loss. The app is designed for users willing to test the new features and to report bugs if they occur. Do not use it for your productive work!\n\nThe dev version can be installed alongside the official Nextcloud app which is available at F-Droid, too. Once a day it is checked if the source code was updated, so there can be longer pauses between builds. diff --git a/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 00000000..42b3df3e --- /dev/null +++ b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +The Nextcloud Dev app is a development snapshot and can be installed parallel. diff --git a/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 00000000..dc9e6199 --- /dev/null +++ b/tests/source-files/com.nextcloud.client.dev/src/versionDev/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Nextcloud Dev \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 00000000..f11ffa13 --- /dev/null +++ b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1 @@ +The copyleft libre software Nextcloud Android app, gives you access to all the files in your Nextcloud.\n\nFeatures:\n* Easy, modern interface, suited to the theme of your server\n* Upload files to your Nextcloud server\n* Share them with others\n* Keep your favorite files and folders synced\n* Search across all folders on your server\n* Auto Upload for photos and videos taken by your device\n* Keep up to date with notifications\n* Multi-account support\n* Secure access to your data with fingerprint or PIN\n* Integration with DAVdroid for easy setup of calendar & Contacts synchronization\n\nPlease report all issues at https://github.com/nextcloud/android/issues and discuss this app at https://help.nextcloud.com/c/clients/android\n\nNew to Nextcloud? Nextcloud is a private file sync & share and communication server. It is libre software, and you can host it yourself or pay a company to do it for you. That way, you are in control of your photos, your calendar and contact data, your documents and everything else.\n\nCheck out Nextcloud at https://nextcloud.com \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 00000000..69b7d99b --- /dev/null +++ b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +The Nextcloud Android app gives you access to all your files in your Nextcloud \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 00000000..9928b03a --- /dev/null +++ b/tests/source-files/com.nextcloud.client/src/generic/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Nextcloud \ No newline at end of file diff --git a/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/full_description.txt b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 00000000..8593fc4f --- /dev/null +++ b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1 @@ +The Open Source Nextcloud Android app allows you to access all your files on your Nextcloud.\nThis is a dev version of the official Nextcloud app and includes brand-new, untested features which might lead to instabilities and data loss. The app is designed for users willing to test the new features and to report bugs if they occur. Do not use it for your productive work!\n\nThe dev version can be installed alongside the official Nextcloud app which is available at F-Droid, too. Once a day it is checked if the source code was updated, so there can be longer pauses between builds. diff --git a/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 00000000..42b3df3e --- /dev/null +++ b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +The Nextcloud Dev app is a development snapshot and can be installed parallel. diff --git a/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 00000000..dc9e6199 --- /dev/null +++ b/tests/source-files/com.nextcloud.client/src/versionDev/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +Nextcloud Dev \ No newline at end of file diff --git a/tests/source-files/fdroid/fdroidclient/build.gradle b/tests/source-files/fdroid/fdroidclient/build.gradle index 1d994dc8..c81ff357 100644 --- a/tests/source-files/fdroid/fdroidclient/build.gradle +++ b/tests/source-files/fdroid/fdroidclient/build.gradle @@ -128,6 +128,21 @@ task binaryDeps(type: Copy, dependsOn: ':F-Droid:prepareReleaseDependencies') { android { compileSdkVersion 21 buildToolsVersion '22.0.1' + + defaultConfig { + + flavorDimensions "default" + + productFlavors { + devVersion { + applicationId "org.fdroid.fdroid.dev" + dimension "default" + versionCode 949 + versionName "0.95-dev" + } + } + + } sourceSets { main { diff --git a/tests/update.TestCase b/tests/update.TestCase index 8a88fc4e..8e4ac319 100755 --- a/tests/update.TestCase +++ b/tests/update.TestCase @@ -42,19 +42,37 @@ class UpdateTest(unittest.TestCase): shutil.rmtree(os.path.join('repo', 'info.guardianproject.urzip'), ignore_errors=True) + shutil.rmtree(os.path.join('build', 'com.nextcloud.client'), ignore_errors=True) + shutil.copytree(os.path.join('source-files', 'com.nextcloud.client'), os.path.join('build', 'com.nextcloud.client')) + + shutil.rmtree(os.path.join('build', 'com.nextcloud.client.dev'), ignore_errors=True) + shutil.copytree(os.path.join('source-files', 'com.nextcloud.client.dev'), + os.path.join('build', 'com.nextcloud.client.dev')) + apps = dict() - for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'): - apps[packageName] = dict() + for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current', + 'com.nextcloud.client', 'com.nextcloud.client.dev'): + apps[packageName] = fdroidserver.metadata.App() apps[packageName]['id'] = packageName apps[packageName]['CurrentVersionCode'] = 0xcafebeef + apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100 + + buildnextcloudclient = fdroidserver.metadata.Build() + buildnextcloudclient.gradle = ['generic'] + apps['com.nextcloud.client']['builds'] = [buildnextcloudclient] + + buildnextclouddevclient = fdroidserver.metadata.Build() + buildnextclouddevclient.gradle = ['versionDev'] + apps['com.nextcloud.client.dev']['builds'] = [buildnextclouddevclient] + fdroidserver.update.insert_localized_app_metadata(apps) appdir = os.path.join('repo', 'info.guardianproject.urzip', 'en-US') self.assertTrue(os.path.isfile(os.path.join(appdir, 'icon.png'))) self.assertTrue(os.path.isfile(os.path.join(appdir, 'featureGraphic.png'))) - self.assertEqual(3, len(apps)) + self.assertEqual(5, len(apps)) for packageName, app in apps.items(): self.assertTrue('localized' in app) self.assertTrue('en-US' in app['localized']) @@ -77,6 +95,14 @@ class UpdateTest(unittest.TestCase): self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic']) self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots'])) self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots'])) + elif packageName == 'com.nextcloud.client': + self.assertEqual('Nextcloud', app['localized']['en-US']['name']) + self.assertEqual(1073, len(app['localized']['en-US']['description'])) + self.assertEqual(78, len(app['localized']['en-US']['summary'])) + elif packageName == 'com.nextcloud.client.dev': + self.assertEqual('Nextcloud Dev', app['localized']['en-US']['name']) + self.assertEqual(586, len(app['localized']['en-US']['description'])) + self.assertEqual(79, len(app['localized']['en-US']['summary'])) def test_insert_triple_t_metadata(self): importer = os.path.join(localmodule, 'tests', 'tmp', 'importer') @@ -87,7 +113,7 @@ class UpdateTest(unittest.TestCase): tmpdir = os.path.join(localmodule, '.testfiles') if not os.path.exists(tmpdir): os.makedirs(tmpdir) - tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir) + tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=tmpdir) packageDir = os.path.join(tmptestsdir, 'build', packageName) shutil.copytree(importer, packageDir) @@ -374,7 +400,7 @@ class UpdateTest(unittest.TestCase): tmpdir = os.path.join(localmodule, '.testfiles') if not os.path.exists(tmpdir): os.makedirs(tmpdir) - tmptestsdir = tempfile.mkdtemp(prefix='test_process_apk_signed_by_disabled_algorithms-', + tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=tmpdir) print('tmptestsdir', tmptestsdir) os.chdir(tmptestsdir) @@ -504,7 +530,7 @@ class UpdateTest(unittest.TestCase): tmpdir = os.path.join(localmodule, '.testfiles') if not os.path.exists(tmpdir): os.makedirs(tmpdir) - tmptestsdir = tempfile.mkdtemp(prefix='test_create_metadata_from_template-', + tmptestsdir = tempfile.mkdtemp(prefix=inspect.currentframe().f_code.co_name, dir=tmpdir) print('tmptestsdir', tmptestsdir) os.chdir(tmptestsdir)