chiark / gitweb /
Merge branch 'py3' into 'master'
authorDaniel Martí <mvdan@mvdan.cc>
Fri, 11 Mar 2016 23:51:56 +0000 (23:51 +0000)
committerDaniel Martí <mvdan@mvdan.cc>
Fri, 11 Mar 2016 23:51:56 +0000 (23:51 +0000)
Python 3

I tried to keep commits separate, so if anything causes trouble, it can be reverted or changed easily.

* pre-commit hooks pass
* all tests pass
* My use of `build`, `checkupdates`, `lint`, `import`, `publish` and `update` work as usual
* 2to3 does not report anything useful anymore (only useless parentheses and list() encapsulation of iterators)
* rewritemeta works exactly as usual

CC @eighthave

See merge request !88

38 files changed:
.gitignore
.gitlab-ci.yml
README.md
docs/fdroid.texi
examples/config.py
examples/makebuildserver.config.py
fdroid
fdroidserver/build.py
fdroidserver/checkupdates.py
fdroidserver/common.py
fdroidserver/gpgsign.py
fdroidserver/import.py
fdroidserver/init.py
fdroidserver/install.py
fdroidserver/lint.py
fdroidserver/metadata.py
fdroidserver/net.py
fdroidserver/publish.py
fdroidserver/readmeta.py
fdroidserver/rewritemeta.py
fdroidserver/scanner.py
fdroidserver/server.py
fdroidserver/signindex.py
fdroidserver/stats.py
fdroidserver/update.py
fdroidserver/verify.py
hooks/pre-commit
makebuildserver
setup.py
tests/build.TestCase
tests/common.TestCase
tests/complete-ci-tests
tests/description-parsing.py
tests/import.TestCase
tests/install.TestCase
tests/metadata.TestCase
tests/run-tests
tests/update.TestCase

index 9bb942bc5e7151b70339cd4604fa793ac55ff84d..9a46bd2de5e1c2c8351507a4f79d9f29a016fa22 100644 (file)
@@ -15,4 +15,4 @@ pylint.parseable
 docs/html/
 
 # files generated by tests
-tests/getsig/tmp/
+tmp/
index c9a85120784232cb510b573792982a4a0793d47e..94da7be2ca8990434e2d7483e032cea1407a2f1c 100644 (file)
@@ -3,6 +3,7 @@ image: mvdan/fdroid-ci:latest
 test:
   script:
     - apt-get update
-    - apt-get install -y python-dev gcc
+    - apt-get install -y python3-dev gcc
+    - pip3 install -e .
     - cd tests
     - ./complete-ci-tests
index 46dbdb0c8a0ac6f12b5cd45ba30062847eb6a486..6bc1379df51dbc6e0eb25ee673e5c605ec314f60 100644 (file)
--- a/README.md
+++ b/README.md
@@ -23,8 +23,7 @@ install, and keep track of updates on your device.
 
 ### Installing
 
-Note that only Python 2 is supported. We recommend version 2.7.7 or
-later.
+Note that only Python 3 is supported. We recommend version 3.4 or later.
 
 The easiest way to install the `fdroidserver` tools is on Ubuntu, Mint or other
 Ubuntu based distributions, you can install using:
@@ -56,7 +55,7 @@ or Cygwin, you can use it:
 
 Python's `pip` also works:
 
-       sudo pip install fdroidserver
+       sudo pip3 install fdroidserver
 
 The combination of `virtualenv` and `pip` is great for testing out the
 latest versions of `fdroidserver`. Using `pip`, `fdroidserver` can
@@ -67,7 +66,7 @@ via other mechanisms like Brew/dnf/pacman/emerge/Fink/MacPorts.
 
 For Debian based distributions:
 
-       apt-get install python-dev python-pip python-virtualenv
+       apt-get install python3-dev python3-pip virtualenv
 
 Then here's how to install:
 
@@ -75,5 +74,5 @@ Then here's how to install:
        cd fdroidserver
        virtualenv env/
        source env/bin/activate
-       pip install -e .
-       python2 setup.py install
+       pip3 install -e .
+       python3 setup.py install
index 6e2434c9abd1ed2b23d47aab25e1a047e3bb3847..29cac7e44aa22f3efefa380c86fb7a57098b958c 100644 (file)
@@ -83,9 +83,7 @@ intended usage. At the very least, you'll need:
 @item
 GNU/Linux
 @item
-Python 2.x
-To be sure of being able to process all apk files without error, you need
-2.7.7 or later. See @code{http://bugs.python.org/issue14315}.
+Python 3.4 or later
 @item
 The Android SDK Tools and Build-tools.
 Note that F-Droid does not assume that you have the Android SDK in your
index 54c326fe584736f3d0731f40dfc49e58222e5aa3..f022c2b0a76010aa5992eb0b1f444d2de671b888 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # Copy this file to config.py, then amend the settings below according to
 # your system configuration.
index 9eae743c4efa98b76d62452eea99bcf5a13e1173..b43777ed28f78f8c4721eea961d28fe8551b55b9 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # You may want to alter these before running ./makebuildserver
 
diff --git a/fdroid b/fdroid
index e24c0081ed8b464c573a13f385ca5e2aee0448e6..045f2aacf8e5f133c61fff43c126d7bcb1add75b 100755 (executable)
--- a/fdroid
+++ b/fdroid
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # fdroid.py - part of the FDroid server tools
 # Copyright (C) 2010-2015, Ciaran Gultnieks, ciaran@ciarang.com
index ed131d67e5036c383419d7dc2f7ee796941b7ca1..10f40dc478da8dc214373adc271ac01ab4b67b9a 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # build.py - part of the FDroid server tools
 # Copyright (C) 2010-2014, Ciaran Gultnieks, ciaran@ciarang.com
@@ -28,15 +27,15 @@ import tarfile
 import traceback
 import time
 import json
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
 from argparse import ArgumentParser
 import logging
 
-import common
-import net
-import metadata
-import scanner
-from common import FDroidException, BuildException, VCSException, FDroidPopen, SdkToolsPopen
+from . import common
+from . import net
+from . import metadata
+from . import scanner
+from .common import FDroidException, BuildException, VCSException, FDroidPopen, SdkToolsPopen
 
 try:
     import paramiko
@@ -463,7 +462,7 @@ def build_local(app, build, vcs, build_dir, output_dir, srclib_dir, extlib_dir,
         if not ndk_path:
             logging.critical("Android NDK version '%s' could not be found!" % build.ndk or 'r10e')
             logging.critical("Configured versions:")
-            for k, v in config['ndk_paths'].iteritems():
+            for k, v in config['ndk_paths'].items():
                 if k.endswith("_orig"):
                     continue
                 logging.critical("  %s: %s" % (k, v))
@@ -1071,7 +1070,7 @@ def main():
         raise FDroidException("No apps to process.")
 
     if options.latest:
-        for app in apps.itervalues():
+        for app in apps.values():
             for build in reversed(app.builds):
                 if build.disable and not options.force:
                     continue
@@ -1087,7 +1086,7 @@ def main():
     # Build applications...
     failed_apps = {}
     build_succeeded = []
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
 
         first = True
 
index 71931e3fa442896e6b10b408f0dd68d5ba725bfa..d9514cd93817d22863f0fd9f13c48679ee171356 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # checkupdates.py - part of the FDroid server tools
 # Copyright (C) 2010-2015, Ciaran Gultnieks, ciaran@ciarang.com
 import sys
 import os
 import re
-import urllib2
+import urllib.request
+import urllib.error
 import time
 import subprocess
 from argparse import ArgumentParser
 import traceback
-import HTMLParser
+from html.parser import HTMLParser
 from distutils.version import LooseVersion
 import logging
 import copy
 
-import common
-import metadata
-from common import VCSException, FDroidException
-from metadata import MetaDataException
+from . import common
+from . import metadata
+from .common import VCSException, FDroidException
+from .metadata import MetaDataException
 
 
 # Check for a new version by looking at a document retrieved via HTTP.
@@ -52,8 +52,8 @@ def check_http(app):
         vercode = "99999999"
         if len(urlcode) > 0:
             logging.debug("...requesting {0}".format(urlcode))
-            req = urllib2.Request(urlcode, None)
-            resp = urllib2.urlopen(req, None, 20)
+            req = urllib.request.Request(urlcode, None)
+            resp = urllib.request.urlopen(req, None, 20)
             page = resp.read()
 
             m = re.search(codeex, page)
@@ -65,8 +65,8 @@ def check_http(app):
         if len(urlver) > 0:
             if urlver != '.':
                 logging.debug("...requesting {0}".format(urlver))
-                req = urllib2.Request(urlver, None)
-                resp = urllib2.urlopen(req, None, 20)
+                req = urllib.request.Request(urlver, None)
+                resp = urllib.request.urlopen(req, None, 20)
                 page = resp.read()
 
             m = re.search(verex, page)
@@ -280,11 +280,11 @@ def check_gplay(app):
     time.sleep(15)
     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)
+    req = urllib.request.Request(url, None, headers)
     try:
-        resp = urllib2.urlopen(req, None, 20)
+        resp = urllib.request.urlopen(req, None, 20)
         page = resp.read()
-    except urllib2.HTTPError as e:
+    except urllib.error.HTTPError as e:
         return (None, str(e.code))
     except Exception as e:
         return (None, 'Failed:' + str(e))
@@ -293,7 +293,7 @@ def check_gplay(app):
 
     m = re.search('itemprop="softwareVersion">[ ]*([^<]+)[ ]*</div>', page)
     if m:
-        html_parser = HTMLParser.HTMLParser()
+        html_parser = HTMLParser()
         version = html_parser.unescape(m.group(1))
 
     if version == 'Varies with device':
@@ -559,7 +559,7 @@ def main():
                                      .format(common.getappname(app), version))
         return
 
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
 
         if options.autoonly and app.AutoUpdateMode in ('None', 'Static'):
             logging.debug("Nothing to do for {0}...".format(appid))
