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, cause=None):
114 super(DependencyError, self).__init__(message)
115 self.checker = checker
116 self.message = message
120 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
122 'check for {0} failed:'.format(self.checker.full_name()),
124 ' For instructions on installing an up-to-date version, see',
128 lines.append(' cause:')
129 lines.extend(' ' + line for line in str(self.cause).splitlines())
130 return '\n'.join(lines)
133 def check(checks=None):
140 checker = CHECKER[check]
141 except KeyError as e:
142 raise InvalidCheck(check)# from e
143 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
145 version = checker.check()
146 except DependencyError as e:
148 _sys.stdout.write('fail\n')
150 _sys.stdout.write('pass\n')
151 successes.append((checker, version))
153 print('\nSuccesses:\n')
154 for checker,version in successes:
155 print('{0} {1}'.format(
157 version or 'unknown'))
161 for failure in failures:
162 if failure not in printed:
165 printed.append(failure)
170 class Dependency (object):
171 def __init__(self, name, long_name=None, minimum_version=None,
172 version_delimiter='.', and_dependencies=None,
173 or_dependencies=None):
175 self.long_name = long_name or name
176 self.minimum_version = minimum_version
177 self.version_delimiter = version_delimiter
178 if not and_dependencies:
179 and_dependencies = []
180 self.and_dependencies = and_dependencies
181 if not or_dependencies:
183 self.or_dependencies = or_dependencies
184 self._check_error = None
187 return '<{0} {1}>'.format(type(self).__name__, self.name)
190 if self.name == self.long_name:
193 return '{0} ({1})'.format(self.long_name, self.name)
196 if self._check_error:
197 raise self._check_error
199 self._check_dependencies()
201 except DependencyError as e:
202 self._check_error = e # cache for future calls
205 def _check_dependencies(self):
206 for dependency in self.and_dependencies:
207 if not hasattr(dependency, 'check'):
208 dependency = CHECKER[dependency]
211 except DependencyError as e:
212 raise DependencyError(
214 message='and-dependency not satisfied for {0}'.format(
217 self.or_pass = or_error = None
218 for dependency in self.or_dependencies:
219 if not hasattr(dependency, 'check'):
220 dependency = CHECKER[dependency]
222 version = dependency.check()
223 except DependencyError as e:
227 'dependency': dependency,
230 break # no need to test other dependencies
231 if self.or_dependencies and not self.or_pass:
232 raise DependencyError(
234 message='or-dependency not satisfied for {0}'.format(
239 version = self._get_version()
240 parsed_version = None
241 if hasattr(self, '_get_parsed_version'):
242 parsed_version = self._get_parsed_version()
243 if self.minimum_version:
244 self._check_version(version=version, parsed_version=parsed_version)
247 def _get_version(self):
248 raise NotImplementedError(self)
250 def _minimum_version_string(self):
251 return self.version_delimiter.join(
252 str(part) for part in self.minimum_version)
254 def _check_version(self, version, parsed_version=None):
255 if not parsed_version:
256 parsed_version = self._parse_version(version=version)
257 if not parsed_version or parsed_version < self.minimum_version:
258 raise DependencyError(
260 message='outdated version of {0}: {1} (need >= {2})'.format(
261 self.full_name(), version, self._minimum_version_string()))
263 def _parse_version(self, version):
267 for part in version.split(self.version_delimiter):
269 parsed_version.append(int(part))
270 except ValueError as e:
271 raise DependencyError(
274 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
276 part, version, self.full_name(),
277 self._minimum_version_string()))# from e
278 return tuple(parsed_version)
281 class PythonDependency (Dependency):
282 def __init__(self, name='python', long_name='Python version',
283 minimum_version=(2, 6), **kwargs):
284 super(PythonDependency, self).__init__(
285 name=name, long_name=long_name, minimum_version=minimum_version,
288 def _get_version(self):
291 def _get_parsed_version(self):
292 return _sys.version_info
295 CHECKER['python'] = PythonDependency()
298 class CommandDependency (Dependency):
299 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
301 def __init__(self, command, version_options=('--version',), stdin=None,
302 version_regexp=None, version_stream='stdout', **kwargs):
303 if 'name' not in kwargs:
304 kwargs['name'] = command
305 super(CommandDependency, self).__init__(**kwargs)
306 self.command = command
307 self.version_options = version_options
309 if not version_regexp:
310 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
311 version_regexp = _re.compile(regexp)
312 self.version_regexp = version_regexp
313 self.version_stream = version_stream
315 def _get_version_stream(self, stdin=None, expect=(0,)):
319 popen_stdin = _subprocess.PIPE
322 command = self.command + (self.exe_extension or '')
324 p = _subprocess.Popen(
325 [command] + list(self.version_options), stdin=popen_stdin,
326 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
327 close_fds=True, shell=False, universal_newlines=True)
329 raise DependencyError(
331 message="could not find '{0}' executable".format(command),
333 stdout,stderr = p.communicate(stdin)
335 if status not in expect:
337 "failed to execute: {0} {1}".format(
339 ' '.join(_shlex.quote(arg)
340 for arg in self.version_options)),
341 'status: {0}'.format(status),
343 for name,string in [('stdout', stdout), ('stderr', stderr)]:
345 lines.extend([name + ':', string])
346 raise DependencyError(checker=self, message='\n'.join(lines))
347 for name,string in [('stdout', stdout), ('stderr', stderr)]:
348 if name == self.version_stream:
350 raise NotImplementedError(self.version_stream)
352 def _get_version(self):
353 version_stream = self._get_version_stream()
354 match = self.version_regexp.search(version_stream)
356 raise DependencyError(
358 message='no version string in output:\n{0}'.format(
360 return match.group(1)
363 for command,long_name,minimum_version in [
364 ('sh', 'Bourne Shell', None),
365 ('ash', 'Almquist Shell', None),
366 ('bash', 'Bourne Again Shell', None),
367 ('csh', 'C Shell', None),
368 ('ksh', 'KornShell', None),
369 ('dash', 'Debian Almquist Shell', None),
370 ('tcsh', 'TENEX C Shell', None),
371 ('zsh', 'Z Shell', None),
372 ('git', 'Git', (1, 7, 0)),
373 ('hg', 'Mercurial', (2, 0, 0)),
375 ('sqlite3', 'SQLite 3', None),
376 ('nosetests', 'Nose', (1, 0, 0)),
377 ('ipython', 'IPython script', (0, 13)),
378 ('emacs', 'Emacs', None),
379 ('xemacs', 'XEmacs', None),
380 ('vim', 'Vim', None),
382 ('nano', 'Nano', None),
383 ('gedit', None, None),
384 ('kate', 'Kate', None),
385 ('notepad++', 'Notepad++', None),
386 ('firefox', 'Firefox', None),
387 ('google-chrome', 'Google Chrome', None),
388 ('chromium', 'Chromium', None),
392 CHECKER[command] = CommandDependency(
393 command=command, long_name=long_name, minimum_version=minimum_version)
394 del command, long_name, minimum_version # cleanup namespace
397 class MakeDependency (CommandDependency):
398 makefile = '\n'.join([
400 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
401 '\t@echo "MAKE=$(MAKE)"',
405 def _get_version(self):
407 return super(MakeDependency, self)._get_version()
408 except DependencyError as e:
409 version_options = self.version_options
410 self.version_options = ['-f', '-']
412 stream = self._get_version_stream(stdin=self.makefile)
414 for line in stream.splitlines():
416 key,value = line.split('=', 1)
417 except ValueError as ve:
418 raise e# from NotImplementedError(stream)
420 if info.get('MAKE_VERSION', None):
421 return info['MAKE_VERSION']
422 elif info.get('MAKE', None):
426 self.version_options = version_options
429 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
432 class EasyInstallDependency (CommandDependency):
433 def _get_version(self):
435 return super(EasyInstallDependency, self)._get_version()
436 except DependencyError as e:
437 version_stream = self.version_stream
439 self.version_stream = 'stderr'
440 stream = self._get_version_stream(expect=(1,))
441 if 'option --version not recognized' in stream:
442 return 'unknown (possibly Setuptools?)'
444 self.version_stream = version_stream
447 CHECKER['easy_install'] = EasyInstallDependency(
448 command='easy_install', long_name='Setuptools easy_install',
449 minimum_version=None)
452 class PythonPackageDependency (Dependency):
453 def __init__(self, package, **kwargs):
454 if 'name' not in kwargs:
455 kwargs['name'] = package
456 if 'and_dependencies' not in kwargs:
457 kwargs['and_dependencies'] = []
458 if 'python' not in kwargs['and_dependencies']:
459 kwargs['and_dependencies'].append('python')
460 super(PythonPackageDependency, self).__init__(**kwargs)
461 self.package = package
463 def _get_version(self):
464 package = self._get_package(self.package)
465 return self._get_version_from_package(package)
467 def _get_package(self, package):
469 return _importlib.import_module(package)
470 except ImportError as e:
471 raise DependencyError(
473 message="could not import the '{0}' package for {1}".format(
474 package, self.full_name()),
477 def _get_version_from_package(self, package):
479 version = package.__version__
480 except AttributeError:
485 for package,name,long_name,minimum_version,and_dependencies in [
486 ('nose', None, 'Nose Python package',
487 CHECKER['nosetests'].minimum_version, None),
488 ('jinja2', 'jinja', 'Jinja', (2, 6), None),
489 ('tornado', None, 'Tornado', (2, 0), None),
490 ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
491 ('IPython', None, 'IPython Python package',
492 CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']),
493 ('numpy', None, 'NumPy', None, None),
494 ('scipy', None, 'SciPy', None, None),
495 ('matplotlib', None, 'Matplotlib', None, None),
496 ('pandas', None, 'Pandas', (0, 8), None),
497 ('sympy', None, 'SymPy', None, None),
498 ('Cython', None, None, None, None),
499 ('networkx', None, 'NetworkX', None, None),
500 ('mayavi.mlab', None, 'MayaVi', None, None),
501 ('setuptools', None, 'Setuptools', None, None),
509 kwargs['and_dependencies'] = and_dependencies
510 CHECKER[name] = PythonPackageDependency(
511 package=package, name=name, long_name=long_name,
512 minimum_version=minimum_version, **kwargs)
514 del package, name, long_name, minimum_version, and_dependencies, kwargs
517 class MercurialPythonPackage (PythonPackageDependency):
518 def _get_version(self):
519 try: # mercurial >= 1.2
520 package = _importlib.import_module('mercurial.util')
521 except ImportError as e: # mercurial <= 1.1.2
522 package = self._get_package('mercurial.version')
523 return package.get_version()
525 return package.version()
528 CHECKER['mercurial'] = MercurialPythonPackage(
529 package='mercurial.util', name='mercurial',
530 long_name='Mercurial Python package',
531 minimum_version=CHECKER['hg'].minimum_version)
534 class SQLitePythonPackage (PythonPackageDependency):
535 def _get_version_from_package(self, package):
538 def _get_parsed_version(self):
539 return _sys.version_info
542 CHECKER['sqlite3-python'] = SQLitePythonPackage(
543 package='sqlite3', name='sqlite3-python',
544 long_name='SQLite Python package',
545 minimum_version=CHECKER['sqlite3'].minimum_version)
548 class VirtualDependency (Dependency):
550 return '{0} {1}'.format(
551 self.or_pass['dependency'].full_name(),
552 self.or_pass['version'])
555 for name,dependencies in [
576 ('virtual-browser', (
581 ('virtual-pypi-installer', (
586 CHECKER[name] = VirtualDependency(
587 name=name, long_name=name, or_dependencies=dependencies)
588 del name, dependencies # cleanup namespace
591 def _print_info(key, value, indent=19):
592 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
594 def print_system_info():
595 print("If you do not understand why the above failures occurred,")
596 print("copy and send the *entire* output (all info above and summary")
597 print("below) to the instructor for help.")
599 print('==================')
600 print('System information')
601 print('==================')
602 _print_info('os.name', _os.name)
603 _print_info('os.uname', _platform.uname())
604 _print_info('platform', _sys.platform)
605 _print_info('platform+', _platform.platform())
607 'linux_distribution',
611 value = getattr(_platform, pversion)()
613 _print_info(pversion, value)
614 _print_info('prefix', _sys.prefix)
615 _print_info('exec_prefix', _sys.exec_prefix)
616 _print_info('executable', _sys.executable)
617 _print_info('version_info', _sys.version_info)
618 _print_info('version', _sys.version)
619 _print_info('environment', '')
620 for key,value in sorted(_os.environ.items()):
621 print(' {0}={1}'.format(key, value))
622 print('==================')
624 def print_suggestions(instructor_fallback=True):
626 print('For suggestions on installing missing packages, see')
627 print('http://software-carpentry.org/setup/')
629 print('For instructings on installing a particular package,')
630 print('see the failure message for that package printed above.')
631 if instructor_fallback:
633 print('For help, email the *entire* output of this script to')
634 print('your instructor.')
637 if __name__ == '__main__':
639 passed = check(_sys.argv[1:])
640 except InvalidCheck as e:
641 print("I don't know how to check for {0!r}".format(e.check))
642 print('I do know how to check for:')
643 for key,checker in sorted(CHECKER.items()):
644 if checker.long_name != checker.name:
645 print(' {0} {1}({2})'.format(
646 key, ' '*(20-len(key)), checker.long_name))
648 print(' {0}'.format(key))
653 print_suggestions(instructor_fallback=True)