ftp.mkdir('metadata')
ftp.mkdir('srclibs')
ftp.chdir('metadata')
- ftp.put(os.path.join('metadata', app['id'] + '.txt'),
- app['id'] + '.txt')
+ ftp.put(os.path.join('metadata', app.id + '.txt'),
+ app.id + '.txt')
# And patches if there are any...
- if os.path.exists(os.path.join('metadata', app['id'])):
- send_dir(os.path.join('metadata', app['id']))
+ if os.path.exists(os.path.join('metadata', app.id)):
+ send_dir(os.path.join('metadata', app.id))
ftp.chdir(homedir)
# Create the build directory...
# (no need if it's a srclib)
if (not basesrclib) and os.path.exists(build_dir):
ftp.chdir(homedir + '/build')
- fv = '.fdroidvcs-' + app['id']
+ fv = '.fdroidvcs-' + app.id
ftp.put(os.path.join('build', fv), fv)
send_dir(build_dir)
cmdline += ' --force --test'
if options.verbose:
cmdline += ' --verbose'
- cmdline += " %s:%s" % (app['id'], thisbuild['vercode'])
+ cmdline += " %s:%s" % (app.id, thisbuild['vercode'])
chan.exec_command('bash -c ". ~/.bsenv && ' + cmdline + '"')
output = ''
while not chan.exit_status_ready():
if returncode != 0:
raise BuildException(
"Build.py failed on server for {0}:{1}".format(
- app['id'], thisbuild['version']), output)
+ app.id, thisbuild['version']), output)
# Retrieve the built files...
logging.info("Retrieving build output...")
except:
raise BuildException(
"Build failed for %s:%s - missing output files".format(
- app['id'], thisbuild['version']), output)
+ app.id, thisbuild['version']), output)
ftp.close()
finally:
if p is not None and p.returncode != 0:
raise BuildException("Error cleaning %s:%s" %
- (app['id'], thisbuild['version']), p.output)
+ (app.id, thisbuild['version']), p.output)
for root, dirs, files in os.walk(build_dir):
if p.returncode != 0:
raise BuildException("Error running build command for %s:%s" %
- (app['id'], thisbuild['version']), p.output)
+ (app.id, thisbuild['version']), p.output)
# Build native stuff if required...
if thisbuild['buildjni'] and thisbuild['buildjni'] != ['no']:
del manifest_text
p = FDroidPopen(cmd, cwd=os.path.join(root_dir, d))
if p.returncode != 0:
- raise BuildException("NDK build failed for %s:%s" % (app['id'], thisbuild['version']), p.output)
+ raise BuildException("NDK build failed for %s:%s" % (app.id, thisbuild['version']), p.output)
p = None
# Build the release...
raise BuildException("Distribute build failed")
cid = bconfig.get('app', 'package.domain') + '.' + bconfig.get('app', 'package.name')
- if cid != app['id']:
+ if cid != app.id:
raise BuildException("Package ID mismatch between metadata and spec")
orientation = bconfig.get('app', 'orientation', 'landscape')
cmd = ['./build.py'
'--dir', root_dir,
'--name', bconfig.get('app', 'title'),
- '--package', app['id'],
+ '--package', app.id,
'--version', bconfig.get('app', 'version'),
'--orientation', orientation
]
bindir = os.path.join(root_dir, 'bin')
if p is not None and p.returncode != 0:
- raise BuildException("Build failed for %s:%s" % (app['id'], thisbuild['version']), p.output)
- logging.info("Successfully built version " + thisbuild['version'] + ' of ' + app['id'])
+ raise BuildException("Build failed for %s:%s" % (app.id, thisbuild['version']), p.output)
+ logging.info("Successfully built version " + thisbuild['version'] + ' of ' + app.id)
if thisbuild['type'] == 'maven':
stdout_apk = '\n'.join([
raise BuildException("Could not find version information in build in output")
if not foundid:
raise BuildException("Could not find package ID in output")
- if foundid != app['id']:
- raise BuildException("Wrong package ID - build " + foundid + " but expected " + app['id'])
+ if foundid != app.id:
+ raise BuildException("Wrong package ID - build " + foundid + " but expected " + app.id)
# Some apps (e.g. Timeriffic) have had the bonkers idea of
# including the entire changelog in the version number. Remove
return False
logging.info("Building version %s (%s) of %s" % (
- thisbuild['version'], thisbuild['vercode'], app['id']))
+ thisbuild['version'], thisbuild['vercode'], app.id))
if server:
# When using server mode, still keep a local cache of the repo, by
apps = common.read_app_args(options.appid, allapps, True)
for appid, app in apps.items():
- if (app['Disabled'] and not options.force) or not app['Repo Type'] or not app['builds']:
+ if (app.Disabled and not options.force) or not app.RepoType or not app.builds:
del apps[appid]
if not apps:
if options.latest:
for app in apps.itervalues():
- for build in reversed(app['builds']):
+ for build in reversed(app.builds):
if build['disable'] and not options.force:
continue
- app['builds'] = [build]
+ app.builds = [build]
break
if options.wiki:
first = True
- for thisbuild in app['builds']:
+ for thisbuild in app.builds:
wikilog = None
try:
# the source repo. We can reuse it on subsequent builds, if
# there are any.
if first:
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
else:
build_dir = os.path.join('build', appid)
# Set up vcs interface and make sure we have the latest code...
logging.debug("Getting {0} vcs interface for {1}"
- .format(app['Repo Type'], app['Repo']))
- vcs = common.getvcs(app['Repo Type'], app['Repo'], build_dir)
+ .format(app.RepoType, app.Repo))
+ vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
first = False
options.server, options.force,
options.onserver, options.refresh):
- if app.get('Binaries', None):
+ if app.Binaries is not None:
# This is an app where we build from source, and
# verify the apk contents against a developer's
# binary. We get that binary now, and save it
# alongside our built one in the 'unsigend'
# directory.
- url = app['Binaries']
+ url = app.Binaries
url = url.replace('%v', thisbuild['version'])
url = url.replace('%c', str(thisbuild['vercode']))
logging.info("...retrieving " + url)
- of = "{0}_{1}.apk.binary".format(app['id'], thisbuild['vercode'])
+ of = "{0}_{1}.apk.binary".format(app.id, thisbuild['vercode'])
of = os.path.join(output_dir, of)
net.download_file(url, local_filename=of)
logging.error("Error while attempting to publish build log")
for app in build_succeeded:
- logging.info("success: %s" % (app['id']))
+ logging.info("success: %s" % (app.id))
if not options.verbose:
for fa in failed_apps:
try:
- if 'Update Check Data' not in app:
+ if not app.UpdateCheckData:
raise FDroidException('Missing Update Check Data')
- urlcode, codeex, urlver, verex = app['Update Check Data'].split('|')
+ urlcode, codeex, urlver, verex = app.UpdateCheckData.split('|')
vercode = "99999999"
if len(urlcode) > 0:
return (version, vercode)
except FDroidException:
- msg = "Could not complete http check for app {0} due to unknown error: {1}".format(app['id'], traceback.format_exc())
+ msg = "Could not complete http check for app {0} due to unknown error: {1}".format(app.id, traceback.format_exc())
return (None, msg)
try:
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
- repotype = common.getsrclibvcs(app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
+ repotype = common.getsrclibvcs(app.Repo)
else:
- build_dir = os.path.join('build', app['id'])
- repotype = app['Repo Type']
+ build_dir = os.path.join('build', app.id)
+ repotype = app.RepoType
if repotype not in ('git', 'git-svn', 'hg', 'bzr'):
return (None, 'Tags update mode only works for git, hg, bzr and git-svn repositories currently', None)
- if repotype == 'git-svn' and ';' not in app['Repo']:
+ if repotype == 'git-svn' and ';' not in app.Repo:
return (None, 'Tags update mode used in git-svn, but the repo was not set up with tags', None)
# Set up vcs interface and make sure we have the latest code...
- vcs = common.getvcs(app['Repo Type'], app['Repo'], build_dir)
+ vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
vcs.gotorevision(None)
flavours = []
- if len(app['builds']) > 0:
- if app['builds'][-1]['gradle']:
- flavours = app['builds'][-1]['gradle']
+ if len(app.builds) > 0:
+ if app.builds[-1]['gradle']:
+ flavours = app.builds[-1]['gradle']
hpak = None
htag = None
return (None, "Couldn't find any version information", None)
except VCSException as vcse:
- msg = "VCS error while scanning app {0}: {1}".format(app['id'], vcse)
+ msg = "VCS error while scanning app {0}: {1}".format(app.id, vcse)
return (None, msg, None)
except Exception:
- msg = "Could not scan app {0} due to unknown error: {1}".format(app['id'], traceback.format_exc())
+ msg = "Could not scan app {0} due to unknown error: {1}".format(app.id, traceback.format_exc())
return (None, msg, None)
try:
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
- repotype = common.getsrclibvcs(app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
+ repotype = common.getsrclibvcs(app.Repo)
else:
- build_dir = os.path.join('build', app['id'])
- repotype = app['Repo Type']
+ build_dir = os.path.join('build', app.id)
+ repotype = app.RepoType
# Set up vcs interface and make sure we have the latest code...
- vcs = common.getvcs(app['Repo Type'], app['Repo'], build_dir)
+ vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
if repotype == 'git':
if branch:
vcs.gotorevision(None)
flavours = []
- if len(app['builds']) > 0:
- if app['builds'][-1]['gradle']:
- flavours = app['builds'][-1]['gradle']
+ if len(app.builds) > 0:
+ if app.builds[-1]['gradle']:
+ flavours = app.builds[-1]['gradle']
hpak = None
hver = None
return (None, "Couldn't find any version information")
except VCSException as vcse:
- msg = "VCS error while scanning app {0}: {1}".format(app['id'], vcse)
+ msg = "VCS error while scanning app {0}: {1}".format(app.id, vcse)
return (None, msg)
except Exception:
- msg = "Could not scan app {0} due to unknown error: {1}".format(app['id'], traceback.format_exc())
+ msg = "Could not scan app {0} due to unknown error: {1}".format(app.id, traceback.format_exc())
return (None, msg)
def check_repotrunk(app, branch=None):
try:
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
- repotype = common.getsrclibvcs(app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
+ repotype = common.getsrclibvcs(app.Repo)
else:
- build_dir = os.path.join('build', app['id'])
- repotype = app['Repo Type']
+ build_dir = os.path.join('build', app.id)
+ repotype = app.RepoType
if repotype not in ('git-svn', ):
return (None, 'RepoTrunk update mode only makes sense in git-svn repositories')
# Set up vcs interface and make sure we have the latest code...
- vcs = common.getvcs(app['Repo Type'], app['Repo'], build_dir)
+ vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
vcs.gotorevision(None)
ref = vcs.getref()
return (ref, ref)
except VCSException as vcse:
- msg = "VCS error while scanning app {0}: {1}".format(app['id'], vcse)
+ msg = "VCS error while scanning app {0}: {1}".format(app.id, vcse)
return (None, msg)
except Exception:
- msg = "Could not scan app {0} due to unknown error: {1}".format(app['id'], traceback.format_exc())
+ msg = "Could not scan app {0} due to unknown error: {1}".format(app.id, traceback.format_exc())
return (None, msg)
# the details of the current version.
def check_gplay(app):
time.sleep(15)
- url = 'https://play.google.com/store/apps/details?id=' + app['id']
+ url = 'https://play.google.com/store/apps/details?id=' + app.id
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux i686; rv:18.0) Gecko/20100101 Firefox/18.0'}
req = urllib2.Request(url, None, headers)
try:
# subdir relative to the build dir if found, None otherwise.
def possible_subdirs(app):
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
else:
- build_dir = os.path.join('build', app['id'])
+ build_dir = os.path.join('build', app.id)
flavours = []
- if len(app['builds']) > 0:
- build = app['builds'][-1]
+ if len(app.builds) > 0:
+ build = app.builds[-1]
if build['gradle']:
flavours = build['gradle']
def fetch_autoname(app, tag):
- if not app["Repo Type"] or app['Update Check Mode'] in ('None', 'Static'):
+ if not app.RepoType or app.UpdateCheckMode in ('None', 'Static'):
return None
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
else:
- build_dir = os.path.join('build', app['id'])
+ build_dir = os.path.join('build', app.id)
try:
- vcs = common.getvcs(app["Repo Type"], app["Repo"], build_dir)
+ vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
vcs.gotorevision(tag)
except VCSException:
return None
flavours = []
- if len(app['builds']) > 0:
- if app['builds'][-1]['gradle']:
- flavours = app['builds'][-1]['gradle']
+ if len(app.builds) > 0:
+ if app.builds[-1]['gradle']:
+ flavours = app.builds[-1]['gradle']
logging.debug("...fetch auto name from " + build_dir)
new_name = None
commitmsg = None
if new_name:
logging.debug("...got autoname '" + new_name + "'")
- if new_name != app['Auto Name']:
- app['Auto Name'] = new_name
+ if new_name != app.AutoName:
+ app.AutoName = new_name
if not commitmsg:
commitmsg = "Set autoname of {0}".format(common.getappname(app))
else:
msg = None
vercode = None
noverok = False
- mode = app['Update Check Mode']
+ mode = app.UpdateCheckMode
if mode.startswith('Tags'):
pattern = mode[5:] if len(mode) > 4 else None
(version, vercode, tag) = check_tags(app, pattern)
version = None
msg = 'Invalid update check method'
- if version and vercode and app['Vercode Operation']:
+ if version and vercode and app.VercodeOperation:
oldvercode = str(int(vercode))
- op = app['Vercode Operation'].replace("%c", oldvercode)
+ op = app.VercodeOperation.replace("%c", oldvercode)
vercode = str(eval(op))
logging.debug("Applied vercode operation: %s -> %s" % (oldvercode, vercode))
updating = False
if version is None:
- logmsg = "...{0} : {1}".format(app['id'], msg)
+ logmsg = "...{0} : {1}".format(app.id, msg)
if noverok:
logging.info(logmsg)
else:
logging.warn(logmsg)
- elif vercode == app['Current Version Code']:
+ elif vercode == app.CurrentVersionCode:
logging.info("...up to date")
else:
- app['Current Version'] = version
- app['Current Version Code'] = str(int(vercode))
+ app.CurrentVersion = version
+ app.CurrentVersionCode = str(int(vercode))
updating = True
commitmsg = fetch_autoname(app, tag)
commitmsg = 'Update CV of %s to %s' % (name, ver)
if options.auto:
- mode = app['Auto Update Mode']
+ mode = app.AutoUpdateMode
if mode in ('None', 'Static'):
pass
elif mode.startswith('Version '):
suffix = ''
gotcur = False
latest = None
- for build in app['builds']:
- if int(build['vercode']) >= int(app['Current Version Code']):
+ for build in app.builds:
+ if int(build['vercode']) >= int(app.CurrentVersionCode):
gotcur = True
if not latest or int(build['vercode']) > int(latest['vercode']):
latest = build
- if int(latest['vercode']) > int(app['Current Version Code']):
+ if int(latest['vercode']) > int(app.CurrentVersionCode):
logging.info("Refusing to auto update, since the latest build is newer")
if not gotcur:
if 'origlines' in newbuild:
del newbuild['origlines']
newbuild['disable'] = False
- newbuild['vercode'] = app['Current Version Code']
- newbuild['version'] = app['Current Version'] + suffix
+ newbuild['vercode'] = app.CurrentVersionCode
+ newbuild['version'] = app.CurrentVersion + suffix
logging.info("...auto-generating build for " + newbuild['version'])
commit = pattern.replace('%v', newbuild['version'])
commit = commit.replace('%c', newbuild['vercode'])
newbuild['commit'] = commit
- app['builds'].append(newbuild)
+ app.builds.append(newbuild)
name = common.getappname(app)
ver = common.getcvname(app)
commitmsg = "Update %s to %s" % (name, ver)
else:
- logging.warn('Invalid auto update mode "' + mode + '" on ' + app['id'])
+ logging.warn('Invalid auto update mode "' + mode + '" on ' + app.id)
if commitmsg:
- metadatapath = os.path.join('metadata', app['id'] + '.txt')
+ metadatapath = os.path.join('metadata', app.id + '.txt')
with open(metadatapath, 'w') as f:
metadata.write_metadata('txt', f, app)
if options.commit:
else:
logging.info("{0} encountered a problem: {1}".format(common.getappname(app), reason))
if version is not None:
- stored = app['Current Version']
+ stored = app.CurrentVersion
if not stored:
logging.info("{0} has no Current Version but has version {1} on the Play Store"
.format(common.getappname(app), version))
for appid, app in apps.iteritems():
- if options.autoonly and app['Auto Update Mode'] in ('None', 'Static'):
+ if options.autoonly and app.AutoUpdateMode in ('None', 'Static'):
logging.debug("Nothing to do for {0}...".format(appid))
continue
vc = vercodes[appid]
if not vc:
continue
- app['builds'] = [b for b in app['builds'] if b['vercode'] in vc]
- if len(app['builds']) != len(vercodes[appid]):
+ app.builds = [b for b in app.builds if b['vercode'] in vc]
+ if len(app.builds) != len(vercodes[appid]):
error = True
- allvcs = [b['vercode'] for b in app['builds']]
+ allvcs = [b['vercode'] for b in app.builds]
for v in vercodes[appid]:
if v not in allvcs:
logging.critical("No such vercode %s for app %s" % (v, appid))
def getapkname(app, build):
- return "%s_%s.apk" % (app['id'], build['vercode'])
+ return "%s_%s.apk" % (app.id, build['vercode'])
def getsrcname(app, build):
- return "%s_%s_src.tar.gz" % (app['id'], build['vercode'])
+ return "%s_%s_src.tar.gz" % (app.id, build['vercode'])
def getappname(app):
- if app['Name']:
- return app['Name']
- if app['Auto Name']:
- return app['Auto Name']
- return app['id']
+ if app.Name:
+ return app.Name
+ if app.AutoName:
+ return app.AutoName
+ return app.id
def getcvname(app):
- return '%s (%s)' % (app['Current Version'], app['Current Version Code'])
+ return '%s (%s)' % (app.CurrentVersion, app.CurrentVersionCode)
def getvcs(vcstype, remote, local):
def app_matches_packagename(app, package):
if not package:
return False
- appid = app['Update Check Name'] or app['id']
+ appid = app.UpdateCheckName or app.id
if appid is None or appid == "Ignore":
return True
return appid == package
# All values returned are strings.
def parse_androidmanifests(paths, app):
- ignoreversions = app['Update Check Ignore']
+ ignoreversions = app.UpdateCheckIgnore
ignoresearch = re.compile(ignoreversions).search if ignoreversions else None
if not paths:
p = FDroidPopen(['bash', '-x', '-c', cmd], cwd=root_dir)
if p.returncode != 0:
raise BuildException("Error running init command for %s:%s" %
- (app['id'], build['version']), p.output)
+ (app.id, build['version']), p.output)
# Apply patches if any
if build['patch']:
for patch in build['patch']:
patch = patch.strip()
logging.info("Applying " + patch)
- patch_path = os.path.join('metadata', app['id'], patch)
+ patch_path = os.path.join('metadata', app.id, patch)
p = FDroidPopen(['patch', '-p1', '-i', os.path.abspath(patch_path)], cwd=build_dir)
if p.returncode != 0:
raise BuildException("Failed to apply patch %s" % patch_path)
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.output)
+ (app.id, build['version']), p.output)
# Generate (or update) the ant build file, build.xml...
if build['update'] and build['update'] != ['no'] and build['type'] == 'ant':
# Figure out what kind of project it is...
projecttype = None
- app['Web Site'] = url # by default, we might override it
+ app.WebSite = url # by default, we might override it
if url.startswith('git://'):
projecttype = 'git'
repo = url
repotype = 'git'
- app['Source Code'] = ""
- app['Web Site'] = ""
+ app.SourceCode = ""
+ app.WebSite = ""
elif url.startswith('https://github.com'):
projecttype = 'github'
repo = url
repotype = 'git'
- app['Source Code'] = url
- app['Issue Tracker'] = url + '/issues'
- app['Web Site'] = ""
+ app.SourceCode = url
+ app.IssueTracker = url + '/issues'
+ app.WebSite = ""
elif url.startswith('https://gitlab.com/'):
projecttype = 'gitlab'
# git can be fussy with gitlab URLs unless they end in .git
else:
repo = url + '.git'
repotype = 'git'
- app['Source Code'] = url + '/tree/HEAD'
- app['Issue Tracker'] = url + '/issues'
+ app.SourceCode = url + '/tree/HEAD'
+ app.IssueTracker = url + '/issues'
elif url.startswith('https://bitbucket.org/'):
if url.endswith('/'):
url = url[:-1]
projecttype = 'bitbucket'
- app['Source Code'] = url + '/src'
- app['Issue Tracker'] = url + '/issues'
+ app.SourceCode = url + '/src'
+ app.IssueTracker = url + '/issues'
# Figure out the repo type and adddress...
- repotype, repo = getrepofrompage(app['Source Code'])
+ repotype, repo = getrepofrompage(app.SourceCode)
if not repotype:
logging.error("Unable to determine vcs type. " + repo)
sys.exit(1)
vcs.gotorevision(options.rev)
root_dir = get_subdir(build_dir)
- app['Repo Type'] = repotype
- app['Repo'] = repo
+ app.RepoType = repotype
+ app.Repo = repo
return root_dir, build_dir
apps = metadata.read_metadata()
package, app = metadata.get_default_app_info()
- app['id'] = None
- app['Update Check Mode'] = "Tags"
+ app.id = None
+ app.UpdateCheckMode = "Tags"
root_dir = None
build_dir = None
root_dir, build_dir = get_metadata_from_url(app, options.url)
elif os.path.isdir('.git'):
if options.url:
- app['Web Site'] = options.url
+ app.WebSite = options.url
root_dir = get_subdir(os.getcwd())
else:
logging.error("Specify project url.")
continue
build[flag] = value
- app['builds'].append(build)
+ app.builds.append(build)
# Keep the repo directory to save bandwidth...
if not os.path.exists('build'):
if build_dir is not None:
shutil.move(build_dir, os.path.join('build', package))
with open('build/.fdroidvcs-' + package, 'w') as f:
- f.write(app['Repo Type'] + ' ' + app['Repo'])
+ f.write(app.RepoType + ' ' + app.Repo)
metadatapath = os.path.join('metadata', package + '.txt')
with open(metadatapath, 'w') as f:
def check_regexes(app):
for f, checks in regex_checks.iteritems():
for m, r in checks:
- v = app[f]
+ v = app.get_field(f)
if type(v) == str:
if v is None:
continue
def check_ucm_tags(app):
- lastbuild = get_lastbuild(app['builds'])
+ lastbuild = get_lastbuild(app.builds)
if (lastbuild is not None
and lastbuild['commit']
- and app['Update Check Mode'] == 'RepoManifest'
+ and app.UpdateCheckMode == 'RepoManifest'
and not lastbuild['commit'].startswith('unknown')
- and lastbuild['vercode'] == app['Current Version Code']
+ and lastbuild['vercode'] == app.CurrentVersionCode
and not lastbuild['forcevercode']
and any(s in lastbuild['commit'] for s in '.,_-/')):
yield "Last used commit '%s' looks like a tag, but Update Check Mode is '%s'" % (
- lastbuild['commit'], app['Update Check Mode'])
+ lastbuild['commit'], app.UpdateCheckMode)
def check_char_limits(app):
limits = config['char_limits']
- summ_chars = len(app['Summary'])
+ summ_chars = len(app.Summary)
if summ_chars > limits['Summary']:
yield "Summary of length %s is over the %i char limit" % (
summ_chars, limits['Summary'])
- desc_charcount = sum(len(l) for l in app['Description'])
+ desc_charcount = sum(len(l) for l in app.Description)
if desc_charcount > limits['Description']:
yield "Description of length %s is over the %i char limit" % (
desc_charcount, limits['Description'])
'gitorious.org',
'code.google.com',
]
- if any(s in app['Repo'] for s in usual_sites):
+ if any(s in app.Repo for s in usual_sites):
for f in ['Web Site', 'Source Code', 'Issue Tracker', 'Changelog']:
- if any(s in app[f] for s in old_sites):
- yield "App is in '%s' but has a link to '%s'" % (app['Repo'], app[f])
+ v = app.get_field(f)
+ if any(s in v for s in old_sites):
+ yield "App is in '%s' but has a link to '%s'" % (app.Repo, v)
def check_useless_fields(app):
- if app['Update Check Name'] == app['id']:
+ if app.UpdateCheckName == app.id:
yield "Update Check Name is set to the known app id - it can be removed"
filling_ucms = re.compile(r'^(Tags.*|RepoManifest.*)')
def check_checkupdates_ran(app):
- if filling_ucms.match(app['Update Check Mode']):
- if all(app[f] == metadata.app_defaults[f] for f in [
- 'Auto Name',
- 'Current Version',
- 'Current Version Code',
- ]):
+ if filling_ucms.match(app.UpdateCheckMode):
+ if not app.AutoName and not app.CurrentVersion and app.CurrentVersionCode == '0':
yield "UCM is set but it looks like checkupdates hasn't been run yet"
def check_empty_fields(app):
- if not app['Categories']:
+ if not app.Categories:
yield "Categories are not set"
all_categories = Set([
def check_categories(app):
- for categ in app['Categories']:
+ for categ in app.Categories:
if categ not in all_categories:
yield "Category '%s' is not valid" % categ
def check_duplicates(app):
- if app['Name'] and app['Name'] == app['Auto Name']:
- yield "Name '%s' is just the auto name - remove it" % app['Name']
+ if app.Name and app.Name == app.AutoName:
+ yield "Name '%s' is just the auto name - remove it" % app.Name
links_seen = set()
for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']:
- if not app[f]:
+ v = app.get_field(f)
+ if not v:
continue
- v = app[f].lower()
+ v = v.lower()
if v in links_seen:
yield "Duplicate link in '%s': %s" % (f, v)
else:
links_seen.add(v)
- name = app['Name'] or app['Auto Name']
- if app['Summary'] and name:
- if app['Summary'].lower() == name.lower():
- yield "Summary '%s' is just the app's name" % app['Summary']
+ name = app.Name or app.AutoName
+ if app.Summary and name:
+ if app.Summary.lower() == name.lower():
+ yield "Summary '%s' is just the app's name" % app.Summary
- desc = app['Description']
- if app['Summary'] and desc and len(desc) == 1:
- if app['Summary'].lower() == desc[0].lower():
- yield "Description '%s' is just the app's summary" % app['Summary']
+ if app.Summary and app.Description and len(app.Description) == 1:
+ if app.Summary.lower() == app.Description[0].lower():
+ yield "Description '%s' is just the app's summary" % app.Summary
seenlines = set()
- for l in app['Description']:
+ for l in app.Description:
if len(l) < 1:
continue
if l in seenlines:
def check_mediawiki_links(app):
- wholedesc = ' '.join(app['Description'])
+ wholedesc = ' '.join(app.Description)
for um in desc_url.finditer(wholedesc):
url = um.group(1)
for m, r in http_checks:
validchars = ['*', '#']
lchar = ''
lcount = 0
- for l in app['Description']:
+ for l in app.Description:
if len(l) < 1:
lcount = 0
continue
def check_builds(app):
- for build in app['builds']:
+ for build in app.builds:
if build['disable']:
continue
for s in ['master', 'origin', 'HEAD', 'default', 'trunk']:
apps = common.read_app_args(options.appid, allapps, False)
for appid, app in apps.iteritems():
- if app['Disabled']:
+ if app.Disabled:
continue
warns = []
def __str__(self):
return self.value
-# In the order in which they are laid out on files
-app_defaults = OrderedDict([
- ('Disabled', None),
- ('AntiFeatures', []),
- ('Provides', None),
- ('Categories', ['None']),
- ('License', 'Unknown'),
- ('Web Site', ''),
- ('Source Code', ''),
- ('Issue Tracker', ''),
- ('Changelog', ''),
- ('Donate', None),
- ('FlattrID', None),
- ('Bitcoin', None),
- ('Litecoin', 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', ''),
+app_fields = set([
+ 'Disabled',
+ 'AntiFeatures',
+ 'Provides',
+ 'Categories',
+ 'License',
+ 'Web Site',
+ 'Source Code',
+ 'Issue Tracker',
+ 'Changelog',
+ 'Donate',
+ 'FlattrID',
+ 'Bitcoin',
+ 'Litecoin',
+ 'Name',
+ 'Auto Name',
+ 'Summary',
+ 'Description',
+ 'Requires Root',
+ 'Repo Type',
+ 'Repo',
+ 'Binaries',
+ 'Maintainer Notes',
+ 'Archive Policy',
+ 'Auto Update Mode',
+ 'Update Check Mode',
+ 'Update Check Ignore',
+ 'Vercode Operation',
+ 'Update Check Name',
+ 'Update Check Data',
+ 'Current Version',
+ 'Current Version Code',
+ 'No Source Since',
+
+ 'comments', # For formats that don't do inline comments
+ 'builds', # For formats that do builds as a list
])
+class App():
+
+ def __init__(self):
+ self.Disabled = None
+ self.AntiFeatures = []
+ self.Provides = None
+ self.Categories = ['None']
+ self.License = 'Unknown'
+ self.WebSite = ''
+ self.SourceCode = ''
+ self.IssueTracker = ''
+ self.Changelog = ''
+ self.Donate = None
+ self.FlattrID = None
+ self.Bitcoin = None
+ self.Litecoin = None
+ self.Name = None
+ self.AutoName = ''
+ self.Summary = ''
+ self.Description = []
+ self.RequiresRoot = False
+ self.RepoType = ''
+ self.Repo = ''
+ self.Binaries = None
+ self.MaintainerNotes = []
+ self.ArchivePolicy = None
+ self.AutoUpdateMode = 'None'
+ self.UpdateCheckMode = 'None'
+ self.UpdateCheckIgnore = None
+ self.VercodeOperation = None
+ self.UpdateCheckName = None
+ self.UpdateCheckData = None
+ self.CurrentVersion = ''
+ self.CurrentVersionCode = '0'
+ self.NoSourceSince = ''
+
+ self.id = None
+ self.metadatapath = None
+ self.builds = []
+ self.comments = {}
+ self.added = None
+ self.lastupdated = None
+
+ @classmethod
+ def field_to_attr(cls, f):
+ return f.replace(' ', '')
+
+ @classmethod
+ def attr_to_field(cls, k):
+ if k in app_fields:
+ return k
+ f = re.sub(r'([a-z])([A-Z])', r'\1 \2', k)
+ return f
+
+ def field_dict(self):
+ return {App.attr_to_field(k): v for k, v in self.__dict__.iteritems()}
+
+ def get_field(self, f):
+ if f not in app_fields:
+ raise MetaDataException('Unrecognised app field: ' + f)
+ k = App.field_to_attr(f)
+ return getattr(self, k)
+
+ def set_field(self, f, v):
+ if f not in app_fields:
+ raise MetaDataException('Unrecognised app field: ' + f)
+ k = App.field_to_attr(f)
+ self.__dict__[k] = v
+
+ def append_field(self, f, v):
+ if f not in app_fields:
+ raise MetaDataException('Unrecognised app field: ' + f)
+ k = App.field_to_attr(f)
+ if k not in self.__dict__:
+ self.__dict__[k] = [v]
+ else:
+ self.__dict__[k].append(v)
+
+ def update_fields(self, d):
+ for f, v in d.iteritems():
+ self.set_field(f, v)
+
+
# In the order in which they are laid out on files
# Sorted by their action and their place in the build timeline
# These variables can have varying datatypes. For example, anything with
# Check an app's metadata information for integrity errors
-def check_metadata(info):
+def check_metadata(app):
for v in valuetypes:
for field in v.fields:
- v.check(info[field], info['id'])
- for build in info['builds']:
+ v.check(app.get_field(field), app.id)
+ for build in app.builds:
for attr in v.attrs:
- v.check(build[attr], info['id'])
+ v.check(build[attr], app.id)
# Formatter for descriptions. Create an instance, and call parseline() with
+ glob.glob(os.path.join('metadata', '*.json'))
+ glob.glob(os.path.join('metadata', '*.xml'))
+ glob.glob(os.path.join('metadata', '*.yaml'))):
- appid, appinfo = parse_metadata(metadatapath)
- if appid in apps:
- raise MetaDataException("Found multiple metadata files for " + appid)
- check_metadata(appinfo)
- apps[appid] = appinfo
+ app = parse_metadata(metadatapath)
+ if app.id in apps:
+ raise MetaDataException("Found multiple metadata files for " + app.id)
+ check_metadata(app)
+ apps[app.id] = app
if xref:
# Parse all descriptions at load time, just to ensure cross-referencing
for appid, app in apps.iteritems():
try:
- description_html(app['Description'], linkres)
+ description_html(app.Description, linkres)
except MetaDataException, e:
raise MetaDataException("Problem with description of " + appid +
" - " + str(e))
return 'buildv2'
if name == 'Use Built':
return 'obsolete'
- if name not in app_defaults:
+ if name not in app_fields:
return 'unknown'
return 'string'
else:
appid, _ = common.get_extension(os.path.basename(metadatapath))
- thisinfo = {}
- thisinfo.update(app_defaults)
- thisinfo['metadatapath'] = metadatapath
+ app = App()
+ app.metadatapath = metadatapath
if appid is not None:
- thisinfo['id'] = appid
-
- # General defaults...
- thisinfo['builds'] = []
- thisinfo['comments'] = dict()
+ app.id = appid
- return appid, thisinfo
+ return app
def sorted_builds(builds):
return sorted(builds, key=lambda build: int(build['vercode']))
-def post_metadata_parse(thisinfo):
+def post_metadata_parse(app):
- supported_metadata = app_defaults.keys() + ['comments', 'builds', 'id', 'metadatapath']
- for k, v in thisinfo.iteritems():
- if k not in supported_metadata:
- raise MetaDataException("Unrecognised metadata: {0}: {1}"
- .format(k, v))
+ for f in app_fields:
+ v = app.get_field(f)
if type(v) in (float, int):
- thisinfo[k] = str(v)
+ app.set_field(f, str(v))
# convert to the odd internal format
- for k in ('Description', 'Maintainer Notes'):
- if isinstance(thisinfo[k], basestring):
- text = thisinfo[k].rstrip().lstrip()
- thisinfo[k] = text.split('\n')
+ for f in ('Description', 'Maintainer Notes'):
+ v = app.get_field(f)
+ if isinstance(v, basestring):
+ text = v.rstrip().lstrip()
+ app.set_field(f, text.split('\n'))
supported_flags = (flag_defaults.keys()
- + ['vercode', 'version', 'versionCode', 'versionName'])
+ + ['vercode', 'version', 'versionCode', 'versionName',
+ 'type', 'ndk_path'])
esc_newlines = re.compile('\\\\( |\\n)')
- for build in thisinfo['builds']:
+ for build in app.builds:
for k, v in build.items():
if k not in supported_flags:
raise MetaDataException("Unrecognised build flag: {0}={1}"
if isinstance(v, bool):
build[k] = 'yes' if v else 'no'
- if not thisinfo['Description']:
- thisinfo['Description'].append('No description available')
+ if not app.Description:
+ app.Description = ['No description available']
- for build in thisinfo['builds']:
+ for build in app.builds:
fill_build_defaults(build)
- thisinfo['builds'] = sorted_builds(thisinfo['builds'])
+ app.builds = sorted_builds(app.builds)
# Parse metadata for a single application.
def parse_json_metadata(metadatapath):
- appid, thisinfo = get_default_app_info(metadatapath)
+ app = get_default_app_info(metadatapath)
# fdroid metadata is only strings and booleans, no floats or ints. And
# json returns unicode, and fdroidserver still uses plain python strings
object_hook=_decode_dict,
parse_int=lambda s: s,
parse_float=lambda s: s)
- thisinfo.update(jsoninfo)
- post_metadata_parse(thisinfo)
+ app.update_fields(jsoninfo)
+ post_metadata_parse(app)
- return (appid, thisinfo)
+ return app
def parse_xml_metadata(metadatapath):
- appid, thisinfo = get_default_app_info(metadatapath)
+ app = get_default_app_info(metadatapath)
tree = ElementTree.ElementTree(file=metadatapath)
root = tree.getroot()
logging.critical(metadatapath + ' does not have root as <resources></resources>!')
sys.exit(1)
- supported_metadata = app_defaults.keys()
for child in root:
if child.tag != 'builds':
# builds does not have name="" attrib
name = child.attrib['name']
- if name not in supported_metadata:
- raise MetaDataException("Unrecognised metadata: <"
- + child.tag + ' name="' + name + '">'
- + child.text
- + "</" + child.tag + '>')
if child.tag == 'string':
- thisinfo[name] = child.text
+ app.set_field(name, child.text)
elif child.tag == 'string-array':
items = []
for item in child:
items.append(item.text)
- thisinfo[name] = items
+ app.set_field(name, items)
elif child.tag == 'builds':
- builds = []
for build in child:
builddict = dict()
for key in build:
builddict[key.tag] = key.text
- builds.append(builddict)
- thisinfo['builds'] = builds
+ app.builds.append(builddict)
# TODO handle this using <xsd:element type="xsd:boolean> in a schema
- if not isinstance(thisinfo['Requires Root'], bool):
- if thisinfo['Requires Root'] == 'true':
- thisinfo['Requires Root'] = True
+ if not isinstance(app.RequiresRoot, bool):
+ if app.RequiresRoot == 'true':
+ app.RequiresRoot = True
else:
- thisinfo['Requires Root'] = False
+ app.RequiresRoot = False
- post_metadata_parse(thisinfo)
+ post_metadata_parse(app)
- return (appid, thisinfo)
+ return app
def parse_yaml_metadata(metadatapath):
- appid, thisinfo = get_default_app_info(metadatapath)
+ app = get_default_app_info(metadatapath)
yamlinfo = yaml.load(open(metadatapath, 'r'), Loader=YamlLoader)
- thisinfo.update(yamlinfo)
- post_metadata_parse(thisinfo)
+ app.update_fields(yamlinfo)
+ post_metadata_parse(app)
- return (appid, thisinfo)
+ return app
def parse_txt_metadata(metadatapath):
def add_comments(key):
if not curcomments:
return
- thisinfo['comments'][key] = list(curcomments)
+ app.comments[key] = list(curcomments)
del curcomments[:]
- appid, thisinfo = get_default_app_info(metadatapath)
+ app = get_default_app_info(metadatapath)
metafile = open(metadatapath, "r")
mode = 0
raise MetaDataException("No commit specified for {0} in {1}"
.format(curbuild['version'], linedesc))
- thisinfo['builds'].append(curbuild)
+ app.builds.append(curbuild)
add_comments('build:' + curbuild['vercode'])
mode = 0
else:
add_comments(field)
if fieldtype == 'multiline':
mode = 1
- thisinfo[field] = []
if value:
raise MetaDataException("Unexpected text on same line as " + field + " in " + linedesc)
elif fieldtype == 'string':
- thisinfo[field] = value
+ app.set_field(field, value)
elif fieldtype == 'list':
- thisinfo[field] = split_list_values(value)
+ app.set_field(field, split_list_values(value))
elif fieldtype == 'build':
if value.endswith("\\"):
mode = 2
buildlines = [value[:-1]]
else:
curbuild = parse_buildline([value])
- thisinfo['builds'].append(curbuild)
- add_comments('build:' + thisinfo['builds'][-1]['vercode'])
+ app.builds.append(curbuild)
+ add_comments('build:' + app.builds[-1]['vercode'])
elif fieldtype == 'buildv2':
curbuild = {}
vv = value.split(',')
if line == '.':
mode = 0
else:
- thisinfo[field].append(line)
+ app.append_field(field, line)
elif mode == 2: # Line continuation mode in Build Version
if line.endswith("\\"):
buildlines.append(line[:-1])
else:
buildlines.append(line)
curbuild = parse_buildline(buildlines)
- thisinfo['builds'].append(curbuild)
- add_comments('build:' + thisinfo['builds'][-1]['vercode'])
+ app.builds.append(curbuild)
+ add_comments('build:' + app.builds[-1]['vercode'])
mode = 0
add_comments(None)
elif mode == 3:
raise MetaDataException("Unterminated build in " + metafile.name)
- post_metadata_parse(thisinfo)
+ post_metadata_parse(app)
- return (appid, thisinfo)
+ return app
def write_plaintext_metadata(mf, app, w_comment, w_field, w_build):
def w_comments(key):
- if key not in app['comments']:
+ if key not in app.comments:
return
- for line in app['comments'][key]:
+ for line in app.comments[key]:
w_comment(line)
def w_field_always(field, value=None):
if value is None:
- value = app[field]
+ value = app.get_field(field)
w_comments(field)
w_field(field, value)
def w_field_nonempty(field, value=None):
if value is None:
- value = app[field]
+ value = app.get_field(field)
w_comments(field)
if value:
w_field(field, value)
w_field_nonempty('Disabled')
- if app['AntiFeatures']:
+ if app.AntiFeatures:
w_field_always('AntiFeatures')
w_field_nonempty('Provides')
w_field_always('Categories')
w_field_nonempty('Name')
w_field_nonempty('Auto Name')
w_field_always('Summary')
- w_field_always('Description', description_txt(app['Description']))
+ w_field_always('Description', description_txt(app.Description))
mf.write('\n')
- if app['Requires Root']:
+ if app.RequiresRoot:
w_field_always('Requires Root', 'yes')
mf.write('\n')
- if app['Repo Type']:
+ if app.RepoType:
w_field_always('Repo Type')
w_field_always('Repo')
- if app['Binaries']:
+ if app.Binaries:
w_field_always('Binaries')
mf.write('\n')
- for build in sorted_builds(app['builds']):
+ for build in sorted_builds(app.builds):
if build['version'] == "Ignore":
continue
w_build(build)
mf.write('\n')
- if app['Maintainer Notes']:
- w_field_always('Maintainer Notes', app['Maintainer Notes'])
+ if app.MaintainerNotes:
+ w_field_always('Maintainer Notes', app.MaintainerNotes)
mf.write('\n')
w_field_nonempty('Archive Policy')
w_field_nonempty('Vercode Operation')
w_field_nonempty('Update Check Name')
w_field_nonempty('Update Check Data')
- if app['Current Version']:
+ if app.CurrentVersion:
w_field_always('Current Version')
w_field_always('Current Version Code')
- if app['No Source Since']:
+ if app.NoSourceSince:
mf.write('\n')
w_field_always('No Source Since')
w_comments(None)
sys.exit(1)
app = allapps[appid]
- if app.get('Binaries', None):
+ if app.Binaries is not None:
# It's an app where we build from source, and verify the apk
# contents against a developer's binary, and then publish their
parser.error("Must give a valid format to --to")
for appid, app in apps.iteritems():
- metadatapath = app['metadatapath']
+ metadatapath = app.metadatapath
base, ext = common.get_extension(metadatapath)
if not options.to and ext not in supported:
logging.info("Ignoring %s file at '%s'" % (ext, metadatapath))
for appid, app in apps.iteritems():
- if app['Disabled']:
+ if app.Disabled:
logging.info("Skipping %s: disabled" % appid)
continue
- if not app['builds']:
+ if not app.builds:
logging.info("Skipping %s: no builds specified" % appid)
continue
try:
- if app['Repo Type'] == 'srclib':
- build_dir = os.path.join('build', 'srclib', app['Repo'])
+ if app.RepoType == 'srclib':
+ build_dir = os.path.join('build', 'srclib', app.Repo)
else:
build_dir = os.path.join('build', appid)
# Set up vcs interface and make sure we have the latest code...
- vcs = common.getvcs(app['Repo Type'], app['Repo'], build_dir)
+ vcs = common.getvcs(app.RepoType, app.Repo, build_dir)
- for thisbuild in app['builds']:
+ for thisbuild in app.builds:
if thisbuild['disable']:
logging.info("...skipping version %s - %s" % (
logging.info("Processing repo types...")
repotypes = Counter()
for app in metaapps:
- rtype = app['Repo Type'] or 'none'
+ rtype = app.RepoType or 'none'
if rtype == 'srclib':
- rtype = common.getsrclibvcs(app['Repo'])
+ rtype = common.getsrclibvcs(app.Repo)
repotypes[rtype] += 1
with open(os.path.join(statsdir, 'repotypes.txt'), 'w') as f:
for rtype, count in repotypes.most_common():
logging.info("Processing update check modes...")
ucms = Counter()
for app in metaapps:
- checkmode = app['Update Check Mode']
+ checkmode = app.UpdateCheckMode
if checkmode.startswith('RepoManifest/'):
checkmode = checkmode[:12]
if checkmode.startswith('Tags '):
logging.info("Processing categories...")
ctgs = Counter()
for app in metaapps:
- for category in app['Categories']:
+ for category in app.Categories:
ctgs[category] += 1
with open(os.path.join(statsdir, 'categories.txt'), 'w') as f:
for category, count in ctgs.most_common():
logging.info("Processing antifeatures...")
afs = Counter()
for app in metaapps:
- if app['AntiFeatures'] is None:
+ if app.AntiFeatures is None:
continue
- for antifeature in app['AntiFeatures']:
+ for antifeature in app.AntiFeatures:
afs[antifeature] += 1
with open(os.path.join(statsdir, 'antifeatures.txt'), 'w') as f:
for antifeature, count in afs.most_common():
logging.info("Processing licenses...")
licenses = Counter()
for app in metaapps:
- license = app['License']
+ license = app.License
licenses[license] += 1
with open(os.path.join(statsdir, 'licenses.txt'), 'w') as f:
for license, count in licenses.most_common():
app = apps[appid]
wikidata = ''
- if app['Disabled']:
- wikidata += '{{Disabled|' + app['Disabled'] + '}}\n'
- if 'AntiFeatures' in app:
- for af in app['AntiFeatures']:
+ if app.Disabled:
+ wikidata += '{{Disabled|' + app.Disabled + '}}\n'
+ if app.AntiFeatures:
+ for af in app.AntiFeatures:
wikidata += '{{AntiFeature|' + af + '}}\n'
- if app['Requires Root']:
+ if app.RequiresRoot:
requiresroot = 'Yes'
else:
requiresroot = 'No'
wikidata += '{{App|id=%s|name=%s|added=%s|lastupdated=%s|source=%s|tracker=%s|web=%s|changelog=%s|donate=%s|flattr=%s|bitcoin=%s|litecoin=%s|license=%s|root=%s}}\n' % (
appid,
- app['Name'],
- time.strftime('%Y-%m-%d', app['added']) if 'added' in app else '',
- time.strftime('%Y-%m-%d', app['lastupdated']) if 'lastupdated' in app else '',
- app['Source Code'],
- app['Issue Tracker'],
- app['Web Site'],
- app['Changelog'],
- app['Donate'],
- app['FlattrID'],
- app['Bitcoin'],
- app['Litecoin'],
- app['License'],
+ app.Name,
+ time.strftime('%Y-%m-%d', app.added) if app.added else '',
+ time.strftime('%Y-%m-%d', app.lastupdated) if app.lastupdated else '',
+ app.SourceCode,
+ app.IssueTracker,
+ app.WebSite,
+ app.Changelog,
+ app.Donate,
+ app.FlattrID,
+ app.Bitcoin,
+ app.Litecoin,
+ app.License,
requiresroot)
- if app['Provides']:
- wikidata += "This app provides: %s" % ', '.join(app['Summary'].split(','))
+ if app.Provides:
+ wikidata += "This app provides: %s" % ', '.join(app.Summary.split(','))
- wikidata += app['Summary']
+ wikidata += app.Summary
wikidata += " - [https://f-droid.org/repository/browse/?fdid=" + appid + " view in repository]\n\n"
wikidata += "=Description=\n"
- wikidata += metadata.description_wiki(app['Description']) + "\n"
+ wikidata += metadata.description_wiki(app.Description) + "\n"
wikidata += "=Maintainer Notes=\n"
- if 'Maintainer Notes' in app:
- wikidata += metadata.description_wiki(app['Maintainer Notes']) + "\n"
+ if app.MaintainerNotes:
+ wikidata += metadata.description_wiki(app.MaintainerNotes) + "\n"
wikidata += "\nMetadata: [https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/{0}.txt current] [https://gitlab.com/fdroid/fdroiddata/commits/master/metadata/{0}.txt history]\n".format(appid)
# Get a list of all packages for this application...
buildfails = False
for apk in apks:
if apk['id'] == appid:
- if str(apk['versioncode']) == app['Current Version Code']:
+ if str(apk['versioncode']) == app.CurrentVersionCode:
gotcurrentver = True
apklist.append(apk)
# Include ones we can't build, as a special case...
- for thisbuild in app['builds']:
+ for thisbuild in app.builds:
if thisbuild['disable']:
- if thisbuild['vercode'] == app['Current Version Code']:
+ if thisbuild['vercode'] == app.CurrentVersionCode:
cantupdate = True
# TODO: Nasty: vercode is a string in the build, and an int elsewhere
apklist.append({'versioncode': int(thisbuild['vercode']),
'version': thisbuild['version'],
'buildproblem': "The build for this version appears to have failed. Check the [[{0}/lastbuild_{1}|build log]].".format(appid, thisbuild['vercode']),
})
- if app['Current Version Code'] == '0':
+ if app.CurrentVersionCode == '0':
cantupdate = True
# Sort with most recent first...
apklist = sorted(apklist, key=lambda apk: apk['versioncode'], reverse=True)
wikidata += "We don't have the current version of this app."
else:
wikidata += "We have the current version of this app."
- wikidata += " (Check mode: " + app['Update Check Mode'] + ") "
- wikidata += " (Auto-update mode: " + app['Auto Update Mode'] + ")\n\n"
- if len(app['No Source Since']) > 0:
- wikidata += "This application has partially or entirely been missing source code since version " + app['No Source Since'] + ".\n\n"
- if len(app['Current Version']) > 0:
- wikidata += "The current (recommended) version is " + app['Current Version']
- wikidata += " (version code " + app['Current Version Code'] + ").\n\n"
+ wikidata += " (Check mode: " + app.UpdateCheckMode + ") "
+ wikidata += " (Auto-update mode: " + app.AutoUpdateMode + ")\n\n"
+ if len(app.NoSourceSince) > 0:
+ wikidata += "This application has partially or entirely been missing source code since version " + app.NoSourceSince + ".\n\n"
+ if len(app.CurrentVersion) > 0:
+ wikidata += "The current (recommended) version is " + app.CurrentVersion
+ wikidata += " (version code " + app.CurrentVersionCode + ").\n\n"
validapks = 0
for apk in apklist:
wikidata += "==" + apk['version'] + "==\n"
wikidata += "Version code: " + str(apk['versioncode']) + '\n'
wikidata += '\n[[Category:' + wikicat + ']]\n'
- if len(app['No Source Since']) > 0:
+ if len(app.NoSourceSince) > 0:
wikidata += '\n[[Category:Apps missing source code]]\n'
- if validapks == 0 and not app['Disabled']:
+ if validapks == 0 and not app.Disabled:
wikidata += '\n[[Category:Apps with no packages]]\n'
- if cantupdate and not app['Disabled']:
+ if cantupdate and not app.Disabled:
wikidata += "\n[[Category:Apps we can't update]]\n"
- if buildfails and not app['Disabled']:
+ if buildfails and not app.Disabled:
wikidata += "\n[[Category:Apps with failing builds]]\n"
- elif not gotcurrentver and not cantupdate and not app['Disabled'] and app['Update Check Mode'] != "Static":
+ elif not gotcurrentver and not cantupdate and not app.Disabled and app.UpdateCheckMode != "Static":
wikidata += '\n[[Category:Apps to Update]]\n'
- if app['Disabled']:
+ if app.Disabled:
wikidata += '\n[[Category:Apps that are disabled]]\n'
- if app['Update Check Mode'] == 'None' and not app['Disabled']:
+ if app.UpdateCheckMode == 'None' and not app.Disabled:
wikidata += '\n[[Category:Apps with no update check]]\n'
- for appcat in app['Categories']:
+ for appcat in app.Categories:
wikidata += '\n[[Category:{0}]]\n'.format(appcat)
# We can't have underscores in the page name, even if they're in
# Make a redirect from the name to the ID too, unless there's
# already an existing page with the name and it isn't a redirect.
noclobber = False
- apppagename = app['Name'].replace('_', ' ')
+ apppagename = app.Name.replace('_', ' ')
apppagename = apppagename.replace('{', '')
apppagename = apppagename.replace('}', ' ')
apppagename = apppagename.replace(':', ' ')
:param repodirs: the repo directories to process
"""
for appid, app in apps.iteritems():
- for build in app['builds']:
+ for build in app.builds:
if not build['disable']:
continue
apkfilename = appid + '_' + str(build['vercode']) + '.apk'
for appid in sortedids:
app = apps[appid]
- if app['Disabled'] is not None:
+ if app.Disabled is not None:
continue
# Get a list of the apks for this app...
continue
apel = doc.createElement("application")
- apel.setAttribute("id", app['id'])
+ apel.setAttribute("id", app.id)
root.appendChild(apel)
- addElement('id', app['id'], doc, apel)
- if 'added' in app:
- addElement('added', time.strftime('%Y-%m-%d', app['added']), doc, apel)
- if 'lastupdated' in app:
- addElement('lastupdated', time.strftime('%Y-%m-%d', app['lastupdated']), doc, apel)
- addElement('name', app['Name'], doc, apel)
- addElement('summary', app['Summary'], doc, apel)
- if app['icon']:
- addElement('icon', app['icon'], doc, apel)
+ addElement('id', app.id, doc, apel)
+ if app.added:
+ addElement('added', time.strftime('%Y-%m-%d', app.added), doc, apel)
+ if app.lastupdated:
+ addElement('lastupdated', time.strftime('%Y-%m-%d', app.lastupdated), doc, apel)
+ addElement('name', app.Name, doc, apel)
+ addElement('summary', app.Summary, doc, apel)
+ if app.icon:
+ addElement('icon', app.icon, doc, apel)
def linkres(appid):
if appid in apps:
- return ("fdroid.app:" + appid, apps[appid]['Name'])
+ return ("fdroid.app:" + appid, apps[appid].Name)
raise MetaDataException("Cannot resolve app id " + appid)
addElement('desc',
- metadata.description_html(app['Description'], linkres),
+ metadata.description_html(app.Description, linkres),
doc, apel)
- addElement('license', app['License'], doc, apel)
- if 'Categories' in app and app['Categories']:
- addElement('categories', ','.join(app["Categories"]), doc, apel)
+ addElement('license', app.License, doc, apel)
+ if app.Categories:
+ addElement('categories', ','.join(app.Categories), doc, apel)
# We put the first (primary) category in LAST, which will have
# the desired effect of making clients that only understand one
# category see that one.
- addElement('category', app["Categories"][0], doc, apel)
- addElement('web', app['Web Site'], doc, apel)
- addElement('source', app['Source Code'], doc, apel)
- addElement('tracker', app['Issue Tracker'], doc, apel)
- addElementNonEmpty('changelog', app['Changelog'], doc, apel)
- addElementNonEmpty('donate', app['Donate'], doc, apel)
- addElementNonEmpty('bitcoin', app['Bitcoin'], doc, apel)
- addElementNonEmpty('litecoin', app['Litecoin'], doc, apel)
- addElementNonEmpty('flattr', app['FlattrID'], doc, apel)
+ addElement('category', app.Categories[0], doc, apel)
+ addElement('web', app.WebSite, doc, apel)
+ addElement('source', app.SourceCode, doc, apel)
+ addElement('tracker', app.IssueTracker, doc, apel)
+ addElementNonEmpty('changelog', app.Changelog, doc, apel)
+ addElementNonEmpty('donate', app.Donate, doc, apel)
+ addElementNonEmpty('bitcoin', app.Bitcoin, doc, apel)
+ addElementNonEmpty('litecoin', app.Litecoin, doc, apel)
+ addElementNonEmpty('flattr', app.FlattrID, doc, apel)
# These elements actually refer to the current version (i.e. which
# one is recommended. They are historically mis-named, and need
# changing, but stay like this for now to support existing clients.
- addElement('marketversion', app['Current Version'], doc, apel)
- addElement('marketvercode', app['Current Version Code'], doc, apel)
+ addElement('marketversion', app.CurrentVersion, doc, apel)
+ addElement('marketvercode', app.CurrentVersionCode, doc, apel)
- if app['AntiFeatures']:
- af = app['AntiFeatures']
+ if app.AntiFeatures:
+ af = app.AntiFeatures
if af:
addElementNonEmpty('antifeatures', ','.join(af), doc, apel)
- if app['Provides']:
- pv = app['Provides'].split(',')
+ if app.Provides:
+ pv = app.Provides.split(',')
addElementNonEmpty('provides', ','.join(pv), doc, apel)
- if app['Requires Root']:
+ if app.RequiresRoot:
addElement('requirements', 'root', doc, apel)
# Sort the apk list into version order, just so the web site
# find the APK for the "Current Version"
if current_version_code < apk['versioncode']:
current_version_code = apk['versioncode']
- if current_version_code < int(app['Current Version Code']):
+ if current_version_code < int(app.CurrentVersionCode):
current_version_file = apk['apkname']
apkel = doc.createElement("package")
if current_version_file is not None \
and config['make_current_version_link'] \
and repodir == 'repo': # only create these
- sanitized_name = re.sub('''[ '"&%?+=/]''', '',
- app[config['current_version_name_source']])
+ namefield = config['current_version_name_source']
+ sanitized_name = re.sub('''[ '"&%?+=/]''', '', app.get_field(namefield))
apklinkname = sanitized_name + '.apk'
current_version_path = os.path.join(repodir, current_version_file)
if os.path.islink(apklinkname):
for appid, app in apps.iteritems():
- if app['Archive Policy']:
- keepversions = int(app['Archive Policy'][:-9])
+ if app.ArchivePolicy:
+ keepversions = int(app.ArchivePolicy[:-9])
else:
keepversions = defaultkeepversions
# Generate a list of categories...
categories = set()
for app in apps.itervalues():
- categories.update(app['Categories'])
+ categories.update(app.Categories)
# Read known apks data (will be updated and written back when we've finished)
knownapks = common.KnownApks()
# same time.
for appid, app in apps.iteritems():
bestver = 0
- added = None
- lastupdated = None
for apk in apks + archapks:
if apk['id'] == appid:
if apk['versioncode'] > bestver:
bestapk = apk
if 'added' in apk:
- if not added or apk['added'] < added:
- added = apk['added']
- if not lastupdated or apk['added'] > lastupdated:
- lastupdated = apk['added']
+ if not app.added or apk['added'] < app.added:
+ app.added = apk['added']
+ if not app.lastupdated or apk['added'] > app.lastupdated:
+ app.lastupdated = apk['added']
- if added:
- app['added'] = added
- else:
+ if not app.added:
logging.debug("Don't know when " + appid + " was added")
- if lastupdated:
- app['lastupdated'] = lastupdated
- else:
+ if not app.lastupdated:
logging.debug("Don't know when " + appid + " was last updated")
if bestver == 0:
- if app['Name'] is None:
- app['Name'] = app['Auto Name'] or appid
- app['icon'] = None
+ if app.Name is None:
+ app.Name = app.AutoName or appid
+ app.icon = None
logging.debug("Application " + appid + " has no packages")
else:
- if app['Name'] is None:
- app['Name'] = bestapk['name']
- app['icon'] = bestapk['icon'] if 'icon' in bestapk else None
+ if app.Name is None:
+ app.Name = bestapk['name']
+ app.icon = bestapk['icon'] if 'icon' in bestapk else None
# Sort the app list by name, then the web site doesn't have to by default.
# (we had to wait until we'd scanned the apks to do this, because mostly the
# name comes from there!)
- sortedids = sorted(apps.iterkeys(), key=lambda appid: apps[appid]['Name'].upper())
+ sortedids = sorted(apps.iterkeys(), key=lambda appid: apps[appid].Name.upper())
# APKs are placed into multiple repos based on the app package, providing
# per-app subscription feeds for nightly builds and things like it
appid = line.rstrip()
data += appid + "\t"
app = apps[appid]
- data += app['Name'] + "\t"
- if app['icon'] is not None:
- data += app['icon'] + "\t"
- data += app['License'] + "\n"
+ data += app.Name + "\t"
+ if app.icon is not None:
+ data += app.icon + "\t"
+ data += app.License + "\n"
with open(os.path.join(repodirs[0], 'latestapps.dat'), 'w') as f:
f.write(data)
config['sdk_path'] = os.getenv('ANDROID_HOME')
config['build_tools'] = 'FAKE_BUILD_TOOLS_VERSION'
fdroidserver.common.config = config
- app = dict()
- app['id'] = 'org.fdroid.froid'
+ app = fdroidserver.metadata.App()
+ app.id = 'org.fdroid.froid'
build = dict(fdroidserver.metadata.flag_defaults)
build['commit'] = 'master'
build['forceversion'] = True
fdroidserver.common.config['sdk_path'] = '/fake/path/to/android-sdk'
url = 'https://gitlab.com/fdroid/fdroidclient'
- appid, app = fdroidserver.metadata.get_default_app_info()
- app['Update Check Mode'] = "Tags"
+ app = fdroidserver.metadata.get_default_app_info()
+ app.UpdateCheckMode = "Tags"
root_dir, src_dir = import_proxy.get_metadata_from_url(app, url)
- self.assertEquals(app['Repo Type'], 'git')
- self.assertEquals(app['Web Site'], 'https://gitlab.com/fdroid/fdroidclient')
- self.assertEquals(app['Repo'], 'https://gitlab.com/fdroid/fdroidclient.git')
+ self.assertEquals(app.RepoType, 'git')
+ self.assertEquals(app.WebSite, 'https://gitlab.com/fdroid/fdroidclient')
+ self.assertEquals(app.Repo, 'https://gitlab.com/fdroid/fdroidclient.git')
if __name__ == "__main__":
apps = fdroidserver.metadata.read_metadata(xref=True)
for appid in ('org.smssecure.smssecure', 'org.adaway', 'net.osmand.plus', 'org.videolan.vlc'):
- with open(os.path.join('metadata', appid + '.pickle'), 'r') as f:
+ app = apps[appid]
+ savepath = os.path.join('metadata', appid + '.pickle')
+ self.assertTrue(appid in apps)
+ with open(savepath, 'r') as f:
frompickle = pickle.load(f)
- self.assertTrue(appid in apps.keys())
- self.assertEquals(apps[appid], frompickle)
+ frommeta = app.field_dict()
+ self.assertEquals(frommeta, frompickle)
+ # with open(savepath, 'wb') as f:
+ # pickle.dump(app, f)
if __name__ == "__main__":
p178
sS'Disabled'
p179
-NsS'Update Check Name'
+NsS'added'
p180
-NsS'Vercode Operation'
+NsS'lastupdated'
p181
-NsS'Current Version'
+NsS'Update Check Name'
p182
-S'1.9.5'
+NsS'Vercode Operation'
p183
+NsS'Current Version'
+p184
+S'1.9.5'
+p185
s.
p483
NsS'Vercode Operation'
p484
-Ns.
\ No newline at end of file
+NsS'added'
+p485
+NsS'lastupdated'
+p486
+Ns.
p227
NsS'Vercode Operation'
p228
-Ns.
\ No newline at end of file
+NsS'added'
+p229
+NsS'lastupdated'
+p230
+Ns.
p1519
sS'Disabled'
p1520
-NsS'Update Check Name'
+NsS'added'
p1521
-NsS'Vercode Operation'
+NsS'lastupdated'
p1522
-S'%c + 5'
+NsS'Update Check Name'
p1523
-sS'Current Version'
+NsS'Vercode Operation'
p1524
-S'1.2.6'
+S'%c + 5'
p1525
+sS'Current Version'
+p1526
+S'1.2.6'
+p1527
s.