index 1b199bb65c1d34d37787ec4ec3a27a351bfcadfb..ab6f238ac535b36a4c5cdebc9bd057aceb6d5b84 100644 (file)
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # common.py - part of the FDroid server tools
 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
@@ -20,6 +20,7 @@
 # common.py is imported by all modules, so do not import third-party
 # libraries here as they will become a requirement for all commands.
 
+import io
 import os
 import sys
 import re
@@ -32,19 +33,15 @@ import operator
 import logging
 import hashlib
 import socket
+import base64
 import xml.etree.ElementTree as XMLElementTree
 
-try:
-    # Python 2
-    from Queue import Queue
-except ImportError:
-    # Python 3
-    from queue import Queue
+from queue import Queue
 
 from zipfile import ZipFile
 
-import metadata
-from fdroidserver.asynchronousfilereader import AsynchronousFileReader
+import fdroidserver.metadata
+from .asynchronousfilereader import AsynchronousFileReader
 
 
 XMLElementTree.register_namespace('android', 'http://schemas.android.com/apk/res/android')
@@ -206,7 +203,9 @@ def read_config(opts, config_file='config.py'):
     config = {}
 
     logging.debug("Reading %s" % config_file)
-    execfile(config_file, config)
+    with io.open(config_file, "rb") as f:
+        code = compile(f.read(), config_file, 'exec')
+        exec(code, None, config)
 
     # smartcardoptions must be a list since its command line args for Popen
     if 'smartcardoptions' in config:
@@ -244,9 +243,9 @@ def read_config(opts, config_file='config.py'):
             config[k] = clean_description(config[k])
 
     if 'serverwebroot' in config:
-        if isinstance(config['serverwebroot'], basestring):
+        if isinstance(config['serverwebroot'], str):
             roots = [config['serverwebroot']]
-        elif all(isinstance(item, basestring) for item in config['serverwebroot']):
+        elif all(isinstance(item, str) for item in config['serverwebroot']):
             roots = config['serverwebroot']
         else:
             raise TypeError('only accepts strings, lists, and tuples')
@@ -339,9 +338,9 @@ def write_password_file(pwtype, password=None):
     filename = '.fdroid.' + pwtype + '.txt'
     fd = os.open(filename, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600)
     if password is None:
-        os.write(fd, config[pwtype])
+        os.write(fd, config[pwtype].encode('utf-8'))
     else:
-        os.write(fd, password)
+        os.write(fd, password.encode('utf-8'))
     os.close(fd)
     config[pwtype + 'file'] = filename
 
@@ -378,7 +377,7 @@ def read_app_args(args, allapps, allow_vercodes=False):
         return allapps
 
     apps = {}
-    for appid, app in allapps.iteritems():
+    for appid, app in allapps.items():
         if appid in vercodes:
             apps[appid] = app
 
@@ -391,7 +390,7 @@ def read_app_args(args, allapps, allow_vercodes=False):
         raise FDroidException("No packages specified")
 
     error = False
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
         vc = vercodes[appid]
         if not vc:
             continue
@@ -486,9 +485,9 @@ def getvcs(vcstype, remote, local):
 
 
 def getsrclibvcs(name):
-    if name not in metadata.srclibs:
+    if name not in fdroidserver.metadata.srclibs:
         raise VCSException("Missing srclib " + name)
-    return metadata.srclibs[name]['Repo Type']
+    return fdroidserver.metadata.srclibs[name]['Repo Type']
 
 
 class vcs:
@@ -532,6 +531,7 @@ class vcs:
         # automatically if either of those things changes.
         fdpath = os.path.join(self.local, '..',
                               '.fdroidvcs-' + os.path.basename(self.local))
+        fdpath = os.path.normpath(fdpath)
         cdata = self.repotype() + ' ' + self.remote
         writeback = True
         deleterepo = False
@@ -563,7 +563,8 @@ class vcs:
 
         # If necessary, write the .fdroidvcs file.
         if writeback and not self.clone_failed:
-            with open(fdpath, 'w') as f:
+            os.makedirs(os.path.dirname(fdpath), exist_ok=True)
+            with open(fdpath, 'w+') as f:
                 f.write(cdata)
 
         if exc is not None:
@@ -947,7 +948,7 @@ def retrieve_string(app_dir, string, xmlfiles=None):
         if element.text is None:
             return ""
         s = XMLElementTree.tostring(element, encoding='utf-8', method='text')
-        return s.strip()
+        return s.decode('utf-8').strip()
 
     for path in xmlfiles:
         if not os.path.isfile(path):
@@ -995,7 +996,7 @@ def fetch_real_name(app_dir, flavours):
             continue
         if "{http://schemas.android.com/apk/res/android}label" not in app.attrib:
             continue
-        label = app.attrib["{http://schemas.android.com/apk/res/android}label"].encode('utf-8')
+        label = app.attrib["{http://schemas.android.com/apk/res/android}label"]
         result = retrieve_string_singleline(app_dir, label)
         if result:
             result = result.strip()
@@ -1008,15 +1009,16 @@ def get_library_references(root_dir):
     proppath = os.path.join(root_dir, 'project.properties')
     if not os.path.isfile(proppath):
         return libraries
-    for line in file(proppath):
-        if not line.startswith('android.library.reference.'):
-            continue
-        path = line.split('=')[1].strip()
-        relpath = os.path.join(root_dir, path)
-        if not os.path.isdir(relpath):
-            continue
-        logging.debug("Found subproject at %s" % path)
-        libraries.append(path)
+    with open(proppath, 'r') as f:
+        for line in f:
+            if not line.startswith('android.library.reference.'):
+                continue
+            path = line.split('=')[1].strip()
+            relpath = os.path.join(root_dir, path)
+            if not os.path.isdir(relpath):
+                continue
+            logging.debug("Found subproject at %s" % path)
+            libraries.append(path)
     return libraries
 
 
@@ -1082,38 +1084,39 @@ def parse_androidmanifests(paths, app):
         package = None
 
         if gradle:
