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
75 'nosetests', # Command line tool
76 'nose', # Python package
78 'sqlite3', # Command line tool
79 'sqlite3-python', # Python package
95 class InvalidCheck (KeyError):
96 def __init__(self, check):
97 super(InvalidCheck, self).__init__(check)
104 class DependencyError (Exception):
105 def _get_message(self):
107 def _set_message(self, message):
108 self._message = message
109 message = property(_get_message, _set_message)
111 def __init__(self, checker, message):
112 super(DependencyError, self).__init__(message)
113 self.checker = checker
114 self.message = message
117 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
118 return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
119 self.checker.full_name(), self.message,
120 'For instructions on installing an up-to-date version, see',
124 def check(checks=None):
131 checker = CHECKER[check]
132 except KeyError as e:
133 raise InvalidCheck(check)# from e
134 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
136 version = checker.check()
137 except DependencyError as e:
139 _sys.stdout.write('fail\n')
141 _sys.stdout.write('pass\n')
142 successes.append((checker, version))
144 print('\nSuccesses:\n')
145 for checker,version in successes:
146 print('{0} {1}'.format(
148 version or 'unknown'))
152 for failure in failures:
153 if failure not in printed:
156 printed.append(failure)
161 class Dependency (object):
162 def __init__(self, name, long_name=None, minimum_version=None,
163 version_delimiter='.', and_dependencies=None,
164 or_dependencies=None):
166 self.long_name = long_name or name
167 self.minimum_version = minimum_version
168 self.version_delimiter = version_delimiter
169 if not and_dependencies:
170 and_dependencies = []
171 self.and_dependencies = and_dependencies
172 if not or_dependencies:
174 self.or_dependencies = or_dependencies
175 self._check_error = None
178 return '<{0} {1}>'.format(type(self).__name__, self.name)
181 if self.name == self.long_name:
184 return '{0} ({1})'.format(self.long_name, self.name)
187 if self._check_error:
188 raise self._check_error
190 self._check_dependencies()
192 except DependencyError as e:
193 self._check_error = e # cache for future calls
196 def _check_dependencies(self):
197 for dependency in self.and_dependencies:
198 if not hasattr(dependency, 'check'):
199 dependency = CHECKER[dependency]
201 self.or_pass = or_error = None
202 for dependency in self.or_dependencies:
203 if not hasattr(dependency, 'check'):
204 dependency = CHECKER[dependency]
206 version = dependency.check()
207 except DependencyError as e:
211 'dependency': dependency,
214 break # no need to test other dependencies
215 if self.or_dependencies and not self.or_pass:
219 version = self._get_version()
220 parsed_version = None
221 if hasattr(self, '_get_parsed_version'):
222 parsed_version = self._get_parsed_version()
223 if self.minimum_version:
224 self._check_version(version=version, parsed_version=parsed_version)
227 def _get_version(self):
228 raise NotImplementedError(self)
230 def _check_version(self, version, parsed_version=None):
231 if not parsed_version:
232 parsed_version = self._parse_version(version=version)
233 if not parsed_version or parsed_version < self.minimum_version:
234 raise DependencyError(
236 message='outdated version of {0}: {1} (need >= {2})'.format(
237 self.full_name(), version,
238 self.version_delimiter.join(
239 str(part) for part in self.minimum_version)))
241 def _parse_version(self, version):
245 for part in version.split(self.version_delimiter):
247 parsed_version.append(int(part))
248 except ValueError as e:
249 raise NotImplementedError((version, part))# from e
250 return tuple(parsed_version)
253 class PythonDependency (Dependency):
254 def __init__(self, name='python', long_name='Python version',
255 minimum_version=(2, 6), **kwargs):
256 super(PythonDependency, self).__init__(
257 name=name, long_name=long_name, minimum_version=minimum_version,
260 def _get_version(self):
263 def _get_parsed_version(self):
264 return _sys.version_info
267 CHECKER['python'] = PythonDependency()
270 class CommandDependency (Dependency):
271 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
273 def __init__(self, command, version_options=('--version',), stdin=None,
274 version_regexp=None, version_stream='stdout', **kwargs):
275 if 'name' not in kwargs:
276 kwargs['name'] = command
277 super(CommandDependency, self).__init__(**kwargs)
278 self.command = command
279 self.version_options = version_options
281 if not version_regexp:
282 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
283 version_regexp = _re.compile(regexp)
284 self.version_regexp = version_regexp
285 self.version_stream = version_stream
287 def _get_version_stream(self, stdin=None, expect=(0,)):
291 popen_stdin = _subprocess.PIPE
294 command = self.command + (self.exe_extension or '')
296 p = _subprocess.Popen(
297 [command] + list(self.version_options), stdin=popen_stdin,
298 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
299 close_fds=True, shell=False, universal_newlines=True)
301 raise DependencyError(
303 message="could not find '{0}' executable".format(command),
305 stdout,stderr = p.communicate(stdin)
307 if status not in expect:
309 "failed to execute: {0} {1}".format(
311 ' '.join(_shlex.quote(arg)
312 for arg in self.version_options)),
313 'status: {0}'.format(status),
315 for name,string in [('stdout', stdout), ('stderr', stderr)]:
317 lines.extend([name + ':', string])
318 raise DependencyError(checker=self, message='\n'.join(lines))
319 for name,string in [('stdout', stdout), ('stderr', stderr)]:
320 if name == self.version_stream:
322 raise NotImplementedError(self.version_stream)
324 def _get_version(self):
325 version_stream = self._get_version_stream()
326 match = self.version_regexp.search(version_stream)
328 raise DependencyError(
330 message='no version string in output:\n{0}'.format(
332 return match.group(1)
335 for command,long_name,minimum_version in [
336 ('sh', 'Bourne Shell', None),
337 ('ash', 'Almquist Shell', None),
338 ('bash', 'Bourne Again Shell', None),
339 ('csh', 'C Shell', None),
340 ('ksh', 'KornShell', None),
341 ('dash', 'Debian Almquist Shell', None),
342 ('tcsh', 'TENEX C Shell', None),
343 ('zsh', 'Z Shell', None),
344 ('git', 'Git', (1, 7, 0)),
345 ('hg', 'Mercurial', (2, 0, 0)),
346 ('sqlite3', 'SQLite 3', None),
347 ('nosetests', 'Nose', (1, 0, 0)),
348 ('emacs', 'Emacs', None),
349 ('xemacs', 'XEmacs', None),
350 ('vim', 'Vim', None),
352 ('nano', 'Nano', None),
353 ('kate', 'Kate', None),
354 ('notepad++', 'Notepad++', None),
355 ('firefox', 'Firefox', None),
356 ('google-chrome', 'Google Chrome', None),
357 ('chromium', 'Chromium', None),
361 CHECKER[command] = CommandDependency(
362 command=command, long_name=long_name, minimum_version=minimum_version)
363 del command, long_name, minimum_version # cleanup namespace
366 class MakeDependency (CommandDependency):
367 makefile = '\n'.join([
369 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
370 '\t@echo "MAKE=$(MAKE)"',
374 def _get_version(self):
376 return super(MakeDependency, self)._get_version()
377 except DependencyError as e:
378 version_options = self.version_options
379 self.version_options = ['-f', '-']
381 stream = self._get_version_stream(stdin=self.makefile)
383 for line in stream.splitlines():
385 key,value = line.split('=', 1)
386 except ValueError as ve:
387 raise e# from NotImplementedError(stream)
389 if info.get('MAKE_VERSION', None):
390 return info['MAKE_VERSION']
391 elif info.get('MAKE', None):
395 self.version_options = version_options
398 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
401 class EasyInstallDependency (CommandDependency):
402 def _get_version(self):
404 return super(EasyInstallDependency, self)._get_version()
405 except DependencyError as e:
406 version_stream = self.version_stream
408 self.version_stream = 'stderr'
409 stream = self._get_version_stream(expect=(1,))
410 if 'option --version not recognized' in stream:
411 return 'unknown (possibly Setuptools?)'
413 self.version_stream = version_stream
416 CHECKER['easy_install'] = EasyInstallDependency(
417 command='easy_install', long_name='Setuptools easy_install',
418 minimum_version=None)
421 class PythonPackageDependency (Dependency):
422 def __init__(self, package, **kwargs):
423 if 'name' not in kwargs:
424 kwargs['name'] = package
425 if 'and_dependencies' not in kwargs:
426 kwargs['and_dependencies'] = []
427 if 'python' not in kwargs['and_dependencies']:
428 kwargs['and_dependencies'].append('python')
429 super(PythonPackageDependency, self).__init__(**kwargs)
430 self.package = package
432 def _get_version(self):
433 package = self._get_package(self.package)
434 return self._get_version_from_package(package)
436 def _get_package(self, package):
438 return _importlib.import_module(package)
439 except ImportError as e:
440 raise DependencyError(
442 message="could not import the '{0}' package for {1}".format(
443 package, self.full_name()),
446 def _get_version_from_package(self, package):
448 version = package.__version__
449 except AttributeError:
454 for package,name,long_name,minimum_version in [
455 ('nose', None, 'Nose Python package',
456 CHECKER['nosetests'].minimum_version),
457 ('IPython', None, None, None),
458 ('numpy', None, 'NumPy', None),
459 ('scipy', None, 'SciPy', None),
460 ('matplotlib', None, 'Matplotlib', None),
461 ('sympy', None, 'SymPy', None),
462 ('Cython', None, None, None),
463 ('networkx', None, 'NetworkX', None),
464 ('mayavi.mlab', None, 'MayaVi', None),
465 ('setuptools', None, 'Setuptools', None),
471 CHECKER[name] = PythonPackageDependency(
472 package=package, name=name, long_name=long_name,
473 minimum_version=minimum_version)
474 del package, name, long_name, minimum_version # cleanup namespace
477 class MercurialPythonPackage (PythonPackageDependency):
478 def _get_version(self):
479 try: # mercurial >= 1.2
480 package = _importlib.import_module('mercurial.util')
481 except ImportError as e: # mercurial <= 1.1.2
482 package = self._get_package('mercurial.version')
483 return package.get_version()
485 return package.version()
488 CHECKER['mercurial'] = MercurialPythonPackage(
489 package='mercurial.util', name='mercurial',
490 long_name='Mercurial Python package',
491 minimum_version=CHECKER['hg'].minimum_version)
494 class SQLitePythonPackage (PythonPackageDependency):
495 def _get_version_from_package(self, package):
498 def _get_parsed_version(self):
499 return _sys.version_info
502 CHECKER['sqlite3-python'] = SQLitePythonPackage(
503 package='sqlite3', name='sqlite3-python',
504 long_name='SQLite Python package',
505 minimum_version=CHECKER['sqlite3'].minimum_version)
508 class VirtualDependency (Dependency):
510 return '{0} {1}'.format(
511 self.or_pass['dependency'].full_name(),
512 self.or_pass['version'])
515 for name,dependencies in [
535 ('virtual-browser', (
541 CHECKER[name] = VirtualDependency(
542 name=name, long_name=name, or_dependencies=dependencies)
543 del name, dependencies # cleanup namespace
546 def _print_info(key, value, indent=19):
547 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
549 def print_system_info():
550 print("If you do not understand why the above failures occurred,")
551 print("copy and send the *entire* output (all info above and summary")
552 print("below) to the instructor for help.")
554 print('==================')
555 print('System information')
556 print('==================')
557 _print_info('os.name', _os.name)
558 _print_info('os.uname', _platform.uname())
559 _print_info('platform', _sys.platform)
560 _print_info('platform+', _platform.platform())
562 'linux_distribution',
566 value = getattr(_platform, pversion)()
568 _print_info(pversion, value)
569 _print_info('prefix', _sys.prefix)
570 _print_info('exec_prefix', _sys.exec_prefix)
571 _print_info('executable', _sys.executable)
572 _print_info('version_info', _sys.version_info)
573 _print_info('version', _sys.version)
574 _print_info('environment', '')
575 for key,value in sorted(_os.environ.items()):
576 print(' {0}={1}'.format(key, value))
577 print('==================')
579 def print_suggestions(instructor_fallback=True):
581 print('For suggestions on installing missing packages, see')
582 print('http://software-carpentry.org/setup/')
584 print('For instructings on installing a particular package,')
585 print('see the failure message for that package printed above.')
586 if instructor_fallback:
588 print('For help, email the *entire* output of this script to')
589 print('your instructor.')
592 if __name__ == '__main__':
594 passed = check(_sys.argv[1:])
595 except InvalidCheck as e:
596 print("I don't know how to check for {0!r}".format(e.check))
597 print('I do know how to check for:')
598 for key,checker in sorted(CHECKER.items()):
599 if checker.long_name != checker.name:
600 print(' {0} {1}({2})'.format(
601 key, ' '*(20-len(key)), checker.long_name))
603 print(' {0}'.format(key))
608 print_suggestions(instructor_fallback=True)