diff --git a/.gitignore b/.gitignore
index 43dba70291d972b73713248ee568984c69a92c04..ede3e9604039690172a9457681aff533bfde2d33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 *~
-mio
+*.bak
+/mio2
+/mio3
diff --git a/build b/build
index aa51638a8c069816593778254549f9c8b62c7a95..10925a9c96c5f6cc1617d4f7b67520b092b842c9 100755
--- a/build
+++ b/build
@@ -4,4 +4,8 @@ TARGET=$(basename $(pwd))
 set -e
 cd src
 find ${TARGET} -name '*.py' | sort | \
-	xargs apa -o ../${TARGET} ${TARGET}.py
+    xargs apa --interpreter /usr/bin/python3 \
+              -o ../${TARGET}3 ${TARGET}.py
+find ${TARGET} -name '*.py' | sort | \
+    xargs apa --interpreter /usr/bin/python2 \
+              -o ../${TARGET}2 ${TARGET}.py
diff --git a/src/mio.py b/src/mio.py
index d6d07782017e33db821d31ffc9fa8f28f6330f16..53e2a0f27c29dfb9b3f33a8ae45fc02fa6a825e2 100755
--- a/src/mio.py
+++ b/src/mio.py
@@ -17,11 +17,6 @@ from mio.exception import *
 from mio.log import log, MUTE, SILENT, NORMAL, VERBOSE
 import locale
 
-# Fix for older python version that does not have True and False defined
-if not hasattr(__builtins__, "True"):
-    __builtins__["True"] = 1
-    __builtins__["False"] = 0
-
 def parse_with_path(path, filename, repository):
     f = mio.filecache.loadfile(path, filename)
     tree = mio.parser.parse(f.filename, url=f.key)
@@ -41,8 +36,7 @@ def parse_with_path(path, filename, repository):
     repository.add(tree._group_, join=join)
 
 def parse(filename, repository):
-    path = list(options.path)
-    for p in path:
+    for p in options.path:
         try:
             parse_with_path(p, filename, repository)
             return
@@ -185,8 +179,7 @@ if __name__ == '__main__':
                            help="increase verbosity")
     
     options = optParser.parse_args(sys.argv[1:])
-    options.path = map(tuple, options.path)
-
+    options.path = [ tuple(p,) for p in options.path ]
     mio.log.set_verbosity(NORMAL + options.verbose - options.quiet)
 
     find_and_parse = FindAndParse()
@@ -259,9 +252,7 @@ if __name__ == '__main__':
             pass
         
         if not error:
-            keys = files.keys()
-            keys.sort()
-            for k in keys:
+            for k in sorted(files.keys()):
                 print(k)
             pass
         else:
@@ -296,9 +287,7 @@ if __name__ == '__main__':
             pass
         
         if not error:
-            keys = rpms.keys()
-            keys.sort()
-            for k in keys:
+            for k in sorted(rpms.keys()):
                 print(k)
             pass
         else:
diff --git a/src/mio/exception.py b/src/mio/exception.py
index 989c1eca39b059c96709404d7d03d8ffe0cdb74b..61467bc29524914e1158cea513e00c6cee587126 100755
--- a/src/mio/exception.py
+++ b/src/mio/exception.py
@@ -1,7 +1,6 @@
 from __future__ import print_function
 import traceback
 import sys
-import urllib2
 
 def declaration(decl):
     try:
diff --git a/src/mio/filecache.py b/src/mio/filecache.py
index cb89186dc2af60edd830d11b3076c6061ba59f45..61217a237bf2724d0550b49f3d1b57427ea212e0 100755
--- a/src/mio/filecache.py
+++ b/src/mio/filecache.py
@@ -7,8 +7,13 @@ import re
 import sys
 import tempfile
 import time
-import urlparse
-import urllib
+try:
+    # Python 3
+    from urllib.parse import urlsplit, urlunsplit, unquote as urlunquote
+except ImportError:
+    # Python 2 fallback
+    from urlparse import urlsplit, urlunsplit
+    from urllib import unquote as urlunquote
 import mio.exception
 import socket
 try:
@@ -27,8 +32,8 @@ mirror = {}
 def subpath(path, *tail):
     result = []
     for p in path:
