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',),
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
258 if not version_regexp:
259 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
260 version_regexp = _re.compile(regexp)
261 self.version_regexp = version_regexp
262 self.version_stream = version_stream
264 def _get_version_stream(self, expect=(0,)):
265 command = self.command + (self.exe_extension or '')
267 p = _subprocess.Popen(
268 [command] + list(self.version_options),
269 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
270 close_fds=True, shell=False, universal_newlines=True)
272 raise DependencyError(
274 message="could not find '{0}' executable".format(command),
276 stdout,stderr = p.communicate()
278 if status not in expect:
280 "failed to execute: {0} {1}".format(
282 ' '.join(_shlex.quote(arg)
283 for arg in self.version_options)),
284 'status: {0}'.format(status),
286 for name,string in [('stdout', stdout), ('stderr', stderr)]:
288 lines.extend([name + ':', string])
289 raise DependencyError(checker=self, message='\n'.join(lines))
290 for name,string in [('stdout', stdout), ('stderr', stderr)]:
291 if name == self.version_stream:
293 raise NotImplementedError(self.version_stream)
295 def _get_version(self):
296 version_stream = self._get_version_stream()
297 match = self.version_regexp.search(version_stream)
299 raise DependencyError(
301 message='no version string in output:\n{0}'.format(
303 return match.group(1)
306 for command,long_name,minimum_version in [
307 ('sh', 'Bourne Shell', None),
308 ('ash', 'Almquist Shell', None),
309 ('bash', 'Bourne Again Shell', None),
310 ('csh', 'C Shell', None),
311 ('ksh', 'KornShell', None),
312 ('dash', 'Debian Almquist Shell', None),
313 ('tcsh', 'TENEX C Shell', None),
314 ('zsh', 'Z Shell', None),
315 ('git', 'Git', (1, 7, 0)),
316 ('hg', 'Mercurial', (2, 0, 0)),
317 ('make', None, None),
318 ('sqlite3', 'SQLite 3', None),
319 ('nosetests', 'Nose', (1, 0, 0)),
320 ('emacs', 'Emacs', None),
321 ('xemacs', 'XEmacs', None),
322 ('vim', 'Vim', None),
324 ('nano', 'Nano', None),
325 ('kate', 'Kate', None),
326 ('notepad++', 'Notepad++', None),
327 ('firefox', 'Firefox', None),
328 ('google-chrome', 'Google Chrome', None),
329 ('chromium', 'Chromium', None),
333 CHECKER[command] = CommandDependency(
334 command=command, long_name=long_name, minimum_version=minimum_version)
335 del command, long_name, minimum_version # cleanup namespace
338 class EasyInstallDependency (CommandDependency):
339 def _get_version(self):
341 return super(EasyInstallDependency, self)._get_version()
342 except DependencyError as e:
343 version_stream = self.version_stream
345 self.version_stream = 'stderr'
346 stream = self._get_version_stream(expect=(1,))
347 if 'option --version not recognized' in stream:
348 return 'unknown (possibly Setuptools?)'
350 self.version_stream = version_stream
353 CHECKER['easy_install'] = EasyInstallDependency(
354 command='easy_install', long_name='Setuptools easy_install',
355 minimum_version=None)
358 class PythonPackageDependency (Dependency):
359 def __init__(self, package, **kwargs):
360 if 'name' not in kwargs:
361 kwargs['name'] = package
362 if 'and_dependencies' not in kwargs:
363 kwargs['and_dependencies'] = []
364 if 'python' not in kwargs['and_dependencies']:
365 kwargs['and_dependencies'].append('python')
366 super(PythonPackageDependency, self).__init__(**kwargs)
367 self.package = package
369 def _get_version(self):
370 package = self._get_package(self.package)
371 return self._get_version_from_package(package)
373 def _get_package(self, package):
375 return _importlib.import_module(package)
376 except ImportError as e:
377 raise DependencyError(
379 message="could not import the '{0}' package for {1}".format(
380 package, self.full_name()),
383 def _get_version_from_package(self, package):
385 version = package.__version__
386 except AttributeError:
391 for package,name,long_name,minimum_version in [
392 ('nose', None, 'Nose Python package',
393 CHECKER['nosetests'].minimum_version),
394 ('IPython', None, None, None),
395 ('numpy', None, 'NumPy', None),
396 ('scipy', None, 'SciPy', None),
397 ('matplotlib', None, 'Matplotlib', None),
398 ('sympy', None, 'SymPy', None),
399 ('Cython', None, None, None),
400 ('networkx', None, 'NetworkX', None),
401 ('mayavi.mlab', None, 'MayaVi', None),
402 ('setuptools', None, 'Setuptools', None),
408 CHECKER[name] = PythonPackageDependency(
409 package=package, name=name, long_name=long_name,
410 minimum_version=minimum_version)
411 del package, name, long_name, minimum_version # cleanup namespace
414 class MercurialPythonPackage (PythonPackageDependency):
415 def _get_version(self):
416 try: # mercurial >= 1.2
417 package = _importlib.import_module('mercurial.util')
418 except ImportError as e: # mercurial <= 1.1.2
419 package = self._get_package('mercurial.version')
420 return package.get_version()
422 return package.version()
425 CHECKER['mercurial'] = MercurialPythonPackage(
426 package='mercurial.util', name='mercurial',
427 long_name='Mercurial Python package',
428 minimum_version=CHECKER['hg'].minimum_version)
431 class SQLitePythonPackage (PythonPackageDependency):
432 def _get_version_from_package(self, package):
435 def _get_parsed_version(self):
436 return _sys.version_info
439 CHECKER['sqlite3-python'] = SQLitePythonPackage(
440 package='sqlite3', name='sqlite3-python',
441 long_name='SQLite Python package',
442 minimum_version=CHECKER['sqlite3'].minimum_version)
445 class VirtualDependency (Dependency):
447 return '{0} {1}'.format(
448 self.or_pass['dependency'].full_name(),
449 self.or_pass['version'])
452 for name,dependencies in [
472 ('virtual-browser', (
478 CHECKER[name] = VirtualDependency(
479 name=name, long_name=name, or_dependencies=dependencies)
480 del name, dependencies # cleanup namespace
483 def _print_info(key, value, indent=19):
484 print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
486 def print_system_info():
487 print("If you do not understand why the above failures occurred,")
488 print("copy and send the *entire* output (all info above and summary")
489 print("below) to the instructor for help.")
491 print('==================')
492 print('System information')
493 print('==================')
494 _print_info('os.name', _os.name)
495 _print_info('os.uname', _platform.uname())
496 _print_info('platform', _sys.platform)
497 _print_info('platform+', _platform.platform())
499 'linux_distribution',
503 value = getattr(_platform, pversion)()
505 _print_info(pversion, value)
506 _print_info('prefix', _sys.prefix)
507 _print_info('exec_prefix', _sys.exec_prefix)
508 _print_info('executable', _sys.executable)
509 _print_info('version_info', _sys.version_info)
510 _print_info('version', _sys.version)
511 _print_info('environment', '')
512 for key,value in sorted(_os.environ.items()):
513 print(' {0}={1}'.format(key, value))
514 print('==================')
516 def print_suggestions(instructor_fallback=True):
518 print('For suggestions on installing missing packages, see')
519 print('http://software-carpentry.org/setup/')
521 print('For instructings on installing a particular package,')
522 print('see the failure message for that package printed above.')
523 if instructor_fallback:
525 print('For help, email the *entire* output of this script to')
526 print('your instructor.')
529 if __name__ == '__main__':
530 if not check(_sys.argv[1:]):
533 print_suggestions(instructor_fallback=True)