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 subprocess as _subprocess
41 # Comment out any entries you don't need
51 'hg', # Command line tool
52 'mercurial', # Python package
53 # Build tools and packaging
58 'nosetests', # Command line tool
59 'nose', # Python package
61 'sqlite3', # Command line tool
62 'sqlite3-python', # Python package
78 class DependencyError (Exception):
79 def _get_message(self):
81 def _set_message(self, message):
82 self._message = message
83 message = property(_get_message, _set_message)
85 def __init__(self, checker, message):
86 super(DependencyError, self).__init__(message)
87 self.checker = checker
88 self.message = message
91 url = 'http://software-carpentry.org/setup/' # TODO: per-package URL
92 return 'check for {0} failed:\n{1}\n{2}\n{3}'.format(
93 self.checker.full_name(), self.message,
94 'For instructions on installing an up-to-date version, see',
98 def check(checks=None):
104 checker = CHECKER[check]
105 _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
107 version = checker.check()
108 except DependencyError as e:
110 _sys.stdout.write('fail\n')
112 _sys.stdout.write('pass\n')
113 successes.append((checker, version))
115 print('\nSuccesses:\n')
116 for checker,version in successes:
117 print('{0} {1}'.format(
119 version or 'unknown'))
123 for failure in failures:
124 if failure not in printed:
127 printed.append(failure)
132 class Dependency (object):
133 def __init__(self, name, long_name=None, minimum_version=None,
134 version_delimiter='.', and_dependencies=None,
135 or_dependencies=None):
137 self.long_name = long_name or name
138 self.minimum_version = minimum_version
139 self.version_delimiter = version_delimiter
140 if not and_dependencies:
141 and_dependencies = []
142 self.and_dependencies = and_dependencies
143 if not or_dependencies:
145 self.or_dependencies = or_dependencies
146 self._check_error = None
149 return '<{0} {1}>'.format(type(self).__name__, self.name)
152 if self.name == self.long_name:
155 return '{0} ({1})'.format(self.long_name, self.name)
158 if self._check_error:
159 raise self._check_error
161 self._check_dependencies()
163 except DependencyError as e:
164 self._check_error = e # cache for future calls
167 def _check_dependencies(self):
168 for dependency in self.and_dependencies:
169 if not hasattr(dependency, 'check'):
170 dependency = CHECKER[dependency]
172 self.or_pass = or_error = None
173 for dependency in self.or_dependencies:
174 if not hasattr(dependency, 'check'):
175 dependency = CHECKER[dependency]
177 version = dependency.check()
178 except DependencyError as e:
182 'dependency': dependency,
185 break # no need to test other dependencies
186 if self.or_dependencies and not self.or_pass:
190 version = self._get_version()
191 parsed_version = None
192 if hasattr(self, '_get_parsed_version'):
193 parsed_version = self._get_parsed_version()
194 if self.minimum_version:
195 self._check_version(version=version, parsed_version=parsed_version)
198 def _get_version(self):
199 raise NotImplementedError(self)
201 def _check_version(self, version, parsed_version=None):
202 if not parsed_version:
203 parsed_version = self._parse_version(version=version)
204 if not parsed_version or parsed_version < self.minimum_version:
205 raise DependencyError(
207 message='outdated version of {0}: {1} (need >= {2})'.format(
208 self.full_name(), version,
209 self.version_delimiter.join(
210 str(part) for part in self.minimum_version)))
212 def _parse_version(self, version):
216 for part in version.split(self.version_delimiter):
218 parsed_version.append(int(part))
219 except ValueError as e:
220 raise NotImplementedError((version, part))# from e
221 return tuple(parsed_version)
224 class PythonDependency (Dependency):
225 def __init__(self, name='python', long_name='Python version',
226 minimum_version=(2, 6), **kwargs):
227 super(PythonDependency, self).__init__(
228 name=name, long_name=long_name, minimum_version=minimum_version,
231 def _get_version(self):
234 def _get_parsed_version(self):
235 return _sys.version_info
238 CHECKER['python'] = PythonDependency()
241 class CommandDependency (Dependency):
242 exe_extension = _distutils_ccompiler.new_compiler().exe_extension
244 def __init__(self, command, version_option='--version',
245 version_regexp=None, version_stream='stdout', **kwargs):
246 if 'name' not in kwargs:
247 kwargs['name'] = command
248 super(CommandDependency, self).__init__(**kwargs)
249 self.command = command
250 self.version_option = version_option
251 if not version_regexp:
252 regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
253 version_regexp = _re.compile(regexp)
254 self.version_regexp = version_regexp
255 self.version_stream = version_stream
257 def _get_version_stream(self, expect=(0,)):
258 command = self.command + (self.exe_extension or '')
260 p = _subprocess.Popen(
261 [command, self.version_option],
262 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
263 close_fds=True, shell=False, universal_newlines=True)
265 raise DependencyError(
267 message="could not find '{0}' executable".format(command),
269 stdout,stderr = p.communicate()
271 if status not in expect:
273 "failed to execute '{0} {1}':".format(
274 command, self.version_option),
275 'status: {0}'.format(status),
277 for name,string in [('stdout', stdout), ('stderr', stderr)]:
279 lines.extend([name + ':', string])
280 raise DependencyError(checker=self, message='\n'.join(lines))
281 for name,string in [('stdout', stdout), ('stderr', stderr)]:
282 if name == self.version_stream:
284 raise NotImplementedError(self.version_stream)
286 def _get_version(self):
287 version_stream = self._get_version_stream()
288 match = self.version_regexp.search(version_stream)
290 raise DependencyError(
292 message='no version string in output:\n{0}'.format(
294 return match.group(1)
297 for command,long_name,minimum_version in [
298 ('sh', 'Bourne Shell', None),
299 ('ash', 'Almquist Shell', None),
300 ('bash', 'Bourne Again Shell', None),
301 ('csh', 'C Shell', None),
302 ('ksh', 'KornShell', None),
303 ('dash', 'Debian Almquist Shell', None),
304 ('tcsh', 'TENEX C Shell', None),
305 ('zsh', 'Z Shell', None),
306 ('git', 'Git', (1, 7, 0)),
307 ('hg', 'Mercurial', (2, 0, 0)),
308 ('make', None, None),
309 ('sqlite3', 'SQLite 3', None),
310 ('nosetests', 'Nose', (1, 0, 0)),
311 ('emacs', 'Emacs', None),
312 ('xemacs', 'XEmacs', None),
313 ('vim', 'Vim', None),
315 ('nano', 'Nano', None),
316 ('kate', 'Kate', None),
317 ('notepad++', 'Notepad++', None),
318 ('firefox', 'Firefox', None),
319 ('google-chrome', 'Google Chrome', None),
320 ('chromium', 'Chromium', None),
324 CHECKER[command] = CommandDependency(
325 command=command, long_name=long_name, minimum_version=minimum_version)
326 del command, long_name, minimum_version # cleanup namespace
329 class EasyInstallDependency (CommandDependency):
330 def _get_version(self):
332 return super(EasyInstallDependency, self)._get_version()
333 except DependencyError as e:
334 version_stream = self.version_stream
336 self.version_stream = 'stderr'
337 stream = self._get_version_stream(expect=(1,))
338 if 'option --version not recognized' in stream:
339 return 'unknown (possibly Setuptools?)'
341 self.version_stream = version_stream
344 CHECKER['easy_install'] = EasyInstallDependency(
345 command='easy_install', long_name='Setuptools easy_install',
346 minimum_version=None)
349 class PythonPackageDependency (Dependency):
350 def __init__(self, package, **kwargs):
351 if 'name' not in kwargs:
352 kwargs['name'] = package
353 if 'and_dependencies' not in kwargs:
354 kwargs['and_dependencies'] = []
355 if 'python' not in kwargs['and_dependencies']:
356 kwargs['and_dependencies'].append('python')
357 super(PythonPackageDependency, self).__init__(**kwargs)
358 self.package = package
360 def _get_version(self):
361 package = self._get_package(self.package)
362 return self._get_version_from_package(package)
364 def _get_package(self, package):
366 return _importlib.import_module(package)
367 except ImportError as e:
368 raise DependencyError(
370 message="could not import the '{0}' package for {1}".format(
371 package, self.full_name()),
374 def _get_version_from_package(self, package):
376 version = package.__version__
377 except AttributeError:
382 for package,name,long_name,minimum_version in [
383 ('nose', None, 'Nose Python package',
384 CHECKER['nosetests'].minimum_version),
385 ('IPython', None, None, None),
386 ('numpy', None, 'NumPy', None),
387 ('scipy', None, 'SciPy', None),
388 ('matplotlib', None, 'Matplotlib', None),
389 ('sympy', None, 'SymPy', None),
390 ('Cython', None, None, None),
391 ('networkx', None, 'NetworkX', None),
392 ('mayavi.mlab', None, 'MayaVi', None),
393 ('setuptools', None, 'Setuptools', None),
399 CHECKER[name] = PythonPackageDependency(
400 package=package, name=name, long_name=long_name,
401 minimum_version=minimum_version)
402 del package, name, long_name, minimum_version # cleanup namespace
405 class MercurialPythonPackage (PythonPackageDependency):
406 def _get_version(self):
407 try: # mercurial >= 1.2
408 package = _importlib.import_module('mercurial.util')
409 except ImportError as e: # mercurial <= 1.1.2
410 package = self._get_package('mercurial.version')
411 return package.get_version()
413 return package.version()
416 CHECKER['mercurial'] = MercurialPythonPackage(
417 package='mercurial.util', name='mercurial',
418 long_name='Mercurial Python package',
419 minimum_version=CHECKER['hg'].minimum_version)
422 class SQLitePythonPackage (PythonPackageDependency):
423 def _get_version_from_package(self, package):
426 def _get_parsed_version(self):
427 return _sys.version_info
430 CHECKER['sqlite3-python'] = SQLitePythonPackage(
431 package='sqlite3', name='sqlite3-python',
432 long_name='SQLite Python package',
433 minimum_version=CHECKER['sqlite3'].minimum_version)
436 class VirtualDependency (Dependency):
438 return '{0} {1}'.format(
439 self.or_pass['dependency'].full_name(),
440 self.or_pass['version'])
443 for name,dependencies in [
463 ('virtual-browser', (
469 CHECKER[name] = VirtualDependency(
470 name=name, long_name=name, or_dependencies=dependencies)
471 del name, dependencies # cleanup namespace
474 def print_system_info():
475 print("If you do not understand why the above failures occurred,")
476 print("copy and send the *entire* output (all info above and summary")
477 print("below) to the instructor for help.")
479 print('==================')
480 print('System information')
481 print('==================')
482 print('os.name : {0}'.format(_os.name))
484 print('os.uname : {0}'.format(_os.uname()))
487 print('platform : {0}'.format(_sys.platform))
488 print('platform+ : {0}'.format(_platform.platform()))
489 print('prefix : {0}'.format(_sys.prefix))
490 print('exec_prefix : {0}'.format(_sys.exec_prefix))
491 print('executable : {0}'.format(_sys.executable))
492 print('version_info : {0}'.format(_sys.version_info))
493 print('version : {0}'.format(_sys.version))
494 print('environment :')
495 for key,value in sorted(_os.environ.items()):
496 print(' {0}={1}'.format(key, value))
497 print('==================')
499 def print_suggestions(instructor_fallback=True):
501 print('For suggestions on installing missing packages, see')
502 print('http://software-carpentry.org/setup/')
504 print('For instructings on installing a particular package,')
505 print('see the failure message for that package printed above.')
506 if instructor_fallback:
508 print('For help, email the *entire* output of this script to')
509 print('your instructor.')
512 if __name__ == '__main__':
513 if not check(_sys.argv[1:]):
516 print_suggestions(instructor_fallback=True)