-            for line in file(path):
-                if gradle_comment.match(line):
-                    continue
-                # Grab first occurence of each to avoid running into
-                # alternative flavours and builds.
-                if not package:
-                    matches = psearch_g(line)
-                    if matches:
-                        s = matches.group(2)
-                        if app_matches_packagename(app, s):
-                            package = s
-                if not version:
-                    matches = vnsearch_g(line)
-                    if matches:
-                        version = matches.group(2)
-                if not vercode:
-                    matches = vcsearch_g(line)
-                    if matches:
-                        vercode = matches.group(1)
+            with open(path, 'r') as f:
+                for line in f:
+                    if gradle_comment.match(line):
+                        continue
+                    # Grab first occurence of each to avoid running into
+                    # alternative flavours and builds.
+                    if not package:
+                        matches = psearch_g(line)
+                        if matches:
+                            s = matches.group(2)
+                            if app_matches_packagename(app, s):
+                                package = s
+                    if not version:
+                        matches = vnsearch_g(line)
+                        if matches:
+                            version = matches.group(2)
+                    if not vercode:
+                        matches = vcsearch_g(line)
+                        if matches:
+                            vercode = matches.group(1)
         else:
             try:
                 xml = parse_xml(path)
                 if "package" in xml.attrib:
-                    s = xml.attrib["package"].encode('utf-8')
+                    s = xml.attrib["package"]
                     if app_matches_packagename(app, s):
                         package = s
                 if "{http://schemas.android.com/apk/res/android}versionName" in xml.attrib:
-                    version = xml.attrib["{http://schemas.android.com/apk/res/android}versionName"].encode('utf-8')
+                    version = xml.attrib["{http://schemas.android.com/apk/res/android}versionName"]
                     base_dir = os.path.dirname(path)
                     version = retrieve_string_singleline(base_dir, version)
                 if "{http://schemas.android.com/apk/res/android}versionCode" in xml.attrib:
-                    a = xml.attrib["{http://schemas.android.com/apk/res/android}versionCode"].encode('utf-8')
+                    a = xml.attrib["{http://schemas.android.com/apk/res/android}versionCode"]
                     if string_is_integer(a):
                         vercode = a
             except Exception:
@@ -1209,10 +1212,10 @@ def getsrclib(spec, srclib_dir, subdir=None, basepath=False,
         if '/' in name:
             name, subdir = name.split('/', 1)
 
-    if name not in metadata.srclibs:
+    if name not in fdroidserver.metadata.srclibs:
         raise VCSException('srclib ' + name + ' not found.')
 
-    srclib = metadata.srclibs[name]
+    srclib = fdroidserver.metadata.srclibs[name]
 
     sdir = os.path.join(srclib_dir, name)
 
@@ -1510,7 +1513,7 @@ def getpaths_map(build_dir, globpaths):
 def getpaths(build_dir, globpaths):
     paths_map = getpaths_map(build_dir, globpaths)
     paths = set()
-    for k, v in paths_map.iteritems():
+    for k, v in paths_map.items():
         for p in v:
             paths.add(p)
     return paths
@@ -1526,12 +1529,13 @@ class KnownApks:
         self.path = os.path.join('stats', 'known_apks.txt')
         self.apks = {}
         if os.path.isfile(self.path):
-            for line in file(self.path):
-                t = line.rstrip().split(' ')
-                if len(t) == 2:
-                    self.apks[t[0]] = (t[1], None)
-                else:
-                    self.apks[t[0]] = (t[1], time.strptime(t[2], '%Y-%m-%d'))
+            with open(self.path, 'r') as f:
+                for line in f:
+                    t = line.rstrip().split(' ')
+                    if len(t) == 2:
+                        self.apks[t[0]] = (t[1], None)
+                    else:
+                        self.apks[t[0]] = (t[1], time.strptime(t[2], '%Y-%m-%d'))
         self.changed = False
 
     def writeifchanged(self):
@@ -1542,7 +1546,7 @@ class KnownApks:
             os.mkdir('stats')
 
         lst = []
-        for apk, app in self.apks.iteritems():
+        for apk, app in self.apks.items():
             appid, added = app
             line = apk + ' ' + appid
             if added:
@@ -1573,7 +1577,7 @@ class KnownApks:
     # with the most recent first.
     def getlatest(self, num):
         apps = {}
-        for apk, app in self.apks.iteritems():
+        for apk, app in self.apks.items():
             appid, added = app
             if added:
                 if appid in apps:
@@ -1581,7 +1585,7 @@ class KnownApks:
                         apps[appid] = added
                 else:
                     apps[appid] = added
-        sortedapps = sorted(apps.iteritems(), key=operator.itemgetter(1))[-num:]
+        sortedapps = sorted(apps.items(), key=operator.itemgetter(1))[-num:]
         lst = [app for app, _ in sortedapps]
         lst.reverse()
         return lst
@@ -1604,8 +1608,9 @@ def isApkDebuggable(apkfile, config):
 
 
 class PopenResult:
-    returncode = None
-    output = ''
+    def __init__(self):
+        self.returncode = None
+        self.output = None
 
 
 def SdkToolsPopen(commands, cwd=None, output=True):
@@ -1620,9 +1625,9 @@ def SdkToolsPopen(commands, cwd=None, output=True):
                        cwd=cwd, output=output)
 
 
