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 ('IPython', None, 'IPython Python package',
489 CHECKER['ipython'].minimum_version, None),
490 ('numpy', None, 'NumPy', None, None),
491 ('scipy', None, 'SciPy', None, None),
492 ('matplotlib', None, 'Matplotlib', None, None),
493 ('pandas', None, 'Pandas', (0, 8), None),
494 ('sympy', None, 'SymPy', None, None),
495 ('Cython', None, None, None, None),
496 ('networkx', None, 'NetworkX', None, None),
497 ('mayavi.mlab', None, 'MayaVi', None, None),
498 ('setuptools', None, 'Setuptools', None, None),
506 kwargs['and_dependencies'] = and_dependencies
507 CHECKER[name] = PythonPackageDependency(
508 package=package, name=name, long_name=long_name,
509 minimum_version=minimum_version, **kwargs)
511 del package, name, long_name, minimum_version, and_dependencies, kwargs
514 class MercurialPythonPackage (PythonPackageDependency):
515 def _get_version(self):
516 try: # mercurial >= 1.2
517 package = _importlib.import_module('mercurial.util')
518 except ImportError as e: # mercurial <= 1.1.2
519 package = self._get_package('mercurial.version')
520 return package.get_version()
522 return package.version()
525 CHECKER['mercurial'] = MercurialPythonPackage(
526 package='mercurial.util', name='mercurial',
527 long_name='Mercurial Python package',
528 minimum_version=CHECKER['hg'].minimum_version)
531 class SQLitePythonPackage (PythonPackageDependency):
532 def _get_version_from_package(self, package):
535 def _get_parsed_version(self):
536 return _sys.version_info
539 CHECKER['sqlite3-python'] = SQLitePythonPackage(
540 package='sqlite3', name='sqlite3-python',
541 long_name='SQLite Python package',
542 minimum_version=CHECKER['sqlite3'].minimum_version)
545 class VirtualDependency (Dependency):
547 return '{0} {1}'.format(
548 self.or_pass['dependency'].full_name(),
549 self.or_pass['version'])
552 for name,dependencies in [
573 ('virtual-browser', (
578 ('virtual-pypi-installer', (
583 CHECKER[name] = VirtualDependency(
584 name=name, long_name=name, or_dependencies=dependencies)
585 del name, dependencies # cleanup namespace
588 def _print_info(key, value, indent=19):
589 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
591 def print_system_info():
592 print("If you do not understand why the above failures occurred,")
593 print("copy and send the *entire* output (all info above and summary")
594 print("below) to the instructor for help.")
596 print('==================')
597 print('System information')
598 print('==================')
599 _print_info('os.name', _os.name)
600 _print_info('os.uname', _platform.uname())
601 _print_info('platform', _sys.platform)
602 _print_info('platform+', _platform.platform())
604 'linux_distribution',
608 value = getattr(_platform, pversion)()
610 _print_info(pversion, value)
611 _print_info('prefix', _sys.prefix)
612 _print_info('exec_prefix', _sys.exec_prefix)
613 _print_info('executable', _sys.executable)
614 _print_info('version_info', _sys.version_info)
615 _print_info('version', _sys.version)
616 _print_info('environment', '')
617 for key,value in sorted(_os.environ.items()):
618 print(' {0}={1}'.format(key, value))
619 print('==================')
621 def print_suggestions(instructor_fallback=True):
623 print('For suggestions on installing missing packages, see')
624 print('http://software-carpentry.org/setup/')
626 print('For instructings on installing a particular package,')
627 print('see the failure message for that package printed above.')
628 if instructor_fallback:
630 print('For help, email the *entire* output of this script to')
631 print('your instructor.')
634 if __name__ == '__main__':
636 passed = check(_sys.argv[1:])
637 except InvalidCheck as e:
638 print("I don't know how to check for {0!r}".format(e.check))
639 print('I do know how to check for:')
640 for key,checker in sorted(CHECKER.items()):
641 if checker.long_name != checker.name:
642 print(' {0} {1}({2})'.format(
643 key, ' '*(20-len(key)), checker.long_name))
645 print(' {0}'.format(key))
650 print_suggestions(instructor_fallback=True)