-        u = urlparse.urlsplit(p)
-        result.append(urlparse.urlunsplit([
+        u = urlsplit(p)
+        result.append(urlunsplit([
             u.scheme,
             u.hostname, 
             '/'.join([u.path] + list(tail)),
@@ -81,10 +86,10 @@ class Unalias:
 
     def __call__(self, url):
         result = []
-        u = urlparse.urlsplit(str(url))
+        u = urlsplit(str(url))
         if u.hostname:
             for hostname in self.unalias_host(u.hostname, u.port or 80):
-                result.append(urlparse.urlunsplit((
+                result.append(urlunsplit((
                     u.scheme,
                     u.netloc.replace(u.hostname, hostname),
                     u.path,
@@ -93,11 +98,10 @@ class Unalias:
                 pass
             pass
         elif u.scheme in [ '', 'file' ]:
-            result.append(urlparse.urlunsplit(('file', '', u.path, '', '')))
+            result.append(urlunsplit(('file', '', u.path, '', '')))
             pass
         else:
-            print(u)
-            pass
+            raise Exception('Unknown URL:', u)
         return result
  
 unalias = Unalias()
@@ -165,7 +169,7 @@ def cleanup():
             os.remove(f)
         
 def createfile():
-    f = open(tempfile.mktemp(), 'w')
+    f = open(tempfile.mktemp(), 'wb')
     temp.append(f.name)
     os.chmod(f.name, 0o700)
     return f
@@ -185,7 +189,7 @@ def loadfile(path, *name):
             pass
         u = mirror[path].urlopen(key.name)
         if u.scheme == 'file':
-            p = urllib.unquote(urlparse.urlsplit(u.geturl()).path)
+            p = urlunquote(urlsplit(u.geturl()).path)
             cache[key] = CacheEntry(p, key)
             pass
         else:
diff --git a/src/mio/parser.py b/src/mio/parser.py
index 8e0f823af97b3240d7dc7b7a8626a29b52cace4c..582871a804b5c5d8e95b9b929d42c6c4e2d68ae4 100755
--- a/src/mio/parser.py
+++ b/src/mio/parser.py
@@ -6,8 +6,14 @@ import time
 import xml.sax
 import xml.parsers.expat
 import traceback
-import urlparse
-import urllib2
+try:
+    # Python 3
+    from urllib.request import urlopen
+    from urllib.parse import urlparse
+except ImportError:
+    # Python 2 fallback
+    from urllib2 import urlopen
+    from urlparse import urlparse
 
 __doc__ = """
 Package for xml parsing
@@ -233,6 +239,9 @@ class AttributeAccessor:
         self._node = node
         self._name = name
 
+    def __lt__(self, other):
+        return self._name < other._name
+
     def __getattr__(self, attr):
         message = "%s.%s not qualified with [...] for %s" % (
             self._node._tag, self._name, attr)
@@ -334,6 +343,9 @@ class Node:
                 raise Exception("Undefined attribute '%s' in '%s'" % (k, tag))
             self._attribute[k] = attr[k]
 
+    def __lt__(self, other):
+        return self._tag < other._tag
+
     def __copy__(self):
         """Copy node, but not children"""
         result = Node(self._tag, self._line, self._attribute)
@@ -367,9 +379,9 @@ class Node:
         result = ""
         line = "%s<%s" % ("  " * indent, self._tag)
         blanks = " " * len(line)
-        key = self._attribute.keys()
-        key.sort(AttrSortWrapper(self, attr_sort))
-        for k in key:
+        for k in sorted(self._attribute.keys(),
+                        key=(attr_sort and AttrSortWrapper(self, attr_sort)
+                             or None)):
             s = " %s='%s'" % (k, xml_escape(self._attribute[k]))
             if len(line) + len(s) > width:
                 result += "%s\n" % line
@@ -384,9 +396,11 @@ class Node:
                 result += '\n'.join(self._char)
                 result += '\n'
                 pass
-            child = self._children
-            child.sort(TagSortWrapper(self, tag_sort))
-            for c in child:
+            print("YYY", (tag_sort and TagSortWrapper(self, tag_sort)
+                          or None), "\n")
+            for c in sorted(self._children,
+                            key=(tag_sort and TagSortWrapper(self, tag_sort)
+                                 or None)):
                 result += c._xml(indent + 1, attr_sort, tag_sort, width)
             result += "%s</%s>\n" % ("  " * indent, self._tag)
         return result
@@ -523,7 +537,7 @@ class Parser:
 
 def parse(source, include_comments=False, url=None):
     """Parse the given 'source' returning an easily traversable tree"""
-    (scheme, host, path, param, query, frag) = urlparse.urlparse(source)
+    (scheme, host, path, param, query, frag) = urlparse(source)
     if not scheme:
         data = open(source).read()
         mtime = os.stat(source)[stat.ST_MTIME]                
diff --git a/src/mio/path.py b/src/mio/path.py
index 4b83639e2e520229ccb1b2f90d5a171523f26d7c..5a555d8ee468a9f49dabf299f8f792f85ab22ae8 100755
--- a/src/mio/path.py
+++ b/src/mio/path.py
@@ -1,6 +1,11 @@
 import re
 import os.path
-import urlparse
+try:
+    # Python 3
+    from urllib.parse import urlparse, urlunparse
+except ImportError:
+    # Python 2 fallback
+    from urlparse import urlparse, urlunparse
 
 def join(elements):
     """join and normalize elements to a proper (url) path"""
@@ -10,16 +15,16 @@ def join(elements):
             if len(join) > 0:
                 join += '/'
             join += e
-    (scheme, host, path, param, query, frag) = urlparse.urlparse(join)
+    (scheme, host, path, param, query, frag) = urlparse(join)
     if scheme:
         path = re.sub('/+', '/', path)
-        result = urlparse.urlunparse((scheme, host, path, param, query, frag))
+        result = urlunparse((scheme, host, path, param, query, frag))
     else:
         result = re.sub('/+', '/', join)
     return result
 
 def geturl(p):
-    (scheme, host, path, param, query, frag) = urlparse.urlparse(p)
+    (scheme, host, path, param, query, frag) = urlparse(p)
     if not scheme:
         scheme = 'file'
         host =''
@@ -27,10 +32,10 @@ def geturl(p):
         param = ''
         query = ''
         frag = ''
-    return urlparse.urlunparse((scheme, host, path, param, query, frag))
+    return urlunparse((scheme, host, path, param, query, frag))
 
 def getfile(p):
-    (scheme, host, path, param, query, frag) = urlparse.urlparse(geturl(p))
+    (scheme, host, path, param, query, frag) = urlparse(geturl(p))
     if scheme == 'file':
         return path
     else:
diff --git a/src/mio/repository.py b/src/mio/repository.py
index c1d5c23eb701e27aeffd5137b73d95f3fd890252..65a31323c7b0397052994fc6e219fb1138edbb95 100755
--- a/src/mio/repository.py
+++ b/src/mio/repository.py
@@ -53,8 +53,7 @@ class Repository:
             g._url = None
             group.append(g)
         result = self.expand(group, error, {})
-        result.sort(by_name)
-        return result
+        return sorted(result, key=lambda a: a.name)
 
     def expand(self, targets, error=None, done={}):
         result = []
@@ -78,10 +77,3 @@ class Repository:
                         raise
         return result
 
-def by_name(a,b):
-    if a.name[0] < b.name[0]:
-        return -1
-    elif a.name[0] == b.name[0]:
-        return 0
-    else:
-        return 1
diff --git a/src/mio/urlgrabber_compat.py b/src/mio/urlgrabber_compat.py
index b48ac394dff9e42f96c228f3fc15df4bf822f40f..c5654e3276c299ecc402726109704f45fd572f1c 100755
--- a/src/mio/urlgrabber_compat.py
+++ b/src/mio/urlgrabber_compat.py
@@ -1,7 +1,12 @@
 #
 # Hackish compat library when urlgrabber is missing (MacOS)
 #
-import urllib2
+try:
+    # Python 3
+    from urllib.request import urlopen
+except ImportError:
+    # Python 2 fallback
+    from urllib2 import urlopen
 import calendar
 import time
 
@@ -27,7 +32,7 @@ class CurlWrapper:
     pass
 
 class Compat: 
-    URLGrabError = Exception()
+    URLGrabError = Exception
 
     def URLGrabber(self):
         pass
@@ -38,13 +43,15 @@ class Compat:
             self.urls = urls
             
         def urlopen(self, path):
+            errors = []
             for u in self.urls:
                 url = "%s/%s" % (u, path)
                 try:
-                    return CurlWrapper(urllib2.urlopen(url))
+                    return CurlWrapper(urlopen(url))
                 except IOError as e:
+                    errors.append((url,e))
                     pass
-            raise IOError("Failed to get '%s' (%s)" % (path, e))
+            raise IOError("Failed to get '%s' (%s)" % (path, errors))
             
     pass
 
diff --git a/src/mio/util.py b/src/mio/util.py
index 3e691d2349f0cddf3eb54cb6986c4d538aeb9146..4418193f51f2fb904142eea2773980a0580f4f78 100755
--- a/src/mio/util.py
+++ b/src/mio/util.py
@@ -4,6 +4,12 @@ import pwd
 import re
 from mio.log import log, NORMAL, VERBOSE
 
+try:
+    # Python 2
+    from __builtin__ import unicode
+except ModuleNotFoundError:
+    # Python 3
+    from builtins import str as unicode
 def mode_mask(s):
     result = 0
     for c in s:
diff --git a/src/mio/yum.py b/src/mio/yum.py
index e0874678f2f6ffbcc7d4a8885788691a33b0d56d..a8eab0ce7a35f71a3e0a2790c9e83b4aef174dc1 100755
--- a/src/mio/yum.py
+++ b/src/mio/yum.py
@@ -97,7 +97,7 @@ def run(path, rpm, exclude):
     # to be done before the install, because outdated packages
     # need to be updated before the install, or we might get
     # a conflict.
-    exclude_arg = map(lambda s: "-x %s" % s, exclude)
+    exclude_arg = [ "-x %s" % s for s in exclude ]
     update = "%s -c %s -e %s -d %s -y update %s" % (
         CMD, f.name, verb, verb, " ".join(exclude_arg))
     mio.log.log(NORMAL, update)
@@ -111,5 +111,6 @@ def run(path, rpm, exclude):
         i += 1
         mio.log.log(NORMAL, "[%d/%d] %s %s" % (i, len(rpm), install, p))
         tmp = mio.daemon_cage.system("%s %s" % (install, p))
-        result = max(filter(None, [result, tmp]) or [0])
+        if tmp > result:
+            result = tmp
     return result