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 DependencyError (Exception):
96 def _get_message(self):
98 def _set_message(self, message):
99 self._message = message
100 message = property(_get_message, _set_message)
102 def __init__(self, checker, message):
103 super(DependencyError, self).__init__(message)
104 self.checker = checker
105 self.message = message
108 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
109 return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
110 self.checker.full_name(), self.message,
111 'For instructions on installing an up-to-date version, see',
115 def check(checks=None):
121 checker = CHECKER[check]
122 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
124 version = checker.check()
125 except DependencyError as e:
127 _sys.stdout.write('fail\n')
129 _sys.stdout.write('pass\n')
130 successes.append((checker, version))
132 print('\nSuccesses:\n')
133 for checker,version in successes:
134 print('{0} {1}'.format(
136 version or 'unknown'))
140 for failure in failures:
141 if failure not in printed:
144 printed.append(failure)
149 class Dependency (object):
150 def __init__(self, name, long_name=None, minimum_version=None,
151 version_delimiter='.', and_dependencies=None,
152 or_dependencies=None):
154 self.long_name = long_name or name
155 self.minimum_version = minimum_version
156 self.version_delimiter = version_delimiter
157 if not and_dependencies:
158 and_dependencies = []
159 self.and_dependencies = and_dependencies
160 if not or_dependencies:
162 self.or_dependencies = or_dependencies
163 self._check_error = None
166 return '<{0} {1}>'.format(type(self).__name__, self.name)
169 if self.name == self.long_name:
172 return '{0} ({1})'.format(self.long_name, self.name)
175 if self._check_error:
176 raise self._check_error
178 self._check_dependencies()
180 except DependencyError as e:
181 self._check_error = e # cache for future calls
184 def _check_dependencies(self):
185 for dependency in self.and_dependencies:
186 if not hasattr(dependency, 'check'):
187 dependency = CHECKER[dependency]
189 self.or_pass = or_error = None
190 for dependency in self.or_dependencies:
191 if not hasattr(dependency, 'check'):
192 dependency = CHECKER[dependency]
194 version = dependency.check()
195 except DependencyError as e:
199 'dependency': dependency,
202 break # no need to test other dependencies
203 if self.or_dependencies and not self.or_pass:
207 version = self._get_version()
208 parsed_version = None
209 if hasattr(self, '_get_parsed_version'):
210 parsed_version = self._get_parsed_version()
211 if self.minimum_version:
212 self._check_version(version=version, parsed_version=parsed_version)
215 def _get_version(self):
216 raise NotImplementedError(self)
218 def _check_version(self, version, parsed_version=None):
219 if not parsed_version:
220 parsed_version = self._parse_version(version=version)
221 if not parsed_version or parsed_version < self.minimum_version:
222 raise DependencyError(
224 message='outdated version of {0}: {1} (need >= {2})'.format(
225 self.full_name(), version,
226 self.version_delimiter.join(
227 str(part) for part in self.minimum_version)))
229 def _parse_version(self, version):
233 for part in version.split(self.version_delimiter):
235 parsed_version.append(int(part))
236 except ValueError as e:
237 raise NotImplementedError((version, part))# from e
238 return tuple(parsed_version)
241 class PythonDependency (Dependency):
242 def __init__(self, name='python', long_name='Python version',
243 minimum_version=(2, 6), **kwargs):
244 super(PythonDependency, self).__init__(
245 name=name, long_name=long_name, minimum_version=minimum_version,
248 def _get_version(self):
251 def _get_parsed_version(self):
252 return _sys.version_info
255 CHECKER['python'] = PythonDependency()
258 class CommandDependency (Dependency):
259 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
261 def __init__(self, command, version_options=('--version',), stdin=None,
262 version_regexp=None, version_stream='stdout', **kwargs):
263 if 'name' not in kwargs:
264 kwargs['name'] = command
265 super(CommandDependency, self).__init__(**kwargs)
266 self.command = command
267 self.version_options = version_options
269 if not version_regexp:
270 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
271 version_regexp = _re.compile(regexp)
272 self.version_regexp = version_regexp
273 self.version_stream = version_stream
275 def _get_version_stream(self, stdin=None, expect=(0,)):
279 popen_stdin = _subprocess.PIPE
282 command = self.command + (self.exe_extension or '')
284 p = _subprocess.Popen(
285 [command] + list(self.version_options), stdin=popen_stdin,
286 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
287 close_fds=True, shell=False, universal_newlines=True)
289 raise DependencyError(
291 message="could not find '{0}' executable".format(command),
293 stdout,stderr = p.communicate(stdin)
295 if status not in expect:
297 "failed to execute: {0} {1}".format(
299 ' '.join(_shlex.quote(arg)
300 for arg in self.version_options)),
301 'status: {0}'.format(status),
303 for name,string in [('stdout', stdout), ('stderr', stderr)]:
305 lines.extend([name + ':', string])
306 raise DependencyError(checker=self, message='\n'.join(lines))
307 for name,string in [('stdout', stdout), ('stderr', stderr)]:
308 if name == self.version_stream:
310 raise NotImplementedError(self.version_stream)
312 def _get_version(self):
313 version_stream = self._get_version_stream()
314 match = self.version_regexp.search(version_stream)
316 raise DependencyError(
318 message='no version string in output:\n{0}'.format(
320 return match.group(1)
323 for command,long_name,minimum_version in [
324 ('sh', 'Bourne Shell', None),
325 ('ash', 'Almquist Shell', None),
326 ('bash', 'Bourne Again Shell', None),
327 ('csh', 'C Shell', None),
328 ('ksh', 'KornShell', None),
329 ('dash', 'Debian Almquist Shell', None),
330 ('tcsh', 'TENEX C Shell', None),
331 ('zsh', 'Z Shell', None),
332 ('git', 'Git', (1, 7, 0)),
333 ('hg', 'Mercurial', (2, 0, 0)),
334 ('sqlite3', 'SQLite 3', None),
335 ('nosetests', 'Nose', (1, 0, 0)),
336 ('emacs', 'Emacs', None),
337 ('xemacs', 'XEmacs', None),
338 ('vim', 'Vim', None),
340 ('nano', 'Nano', None),
341 ('kate', 'Kate', None),
342 ('notepad++', 'Notepad++', None),
343 ('firefox', 'Firefox', None),
344 ('google-chrome', 'Google Chrome', None),
345 ('chromium', 'Chromium', None),
349 CHECKER[command] = CommandDependency(
350 command=command, long_name=long_name, minimum_version=minimum_version)
351 del command, long_name, minimum_version # cleanup namespace
354 class MakeDependency (CommandDependency):
355 makefile = '\n'.join([
357 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
358 '\t@echo "MAKE=$(MAKE)"',
362 def _get_version(self):
364 return super(MakeDependency, self)._get_version()
365 except DependencyError as e:
366 version_options = self.version_options
367 self.version_options = ['-f', '-']
369 stream = self._get_version_stream(stdin=self.makefile)
371 for line in stream.splitlines():
373 key,value = line.split('=', 1)
374 except ValueError as ve:
375 raise e# from NotImplementedError(stream)
377 if info.get('MAKE_VERSION', None):
378 return info['MAKE_VERSION']
379 elif info.get('MAKE', None):
383 self.version_options = version_options
386 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
389 class EasyInstallDependency (CommandDependency):
390 def _get_version(self):
392 return super(EasyInstallDependency, self)._get_version()
393 except DependencyError as e:
394 version_stream = self.version_stream
396 self.version_stream = 'stderr'
397 stream = self._get_version_stream(expect=(1,))
398 if 'option --version not recognized' in stream:
399 return 'unknown (possibly Setuptools?)'
401 self.version_stream = version_stream
404 CHECKER['easy_install'] = EasyInstallDependency(
405 command='easy_install', long_name='Setuptools easy_install',
406 minimum_version=None)
409 class PythonPackageDependency (Dependency):
410 def __init__(self, package, **kwargs):
411 if 'name' not in kwargs:
412 kwargs['name'] = package
413 if 'and_dependencies' not in kwargs:
414 kwargs['and_dependencies'] = []
415 if 'python' not in kwargs['and_dependencies']:
416 kwargs['and_dependencies'].append('python')
417 super(PythonPackageDependency, self).__init__(**kwargs)
418 self.package = package
420 def _get_version(self):
421 package = self._get_package(self.package)
422 return self._get_version_from_package(package)
424 def _get_package(self, package):
426 return _importlib.import_module(package)
427 except ImportError as e:
428 raise DependencyError(
430 message="could not import the '{0}' package for {1}".format(
431 package, self.full_name()),
434 def _get_version_from_package(self, package):
436 version = package.__version__
437 except AttributeError:
442 for package,name,long_name,minimum_version in [
443 ('nose', None, 'Nose Python package',
444 CHECKER['nosetests'].minimum_version),
445 ('IPython', None, None, None),
446 ('numpy', None, 'NumPy', None),
447 ('scipy', None, 'SciPy', None),
448 ('matplotlib', None, 'Matplotlib', None),
449 ('sympy', None, 'SymPy', None),
450 ('Cython', None, None, None),
451 ('networkx', None, 'NetworkX', None),
452 ('mayavi.mlab', None, 'MayaVi', None),
453 ('setuptools', None, 'Setuptools', None),
459 CHECKER[name] = PythonPackageDependency(
460 package=package, name=name, long_name=long_name,
461 minimum_version=minimum_version)
462 del package, name, long_name, minimum_version # cleanup namespace
465 class MercurialPythonPackage (PythonPackageDependency):
466 def _get_version(self):
467 try: # mercurial >= 1.2
468 package = _importlib.import_module('mercurial.util')
469 except ImportError as e: # mercurial <= 1.1.2
470 package = self._get_package('mercurial.version')
471 return package.get_version()
473 return package.version()
476 CHECKER['mercurial'] = MercurialPythonPackage(
477 package='mercurial.util', name='mercurial',
478 long_name='Mercurial Python package',
479 minimum_version=CHECKER['hg'].minimum_version)
482 class SQLitePythonPackage (PythonPackageDependency):
483 def _get_version_from_package(self, package):
486 def _get_parsed_version(self):
487 return _sys.version_info
490 CHECKER['sqlite3-python'] = SQLitePythonPackage(
491 package='sqlite3', name='sqlite3-python',
492 long_name='SQLite Python package',
493 minimum_version=CHECKER['sqlite3'].minimum_version)
496 class VirtualDependency (Dependency):
498 return '{0} {1}'.format(
499 self.or_pass['dependency'].full_name(),
500 self.or_pass['version'])
503 for name,dependencies in [
523 ('virtual-browser', (
529 CHECKER[name] = VirtualDependency(
530 name=name, long_name=name, or_dependencies=dependencies)
531 del name, dependencies # cleanup namespace
534 def _print_info(key, value, indent=19):
535 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
537 def print_system_info():
538 print("If you do not understand why the above failures occurred,")
539 print("copy and send the *entire* output (all info above and summary")
540 print("below) to the instructor for help.")
542 print('==================')
543 print('System information')
544 print('==================')
545 _print_info('os.name', _os.name)
546 _print_info('os.uname', _platform.uname())
547 _print_info('platform', _sys.platform)
548 _print_info('platform+', _platform.platform())
550 'linux_distribution',
554 value = getattr(_platform, pversion)()
556 _print_info(pversion, value)
557 _print_info('prefix', _sys.prefix)
558 _print_info('exec_prefix', _sys.exec_prefix)
559 _print_info('executable', _sys.executable)
560 _print_info('version_info', _sys.version_info)
561 _print_info('version', _sys.version)
562 _print_info('environment', '')
563 for key,value in sorted(_os.environ.items()):
564 print(' {0}={1}'.format(key, value))
565 print('==================')
567 def print_suggestions(instructor_fallback=True):
569 print('For suggestions on installing missing packages, see')
570 print('http://software-carpentry.org/setup/')
572 print('For instructings on installing a particular package,')
573 print('see the failure message for that package printed above.')
574 if instructor_fallback:
576 print('For help, email the *entire* output of this script to')
577 print('your instructor.')
580 if __name__ == '__main__':
581 if not check(_sys.argv[1:]):
584 print_suggestions(instructor_fallback=True)