3 """Test script to check for required functionality.
5 Execute this code at the command line by typing:
7 python swc-installation-test-2.py
9 Run the script and follow the instructions it prints at the end.
11 This script requires at least Python 2.6. You can check the version
12 of Python that you have installed with 'swc-installation-test-1.py'.
15 from __future__ import print_function # for Python 2.6 compatibility
17 import distutils.ccompiler as _distutils_ccompiler
18 try: # Python 2.7 and 3.x
19 import importlib as _importlib
20 except ImportError: # Python 2.6 and earlier
21 class _Importlib (object):
22 """Minimal workarounds for functions we need
25 def import_module(name):
26 module = __import__(name)
27 for n in name.split('.')[1:]:
28 module = getattr(module, n)
30 _importlib = _Importlib()
31 import logging as _logging
33 import platform as _platform
35 import shlex as _shlex
36 import subprocess as _subprocess
40 if not hasattr(_shlex, 'quote'): # Python versions older than 3.3
41 # Use the undocumented pipes.quote()
42 import pipes as _pipes
43 _shlex.quote = _pipes.quote
48 # Comment out any entries you don't need
58 'hg', # Command line tool
59 'mercurial', # Python package
60 # Build tools and packaging
65 'nosetests', # Command line tool
66 'nose', # Python package
68 'sqlite3', # Command line tool
69 'sqlite3-python', # Python package
85 class DependencyError (Exception):
86 def _get_message(self):
88 def _set_message(self, message):
89 self._message = message
90 message = property(_get_message, _set_message)
92 def __init__(self, checker, message):
93 super(DependencyError, self).__init__(message)
94 self.checker = checker
95 self.message = message
98 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
99 return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
100 self.checker.full_name(), self.message,
101 'For instructions on installing an up-to-date version, see',
105 def check(checks=None):
111 checker = CHECKER[check]
112 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
114 version = checker.check()
115 except DependencyError as e:
117 _sys.stdout.write('fail\n')
119 _sys.stdout.write('pass\n')
120 successes.append((checker, version))
122 print('\nSuccesses:\n')
123 for checker,version in successes:
124 print('{0} {1}'.format(
126 version or 'unknown'))
130 for failure in failures:
131 if failure not in printed:
134 printed.append(failure)
139 class Dependency (object):
140 def __init__(self, name, long_name=None, minimum_version=None,
141 version_delimiter='.', and_dependencies=None,
142 or_dependencies=None):
144 self.long_name = long_name or name
145 self.minimum_version = minimum_version
146 self.version_delimiter = version_delimiter
147 if not and_dependencies:
148 and_dependencies = []
149 self.and_dependencies = and_dependencies
150 if not or_dependencies:
152 self.or_dependencies = or_dependencies
153 self._check_error = None
156 return '<{0} {1}>'.format(type(self).__name__, self.name)
159 if self.name == self.long_name:
162 return '{0} ({1})'.format(self.long_name, self.name)
165 if self._check_error:
166 raise self._check_error
168 self._check_dependencies()
170 except DependencyError as e:
171 self._check_error = e # cache for future calls
174 def _check_dependencies(self):
175 for dependency in self.and_dependencies:
176 if not hasattr(dependency, 'check'):
177 dependency = CHECKER[dependency]
179 self.or_pass = or_error = None
180 for dependency in self.or_dependencies:
181 if not hasattr(dependency, 'check'):
182 dependency = CHECKER[dependency]
184 version = dependency.check()
185 except DependencyError as e:
189 'dependency': dependency,
192 break # no need to test other dependencies
193 if self.or_dependencies and not self.or_pass:
197 version = self._get_version()
198 parsed_version = None
199 if hasattr(self, '_get_parsed_version'):
200 parsed_version = self._get_parsed_version()
201 if self.minimum_version:
202 self._check_version(version=version, parsed_version=parsed_version)
205 def _get_version(self):
206 raise NotImplementedError(self)
208 def _check_version(self, version, parsed_version=None):
209 if not parsed_version:
210 parsed_version = self._parse_version(version=version)
211 if not parsed_version or parsed_version < self.minimum_version:
212 raise DependencyError(
214 message='outdated version of {0}: {1} (need >= {2})'.format(
215 self.full_name(), version,
216 self.version_delimiter.join(
217 str(part) for part in self.minimum_version)))
219 def _parse_version(self, version):
223 for part in version.split(self.version_delimiter):
225 parsed_version.append(int(part))
226 except ValueError as e:
227 raise NotImplementedError((version, part))# from e
228 return tuple(parsed_version)
231 class PythonDependency (Dependency):
232 def __init__(self, name='python', long_name='Python version',
233 minimum_version=(2, 6), **kwargs):
234 super(PythonDependency, self).__init__(
235 name=name, long_name=long_name, minimum_version=minimum_version,
238 def _get_version(self):
241 def _get_parsed_version(self):
242 return _sys.version_info
245 CHECKER['python'] = PythonDependency()
248 class CommandDependency (Dependency):
249 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
251 def __init__(self, command, version_options=('--version',), stdin=None,
252 version_regexp=None, version_stream='stdout', **kwargs):
253 if 'name' not in kwargs:
254 kwargs['name'] = command
255 super(CommandDependency, self).__init__(**kwargs)
256 self.command = command
257 self.version_options = version_options
259 if not version_regexp:
260 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
261 version_regexp = _re.compile(regexp)
262 self.version_regexp = version_regexp
263 self.version_stream = version_stream
265 def _get_version_stream(self, stdin=None, expect=(0,)):
269 popen_stdin = _subprocess.PIPE
272 command = self.command + (self.exe_extension or '')
274 p = _subprocess.Popen(
275 [command] + list(self.version_options), stdin=popen_stdin,
276 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
277 close_fds=True, shell=False, universal_newlines=True)
279 raise DependencyError(
281 message="could not find '{0}' executable".format(command),
283 stdout,stderr = p.communicate(stdin)
285 if status not in expect:
287 "failed to execute: {0} {1}".format(
289 ' '.join(_shlex.quote(arg)
290 for arg in self.version_options)),
291 'status: {0}'.format(status),
293 for name,string in [('stdout', stdout), ('stderr', stderr)]:
295 lines.extend([name + ':', string])
296 raise DependencyError(checker=self, message='\n'.join(lines))
297 for name,string in [('stdout', stdout), ('stderr', stderr)]:
298 if name == self.version_stream:
300 raise NotImplementedError(self.version_stream)
302 def _get_version(self):
303 version_stream = self._get_version_stream()
304 match = self.version_regexp.search(version_stream)
306 raise DependencyError(
308 message='no version string in output:\n{0}'.format(
310 return match.group(1)
313 for command,long_name,minimum_version in [
314 ('sh', 'Bourne Shell', None),
315 ('ash', 'Almquist Shell', None),
316 ('bash', 'Bourne Again Shell', None),
317 ('csh', 'C Shell', None),
318 ('ksh', 'KornShell', None),
319 ('dash', 'Debian Almquist Shell', None),
320 ('tcsh', 'TENEX C Shell', None),
321 ('zsh', 'Z Shell', None),
322 ('git', 'Git', (1, 7, 0)),
323 ('hg', 'Mercurial', (2, 0, 0)),
324 ('make', None, None),
325 ('sqlite3', 'SQLite 3', None),
326 ('nosetests', 'Nose', (1, 0, 0)),
327 ('emacs', 'Emacs', None),
328 ('xemacs', 'XEmacs', None),
329 ('vim', 'Vim', None),
331 ('nano', 'Nano', None),
332 ('kate', 'Kate', None),
333 ('notepad++', 'Notepad++', None),
334 ('firefox', 'Firefox', None),
335 ('google-chrome', 'Google Chrome', None),
336 ('chromium', 'Chromium', None),
340 CHECKER[command] = CommandDependency(
341 command=command, long_name=long_name, minimum_version=minimum_version)
342 del command, long_name, minimum_version # cleanup namespace
345 class EasyInstallDependency (CommandDependency):
346 def _get_version(self):
348 return super(EasyInstallDependency, self)._get_version()
349 except DependencyError as e:
350 version_stream = self.version_stream
352 self.version_stream = 'stderr'
353 stream = self._get_version_stream(expect=(1,))
354 if 'option --version not recognized' in stream:
355 return 'unknown (possibly Setuptools?)'
357 self.version_stream = version_stream
360 CHECKER['easy_install'] = EasyInstallDependency(
361 command='easy_install', long_name='Setuptools easy_install',
362 minimum_version=None)
365 class PythonPackageDependency (Dependency):
366 def __init__(self, package, **kwargs):
367 if 'name' not in kwargs:
368 kwargs['name'] = package
369 if 'and_dependencies' not in kwargs:
370 kwargs['and_dependencies'] = []
371 if 'python' not in kwargs['and_dependencies']:
372 kwargs['and_dependencies'].append('python')
373 super(PythonPackageDependency, self).__init__(**kwargs)
374 self.package = package
376 def _get_version(self):
377 package = self._get_package(self.package)
378 return self._get_version_from_package(package)
380 def _get_package(self, package):
382 return _importlib.import_module(package)
383 except ImportError as e:
384 raise DependencyError(
386 message="could not import the '{0}' package for {1}".format(
387 package, self.full_name()),
390 def _get_version_from_package(self, package):
392 version = package.__version__
393 except AttributeError:
398 for package,name,long_name,minimum_version in [
399 ('nose', None, 'Nose Python package',
400 CHECKER['nosetests'].minimum_version),
401 ('IPython', None, None, None),
402 ('numpy', None, 'NumPy', None),
403 ('scipy', None, 'SciPy', None),
404 ('matplotlib', None, 'Matplotlib', None),
405 ('sympy', None, 'SymPy', None),
406 ('Cython', None, None, None),
407 ('networkx', None, 'NetworkX', None),
408 ('mayavi.mlab', None, 'MayaVi', None),
409 ('setuptools', None, 'Setuptools', None),
415 CHECKER[name] = PythonPackageDependency(
416 package=package, name=name, long_name=long_name,
417 minimum_version=minimum_version)
418 del package, name, long_name, minimum_version # cleanup namespace
421 class MercurialPythonPackage (PythonPackageDependency):
422 def _get_version(self):
423 try: # mercurial >= 1.2
424 package = _importlib.import_module('mercurial.util')
425 except ImportError as e: # mercurial <= 1.1.2
426 package = self._get_package('mercurial.version')
427 return package.get_version()
429 return package.version()
432 CHECKER['mercurial'] = MercurialPythonPackage(
433 package='mercurial.util', name='mercurial',
434 long_name='Mercurial Python package',
435 minimum_version=CHECKER['hg'].minimum_version)
438 class SQLitePythonPackage (PythonPackageDependency):
439 def _get_version_from_package(self, package):
442 def _get_parsed_version(self):
443 return _sys.version_info
446 CHECKER['sqlite3-python'] = SQLitePythonPackage(
447 package='sqlite3', name='sqlite3-python',
448 long_name='SQLite Python package',
449 minimum_version=CHECKER['sqlite3'].minimum_version)
452 class VirtualDependency (Dependency):
454 return '{0} {1}'.format(
455 self.or_pass['dependency'].full_name(),
456 self.or_pass['version'])
459 for name,dependencies in [
479 ('virtual-browser', (
485 CHECKER[name] = VirtualDependency(
486 name=name, long_name=name, or_dependencies=dependencies)
487 del name, dependencies # cleanup namespace
490 def _print_info(key, value, indent=19):
491 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
493 def print_system_info():
494 print("If you do not understand why the above failures occurred,")
495 print("copy and send the *entire* output (all info above and summary")
496 print("below) to the instructor for help.")
498 print('==================')
499 print('System information')
500 print('==================')
501 _print_info('os.name', _os.name)
502 _print_info('os.uname', _platform.uname())
503 _print_info('platform', _sys.platform)
504 _print_info('platform+', _platform.platform())
506 'linux_distribution',
510 value = getattr(_platform, pversion)()
512 _print_info(pversion, value)
513 _print_info('prefix', _sys.prefix)
514 _print_info('exec_prefix', _sys.exec_prefix)
515 _print_info('executable', _sys.executable)
516 _print_info('version_info', _sys.version_info)
517 _print_info('version', _sys.version)
518 _print_info('environment', '')
519 for key,value in sorted(_os.environ.items()):
520 print(' {0}={1}'.format(key, value))
521 print('==================')
523 def print_suggestions(instructor_fallback=True):
525 print('For suggestions on installing missing packages, see')
526 print('http://software-carpentry.org/setup/')
528 print('For instructings on installing a particular package,')
529 print('see the failure message for that package printed above.')
530 if instructor_fallback:
532 print('For help, email the *entire* output of this script to')
533 print('your instructor.')
536 if __name__ == '__main__':
537 if not check(_sys.argv[1:]):
540 print_suggestions(instructor_fallback=True)