chiark / gitweb /
Properly default to NDK r10e
[fdroidserver.git] / fdroidserver / metadata.py
index 6ed8410529dbb5468005188983d2a51efa2cf8b0..09a5d07f6a9e0211b094516e781b5b7091755e39 100644 (file)
@@ -25,10 +25,13 @@ import logging
 
 from collections import OrderedDict
 
+import common
+
 srclibs = None
 
 
 class MetaDataException(Exception):
+
     def __init__(self, value):
         self.value = value
 
@@ -45,6 +48,7 @@ app_defaults = OrderedDict([
     ('Web Site', ''),
     ('Source Code', ''),
     ('Issue Tracker', ''),
+    ('Changelog', ''),
     ('Donate', None),
     ('FlattrID', None),
     ('Bitcoin', None),
@@ -57,6 +61,7 @@ app_defaults = OrderedDict([
     ('Requires Root', False),
     ('Repo Type', ''),
     ('Repo', ''),
+    ('Binaries', None),
     ('Maintainer Notes', []),
     ('Archive Policy', None),
     ('Auto Update Mode', 'None'),
@@ -68,7 +73,7 @@ app_defaults = OrderedDict([
     ('Current Version', ''),
     ('Current Version Code', '0'),
     ('No Source Since', ''),
-    ])
+])
 
 
 # In the order in which they are laid out on files
@@ -98,10 +103,11 @@ flag_defaults = OrderedDict([
     ('scandelete', []),
     ('build', ''),
     ('buildjni', []),
+    ('ndk', 'r10e'),  # defaults to latest
     ('preassemble', []),
-    ('antcommand', None),
+    ('antcommands', None),
     ('novcheck', False),
-    ])
+])
 
 
 # Designates a metadata field type and checks that it matches
@@ -154,12 +160,17 @@ class FieldValidator():
 valuetypes = {
     FieldValidator("Integer",
                    r'^[1-9][0-9]*$', None,
-                   ['FlattrID'],
+                   [],
                    ['vercode']),
 
+    FieldValidator("Hexadecimal",
+                   r'^[0-9a-f]+$', None,
+                   ['FlattrID'],
+                   []),
+
     FieldValidator("HTTP link",
                    r'^http[s]?://', None,
-                   ["Web Site", "Source Code", "Issue Tracker", "Donate"], []),
+                   ["Web Site", "Source Code", "Issue Tracker", "Changelog", "Donate"], []),
 
     FieldValidator("Bitcoin address",
                    r'^[a-zA-Z0-9]{27,34}$', None,
@@ -192,6 +203,11 @@ valuetypes = {
                    ["Repo Type"],
                    []),
 
+    FieldValidator("Binaries",
+                   r'^http[s]?://', None,
+                   ["Binaries"],
+                   []),
+
     FieldValidator("Archive Policy",
                    r'^[0-9]+ versions$', None,
                    ["Archive Policy"],
@@ -211,7 +227,7 @@ valuetypes = {
                    r"^(Tags|Tags .+|RepoManifest|RepoManifest/.+|RepoTrunk|HTTP|Static|None)$", None,
                    ["Update Check Mode"],
                    [])
-    }
+}
 
 
 # Check an app's metadata information for integrity errors
@@ -226,7 +242,7 @@ def check_metadata(info):
 
 # Formatter for descriptions. Create an instance, and call parseline() with
 # each line of the description source from the metadata. At the end, call
-# end() and then text_plain, text_wiki and text_html will contain the result.
+# end() and then text_wiki and text_html will contain the result.
 class DescriptionFormatter:
     stNONE = 0
     stPARA = 1
@@ -235,7 +251,6 @@ class DescriptionFormatter:
     bold = False
     ital = False
     state = stNONE
-    text_plain = ''
     text_wiki = ''
     text_html = ''
     linkResolver = None
@@ -254,7 +269,6 @@ class DescriptionFormatter:
             self.endol()
 
     def endpara(self):
-        self.text_plain += '\n'
         self.text_html += '</p>'
         self.state = self.stNONE
 
@@ -334,7 +348,6 @@ class DescriptionFormatter:
 
     def addtext(self, txt):
         p, h = self.linkify(txt)
-        self.text_plain += p
         self.text_html += h
 
     def parseline(self, line):
@@ -347,7 +360,6 @@ class DescriptionFormatter:
                 self.text_html += '<ul>'
                 self.state = self.stUL
             self.text_html += '<li>'
-            self.text_plain += '* '
             self.addtext(line[1:])
             self.text_html += '</li>'
         elif line.startswith('# '):
@@ -356,7 +368,6 @@ class DescriptionFormatter:
                 self.text_html += '<ol>'
                 self.state = self.stOL
             self.text_html += '<li>'
-            self.text_plain += '* '  # TODO: lazy - put the numbers in!
             self.addtext(line[1:])
             self.text_html += '</li>'
         else:
