from collections import OrderedDict
-srclibs = {}
+srclibs = None
class MetaDataException(Exception):
+
def __init__(self, value):
self.value = value
('Requires Root', False),
('Repo Type', ''),
('Repo', ''),
+ ('Binaries', None),
('Maintainer Notes', []),
('Archive Policy', None),
('Auto Update Mode', 'None'),
('Current Version', ''),
('Current Version Code', '0'),
('No Source Since', ''),
- ])
+])
# In the order in which they are laid out on files
('commit', None),
('subdir', None),
('submodules', False),
- ('init', None),
+ ('init', ''),
('patch', []),
('gradle', False),
('maven', False),
('forcevercode', False),
('rm', []),
('extlibs', []),
- ('prebuild', []),
+ ('prebuild', ''),
('update', ['auto']),
('target', None),
('scanignore', []),
('scandelete', []),
- ('build', []),
+ ('build', ''),
('buildjni', []),
('preassemble', []),
- ('antcommand', None),
+ ('antcommands', None),
('novcheck', False),
- ])
+])
# Designates a metadata field type and checks that it matches
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"], []),
["Repo Type"],
[]),
+ FieldValidator("Binaries",
+ r'^http[s]?://', None,
+ ["Binaries"],
+ []),
+
FieldValidator("Archive Policy",
r'^[0-9]+ versions$', None,
["Archive Policy"],
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 v in valuetypes:
for field in v.fields:
- if field in info:
- v.check(info[field], info['id'])
+ v.check(info[field], info['id'])
for build in info['builds']:
for attr in v.attrs:
- if build[attr]:
- v.check(build[attr], info['id'])
+ v.check(build[attr], info['id'])
# Formatter for descriptions. Create an instance, and call parseline() with
metadata.
"""
global srclibs
+
+ # They were already loaded
+ if srclibs is not None:
+ return
+
srclibs = {}
srcdir = 'srclibs'
# Read all metadata. Returns a list of 'app' objects (which are dictionaries as
# returned by the parse_metadata function.
def read_metadata(xref=True):
- apps = []
+
+ # 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'))):
- 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
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'
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()
+
+
# 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):
t = flagtype(pk)
if t == 'list':
# Port legacy ';' separators
- thisbuild[pk] = [v.strip() for v in pv.replace(';', ',').split(',')]
+ pv = [v.strip() for v in pv.replace(';', ',').split(',')]
+ 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':
thisinfo['comments'].append((key, comment))
del curcomments[:]
- def get_build_type(build):
- for t in ['maven', 'gradle', 'kivy']:
- if build[t]:
- return t
- if build['output']:
- return 'raw'
- return 'ant'
-
thisinfo = {}
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 = []
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))
thisinfo['builds'].append(curbuild)
- add_comments('build:' + curbuild['version'])
+ add_comments('build:' + curbuild['vercode'])
mode = 0
else:
if line.endswith('\\'):
buildlines = [value[:-1]]
else:
curbuild = parse_buildline([value])
- add_comments('build:' + thisinfo['builds'][-1]['version'])
+ thisinfo['builds'].append(curbuild)
+ add_comments('build:' + thisinfo['builds'][-1]['vercode'])
elif fieldtype == 'buildv2':
curbuild = {}
vv = value.split(',')
buildlines.append(line)
curbuild = parse_buildline(buildlines)
thisinfo['builds'].append(curbuild)
- add_comments('build:' + thisinfo['builds'][-1]['version'])
+ add_comments('build:' + thisinfo['builds'][-1]['vercode'])
mode = 0
add_comments(None)
thisinfo['Description'].append('No description available')
for build in thisinfo['builds']:
- for flag, value in flag_defaults.iteritems():
- if flag in build:
- continue
- build[flag] = value
- build['type'] = get_build_type(build)
+ fill_build_defaults(build)
- return thisinfo
+ thisinfo['builds'] = sorted(thisinfo['builds'], key=lambda build: int(build['vercode']))
+
+ return (appid, thisinfo)
# Write a metadata file.
mf.write("%s\n" % comment)
written += 1
if written > 0:
- logging.debug("...writing comments for " + (key if key else 'EOF'))
+ logging.debug("...writing comments for " + (key or 'EOF'))
def writefield(field, value=None):
writecomments(field)
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('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'])
+
+ 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]:
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')