repo: try to reexec self with Python 3 as needed
We want to start warning about Python 2 usage, but we can't do it
simply because the shebang is /usr/bin/python which might be an old
version like python2.7.
We can't change the shebang because program name usage is spotty at
best: on some platforms (like macOS), it's not uncommon to not have
a `python3` wrapper, only a major.minor one like `python3.6`. Using
python3 wouldn't guarantee a new enough version of Python 3 anyways,
and we don't want to require Python 3.6 exactly, just that minimum.
So we check the current Python version. If it's older than the ver
of Python 3 we want, we search for a `python3.X` version to run. If
those don't work, we see if `python3` exists and is a new enough ver.
If it's not, we die if the current Python 3 is too old, and we start
issuing warnings if the current Python version is 2.7. This should
allow the user to take a bit more action by installing Python 3 on
their system without having to worry about changing /usr/bin/python.
Once we require Python 3 completely, we can simplify this logic a bit
by always bootstrapping up to Python 3 and failing with Python 2.
We have a few KI with Windows atm though, so keep it disabled there
until the fixes are merged.
Bug: https://crbug.com/gerrit/10418
Change-Id: I5e157defc788e31efb3e21e93f53fabdc7d75a3c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/253136
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
diff --git a/repo b/repo
index 0b87073..95a212d 100755
--- a/repo
+++ b/repo
@@ -10,6 +10,84 @@
from __future__ import print_function
+import os
+import platform
+import subprocess
+import sys
+
+
+def exec_command(cmd):
+ """Execute |cmd| or return None on failure."""
+ try:
+ if platform.system() == 'Windows':
+ ret = subprocess.call(cmd)
+ sys.exit(ret)
+ else:
+ os.execvp(cmd[0], cmd)
+ except:
+ pass
+
+
+def check_python_version():
+ """Make sure the active Python version is recent enough."""
+ def reexec(prog):
+ exec_command([prog] + sys.argv)
+
+ MIN_PYTHON_VERSION = (3, 6)
+
+ ver = sys.version_info
+ major = ver.major
+ minor = ver.minor
+
+ # Abort on very old Python 2 versions.
+ if (major, minor) < (2, 7):
+ print('repo: error: Your Python version is too old. '
+ 'Please use Python {}.{} or newer instead.'.format(
+ *MIN_PYTHON_VERSION), file=sys.stderr)
+ sys.exit(1)
+
+ # Try to re-exec the version specific Python 3 if needed.
+ if (major, minor) < MIN_PYTHON_VERSION:
+ # Python makes releases ~once a year, so try our min version +10 to help
+ # bridge the gap. This is the fallback anyways so perf isn't critical.
+ min_major, min_minor = MIN_PYTHON_VERSION
+ for inc in range(0, 10):
+ reexec('python{}.{}'.format(min_major, min_minor + inc))
+
+ # Try the generic Python 3 wrapper, but only if it's new enough. We don't
+ # want to go from (still supported) Python 2.7 to (unsupported) Python 3.5.
+ try:
+ proc = subprocess.Popen(
+ ['python3', '-c', 'import sys; '
+ 'print(sys.version_info.major, sys.version_info.minor)'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (output, _) = proc.communicate()
+ python3_ver = tuple(int(x) for x in output.decode('utf-8').split())
+ except (OSError, subprocess.CalledProcessError):
+ python3_ver = None
+
+ # The python3 version looks like it's new enough, so give it a try.
+ if python3_ver and python3_ver >= MIN_PYTHON_VERSION:
+ reexec('python3')
+
+ # We're still here, so diagnose things for the user.
+ if major < 3:
+ print('repo: warning: Python 2 is no longer supported; '
+ 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION),
+ file=sys.stderr)
+ else:
+ print('repo: error: Python 3 version is too old; '
+ 'Please use Python {}.{} or newer.'.format(*MIN_PYTHON_VERSION),
+ file=sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ # TODO(vapier): Enable this on Windows once we have Python 3 issues fixed.
+ if platform.system() != 'Windows':
+ check_python_version()
+
+
# repo default configuration
#
import os
@@ -91,7 +169,6 @@
S_repo = 'repo' # special repo repository
S_manifests = 'manifests' # special manifest repository
REPO_MAIN = S_repo + '/main.py' # main script
-MIN_PYTHON_VERSION = (2, 7) # minimum supported python version
GITC_CONFIG_FILE = '/gitc/.config'
GITC_FS_ROOT_DIR = '/gitc/manifest-rw/'
@@ -99,12 +176,9 @@
import collections
import errno
import optparse
-import platform
import re
import shutil
import stat
-import subprocess
-import sys
if sys.version_info[0] == 3:
import urllib.request
@@ -117,17 +191,6 @@
urllib.error = urllib2
-# Python version check
-ver = sys.version_info
-if (ver[0], ver[1]) < MIN_PYTHON_VERSION:
- print('error: Python version {} unsupported.\n'
- 'Please use Python {}.{} instead.'.format(
- sys.version.split(' ')[0],
- MIN_PYTHON_VERSION[0],
- MIN_PYTHON_VERSION[1],
- ), file=sys.stderr)
- sys.exit(1)
-
home_dot_repo = os.path.expanduser('~/.repoconfig')
gpg_dir = os.path.join(home_dot_repo, 'gnupg')
@@ -894,15 +957,9 @@
'--']
me.extend(orig_args)
me.extend(extra_args)
- try:
- if platform.system() == "Windows":
- sys.exit(subprocess.call(me))
- else:
- os.execv(sys.executable, me)
- except OSError as e:
- print("fatal: unable to start %s" % repo_main, file=sys.stderr)
- print("fatal: %s" % e, file=sys.stderr)
- sys.exit(148)
+ exec_command(me)
+ print("fatal: unable to start %s" % repo_main, file=sys.stderr)
+ sys.exit(148)
if __name__ == '__main__':