diff options
author | Mike Frysinger <vapier@google.com> | 2019-06-13 01:48:12 -0400 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2020-02-05 21:32:02 +0000 |
commit | 3ba716f3823c010a9788077d9515c26db5d58f11 (patch) | |
tree | 0a2aedbda1a4940dec2fe63d513935e847f9e106 | |
parent | 655aedd7f34d9f2ff6dd3cb77c080addd0f06c4b (diff) | |
download | git-repo-3ba716f3823c010a9788077d9515c26db5d58f11.tar.gz |
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>
-rwxr-xr-x | repo | 105 |
1 files changed, 81 insertions, 24 deletions
@@ -10,6 +10,84 @@ copy of repo in the checkout. | |||
10 | 10 | ||
11 | from __future__ import print_function | 11 | from __future__ import print_function |
12 | 12 | ||
13 | import os | ||
14 | import platform | ||
15 | import subprocess | ||
16 | import sys | ||
17 | |||
18 | |||
19 | def exec_command(cmd): | ||
20 | """Execute |cmd| or return None on failure.""" | ||
21 | try: | ||
22 | if platform.system() == 'Windows': | ||
23 | ret = subprocess.call(cmd) | ||
24 | sys.exit(ret) | ||
25 | else: | ||
26 | os.execvp(cmd[0], cmd) | ||
27 | except: | ||
28 | pass | ||
29 | |||
30 | |||
31 | def check_python_version(): | ||
32 | """Make sure the active Python version is recent enough.""" | ||
33 | def reexec(prog): | ||
34 | exec_command([prog] + sys.argv) | ||
35 | |||
36 | MIN_PYTHON_VERSION = (3, 6) | ||
37 | |||
38 | ver = sys.version_info | ||
39 | major = ver.major | ||
40 | minor = ver.minor | ||
41 | |||
42 | # Abort on very old Python 2 versions. | ||
43 | if (major, minor) < (2, 7): | ||
44 | print('repo: error: Your Python version is too old. ' | ||
45 | 'Please use Python {}.{} or newer instead.'.format( | ||
46 | *MIN_PYTHON_VERSION), file=sys.stderr) | ||
47 | sys.exit(1) | ||
48 | |||
49 | # Try to re-exec the version specific Python 3 if needed. | ||
50 | if (major, minor) < MIN_PYTHON_VERSION: | ||
51 | # Python makes releases ~once a year, so try our min version +10 to help | ||
52 | # bridge the gap. This is the fallback anyways so perf isn't critical. | ||
53 | min_major, min_minor = MIN_PYTHON_VERSION | ||
54 | for inc in range(0, 10): | ||
55 | reexec('python{}.{}'.format(min_major, min_minor + inc)) | ||
56 | |||
57 | # Try the generic Python 3 wrapper, but only if it's new enough. We don't | ||
58 | # want to go from (still supported) Python 2.7 to (unsupported) Python 3.5. | ||
59 | try: | ||
60 | proc = subprocess.Popen( | ||
61 | ['python3', '-c', 'import sys; ' | ||
62 | 'print(sys.version_info.major, sys.version_info.minor)'], | ||
63 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
64 | (output, _) = proc.communicate() | ||
65 | python3_ver = tuple(int(x) for x in output.decode('utf-8').split()) | ||
66 | except (OSError, subprocess.CalledProcessError): | ||
67 | python3_ver = None | ||
68 | |||
69 | # The python3 version looks like it's new enough, so give it a try. | ||
70 | if python3_ver and python3_ver >= MIN_PYTHON_VERSION: | ||
71 | reexec('python3') | ||
72 | |||
73 | # We're still here, so diagnose things for the user. | ||
74 | if major < 3: | ||
75 | print('repo: warning: Python 2 is no longer supported; ' | ||
76 | 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION), | ||
77 | file=sys.stderr) | ||
78 | else: | ||
79 | print('repo: error: Python 3 version is too old; ' | ||
80 | 'Please use Python {}.{} or newer.'.format(*MIN_PYTHON_VERSION), | ||
81 | file=sys.stderr) | ||
82 | sys.exit(1) | ||
83 | |||
84 | |||
85 | if __name__ == '__main__': | ||
86 | # TODO(vapier): Enable this on Windows once we have Python 3 issues fixed. | ||
87 | if platform.system() != 'Windows': | ||
88 | check_python_version() | ||
89 | |||
90 | |||
13 | # repo default configuration | 91 | # repo default configuration |
14 | # | 92 | # |
15 | import os | 93 | import os |
@@ -91,7 +169,6 @@ repodir = '.repo' # name of repo's private directory | |||
91 | S_repo = 'repo' # special repo repository | 169 | S_repo = 'repo' # special repo repository |
92 | S_manifests = 'manifests' # special manifest repository | 170 | S_manifests = 'manifests' # special manifest repository |
93 | REPO_MAIN = S_repo + '/main.py' # main script | 171 | REPO_MAIN = S_repo + '/main.py' # main script |
94 | MIN_PYTHON_VERSION = (2, 7) # minimum supported python version | ||
95 | GITC_CONFIG_FILE = '/gitc/.config' | 172 | GITC_CONFIG_FILE = '/gitc/.config' |
96 | GITC_FS_ROOT_DIR = '/gitc/manifest-rw/' | 173 | GITC_FS_ROOT_DIR = '/gitc/manifest-rw/' |
97 | 174 | ||
@@ -99,12 +176,9 @@ GITC_FS_ROOT_DIR = '/gitc/manifest-rw/' | |||
99 | import collections | 176 | import collections |
100 | import errno | 177 | import errno |
101 | import optparse | 178 | import optparse |
102 | import platform | ||
103 | import re | 179 | import re |
104 | import shutil | 180 | import shutil |
105 | import stat | 181 | import stat |
106 | import subprocess | ||
107 | import sys | ||
108 | 182 | ||
109 | if sys.version_info[0] == 3: | 183 | if sys.version_info[0] == 3: |
110 | import urllib.request | 184 | import urllib.request |
@@ -117,17 +191,6 @@ else: | |||
117 | urllib.error = urllib2 | 191 | urllib.error = urllib2 |
118 | 192 | ||
119 | 193 | ||
120 | # Python version check | ||
121 | ver = sys.version_info | ||
122 | if (ver[0], ver[1]) < MIN_PYTHON_VERSION: | ||
123 | print('error: Python version {} unsupported.\n' | ||
124 | 'Please use Python {}.{} instead.'.format( | ||
125 | sys.version.split(' ')[0], | ||
126 | MIN_PYTHON_VERSION[0], | ||
127 | MIN_PYTHON_VERSION[1], | ||
128 | ), file=sys.stderr) | ||
129 | sys.exit(1) | ||
130 | |||
131 | home_dot_repo = os.path.expanduser('~/.repoconfig') | 194 | home_dot_repo = os.path.expanduser('~/.repoconfig') |
132 | gpg_dir = os.path.join(home_dot_repo, 'gnupg') | 195 | gpg_dir = os.path.join(home_dot_repo, 'gnupg') |
133 | 196 | ||
@@ -894,15 +957,9 @@ def main(orig_args): | |||
894 | '--'] | 957 | '--'] |
895 | me.extend(orig_args) | 958 | me.extend(orig_args) |
896 | me.extend(extra_args) | 959 | me.extend(extra_args) |
897 | try: | 960 | exec_command(me) |
898 | if platform.system() == "Windows": | 961 | print("fatal: unable to start %s" % repo_main, file=sys.stderr) |
899 | sys.exit(subprocess.call(me)) | 962 | sys.exit(148) |
900 | else: | ||
901 | os.execv(sys.executable, me) | ||
902 | except OSError as e: | ||
903 | print("fatal: unable to start %s" % repo_main, file=sys.stderr) | ||
904 | print("fatal: %s" % e, file=sys.stderr) | ||
905 | sys.exit(148) | ||
906 | 963 | ||
907 | 964 | ||
908 | if __name__ == '__main__': | 965 | if __name__ == '__main__': |