-def FDroidPopen(commands, cwd=None, output=True, stderr_to_stdout=True):
+def FDroidPopenBytes(commands, cwd=None, output=True, stderr_to_stdout=True):
     """
-    Run a command and capture the possibly huge output.
+    Run a command and capture the possibly huge output as bytes.
 
     :param commands: command and argument list like in subprocess.Popen
     :param cwd: optionally specifies a working directory
@@ -1653,13 +1658,14 @@ def FDroidPopen(commands, cwd=None, output=True, stderr_to_stdout=True):
         while not stderr_reader.eof():
             while not stderr_queue.empty():
                 line = stderr_queue.get()
-                sys.stderr.write(line)
+                sys.stderr.buffer.write(line)
                 sys.stderr.flush()
 
             time.sleep(0.1)
 
     stdout_queue = Queue()
     stdout_reader = AsynchronousFileReader(p.stdout, stdout_queue)
+    buf = io.BytesIO()
 
     # Check the queue for output (until there is no more to get)
     while not stdout_reader.eof():
@@ -1667,13 +1673,28 @@ def FDroidPopen(commands, cwd=None, output=True, stderr_to_stdout=True):
             line = stdout_queue.get()
             if output and options.verbose:
                 # Output directly to console
-                sys.stderr.write(line)
+                sys.stderr.buffer.write(line)
                 sys.stderr.flush()
-            result.output += line
+            buf.write(line)
 
         time.sleep(0.1)
 
     result.returncode = p.wait()
+    result.output = buf.getvalue()
+    buf.close()
+    return result
+
+
+def FDroidPopen(commands, cwd=None, output=True, stderr_to_stdout=True):
+    """
+    Run a command and capture the possibly huge output as a str.
+
+    :param commands: command and argument list like in subprocess.Popen
+    :param cwd: optionally specifies a working directory
+    :returns: A PopenResult.
+    """
+    result = FDroidPopenBytes(commands, cwd, output, stderr_to_stdout)
+    result.output = result.output.decode('utf-8')
     return result
 
 
@@ -1919,8 +1940,9 @@ def genpassword():
     '''generate a random password for when generating keys'''
     h = hashlib.sha256()
     h.update(os.urandom(16))  # salt
-    h.update(bytes(socket.getfqdn()))
-    return h.digest().encode('base64').strip()
+    h.update(socket.getfqdn().encode('utf-8'))
+    passwd = base64.b64encode(h.digest()).strip()
+    return passwd.decode('utf-8')
 
 
 def genkeystore(localconfig):
index 07b0b155b65680172d79196780ed47d2eb4f1c9a..41b5a43f831f314b109ee4031cfd2482ea318432 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # gpgsign.py - part of the FDroid server tools
 # Copyright (C) 2014, Ciaran Gultnieks, ciaran@ciarang.com
@@ -23,8 +22,8 @@ import glob
 from argparse import ArgumentParser
 import logging
 
-import common
-from common import FDroidPopen
+from . import common
+from .common import FDroidPopen
 
 config = None
 options = None
index 14f2f79d4ca2d5aabc4e4d4c55972953b5634b2e..3b80e485cca797c81fd281bcfe4b25caca8abdcf 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # import.py - part of the FDroid server tools
 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
 import sys
 import os
 import shutil
-import urllib
+import urllib.request
 from argparse import ArgumentParser
-from ConfigParser import ConfigParser
+from configparser import ConfigParser
 import logging
-import common
-import metadata
+
+from . import common
+from . import metadata
 
 
 # Get the repo type and address from the given web page. The page is scanned
@@ -35,7 +35,7 @@ import metadata
 # Returns repotype, address, or None, reason
 def getrepofrompage(url):
 
-    req = urllib.urlopen(url)
+    req = urllib.request.urlopen(url)
     if req.getcode() != 200:
         return (None, 'Unable to get ' + url + ' - return code ' + str(req.getcode()))
     page = req.read()
index f8f71bfba75447aa5494a3f6728cd11c02234033..590307d9e8606a3a96edc8ebc1cc9e614ae337f1 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # update.py - part of the FDroid server tools
 # Copyright (C) 2010-2013, Ciaran Gultnieks, ciaran@ciarang.com
@@ -28,7 +27,7 @@ import sys
 from argparse import ArgumentParser
 import logging
 
-import common
+from . import common
 
 config = {}
 options = None
@@ -103,8 +102,8 @@ def main():
             default_sdk_path = '/opt/android-sdk'
             while not options.no_prompt:
                 try:
-                    s = raw_input('Enter the path to the Android SDK ('
-                                  + default_sdk_path + ') here:\n> ')
+                    s = input('Enter the path to the Android SDK ('
+                              + default_sdk_path + ') here:\n> ')
                 except KeyboardInterrupt:
                     print('')
                     sys.exit(1)
index af4a71bee4ef279cf9722c2385cee5e48f7c5634..350003ac7e8cc9b9e0f77785446a91c46b4bb190 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # install.py - part of the FDroid server tools
 # Copyright (C) 2013, Ciaran Gultnieks, ciaran@ciarang.com
@@ -24,8 +23,8 @@ import glob
 from argparse import ArgumentParser
 import logging
 
-import common
-from common import SdkToolsPopen, FDroidException
+from . import common
+from .common import SdkToolsPopen, FDroidException
 
 options = None
 config = None
@@ -82,7 +81,7 @@ def main():
                 continue
             apks[appid] = apkfile
 
-        for appid, apk in apks.iteritems():
+        for appid, apk in apks.items():
             if not apk:
                 raise FDroidException("No signed apk available for %s" % appid)
 
@@ -91,7 +90,7 @@ def main():
         apks = {common.apknameinfo(apkfile)[0]: apkfile for apkfile in
                 sorted(glob.glob(os.path.join(output_dir, '*.apk')))}
 
-    for appid, apk in apks.iteritems():
+    for appid, apk in apks.items():
         # Get device list each time to avoid device not found errors
         devs = devices()
         if not devs:
index 4aa9dcbccc9ace00119e462da3d6ff66d6e81b2b..5c25c33c12b8f97050a528dd27ed076b900f948e 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # lint.py - part of the FDroid server tool
 # Copyright (C) 2013-2014 Daniel Martí <mvdan@mvdan.cc>
 from argparse import ArgumentParser
 import re
 import sys
-from sets import Set
 
-import common
-import metadata
-import rewritemeta
+from . import common
+from . import metadata
+from . import rewritemeta
 
 config = None
 options = None
@@ -118,7 +116,7 @@ regex_checks = {
 
 
 def check_regexes(app):
-    for f, checks in regex_checks.iteritems():
+    for f, checks in regex_checks.items():
         for m, r in checks:
             v = app.get_field(f)
             t = metadata.fieldtype(f)
@@ -205,7 +203,7 @@ def check_empty_fields(app):
     if not app.Categories:
         yield "Categories are not set"
 
-all_categories = Set([
+all_categories = set([
     "Connectivity",
     "Development",
     "Games",
@@ -333,7 +331,7 @@ def main():
     allapps = metadata.read_metadata(xref=True)
     apps = common.read_app_args(options.appid, allapps, False)
 
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
         if app.Disabled:
             continue
 
index bc8708df4377c7889e8dbe164fe6ce1fce2fb7c3..d69e9074cd899b1ea7908a4f748d7df10d5c8992 100644 (file)
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # metadata.py - part of the FDroid server tools
 # Copyright (C) 2013, Ciaran Gultnieks, ciaran@ciarang.com
@@ -23,11 +23,7 @@ import re
 import glob
 import cgi
 import textwrap
-
-try:
-    from cStringIO import StringIO
-except:
-    from StringIO import StringIO
+import io
 
 import yaml
 # use libyaml if it is available
@@ -41,7 +37,7 @@ except ImportError:
 # use the C implementation when available
 import xml.etree.cElementTree as ElementTree
 
-import common
+import fdroidserver.common
 
 srclibs = None
 
@@ -162,11 +158,11 @@ class App():
     # names. Should only be used for tests.
     def field_dict(self):
         d = {}
-        for k, v in self.__dict__.iteritems():
+        for k, v in self.__dict__.items():
             if k == 'builds':
                 d['builds'] = []
                 for build in v:
-                    b = {k: v for k, v in build.__dict__.iteritems() if not k.startswith('_')}
+                    b = {k: v for k, v in build.__dict__.items() if not k.startswith('_')}
                     d['builds'].append(b)
             elif not k.startswith('_'):
                 f = App.attr_to_field(k)
@@ -200,7 +196,7 @@ class App():
 
     # Like dict.update(), but using human-readable field names
     def update_fields(self, d):
-        for f, v in d.iteritems():
+        for f, v in d.items():
             if f == 'builds':
                 for b in v:
                     build = Build()
@@ -352,13 +348,13 @@ class Build():
         version = self.ndk
         if not version:
             version = 'r10e'  # falls back to latest
-        paths = common.config['ndk_paths']
+        paths = fdroidserver.common.config['ndk_paths']
         if version not in paths:
             return ''
         return paths[version]
 
     def update_flags(self, d):
-        for f, v in d.iteritems():
+        for f, v in d.items():
             self.set_flag(f, v)
 
 flagtypes = {
@@ -513,8 +509,8 @@ class DescriptionFormatter:
         self.laststate = self.stNONE
         self.text_html = ''
         self.text_txt = ''
-        self.html = StringIO()
-        self.text = StringIO()
+        self.html = io.StringIO()
+        self.text = io.StringIO()
         self.para_lines = []
         self.linkResolver = None
         self.linkResolver = linkres
@@ -534,10 +530,10 @@ class DescriptionFormatter:
         self.state = self.stNONE
         whole_para = ' '.join(self.para_lines)
         self.addtext(whole_para)
-        wrapped = textwrap.fill(whole_para.decode('utf-8'), 80,
+        wrapped = textwrap.fill(whole_para, 80,
                                 break_long_words=False,
                                 break_on_hyphens=False)
-        self.text.write(wrapped.encode('utf-8'))
+        self.text.write(wrapped)
         self.html.write('</p>')
         del self.para_lines[:]
 
@@ -709,7 +705,7 @@ def parse_srclib(metadatapath):
     if not os.path.exists(metadatapath):
         return thisinfo
 
-    metafile = open(metadatapath, "r")
+    metafile = open(metadatapath, "r", encoding='utf-8')
 
     n = 0
     for line in metafile:
@@ -797,7 +793,7 @@ def read_metadata(xref=True):
                 return ("fdroid.app:" + appid, "Dummy name - don't know yet")
             raise MetaDataException("Cannot resolve app id " + appid)
 
-        for appid, app in apps.iteritems():
+        for appid, app in apps.items():
             try:
                 description_html(app.Description, linkres)
             except MetaDataException as e:
@@ -826,7 +822,7 @@ def get_default_app_info(metadatapath=None):
     if metadatapath is None:
         appid = None
     else:
-        appid, _ = common.get_extension(os.path.basename(metadatapath))
+        appid, _ = fdroidserver.common.get_extension(os.path.basename(metadatapath))
 
     app = App()
     app.metadatapath = metadatapath
@@ -846,17 +842,14 @@ esc_newlines = re.compile(r'\\( |\n)')
 # This function uses __dict__ to be faster
 def post_metadata_parse(app):
 
-    for k, v in app.__dict__.iteritems():
-        if k not in app._modified:
-            continue
+    for k in app._modified:
+        v = app.__dict__[k]
         if type(v) in (float, int):
             app.__dict__[k] = str(v)
 
     for build in app.builds:
-        for k, v in build.__dict__.iteritems():
-
-            if k not in build._modified:
-                continue
+        for k in build._modified:
+            v = build.__dict__[k]
             if type(v) in (float, int):
                 build.__dict__[k] = str(v)
                 continue
@@ -866,7 +859,7 @@ def post_metadata_parse(app):
                 build.__dict__[k] = re.sub(esc_newlines, '', v).lstrip().rstrip()
             elif ftype == TYPE_BOOL:
                 # TODO handle this using <xsd:element type="xsd:boolean> in a schema
-                if isinstance(v, basestring):
+                if isinstance(v, str):
                     build.__dict__[k] = _decode_bool(v)
             elif ftype == TYPE_STRING:
                 if isinstance(v, bool) and v:
@@ -904,36 +897,6 @@ def post_metadata_parse(app):
 #
 
 
-def _decode_list(data):
-    '''convert items in a list from unicode to basestring'''
-    rv = []
-    for item in data:
-        if isinstance(item, unicode):
-            item = item.encode('utf-8')
-        elif isinstance(item, list):
-            item = _decode_list(item)
-        elif isinstance(item, dict):
-            item = _decode_dict(item)
-        rv.append(item)
-    return rv
-
-
-def _decode_dict(data):
-    '''convert items in a dict from unicode to basestring'''
-    rv = {}
-    for k, v in data.iteritems():
-        if isinstance(k, unicode):
-            k = k.encode('utf-8')
-        if isinstance(v, unicode):
-            v = v.encode('utf-8')
-        elif isinstance(v, list):
-            v = _decode_list(v)
-        elif isinstance(v, dict):
-            v = _decode_dict(v)
-        rv[k] = v
-    return rv
-
-
 bool_true = re.compile(r'([Yy]es|[Tt]rue)')
 bool_false = re.compile(r'([Nn]o|[Ff]alse)')
 
@@ -947,17 +910,17 @@ def _decode_bool(s):
 
 
 def parse_metadata(metadatapath):
-    _, ext = common.get_extension(metadatapath)
-    accepted = common.config['accepted_formats']
+    _, ext = fdroidserver.common.get_extension(metadatapath)
+    accepted = fdroidserver.common.config['accepted_formats']
     if ext not in accepted:
         raise MetaDataException('"%s" is not an accepted format, convert to: %s' % (
             metadatapath, ', '.join(accepted)))
 
     app = App()
     app.metadatapath = metadatapath
-    app.id, _ = common.get_extension(os.path.basename(metadatapath))
+    app.id, _ = fdroidserver.common.get_extension(os.path.basename(metadatapath))
 
-    with open(metadatapath, 'r') as mf:
+    with open(metadatapath, 'r', encoding='utf-8') as mf:
         if ext == 'txt':
             parse_txt_metadata(mf, app)
         elif ext == 'json':
@@ -975,11 +938,9 @@ def parse_metadata(metadatapath):
 
 def parse_json_metadata(mf, app):
 
-    # fdroid metadata is only strings and booleans, no floats or ints. And
-    # json returns unicode, and fdroidserver still uses plain python strings
+    # fdroid metadata is only strings and booleans, no floats or ints.
     # TODO create schema using https://pypi.python.org/pypi/jsonschema
-    jsoninfo = json.load(mf, object_hook=_decode_dict,
-                         parse_int=lambda s: s,
+    jsoninfo = json.load(mf, parse_int=lambda s: s,
                          parse_float=lambda s: s)
     app.update_fields(jsoninfo)
     for f in ['Description', 'Maintainer Notes']:
@@ -1253,7 +1214,7 @@ def write_plaintext_metadata(mf, app, w_comment, w_field, w_build):
             w_field_always('Binaries')
         mf.write('\n')
 
-    for build in sorted_builds(app.builds):
+    for build in app.builds:
 
         if build.version == "Ignore":
             continue
index 42d7567dd0606bd90d163efd06b0255183dabab6..c5bf283c9f89ebedafe50bd8dbbd7a4cd9678b05 100644 (file)
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # net.py - part of the FDroid server tools
 # Copyright (C) 2015 Hans-Christoph Steiner <hans@eds.org>
index f0089d862e9f619d9c10390a055dd8ef1a8eecc1..668f19a3f9ebc84518779ee8db4a05b911dac0ee 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # publish.py - part of the FDroid server tools
 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
 import sys
 import os
 import shutil
-import md5
 import glob
+import hashlib
 from argparse import ArgumentParser
 import logging
 
-import common
-import metadata
-from common import FDroidPopen, SdkToolsPopen, BuildException
+from . import common
+from . import metadata
+from .common import FDroidPopen, SdkToolsPopen, BuildException
 
 config = None
 options = None
@@ -91,8 +90,8 @@ def main():
     vercodes = common.read_pkg_args(options.appid, True)
     allaliases = []
     for appid in allapps:
-        m = md5.new()
-        m.update(appid)
+        m = hashlib.md5()
+        m.update(appid.encode('utf-8'))
         keyalias = m.hexdigest()[:8]
         if keyalias in allaliases:
             logging.error("There is a keyalias collision - publishing halted")
@@ -156,12 +155,12 @@ def main():
                 # For this particular app, the key alias is overridden...
                 keyalias = config['keyaliases'][appid]
                 if keyalias.startswith('@'):
-                    m = md5.new()
-                    m.update(keyalias[1:])
+                    m = hashlib.md5()
+                    m.update(keyalias[1:].encode('utf-8'))
                     keyalias = m.hexdigest()[:8]
             else:
-                m = md5.new()
-                m.update(appid)
+                m = hashlib.md5()
+                m.update(appid.encode('utf-8'))
                 keyalias = m.hexdigest()[:8]
             logging.info("Key alias: " + keyalias)
 
index f3fa12c035873c7b66e8ad84d58420372a0766d5..e04b5623c7c51cb279a0ceca0f4741984012032e 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # readmeta.py - part of the FDroid server tools
 # Copyright (C) 2014 Daniel Martí <mvdan@mvdan.cc>
@@ -18,8 +17,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from argparse import ArgumentParser
-import common
-import metadata
+from . import common
+from . import metadata
 
 
 def main():
index 51d82c37e21b283f89baec9291b03ee4e4e53a5a..971ab18cb597d5899dc2a646bf850e4004f974c0 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # rewritemeta.py - part of the FDroid server tools
 # This cleans up the original .txt metadata file format.
 from argparse import ArgumentParser
 import os
 import logging
-try:
-    from cStringIO import StringIO
-except:
-    from StringIO import StringIO
+import io
 
-import common
-import metadata
+from . import common
+from . import metadata
 
 config = None
 options = None
 
 
 def proper_format(app):
-    s = StringIO()
+    s = io.StringIO()
     # TODO: currently reading entire file again, should reuse first
     # read in metadata.py
     with open(app.metadatapath, 'r') as f:
@@ -73,7 +69,7 @@ def main():
     if options.to is not None and options.to not in supported:
         parser.error("Must give a valid format to --to")
 
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
         base, ext = common.get_extension(app.metadatapath)
         if not options.to and ext not in supported:
             logging.info("Ignoring %s file at '%s'" % (ext, app.metadatapath))
@@ -85,7 +81,7 @@ def main():
 
         if options.list:
             if not proper_format(app):
-                print app.metadatapath
+                print(app.metadatapath)
             continue
 
         with open(base + '.' + to_ext, 'w') as f:
index 98e3421b72ed55936b55ec066783b5131ddf816f..53c938fbe339e7b33560ca6a7aa220dac5a14560 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # scanner.py - part of the FDroid server tools
 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
@@ -23,9 +22,9 @@ import traceback
 from argparse import ArgumentParser
 import logging
 
-import common
-import metadata
-from common import BuildException, VCSException
+from . import common
+from . import metadata
+from .common import BuildException, VCSException
 
 config = None
 options = None
@@ -68,7 +67,7 @@ def scan_source(build_dir, root_dir, build):
     }
 
     def suspects_found(s):
-        for n, r in usual_suspects.iteritems():
+        for n, r in usual_suspects.items():
             if r.match(s):
                 yield n
 
@@ -95,7 +94,7 @@ def scan_source(build_dir, root_dir, build):
     scandelete_worked = set()
 
     def toignore(fd):
-        for k, paths in scanignore.iteritems():
+        for k, paths in scanignore.items():
             for p in paths:
                 if fd.startswith(p):
                     scanignore_worked.add(k)
@@ -103,7 +102,7 @@ def scan_source(build_dir, root_dir, build):
         return False
 
     def todelete(fd):
-        for k, paths in scandelete.iteritems():
+        for k, paths in scandelete.items():
             for p in paths:
                 if fd.startswith(p):
                     scandelete_worked.add(k)
@@ -200,10 +199,11 @@ def scan_source(build_dir, root_dir, build):
             elif ext == 'java':
                 if not os.path.isfile(fp):
                     continue
-                for line in file(fp):
-                    if 'DexClassLoader' in line:
-                        count += handleproblem('DexClassLoader', fd, fp)
-                        break
+                with open(fp, 'r') as f:
+                    for line in f:
+                        if 'DexClassLoader' in line:
+                            count += handleproblem('DexClassLoader', fd, fp)
+                            break
 
             elif ext == 'gradle':
                 if not os.path.isfile(fp):
@@ -267,7 +267,7 @@ def main():
     srclib_dir = os.path.join(build_dir, 'srclib')
     extlib_dir = os.path.join(build_dir, 'extlib')
 
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
 
         if app.Disabled:
             logging.info("Skipping %s: disabled" % appid)
index 802cac22588fa803df40f9dd28df74b04ea0db7f..ed39f0a7c48cb728b8bc78efcb73831958717aa6 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # server.py - part of the FDroid server tools
 # Copyright (C) 2010-15, Ciaran Gultnieks, ciaran@ciarang.com
@@ -26,7 +25,8 @@ import pwd
 import subprocess
 from argparse import ArgumentParser
 import logging
-import common
+
+from . import common
 
 config = None
 options = None
@@ -296,12 +296,12 @@ def main():
             sftp = ssh.open_sftp()
             if os.path.basename(remotepath) \
                     not in sftp.listdir(os.path.dirname(remotepath)):
-                sftp.mkdir(remotepath, mode=0755)
+                sftp.mkdir(remotepath, mode=0o755)
             for repo_section in repo_sections:
                 repo_path = os.path.join(remotepath, repo_section)
                 if os.path.basename(repo_path) \
                         not in sftp.listdir(remotepath):
-                    sftp.mkdir(repo_path, mode=0755)
+                    sftp.mkdir(repo_path, mode=0o755)
             sftp.close()
             ssh.close()
     elif options.command == 'update':
index 1d0c320067714f1f1c25d8cc79ba75e075f1d222..c550b995478563f239f06bf1aa373e711ccc0c32 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # gpgsign.py - part of the FDroid server tools
 # Copyright (C) 2015, Ciaran Gultnieks, ciaran@ciarang.com
@@ -22,8 +21,8 @@ import os
 from argparse import ArgumentParser
 import logging
 
-import common
-from common import FDroidPopen
+from . import common
+from .common import FDroidPopen
 
 config = None
 options = None
index b977ef1b0059e075735ac64b8833d77ed3b51179..1a050033a871bca175733ab9a04805e2b0db4483 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # stats.py - part of the FDroid server tools
 # Copyright (C) 2010-13, Ciaran Gultnieks, ciaran@ciarang.com
@@ -28,11 +27,12 @@ from argparse import ArgumentParser
 import paramiko
 import socket
 import logging
-import common
-import metadata
 import subprocess
 from collections import Counter
 
+from . import common
+from . import metadata
+
 
 def carbon_send(key, value):
     s = socket.socket()
@@ -75,7 +75,7 @@ def main():
         sys.exit(1)
 
     # Get all metadata-defined apps...
-    allmetaapps = [app for app in metadata.read_metadata().itervalues()]
+    allmetaapps = [app for app in metadata.read_metadata().values()]
     metaapps = [app for app in allmetaapps if not app.Disabled]
 
     statsdir = 'stats'
index 38a61c8b3aae245ff22b135041ade42068217c96..0e0aef68458ee100841929b6e1cad2c7f5f9d07b 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # update.py - part of the FDroid server tools
 # Copyright (C) 2010-2015, Ciaran Gultnieks, ciaran@ciarang.com
@@ -27,7 +26,7 @@ import socket
 import zipfile
 import hashlib
 import pickle
-import urlparse
+import urllib.parse
 from datetime import datetime, timedelta
 from xml.dom.minidom import Document
 from argparse import ArgumentParser
@@ -35,16 +34,15 @@ import time
 from pyasn1.error import PyAsn1Error
 from pyasn1.codec.der import decoder, encoder
 from pyasn1_modules import rfc2315
-from hashlib import md5
 from binascii import hexlify, unhexlify
 
 from PIL import Image
 import logging
 
-import common
-import metadata
-from common import FDroidPopen, SdkToolsPopen
-from metadata import MetaDataException
+from . import common
+from . import metadata
+from .common import FDroidPopen, FDroidPopenBytes, SdkToolsPopen
+from .metadata import MetaDataException
 
 screen_densities = ['640', '480', '320', '240', '160', '120']
 
@@ -292,7 +290,7 @@ def delete_disabled_builds(apps, apkcache, repodirs):
     :param apkcache: current apk cache information
     :param repodirs: the repo directories to process
     """
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
         for build in app.builds:
             if not build.disable:
                 continue
