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',
76 'nosetests', # Command line tool
77 'nose', # Python package
79 'sqlite3', # Command line tool
80 'sqlite3-python', # Python package
83 'ipython', # Command line tool
84 'IPython', # Python package
98 class InvalidCheck (KeyError):
99 def __init__(self, check):
100 super(InvalidCheck, self).__init__(check)
107 class DependencyError (Exception):
108 def _get_message(self):
110 def _set_message(self, message):
111 self._message = message
112 message = property(_get_message, _set_message)
114 def __init__(self, checker, message, causes=None):
115 super(DependencyError, self).__init__(message)
116 self.checker = checker
117 self.message = message
123 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
125 'check for {0} failed:'.format(self.checker.full_name()),
127 ' For instructions on installing an up-to-date version, see',
131 lines.append(' causes:')
132 for cause in self.causes:
133 lines.extend(' ' + line for line in str(cause).splitlines())
134 return '\n'.join(lines)
137 def check(checks=None):
144 checker = CHECKER[check]
145 except KeyError as e:
146 raise InvalidCheck(check)# from e
147 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
149 version = checker.check()
150 except DependencyError as e:
152 _sys.stdout.write('fail\n')
154 _sys.stdout.write('pass\n')
155 successes.append((checker, version))
157 print('\nSuccesses:\n')
158 for checker,version in successes:
159 print('{0} {1}'.format(
161 version or 'unknown'))
165 for failure in failures:
166 if failure not in printed:
169 printed.append(failure)
174 class Dependency (object):
175 def __init__(self, name, long_name=None, minimum_version=None,
176 version_delimiter='.', and_dependencies=None,
177 or_dependencies=None):
179 self.long_name = long_name or name
180 self.minimum_version = minimum_version
181 self.version_delimiter = version_delimiter
182 if not and_dependencies:
183 and_dependencies = []
184 self.and_dependencies = and_dependencies
185 if not or_dependencies:
187 self.or_dependencies = or_dependencies
188 self._check_error = None
191 return '<{0} {1}>'.format(type(self).__name__, self.name)
194 if self.name == self.long_name:
197 return '{0} ({1})'.format(self.long_name, self.name)
200 if self._check_error:
201 raise self._check_error
203 self._check_dependencies()
205 except DependencyError as e:
206 self._check_error = e # cache for future calls
209 def _check_dependencies(self):
210 for dependency in self.and_dependencies:
211 if not hasattr(dependency, 'check'):
212 dependency = CHECKER[dependency]
215 except DependencyError as e:
216 raise DependencyError(
219 'some dependencies for {0} were not satisfied'
220 ).format(self.full_name()),
224 for dependency in self.or_dependencies:
225 if not hasattr(dependency, 'check'):
226 dependency = CHECKER[dependency]
228 version = dependency.check()
229 except DependencyError as e:
233 'dependency': dependency,
236 break # no need to test other dependencies
237 if self.or_dependencies and not self.or_pass:
238 raise DependencyError(
241 '{0} requires at least one of the following dependencies'
242 ).format(self.full_name()),
246 version = self._get_version()
247 parsed_version = None
248 if hasattr(self, '_get_parsed_version'):
249 parsed_version = self._get_parsed_version()
250 if self.minimum_version:
251 self._check_version(version=version, parsed_version=parsed_version)
254 def _get_version(self):
255 raise NotImplementedError(self)
257 def _minimum_version_string(self):
258 return self.version_delimiter.join(
259 str(part) for part in self.minimum_version)
261 def _check_version(self, version, parsed_version=None):
262 if not parsed_version:
263 parsed_version = self._parse_version(version=version)
264 if not parsed_version or parsed_version < self.minimum_version:
265 raise DependencyError(
267 message='outdated version of {0}: {1} (need >= {2})'.format(
268 self.full_name(), version, self._minimum_version_string()))
270 def _parse_version(self, version):
274 for part in version.split(self.version_delimiter):
276 parsed_version.append(int(part))
277 except ValueError as e:
278 raise DependencyError(
281 'unparsable {0!r} in version {1} of {2}, (need >= {3})'
283 part, version, self.full_name(),
284 self._minimum_version_string()))# from e
285 return tuple(parsed_version)
288 class PythonDependency (Dependency):
289 def __init__(self, name='python', long_name='Python version',
290 minimum_version=(2, 6), **kwargs):
291 super(PythonDependency, self).__init__(
292 name=name, long_name=long_name, minimum_version=minimum_version,
295 def _get_version(self):
298 def _get_parsed_version(self):
299 return _sys.version_info
302 CHECKER['python'] = PythonDependency()
305 class CommandDependency (Dependency):
306 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
308 def __init__(self, command, version_options=('--version',), stdin=None,
309 version_regexp=None, version_stream='stdout', **kwargs):
310 if 'name' not in kwargs:
311 kwargs['name'] = command
312 super(CommandDependency, self).__init__(**kwargs)
313 self.command = command
314 self.version_options = version_options
316 if not version_regexp:
317 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
318 version_regexp = _re.compile(regexp)
319 self.version_regexp = version_regexp
320 self.version_stream = version_stream
322 def _get_version_stream(self, stdin=None, expect=(0,)):
326 popen_stdin = _subprocess.PIPE
329 command = self.command + (self.exe_extension or '')
331 p = _subprocess.Popen(
332 [command] + list(self.version_options), stdin=popen_stdin,
333 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
334 close_fds=True, shell=False, universal_newlines=True)
336 raise DependencyError(
338 message="could not find '{0}' executable".format(command),
340 stdout,stderr = p.communicate(stdin)
342 if status not in expect:
344 "failed to execute: {0} {1}".format(
346 ' '.join(_shlex.quote(arg)
347 for arg in self.version_options)),
348 'status: {0}'.format(status),
350 for name,string in [('stdout', stdout), ('stderr', stderr)]:
352 lines.extend([name + ':', string])
353 raise DependencyError(checker=self, message='\n'.join(lines))
354 for name,string in [('stdout', stdout), ('stderr', stderr)]:
355 if name == self.version_stream:
357 raise NotImplementedError(self.version_stream)
359 def _get_version(self):
360 version_stream = self._get_version_stream()
361 match = self.version_regexp.search(version_stream)
363 raise DependencyError(
365 message='no version string in output:\n{0}'.format(
367 return match.group(1)
370 for command,long_name,minimum_version in [
371 ('sh', 'Bourne Shell', None),
372 ('ash', 'Almquist Shell', None),
373 ('bash', 'Bourne Again Shell', None),
374 ('csh', 'C Shell', None),
375 ('ksh', 'KornShell', None),
376 ('dash', 'Debian Almquist Shell', None),
377 ('tcsh', 'TENEX C Shell', None),
378 ('zsh', 'Z Shell', None),
379 ('git', 'Git', (1, 7, 0)),
380 ('hg', 'Mercurial', (2, 0, 0)),
382 ('sqlite3', 'SQLite 3', None),
383 ('nosetests', 'Nose', (1, 0, 0)),
384 ('ipython', 'IPython script', (0, 13)),
385 ('emacs', 'Emacs', None),
386 ('xemacs', 'XEmacs', None),
387 ('vim', 'Vim', None),
389 ('nano', 'Nano', None),
390 ('gedit', None, None),
391 ('kate', 'Kate', None),
392 ('notepad++', 'Notepad++', None),
393 ('firefox', 'Firefox', None),
394 ('google-chrome', 'Google Chrome', None),
395 ('chromium', 'Chromium', None),
399 CHECKER[command] = CommandDependency(
400 command=command, long_name=long_name, minimum_version=minimum_version)
401 del command, long_name, minimum_version # cleanup namespace
404 class MakeDependency (CommandDependency):
405 makefile = '\n'.join([
407 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
408 '\t@echo "MAKE=$(MAKE)"',
412 def _get_version(self):
414 return super(MakeDependency, self)._get_version()
415 except DependencyError as e:
416 version_options = self.version_options
417 self.version_options = ['-f', '-']
419 stream = self._get_version_stream(stdin=self.makefile)
421 for line in stream.splitlines():
423 key,value = line.split('=', 1)
424 except ValueError as ve:
425 raise e# from NotImplementedError(stream)
427 if info.get('MAKE_VERSION', None):
428 return info['MAKE_VERSION']
429 elif info.get('MAKE', None):
433 self.version_options = version_options
436 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
439 class EasyInstallDependency (CommandDependency):
440 def _get_version(self):
442 return super(EasyInstallDependency, self)._get_version()
443 except DependencyError as e:
444 version_stream = self.version_stream
446 self.version_stream = 'stderr'
447 stream = self._get_version_stream(expect=(1,))
448 if 'option --version not recognized' in stream:
449 return 'unknown (possibly Setuptools?)'
451 self.version_stream = version_stream
454 CHECKER['easy_install'] = EasyInstallDependency(
455 command='easy_install', long_name='Setuptools easy_install',
456 minimum_version=None)
459 class PathCommandDependency (CommandDependency):
460 """A command that doesn't support --version or equivalent options
462 On some operating systems (e.g. OS X), a command's executable may
463 be hard to find, or not exist in the PATH. Work around that by
464 just checking for the existence of a characteristic file or
465 directory. Since the characteristic path may depend on OS,
466 installed version, etc., take a list of paths, and succeed if any
469 def __init__(self, paths, **kwargs):
470 super(PathCommandDependency, self).__init__(self, **kwargs)
473 def _get_version_stream(self, *args, **kwargs):
474 raise NotImplementedError()
476 def _get_version(self):
477 for path in self.paths:
478 if _os.path.exists(path):
480 raise DependencyError(
483 'nothing exists at any of the expected paths for {0}:\n {1}'
486 '\n '.join(p for p in self.paths)))
489 for paths,name,long_name in [
490 ([_os.path.join(_os.sep, 'Applications', 'Sublime Text 2.app')],
491 'sublime-text', 'Sublime Text'),
492 ([_os.path.join(_os.sep, 'Applications', 'TextMate.app')],
493 'textmate', 'TextMate'),
494 ([_os.path.join(_os.sep, 'Applications', 'TextWrangler.app')],
495 'textwrangler', 'TextWrangler'),
496 ([_os.path.join(_os.sep, 'Applications', 'Xcode.app'), # OS X >=1.7
497 _os.path.join(_os.sep, 'Developer', 'Applications', 'Xcode.app'
504 CHECKER[name] = PathCommandDependency(
505 paths=paths, name=name, long_name=long_name)
506 del paths, name, long_name # cleanup namespace
509 class PythonPackageDependency (Dependency):
510 def __init__(self, package, **kwargs):
511 if 'name' not in kwargs:
512 kwargs['name'] = package
513 if 'and_dependencies' not in kwargs:
514 kwargs['and_dependencies'] = []
515 if 'python' not in kwargs['and_dependencies']:
516 kwargs['and_dependencies'].append('python')
517 super(PythonPackageDependency, self).__init__(**kwargs)
518 self.package = package
520 def _get_version(self):
521 package = self._get_package(self.package)
522 return self._get_version_from_package(package)
524 def _get_package(self, package):
526 return _importlib.import_module(package)
527 except ImportError as e:
528 raise DependencyError(
530 message="could not import the '{0}' package for {1}".format(
531 package, self.full_name()),
534 def _get_version_from_package(self, package):
536 version = package.__version__
537 except AttributeError:
542 for package,name,long_name,minimum_version,and_dependencies in [
543 ('nose', None, 'Nose Python package',
544 CHECKER['nosetests'].minimum_version, None),
545 ('jinja2', 'jinja', 'Jinja', (2, 6), None),
546 ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
547 ('IPython', None, 'IPython Python package',
548 CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']),
549 ('numpy', None, 'NumPy', None, None),
550 ('scipy', None, 'SciPy', None, None),
551 ('matplotlib', None, 'Matplotlib', None, None),
552 ('pandas', None, 'Pandas', (0, 8), None),
553 ('sympy', None, 'SymPy', None, None),
554 ('Cython', None, None, None, None),
555 ('networkx', None, 'NetworkX', None, None),
556 ('mayavi.mlab', None, 'MayaVi', None, None),
557 ('setuptools', None, 'Setuptools', None, None),
565 kwargs['and_dependencies'] = and_dependencies
566 CHECKER[name] = PythonPackageDependency(
567 package=package, name=name, long_name=long_name,
568 minimum_version=minimum_version, **kwargs)
570 del package, name, long_name, minimum_version, and_dependencies, kwargs
573 class MercurialPythonPackage (PythonPackageDependency):
574 def _get_version(self):
575 try: # mercurial >= 1.2
576 package = _importlib.import_module('mercurial.util')
577 except ImportError as e: # mercurial <= 1.1.2
578 package = self._get_package('mercurial.version')
579 return package.get_version()
581 return package.version()
584 CHECKER['mercurial'] = MercurialPythonPackage(
585 package='mercurial.util', name='mercurial',
586 long_name='Mercurial Python package',
587 minimum_version=CHECKER['hg'].minimum_version)
590 class TornadoPythonPackage (PythonPackageDependency):
591 def _get_version_from_package(self, package):
592 return package.version
594 def _get_parsed_version(self):
595 package = self._get_package(self.package)
596 return package.version_info
599 CHECKER['tornado'] = TornadoPythonPackage(
600 package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
603 class SQLitePythonPackage (PythonPackageDependency):
604 def _get_version_from_package(self, package):
607 def _get_parsed_version(self):
608 return _sys.version_info
611 CHECKER['sqlite3-python'] = SQLitePythonPackage(
612 package='sqlite3', name='sqlite3-python',
613 long_name='SQLite Python package',
614 minimum_version=CHECKER['sqlite3'].minimum_version)
617 class VirtualDependency (Dependency):
619 return '{0} {1}'.format(
620 self.or_pass['dependency'].full_name(),
621 self.or_pass['version'])
624 for name,long_name,dependencies in [
625 ('virtual-shell', 'command line shell', (
635 ('virtual-editor', 'text/code editor', (
648 ('virtual-browser', 'web browser', (
653 ('virtual-pypi-installer', 'PyPI installer', (
658 CHECKER[name] = VirtualDependency(
659 name=name, long_name=long_name, or_dependencies=dependencies)
660 del name, long_name, dependencies # cleanup namespace
663 def _print_info(key, value, indent=19):
664 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
666 def print_system_info():
667 print("If you do not understand why the above failures occurred,")
668 print("copy and send the *entire* output (all info above and summary")
669 print("below) to the instructor for help.")
671 print('==================')
672 print('System information')
673 print('==================')
674 _print_info('os.name', _os.name)
675 _print_info('os.uname', _platform.uname())
676 _print_info('platform', _sys.platform)
677 _print_info('platform+', _platform.platform())
679 'linux_distribution',
683 value = getattr(_platform, pversion)()
685 _print_info(pversion, value)
686 _print_info('prefix', _sys.prefix)
687 _print_info('exec_prefix', _sys.exec_prefix)
688 _print_info('executable', _sys.executable)
689 _print_info('version_info', _sys.version_info)
690 _print_info('version', _sys.version)
691 _print_info('environment', '')
692 for key,value in sorted(_os.environ.items()):
693 print(' {0}={1}'.format(key, value))
694 print('==================')
696 def print_suggestions(instructor_fallback=True):
698 print('For suggestions on installing missing packages, see')
699 print('http://software-carpentry.org/setup/')
701 print('For instructings on installing a particular package,')
702 print('see the failure message for that package printed above.')
703 if instructor_fallback:
705 print('For help, email the *entire* output of this script to')
706 print('your instructor.')
709 if __name__ == '__main__':
711 passed = check(_sys.argv[1:])
712 except InvalidCheck as e:
713 print("I don't know how to check for {0!r}".format(e.check))
714 print('I do know how to check for:')
715 for key,checker in sorted(CHECKER.items()):
716 if checker.long_name != checker.name:
717 print(' {0} {1}({2})'.format(
718 key, ' '*(20-len(key)), checker.long_name))
720 print(' {0}'.format(key))
725 print_suggestions(instructor_fallback=True)