Thu, 15 Jan 2015 21:13:52 +0100
Remove forgotten relic of ABI crash risk averse overloaded method change.
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 # You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__ import print_function, unicode_literals
7 import os
8 import re
9 import subprocess
10 import sys
12 from distutils.version import StrictVersion
15 NO_MERCURIAL = '''
16 Could not find Mercurial (hg) in the current shell's path. Try starting a new
17 shell and running the bootstrapper again.
18 '''
20 MERCURIAL_UNABLE_UPGRADE = '''
21 You are currently running Mercurial %s. Running %s or newer is
22 recommended for performance and stability reasons.
24 Unfortunately, this bootstrapper currently does not know how to automatically
25 upgrade Mercurial on your machine.
27 You can usually install Mercurial through your package manager or by
28 downloading a package from http://mercurial.selenic.com/.
29 '''
31 MERCURIAL_UPGRADE_FAILED = '''
32 We attempted to upgrade Mercurial to a modern version (%s or newer).
33 However, you appear to have version %s still.
35 It's possible your package manager doesn't support a modern version of
36 Mercurial. It's also possible Mercurial is not being installed in the search
37 path for this shell. Try creating a new shell and run this bootstrapper again.
39 If it continues to fail, consider installing Mercurial by following the
40 instructions at http://mercurial.selenic.com/.
41 '''
43 PYTHON_UNABLE_UPGRADE = '''
44 You are currently running Python %s. Running %s or newer (but
45 not 3.x) is required.
47 Unfortunately, this bootstrapper does not currently know how to automatically
48 upgrade Python on your machine.
50 Please search the Internet for how to upgrade your Python and try running this
51 bootstrapper again to ensure your machine is up to date.
52 '''
54 PYTHON_UPGRADE_FAILED = '''
55 We attempted to upgrade Python to a modern version (%s or newer).
56 However, you appear to still have version %s.
58 It's possible your package manager doesn't yet expose a modern version of
59 Python. It's also possible Python is not being installed in the search path for
60 this shell. Try creating a new shell and run this bootstrapper again.
62 If this continues to fail and you are sure you have a modern Python on your
63 system, ensure it is on the $PATH and try again. If that fails, you'll need to
64 install Python manually and ensure the path with the python binary is listed in
65 the $PATH environment variable.
67 We recommend the following tools for installing Python:
69 pyenv -- https://github.com/yyuu/pyenv)
70 pythonz -- https://github.com/saghul/pythonz
71 official installers -- http://www.python.org/
72 '''
75 # Upgrade Mercurial older than this.
76 MODERN_MERCURIAL_VERSION = StrictVersion('2.5')
78 # Upgrade Python older than this.
79 MODERN_PYTHON_VERSION = StrictVersion('2.7.3')
82 class BaseBootstrapper(object):
83 """Base class for system bootstrappers."""
85 def __init__(self):
86 self.package_manager_updated = False
88 def install_system_packages(self):
89 raise NotImplemented('%s must implement install_system_packages()' %
90 __name__)
92 def which(self, name):
93 """Python implementation of which.
95 It returns the path of an executable or None if it couldn't be found.
96 """
97 for path in os.environ['PATH'].split(os.pathsep):
98 test = os.path.join(path, name)
99 if os.path.exists(test) and os.access(test, os.X_OK):
100 return test
102 return None
104 def run_as_root(self, command):
105 if os.geteuid() != 0:
106 if self.which('sudo'):
107 command.insert(0, 'sudo')
108 else:
109 command = ['su', 'root', '-c', ' '.join(command)]
111 print('Executing as root:', subprocess.list2cmdline(command))
113 subprocess.check_call(command, stdin=sys.stdin)
115 def yum_install(self, *packages):
116 command = ['yum', 'install']
117 command.extend(packages)
119 self.run_as_root(command)
121 def yum_groupinstall(self, *packages):
122 command = ['yum', 'groupinstall']
123 command.extend(packages)
125 self.run_as_root(command)
127 def yum_update(self, *packages):
128 command = ['yum', 'update']
129 command.extend(packages)
131 self.run_as_root(command)
133 def apt_install(self, *packages):
134 command = ['apt-get', 'install']
135 command.extend(packages)
137 self.run_as_root(command)
139 def apt_update(self):
140 command = ['apt-get', 'update']
142 self.run_as_root(command)
144 def apt_add_architecture(self, arch):
145 command = ['dpkg', '--add-architecture']
146 command.extend(arch)
148 self.run_as_root(command)
150 def check_output(self, *args, **kwargs):
151 """Run subprocess.check_output even if Python doesn't provide it."""
152 fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output)
154 return fn(*args, **kwargs)
156 @staticmethod
157 def _check_output(*args, **kwargs):
158 """Python 2.6 compatible implementation of subprocess.check_output."""
159 proc = subprocess.Popen(stdout=subprocess.PIPE, *args, **kwargs)
160 output, unused_err = proc.communicate()
161 retcode = proc.poll()
162 if retcode:
163 cmd = kwargs.get('args', args[0])
164 e = subprocess.CalledProcessError(retcode, cmd)
165 e.output = output
166 raise e
168 return output
170 def prompt_int(self, prompt, low, high, limit=5):
171 ''' Prompts the user with prompt and requires an integer between low and high. '''
172 valid = False
173 while not valid and limit > 0:
174 try:
175 choice = int(raw_input(prompt))
176 if not low <= choice <= high:
177 print("ERROR! Please enter a valid option!")
178 limit -= 1
179 else:
180 valid = True
181 except ValueError:
182 print("ERROR! Please enter a valid option!")
183 limit -= 1
185 if limit > 0:
186 return choice
187 else:
188 raise Exception("Error! Reached max attempts of entering option.")
190 def _ensure_package_manager_updated(self):
191 if self.package_manager_updated:
192 return
194 self._update_package_manager()
195 self.package_manager_updated = True
197 def _update_package_manager(self):
198 """Updates the package manager's manifests/package list.
200 This should be defined in child classes.
201 """
203 def _hgplain_env(self):
204 """ Returns a copy of the current environment updated with the HGPLAIN
205 environment variable.
207 HGPLAIN prevents Mercurial from applying locale variations to the output
208 making it suitable for use in scripts.
209 """
210 env = os.environ.copy()
211 env['HGPLAIN'] = '1'
212 return env
214 def is_mercurial_modern(self):
215 hg = self.which('hg')
216 if not hg:
217 print(NO_MERCURIAL)
218 return False, False, None
220 info = self.check_output([hg, '--version'], env=self._hgplain_env()).splitlines()[0]
222 match = re.search('version ([^\+\)]+)', info)
223 if not match:
224 print('ERROR: Unable to identify Mercurial version.')
225 return True, False, None
227 our = StrictVersion(match.group(1))
229 return True, our >= MODERN_MERCURIAL_VERSION, our
231 def ensure_mercurial_modern(self):
232 installed, modern, version = self.is_mercurial_modern()
234 if not installed or modern:
235 print('Your version of Mercurial (%s) is sufficiently modern.' %
236 version)
237 return
239 self._ensure_package_manager_updated()
240 self.upgrade_mercurial(version)
242 installed, modern, after = self.is_mercurial_modern()
244 if installed and not modern:
245 print(MERCURIAL_UPGRADE_FAILED % (MODERN_MERCURIAL_VERSION, after))
247 def upgrade_mercurial(self, current):
248 """Upgrade Mercurial.
250 Child classes should reimplement this.
251 """
252 print(MERCURIAL_UNABLE_UPGRADE % (current, MODERN_MERCURIAL_VERSION))
254 def is_python_modern(self):
255 python = None
257 for test in ['python2.7', 'python']:
258 python = self.which(test)
259 if python:
260 break
262 assert python
264 info = self.check_output([python, '--version'],
265 stderr=subprocess.STDOUT)
266 match = re.search('Python ([a-z0-9\.]+)', info)
267 if not match:
268 print('ERROR Unable to identify Python version.')
269 return False, None
271 our = StrictVersion(match.group(1))
273 return our >= MODERN_PYTHON_VERSION, our
275 def ensure_python_modern(self):
276 modern, version = self.is_python_modern()
278 if modern:
279 print('Your version of Python (%s) is new enough.' % version)
280 return
282 print('Your version of Python (%s) is too old. Will try to upgrade.' %
283 version)
285 self._ensure_package_manager_updated()
286 self.upgrade_python(version)
288 modern, after = self.is_python_modern()
290 if not modern:
291 print(PYTHON_UPGRADE_FAILED % (MODERN_PYTHON_VERSION, after))
292 sys.exit(1)
294 def upgrade_python(self, current):
295 """Upgrade Python.
297 Child classes should reimplement this.
298 """
299 print(PYTHON_UNABLE_UPGRADE % (current, MODERN_PYTHON_VERSION))