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 ('kate', 'Kate', None),
366 ('notepad++', 'Notepad++', None),
367 ('firefox', 'Firefox', None),
368 ('google-chrome', 'Google Chrome', None),
369 ('chromium', 'Chromium', None),
373 CHECKER[command] = CommandDependency(
374 command=command, long_name=long_name, minimum_version=minimum_version)
375 del command, long_name, minimum_version # cleanup namespace
378 class MakeDependency (CommandDependency):
379 makefile = '\n'.join([
381 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
382 '\t@echo "MAKE=$(MAKE)"',
386 def _get_version(self):
388 return super(MakeDependency, self)._get_version()
389 except DependencyError as e:
390 version_options = self.version_options
391 self.version_options = ['-f', '-']
393 stream = self._get_version_stream(stdin=self.makefile)
395 for line in stream.splitlines():
397 key,value = line.split('=', 1)
398 except ValueError as ve:
399 raise e# from NotImplementedError(stream)
401 if info.get('MAKE_VERSION', None):
402 return info['MAKE_VERSION']
403 elif info.get('MAKE', None):
407 self.version_options = version_options
410 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
413 class EasyInstallDependency (CommandDependency):
414 def _get_version(self):
416 return super(EasyInstallDependency, self)._get_version()
417 except DependencyError as e:
418 version_stream = self.version_stream
420 self.version_stream = 'stderr'
421 stream = self._get_version_stream(expect=(1,))
422 if 'option --version not recognized' in stream:
423 return 'unknown (possibly Setuptools?)'
425 self.version_stream = version_stream
428 CHECKER['easy_install'] = EasyInstallDependency(
429 command='easy_install', long_name='Setuptools easy_install',
430 minimum_version=None)
433 class PythonPackageDependency (Dependency):
434 def __init__(self, package, **kwargs):
435 if 'name' not in kwargs:
436 kwargs['name'] = package
437 if 'and_dependencies' not in kwargs:
438 kwargs['and_dependencies'] = []
439 if 'python' not in kwargs['and_dependencies']:
440 kwargs['and_dependencies'].append('python')
441 super(PythonPackageDependency, self).__init__(**kwargs)
442 self.package = package
444 def _get_version(self):
445 package = self._get_package(self.package)
446 return self._get_version_from_package(package)
448 def _get_package(self, package):
450 return _importlib.import_module(package)
451 except ImportError as e:
452 raise DependencyError(
454 message="could not import the '{0}' package for {1}".format(
455 package, self.full_name()),
458 def _get_version_from_package(self, package):
460 version = package.__version__
461 except AttributeError:
466 for package,name,long_name,minimum_version in [
467 ('nose', None, 'Nose Python package',
468 CHECKER['nosetests'].minimum_version),
469 ('IPython', None, 'IPython Python package',
470 CHECKER['ipython'].minimum_version),
471 ('numpy', None, 'NumPy', None),
472 ('scipy', None, 'SciPy', None),
473 ('matplotlib', None, 'Matplotlib', None),
474 ('pandas', None, 'Pandas', (0, 8)),
475 ('sympy', None, 'SymPy', None),
476 ('Cython', None, None, None),
477 ('networkx', None, 'NetworkX', None),
478 ('mayavi.mlab', None, 'MayaVi', None),
479 ('setuptools', None, 'Setuptools', None),
485 CHECKER[name] = PythonPackageDependency(
486 package=package, name=name, long_name=long_name,
487 minimum_version=minimum_version)
488 del package, name, long_name, minimum_version # cleanup namespace
491 class MercurialPythonPackage (PythonPackageDependency):
492 def _get_version(self):
493 try: # mercurial >= 1.2
494 package = _importlib.import_module('mercurial.util')
495 except ImportError as e: # mercurial <= 1.1.2
496 package = self._get_package('mercurial.version')
497 return package.get_version()
499 return package.version()
502 CHECKER['mercurial'] = MercurialPythonPackage(
503 package='mercurial.util', name='mercurial',
504 long_name='Mercurial Python package',
505 minimum_version=CHECKER['hg'].minimum_version)
508 class SQLitePythonPackage (PythonPackageDependency):
509 def _get_version_from_package(self, package):
512 def _get_parsed_version(self):
513 return _sys.version_info
516 CHECKER['sqlite3-python'] = SQLitePythonPackage(
517 package='sqlite3', name='sqlite3-python',
518 long_name='SQLite Python package',
519 minimum_version=CHECKER['sqlite3'].minimum_version)
522 class VirtualDependency (Dependency):
524 return '{0} {1}'.format(
525 self.or_pass['dependency'].full_name(),
526 self.or_pass['version'])
529 for name,dependencies in [
549 ('virtual-browser', (
554 ('virtual-pypi-installer', (
559 CHECKER[name] = VirtualDependency(
560 name=name, long_name=name, or_dependencies=dependencies)
561 del name, dependencies # cleanup namespace
564 def _print_info(key, value, indent=19):
565 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
567 def print_system_info():
568 print("If you do not understand why the above failures occurred,")
569 print("copy and send the *entire* output (all info above and summary")
570 print("below) to the instructor for help.")
572 print('==================')
573 print('System information')
574 print('==================')
575 _print_info('os.name', _os.name)
576 _print_info('os.uname', _platform.uname())
577 _print_info('platform', _sys.platform)
578 _print_info('platform+', _platform.platform())
580 'linux_distribution',
584 value = getattr(_platform, pversion)()
586 _print_info(pversion, value)
587 _print_info('prefix', _sys.prefix)
588 _print_info('exec_prefix', _sys.exec_prefix)
589 _print_info('executable', _sys.executable)
590 _print_info('version_info', _sys.version_info)
591 _print_info('version', _sys.version)
592 _print_info('environment', '')
593 for key,value in sorted(_os.environ.items()):
594 print(' {0}={1}'.format(key, value))
595 print('==================')
597 def print_suggestions(instructor_fallback=True):
599 print('For suggestions on installing missing packages, see')
600 print('http://software-carpentry.org/setup/')
602 print('For instructings on installing a particular package,')
603 print('see the failure message for that package printed above.')
604 if instructor_fallback:
606 print('For help, email the *entire* output of this script to')
607 print('your instructor.')
610 if __name__ == '__main__':
612 passed = check(_sys.argv[1:])
613 except InvalidCheck as e:
614 print("I don't know how to check for {0!r}".format(e.check))
615 print('I do know how to check for:')
616 for key,checker in sorted(CHECKER.items()):
617 if checker.long_name != checker.name:
618 print(' {0} {1}({2})'.format(
619 key, ' '*(20-len(key)), checker.long_name))
621 print(' {0}'.format(key))
626 print_suggestions(instructor_fallback=True)