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):
114 super(DependencyError, self).__init__(message)
115 self.checker = checker
116 self.message = message
119 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
120 return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
121 self.checker.full_name(), self.message,
122 'For instructions on installing an up-to-date version, see',
126 def check(checks=None):
133 checker = CHECKER[check]
134 except KeyError as e:
135 raise InvalidCheck(check)# from e
136 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
138 version = checker.check()
139 except DependencyError as e:
141 _sys.stdout.write('fail\n')
143 _sys.stdout.write('pass\n')
144 successes.append((checker, version))
146 print('\nSuccesses:\n')
147 for checker,version in successes:
148 print('{0} {1}'.format(
150 version or 'unknown'))
154 for failure in failures:
155 if failure not in printed:
158 printed.append(failure)
163 class Dependency (object):
164 def __init__(self, name, long_name=None, minimum_version=None,
165 version_delimiter='.', and_dependencies=None,
166 or_dependencies=None):
168 self.long_name = long_name or name
169 self.minimum_version = minimum_version
170 self.version_delimiter = version_delimiter
171 if not and_dependencies:
172 and_dependencies = []
173 self.and_dependencies = and_dependencies
174 if not or_dependencies:
176 self.or_dependencies = or_dependencies
177 self._check_error = None
180 return '<{0} {1}>'.format(type(self).__name__, self.name)
183 if self.name == self.long_name:
186 return '{0} ({1})'.format(self.long_name, self.name)
189 if self._check_error:
190 raise self._check_error
192 self._check_dependencies()
194 except DependencyError as e:
195 self._check_error = e # cache for future calls
198 def _check_dependencies(self):
199 for dependency in self.and_dependencies:
200 if not hasattr(dependency, 'check'):
201 dependency = CHECKER[dependency]
203 self.or_pass = or_error = None
204 for dependency in self.or_dependencies:
205 if not hasattr(dependency, 'check'):
206 dependency = CHECKER[dependency]
208 version = dependency.check()
209 except DependencyError as e:
213 'dependency': dependency,
216 break # no need to test other dependencies
217 if self.or_dependencies and not self.or_pass:
221 version = self._get_version()
222 parsed_version = None
223 if hasattr(self, '_get_parsed_version'):
224 parsed_version = self._get_parsed_version()
225 if self.minimum_version:
226 self._check_version(version=version, parsed_version=parsed_version)
229 def _get_version(self):
230 raise NotImplementedError(self)
232 def _minimum_version_string(self):
233 return self.version_delimiter.join(
234 str(part) for part in self.minimum_version)
236 def _check_version(self, version, parsed_version=None):
237 if not parsed_version:
238 parsed_version = self._parse_version(version=version)
239 if not parsed_version or parsed_version < self.minimum_version:
240 raise DependencyError(
242 message='outdated version of {0}: {1} (need >= {2})'.format(
243 self.full_name(), version, self._minimum_version_string()))
245 def _parse_version(self, version):
249 for part in version.split(self.version_delimiter):
251 parsed_version.append(int(part))
252 except ValueError as e:
253 raise DependencyError(
256 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
258 part, version, self.full_name(),
259 self._minimum_version_string()))# from e
260 return tuple(parsed_version)
263 class PythonDependency (Dependency):
264 def __init__(self, name='python', long_name='Python version',
265 minimum_version=(2, 6), **kwargs):
266 super(PythonDependency, self).__init__(
267 name=name, long_name=long_name, minimum_version=minimum_version,
270 def _get_version(self):
273 def _get_parsed_version(self):
274 return _sys.version_info
277 CHECKER['python'] = PythonDependency()
280 class CommandDependency (Dependency):
281 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
283 def __init__(self, command, version_options=('--version',), stdin=None,
284 version_regexp=None, version_stream='stdout', **kwargs):
285 if 'name' not in kwargs:
286 kwargs['name'] = command
287 super(CommandDependency, self).__init__(**kwargs)
288 self.command = command
289 self.version_options = version_options
291 if not version_regexp:
292 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
293 version_regexp = _re.compile(regexp)
294 self.version_regexp = version_regexp
295 self.version_stream = version_stream
297 def _get_version_stream(self, stdin=None, expect=(0,)):
301 popen_stdin = _subprocess.PIPE
304 command = self.command + (self.exe_extension or '')
306 p = _subprocess.Popen(
307 [command] + list(self.version_options), stdin=popen_stdin,
308 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
309 close_fds=True, shell=False, universal_newlines=True)
311 raise DependencyError(
313 message="could not find '{0}' executable".format(command),
315 stdout,stderr = p.communicate(stdin)
317 if status not in expect:
319 "failed to execute: {0} {1}".format(
321 ' '.join(_shlex.quote(arg)
322 for arg in self.version_options)),
323 'status: {0}'.format(status),
325 for name,string in [('stdout', stdout), ('stderr', stderr)]:
327 lines.extend([name + ':', string])
328 raise DependencyError(checker=self, message='\n'.join(lines))
329 for name,string in [('stdout', stdout), ('stderr', stderr)]:
330 if name == self.version_stream:
332 raise NotImplementedError(self.version_stream)
334 def _get_version(self):
335 version_stream = self._get_version_stream()
336 match = self.version_regexp.search(version_stream)
338 raise DependencyError(
340 message='no version string in output:\n{0}'.format(
342 return match.group(1)
345 for command,long_name,minimum_version in [
346 ('sh', 'Bourne Shell', None),
347 ('ash', 'Almquist Shell', None),
348 ('bash', 'Bourne Again Shell', None),
349 ('csh', 'C Shell', None),
350 ('ksh', 'KornShell', None),
351 ('dash', 'Debian Almquist Shell', None),
352 ('tcsh', 'TENEX C Shell', None),
353 ('zsh', 'Z Shell', None),
354 ('git', 'Git', (1, 7, 0)),
355 ('hg', 'Mercurial', (2, 0, 0)),
357 ('sqlite3', 'SQLite 3', None),
358 ('nosetests', 'Nose', (1, 0, 0)),
359 ('ipython', 'IPython script', (0, 13)),
360 ('emacs', 'Emacs', None),
361 ('xemacs', 'XEmacs', None),
362 ('vim', 'Vim', None),
364 ('nano', 'Nano', None),
365 ('gedit', None, None),
366 ('kate', 'Kate', None),
367 ('notepad++', 'Notepad++', None),
368 ('firefox', 'Firefox', None),
369 ('google-chrome', 'Google Chrome', None),
370 ('chromium', 'Chromium', None),
374 CHECKER[command] = CommandDependency(
375 command=command, long_name=long_name, minimum_version=minimum_version)
376 del command, long_name, minimum_version # cleanup namespace
379 class MakeDependency (CommandDependency):
380 makefile = '\n'.join([
382 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
383 '\t@echo "MAKE=$(MAKE)"',
387 def _get_version(self):
389 return super(MakeDependency, self)._get_version()
390 except DependencyError as e:
391 version_options = self.version_options
392 self.version_options = ['-f', '-']
394 stream = self._get_version_stream(stdin=self.makefile)
396 for line in stream.splitlines():
398 key,value = line.split('=', 1)
399 except ValueError as ve:
400 raise e# from NotImplementedError(stream)
402 if info.get('MAKE_VERSION', None):
403 return info['MAKE_VERSION']
404 elif info.get('MAKE', None):
408 self.version_options = version_options
411 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
414 class EasyInstallDependency (CommandDependency):
415 def _get_version(self):
417 return super(EasyInstallDependency, self)._get_version()
418 except DependencyError as e:
419 version_stream = self.version_stream
421 self.version_stream = 'stderr'
422 stream = self._get_version_stream(expect=(1,))
423 if 'option --version not recognized' in stream:
424 return 'unknown (possibly Setuptools?)'
426 self.version_stream = version_stream
429 CHECKER['easy_install'] = EasyInstallDependency(
430 command='easy_install', long_name='Setuptools easy_install',
431 minimum_version=None)
434 class PythonPackageDependency (Dependency):
435 def __init__(self, package, **kwargs):
436 if 'name' not in kwargs:
437 kwargs['name'] = package
438 if 'and_dependencies' not in kwargs:
439 kwargs['and_dependencies'] = []
440 if 'python' not in kwargs['and_dependencies']:
441 kwargs['and_dependencies'].append('python')
442 super(PythonPackageDependency, self).__init__(**kwargs)
443 self.package = package
445 def _get_version(self):
446 package = self._get_package(self.package)
447 return self._get_version_from_package(package)
449 def _get_package(self, package):
451 return _importlib.import_module(package)
452 except ImportError as e:
453 raise DependencyError(
455 message="could not import the '{0}' package for {1}".format(
456 package, self.full_name()),
459 def _get_version_from_package(self, package):
461 version = package.__version__
462 except AttributeError:
467 for package,name,long_name,minimum_version,and_dependencies in [
468 ('nose', None, 'Nose Python package',
469 CHECKER['nosetests'].minimum_version, None),
470 ('IPython', None, 'IPython Python package',
471 CHECKER['ipython'].minimum_version, None),
472 ('numpy', None, 'NumPy', None, None),
473 ('scipy', None, 'SciPy', None, None),
474 ('matplotlib', None, 'Matplotlib', None, None),
475 ('pandas', None, 'Pandas', (0, 8), None),
476 ('sympy', None, 'SymPy', None, None),
477 ('Cython', None, None, None, None),
478 ('networkx', None, 'NetworkX', None, None),
479 ('mayavi.mlab', None, 'MayaVi', None, None),
480 ('setuptools', None, 'Setuptools', None, None),
488 kwargs['and_dependencies'] = and_dependencies
489 CHECKER[name] = PythonPackageDependency(
490 package=package, name=name, long_name=long_name,
491 minimum_version=minimum_version, **kwargs)
493 del package, name, long_name, minimum_version, and_dependencies, kwargs
496 class MercurialPythonPackage (PythonPackageDependency):
497 def _get_version(self):
498 try: # mercurial >= 1.2
499 package = _importlib.import_module('mercurial.util')
500 except ImportError as e: # mercurial <= 1.1.2
501 package = self._get_package('mercurial.version')
502 return package.get_version()
504 return package.version()
507 CHECKER['mercurial'] = MercurialPythonPackage(
508 package='mercurial.util', name='mercurial',
509 long_name='Mercurial Python package',
510 minimum_version=CHECKER['hg'].minimum_version)
513 class SQLitePythonPackage (PythonPackageDependency):
514 def _get_version_from_package(self, package):
517 def _get_parsed_version(self):
518 return _sys.version_info
521 CHECKER['sqlite3-python'] = SQLitePythonPackage(
522 package='sqlite3', name='sqlite3-python',
523 long_name='SQLite Python package',
524 minimum_version=CHECKER['sqlite3'].minimum_version)
527 class VirtualDependency (Dependency):
529 return '{0} {1}'.format(
530 self.or_pass['dependency'].full_name(),
531 self.or_pass['version'])
534 for name,dependencies in [
555 ('virtual-browser', (
560 ('virtual-pypi-installer', (
565 CHECKER[name] = VirtualDependency(
566 name=name, long_name=name, or_dependencies=dependencies)
567 del name, dependencies # cleanup namespace
570 def _print_info(key, value, indent=19):
571 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
573 def print_system_info():
574 print("If you do not understand why the above failures occurred,")
575 print("copy and send the *entire* output (all info above and summary")
576 print("below) to the instructor for help.")
578 print('==================')
579 print('System information')
580 print('==================')
581 _print_info('os.name', _os.name)
582 _print_info('os.uname', _platform.uname())
583 _print_info('platform', _sys.platform)
584 _print_info('platform+', _platform.platform())
586 'linux_distribution',
590 value = getattr(_platform, pversion)()
592 _print_info(pversion, value)
593 _print_info('prefix', _sys.prefix)
594 _print_info('exec_prefix', _sys.exec_prefix)
595 _print_info('executable', _sys.executable)
596 _print_info('version_info', _sys.version_info)
597 _print_info('version', _sys.version)
598 _print_info('environment', '')
599 for key,value in sorted(_os.environ.items()):
600 print(' {0}={1}'.format(key, value))
601 print('==================')
603 def print_suggestions(instructor_fallback=True):
605 print('For suggestions on installing missing packages, see')
606 print('http://software-carpentry.org/setup/')
608 print('For instructings on installing a particular package,')
609 print('see the failure message for that package printed above.')
610 if instructor_fallback:
612 print('For help, email the *entire* output of this script to')
613 print('your instructor.')
616 if __name__ == '__main__':
618 passed = check(_sys.argv[1:])
619 except InvalidCheck as e:
620 print("I don't know how to check for {0!r}".format(e.check))
621 print('I do know how to check for:')
622 for key,checker in sorted(CHECKER.items()):
623 if checker.long_name != checker.name:
624 print(' {0} {1}({2})'.format(
625 key, ' '*(20-len(key)), checker.long_name))
627 print(' {0}'.format(key))
632 print_suggestions(instructor_fallback=True)