@@ -402,7 +400,7 @@ def getsig(apkpath):
 
     cert_encoded = encoder.encode(certificates)[4:]
 
-    return md5(cert_encoded.encode('hex')).hexdigest()
+    return hashlib.md5(hexlify(cert_encoded)).hexdigest()
 
 
 def scan_apks(apps, apkcache, repodir, knownapks, use_date_from_apk=False):
@@ -713,7 +711,7 @@ repo_pubkey_fingerprint = None
 def cert_fingerprint(data):
     digest = hashlib.sha256(data).digest()
     ret = []
-    ret.append(' '.join("%02X" % ord(b) for b in digest))
+    ret.append(' '.join("%02X" % b for b in bytearray(digest)))
     return " ".join(ret)
 
 
@@ -722,12 +720,12 @@ def extract_pubkey():
     if 'repo_pubkey' in config:
         pubkey = unhexlify(config['repo_pubkey'])
     else:
-        p = FDroidPopen([config['keytool'], '-exportcert',
-                         '-alias', config['repo_keyalias'],
-                         '-keystore', config['keystore'],
-                         '-storepass:file', config['keystorepassfile']]
-                        + config['smartcardoptions'],
-                        output=False, stderr_to_stdout=False)
+        p = FDroidPopenBytes([config['keytool'], '-exportcert',
+                              '-alias', config['repo_keyalias'],
+                              '-keystore', config['keystore'],
+                              '-storepass:file', config['keystorepassfile']]
+                             + config['smartcardoptions'],
+                             output=False, stderr_to_stdout=False)
         if p.returncode != 0 or len(p.output) < 20:
             msg = "Failed to get repo pubkey!"
             if config['keystore'] == 'NONE':
