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'.
15 from __future__ import print_function # for Python 2.6 compatibility
17 import distutils.ccompiler as _distutils_ccompiler
18 try: # Python 2.7 and 3.x
19 import importlib as _importlib
20 except ImportError: # Python 2.6 and earlier
21 class _Importlib (object):
22 """Minimal workarounds for functions we need
25 def import_module(name):
26 module = __import__(name)
27 for n in name.split('.')[1:]:
28 module = getattr(module, n)
30 _importlib = _Importlib()
31 import logging as _logging
33 import platform as _platform
35 import shlex as _shlex
36 import subprocess as _subprocess
40 if not hasattr(_shlex, 'quote'): # Python versions older than 3.3
41 # Use the undocumented pipes.quote()
42 import pipes as _pipes
43 _shlex.quote = _pipes.quote
48 # Comment out any entries you don't need
58 'hg', # Command line tool
59 'mercurial', # Python package
60 # Build tools and packaging
65 'nosetests', # Command line tool
66 'nose', # Python package
68 'sqlite3', # Command line tool
69 'sqlite3-python', # Python package
85 class DependencyError (Exception):
86 def _get_message(self):
88 def _set_message(self, message):
89 self._message = message
90 message = property(_get_message, _set_message)
92 def __init__(self, checker, message):
93 super(DependencyError, self).__init__(message)
94 self.checker = checker
95 self.message = message
98 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
99 return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
100 self.checker.full_name(), self.message,
101 'For instructions on installing an up-to-date version, see',
105 def check(checks=None):
111 checker = CHECKER[check]
112 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
114 version = checker.check()
115 except DependencyError as e:
117 _sys.stdout.write('fail\n')
119 _sys.stdout.write('pass\n')
120 successes.append((checker, version))
122 print('\nSuccesses:\n')
123 for checker,version in successes:
124 print('{0} {1}'.format(
126 version or 'unknown'))
130 for failure in failures:
131 if failure not in printed:
134 printed.append(failure)
139 class Dependency (object):
140 def __init__(self, name, long_name=None, minimum_version=None,
141 version_delimiter='.', and_dependencies=None,
142 or_dependencies=None):
144 self.long_name = long_name or name
145 self.minimum_version = minimum_version
146 self.version_delimiter = version_delimiter
147 if not and_dependencies:
148 and_dependencies = []
149 self.and_dependencies = and_dependencies
150 if not or_dependencies:
152 self.or_dependencies = or_dependencies
153 self._check_error = None
156 return '<{0} {1}>'.format(type(self).__name__, self.name)
159 if self.name == self.long_name:
162 return '{0} ({1})'.format(self.long_name, self.name)
165 if self._check_error:
166 raise self._check_error
168 self._check_dependencies()
170 except DependencyError as e:
171 self._check_error = e # cache for future calls
174 def _check_dependencies(self):
175 for dependency in self.and_dependencies:
176 if not hasattr(dependency, 'check'):
177 dependency = CHECKER[dependency]
179 self.or_pass = or_error = None
180 for dependency in self.or_dependencies:
181 if not hasattr(dependency, 'check'):
182 dependency = CHECKER[dependency]
184 version = dependency.check()
185 except DependencyError as e:
189 'dependency': dependency,
192 break # no need to test other dependencies
193 if self.or_dependencies and not self.or_pass:
197 version = self._get_version()
198 parsed_version = None
199 if hasattr(self, '_get_parsed_version'):
200 parsed_version = self._get_parsed_version()
201 if self.minimum_version:
202 self._check_version(version=version, parsed_version=parsed_version)
205 def _get_version(self):
206 raise NotImplementedError(self)
208 def _check_version(self, version, parsed_version=None):
209 if not parsed_version:
210 parsed_version = self._parse_version(version=version)
211 if not parsed_version or parsed_version < self.minimum_version:
212 raise DependencyError(
214 message='outdated version of {0}: {1} (need >= {2})'.format(
215 self.full_name(), version,
216 self.version_delimiter.join(
217 str(part) for part in self.minimum_version)))
219 def _parse_version(self, version):
223 for part in version.split(self.version_delimiter):
225 parsed_version.append(int(part))
226 except ValueError as e:
227 raise NotImplementedError((version, part))# from e
228 return tuple(parsed_version)
231 class PythonDependency (Dependency):
232 def __init__(self, name='python', long_name='Python version',
233 minimum_version=(2, 6), **kwargs):
234 super(PythonDependency, self).__init__(
235 name=name, long_name=long_name, minimum_version=minimum_version,
238 def _get_version(self):
241 def _get_parsed_version(self):
242 return _sys.version_info
245 CHECKER['python'] = PythonDependency()
248 class CommandDependency (Dependency):
249 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
251 def __init__(self, command, version_options=('--version',), stdin=None,
252 version_regexp=None, version_stream='stdout', **kwargs):
253 if 'name' not in kwargs:
254 kwargs['name'] = command
255 super(CommandDependency, self).__init__(**kwargs)
256 self.command = command
257 self.version_options = version_options
259 if not version_regexp:
260 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
261 version_regexp = _re.compile(regexp)
262 self.version_regexp = version_regexp
263 self.version_stream = version_stream
265 def _get_version_stream(self, stdin=None, expect=(0,)):
269 popen_stdin = _subprocess.PIPE
272 command = self.command + (self.exe_extension or '')
274 p = _subprocess.Popen(
275 [command] + list(self.version_options), stdin=popen_stdin,
276 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
277 close_fds=True, shell=False, universal_newlines=True)
279 raise DependencyError(
281 message="could not find '{0}' executable".format(command),
283 stdout,stderr = p.communicate(stdin)
285 if status not in expect:
287 "failed to execute: {0} {1}".format(
289 ' '.join(_shlex.quote(arg)
290 for arg in self.version_options)),
291 'status: {0}'.format(status),
293 for name,string in [('stdout', stdout), ('stderr', stderr)]:
295 lines.extend([name + ':', string])
296 raise DependencyError(checker=self, message='\n'.join(lines))
297 for name,string in [('stdout', stdout), ('stderr', stderr)]:
298 if name == self.version_stream:
300 raise NotImplementedError(self.version_stream)
302 def _get_version(self):
303 version_stream = self._get_version_stream()
304 match = self.version_regexp.search(version_stream)
306 raise DependencyError(
308 message='no version string in output:\n{0}'.format(
310 return match.group(1)
313 for command,long_name,minimum_version in [
314 ('sh', 'Bourne Shell', None),
315 ('ash', 'Almquist Shell', None),
316 ('bash', 'Bourne Again Shell', None),
317 ('csh', 'C Shell', None),
318 ('ksh', 'KornShell', None),
319 ('dash', 'Debian Almquist Shell', None),
320 ('tcsh', 'TENEX C Shell', None),
321 ('zsh', 'Z Shell', None),
322 ('git', 'Git', (1, 7, 0)),
323 ('hg', 'Mercurial', (2, 0, 0)),
324 ('sqlite3', 'SQLite 3', None),
325 ('nosetests', 'Nose', (1, 0, 0)),
326 ('emacs', 'Emacs', None),
327 ('xemacs', 'XEmacs', None),
328 ('vim', 'Vim', None),
330 ('nano', 'Nano', None),
331 ('kate', 'Kate', None),
332 ('notepad++', 'Notepad++', None),
333 ('firefox', 'Firefox', None),
334 ('google-chrome', 'Google Chrome', None),
335 ('chromium', 'Chromium', None),
339 CHECKER[command] = CommandDependency(
340 command=command, long_name=long_name, minimum_version=minimum_version)
341 del command, long_name, minimum_version # cleanup namespace
344 class MakeDependency (CommandDependency):
345 makefile = '\n'.join([
347 '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
348 '\t@echo "MAKE=$(MAKE)"',
352 def _get_version(self):
354 return super(MakeDependency, self)._get_version()
355 except DependencyError as e:
356 version_options = self.version_options
357 self.version_options = ['-f', '-']
359 stream = self._get_version_stream(stdin=self.makefile)
361 for line in stream.splitlines():
363 key,value = line.split('=', 1)
364 except ValueError as ve:
365 raise e# from NotImplementedError(stream)
367 if info.get('MAKE_VERSION', None):
368 return info['MAKE_VERSION']
369 elif info.get('MAKE', None):
373 self.version_options = version_options
376 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
379 class EasyInstallDependency (CommandDependency):
380 def _get_version(self):
382 return super(EasyInstallDependency, self)._get_version()
383 except DependencyError as e:
384 version_stream = self.version_stream
386 self.version_stream = 'stderr'
387 stream = self._get_version_stream(expect=(1,))
388 if 'option --version not recognized' in stream:
389 return 'unknown (possibly Setuptools?)'
391 self.version_stream = version_stream
394 CHECKER['easy_install'] = EasyInstallDependency(
395 command='easy_install', long_name='Setuptools easy_install',
396 minimum_version=None)
399 class PythonPackageDependency (Dependency):
400 def __init__(self, package, **kwargs):
401 if 'name' not in kwargs:
402 kwargs['name'] = package
403 if 'and_dependencies' not in kwargs:
404 kwargs['and_dependencies'] = []
405 if 'python' not in kwargs['and_dependencies']:
406 kwargs['and_dependencies'].append('python')
407 super(PythonPackageDependency, self).__init__(**kwargs)
408 self.package = package
410 def _get_version(self):
411 package = self._get_package(self.package)
412 return self._get_version_from_package(package)
414 def _get_package(self, package):
416 return _importlib.import_module(package)
417 except ImportError as e:
418 raise DependencyError(
420 message="could not import the '{0}' package for {1}".format(
421 package, self.full_name()),
424 def _get_version_from_package(self, package):
426 version = package.__version__
427 except AttributeError:
432 for package,name,long_name,minimum_version in [
433 ('nose', None, 'Nose Python package',
434 CHECKER['nosetests'].minimum_version),
435 ('IPython', None, None, None),
436 ('numpy', None, 'NumPy', None),
437 ('scipy', None, 'SciPy', None),
438 ('matplotlib', None, 'Matplotlib', None),
439 ('sympy', None, 'SymPy', None),
440 ('Cython', None, None, None),
441 ('networkx', None, 'NetworkX', None),
442 ('mayavi.mlab', None, 'MayaVi', None),
443 ('setuptools', None, 'Setuptools', None),
449 CHECKER[name] = PythonPackageDependency(
450 package=package, name=name, long_name=long_name,
451 minimum_version=minimum_version)
452 del package, name, long_name, minimum_version # cleanup namespace
455 class MercurialPythonPackage (PythonPackageDependency):
456 def _get_version(self):
457 try: # mercurial >= 1.2
458 package = _importlib.import_module('mercurial.util')
459 except ImportError as e: # mercurial <= 1.1.2
460 package = self._get_package('mercurial.version')
461 return package.get_version()
463 return package.version()
466 CHECKER['mercurial'] = MercurialPythonPackage(
467 package='mercurial.util', name='mercurial',
468 long_name='Mercurial Python package',
469 minimum_version=CHECKER['hg'].minimum_version)
472 class SQLitePythonPackage (PythonPackageDependency):
473 def _get_version_from_package(self, package):
476 def _get_parsed_version(self):
477 return _sys.version_info
480 CHECKER['sqlite3-python'] = SQLitePythonPackage(
481 package='sqlite3', name='sqlite3-python',
482 long_name='SQLite Python package',
483 minimum_version=CHECKER['sqlite3'].minimum_version)
486 class VirtualDependency (Dependency):
488 return '{0} {1}'.format(
489 self.or_pass['dependency'].full_name(),
490 self.or_pass['version'])
493 for name,dependencies in [
513 ('virtual-browser', (
519 CHECKER[name] = VirtualDependency(
520 name=name, long_name=name, or_dependencies=dependencies)
521 del name, dependencies # cleanup namespace
524 def _print_info(key, value, indent=19):
525 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
527 def print_system_info():
528 print("If you do not understand why the above failures occurred,")
529 print("copy and send the *entire* output (all info above and summary")
530 print("below) to the instructor for help.")
532 print('==================')
533 print('System information')
534 print('==================')
535 _print_info('os.name', _os.name)
536 _print_info('os.uname', _platform.uname())
537 _print_info('platform', _sys.platform)
538 _print_info('platform+', _platform.platform())
540 'linux_distribution',
544 value = getattr(_platform, pversion)()
546 _print_info(pversion, value)
547 _print_info('prefix', _sys.prefix)
548 _print_info('exec_prefix', _sys.exec_prefix)
549 _print_info('executable', _sys.executable)
550 _print_info('version_info', _sys.version_info)
551 _print_info('version', _sys.version)
552 _print_info('environment', '')
553 for key,value in sorted(_os.environ.items()):
554 print(' {0}={1}'.format(key, value))
555 print('==================')
557 def print_suggestions(instructor_fallback=True):
559 print('For suggestions on installing missing packages, see')
560 print('http://software-carpentry.org/setup/')
562 print('For instructings on installing a particular package,')
563 print('see the failure message for that package printed above.')
564 if instructor_fallback:
566 print('For help, email the *entire* output of this script to')
567 print('your instructor.')
570 if __name__ == '__main__':
571 if not check(_sys.argv[1:]):
574 print_suggestions(instructor_fallback=True)