diff --git a/config.sample.py b/config.sample.py index 7d666d1e..d1417362 100644 --- a/config.sample.py +++ b/config.sample.py @@ -5,9 +5,8 @@ sdk_path = "/path/to/android-sdk-linux_86" ndk_path = "/path/to/android-ndk-r8e" -# May be necessary for fdroid update; you may still need to make a symlink to -# aapt in platform-tools -aapt_path = "/path/to/android-sdk-linux_x86/build-tools/17.0.0/aapt" +# Build tools version to be used +build_tools = "18.0.1" #You probably don't need to change this... javacc_path = "/usr/share/java" @@ -15,6 +14,12 @@ javacc_path = "/usr/share/java" #Command for running maven 3 (could be mvn, mvn3, or a full path) mvn3 = "mvn3" +# Gradle command +gradle = "gradle" + +# Android gradle plugin version +gradle_plugin = "0.5.+" + repo_url = "https://f-droid.org/repo" repo_name = "F-Droid" repo_icon = "fdroid-icon.png" diff --git a/docs/fdroid.texi b/docs/fdroid.texi index 1f0de51a..ff4861c8 100644 --- a/docs/fdroid.texi +++ b/docs/fdroid.texi @@ -772,6 +772,14 @@ already exist, but not a good idea if it's heavily customised. If you get an error about invalid target, first try @code{init=rm -rf bin/}; otherwise this parameter should do the trick. +Please note that gradle builds should be using compilesdk=. + +@item compilesdk= +Practically accomplishes the same that target= does when used in ant and maven +projects. compilesdk= is used rather than target= so as to not cause any more +confusion. It only takes effect on gradle builds in the build.gradle file, +thus using it in any other case is not wise. + @item update=xxx By default, 'android update project' is used to generate or update the build.xml file. Specifying update=no bypasses that. diff --git a/fdroidserver/build.py b/fdroidserver/build.py index e51716b2..86ca309e 100644 --- a/fdroidserver/build.py +++ b/fdroidserver/build.py @@ -415,6 +415,29 @@ def build_local(app, thisbuild, vcs, build_dir, output_dir, srclib_dir, extlib_d if 'mvnflags' in thisbuild: mvncmd += thisbuild['mvnflags'] p = subprocess.Popen(mvncmd, cwd=root_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif 'gradle' in thisbuild: + flavour = thisbuild['gradle'] + + if 'compilesdk' in thisbuild: + level = thisbuild["compilesdk"].split('-')[1] + subprocess.call(['sed', '-i', + 's@compileSdkVersion[ ]*[0-9]*@compileSdkVersion '+level+'@g', + 'build.gradle'], cwd=root_dir) + + subprocess.call(['sed', '-i', + 's@buildToolsVersion[ ]*["\'][0-9\.]*["\']@buildToolsVersion "'+build_tools+'"@g', + 'build.gradle'], cwd=root_dir) + + subprocess.call(['sed', '-i', + 's@com.android.tools.build:gradle:[0-9\.\+]*@com.android.tools.build:gradle:'+gradle_plugin+'@g', + 'build.gradle'], cwd=root_dir) + + if install: + commands = [gradle, 'assemble'+flavour+'Debug', 'install'+flavour+'Debug'] + else: + commands = [gradle, 'assemble'+flavour+'Release'] + + p = subprocess.Popen(commands, cwd=root_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) else: if install: antcommands = ['debug','install'] @@ -464,6 +487,8 @@ def build_local(app, thisbuild, vcs, build_dir, output_dir, srclib_dir, extlib_d raise BuildException('Failed to find output') src = m.group(1) src = os.path.join(bindir, src) + '.apk' + elif 'gradle' in thisbuild: + src = os.path.join(build_dir, 'build', 'apk', '-'.join([app['id'], flavour, 'release', 'unsigned'])+'.apk') else: src = re.match(r".*^.*Creating (.+) for release.*$.*", output, re.S|re.M).group(1) @@ -478,14 +503,8 @@ def build_local(app, thisbuild, vcs, build_dir, output_dir, srclib_dir, extlib_d print "Checking " + src if not os.path.exists(src): raise BuildException("Unsigned apk is not at expected location of " + src) - if ('aapt_path' not in globals()): - # (re-)read configuration - execfile('config.py', globals()) - if not os.path.exists(aapt_path): - print "Missing aapt - check aapt_path in your config" - sys.exit(1) - p = subprocess.Popen([aapt_path, + p = subprocess.Popen([os.path.join(sdk_path, 'build-tools', build_tools, 'aapt'), 'dump', 'badging', src], stdout=subprocess.PIPE) output = p.communicate()[0] diff --git a/fdroidserver/checkupdates.py b/fdroidserver/checkupdates.py index 0cb435ac..d030f4a6 100644 --- a/fdroidserver/checkupdates.py +++ b/fdroidserver/checkupdates.py @@ -60,9 +60,12 @@ def check_tags(app, sdk_path): vcs.gotorevision(None) + flavour = None if len(app['builds']) > 0: if 'subdir' in app['builds'][-1]: build_dir = os.path.join(build_dir, app['builds'][-1]['subdir']) + if 'gradle' in app['builds'][-1]: + flavour = app['builds'][-1]['gradle'] hver = None hcode = "0" @@ -71,15 +74,13 @@ def check_tags(app, sdk_path): vcs.gotorevision(tag) # Only process tags where the manifest exists... - path = common.manifest_path(build_dir) - print "Trying manifest at %s" % path - if os.path.exists(path): - version, vercode, package = common.parse_androidmanifest(build_dir) - print "Manifest exists. Found version %s" % version - if package and package == app['id'] and version and vercode: - if int(vercode) > int(hcode): - hcode = str(int(vercode)) - hver = version + paths = common.manifest_paths(build_dir, flavour) + version, vercode, package = common.parse_androidmanifests(paths) + print "Manifest exists. Found version %s" % version + if package and package == app['id'] and version and vercode: + if int(vercode) > int(hcode): + hcode = str(int(vercode)) + hver = version if hver: return (hver, hcode) @@ -138,20 +139,20 @@ def check_repomanifest(app, sdk_path, branch=None): elif vcs.repotype() == 'bzr': vcs.gotorevision(None) + flavour = None + if len(app['builds']) > 0: if 'subdir' in app['builds'][-1]: build_dir = os.path.join(build_dir, app['builds'][-1]['subdir']) + if 'gradle' in app['builds'][-1]: + flavour = app['builds'][-1]['gradle'] if not os.path.isdir(build_dir): return (None, "Subdir '" + app['builds'][-1]['subdir'] + "'is not a valid directory") - if os.path.exists(os.path.join(build_dir, 'AndroidManifest.xml')): - version, vercode, package = common.parse_androidmanifest(build_dir) - elif os.path.exists(os.path.join(build_dir, 'src', 'main', 'AndroidManifest.xml')): - # Alternate location for simple gradle locations... - version, vercode, package = common.parse_androidmanifest(os.path.join(build_dir, 'src', 'main')) - else: - return (None, "AndroidManifest.xml not found") + paths = common.manifest_paths(build_dir, flavour) + + version, vercode, package = common.parse_androidmanifests(paths) if not package: return (None, "Couldn't find package ID") if package != app['id']: @@ -219,6 +220,7 @@ def check_market(app): def main(): #Read configuration... + globals()['gradle'] = "gradle" execfile('config.py', globals()) # Parse command line... @@ -302,15 +304,18 @@ def main(): vcs = common.getvcs(app["Repo Type"], app["Repo"], app_dir, sdk_path) vcs.gotorevision(None) + flavour = None if len(app['builds']) > 0: if 'subdir' in app['builds'][-1]: app_dir = os.path.join(app_dir, app['builds'][-1]['subdir']) + if 'gradle' in app['builds'][-1]: + flavour = app['builds'][-1]['gradle'] - new_name = common.fetch_real_name(app_dir) + new_name = common.fetch_real_name(app_dir, flavour) if new_name != app['Auto Name']: app['Auto Name'] = new_name - if not writeit: - writeit = True + writeit = True + except Exception: msg = "Auto Name failed for %s due to exception: %s" % (app['id'], traceback.format_exc()) diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 67551c2d..12794bac 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -22,6 +22,7 @@ import subprocess import time import operator import cgi +import fileinput def getvcs(vcstype, remote, local, sdk_path): if vcstype == 'git': @@ -861,77 +862,109 @@ def description_html(lines,linkres): ps.end() return ps.text_html -def retrieve_string(app_dir, string_id): - string_search = re.compile(r'.*"'+string_id+'".*>([^<]+?)<.*').search - for xmlfile in glob.glob(os.path.join( - app_dir, 'res', 'values', '*.xml')): +def retrieve_string(xml_dir, string): + if not string.startswith('@string/'): + return string.replace("\\'","'") + string_search = re.compile(r'.*"'+string[8:]+'".*>([^<]+?)<.*').search + for xmlfile in glob.glob(os.path.join(xml_dir, '*.xml')): for line in file(xmlfile): matches = string_search(line) if matches: - s = matches.group(1) - if s.startswith('@string/'): - return retrieve_string(app_dir, s[8:]); - return s.replace("\\'","'") + return retrieve_string(xml_dir, matches.group(1)) return '' -# Find the AM.xml - try the new gradle method first. -def manifest_path(app_dir): - gradlepath = os.path.join(app_dir, 'source', 'main', 'AndroidManifest.xml') - if os.path.exists(gradlepath): - return gradlepath - rootpath = os.path.join(app_dir, 'AndroidManifest.xml') - return rootpath +# Return list of existing files that will be used to find the highest vercode +def manifest_paths(app_dir, flavour): + possible_manifests = [ os.path.join(app_dir, 'AndroidManifest.xml'), + os.path.join(app_dir, 'src', 'main', 'AndroidManifest.xml'), + os.path.join(app_dir, 'build.gradle') ] + + if flavour is not None: + possible_manifests.append( + os.path.join(app_dir, 'src', flavour, 'AndroidManifest.xml')) + + return [path for path in possible_manifests if os.path.isfile(path)] # Retrieve the package name -def fetch_real_name(app_dir): +def fetch_real_name(app_dir, flavour): app_search = re.compile(r'.*([^<]+?)<.*').search - version = None - vercode = None - package = None - for line in file(manifest_path(app_dir)): - if not package: - matches = psearch(line) - if matches: - package = matches.group(1) - if not version: - matches = vnsearch(line) - if matches: - version = matches.group(1) - if not vercode: - matches = vcsearch(line) - if matches: - vercode = matches.group(1) - if version.startswith('@string/'): - version = retrieve_string(app_dir, version[8:]) - return (version, vercode, package) + + vcsearch_g = re.compile(r'.*versionCode[ ]+?([0-9]+?).*').search + vnsearch_g = re.compile(r'.*versionName[ ]+?"([^"]+?)".*').search + psearch_g = re.compile(r'.*packageName[ ]+?"([^"]+)".*').search + + max_version = None + max_vercode = None + max_package = None + + for path in paths: + + gradle = path.endswith("gradle") + version = None + vercode = None + # Remember package name, may be defined separately from version+vercode + package = max_package + + for line in file(path): + if not package: + if gradle: + matches = psearch_g(line) + else: + matches = psearch(line) + if matches: + package = matches.group(1) + if not version: + if gradle: + matches = vnsearch_g(line) + else: + matches = vnsearch(line) + if matches: + version = matches.group(1) + if not vercode: + if gradle: + matches = vcsearch_g(line) + else: + matches = vcsearch(line) + if matches: + vercode = matches.group(1) + + # Better some package name than nothing + if max_package is None: + max_package = package + + if max_vercode is None or (vercode is not None and vercode > max_vercode): + max_version = version + max_vercode = vercode + max_package = package + + return (max_version, max_vercode, max_package) class BuildException(Exception): def __init__(self, value, stdout = None, stderr = None): @@ -1134,7 +1167,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, sdk_path, # Generate (or update) the ant build file, build.xml... updatemode = build.get('update', '.') if (updatemode != 'no' and - 'maven' not in build): + 'maven' not in build and 'gradle' not in build): parms = [os.path.join(sdk_path, 'tools', 'android'), 'update', 'project', '-p', '.'] parms.append('--subprojects') @@ -1575,14 +1608,9 @@ def isApkDebuggable(apkfile): :param apkfile: full path to the apk to check""" - if ('aapt_path' not in globals()): - # (re-)read configuration - execfile('config.py', globals()) - if not os.path.exists(aapt_path): - print "Missing aapt - check aapt_path in your config" - sys.exit(1) + execfile('config.py', globals()) - p = subprocess.Popen([aapt_path, + p = subprocess.Popen([os.path.join(sdk_path, 'build-tools', build_tools, 'aapt'), 'dump', 'xmltree', apkfile, 'AndroidManifest.xml'], stdout=subprocess.PIPE) output = p.communicate()[0] diff --git a/fdroidserver/update.py b/fdroidserver/update.py index c705cb4b..d40773d5 100644 --- a/fdroidserver/update.py +++ b/fdroidserver/update.py @@ -267,10 +267,7 @@ def scan_apks(apps, apkcache, repodir, knownapks): thisinfo['size'] = os.path.getsize(apkfile) thisinfo['permissions'] = [] thisinfo['features'] = [] - if not os.path.exists(aapt_path): - print "Missing aapt - check aapt_path in your config" - sys.exit(1) - p = subprocess.Popen([aapt_path, + p = subprocess.Popen([os.path.join(sdk_path, 'build-tools', build_tools, 'aapt'), 'dump', 'badging', apkfile], stdout=subprocess.PIPE) output = p.communicate()[0]