# -*- coding: utf-8 -*-
#
-# common.py - part of the FDroid server tools
+# metadata.py - part of the FDroid server tools
# Copyright (C) 2013, Ciaran Gultnieks, ciaran@ciarang.com
-# Copyright (C) 2013 Daniel Martà <mvdan@mvdan.cc>
+# Copyright (C) 2013-2014 Daniel Martà <mvdan@mvdan.cc>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import os, re, glob
+import os
+import re
+import glob
import cgi
+import logging
+
+from collections import OrderedDict
+
+import common
+
+srclibs = None
+
class MetaDataException(Exception):
+
def __init__(self, value):
self.value = value
def __str__(self):
- return repr(self.value)
+ return self.value
+
+# In the order in which they are laid out on files
+app_defaults = OrderedDict([
+ ('Disabled', None),
+ ('AntiFeatures', None),
+ ('Provides', None),
+ ('Categories', ['None']),
+ ('License', 'Unknown'),
+ ('Web Site', ''),
+ ('Source Code', ''),
+ ('Issue Tracker', ''),
+ ('Changelog', ''),
+ ('Donate', None),
+ ('FlattrID', None),
+ ('Bitcoin', None),
+ ('Litecoin', None),
+ ('Dogecoin', None),
+ ('Name', None),
+ ('Auto Name', ''),
+ ('Summary', ''),
+ ('Description', []),
+ ('Requires Root', False),
+ ('Repo Type', ''),
+ ('Repo', ''),
+ ('Binaries', None),
+ ('Maintainer Notes', []),
+ ('Archive Policy', None),
+ ('Auto Update Mode', 'None'),
+ ('Update Check Mode', 'None'),
+ ('Update Check Ignore', None),
+ ('Vercode Operation', None),
+ ('Update Check Name', None),
+ ('Update Check Data', None),
+ ('Current Version', ''),
+ ('Current Version Code', '0'),
+ ('No Source Since', ''),
+])
+
+
+# In the order in which they are laid out on files
+# Sorted by their action and their place in the build timeline
+flag_defaults = OrderedDict([
+ ('disable', False),
+ ('commit', None),
+ ('subdir', None),
+ ('submodules', False),
+ ('init', ''),
+ ('patch', []),
+ ('gradle', False),
+ ('maven', False),
+ ('kivy', False),
+ ('output', None),
+ ('srclibs', []),
+ ('oldsdkloc', False),
+ ('encoding', None),
+ ('forceversion', False),
+ ('forcevercode', False),
+ ('rm', []),
+ ('extlibs', []),
+ ('prebuild', ''),
+ ('update', ['auto']),
+ ('target', None),
+ ('scanignore', []),
+ ('scandelete', []),
+ ('build', ''),
+ ('buildjni', []),
+ ('ndk', 'r10e'), # defaults to latest
+ ('preassemble', []),
+ ('antcommands', None),
+ ('novcheck', False),
+])
+
# Designates a metadata field type and checks that it matches
#
# 'fields' - Metadata fields (Field:Value) of this type
# 'attrs' - Build attributes (attr=value) of this type
#
-class FieldType():
+class FieldValidator():
+
def __init__(self, name, matching, sep, fields, attrs):
self.name = name
self.matching = matching
for v in values:
if not self.compiled.match(v):
raise MetaDataException("'%s' is not a valid %s in %s. "
- % (v, self.name, appid) +
- "Regex pattern: %s" % (self.matching))
+ % (v, self.name, appid) +
+ "Regex pattern: %s" % (self.matching))
def _assert_list(self, values, appid):
for v in values:
if v not in self.matching:
raise MetaDataException("'%s' is not a valid %s in %s. "
- % (v, self.name, appid) +
- "Possible values: %s" % (", ".join(self.matching)))
+ % (v, self.name, appid) +
+ "Possible values: %s" % (", ".join(self.matching)))
def check(self, value, appid):
if type(value) is not str or not value:
# Generic value types
valuetypes = {
- 'int' : FieldType("Integer",
- r'^[1-9][0-9]*$', None,
- [ 'FlattrID' ],
- [ 'vercode' ]),
-
- 'http' : FieldType("HTTP link",
- r'^http[s]?://', None,
- [ "Web Site", "Source Code", "Issue Tracker", "Donate" ], []),
-
- 'bitcoin' : FieldType("Bitcoin address",
- r'^[a-zA-Z0-9]{27,34}$', None,
- [ "Bitcoin" ],
- [ ]),
-
- 'litecoin' : FieldType("Litecoin address",
- r'^L[a-zA-Z0-9]{33}$', None,
- [ "Litecoin" ],
- [ ]),
-
- 'dogecoin' : FieldType("Dogecoin address",
- r'^D[a-zA-Z0-9]{33}$', None,
- [ "Dogecoin" ],
- [ ]),
-
- 'Bool' : FieldType("Boolean",
- ['Yes', 'No'], None,
- [ "Requires Root" ],
- [ ]),
-
- 'bool' : FieldType("Boolean",
- ['yes', 'no'], None,
- [ ],
- [ 'submodules', 'oldsdkloc', 'forceversion', 'forcevercode',
- 'fixtrans', 'fixapos', 'novcheck' ]),
-
- 'Repo Type' : FieldType("Repo Type",
- [ 'git', 'git-svn', 'svn', 'hg', 'bzr', 'srclib' ], None,
- [ "Repo Type" ],
- [ ]),
-
- 'archive' : FieldType("Archive Policy",
- r'^[0-9]+ versions$', None,
- [ "Archive Policy" ],
- [ ]),
-
- 'antifeatures' : FieldType("Anti-Feature",
- [ "Ads", "Tracking", "NonFreeNet", "NonFreeDep", "NonFreeAdd", "UpstreamNonFree" ], ',',
- [ "AntiFeatures" ],
- [ ]),
-
- 'autoupdatemodes' : FieldType("Auto Update Mode",
- r"^(Version .+|None)$", None,
- [ "Auto Update Mode" ],
- [ ]),
-
- 'updatecheckmodes' : FieldType("Update Check Mode",
- r"^(Tags|RepoManifest|RepoManifest/.+|RepoTrunk|HTTP|Static|None)$", None,
- [ "Update Check Mode" ],
- [ ])
+ FieldValidator("Integer",
+ r'^[1-9][0-9]*$', None,
+ [],
+ ['vercode']),
+
+ FieldValidator("Hexadecimal",
+ r'^[0-9a-f]+$', None,
+ ['FlattrID'],
+ []),
+
+ FieldValidator("HTTP link",
+ r'^http[s]?://', None,
+ ["Web Site", "Source Code", "Issue Tracker", "Changelog", "Donate"], []),
+
+ FieldValidator("Bitcoin address",
+ r'^[a-zA-Z0-9]{27,34}$', None,
+ ["Bitcoin"],
+ []),
+
+ FieldValidator("Litecoin address",
+ r'^L[a-zA-Z0-9]{33}$', None,
+ ["Litecoin"],
+ []),
+
+ FieldValidator("Dogecoin address",
+ r'^D[a-zA-Z0-9]{33}$', None,
+ ["Dogecoin"],
+ []),
+
+ FieldValidator("Boolean",
+ ['Yes', 'No'], None,
+ ["Requires Root"],
+ []),
+
+ FieldValidator("bool",
+ ['yes', 'no'], None,
+ [],
+ ['submodules', 'oldsdkloc', 'forceversion', 'forcevercode',
+ 'novcheck']),
+
+ FieldValidator("Repo Type",
+ ['git', 'git-svn', 'svn', 'hg', 'bzr', 'srclib'], None,
+ ["Repo Type"],
+ []),
+
+ FieldValidator("Binaries",
+ r'^http[s]?://', None,
+ ["Binaries"],
+ []),
+
+ FieldValidator("Archive Policy",
+ r'^[0-9]+ versions$', None,
+ ["Archive Policy"],
+ []),
+
+ FieldValidator("Anti-Feature",
+ ["Ads", "Tracking", "NonFreeNet", "NonFreeDep", "NonFreeAdd", "UpstreamNonFree"], ',',
+ ["AntiFeatures"],
+ []),
+
+ FieldValidator("Auto Update Mode",
+ r"^(Version .+|None)$", None,
+ ["Auto Update Mode"],
+ []),
+
+ FieldValidator("Update Check Mode",
+ r"^(Tags|Tags .+|RepoManifest|RepoManifest/.+|RepoTrunk|HTTP|Static|None)$", None,
+ ["Update Check Mode"],
+ [])
}
+
# Check an app's metadata information for integrity errors
def check_metadata(info):
- for k, t in valuetypes.iteritems():
- for field in t.fields:
- if field in info:
- t.check(info[field], info['id'])
- if k == 'Bool':
- info[field] = info[field] == "Yes"
+ for v in valuetypes:
+ for field in v.fields:
+ v.check(info[field], info['id'])
for build in info['builds']:
- for attr in t.attrs:
- if attr in build:
- t.check(build[attr], info['id'])
- if k == 'bool':
- build[attr] = build[attr] == "yes"
- elif k == 'bool':
- build[attr] = False
+ for attr in v.attrs:
+ v.check(build[attr], info['id'])
+
# 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
bold = False
ital = False
state = stNONE
- text_plain = ''
text_wiki = ''
text_html = ''
linkResolver = None
+
def __init__(self, linkres):
self.linkResolver = linkres
+
def endcur(self, notstates=None):
if notstates and self.state in notstates:
return
self.endul()
elif self.state == self.stOL:
self.endol()
+
def endpara(self):
- self.text_plain += '\n'
self.text_html += '</p>'
self.state = self.stNONE
+
def endul(self):
self.text_html += '</ul>'
self.state = self.stNONE
+
def endol(self):
self.text_html += '</ol>'
self.state = self.stNONE
self.ital = not self.ital
txt = txt[2:]
-
def linkify(self, txt):
linkified_plain = ''
linkified_html = ''
urltext = url
linkified_html += '<a href="' + url + '">' + cgi.escape(urltext) + '</a>'
linkified_plain += urltext
- txt = txt[index+2:]
+ txt = txt[index + 2:]
else:
index = txt.find("]")
if index == -1:
linkified_plain += urltxt
if urltxt != url:
linkified_plain += ' (' + url + ')'
- txt = txt[index+1:]
+ txt = txt[index + 1:]
def addtext(self, txt):
p, h = self.linkify(txt)
- self.text_plain += p
self.text_html += h
def parseline(self, line):
self.text_wiki += "%s\n" % line
if not line:
self.endcur()
- elif line.startswith('*'):
+ elif line.startswith('* '):
self.endcur([self.stUL])
if self.state != self.stUL:
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('#'):
+ elif line.startswith('# '):
self.endcur([self.stOL])
if self.state != self.stOL:
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:
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,
ps.end()
return ps.text_wiki
+
# Parse multiple lines of description as written in a metadata file, returning
# a single string in HTML format.
-def description_html(lines,linkres):
+def description_html(lines, linkres):
ps = DescriptionFormatter(linkres)
for line in lines:
ps.parseline(line)
ps.end()
return ps.text_html
-def parse_srclib(metafile, **kw):
+
+def parse_srclib(metafile):
thisinfo = {}
if metafile and not isinstance(metafile, file):
thisinfo['Repo'] = ''
thisinfo['Subdir'] = None
thisinfo['Prepare'] = None
- thisinfo['Srclibs'] = None
- thisinfo['Update Project'] = None
if metafile is None:
return thisinfo
+ n = 0
for line in metafile:
+ n += 1
line = line.rstrip('\r\n')
if not line or line.startswith("#"):
continue
try:
- field, value = line.split(':',1)
+ field, value = line.split(':', 1)
except ValueError:
- raise MetaDataException("Invalid metadata in " + metafile.name + " at: " + line)
+ raise MetaDataException("Invalid metadata in %s:%d" % (line, n))
if field == "Subdir":
thisinfo[field] = value.split(',')
return thisinfo
+
+def read_srclibs():
+ """Read all srclib metadata.
+
+ The information read will be accessible as metadata.srclibs, which is a
+ dictionary, keyed on srclib name, with the values each being a dictionary
+ in the same format as that returned by the parse_srclib function.
+
+ A MetaDataException is raised if there are any problems with the srclib
+ metadata.
+ """
+ global srclibs
+
+ # They were already loaded
+ if srclibs is not None:
+ return
+
+ srclibs = {}
+
+ srcdir = 'srclibs'
+ if not os.path.exists(srcdir):
+ os.makedirs(srcdir)
+
+ for metafile in sorted(glob.glob(os.path.join(srcdir, '*.txt'))):
+ srclibname = os.path.basename(metafile[:-4])
+ srclibs[srclibname] = parse_srclib(metafile)
+
+
# Read all metadata. Returns a list of 'app' objects (which are dictionaries as
# returned by the parse_metadata function.
-def read_metadata(xref=True, package=None):
- apps = []
+def read_metadata(xref=True):
+
+ # Always read the srclibs before the apps, since they can use a srlib as
+ # their source repository.
+ read_srclibs()
+
+ 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'))):
- if package is None or metafile == os.path.join('metadata', package + '.txt'):
- try:
- appinfo = parse_metadata(metafile)
- except Exception, e:
- raise MetaDataException("Problem reading metadata file %s: - %s" % (metafile, str(e)))
- check_metadata(appinfo)
- apps.append(appinfo)
+ appid, appinfo = parse_metadata(metafile)
+ check_metadata(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'] +
- " - " + str(e))
+ except MetaDataException, e:
+ raise MetaDataException("Problem with description of " + appid +
+ " - " + str(e))
return apps
+
# Get the type expected for a given metadata field.
def metafieldtype(name):
if name in ['Description', 'Maintainer Notes']:
return 'multiline'
+ if name in ['Categories']:
+ return 'list'
if name == 'Build Version':
return 'build'
if name == 'Build':
return 'buildv2'
if name == 'Use Built':
return 'obsolete'
+ if name not in app_defaults:
+ return 'unknown'
return 'string'
+
+def flagtype(name):
+ if name in ['extlibs', 'srclibs', 'patch', 'rm', 'buildjni', 'preassemble',
+ 'update', 'scanignore', 'scandelete', 'gradle', 'antcommands']:
+ return 'list'
+ if name in ['init', 'prebuild', 'build']:
+ return 'script'
+ if name in ['submodules', 'oldsdkloc', 'forceversion', 'forcevercode',
+ 'novcheck']:
+ return 'bool'
+ return 'string'
+
+
+def fill_build_defaults(build):
+
+ def get_build_type():
+ for t in ['maven', 'gradle', 'kivy']:
+ if build[t]:
+ return t
+ if build['output']:
+ return 'raw'
+ return 'ant'
+
+ for flag, value in flag_defaults.iteritems():
+ if flag in 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.
#
# 'metafile' - the filename to read. The package id for the application comes
#
# 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
#
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}"
+ .format(buildlines[0], linedesc))
+ pk, pv = bv
+ if pk in thisbuild:
+ raise MetaDataException("Duplicate definition on {0} in version {1} of {2}"
+ .format(pk, thisbuild['version'], linedesc))
+
+ pk = pk.lstrip()
+ if pk not in flag_defaults:
+ raise MetaDataException("Unrecognised build flag at {0} in {1}"
+ .format(p, linedesc))
+ t = flagtype(pk)
+ if t == 'list':
+ 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':
+ value = pv == 'yes'
+ if value:
+ thisbuild[pk] = True
+ else:
+ logging.debug("...ignoring bool flag %s" % p)
+
+ else:
+ raise MetaDataException("Unrecognised build flag type '%s' at %s in %s"
+ % (t, p, linedesc))
+
def parse_buildline(lines):
value = "".join(lines)
parts = [p.replace("\\,", ",")
commit = 'unknown - see disabled'
index = parts[2].rfind('at ')
if index != -1:
- commit = parts[2][index+3:]
+ commit = parts[2][index + 3:]
if commit.endswith(')'):
commit = commit[:-1]
thisbuild['commit'] = commit
else:
thisbuild['commit'] = parts[2]
for p in parts[3:]:
- pk, pv = p.split('=', 1)
- thisbuild[pk.strip()] = pv
+ add_buildflag(p, thisbuild)
return thisbuild
thisinfo['comments'].append((key, comment))
del curcomments[:]
- def get_build_type(build):
- for t in ['maven', 'gradle', 'kivy']:
- if build.get(t, 'no') != 'no':
- return t
- return 'ant'
-
thisinfo = {}
if metafile:
if not isinstance(metafile, file):
metafile = open(metafile, "r")
- thisinfo['id'] = metafile.name[9:-4]
- else:
- thisinfo['id'] = None
-
- # Defaults for fields that come from metadata...
- thisinfo['Name'] = None
- thisinfo['Provides'] = None
- thisinfo['Auto Name'] = ''
- thisinfo['Categories'] = 'None'
- thisinfo['Description'] = []
- thisinfo['Summary'] = ''
- thisinfo['License'] = 'Unknown'
- thisinfo['Web Site'] = ''
- thisinfo['Source Code'] = ''
- thisinfo['Issue Tracker'] = ''
- thisinfo['Donate'] = None
- thisinfo['FlattrID'] = None
- thisinfo['Bitcoin'] = None
- thisinfo['Litecoin'] = None
- thisinfo['Dogecoin'] = None
- thisinfo['Disabled'] = None
- thisinfo['AntiFeatures'] = None
- thisinfo['Archive Policy'] = None
- thisinfo['Update Check Mode'] = 'None'
- thisinfo['Vercode Operation'] = None
- thisinfo['Auto Update Mode'] = 'None'
- thisinfo['Current Version'] = ''
- thisinfo['Current Version Code'] = '0'
- thisinfo['Repo Type'] = ''
- thisinfo['Repo'] = ''
- thisinfo['Requires Root'] = False
- thisinfo['No Source Since'] = ''
+ 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 = []
curcomments = []
curbuild = None
+ vc_seen = {}
+ c = 0
for line in metafile:
+ c += 1
+ linedesc = "%s:%d" % (metafile.name, c)
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:
- raise MetaDataException("No commit specified for {0} in {1}".format(
- curbuild['version'], metafile.name))
+ 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))
+
thisinfo['builds'].append(curbuild)
- add_comments('build:' + curbuild['version'])
+ add_comments('build:' + curbuild['vercode'])
mode = 0
else:
if line.endswith('\\'):
else:
buildlines.append(line.lstrip())
bl = ''.join(buildlines)
- bv = bl.split('=', 1)
- if len(bv) != 2:
- raise MetaDataException("Invalid build flag at {0} in {1}".
- format(buildlines[0], metafile.name))
- name, val = bv
- if name in curbuild:
- raise MetaDataException("Duplicate definition on {0} in version {1} of {2}".
- format(name, curbuild['version'], metafile.name))
- curbuild[name] = val.lstrip()
+ add_buildflag(bl, curbuild)
buildlines = []
if mode == 0:
curcomments.append(line)
continue
try:
- field, value = line.split(':',1)
+ field, value = line.split(':', 1)
except ValueError:
- raise MetaDataException("Invalid metadata in " + metafile.name + " at: " + line)
+ raise MetaDataException("Invalid metadata in " + linedesc)
+ if field != field.strip() or value != value.strip():
+ raise MetaDataException("Extra spacing found in " + linedesc)
# Translate obsolete fields...
if field == 'Market Version':
mode = 1
thisinfo[field] = []
if value:
- raise MetaDataException("Unexpected text on same line as " + field + " in " + metafile.name)
+ raise MetaDataException("Unexpected text on same line as " + field + " in " + linedesc)
elif fieldtype == 'string':
- if field == 'Category' and thisinfo['Categories'] == 'None':
- thisinfo['Categories'] = value.replace(';',',')
thisinfo[field] = value
+ elif fieldtype == 'list':
+ thisinfo[field] = split_list_values(value)
elif fieldtype == 'build':
if value.endswith("\\"):
mode = 2
buildlines = [value[:-1]]
else:
- thisinfo['builds'].append(parse_buildline([value]))
- add_comments('build:' + thisinfo['builds'][-1]['version'])
+ curbuild = parse_buildline([value])
+ thisinfo['builds'].append(curbuild)
+ add_comments('build:' + thisinfo['builds'][-1]['vercode'])
elif fieldtype == 'buildv2':
curbuild = {}
vv = value.split(',')
if len(vv) != 2:
- raise MetaDataException('Build should have comma-separated version and vercode, not "{0}", in {1}'.
- format(value, metafile.name))
+ raise MetaDataException('Build should have comma-separated version and vercode, not "{0}", in {1}'
+ .format(value, linedesc))
curbuild['version'] = vv[0]
curbuild['vercode'] = vv[1]
+ if curbuild['vercode'] in vc_seen:
+ raise MetaDataException('Duplicate build recipe found for vercode %s in %s' % (
+ curbuild['vercode'], linedesc))
+ vc_seen[curbuild['vercode']] = True
buildlines = []
mode = 3
elif fieldtype == 'obsolete':
pass # Just throw it away!
else:
- raise MetaDataException("Unrecognised field type for " + field + " in " + metafile.name)
+ raise MetaDataException("Unrecognised field type for " + field + " in " + linedesc)
elif mode == 1: # Multiline field
if line == '.':
mode = 0
buildlines.append(line[:-1])
else:
buildlines.append(line)
- thisinfo['builds'].append(
- parse_buildline(buildlines))
- add_comments('build:' + thisinfo['builds'][-1]['version'])
+ curbuild = parse_buildline(buildlines)
+ thisinfo['builds'].append(curbuild)
+ add_comments('build:' + thisinfo['builds'][-1]['vercode'])
mode = 0
add_comments(None)
thisinfo['Description'].append('No description available')
for build in thisinfo['builds']:
- build['type'] = get_build_type(build)
+ fill_build_defaults(build)
+
+ thisinfo['builds'] = sorted(thisinfo['builds'], key=lambda build: int(build['vercode']))
+
+ return (appid, thisinfo)
- return thisinfo
# Write a metadata file.
#
if pf == key:
mf.write("%s\n" % comment)
written += 1
- #if options.verbose and written > 0:
- #print "...writing comments for " + (key if key else 'EOF')
+ if written > 0:
+ logging.debug("...writing comments for " + (key or 'EOF'))
def writefield(field, value=None):
writecomments(field)
if value is None:
value = app[field]
+ t = metafieldtype(field)
+ if t == 'list':
+ 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']:
if app['Repo Type']:
writefield('Repo Type')
writefield('Repo')
+ if app['Binaries']:
+ writefield('Binaries')
mf.write('\n')
for build in app['builds']:
- writecomments('build:' + build['version'])
- mf.write("Build:%s,%s\n" % ( build['version'], build['vercode']))
-
- # This defines the preferred order for the build items - as in the
- # manual, they're roughly in order of application.
- keyorder = ['disable', 'commit', 'subdir', 'submodules', 'init',
- 'gradle', 'maven', 'oldsdkloc', 'target',
- 'update', 'encoding', 'forceversion', 'forcevercode', 'rm',
- 'fixtrans', 'fixapos', 'extlibs', 'srclibs', 'patch',
- 'prebuild', 'scanignore', 'scandelete', 'build', 'buildjni',
- 'preassemble', 'bindir', 'antcommand', 'novcheck']
+
+ if build['version'] == "Ignore":
+ continue
+
+ writecomments('build:' + build['vercode'])
+ mf.write("Build:%s,%s\n" % (build['version'], build['vercode']))
def write_builditem(key, value):
- if key in ['version', 'vercode', 'origlines', 'type']:
+
+ if key in ['version', 'vercode']:
+ return
+
+ if value == flag_defaults[key]:
return
- if key in valuetypes['bool'].attrs:
- if not value:
- return
- value = 'yes'
- #if options.verbose:
- #print "...writing {0} : {1}".format(key, value)
+
+ t = flagtype(key)
+
+ logging.debug("...writing {0} : {1}".format(key, value))
outline = ' %s=' % key
- outline += '&& \\\n '.join([s.lstrip() for s in value.split('&& ')])
+
+ if t == 'string':
+ outline += value
+ if t == 'bool':
+ outline += 'yes'
+ elif t == 'script':
+ outline += '&& \\\n '.join([s.lstrip() for s in value.split('&& ')])
+ elif t == 'list':
+ outline += ','.join(value) if type(value) == list else value
+
outline += '\n'
mf.write(outline)
- for key in keyorder:
- if key in build:
- write_builditem(key, build[key])
- for key, value in build.iteritems():
- if not key in keyorder:
- write_builditem(key, value)
+ for flag in flag_defaults:
+ value = build[flag]
+ if value:
+ write_builditem(flag, value)
mf.write('\n')
- if 'Maintainer Notes' in app:
+ if app['Maintainer Notes']:
writefield('Maintainer Notes', '')
for line in app['Maintainer Notes']:
mf.write("%s\n" % line)
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['Vercode Operation']:
- writefield('Vercode Operation')
- if 'Update Check Data' in app:
- 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')
mf.write('\n')
writecomments(None)
mf.close()
-
-