@@ -366,23 +377,12 @@ class DescriptionFormatter:
                 self.state = self.stPARA
             elif self.state == self.stPARA:
                 self.text_html += ' '
-                self.text_plain += ' '
             self.addtext(line)
 
     def end(self):
         self.endcur()
 
 
-# Parse multiple lines of description as written in a metadata file, returning
-# a single string in plain text format.
-def description_plain(lines, linkres):
-    ps = DescriptionFormatter(linkres)
-    for line in lines:
-        ps.parseline(line)
-    ps.end()
-    return ps.text_plain
-
-
 # Parse multiple lines of description as written in a metadata file, returning
 # a single string in wiki format. Used for the Maintainer Notes field as well,
 # because it's the same format.
@@ -415,7 +415,6 @@ def parse_srclib(metafile):
     thisinfo['Repo'] = ''
     thisinfo['Subdir'] = None
     thisinfo['Prepare'] = None
-    thisinfo['Srclibs'] = None
 
     if metafile is None:
         return thisinfo
@@ -475,30 +474,30 @@ def read_metadata(xref=True):
     # their source repository.
     read_srclibs()
 
-    apps = []
+    apps = {}
 
     for basedir in ('metadata', 'tmp'):
         if not os.path.exists(basedir):
             os.makedirs(basedir)
 
     for metafile in sorted(glob.glob(os.path.join('metadata', '*.txt'))):
-        appinfo = parse_metadata(metafile)
+        appid, appinfo = parse_metadata(metafile)
         check_metadata(appinfo)
-        apps.append(appinfo)
+        apps[appid] = appinfo
 
     if xref:
         # Parse all descriptions at load time, just to ensure cross-referencing
         # errors are caught early rather than when they hit the build server.
-        def linkres(link):
-            for app in apps:
-                if app['id'] == link:
-                    return ("fdroid.app:" + link, "Dummy name - don't know yet")
-            raise MetaDataException("Cannot resolve app id " + link)
-        for app in apps:
+        def linkres(appid):
+            if appid in apps:
+                return ("fdroid.app:" + appid, "Dummy name - don't know yet")
+            raise MetaDataException("Cannot resolve app id " + appid)
+
+        for appid, app in apps.iteritems():
             try:
                 description_html(app['Description'], linkres)