@@ -774,7 +772,7 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
 
     mirrorcheckfailed = False
     for mirror in config.get('mirrors', []):
-        base = os.path.basename(urlparse.urlparse(mirror).path.rstrip('/'))
+        base = os.path.basename(urllib.parse.urlparse(mirror).path.rstrip('/'))
         if config.get('nonstandardwebroot') is not True and base != 'fdroid':
             logging.error("mirror '" + mirror + "' does not end with 'fdroid'!")
             mirrorcheckfailed = True
@@ -788,9 +786,9 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
         repoel.setAttribute("icon", os.path.basename(config['archive_icon']))
         repoel.setAttribute("url", config['archive_url'])
         addElement('description', config['archive_description'], doc, repoel)
-        urlbasepath = os.path.basename(urlparse.urlparse(config['archive_url']).path)
+        urlbasepath = os.path.basename(urllib.parse.urlparse(config['archive_url']).path)
         for mirror in config.get('mirrors', []):
-            addElement('mirror', urlparse.urljoin(mirror, urlbasepath), doc, repoel)
+            addElement('mirror', urllib.parse.urljoin(mirror, urlbasepath), doc, repoel)
 
     else:
         repoel.setAttribute("name", config['repo_name'])
@@ -799,9 +797,9 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
         repoel.setAttribute("icon", os.path.basename(config['repo_icon']))
         repoel.setAttribute("url", config['repo_url'])
         addElement('description', config['repo_description'], doc, repoel)
