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
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)),
347 ('sqlite3', 'SQLite 3', None),
348 ('nosetests', 'Nose', (1, 0, 0)),
349 ('emacs', 'Emacs', None),
350 ('xemacs', 'XEmacs', None),
351 ('vim', 'Vim', None),
353 ('nano', 'Nano', None),
354 ('kate', 'Kate', None),
355 ('notepad++', 'Notepad++', None),
356 ('firefox', 'Firefox', None),
357 ('google-chrome', 'Google Chrome', None),
358 ('chromium', 'Chromium', None),
362 CHECKER[command] = CommandDependency(
363 command=command, long_name=long_name, minimum_version=minimum_version)
364 del command, long_name, minimum_version # cleanup namespace
367 class MakeDependency (CommandDependency):
368 makefile = '\n'.join([
370 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
371 '\t@echo "MAKE=$(MAKE)"',
375 def _get_version(self):
377 return super(MakeDependency, self)._get_version()
378 except DependencyError as e:
379 version_options = self.version_options
380 self.version_options = ['-f', '-']
382 stream = self._get_version_stream(stdin=self.makefile)
384 for line in stream.splitlines():
386 key,value = line.split('=', 1)
387 except ValueError as ve:
388 raise e# from NotImplementedError(stream)
390 if info.get('MAKE_VERSION', None):
391 return info['MAKE_VERSION']
392 elif info.get('MAKE', None):
396 self.version_options = version_options
399 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
402 class EasyInstallDependency (CommandDependency):
403 def _get_version(self):
405 return super(EasyInstallDependency, self)._get_version()
406 except DependencyError as e:
407 version_stream = self.version_stream
409 self.version_stream = 'stderr'
410 stream = self._get_version_stream(expect=(1,))
411 if 'option --version not recognized' in stream:
412 return 'unknown (possibly Setuptools?)'
414 self.version_stream = version_stream
417 CHECKER['easy_install'] = EasyInstallDependency(
418 command='easy_install', long_name='Setuptools easy_install',
419 minimum_version=None)
422 class PythonPackageDependency (Dependency):
423 def __init__(self, package, **kwargs):
424 if 'name' not in kwargs:
425 kwargs['name'] = package
426 if 'and_dependencies' not in kwargs:
427 kwargs['and_dependencies'] = []
428 if 'python' not in kwargs['and_dependencies']:
429 kwargs['and_dependencies'].append('python')
430 super(PythonPackageDependency, self).__init__(**kwargs)
431 self.package = package
433 def _get_version(self):
434 package = self._get_package(self.package)
435 return self._get_version_from_package(package)
437 def _get_package(self, package):
439 return _importlib.import_module(package)
440 except ImportError as e:
441 raise DependencyError(
443 message="could not import the '{0}' package for {1}".format(
444 package, self.full_name()),
447 def _get_version_from_package(self, package):
449 version = package.__version__
450 except AttributeError:
455 for package,name,long_name,minimum_version in [
456 ('nose', None, 'Nose Python package',
457 CHECKER['nosetests'].minimum_version),
458 ('IPython', None, None, None),
459 ('numpy', None, 'NumPy', None),
460 ('scipy', None, 'SciPy', None),
461 ('matplotlib', None, 'Matplotlib', None),
462 ('sympy', None, 'SymPy', None),
463 ('Cython', None, None, None),
464 ('networkx', None, 'NetworkX', None),
465 ('mayavi.mlab', None, 'MayaVi', None),
466 ('setuptools', None, 'Setuptools', None),
472 CHECKER[name] = PythonPackageDependency(
473 package=package, name=name, long_name=long_name,
474 minimum_version=minimum_version)
475 del package, name, long_name, minimum_version # cleanup namespace
478 class MercurialPythonPackage (PythonPackageDependency):
479 def _get_version(self):
480 try: # mercurial >= 1.2
481 package = _importlib.import_module('mercurial.util')
482 except ImportError as e: # mercurial <= 1.1.2
483 package = self._get_package('mercurial.version')
484 return package.get_version()
486 return package.version()
489 CHECKER['mercurial'] = MercurialPythonPackage(
490 package='mercurial.util', name='mercurial',
491 long_name='Mercurial Python package',
492 minimum_version=CHECKER['hg'].minimum_version)
495 class SQLitePythonPackage (PythonPackageDependency):
496 def _get_version_from_package(self, package):
499 def _get_parsed_version(self):
500 return _sys.version_info
503 CHECKER['sqlite3-python'] = SQLitePythonPackage(
504 package='sqlite3', name='sqlite3-python',
505 long_name='SQLite Python package',
506 minimum_version=CHECKER['sqlite3'].minimum_version)
509 class VirtualDependency (Dependency):
511 return '{0} {1}'.format(
512 self.or_pass['dependency'].full_name(),
513 self.or_pass['version'])
516 for name,dependencies in [
536 ('virtual-browser', (
541 ('virtual-pypi-installer', (
546 CHECKER[name] = VirtualDependency(
547 name=name, long_name=name, or_dependencies=dependencies)
548 del name, dependencies # cleanup namespace
551 def _print_info(key, value, indent=19):
552 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
554 def print_system_info():
555 print("If you do not understand why the above failures occurred,")
556 print("copy and send the *entire* output (all info above and summary")
557 print("below) to the instructor for help.")
559 print('==================')
560 print('System information')
561 print('==================')
562 _print_info('os.name', _os.name)
563 _print_info('os.uname', _platform.uname())
564 _print_info('platform', _sys.platform)
565 _print_info('platform+', _platform.platform())
567 'linux_distribution',
571 value = getattr(_platform, pversion)()
573 _print_info(pversion, value)
574 _print_info('prefix', _sys.prefix)
575 _print_info('exec_prefix', _sys.exec_prefix)
576 _print_info('executable', _sys.executable)
577 _print_info('version_info', _sys.version_info)
578 _print_info('version', _sys.version)
579 _print_info('environment', '')
580 for key,value in sorted(_os.environ.items()):
581 print(' {0}={1}'.format(key, value))
582 print('==================')
584 def print_suggestions(instructor_fallback=True):
586 print('For suggestions on installing missing packages, see')
587 print('http://software-carpentry.org/setup/')
589 print('For instructings on installing a particular package,')
590 print('see the failure message for that package printed above.')
591 if instructor_fallback:
593 print('For help, email the *entire* output of this script to')
594 print('your instructor.')
597 if __name__ == '__main__':
599 passed = check(_sys.argv[1:])
600 except InvalidCheck as e:
601 print("I don't know how to check for {0!r}".format(e.check))
602 print('I do know how to check for:')
603 for key,checker in sorted(CHECKER.items()):
604 if checker.long_name != checker.name:
605 print(' {0} {1}({2})'.format(
606 key, ' '*(20-len(key)), checker.long_name))
608 print(' {0}'.format(key))
613 print_suggestions(instructor_fallback=True)