-            except Exception, e:
-                raise MetaDataException("Problem with description of " + app['id'] +
+            except MetaDataException, e:
+                raise MetaDataException("Problem with description of " + appid +
                                         " - " + str(e))
 
     return apps
@@ -522,8 +521,8 @@ def metafieldtype(name):
 
 
 def flagtype(name):
-    if name in ['extlibs', 'srclibs', 'patch', 'rm', 'buildjni',
-                'update', 'scanignore', 'scandelete']:
+    if name in ['extlibs', 'srclibs', 'patch', 'rm', 'buildjni', 'preassemble',
+                'update', 'scanignore', 'scandelete', 'gradle', 'antcommands']:
         return 'list'
     if name in ['init', 'prebuild', 'build']:
         return 'script'
@@ -548,6 +547,13 @@ def fill_build_defaults(build):
             continue
         build[flag] = value
     build['type'] = get_build_type()
+    build['ndk_path'] = common.get_ndk_path(build['ndk'])
+
+
+def split_list_values(s):
+    # Port legacy ';' separators
+    l = [v.strip() for v in s.replace(';', ',').split(',')]
+    return [v for v in l if v]
 
 
 # Parse metadata for a single application.
@@ -563,7 +569,6 @@ def fill_build_defaults(build):
 #
 # Known keys not originating from the metadata are:
 #
-#  'id'               - the application's package ID
 #  'builds'           - a list of dictionaries containing build information
 #                       for each defined build
 #  'comments'         - a list of comments from the metadata file. Each is
@@ -577,9 +582,13 @@ def fill_build_defaults(build):
 #
 def parse_metadata(metafile):
 
+    appid = None
     linedesc = None
 
     def add_buildflag(p, thisbuild):
+        if not p.strip():
+            raise MetaDataException("Empty build flag at {1}"
+                                    .format(buildlines[0], linedesc))
         bv = p.split('=', 1)
         if len(bv) != 2:
             raise MetaDataException("Invalid build flag at {0} in {1}"
@@ -595,8 +604,11 @@ def parse_metadata(metafile):
                                     .format(p, linedesc))
         t = flagtype(pk)
         if t == 'list':
-            # Port legacy ';' separators
-            thisbuild[pk] = [v.strip() for v in pv.replace(';', ',').split(',')]
+            pv = split_list_values(pv)
+            if pk == 'gradle':
+                if len(pv) == 1 and pv[0] in ['main', 'yes']:
+                    pv = ['yes']
+            thisbuild[pk] = pv
         elif t == 'string' or t == 'script':
             thisbuild[pk] = pv
         elif t == 'bool':
@@ -649,18 +661,17 @@ def parse_metadata(metafile):
     if metafile:
         if not isinstance(metafile, file):
             metafile = open(metafile, "r")
-        thisinfo['id'] = metafile.name[9:-4]
-    else:
-        thisinfo['id'] = None
+        appid = metafile.name[9:-4]
 
     thisinfo.update(app_defaults)
+    thisinfo['id'] = appid
 
     # General defaults...
     thisinfo['builds'] = []
     thisinfo['comments'] = []
 
     if metafile is None:
-        return thisinfo
+        return appid, thisinfo
 
     mode = 0
     buildlines = []
@@ -675,7 +686,8 @@ def parse_metadata(metafile):
         line = line.rstrip('\r\n')
         if mode == 3:
             if not any(line.startswith(s) for s in (' ', '\t')):
-                if 'commit' not in curbuild and 'disable' not in curbuild:
+                commit = curbuild['commit'] if 'commit' in curbuild else None
+                if not commit and 'disable' not in curbuild:
                     raise MetaDataException("No commit specified for {0} in {1}"
                                             .format(curbuild['version'], linedesc))
 
@@ -721,7 +733,7 @@ def parse_metadata(metafile):
             elif fieldtype == 'string':
                 thisinfo[field] = value
             elif fieldtype == 'list':
-                thisinfo[field] = [v.strip() for v in value.replace(';', ',').split(',')]
+                thisinfo[field] = split_list_values(value)
             elif fieldtype == 'build':
                 if value.endswith("\\"):
                     mode = 2
@@ -778,7 +790,9 @@ def parse_metadata(metafile):
     for build in thisinfo['builds']:
         fill_build_defaults(build)
 
-    return thisinfo
+    thisinfo['builds'] = sorted(thisinfo['builds'], key=lambda build: int(build['vercode']))
+
+    return (appid, thisinfo)
 
 
 # Write a metadata file.
@@ -805,33 +819,30 @@ def write_metadata(dest, app):
             value = ','.join(value)
         mf.write("%s:%s\n" % (field, value))
 
+    def writefield_nonempty(field, value=None):
+        if value is None:
+            value = app[field]
+        if value:
+            writefield(field, value)
+
     mf = open(dest, 'w')
-    if app['Disabled']:
-        writefield('Disabled')
-    if app['AntiFeatures']:
-        writefield('AntiFeatures')
-    if app['Provides']:
-        writefield('Provides')
+    writefield_nonempty('Disabled')
+    writefield_nonempty('AntiFeatures')
+    writefield_nonempty('Provides')
     writefield('Categories')
     writefield('License')
     writefield('Web Site')
     writefield('Source Code')
     writefield('Issue Tracker')
-    if app['Donate']:
-        writefield('Donate')
-    if app['FlattrID']:
-        writefield('FlattrID')
-    if app['Bitcoin']:
-        writefield('Bitcoin')
-    if app['Litecoin']:
-        writefield('Litecoin')
-    if app['Dogecoin']:
-        writefield('Dogecoin')
+    writefield_nonempty('Changelog')
+    writefield_nonempty('Donate')
+    writefield_nonempty('FlattrID')
+    writefield_nonempty('Bitcoin')
+    writefield_nonempty('Litecoin')
+    writefield_nonempty('Dogecoin')
     mf.write('\n')
-    if app['Name']:
-        writefield('Name')
-    if app['Auto Name']:
-        writefield('Auto Name')
+    writefield_nonempty('Name')
+    writefield_nonempty('Auto Name')
     writefield('Summary')
     writefield('Description', '')
     for line in app['Description']:
@@ -844,6 +855,8 @@ def write_metadata(dest, app):
     if app['Repo Type']:
         writefield('Repo Type')
         writefield('Repo')
+        if app['Binaries']:
+            writefield('Binaries')
         mf.write('\n')
     for build in app['builds']:
 
@@ -891,18 +904,13 @@ def write_metadata(dest, app):
         mf.write('.\n')
         mf.write('\n')
 
-    if app['Archive Policy']:
-        writefield('Archive Policy')
+    writefield_nonempty('Archive Policy')
     writefield('Auto Update Mode')
     writefield('Update Check Mode')
-    if app['Update Check Ignore']:
-        writefield('Update Check Ignore')
-    if app['Vercode Operation']:
-        writefield('Vercode Operation')
-    if app['Update Check Name']:
-        writefield('Update Check Name')
-    if app['Update Check Data']:
-        writefield('Update Check Data')
+    writefield_nonempty('Update Check Ignore')
+    writefield_nonempty('Vercode Operation')
+    writefield_nonempty('Update Check Name')
+    writefield_nonempty('Update Check Data')
     if app['Current Version']:
         writefield('Current Version')
         writefield('Current Version Code')