-        urlbasepath = os.path.basename(urlparse.urlparse(config['repo_url']).path)
+        urlbasepath = os.path.basename(urllib.parse.urlparse(config['repo_url']).path)
         for mirror in config.get('mirrors', []):
-            addElement('mirror', urlparse.urljoin(mirror, urlbasepath), doc, repoel)
+            addElement('mirror', urllib.parse.urljoin(mirror, urlbasepath), doc, repoel)
 
     repoel.setAttribute("version", "15")
     repoel.setAttribute("timestamp", str(int(time.time())))
@@ -828,7 +826,7 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
             logging.warning("\tfdroid update --create-key")
             sys.exit(1)
 
-    repoel.setAttribute("pubkey", extract_pubkey())
+    repoel.setAttribute("pubkey", extract_pubkey().decode('utf-8'))
     root.appendChild(repoel)
 
     for appid in sortedids:
@@ -968,9 +966,9 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
                     os.symlink(sigfile_path, siglinkname)
 
     if options.pretty:
-        output = doc.toprettyxml()
+        output = doc.toprettyxml(encoding='utf-8')
     else:
-        output = doc.toxml()
+        output = doc.toxml(encoding='utf-8')
 
     with open(os.path.join(repodir, 'index.xml'), 'wb') as f:
         f.write(output)
@@ -1025,7 +1023,7 @@ def make_index(apps, sortedids, apks, repodir, archive, categories):
 
 def archive_old_apks(apps, apks, archapks, repodir, archivedir, defaultkeepversions):
 
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
 
         if app.ArchivePolicy:
             keepversions = int(app.ArchivePolicy[:-9])
@@ -1199,7 +1197,7 @@ def main():
 
     # Generate a list of categories...
     categories = set()
-    for app in apps.itervalues():
+    for app in apps.values():
         categories.update(app.Categories)
 
     # Read known apks data (will be updated and written back when we've finished)
@@ -1269,7 +1267,7 @@ def main():
     # level. When doing this, we use the info from the most recent version's apk.
     # We deal with figuring out when the app was added and last updated at the
     # same time.
-    for appid, app in apps.iteritems():
+    for appid, app in apps.items():
         bestver = 0
         for apk in apks + archapks:
             if apk['id'] == appid:
@@ -1303,13 +1301,13 @@ def main():
     # 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.keys(), 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
     if config['per_app_repos']:
         add_apks_to_per_app_repos(repodirs[0], apks)
-        for appid, app in apps.iteritems():
+        for appid, app in apps.items():
             repodir = os.path.join(appid, 'fdroid', 'repo')
             appdict = dict()
             appdict[appid] = app
@@ -1338,14 +1336,15 @@ def main():
         # Generate latest apps data for widget
         if os.path.exists(os.path.join('stats', 'latestapps.txt')):
             data = ''
-            for line in file(os.path.join('stats', 'latestapps.txt')):
-                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"
+            with open(os.path.join('stats', 'latestapps.txt'), 'r') as f:
+                for line in f:
+                    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"
             with open(os.path.join(repodirs[0], 'latestapps.dat'), 'w') as f:
                 f.write(data)
 
index 2fb066be438f52017f1f0f5368755593229339b5..82a690c4eb05f8a5084769c7587357b1d6d53682 100644 (file)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 #
 # verify.py - part of the FDroid server tools
 # Copyright (C) 2013, Ciaran Gultnieks, ciaran@ciarang.com
@@ -23,9 +22,9 @@ import glob
 from argparse import ArgumentParser
 import logging
 
-import common
-import net
-from common import FDroidException
+from . import common
+from . import net
+from .common import FDroidException
 
 options = None
 config = None
index 4bb0a1edf7744ca65ae29248a688659ad8fccf61..f0e4d657d4b92f40294154a6b27d2d6b48e800eb 100755 (executable)
@@ -46,17 +46,16 @@ else
     done
 fi
 
-# In the default configuration, the checks E123, E133, E226, E241 and E242 are
-# ignored because they are not rules unanimously accepted
-# On top of those, we ignore:
+# We ignore the following PEP8 warnings
+# * E123: closing bracket does not match indentation of opening bracket's line
+#   - Broken if multiple indentation levels start on a single line
 # * E501: line too long (82 > 79 characters)
 #   - Recommended for readability but not enforced
 #   - Some lines are awkward to wrap around a char limit
 # * W503: line break before binary operator
-#   - It's quite new
 #   - Quite pedantic
 
