swc-installation-test-2.py: Disable sympy, Cython, networkx, and mayavi.mlab
[swc-setup-installation-test.git] / swc-installation-test-2.py
1 #!/usr/bin/env python
2
3 """Test script to check for required functionality.
4
5 Execute this code at the command line by typing:
6
7   python swc-installation-test-2.py
8
9 Run the script and follow the instructions it prints at the end.
10
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'.
13
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:
17
18   python swc-installation-test-2.py git virtual-editor
19
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
22 dependency.
23 """
24
25 # Some details about the implementation:
26
27 # The dependencies are divided into a hierarchy of classes rooted on
28 # Dependency class. You can refer to the code to see which package
29 # comes under which type of dependency.
30
31 # The CHECKER dictionary stores information about all the dependencies
32 # and CHECKS stores list of the dependencies which are to be checked in
33 # the current workshop.
34
35 # In the "__name__ == '__main__'" block, we launch all the checks with
36 # check() function, which prints information about the tests as they run
37 # and details about the failures after the tests complete. In case of
38 # failure, the functions print_system_info() and print_suggestions()
39 # are called after this, where the former prints information about the
40 # user's system for debugging purposes while the latter prints some
41 # suggestions to follow.
42
43
44 from __future__ import print_function  # for Python 2.6 compatibility
45
46 import distutils.ccompiler as _distutils_ccompiler
47 import fnmatch as _fnmatch
48 try:  # Python 2.7 and 3.x
49     import importlib as _importlib
50 except ImportError:  # Python 2.6 and earlier
51     class _Importlib (object):
52         """Minimal workarounds for functions we need
53         """
54         @staticmethod
55         def import_module(name):
56             module = __import__(name)
57             for n in name.split('.')[1:]:
58                 module = getattr(module, n)
59             return module
60     _importlib = _Importlib()
61 import logging as _logging
62 import os as _os
63 import platform as _platform
64 import re as _re
65 import shlex as _shlex
66 import subprocess as _subprocess
67 import sys as _sys
68 try:  # Python 3.x
69     import urllib.parse as _urllib_parse
70 except ImportError:  # Python 2.x
71     import urllib as _urllib_parse  # for quote()
72 import xml.etree.ElementTree as _element_tree
73
74
75 if not hasattr(_shlex, 'quote'):  # Python versions older than 3.3
76     # Use the undocumented pipes.quote()
77     import pipes as _pipes
78     _shlex.quote = _pipes.quote
79
80
81 __version__ = '0.1'
82
83 # Comment out any entries you don't need
84 CHECKS = [
85 # Shell
86     'virtual-shell',
87 # Editors
88     'virtual-editor',
89 # Browsers
90     'virtual-browser',
91 # Version control
92     'git',
93     'hg',              # Command line tool
94     #'mercurial',       # Python package
95     'EasyMercurial',
96 # Build tools and packaging
97     'make',
98     'virtual-pypi-installer',
99     'setuptools',
100     #'xcode',
101 # Testing
102     'nosetests',       # Command line tool
103     'nose',            # Python package
104     'py.test',         # Command line tool
105     'pytest',          # Python package
106 # SQL
107     'sqlite3',         # Command line tool
108     'sqlite3-python',  # Python package
109 # Python
110     'python',
111     'ipython',         # Command line tool
112     'IPython',         # Python package
113     'argparse',        # Useful for utility scripts
114     'numpy',
115     'scipy',
116     'matplotlib',
117     'pandas',
118     #'sympy',
119     #'Cython',
120     #'networkx',
121     #'mayavi.mlab',
122     ]
123
124 CHECKER = {}
125
126 _ROOT_PATH = _os.sep
127 if _platform.system() == 'win32':
128     _ROOT_PATH = 'c:\\'
129
130
131 class InvalidCheck (KeyError):
132     def __init__(self, check):
133         super(InvalidCheck, self).__init__(check)
134         self.check = check
135
136     def __str__(self):
137         return self.check
138
139
140 class DependencyError (Exception):
141     _default_url = 'http://software-carpentry.org/setup/'
142     _setup_urls = {  # (system, version, package) glob pairs
143         ('*', '*', 'Cython'): 'http://docs.cython.org/src/quickstart/install.html',
144         ('Linux', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-linux',
145         ('Darwin', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-mac',
146         ('Windows', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-windows',
147         ('*', '*', 'EasyMercurial'): 'http://easyhg.org/download.html',
148         ('*', '*', 'argparse'): 'https://pypi.python.org/pypi/argparse#installation',
149         ('*', '*', 'ash'): 'http://www.in-ulm.de/~mascheck/various/ash/',
150         ('*', '*', 'bash'): 'http://www.gnu.org/software/bash/manual/html_node/Basic-Installation.html#Basic-Installation',
151         ('Linux', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/LinuxBuildInstructions',
152         ('Darwin', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/MacBuildInstructions',
153         ('Windows', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos/build-instructions-windows',
154         ('*', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos',
155         ('Windows', '*', 'emacs'): 'http://www.gnu.org/software/emacs/windows/Installing-Emacs.html',
156         ('*', '*', 'emacs'): 'http://www.gnu.org/software/emacs/#Obtaining',
157         ('*', '*', 'firefox'): 'http://www.mozilla.org/en-US/firefox/new/',
158         ('Linux', '*', 'gedit'): 'http://www.linuxfromscratch.org/blfs/view/svn/gnome/gedit.html',
159         ('*', '*', 'git'): 'http://git-scm.com/downloads',
160         ('*', '*', 'google-chrome'): 'https://www.google.com/intl/en/chrome/browser/',
161         ('*', '*', 'hg'): 'http://mercurial.selenic.com/',
162         ('*', '*', 'mercurial'): 'http://mercurial.selenic.com/',
163         ('*', '*', 'IPython'): 'http://ipython.org/install.html',
164         ('*', '*', 'ipython'): 'http://ipython.org/install.html',
165         ('*', '*', 'jinja'): 'http://jinja.pocoo.org/docs/intro/#installation',
166         ('*', '*', 'kate'): 'http://kate-editor.org/get-it/',
167         ('*', '*', 'make'): 'http://www.gnu.org/software/make/',
168         ('Darwin', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#building-on-osx',
169         ('Windows', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing-on-windows',
170         ('*', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing',
171         ('*', '*', 'mayavi.mlab'): 'http://docs.enthought.com/mayavi/mayavi/installation.html',
172         ('*', '*', 'nano'): 'http://www.nano-editor.org/dist/latest/faq.html#3',
173         ('*', '*', 'networkx'): 'http://networkx.github.com/documentation/latest/install.html#installing',
174         ('*', '*', 'nose'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
175         ('*', '*', 'nosetests'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start',
176         ('*', '*', 'notepad++'): 'http://notepad-plus-plus.org/download/v6.3.html',
177         ('*', '*', 'numpy'): 'http://docs.scipy.org/doc/numpy/user/install.html',
178         ('*', '*', 'pandas'): 'http://pandas.pydata.org/pandas-docs/stable/install.html',
179         ('*', '*', 'pip'): 'http://www.pip-installer.org/en/latest/installing.html',
180         ('*', '*', 'pytest'): 'http://pytest.org/latest/getting-started.html',
181         ('*', '*', 'python'): 'http://www.python.org/download/releases/2.7.3/#download',
182         ('*', '*', 'pyzmq'): 'https://github.com/zeromq/pyzmq/wiki/Building-and-Installing-PyZMQ',
183         ('*', '*', 'py.test'): 'http://pytest.org/latest/getting-started.html',
184         ('Linux', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Linux',
185         ('Darwin', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Mac_OS_X',
186         ('Windows', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Windows',
187         ('*', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy',
188         ('*', '*', 'setuptools'): 'https://pypi.python.org/pypi/setuptools#installation-instructions',
189         ('*', '*', 'sqlite3'): 'http://www.sqlite.org/download.html',
190         ('*', '*', 'sublime-text'): 'http://www.sublimetext.com/2',
191         ('*', '*', 'sympy'): 'http://docs.sympy.org/dev/install.html',
192         ('Darwin', '*', 'textmate'): 'http://macromates.com/',
193         ('Darwin', '*', 'textwrangler'): 'http://www.barebones.com/products/textwrangler/download.html',
194         ('*', '*', 'tornado'): 'http://www.tornadoweb.org/',
195         ('*', '*', 'vim'): 'http://www.vim.org/download.php',
196         ('Darwin', '*', 'xcode'): 'https://developer.apple.com/xcode/',
197         ('*', '*', 'xemacs'): 'http://www.us.xemacs.org/Install/',
198         ('*', '*', 'zsh'): 'http://www.zsh.org/',
199         }
200
201     def _get_message(self):
202         return self._message
203     def _set_message(self, message):
204         self._message = message
205     message = property(_get_message, _set_message)
206
207     def __init__(self, checker, message, causes=None):
208         super(DependencyError, self).__init__(message)
209         self.checker = checker
210         self.message = message
211         if causes is None:
212             causes = []
213         self.causes = causes
214
215     def get_url(self):
216         system = _platform.system()
217         version = None
218         for pversion in (
219             'linux_distribution',
220             'mac_ver',
221             'win32_ver',
222             ):
223             value = getattr(_platform, pversion)()
224             if value[0]:
225                 version = value[0]
226                 break
227         package = self.checker.name
228         for (s,v,p),url in self._setup_urls.items():
229             if (_fnmatch.fnmatch(system, s) and
230                     _fnmatch.fnmatch(version, v) and
231                     _fnmatch.fnmatch(package, p)):
232                 return url
233         return self._default_url
234
235     def __str__(self):
236         url = self.get_url()
237         lines = [
238             'check for {0} failed:'.format(self.checker.full_name()),
239             '  ' + self.message,
240             '  For instructions on installing an up-to-date version, see',
241             '  ' + url,
242             ]
243         if self.causes:
244             lines.append('  causes:')
245             for cause in self.causes:
246                 lines.extend('  ' + line for line in str(cause).splitlines())
247         return '\n'.join(lines)
248
249
250 def check(checks=None):
251     successes = []
252     failures = []
253     if not checks:
254         checks = CHECKS
255     for check in checks:
256         try:
257             checker = CHECKER[check]
258         except KeyError as e:
259             raise InvalidCheck(check)# from e
260         _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
261         try:
262             version = checker.check()
263         except DependencyError as e:
264             failures.append(e)
265             _sys.stdout.write('fail\n')
266         else:
267             _sys.stdout.write('pass\n')
268             successes.append((checker, version))
269     if successes:
270         print('\nSuccesses:\n')
271         for checker,version in successes:
272             print('{0} {1}'.format(
273                     checker.full_name(),
274                     version or 'unknown'))
275     if failures:
276         print('\nFailures:')
277         printed = []
278         for failure in failures:
279             if failure not in printed:
280                 print()
281                 print(failure)
282                 printed.append(failure)
283         return False
284     return True
285
286
287 class Dependency (object):
288     def __init__(self, name, long_name=None, minimum_version=None,
289                  version_delimiter='.', and_dependencies=None,
290                  or_dependencies=None):
291         self.name = name
292         self.long_name = long_name or name
293         self.minimum_version = minimum_version
294         self.version_delimiter = version_delimiter
295         if not and_dependencies:
296             and_dependencies = []
297         self.and_dependencies = and_dependencies
298         if not or_dependencies:
299             or_dependencies = []
300         self.or_dependencies = or_dependencies
301         self._check_error = None
302
303     def __str__(self):
304         return '<{0} {1}>'.format(type(self).__name__, self.name)
305
306     def full_name(self):
307         if self.name == self.long_name:
308             return self.name
309         else:
310             return '{0} ({1})'.format(self.long_name, self.name)
311
312     def check(self):
313         if self._check_error:
314             raise self._check_error
315         try:
316             self._check_dependencies()
317             return self._check()
318         except DependencyError as e:
319             self._check_error = e  # cache for future calls
320             raise
321
322     def _check_dependencies(self):
323         for dependency in self.and_dependencies:
324             if not hasattr(dependency, 'check'):
325                 dependency = CHECKER[dependency]
326             try:
327                 dependency.check()
328             except DependencyError as e:
329                 raise DependencyError(
330                     checker=self,
331                     message=(
332                         'some dependencies for {0} were not satisfied'
333                         ).format(self.full_name()),
334                     causes=[e])
335         self.or_pass = None
336         or_errors = []
337         for dependency in self.or_dependencies:
338             if not hasattr(dependency, 'check'):
339                 dependency = CHECKER[dependency]
340             try:
341                 version = dependency.check()
342             except DependencyError as e:
343                 or_errors.append(e)
344             else:
345                 self.or_pass = {
346                     'dependency': dependency,
347                     'version': version,
348                     }
349                 break  # no need to test other dependencies
350         if self.or_dependencies and not self.or_pass:
351             raise DependencyError(
352                 checker=self,
353                 message=(
354                     '{0} requires at least one of the following dependencies'
355                     ).format(self.full_name()),
356                     causes=or_errors)
357
358     def _check(self):
359         version = self._get_version()
360         parsed_version = None
361         if hasattr(self, '_get_parsed_version'):
362             parsed_version = self._get_parsed_version()
363         if self.minimum_version:
364             self._check_version(version=version, parsed_version=parsed_version)
365         return version
366
367     def _get_version(self):
368         raise NotImplementedError(self)
369
370     def _minimum_version_string(self):
371         return self.version_delimiter.join(
372             str(part) for part in self.minimum_version)
373
374     def _check_version(self, version, parsed_version=None):
375         if not parsed_version:
376             parsed_version = self._parse_version(version=version)
377         if not parsed_version or parsed_version < self.minimum_version:
378             raise DependencyError(
379                 checker=self,
380                 message='outdated version of {0}: {1} (need >= {2})'.format(
381                     self.full_name(), version, self._minimum_version_string()))
382
383     def _parse_version(self, version):
384         if not version:
385             return None
386         parsed_version = []
387         for part in version.split(self.version_delimiter):
388             try:
389                 parsed_version.append(int(part))
390             except ValueError as e:
391                 raise DependencyError(
392                     checker=self,
393                     message=(
394                         'unparsable {0!r} in version {1} of {2}, (need >= {3})'
395                         ).format(
396                         part, version, self.full_name(),
397                         self._minimum_version_string()))# from e
398         return tuple(parsed_version)
399
400
401 class VirtualDependency (Dependency):
402     def _check(self):
403         return '{0} {1}'.format(
404             self.or_pass['dependency'].full_name(),
405             self.or_pass['version'])
406
407
408 class CommandDependency (Dependency):
409     exe_extension = _distutils_ccompiler.new_compiler().exe_extension
410
411     def __init__(self, command, paths=None, version_options=('--version',),
412                  stdin=None, version_regexp=None, version_stream='stdout',
413                  **kwargs):
414         if 'name' not in kwargs:
415             kwargs['name'] = command
416         super(CommandDependency, self).__init__(**kwargs)
417         self.command = command
418         self.paths = paths
419         self.version_options = version_options
420         self.stdin = None
421         if not version_regexp:
422             regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
423             version_regexp = _re.compile(regexp)
424         self.version_regexp = version_regexp
425         self.version_stream = version_stream
426
427     def _get_command_version_stream(self, command=None, stdin=None,
428                                     expect=(0,)):
429         if command is None:
430             command = self.command + (self.exe_extension or '')
431         if not stdin:
432             stdin = self.stdin
433         if stdin:
434             popen_stdin = _subprocess.PIPE
435         else:
436             popen_stdin = None
437         try:
438             p = _subprocess.Popen(
439                 [command] + list(self.version_options), stdin=popen_stdin,
440                 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
441                 universal_newlines=True)
442         except OSError as e:
443             raise DependencyError(
444                 checker=self,
445                 message="could not find '{0}' executable".format(command),
446                 )# from e
447         stdout,stderr = p.communicate(stdin)
448         status = p.wait()
449         if status not in expect:
450             lines = [
451                 "failed to execute: {0} {1}".format(
452                     command,
453                     ' '.join(_shlex.quote(arg)
454                              for arg in self.version_options)),
455                 'status: {0}'.format(status),
456                 ]
457             for name,string in [('stdout', stdout), ('stderr', stderr)]:
458                 if string:
459                     lines.extend([name + ':', string])
460             raise DependencyError(checker=self, message='\n'.join(lines))
461         for name,string in [('stdout', stdout), ('stderr', stderr)]:
462             if name == self.version_stream:
463                 if not string:
464                     raise DependencyError(
465                         checker=self,
466                         message='empty version stream on {0} for {1}'.format(
467                             self.version_stream, command))
468                 return string
469         raise NotImplementedError(self.version_stream)
470
471     def _get_version_stream(self, **kwargs):
472         paths = [self.command + (self.exe_extension or '')]
473         if self.exe_extension:
474             paths.append(self.command)  # also look at the extension-less path
475         if self.paths:
476             paths.extend(self.paths)
477         or_errors = []
478         for path in paths:
479             try:
480                 return self._get_command_version_stream(command=path, **kwargs)
481             except DependencyError as e:
482                 or_errors.append(e)
483         raise DependencyError(
484             checker=self,
485             message='errors finding {0} version'.format(
486                 self.full_name()),
487             causes=or_errors)
488
489     def _get_version(self):
490         version_stream = self._get_version_stream()
491         match = self.version_regexp.search(version_stream)
492         if not match:
493             raise DependencyError(
494                 checker=self,
495                 message='no version string in output:\n{0}'.format(
496                     version_stream))
497         return match.group(1)
498
499
500 class VersionPlistCommandDependency (CommandDependency):
501     """A command that doesn't support --version or equivalent options
502
503     On OS X, a command's executable may be hard to find, or not exist
504     in the PATH.  Work around that by looking up the version
505     information in the package's version.plist file.
506     """
507     def __init__(self, key='CFBundleShortVersionString', **kwargs):
508         super(VersionPlistCommandDependency, self).__init__(**kwargs)
509         self.key = key
510
511     def _get_command_version_stream(self, *args, **kwargs):
512         raise NotImplementedError()
513
514     def _get_version_stream(self, *args, **kwargs):
515         raise NotImplementedError()
516
517     @staticmethod
518     def _get_parent(root, element):
519         """Returns the parent of this element or None for the root element
520         """
521         for node in root.iter():
522             if element in node:
523                 return node
524         raise ValueError((root, element))
525
526     @classmethod
527     def _get_next(cls, root, element):
528         """Returns the following sibling of this element or None
529         """
530         parent = cls._get_parent(root=root, element=element)
531         siblings = iter(parent)
532         for node in siblings:
533             if node == element:
534                 try:
535                     return next(siblings)
536                 except StopIteration:
537                     return None
538         return None
539
540     def _get_version_from_plist(self, path):
541         """Parse the plist and return the value string for self.key
542         """
543         tree = _element_tree.parse(source=path)
544         data = {}
545         for key in tree.findall('.//key'):
546             value = self._get_next(root=tree, element=key)
547             if value.tag != 'string':
548                 raise ValueError((tree, key, value))
549             data[key.text] = value.text
550         return data[self.key]
551
552     def _get_version(self):
553         for path in self.paths:
554             if _os.path.exists(path):
555                 return self._get_version_from_plist(path=path)
556         raise DependencyError(
557             checker=self,
558             message=(
559                 'nothing exists at any of the expected paths for {0}:\n    {1}'
560                 ).format(
561                 self.full_name(),
562                 '\n    '.join(p for p in self.paths)))
563
564
565 class UserTaskDependency (Dependency):
566     "Prompt the user to complete a task and check for success"
567     def __init__(self, prompt, **kwargs):
568         super(UserTaskDependency, self).__init__(**kwargs)
569         self.prompt = prompt
570
571     def _check(self):
572         if _sys.version_info >= (3, ):
573             result = input(self.prompt)
574         else:  # Python 2.x
575             result = raw_input(self.prompt)
576         return self._check_result(result)
577
578     def _check_result(self, result):
579         raise NotImplementedError()
580
581
582 class EditorTaskDependency (UserTaskDependency):
583     def __init__(self, **kwargs):
584         self.path = _os.path.expanduser(_os.path.join(
585                 '~', 'swc-installation-test.txt'))
586         self.contents = 'Hello, world!'
587         super(EditorTaskDependency, self).__init__(
588             prompt=(
589                 'Open your favorite text editor and create the file\n'
590                 '  {0}\n'
591                 'containing the line:\n'
592                 '  {1}\n'
593                 'Press enter here after you have done this.\n'
594                 'You may remove the file after you have finished testing.'
595                 ).format(self.path, self.contents),
596             **kwargs)
597
598     def _check_result(self, result):
599         message = None
600         try:
601             with open(self.path, 'r') as f:
602                 contents = f.read()
603         except IOError as e:
604             raise DependencyError(
605                 checker=self,
606                 message='could not open {0!r}: {1}'.format(self.path, e)
607                 )# from e
608         if contents.strip() != self.contents:
609             raise DependencyError(
610                 checker=self,
611                 message=(
612                     'file contents ({0!r}) did not match the expected {1!r}'
613                     ).format(contents, self.contents))
614
615
616 class MakeDependency (CommandDependency):
617     makefile = '\n'.join([
618             'all:',
619             '\t@echo "MAKE_VERSION=$(MAKE_VERSION)"',
620             '\t@echo "MAKE=$(MAKE)"',
621             '',
622             ])
623
624     def _get_version(self):
625         try:
626             return super(MakeDependency, self)._get_version()
627         except DependencyError as e:
628             version_options = self.version_options
629             self.version_options = ['-f', '-']
630             try:
631                 stream = self._get_version_stream(stdin=self.makefile)
632                 info = {}
633                 for line in stream.splitlines():
634                     try:
635                         key,value = line.split('=', 1)
636                     except ValueError as ve:
637                         raise e# from NotImplementedError(stream)
638                     info[key] = value
639                 if info.get('MAKE_VERSION', None):
640                     return info['MAKE_VERSION']
641                 elif info.get('MAKE', None):
642                     return None
643                 raise e
644             finally:
645                 self.version_options = version_options
646
647
648 class EasyInstallDependency (CommandDependency):
649     def _get_version(self):
650         try:
651             return super(EasyInstallDependency, self)._get_version()
652         except DependencyError as e:
653             version_stream = self.version_stream
654             try:
655                 self.version_stream = 'stderr'
656                 stream = self._get_version_stream(expect=(1,))
657                 if 'option --version not recognized' in stream:
658                     return 'unknown (possibly Setuptools?)'
659             finally:
660                 self.version_stream = version_stream
661
662
663 class PythonDependency (Dependency):
664     def __init__(self, name='python', long_name='Python version',
665                  minimum_version=(2, 6), **kwargs):
666         super(PythonDependency, self).__init__(
667             name=name, long_name=long_name, minimum_version=minimum_version,
668             **kwargs)
669
670     def _get_version(self):
671         return _sys.version
672
673     def _get_parsed_version(self):
674         return _sys.version_info
675
676
677 class PythonPackageDependency (Dependency):
678     def __init__(self, package, **kwargs):
679         if 'name' not in kwargs:
680             kwargs['name'] = package
681         if 'and_dependencies' not in kwargs:
682             kwargs['and_dependencies'] = []
683         if 'python' not in kwargs['and_dependencies']:
684             kwargs['and_dependencies'].append('python')
685         super(PythonPackageDependency, self).__init__(**kwargs)
686         self.package = package
687
688     def _get_version(self):
689         package = self._get_package(self.package)
690         return self._get_version_from_package(package)
691
692     def _get_package(self, package):
693         try:
694             return _importlib.import_module(package)
695         except ImportError as e:
696             raise DependencyError(
697                 checker=self,
698                 message="could not import the '{0}' package for {1}".format(
699                     package, self.full_name()),
700                 )# from e
701
702     def _get_version_from_package(self, package):
703         try:
704             version = package.__version__
705         except AttributeError:
706             version = None
707         return version
708
709
710 class MercurialPythonPackage (PythonPackageDependency):
711     def _get_version(self):
712         try:  # mercurial >= 1.2
713             package = _importlib.import_module('mercurial.util')
714         except ImportError as e:  # mercurial <= 1.1.2
715             package = self._get_package('mercurial.version')
716             return package.get_version()
717         else:
718             return package.version()
719
720
721 class TornadoPythonPackage (PythonPackageDependency):
722     def _get_version_from_package(self, package):
723         return package.version
724
725     def _get_parsed_version(self):
726         package = self._get_package(self.package)
727         return package.version_info
728
729
730 class SQLitePythonPackage (PythonPackageDependency):
731     def _get_version_from_package(self, package):
732         return _sys.version
733
734     def _get_parsed_version(self):
735         return _sys.version_info
736
737
738 def _program_files_paths(*args):
739     "Utility for generating MS Windows search paths"
740     pf = _os.environ.get('ProgramFiles', '/usr/bin')
741     pfx86 = _os.environ.get('ProgramFiles(x86)', pf)
742     paths = [_os.path.join(pf, *args)]
743     if pfx86 != pf:
744         paths.append(_os.path.join(pfx86, *args))
745     return paths
746
747
748 CHECKER['python'] = PythonDependency()
749
750
751 for command,long_name,minimum_version,paths in [
752         ('sh', 'Bourne Shell', None, None),
753         ('ash', 'Almquist Shell', None, None),
754         ('bash', 'Bourne Again Shell', None, None),
755         ('csh', 'C Shell', None, None),
756         ('ksh', 'KornShell', None, None),
757         ('dash', 'Debian Almquist Shell', None, None),
758         ('tcsh', 'TENEX C Shell', None, None),
759         ('zsh', 'Z Shell', None, None),
760         ('git', 'Git', (1, 7, 0), None),
761         ('hg', 'Mercurial', (2, 0, 0), None),
762         ('EasyMercurial', None, (1, 3), None),
763         ('pip', None, None, None),
764         ('sqlite3', 'SQLite 3', None, None),
765         ('nosetests', 'Nose', (1, 0, 0), None),
766         ('ipython', 'IPython script', (1, 0), None),
767         ('emacs', 'Emacs', None, None),
768         ('xemacs', 'XEmacs', None, None),
769         ('vim', 'Vim', None, None),
770         ('vi', None, None, None),
771         ('nano', 'Nano', None, None),
772         ('gedit', None, None, None),
773         ('kate', 'Kate', None, None),
774         ('notepad++', 'Notepad++', None,
775          _program_files_paths('Notepad++', 'notepad++.exe')),
776         ('firefox', 'Firefox', None,
777          _program_files_paths('Mozilla Firefox', 'firefox.exe')),
778         ('google-chrome', 'Google Chrome', None,
779          _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe')
780          ),
781         ('chromium', 'Chromium', None, None),
782         ]:
783     if not long_name:
784         long_name = command
785     CHECKER[command] = CommandDependency(
786         command=command, paths=paths, long_name=long_name,
787         minimum_version=minimum_version)
788 del command, long_name, minimum_version, paths  # cleanup namespace
789
790
791 CHECKER['make'] = MakeDependency(command='make', minimum_version=None)
792
793
794 CHECKER['easy_install'] = EasyInstallDependency(
795     command='easy_install', long_name='Setuptools easy_install',
796     minimum_version=None)
797
798
799 CHECKER['py.test'] = CommandDependency(
800     command='py.test', version_stream='stderr',
801     minimum_version=None)
802
803
804 for paths,name,long_name in [
805         ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app',
806                         'Contents', 'version.plist')],
807          'sublime-text', 'Sublime Text'),
808         ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app',
809                         'Contents', 'version.plist')],
810          'textmate', 'TextMate'),
811         ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app',
812                         'Contents', 'version.plist')],
813          'textwrangler', 'TextWrangler'),
814         ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app',
815                         'Contents', 'version.plist')],
816          'safari', 'Safari'),
817         ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app',
818                         'Contents', 'version.plist'),  # OS X >=1.7
819           _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app',
820                         'Contents', 'version.plist'),  # OS X 1.6,
821           ],
822          'xcode', 'Xcode'),
823         ]:
824     if not long_name:
825         long_name = name
826     CHECKER[name] = VersionPlistCommandDependency(
827         command=None, paths=paths, name=name, long_name=long_name)
828 del paths, name, long_name  # cleanup namespace
829
830
831 for package,name,long_name,minimum_version,and_dependencies in [
832         ('nose', None, 'Nose Python package',
833          CHECKER['nosetests'].minimum_version, None),
834         ('pytest', None, 'pytest Python package',
835          CHECKER['py.test'].minimum_version, None),
836         ('jinja2', 'jinja', 'Jinja', (2, 6), None),
837         ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None),
838         ('IPython', None, 'IPython Python package',
839          CHECKER['ipython'].minimum_version, [
840              'jinja',
841              'tornado',
842              'pyzmq',
843              VirtualDependency(
844                  name='virtual-browser-ipython',
845                  long_name='IPython-compatible web browser',
846                  or_dependencies=[
847                      CommandDependency(
848                          command=CHECKER['firefox'].command,
849                          paths=CHECKER['firefox'].paths,
850                          name='{0}-for-ipython'.format(
851                              CHECKER['firefox'].name),
852                          long_name='{0} for IPython'.format(
853                              CHECKER['firefox'].long_name),
854                          minimum_version=(6, 0)),
855                      CommandDependency(
856                          command=CHECKER['google-chrome'].command,
857                          paths=CHECKER['google-chrome'].paths,
858                          name='{0}-for-ipython'.format(
859                              CHECKER['google-chrome'].name),
860                          long_name='{0} for IPython'.format(
861                              CHECKER['google-chrome'].long_name),
862                          minimum_version=(13, 0)),
863                      CommandDependency(
864                          command=CHECKER['chromium'].command,
865                          paths=CHECKER['chromium'].paths,
866                          name='{0}-for-ipython'.format(
867                              CHECKER['chromium'].name),
868                          long_name='{0} for IPython'.format(
869                              CHECKER['chromium'].long_name),
870                          minimum_version=(13, 0)),
871                      VersionPlistCommandDependency(
872                          command=CHECKER['safari'].command,
873                          paths=CHECKER['safari'].paths,
874                          key=CHECKER['safari'].key,
875                          name='{0}-for-ipython'.format(
876                              CHECKER['safari'].name),
877                          long_name='{0} for IPython'.format(
878                              CHECKER['safari'].long_name),
879                          minimum_version=(5, 0)),
880                  ]),
881          ]),
882         ('argparse', None, 'Argparse', None, None),
883         ('numpy', None, 'NumPy', None, None),
884         ('scipy', None, 'SciPy', None, None),
885         ('matplotlib', None, 'Matplotlib', None, None),
886         ('pandas', None, 'Pandas', (0, 8), None),
887         ('sympy', None, 'SymPy', None, None),
888         ('Cython', None, None, None, None),
889         ('networkx', None, 'NetworkX', None, None),
890         ('mayavi.mlab', None, 'MayaVi', None, None),
891         ('setuptools', None, 'Setuptools', None, None),
892         ]:
893     if not name:
894         name = package
895     if not long_name:
896         long_name = name
897     kwargs = {}
898     if and_dependencies:
899         kwargs['and_dependencies'] = and_dependencies
900     CHECKER[name] = PythonPackageDependency(
901         package=package, name=name, long_name=long_name,
902         minimum_version=minimum_version, **kwargs)
903 # cleanup namespace
904 del package, name, long_name, minimum_version, and_dependencies, kwargs
905
906
907 CHECKER['mercurial'] = MercurialPythonPackage(
908     package='mercurial.util', name='mercurial',
909     long_name='Mercurial Python package',
910     minimum_version=CHECKER['hg'].minimum_version)
911
912
913 CHECKER['tornado'] = TornadoPythonPackage(
914     package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0))
915
916
917 CHECKER['sqlite3-python'] = SQLitePythonPackage(
918     package='sqlite3', name='sqlite3-python',
919     long_name='SQLite Python package',
920     minimum_version=CHECKER['sqlite3'].minimum_version)
921
922
923 CHECKER['other-editor'] = EditorTaskDependency(
924     name='other-editor', long_name='')
925
926
927 for name,long_name,dependencies in [
928         ('virtual-shell', 'command line shell', (
929             'bash',
930             'dash',
931             'ash',
932             'zsh',
933             'ksh',
934             'csh',
935             'tcsh',
936             'sh',
937             )),
938         ('virtual-editor', 'text/code editor', (
939             'emacs',
940             'xemacs',
941             'vim',
942             'vi',
943             'nano',
944             'gedit',
945             'kate',
946             'notepad++',
947             'sublime-text',
948             'textmate',
949             'textwrangler',
950             'other-editor',  # last because it requires user interaction
951             )),
952         ('virtual-browser', 'web browser', (
953             'firefox',
954             'google-chrome',
955             'chromium',
956             'safari',
957             )),
958         ('virtual-pypi-installer', 'PyPI installer', (
959             'pip',
960             'easy_install',
961             )),
962         ]:
963     CHECKER[name] = VirtualDependency(
964         name=name, long_name=long_name, or_dependencies=dependencies)
965 del name, long_name, dependencies  # cleanup namespace
966
967
968 def _print_info(key, value, indent=19):
969     print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value))
970
971 def print_system_info():
972     print("If you do not understand why the above failures occurred,")
973     print("copy and send the *entire* output (all info above and summary")
974     print("below) to the instructor for help.")
975     print()
976     print('==================')
977     print('System information')
978     print('==================')
979     _print_info('os.name', _os.name)
980     _print_info('os.uname', _platform.uname())
981     _print_info('platform', _sys.platform)
982     _print_info('platform+', _platform.platform())
983     for pversion in (
984             'linux_distribution',
985             'mac_ver',
986             'win32_ver',
987             ):
988         value = getattr(_platform, pversion)()
989         if value[0]:
990             _print_info(pversion, value)
991     _print_info('prefix', _sys.prefix)
992     _print_info('exec_prefix', _sys.exec_prefix)
993     _print_info('executable', _sys.executable)
994     _print_info('version_info', _sys.version_info)
995     _print_info('version', _sys.version)
996     _print_info('environment', '')
997     for key,value in sorted(_os.environ.items()):
998         print('  {0}={1}'.format(key, value))
999     print('==================')
1000
1001 def print_suggestions(instructor_fallback=True):
1002     print()
1003     print('For suggestions on installing missing packages, see')
1004     print('http://software-carpentry.org/setup/')
1005     print('')
1006     print('For instructings on installing a particular package,')
1007     print('see the failure message for that package printed above.')
1008     if instructor_fallback:
1009         print('')
1010         print('For help, email the *entire* output of this script to')
1011         print('your instructor.')
1012
1013
1014 if __name__ == '__main__':
1015     import optparse as _optparse
1016
1017     parser = _optparse.OptionParser(usage='%prog [options] [check...]')
1018     epilog = __doc__
1019     parser.format_epilog = lambda formatter: '\n' + epilog
1020     parser.add_option(
1021         '-v', '--verbose', action='store_true',
1022         help=('print additional information to help troubleshoot '
1023               'installation issues'))
1024     options,args = parser.parse_args()
1025     try:
1026         passed = check(args)
1027     except InvalidCheck as e:
1028         print("I don't know how to check for {0!r}".format(e.check))
1029         print('I do know how to check for:')
1030         for key,checker in sorted(CHECKER.items()):
1031             if checker.long_name != checker.name:
1032                 print('  {0} {1}({2})'.format(
1033                         key, ' '*(20-len(key)), checker.long_name))
1034             else:
1035                 print('  {0}'.format(key))
1036         _sys.exit(1)
1037     if not passed:
1038         if options.verbose:
1039             print()
1040             print_system_info()
1041             print_suggestions(instructor_fallback=True)
1042         _sys.exit(1)