chiark / gitweb /
Set the FDroidOpen output default back to true
[fdroidserver.git] / fdroidserver / common.py
index 6bae404505d0ac9eedc92b2aeaa93f7de851bab5..32a924b5200664cd73aef5b555c076e6cc35566c 100644 (file)
@@ -30,6 +30,7 @@ import Queue
 import threading
 import magic
 import logging
+from distutils.version import LooseVersion
 
 import metadata
 
@@ -40,8 +41,8 @@ options = None
 def get_default_config():
     return {
         'sdk_path': os.getenv("ANDROID_HOME"),
-        'ndk_path': "$ANDROID_NDK",
-        'build_tools': "19.0.3",
+        'ndk_path': os.getenv("ANDROID_NDK"),
+        'build_tools': "19.1.0",
         'ant': "ant",
         'mvn3': "mvn",
         'gradle': 'gradle',
@@ -50,7 +51,7 @@ def get_default_config():
         'stats_to_carbon': False,
         'repo_maxage': 0,
         'build_server_always': False,
-        'keystore': '$HOME/.local/share/fdroidserver/keystore.jks',
+        'keystore': os.path.join(os.getenv("HOME"), '.local', 'share', 'fdroidserver', 'keystore.jks'),
         'smartcardoptions': [],
         'char_limits': {
             'Summary': 50,
@@ -91,6 +92,11 @@ def read_config(opts, config_file='config.py'):
                                       'sun.security.pkcs11.SunPKCS11',
                                       '-providerArg', 'opensc-fdroid.cfg']
 
+    if any(k in config for k in ["keystore", "keystorepass", "keypass"]):
+        st = os.stat(config_file)
+        if st.st_mode & stat.S_IRWXG or st.st_mode & stat.S_IRWXO:
+            logging.warn("unsafe permissions on {0} (should be 0600)!".format(config_file))
+
     defconfig = get_default_config()
     for k, v in defconfig.items():
         if k not in config:
@@ -106,11 +112,6 @@ def read_config(opts, config_file='config.py'):
     if not test_sdk_exists(config):
         sys.exit(3)
 
-    if any(k in config for k in ["keystore", "keystorepass", "keypass"]):
-        st = os.stat(config_file)
-        if st.st_mode & stat.S_IRWXG or st.st_mode & stat.S_IRWXO:
-            logging.warn("unsafe permissions on {0} (should be 0600)!".format(config_file))
-
     for k in ["keystorepass", "keypass"]:
         if k in config:
             write_password_file(k)
@@ -141,6 +142,26 @@ def test_sdk_exists(c):
     if not os.path.isdir(os.path.join(c['sdk_path'], 'build-tools')):
         logging.critical('Android SDK path "' + c['sdk_path'] + '" does not contain "build-tools/"!')
         return False
+    if not os.path.isdir(os.path.join(c['sdk_path'], 'build-tools', c['build_tools'])):
+        logging.critical('Configured build-tools version "' + c['build_tools'] + '" not found in the SDK!')
+        return False
+    return True
+
+
+def test_build_tools_exists(c):
+    if not test_sdk_exists(c):
+        return False
+    build_tools = os.path.join(c['sdk_path'], 'build-tools')
+    versioned_build_tools = os.path.join(build_tools, c['build_tools'])
+    if not os.path.isdir(versioned_build_tools):
+        logging.critical('Android Build Tools path "'
+                         + versioned_build_tools + '" does not exist!')
+        return False
+    if not os.path.exists(os.path.join(c['sdk_path'], 'build-tools', c['build_tools'], 'aapt')):
+        logging.critical('Android Build Tools "'
+                         + versioned_build_tools
+                         + '" does not contain "aapt"!')
+        return False
     return True
 
 
@@ -280,10 +301,9 @@ def getvcs(vcstype, remote, local):
 
 
 def getsrclibvcs(name):
-    srclib_path = os.path.join('srclibs', name + ".txt")
-    if not os.path.exists(srclib_path):
+    if name not in metadata.srclibs:
         raise VCSException("Missing srclib " + name)
-    return metadata.parse_srclib(srclib_path)['Repo Type']
+    return metadata.srclibs[name]['Repo Type']
 
 
 class vcs:
@@ -713,7 +733,7 @@ def retrieve_string(app_dir, string, xmlfiles=None):
 
     string_search = None
     if string.startswith('@string/'):
-        string_search = re.compile(r'.*"' + string[8:] + '".*?>([^<]+?)<.*').search
+        string_search = re.compile(r'.*name="' + string[8:] + '".*?>([^<]+?)<.*').search
     elif string.startswith('&') and string.endswith(';'):
         string_search = re.compile(r'.*<!ENTITY.*' + string[1:-1] + '.*?"([^"]+?)".*>').search
 
@@ -823,7 +843,7 @@ def remove_debuggable_flags(root_dir):
 # Extract some information from the AndroidManifest.xml at the given path.
 # Returns (version, vercode, package), any or all of which might be None.
 # All values returned are strings.
-def parse_androidmanifests(paths):
+def parse_androidmanifests(paths, ignoreversions=None):
 
     if not paths:
         return (None, None, None)
@@ -836,6 +856,8 @@ def parse_androidmanifests(paths):
     vnsearch_g = re.compile(r'.*versionName *=* *(["\'])((?:(?=(\\?))\3.)*?)\1.*').search
     psearch_g = re.compile(r'.*packageName *=* *["\']([^"]+)["\'].*').search
 
+    ignoresearch = re.compile(ignoreversions).search if ignoreversions else None
+
     max_version = None
     max_vercode = None
     max_package = None
@@ -871,14 +893,23 @@ def parse_androidmanifests(paths):
                 if matches:
                     vercode = matches.group(1)
 
-        # Better some package name than nothing
-        if max_package is None:
+        # Always grab the package name and version name in case they are not
+        # together with the highest version code
+        if max_package is None and package is not None:
             max_package = package
+        if max_version is None and version is not None:
+            max_version = version
 
         if max_vercode is None or (vercode is not None and vercode > max_vercode):
-            max_version = version
-            max_vercode = vercode
-            max_package = package
+            if not ignoresearch or not ignoresearch(version):
+                if version is not None:
+                    max_version = version
+                if vercode is not None:
+                    max_vercode = vercode
+                if package is not None:
+                    max_package = package
+            else:
+                max_version = "Ignore"
 
     if max_version is None:
         max_version = "Unknown"
@@ -902,7 +933,7 @@ class BuildException(Exception):
         return ret
 
     def __str__(self):
-        ret = repr(self.value)
+        ret = self.value
         if self.detail:
             ret += "\n==== detail begin ====\n%s\n==== detail end ====" % self.detail.strip()
         return ret
@@ -913,7 +944,7 @@ class VCSException(Exception):
         self.value = value
 
     def __str__(self):
-        return repr(self.value)
+        return self.value
 
 
 # Get the specified source library.
@@ -935,12 +966,10 @@ def getsrclib(spec, srclib_dir, srclibpaths=[], subdir=None,
         if '/' in name:
             name, subdir = name.split('/', 1)
 
-    srclib_path = os.path.join('srclibs', name + ".txt")
-
-    if not os.path.exists(srclib_path):
+    if name not in metadata.srclibs:
         raise BuildException('srclib ' + name + ' not found.')
 
-    srclib = metadata.parse_srclib(srclib_path)
+    srclib = metadata.srclibs[name]
 
     sdir = os.path.join(srclib_dir, name)
 
@@ -1016,7 +1045,7 @@ def getsrclib(spec, srclib_dir, srclibpaths=[], subdir=None,
 def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=False):
 
     # Optionally, the actual app source can be in a subdirectory
-    if 'subdir' in build:
+    if build['subdir']:
         root_dir = os.path.join(build_dir, build['subdir'])
     else:
         root_dir = build_dir
@@ -1036,7 +1065,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
         raise BuildException('Missing subdir ' + root_dir)
 
     # Run an init command if one is required
-    if 'init' in build:
+    if build['init']:
         cmd = replace_config_vars(build['init'])
         logging.info("Running 'init' commands in %s" % root_dir)
 
@@ -1046,7 +1075,8 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
                                  (app['id'], build['version']), p.stdout)
 
     # Apply patches if any
-    if 'patch' in build:
+    if build['patch']:
+        logging.info("Applying patches")
         for patch in build['patch']:
             patch = patch.strip()
             logging.info("Applying " + patch)
@@ -1057,7 +1087,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
 
     # Get required source libraries
     srclibpaths = []
-    if 'srclibs' in build:
+    if build['srclibs']:
         logging.info("Collecting source libraries")
         for lib in build['srclibs']:
             srclibpaths.append(getsrclib(lib, srclib_dir, srclibpaths,
@@ -1073,7 +1103,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
 
     # Update the local.properties file
     localprops = [os.path.join(build_dir, 'local.properties')]
-    if 'subdir' in build:
+    if build['subdir']:
         localprops += [os.path.join(root_dir, 'local.properties')]
     for path in localprops:
         if not os.path.isfile(path):
@@ -1097,7 +1127,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
             props += "ndk.dir=%s\n" % config['ndk_path']
             props += "ndk-location=%s\n" % config['ndk_path']
         # Add java.encoding if necessary
-        if 'encoding' in build:
+        if build['encoding']:
             props += "java.encoding=%s\n" % build['encoding']
         f = open(path, 'w')
         f.write(props)
@@ -1109,7 +1139,42 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
         if flavour in ['main', 'yes', '']:
             flavour = None
 
-        if 'target' in build:
+        version_regex = re.compile(r".*'com\.android\.tools\.build:gradle:([^\.]+\.[^\.]+).*'.*")
+        gradlepluginver = None
+
+        gradle_files = [os.path.join(root_dir, 'build.gradle')]
+
+        # Parent dir build.gradle
+        parent_dir = os.path.normpath(os.path.join(root_dir, '..'))
+        if parent_dir.startswith(build_dir):
+            gradle_files.append(os.path.join(parent_dir, 'build.gradle'))
+
+        # Gradle execution dir build.gradle
+        if '@' in build['gradle']:
+            gradle_file = os.path.join(root_dir, build['gradle'].split('@', 1)[1], 'build.gradle')
+            gradle_file = os.path.normpath(gradle_file)
+            if gradle_file not in gradle_files:
+                gradle_files.append(gradle_file)
+
+        for path in gradle_files:
+            if gradlepluginver:
+                break
+            if not os.path.isfile(path):
+                continue
+            with open(path) as f:
+                for line in f:
+                    match = version_regex.match(line)
+                    if match:
+                        gradlepluginver = match.group(1)
+                        break
+
+        if gradlepluginver:
+            build['gradlepluginver'] = LooseVersion(gradlepluginver)
+        else:
+            logging.warn("Could not fetch the gradle plugin version, defaulting to 0.11")
+            build['gradlepluginver'] = LooseVersion('0.11')
+
+        if build['target']:
             n = build["target"].split('-')[1]
             FDroidPopen(['sed', '-i',
                          's@compileSdkVersion *[0-9]*@compileSdkVersion ' + n + '@g',
@@ -1167,7 +1232,8 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
                     raise BuildException("Failed to amend build.gradle")
 
     # Delete unwanted files
-    if 'rm' in build:
+    if build['rm']:
+        logging.info("Removing specified files")
         for part in getpaths(build_dir, build, 'rm'):
             dest = os.path.join(build_dir, part)
             logging.info("Removing {0}".format(part))
@@ -1182,7 +1248,7 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
     remove_signing_keys(build_dir)
 
     # Add required external libraries
-    if 'extlibs' in build:
+    if build['extlibs']:
         logging.info("Collecting prebuilt libraries")
         libsdir = os.path.join(root_dir, 'libs')
         if not os.path.exists(libsdir):
@@ -1197,7 +1263,9 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
             shutil.copyfile(libsrc, os.path.join(libsdir, libf))
 
     # Run a pre-build command if one is required
-    if 'prebuild' in build:
+    if build['prebuild']:
+        logging.info("Running 'prebuild' commands in %s" % root_dir)
+
         cmd = replace_config_vars(build['prebuild'])
 
         # Substitute source library paths into prebuild commands
@@ -1205,27 +1273,24 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
             libpath = os.path.relpath(libpath, root_dir)
             cmd = cmd.replace('$$' + name + '$$', libpath)
 
-        logging.info("Running 'prebuild' commands in %s" % root_dir)
-
         p = FDroidPopen(['bash', '-x', '-c', cmd], cwd=root_dir)
         if p.returncode != 0:
             raise BuildException("Error running prebuild command for %s:%s" %
                                  (app['id'], build['version']), p.stdout)
 
-    updatemode = build.get('update', ['auto'])
     # Generate (or update) the ant build file, build.xml...
-    if updatemode != ['no'] and build['type'] == 'ant':
+    if build['update'] and build['update'] != ['no'] and build['type'] == 'ant':
         parms = [os.path.join(config['sdk_path'], 'tools', 'android'), 'update']
         lparms = parms + ['lib-project']
         parms = parms + ['project']
 
-        if 'target' in build and build['target']:
+        if build['target']:
             parms += ['-t', build['target']]
             lparms += ['-t', build['target']]
-        if updatemode == ['auto']:
+        if build['update'] == ['auto']:
             update_dirs = ant_subprojects(root_dir) + ['.']
         else:
-            update_dirs = updatemode
+            update_dirs = build['update']
 
         for d in update_dirs:
             subdir = os.path.join(root_dir, d)
@@ -1252,8 +1317,6 @@ def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=
 # Split and extend via globbing the paths from a field
 def getpaths(build_dir, build, field):
     paths = []
-    if field not in build:
-        return paths
     for p in build[field]:
         p = p.strip()
         full_path = os.path.join(build_dir, p)
@@ -1284,6 +1347,7 @@ def scan_source(build_dir, root_dir, thisbuild):
         re.compile(r'bugsense', re.IGNORECASE),
         re.compile(r'crashlytics', re.IGNORECASE),
         re.compile(r'ouya.*sdk', re.IGNORECASE),
+        re.compile(r'libspen23', re.IGNORECASE),
         ]
 
     scanignore = getpaths(build_dir, thisbuild, 'scanignore')
@@ -1391,8 +1455,8 @@ def scan_source(build_dir, root_dir, thisbuild):
     # indicate a problem (if it's not a problem, explicitly use
     # buildjni=no to bypass this check)
     if (os.path.exists(os.path.join(root_dir, 'jni')) and
-            thisbuild.get('buildjni') is None):
-        logging.warn('Found jni directory, but buildjni is not enabled')
+            not thisbuild['buildjni']):
+        logging.error('Found jni directory, but buildjni is not enabled')
         count += 1
 
     return count
@@ -1431,7 +1495,7 @@ class KnownApks:
     # Record an apk (if it's new, otherwise does nothing)
     # Returns the date it was added.
     def recordapk(self, apk, app):
-        if not apk in self.apks:
+        if apk not in self.apks:
             self.apks[apk] = (app, time.gmtime(time.time()))
             self.changed = True
         _, added = self.apks[apk]
@@ -1539,7 +1603,7 @@ def FDroidPopen(commands, cwd=None, shell=False, output=True):
     while not stdout_reader.eof():
         while not stdout_queue.empty():
             line = stdout_queue.get()
-            if output and options.verbose:
+            if output:
                 # Output directly to console
                 sys.stdout.write(line)
                 sys.stdout.flush()
@@ -1557,7 +1621,7 @@ def remove_signing_keys(build_dir):
     signing_configs = re.compile(r'^[\t ]*signingConfigs[ \t]*{[ \t]*$')
     line_matches = [
         re.compile(r'^[\t ]*signingConfig [^ ]*$'),
-        re.compile(r'.*android\.signingConfigs\..*'),
+        re.compile(r'.*android\.signingConfigs\.[^{]*$'),
         re.compile(r'.*variant\.outputFile = .*'),
         re.compile(r'.*\.readLine\(.*'),
         ]