-PEP8_IGNORE="E123,E133,E226,E241,E242,E501,W503"
+PEP8_IGNORE="E123,E501,W503"
 
 err() {
        echo ERROR: "$@"
@@ -71,22 +70,21 @@ cmd_exists() {
        command -v $1 1>/dev/null
 }
 
-if cmd_exists pyflakes-python2; then
-       PYFLAKES=pyflakes-python2
-elif cmd_exists pyflakes; then
-       PYFLAKES=pyflakes
-else
-       PYFLAKES=echo
-       warn "pyflakes is not installed, using dummy placeholder!"
-fi
+find_command() {
+       local name=$1
+       for suff in "3" "-python3" ""; do
+               cmd=${1}${suff}
+               if cmd_exists $cmd; then
+                       echo -n $cmd
+                       return 0
+               fi
+       done
+       warn "$1 is not installed, using dummy placeholder!"
+       echo -n echo
+}
 
-if cmd_exists pep8-python2; then
-       PEP8=pep8-python2
-elif cmd_exists pep8; then
-       PEP8=pep8
-else
-       err "pep8 is not installed!"
-fi
+PYFLAKES=$(find_command pyflakes)
+PEP8=$(find_command pep8)
 
 if [ "$PY_FILES $PY_TEST_FILES" != " " ]; then
     if ! $PYFLAKES $PY_FILES $PY_TEST_FILES; then
index 94d6695f21106141703902689b73b1538678c1ad..75e3650b7da1651f2171cdcb8f0c174b000ad61c 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 import os
 import sys
@@ -26,7 +26,7 @@ def vagrant(params, cwd=None, printout=False):
             line = p.stdout.readline()
             if len(line) == 0:
                 break
-            print line,
+            print(line)
             out += line
         p.wait()
     else:
@@ -63,13 +63,13 @@ config = {
 
 # load config file, if present
 if os.path.exists('makebuildserver.config.py'):
-    execfile('makebuildserver.config.py', config)
+    exec(compile(open('makebuildserver.config.py').read(), 'makebuildserver.config.py', 'exec'), config)
 elif os.path.exists('makebs.config.py'):
     # this is the old name for the config file
-    execfile('makebs.config.py', config)
+    exec(compile(open('makebs.config.py').read(), 'makebs.config.py', 'exec'), config)
 
 if not os.path.exists('makebuildserver') or not os.path.exists(serverdir):
-    print 'This must be run from the correct directory!'
+    print('This must be run from the correct directory!')
     sys.exit(1)
 
 if os.path.exists(boxfile):
@@ -81,7 +81,7 @@ if options.clean:
 # Update cached files.
 cachedir = config['cachedir']
 if not os.path.exists(cachedir):
-    os.makedirs(cachedir, 0755)
+    os.makedirs(cachedir, 0o755)
 
 cachefiles = [
     ('android-sdk_r24.4.1-linux.tgz',
@@ -318,17 +318,17 @@ for f, src, shasum in cachefiles:
     if os.path.exists(relpath) and os.stat(relpath).st_size == 0:
         os.remove(relpath)
     if not os.path.exists(relpath):
-        print "Downloading " + f + " to cache"
+        print("Downloading " + f + " to cache")
         if subprocess.call(['wget', src, '-O', f], cwd=cachedir) != 0:
-            print "...download of " + f + " failed."
+            print("...download of " + f + " failed.")
             sys.exit(1)
     if shasum:
         v = sha256_for_file(relpath)
         if v != shasum:
-            print "Invalid shasum of '" + v + "' detected for " + f
+            print("Invalid shasum of '" + v + "' detected for " + f)
             sys.exit(1)
         else:
-            print "...shasum verified for " + f
+            print("...shasum verified for " + f)
 
     wanted.append(f)
 
@@ -418,57 +418,57 @@ if os.path.exists(vf):
     with open(vf, 'r') as f:
         oldvf = f.read()
     if oldvf != vagrantfile:
-        print "Server configuration has changed, rebuild from scratch is required"
+        print("Server configuration has changed, rebuild from scratch is required")
         vagrant(['destroy', '-f'], serverdir)
     else:
-        print "Re-provisioning existing server"
+        print("Re-provisioning existing server")
         writevf = False
 else:
-    print "No existing server - building from scratch"
+    print("No existing server - building from scratch")
 if writevf:
     with open(vf, 'w') as f:
         f.write(vagrantfile)
 
 
-print "Configuring build server VM"
+print("Configuring build server VM")
 returncode, out = vagrant(['up', '--provision'], serverdir, printout=True)
 with open(os.path.join(serverdir, 'up.log'), 'w') as log:
     log.write(out)
 if returncode != 0:
-    print "Failed to configure server"
+    print("Failed to configure server")
     sys.exit(1)
 
-print "Writing buildserver ID"
+print("Writing buildserver ID")
 p = subprocess.Popen(['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE)
 buildserverid = p.communicate()[0].strip()
-print "...ID is " + buildserverid
+print("...ID is " + buildserverid)
 subprocess.call(
     ['vagrant', 'ssh', '-c', 'sh -c "echo {0} >/home/vagrant/buildserverid"'
         .format(buildserverid)],
     cwd=serverdir)
 
-print "Stopping build server VM"
+print("Stopping build server VM")
 vagrant(['halt'], serverdir)
 
-print "Waiting for build server VM to be finished"
+print("Waiting for build server VM to be finished")
 ready = False
 while not ready:
     time.sleep(2)
     returncode, out = vagrant(['status'], serverdir)
     if returncode != 0:
-        print "Error while checking status"
+        print("Error while checking status")
         sys.exit(1)
     for line in out.splitlines():
         if line.startswith("default"):
             if line.find("poweroff") != -1:
                 ready = True
             else:
-                print "Status: " + line
+                print("Status: " + line)
 
-print "Packaging"
+print("Packaging")
 vagrant(['package', '--output', os.path.join('..', boxfile)], serverdir,
         printout=options.verbose)
-print "Adding box"
+print("Adding box")
 vagrant(['box', 'add', 'buildserver', boxfile, '-f'],
         printout=options.verbose)
 
index 947112f9a4e961af407bb4ffdc2e1b12660824ea..017cccd9cdaebffa41f57634c1c1002bf2a6f6bd 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 from setuptools import setup
 import sys
index 0665bfffa7f41b3c2de249a7957b5fb74b046586..b3b90fc3ded775290d8ea6f861931e5b2da8fff9 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
@@ -37,7 +36,7 @@ class BuildTest(unittest.TestCase):
                     break
             return True
         else:
-            print 'no build-tools found: ' + build_tools
+            print('no build-tools found: ' + build_tools)
             return False
 
     def _find_all(self):
index 32e7af7f468c38bf7d7a3e8b71b9426ff4bde610..48e1d29d71f568f3b088a150f5f02597176b1a05 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
@@ -37,7 +36,7 @@ class CommonTest(unittest.TestCase):
                     break
             return True
         else:
-            print 'no build-tools found: ' + build_tools
+            print('no build-tools found: ' + build_tools)
             return False
 
     def _find_all(self):
@@ -61,7 +60,7 @@ class CommonTest(unittest.TestCase):
             if self._set_build_tools():
                 self._find_all()
             else:
-                print 'no build-tools found: ' + build_tools
+                print('no build-tools found: ' + build_tools)
 
     def testIsApkDebuggable(self):
         config = dict()
index b536dc9e3a42e5be35efea20a20552a59045fd7f..8e7815275e7bf21a5f0a42ef06f496918ab3d248 100755 (executable)
@@ -51,10 +51,10 @@ cd $WORKSPACE/tests
 #------------------------------------------------------------------------------#
 # test building the source tarball, then installing it
 cd $WORKSPACE
-python2 setup.py sdist
+python3 setup.py sdist
 
 rm -rf $WORKSPACE/env
-virtualenv --python=python2 $WORKSPACE/env
+virtualenv --python=python3 $WORKSPACE/env
 . $WORKSPACE/env/bin/activate
 pip install dist/fdroidserver-*.tar.gz
 
@@ -66,10 +66,10 @@ fdroid=$WORKSPACE/env/bin/fdroid $WORKSPACE/tests/run-tests $apksource
 # test install using install direct from git repo
 cd $WORKSPACE
 rm -rf $WORKSPACE/env
-virtualenv --python=python2 --system-site-packages $WORKSPACE/env
+virtualenv --python=python3 --system-site-packages $WORKSPACE/env
 . $WORKSPACE/env/bin/activate
 pip install -e $WORKSPACE
-python2 setup.py install
+python3 setup.py install
 
 # run tests in new pip+virtualenv install
 fdroid=$WORKSPACE/env/bin/fdroid $WORKSPACE/tests/run-tests $apksource
@@ -86,7 +86,7 @@ sh hooks/pre-commit
 cd $WORKSPACE
 set +e
 # use the virtualenv python so pylint checks against its installed libs
-    PYTHONPATH=$WORKSPACE/.pylint-plugins python2 /usr/bin/pylint \
+    PYTHONPATH=$WORKSPACE/.pylint-plugins python3 /usr/bin/pylint \
         --output-format=parseable --reports=n \
         --load-plugins astng_hashlib \
         fdroidserver/*.py fdroid makebuildserver setup.py > $WORKSPACE/pylint.parseable
index 05eba4b98eb79b2deb6ae938d35b7ecc6ad18293..780fae04cca81c260c3281609382ff3fe1c9f7a1 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 import os
 import sys
index 1d00a6885e4c99464f0a1780650eb87179fc5b9e..cce85d68d6828ba66c3a486d254c92fdc36551ad 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
index 2734f132b0298eb9e28ab0a5e397c86c3cb59f00..d1ed93fff1f4c9ef8e40bcacb21d1ea8c22c6e34 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
@@ -32,7 +31,7 @@ class InstallTest(unittest.TestCase):
         devices = fdroidserver.install.devices()
         self.assertIsInstance(devices, list, 'install.devices() did not return a list!')
         for device in devices:
-            self.assertIsInstance(device, basestring)
+            self.assertIsInstance(device, str)
 
 
 if __name__ == "__main__":
index c1ffbf363af4c6c264697ec43e82a79e112c1601..9dfe2bbcdf1adf94180959cde2ebe21b0bc7b6f6 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
@@ -43,7 +42,7 @@ class MetadataTest(unittest.TestCase):
             savepath = os.path.join('metadata', appid + '.pickle')
             frommeta = app.field_dict()
             self.assertTrue(appid in apps)
-            with open(savepath, 'r') as f:
+            with open(savepath, 'rb') as f:
                 frompickle = pickle.load(f)
             self.assertEquals(frommeta, frompickle)
             # Uncomment to overwrite
index de9c9a1532247bf017210027da4075898b75f24b..8ec03c353c106f163b172773e664b519d07d52b9 100755 (executable)
@@ -83,7 +83,7 @@ fi
 
 # allow the location of python to be overridden
 if [ -z $python ]; then
-    python=python2
+    python=python3
 fi
 
 set -x # show each command as it is executed
index 07f5765428104daa6061c708352e0fc10b181fd9..83349f5e74593d93c5f4603c14915ca7c4ff7e8b 100755 (executable)
@@ -1,5 +1,4 @@
-#!/usr/bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # http://www.drdobbs.com/testing/unit-testing-with-python/240165163
 
@@ -9,6 +8,7 @@ import optparse
 import os
 import sys
 import unittest
+from binascii import unhexlify
 
 localmodule = os.path.realpath(
     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
@@ -56,10 +56,10 @@ class UpdateTest(unittest.TestCase):
         self.assertEquals(len(sig), len(pysig),
                           "the length of the two sigs are different!")
         try:
-            self.assertEquals(sig.decode('hex'), pysig.decode('hex'),
+            self.assertEquals(unhexlify(sig), unhexlify(pysig),
                               "the length of the two sigs are different!")
         except TypeError as e:
-            print e
+            print(e)
             self.assertTrue(False, 'TypeError!')
 
     def testBadGetsig(self):