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'.
14 By default, this script will test for all the dependencies your
15 instructor thinks you need. If you want to test for a different set
16 of packages, you can list them on the command line. For example:
18 python swc-installation-test-2.py git virtual-editor
20 This is useful if the original test told you to install a more recent
21 version of a particular dependency, and you just want to re-test that
25 from __future__ import print_function # for Python 2.6 compatibility
27 import distutils.ccompiler as _distutils_ccompiler
28 try: # Python 2.7 and 3.x
29 import importlib as _importlib
30 except ImportError: # Python 2.6 and earlier
31 class _Importlib (object):
32 """Minimal workarounds for functions we need
35 def import_module(name):
36 module = __import__(name)
37 for n in name.split('.')[1:]:
38 module = getattr(module, n)
40 _importlib = _Importlib()
41 import logging as _logging
43 import platform as _platform
45 import shlex as _shlex
46 import subprocess as _subprocess
50 if not hasattr(_shlex, 'quote'): # Python versions older than 3.3
51 # Use the undocumented pipes.quote()
52 import pipes as _pipes
53 _shlex.quote = _pipes.quote
58 # Comment out any entries you don't need
68 'hg', # Command line tool
69 #'mercurial', # Python package
70 # Build tools and packaging
72 'virtual-pypi-installer',
75 'nosetests', # Command line tool
76 'nose', # Python package
78 'sqlite3', # Command line tool
79 'sqlite3-python', # Python package
82 'ipython', # Command line tool
83 'IPython', # Python package
97 class InvalidCheck (KeyError):
98 def __init__(self, check):
99 super(InvalidCheck, self).__init__(check)
106 class DependencyError (Exception):
107 def _get_message(self):
109 def _set_message(self, message):
110 self._message = message
111 message = property(_get_message, _set_message)
113 def __init__(self, checker, message, causes=None):
114 super(DependencyError, self).__init__(message)
115 self.checker = checker
116 self.message = message
122 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
124 'check for {0} failed:'.format(self.checker.full_name()),
126 ' For instructions on installing an up-to-date version, see',
130 lines.append(' causes:')
131 for cause in self.causes:
132 lines.extend(' ' + line for line in str(cause).splitlines())
133 return '\n'.join(lines)
136 def check(checks=None):
143 checker = CHECKER[check]
144 except KeyError as e:
145 raise InvalidCheck(check)# from e
146 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
148 version = checker.check()
149 except DependencyError as e:
151 _sys.stdout.write('fail\n')
153 _sys.stdout.write('pass\n')
154 successes.append((checker, version))
156 print('\nSuccesses:\n')
157 for checker,version in successes:
158 print('{0} {1}'.format(
160 version or 'unknown'))
164 for failure in failures:
165 if failure not in printed:
168 printed.append(failure)
173 class Dependency (object):
174 def __init__(self, name, long_name=None, minimum_version=None,
175 version_delimiter='.', and_dependencies=None,
176 or_dependencies=None):
178 self.long_name = long_name or name
179 self.minimum_version = minimum_version
180 self.version_delimiter = version_delimiter
181 if not and_dependencies:
182 and_dependencies = []
183 self.and_dependencies = and_dependencies
184 if not or_dependencies:
186 self.or_dependencies = or_dependencies
187 self._check_error = None
190 return '<{0} {1}>'.format(type(self).__name__, self.name)
193 if self.name == self.long_name:
196 return '{0} ({1})'.format(self.long_name, self.name)
199 if self._check_error:
200 raise self._check_error
202 self._check_dependencies()
204 except DependencyError as e:
205 self._check_error = e # cache for future calls
208 def _check_dependencies(self):
209 for dependency in self.and_dependencies:
210 if not hasattr(dependency, 'check'):
211 dependency = CHECKER[dependency]
214 except DependencyError as e:
215 raise DependencyError(
218 'some dependencies for {0} were not satisfied'
219 ).format(self.full_name()),
223 for dependency in self.or_dependencies:
224 if not hasattr(dependency, 'check'):
225 dependency = CHECKER[dependency]
227 version = dependency.check()
228 except DependencyError as e:
232 'dependency': dependency,
235 break # no need to test other dependencies
236 if self.or_dependencies and not self.or_pass:
237 raise DependencyError(
240 '{0} requires at least one of the following dependencies'
241 ).format(self.full_name()),
245 version = self._get_version()
246 parsed_version = None
247 if hasattr(self, '_get_parsed_version'):
248 parsed_version = self._get_parsed_version()
249 if self.minimum_version:
250 self._check_version(version=version, parsed_version=parsed_version)
253 def _get_version(self):
254 raise NotImplementedError(self)
256 def _minimum_version_string(self):
257 return self.version_delimiter.join(
258 str(part) for part in self.minimum_version)
260 def _check_version(self, version, parsed_version=None):
261 if not parsed_version:
262 parsed_version = self._parse_version(version=version)
263 if not parsed_version or parsed_version < self.minimum_version:
264 raise DependencyError(
266 message='outdated version of {0}: {1} (need >= {2})'.format(
267 self.full_name(), version, self._minimum_version_string()))
269 def _parse_version(self, version):
273 for part in version.split(self.version_delimiter):
275 parsed_version.append(int(part))
276 except ValueError as e:
277 raise DependencyError(
280 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
282 part, version, self.full_name(),
283 self._minimum_version_string()))# from e
284 return tuple(parsed_version)
287 class PythonDependency (Dependency):
288 def __init__(self, name='python', long_name='Python version',
289 minimum_version=(2, 6), **kwargs):
290 super(PythonDependency, self).__init__(
291 name=name, long_name=long_name, minimum_version=minimum_version,
294 def _get_version(self):
297 def _get_parsed_version(self):
298 return _sys.version_info
301 CHECKER['python'] = PythonDependency()
304 class CommandDependency (Dependency):
305 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
307 def __init__(self, command, version_options=('--version',), stdin=None,
308 version_regexp=None, version_stream='stdout', **kwargs):
309 if 'name' not in kwargs:
310 kwargs['name'] = command
311 super(CommandDependency, self).__init__(**kwargs)
312 self.command = command
313 self.version_options = version_options
315 if not version_regexp:
316 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
317 version_regexp = _re.compile(regexp)
318 self.version_regexp = version_regexp
319 self.version_stream = version_stream
321 def _get_version_stream(self, stdin=None, expect=(0,)):
325 popen_stdin = _subprocess.PIPE
328 command = self.command + (self.exe_extension or '')
330 p = _subprocess.Popen(
331 [command] + list(self.version_options), stdin=popen_stdin,
332 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
333 close_fds=True, shell=False, universal_newlines=True)
335 raise DependencyError(
337 message="could not find '{0}' executable".format(command),
339 stdout,stderr = p.communicate(stdin)
341 if status not in expect:
343 "failed to execute: {0} {1}".format(
345 ' '.join(_shlex.quote(arg)
346 for arg in self.version_options)),
347 'status: {0}'.format(status),
349 for name,string in [('stdout', stdout), ('stderr', stderr)]:
351 lines.extend([name + ':', string])
352 raise DependencyError(checker=self, message='\n'.join(lines))
353 for name,string in [('stdout', stdout), ('stderr', stderr)]:
354 if name == self.version_stream:
356 raise NotImplementedError(self.version_stream)
358 def _get_version(self):
359 version_stream = self._get_version_stream()
360 match = self.version_regexp.search(version_stream)
362 raise DependencyError(
364 message='no version string in output:\n{0}'.format(
366 return match.group(1)
369 for command,long_name,minimum_version in [
370 ('sh', 'Bourne Shell', None),
371 ('ash', 'Almquist Shell', None),
372 ('bash', 'Bourne Again Shell', None),
373 ('csh', 'C Shell', None),
374 ('ksh', 'KornShell', None),
375 ('dash', 'Debian Almquist Shell', None),
376 ('tcsh', 'TENEX C Shell', None),
377 ('zsh', 'Z Shell', None),
378 ('git', 'Git', (1, 7, 0)),
379 ('hg', 'Mercurial', (2, 0, 0)),
381 ('sqlite3', 'SQLite 3', None),
382 ('nosetests', 'Nose', (1, 0, 0)),
383 ('ipython', 'IPython script', (0, 13)),
384 ('emacs', 'Emacs', None),
385 ('xemacs', 'XEmacs', None),
386 ('vim', 'Vim', None),
388 ('nano', 'Nano', None),
389 ('gedit', None, None),
390 ('kate', 'Kate', None),
391 ('notepad++', 'Notepad++', None),
392 ('firefox', 'Firefox', None),
393 ('google-chrome', 'Google Chrome', None),
394 ('chromium', 'Chromium', None),
398 CHECKER[command] = CommandDependency(
399 command=command, long_name=long_name, minimum_version=minimum_version)
400 del command, long_name, minimum_version # cleanup namespace
403 class MakeDependency (CommandDependency):
404 makefile = '\n'.join([
406 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
407 '\t@echo "MAKE=$(MAKE)"',
411 def _get_version(self):
413 return super(MakeDependency, self)._get_version()
414 except DependencyError as e:
415 version_options = self.version_options
416 self.version_options = ['-f', '-']
418 stream = self._get_version_stream(stdin=self.makefile)
420 for line in stream.splitlines():
422 key,value = line.split('=', 1)
423 except ValueError as ve:
424 raise e# from NotImplementedError(stream)
426 if info.get('MAKE_VERSION', None):
427 return info['MAKE_VERSION']
428 elif info.get('MAKE', None):
432 self.version_options = version_options
435 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
438 class EasyInstallDependency (CommandDependency):
439 def _get_version(self):
441 return super(EasyInstallDependency, self)._get_version()
442 except DependencyError as e:
443 version_stream = self.version_stream
445 self.version_stream = 'stderr'
446 stream = self._get_version_stream(expect=(1,))
447 if 'option --version not recognized' in stream:
448 return 'unknown (possibly Setuptools?)'
450 self.version_stream = version_stream
453 CHECKER['easy_install'] = EasyInstallDependency(
454 command='easy_install', long_name='Setuptools easy_install',
455 minimum_version=None)
458 class PythonPackageDependency (Dependency):
459 def __init__(self, package, **kwargs):
460 if 'name' not in kwargs:
461 kwargs['name'] = package
462 if 'and_dependencies' not in kwargs:
463 kwargs['and_dependencies'] = []
464 if 'python' not in kwargs['and_dependencies']:
465 kwargs['and_dependencies'].append('python')
466 super(PythonPackageDependency, self).__init__(**kwargs)
467 self.package = package
469 def _get_version(self):
470 package = self._get_package(self.package)
471 return self._get_version_from_package(package)
473 def _get_package(self, package):
475 return _importlib.import_module(package)
476 except ImportError as e:
477 raise DependencyError(
479 message="could not import the '{0}' package for {1}".format(
480 package, self.full_name()),
483 def _get_version_from_package(self, package):
485 version = package.__version__
486 except AttributeError:
491 for package,name,long_name,minimum_version,and_dependencies in [
492 ('nose', None, 'Nose Python package',
493 CHECKER['nosetests'].minimum_version, None),
494 ('jinja2', 'jinja', 'Jinja', (2, 6), None),
495 ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
496 ('IPython', None, 'IPython Python package',
497 CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']),
498 ('numpy', None, 'NumPy', None, None),
499 ('scipy', None, 'SciPy', None, None),
500 ('matplotlib', None, 'Matplotlib', None, None),
501 ('pandas', None, 'Pandas', (0, 8), None),
502 ('sympy', None, 'SymPy', None, None),
503 ('Cython', None, None, None, None),
504 ('networkx', None, 'NetworkX', None, None),
505 ('mayavi.mlab', None, 'MayaVi', None, None),
506 ('setuptools', None, 'Setuptools', None, None),
514 kwargs['and_dependencies'] = and_dependencies
515 CHECKER[name] = PythonPackageDependency(
516 package=package, name=name, long_name=long_name,
517 minimum_version=minimum_version, **kwargs)
519 del package, name, long_name, minimum_version, and_dependencies, kwargs
522 class MercurialPythonPackage (PythonPackageDependency):
523 def _get_version(self):
524 try: # mercurial >= 1.2
525 package = _importlib.import_module('mercurial.util')
526 except ImportError as e: # mercurial <= 1.1.2
527 package = self._get_package('mercurial.version')
528 return package.get_version()
530 return package.version()
533 CHECKER['mercurial'] = MercurialPythonPackage(
534 package='mercurial.util', name='mercurial',
535 long_name='Mercurial Python package',
536 minimum_version=CHECKER['hg'].minimum_version)
539 class TornadoPythonPackage (PythonPackageDependency):
540 def _get_version_from_package(self, package):
541 return package.version
543 def _get_parsed_version(self):
544 package = self._get_package(self.package)
545 return package.version_info
548 CHECKER['tornado'] = TornadoPythonPackage(
549 package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
552 class SQLitePythonPackage (PythonPackageDependency):
553 def _get_version_from_package(self, package):
556 def _get_parsed_version(self):
557 return _sys.version_info
560 CHECKER['sqlite3-python'] = SQLitePythonPackage(
561 package='sqlite3', name='sqlite3-python',
562 long_name='SQLite Python package',
563 minimum_version=CHECKER['sqlite3'].minimum_version)
566 class VirtualDependency (Dependency):
568 return '{0} {1}'.format(
569 self.or_pass['dependency'].full_name(),
570 self.or_pass['version'])
573 for name,long_name,dependencies in [
574 ('virtual-shell', 'command line shell', (
584 ('virtual-editor', 'text/code editor', (
594 ('virtual-browser', 'web browser', (
599 ('virtual-pypi-installer', 'PyPI installer', (
604 CHECKER[name] = VirtualDependency(
605 name=name, long_name=long_name, or_dependencies=dependencies)
606 del name, long_name, dependencies # cleanup namespace
609 def _print_info(key, value, indent=19):
610 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
612 def print_system_info():
613 print("If you do not understand why the above failures occurred,")
614 print("copy and send the *entire* output (all info above and summary")
615 print("below) to the instructor for help.")
617 print('==================')
618 print('System information')
619 print('==================')
620 _print_info('os.name', _os.name)
621 _print_info('os.uname', _platform.uname())
622 _print_info('platform', _sys.platform)
623 _print_info('platform+', _platform.platform())
625 'linux_distribution',
629 value = getattr(_platform, pversion)()
631 _print_info(pversion, value)
632 _print_info('prefix', _sys.prefix)
633 _print_info('exec_prefix', _sys.exec_prefix)
634 _print_info('executable', _sys.executable)
635 _print_info('version_info', _sys.version_info)
636 _print_info('version', _sys.version)
637 _print_info('environment', '')
638 for key,value in sorted(_os.environ.items()):
639 print(' {0}={1}'.format(key, value))
640 print('==================')
642 def print_suggestions(instructor_fallback=True):
644 print('For suggestions on installing missing packages, see')
645 print('http://software-carpentry.org/setup/')
647 print('For instructings on installing a particular package,')
648 print('see the failure message for that package printed above.')
649 if instructor_fallback:
651 print('For help, email the *entire* output of this script to')
652 print('your instructor.')
655 if __name__ == '__main__':
657 passed = check(_sys.argv[1:])
658 except InvalidCheck as e:
659 print("I don't know how to check for {0!r}".format(e.check))
660 print('I do know how to check for:')
661 for key,checker in sorted(CHECKER.items()):
662 if checker.long_name != checker.name:
663 print(' {0} {1}({2})'.format(
664 key, ' '*(20-len(key)), checker.long_name))
666 print(' {0}'.format(key))
671 print_suggestions(instructor_fallback=True)