|
#!/usr/bin/env python |
|
|
|
"""Test script to check for required functionality. |
|
|
|
Execute this code at the command line by typing: |
|
|
|
python swc-installation-test-2.py |
|
|
|
Run the script and follow the instructions it prints at the end. |
|
|
|
This script requires at least Python 2.6. You can check the version |
|
of Python that you have installed with 'swc-installation-test-1.py'. |
|
|
|
By default, this script will test for all the dependencies your |
|
instructor thinks you need. If you want to test for a different set |
|
of packages, you can list them on the command line. For example: |
|
|
|
python swc-installation-test-2.py git virtual-editor |
|
|
|
This is useful if the original test told you to install a more recent |
|
version of a particular dependency, and you just want to re-test that |
|
dependency. |
|
""" |
|
|
|
# Some details about the implementation: |
|
|
|
# The dependencies are divided into a hierarchy of classes rooted on |
|
# Dependency class. You can refer to the code to see which package |
|
# comes under which type of dependency. |
|
|
|
# The CHECKER dictionary stores information about all the dependencies |
|
# and CHECKS stores list of the dependencies which are to be checked in |
|
# the current workshop. |
|
|
|
# In the "__name__ == '__main__'" block, we launch all the checks with |
|
# check() function, which prints information about the tests as they run |
|
# and details about the failures after the tests complete. In case of |
|
# failure, the functions print_system_info() and print_suggestions() |
|
# are called after this, where the former prints information about the |
|
# user's system for debugging purposes while the latter prints some |
|
# suggestions to follow. |
|
|
|
|
|
from __future__ import print_function # for Python 2.6 compatibility |
|
|
|
import distutils.ccompiler as _distutils_ccompiler |
|
import fnmatch as _fnmatch |
|
try: # Python 2.7 and 3.x |
|
import importlib as _importlib |
|
except ImportError: # Python 2.6 and earlier |
|
class _Importlib (object): |
|
"""Minimal workarounds for functions we need |
|
""" |
|
@staticmethod |
|
def import_module(name): |
|
module = __import__(name) |
|
for n in name.split('.')[1:]: |
|
module = getattr(module, n) |
|
return module |
|
_importlib = _Importlib() |
|
import logging as _logging |
|
import os as _os |
|
import platform as _platform |
|
import re as _re |
|
import shlex as _shlex |
|
import subprocess as _subprocess |
|
import sys as _sys |
|
try: # Python 3.x |
|
import urllib.parse as _urllib_parse |
|
except ImportError: # Python 2.x |
|
import urllib as _urllib_parse # for quote() |
|
import xml.etree.ElementTree as _element_tree |
|
|
|
|
|
if not hasattr(_shlex, 'quote'): # Python versions older than 3.3 |
|
# Use the undocumented pipes.quote() |
|
import pipes as _pipes |
|
_shlex.quote = _pipes.quote |
|
|
|
|
|
__version__ = '0.1' |
|
|
|
# Comment out any entries you don't need |
|
CHECKS = [ |
|
# Shell |
|
'virtual-shell', |
|
# Editors |
|
'virtual-editor', |
|
# Browsers |
|
'virtual-browser', |
|
# Version control |
|
'git', |
|
# 'hg', # Command line tool |
|
#'mercurial', # Python package |
|
# 'EasyMercurial', |
|
# Build tools and packaging |
|
# 'make', |
|
'virtual-pypi-installer', |
|
'setuptools', |
|
#'xcode', |
|
# Testing |
|
'nosetests', # Command line tool |
|
'nose', # Python package |
|
'py.test', # Command line tool |
|
'pytest', # Python package |
|
# SQL |
|
'sqlite3', # Command line tool |
|
'sqlite3-python', # Python package |
|
# Python |
|
'python', |
|
'ipython', # Command line tool |
|
'IPython', # Python package |
|
'argparse', # Useful for utility scripts |
|
'numpy', |
|
'scipy', |
|
'matplotlib', |
|
'pandas', |
|
#'sympy', |
|
#'Cython', |
|
#'networkx', |
|
#'mayavi.mlab', |
|
] |
|
|
|
CHECKER = {} |
|
|
|
_ROOT_PATH = _os.sep |
|
if _platform.system() == 'win32': |
|
_ROOT_PATH = 'c:\\' |
|
|
|
|
|
class InvalidCheck (KeyError): |
|
def __init__(self, check): |
|
super(InvalidCheck, self).__init__(check) |
|
self.check = check |
|
|
|
def __str__(self): |
|
return self.check |
|
|
|
|
|
class DependencyError (Exception): |
|
_default_url = 'http://software-carpentry.org/setup/' |
|
_setup_urls = { # (system, version, package) glob pairs |
|
('*', '*', 'Cython'): 'http://docs.cython.org/src/quickstart/install.html', |
|
('Linux', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-linux', |
|
('Darwin', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-mac', |
|
('Windows', '*', 'EasyMercurial'): 'http://easyhg.org/download.html#download-windows', |
|
('*', '*', 'EasyMercurial'): 'http://easyhg.org/download.html', |
|
('*', '*', 'argparse'): 'https://pypi.python.org/pypi/argparse#installation', |
|
('*', '*', 'ash'): 'http://www.in-ulm.de/~mascheck/various/ash/', |
|
('*', '*', 'bash'): 'http://www.gnu.org/software/bash/manual/html_node/Basic-Installation.html#Basic-Installation', |
|
('Linux', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/LinuxBuildInstructions', |
|
('Darwin', '*', 'chromium'): 'http://code.google.com/p/chromium/wiki/MacBuildInstructions', |
|
('Windows', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos/build-instructions-windows', |
|
('*', '*', 'chromium'): 'http://www.chromium.org/developers/how-tos', |
|
('Windows', '*', 'emacs'): 'http://www.gnu.org/software/emacs/windows/Installing-Emacs.html', |
|
('*', '*', 'emacs'): 'http://www.gnu.org/software/emacs/#Obtaining', |
|
('*', '*', 'firefox'): 'http://www.mozilla.org/en-US/firefox/new/', |
|
('Linux', '*', 'gedit'): 'http://www.linuxfromscratch.org/blfs/view/svn/gnome/gedit.html', |
|
('*', '*', 'git'): 'http://git-scm.com/downloads', |
|
('*', '*', 'google-chrome'): 'https://www.google.com/intl/en/chrome/browser/', |
|
('*', '*', 'hg'): 'http://mercurial.selenic.com/', |
|
('*', '*', 'mercurial'): 'http://mercurial.selenic.com/', |
|
('*', '*', 'IPython'): 'http://ipython.org/install.html', |
|
('*', '*', 'ipython'): 'http://ipython.org/install.html', |
|
('*', '*', 'jinja'): 'http://jinja.pocoo.org/docs/intro/#installation', |
|
('*', '*', 'kate'): 'http://kate-editor.org/get-it/', |
|
('*', '*', 'make'): 'http://www.gnu.org/software/make/', |
|
('Darwin', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#building-on-osx', |
|
('Windows', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing-on-windows', |
|
('*', '*', 'matplotlib'): 'http://matplotlib.org/users/installing.html#installing', |
|
('*', '*', 'mayavi.mlab'): 'http://docs.enthought.com/mayavi/mayavi/installation.html', |
|
('*', '*', 'nano'): 'http://www.nano-editor.org/dist/latest/faq.html#3', |
|
('*', '*', 'networkx'): 'http://networkx.github.com/documentation/latest/install.html#installing', |
|
('*', '*', 'nose'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start', |
|
('*', '*', 'nosetests'): 'https://nose.readthedocs.org/en/latest/#installation-and-quick-start', |
|
('*', '*', 'notepad++'): 'http://notepad-plus-plus.org/download/v6.3.html', |
|
('*', '*', 'numpy'): 'http://docs.scipy.org/doc/numpy/user/install.html', |
|
('*', '*', 'pandas'): 'http://pandas.pydata.org/pandas-docs/stable/install.html', |
|
('*', '*', 'pip'): 'http://www.pip-installer.org/en/latest/installing.html', |
|
('*', '*', 'pytest'): 'http://pytest.org/latest/getting-started.html', |
|
('*', '*', 'python'): 'http://www.python.org/download/releases/2.7.3/#download', |
|
('*', '*', 'pyzmq'): 'https://github.com/zeromq/pyzmq/wiki/Building-and-Installing-PyZMQ', |
|
('*', '*', 'py.test'): 'http://pytest.org/latest/getting-started.html', |
|
('Linux', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Linux', |
|
('Darwin', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Mac_OS_X', |
|
('Windows', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy/Windows', |
|
('*', '*', 'scipy'): 'http://www.scipy.org/Installing_SciPy', |
|
('*', '*', 'setuptools'): 'https://pypi.python.org/pypi/setuptools#installation-instructions', |
|
('*', '*', 'sqlite3'): 'http://www.sqlite.org/download.html', |
|
('*', '*', 'sublime-text'): 'http://www.sublimetext.com/2', |
|
('*', '*', 'sympy'): 'http://docs.sympy.org/dev/install.html', |
|
('Darwin', '*', 'textmate'): 'http://macromates.com/', |
|
('Darwin', '*', 'textwrangler'): 'http://www.barebones.com/products/textwrangler/download.html', |
|
('*', '*', 'tornado'): 'http://www.tornadoweb.org/', |
|
('*', '*', 'vim'): 'http://www.vim.org/download.php', |
|
('Darwin', '*', 'xcode'): 'https://developer.apple.com/xcode/', |
|
('*', '*', 'xemacs'): 'http://www.us.xemacs.org/Install/', |
|
('*', '*', 'zsh'): 'http://www.zsh.org/', |
|
} |
|
|
|
def _get_message(self): |
|
return self._message |
|
def _set_message(self, message): |
|
self._message = message |
|
message = property(_get_message, _set_message) |
|
|
|
def __init__(self, checker, message, causes=None): |
|
super(DependencyError, self).__init__(message) |
|
self.checker = checker |
|
self.message = message |
|
if causes is None: |
|
causes = [] |
|
self.causes = causes |
|
|
|
def get_url(self): |
|
system = _platform.system() |
|
version = None |
|
for pversion in ( |
|
'linux_distribution', |
|
'mac_ver', |
|
'win32_ver', |
|
): |
|
value = getattr(_platform, pversion)() |
|
if value[0]: |
|
version = value[0] |
|
break |
|
package = self.checker.name |
|
for (s,v,p),url in self._setup_urls.items(): |
|
if (_fnmatch.fnmatch(system, s) and |
|
_fnmatch.fnmatch(version, v) and |
|
_fnmatch.fnmatch(package, p)): |
|
return url |
|
return self._default_url |
|
|
|
def __str__(self): |
|
url = self.get_url() |
|
lines = [ |
|
'check for {0} failed:'.format(self.checker.full_name()), |
|
' ' + self.message, |
|
' For instructions on installing an up-to-date version, see', |
|
' ' + url, |
|
] |
|
if self.causes: |
|
lines.append(' causes:') |
|
for cause in self.causes: |
|
lines.extend(' ' + line for line in str(cause).splitlines()) |
|
return '\n'.join(lines) |
|
|
|
|
|
def check(checks=None): |
|
successes = [] |
|
failures = [] |
|
if not checks: |
|
checks = CHECKS |
|
for check in checks: |
|
try: |
|
checker = CHECKER[check] |
|
except KeyError as e: |
|
raise InvalidCheck(check)# from e |
|
_sys.stdout.write('check {0}...\t'.format(checker.full_name())) |
|
try: |
|
version = checker.check() |
|
except DependencyError as e: |
|
failures.append(e) |
|
_sys.stdout.write('fail\n') |
|
else: |
|
_sys.stdout.write('pass\n') |
|
successes.append((checker, version)) |
|
if successes: |
|
print('\nSuccesses:\n') |
|
for checker,version in successes: |
|
print('{0} {1}'.format( |
|
checker.full_name(), |
|
version or 'unknown')) |
|
if failures: |
|
print('\nFailures:') |
|
printed = [] |
|
for failure in failures: |
|
if failure not in printed: |
|
print() |
|
print(failure) |
|
printed.append(failure) |
|
return False |
|
return True |
|
|
|
|
|
class Dependency (object): |
|
def __init__(self, name, long_name=None, minimum_version=None, |
|
version_delimiter='.', and_dependencies=None, |
|
or_dependencies=None): |
|
self.name = name |
|
self.long_name = long_name or name |
|
self.minimum_version = minimum_version |
|
self.version_delimiter = version_delimiter |
|
if not and_dependencies: |
|
and_dependencies = [] |
|
self.and_dependencies = and_dependencies |
|
if not or_dependencies: |
|
or_dependencies = [] |
|
self.or_dependencies = or_dependencies |
|
self._check_error = None |
|
|
|
def __str__(self): |
|
return '<{0} {1}>'.format(type(self).__name__, self.name) |
|
|
|
def full_name(self): |
|
if self.name == self.long_name: |
|
return self.name |
|
else: |
|
return '{0} ({1})'.format(self.long_name, self.name) |
|
|
|
def check(self): |
|
if self._check_error: |
|
raise self._check_error |
|
try: |
|
self._check_dependencies() |
|
return self._check() |
|
except DependencyError as e: |
|
self._check_error = e # cache for future calls |
|
raise |
|
|
|
def _check_dependencies(self): |
|
for dependency in self.and_dependencies: |
|
if not hasattr(dependency, 'check'): |
|
dependency = CHECKER[dependency] |
|
try: |
|
dependency.check() |
|
except DependencyError as e: |
|
raise DependencyError( |
|
checker=self, |
|
message=( |
|
'some dependencies for {0} were not satisfied' |
|
).format(self.full_name()), |
|
causes=[e]) |
|
self.or_pass = None |
|
or_errors = [] |
|
for dependency in self.or_dependencies: |
|
if not hasattr(dependency, 'check'): |
|
dependency = CHECKER[dependency] |
|
try: |
|
version = dependency.check() |
|
except DependencyError as e: |
|
or_errors.append(e) |
|
else: |
|
self.or_pass = { |
|
'dependency': dependency, |
|
'version': version, |
|
} |
|
break # no need to test other dependencies |
|
if self.or_dependencies and not self.or_pass: |
|
raise DependencyError( |
|
checker=self, |
|
message=( |
|
'{0} requires at least one of the following dependencies' |
|
).format(self.full_name()), |
|
causes=or_errors) |
|
|
|
def _check(self): |
|
version = self._get_version() |
|
parsed_version = None |
|
if hasattr(self, '_get_parsed_version'): |
|
parsed_version = self._get_parsed_version() |
|
if self.minimum_version: |
|
self._check_version(version=version, parsed_version=parsed_version) |
|
return version |
|
|
|
def _get_version(self): |
|
raise NotImplementedError(self) |
|
|
|
def _minimum_version_string(self): |
|
return self.version_delimiter.join( |
|
str(part) for part in self.minimum_version) |
|
|
|
def _check_version(self, version, parsed_version=None): |
|
if not parsed_version: |
|
parsed_version = self._parse_version(version=version) |
|
if not parsed_version or parsed_version < self.minimum_version: |
|
raise DependencyError( |
|
checker=self, |
|
message='outdated version of {0}: {1} (need >= {2})'.format( |
|
self.full_name(), version, self._minimum_version_string())) |
|
|
|
def _parse_version(self, version): |
|
if not version: |
|
return None |
|
parsed_version = [] |
|
for part in version.split(self.version_delimiter): |
|
try: |
|
parsed_version.append(int(part)) |
|
except ValueError as e: |
|
raise DependencyError( |
|
checker=self, |
|
message=( |
|
'unparsable {0!r} in version {1} of {2}, (need >= {3})' |
|
).format( |
|
part, version, self.full_name(), |
|
self._minimum_version_string()))# from e |
|
return tuple(parsed_version) |
|
|
|
|
|
class VirtualDependency (Dependency): |
|
def _check(self): |
|
return '{0} {1}'.format( |
|
self.or_pass['dependency'].full_name(), |
|
self.or_pass['version']) |
|
|
|
|
|
class CommandDependency (Dependency): |
|
exe_extension = _distutils_ccompiler.new_compiler().exe_extension |
|
|
|
def __init__(self, command, paths=None, version_options=('--version',), |
|
stdin=None, version_regexp=None, version_stream='stdout', |
|
**kwargs): |
|
if 'name' not in kwargs: |
|
kwargs['name'] = command |
|
super(CommandDependency, self).__init__(**kwargs) |
|
self.command = command |
|
self.paths = paths |
|
self.version_options = version_options |
|
self.stdin = None |
|
if not version_regexp: |
|
regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter) |
|
version_regexp = _re.compile(regexp) |
|
self.version_regexp = version_regexp |
|
self.version_stream = version_stream |
|
|
|
def _get_command_version_stream(self, command=None, stdin=None, |
|
expect=(0,)): |
|
if command is None: |
|
command = self.command + (self.exe_extension or '') |
|
if not stdin: |
|
stdin = self.stdin |
|
if stdin: |
|
popen_stdin = _subprocess.PIPE |
|
else: |
|
popen_stdin = None |
|
try: |
|
p = _subprocess.Popen( |
|
[command] + list(self.version_options), stdin=popen_stdin, |
|
stdout=_subprocess.PIPE, stderr=_subprocess.PIPE, |
|
universal_newlines=True) |
|
except OSError as e: |
|
raise DependencyError( |
|
checker=self, |
|
message="could not find '{0}' executable".format(command), |
|
)# from e |
|
stdout,stderr = p.communicate(stdin) |
|
status = p.wait() |
|
if status not in expect: |
|
lines = [ |
|
"failed to execute: {0} {1}".format( |
|
command, |
|
' '.join(_shlex.quote(arg) |
|
for arg in self.version_options)), |
|
'status: {0}'.format(status), |
|
] |
|
for name,string in [('stdout', stdout), ('stderr', stderr)]: |
|
if string: |
|
lines.extend([name + ':', string]) |
|
raise DependencyError(checker=self, message='\n'.join(lines)) |
|
for name,string in [('stdout', stdout), ('stderr', stderr)]: |
|
if name == self.version_stream: |
|
if not string: |
|
raise DependencyError( |
|
checker=self, |
|
message='empty version stream on {0} for {1}'.format( |
|
self.version_stream, command)) |
|
return string |
|
raise NotImplementedError(self.version_stream) |
|
|
|
def _get_version_stream(self, **kwargs): |
|
paths = [self.command + (self.exe_extension or '')] |
|
if self.exe_extension: |
|
paths.append(self.command) # also look at the extension-less path |
|
if self.paths: |
|
paths.extend(self.paths) |
|
or_errors = [] |
|
for path in paths: |
|
try: |
|
return self._get_command_version_stream(command=path, **kwargs) |
|
except DependencyError as e: |
|
or_errors.append(e) |
|
raise DependencyError( |
|
checker=self, |
|
message='errors finding {0} version'.format( |
|
self.full_name()), |
|
causes=or_errors) |
|
|
|
def _get_version(self): |
|
version_stream = self._get_version_stream() |
|
match = self.version_regexp.search(version_stream) |
|
if not match: |
|
raise DependencyError( |
|
checker=self, |
|
message='no version string in output:\n{0}'.format( |
|
version_stream)) |
|
return match.group(1) |
|
|
|
|
|
class VersionPlistCommandDependency (CommandDependency): |
|
"""A command that doesn't support --version or equivalent options |
|
|
|
On OS X, a command's executable may be hard to find, or not exist |
|
in the PATH. Work around that by looking up the version |
|
information in the package's version.plist file. |
|
""" |
|
def __init__(self, key='CFBundleShortVersionString', **kwargs): |
|
super(VersionPlistCommandDependency, self).__init__(**kwargs) |
|
self.key = key |
|
|
|
def _get_command_version_stream(self, *args, **kwargs): |
|
raise NotImplementedError() |
|
|
|
def _get_version_stream(self, *args, **kwargs): |
|
raise NotImplementedError() |
|
|
|
@staticmethod |
|
def _get_parent(root, element): |
|
"""Returns the parent of this element or None for the root element |
|
""" |
|
for node in root.iter(): |
|
if element in node: |
|
return node |
|
raise ValueError((root, element)) |
|
|
|
@classmethod |
|
def _get_next(cls, root, element): |
|
"""Returns the following sibling of this element or None |
|
""" |
|
parent = cls._get_parent(root=root, element=element) |
|
siblings = iter(parent) |
|
for node in siblings: |
|
if node == element: |
|
try: |
|
return next(siblings) |
|
except StopIteration: |
|
return None |
|
return None |
|
|
|
def _get_version_from_plist(self, path): |
|
"""Parse the plist and return the value string for self.key |
|
""" |
|
tree = _element_tree.parse(source=path) |
|
data = {} |
|
for key in tree.findall('.//key'): |
|
value = self._get_next(root=tree, element=key) |
|
if value.tag != 'string': |
|
raise ValueError((tree, key, value)) |
|
data[key.text] = value.text |
|
return data[self.key] |
|
|
|
def _get_version(self): |
|
for path in self.paths: |
|
if _os.path.exists(path): |
|
return self._get_version_from_plist(path=path) |
|
raise DependencyError( |
|
checker=self, |
|
message=( |
|
'nothing exists at any of the expected paths for {0}:\n {1}' |
|
).format( |
|
self.full_name(), |
|
'\n '.join(p for p in self.paths))) |
|
|
|
|
|
class UserTaskDependency (Dependency): |
|
"Prompt the user to complete a task and check for success" |
|
def __init__(self, prompt, **kwargs): |
|
super(UserTaskDependency, self).__init__(**kwargs) |
|
self.prompt = prompt |
|
|
|
def _check(self): |
|
if _sys.version_info >= (3, ): |
|
result = input(self.prompt) |
|
else: # Python 2.x |
|
result = raw_input(self.prompt) |
|
return self._check_result(result) |
|
|
|
def _check_result(self, result): |
|
raise NotImplementedError() |
|
|
|
|
|
class EditorTaskDependency (UserTaskDependency): |
|
def __init__(self, **kwargs): |
|
self.path = _os.path.expanduser(_os.path.join( |
|
'~', 'swc-installation-test.txt')) |
|
self.contents = 'Hello, world!' |
|
super(EditorTaskDependency, self).__init__( |
|
prompt=( |
|
'Open your favorite text editor and create the file\n' |
|
' {0}\n' |
|
'containing the line:\n' |
|
' {1}\n' |
|
'Press enter here after you have done this.\n' |
|
'You may remove the file after you have finished testing.' |
|
).format(self.path, self.contents), |
|
**kwargs) |
|
|
|
def _check_result(self, result): |
|
message = None |
|
try: |
|
with open(self.path, 'r') as f: |
|
contents = f.read() |
|
except IOError as e: |
|
raise DependencyError( |
|
checker=self, |
|
message='could not open {0!r}: {1}'.format(self.path, e) |
|
)# from e |
|
if contents.strip() != self.contents: |
|
raise DependencyError( |
|
checker=self, |
|
message=( |
|
'file contents ({0!r}) did not match the expected {1!r}' |
|
).format(contents, self.contents)) |
|
|
|
|
|
class MakeDependency (CommandDependency): |
|
makefile = '\n'.join([ |
|
'all:', |
|
'\t@echo "MAKE_VERSION=$(MAKE_VERSION)"', |
|
'\t@echo "MAKE=$(MAKE)"', |
|
'', |
|
]) |
|
|
|
def _get_version(self): |
|
try: |
|
return super(MakeDependency, self)._get_version() |
|
except DependencyError as e: |
|
version_options = self.version_options |
|
self.version_options = ['-f', '-'] |
|
try: |
|
stream = self._get_version_stream(stdin=self.makefile) |
|
info = {} |
|
for line in stream.splitlines(): |
|
try: |
|
key,value = line.split('=', 1) |
|
except ValueError as ve: |
|
raise e# from NotImplementedError(stream) |
|
info[key] = value |
|
if info.get('MAKE_VERSION', None): |
|
return info['MAKE_VERSION'] |
|
elif info.get('MAKE', None): |
|
return None |
|
raise e |
|
finally: |
|
self.version_options = version_options |
|
|
|
|
|
class EasyInstallDependency (CommandDependency): |
|
def _get_version(self): |
|
try: |
|
return super(EasyInstallDependency, self)._get_version() |
|
except DependencyError as e: |
|
version_stream = self.version_stream |
|
try: |
|
self.version_stream = 'stderr' |
|
stream = self._get_version_stream(expect=(1,)) |
|
if 'option --version not recognized' in stream: |
|
return 'unknown (possibly Setuptools?)' |
|
finally: |
|
self.version_stream = version_stream |
|
|
|
|
|
class PythonDependency (Dependency): |
|
def __init__(self, name='python', long_name='Python version', |
|
minimum_version=(2, 6), **kwargs): |
|
super(PythonDependency, self).__init__( |
|
name=name, long_name=long_name, minimum_version=minimum_version, |
|
**kwargs) |
|
|
|
def _get_version(self): |
|
return _sys.version |
|
|
|
def _get_parsed_version(self): |
|
return _sys.version_info |
|
|
|
|
|
class PythonPackageDependency (Dependency): |
|
def __init__(self, package, **kwargs): |
|
if 'name' not in kwargs: |
|
kwargs['name'] = package |
|
if 'and_dependencies' not in kwargs: |
|
kwargs['and_dependencies'] = [] |
|
if 'python' not in kwargs['and_dependencies']: |
|
kwargs['and_dependencies'].append('python') |
|
super(PythonPackageDependency, self).__init__(**kwargs) |
|
self.package = package |
|
|
|
def _get_version(self): |
|
package = self._get_package(self.package) |
|
return self._get_version_from_package(package) |
|
|
|
def _get_package(self, package): |
|
try: |
|
return _importlib.import_module(package) |
|
except ImportError as e: |
|
raise DependencyError( |
|
checker=self, |
|
message="could not import the '{0}' package for {1}".format( |
|
package, self.full_name()), |
|
)# from e |
|
|
|
def _get_version_from_package(self, package): |
|
try: |
|
version = package.__version__ |
|
except AttributeError: |
|
version = None |
|
return version |
|
|
|
|
|
class MercurialPythonPackage (PythonPackageDependency): |
|
def _get_version(self): |
|
try: # mercurial >= 1.2 |
|
package = _importlib.import_module('mercurial.util') |
|
except ImportError as e: # mercurial <= 1.1.2 |
|
package = self._get_package('mercurial.version') |
|
return package.get_version() |
|
else: |
|
return package.version() |
|
|
|
|
|
class TornadoPythonPackage (PythonPackageDependency): |
|
def _get_version_from_package(self, package): |
|
return package.version |
|
|
|
def _get_parsed_version(self): |
|
package = self._get_package(self.package) |
|
return package.version_info |
|
|
|
|
|
class SQLitePythonPackage (PythonPackageDependency): |
|
def _get_version_from_package(self, package): |
|
return _sys.version |
|
|
|
def _get_parsed_version(self): |
|
return _sys.version_info |
|
|
|
|
|
def _program_files_paths(*args): |
|
"Utility for generating MS Windows search paths" |
|
pf = _os.environ.get('ProgramFiles', '/usr/bin') |
|
pfx86 = _os.environ.get('ProgramFiles(x86)', pf) |
|
paths = [_os.path.join(pf, *args)] |
|
if pfx86 != pf: |
|
paths.append(_os.path.join(pfx86, *args)) |
|
return paths |
|
|
|
|
|
CHECKER['python'] = PythonDependency() |
|
|
|
|
|
for command,long_name,minimum_version,paths in [ |
|
('sh', 'Bourne Shell', None, None), |
|
('ash', 'Almquist Shell', None, None), |
|
('bash', 'Bourne Again Shell', None, None), |
|
('csh', 'C Shell', None, None), |
|
('ksh', 'KornShell', None, None), |
|
('dash', 'Debian Almquist Shell', None, None), |
|
('tcsh', 'TENEX C Shell', None, None), |
|
('zsh', 'Z Shell', None, None), |
|
('git', 'Git', (1, 7, 0), None), |
|
('hg', 'Mercurial', (2, 0, 0), None), |
|
('EasyMercurial', None, (1, 3), None), |
|
('pip', None, None, None), |
|
('sqlite3', 'SQLite 3', None, None), |
|
('nosetests', 'Nose', (1, 0, 0), None), |
|
('ipython', 'IPython script', (1, 0), None), |
|
('emacs', 'Emacs', None, None), |
|
('xemacs', 'XEmacs', None, None), |
|
('vim', 'Vim', None, None), |
|
('vi', None, None, None), |
|
('nano', 'Nano', None, None), |
|
('gedit', None, None, None), |
|
('kate', 'Kate', None, None), |
|
('notepad++', 'Notepad++', None, |
|
_program_files_paths('Notepad++', 'notepad++.exe')), |
|
('firefox', 'Firefox', None, |
|
_program_files_paths('Mozilla Firefox', 'firefox.exe')), |
|
('google-chrome', 'Google Chrome', None, |
|
_program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe') |
|
), |
|
('chromium', 'Chromium', None, None), |
|
]: |
|
if not long_name: |
|
long_name = command |
|
CHECKER[command] = CommandDependency( |
|
command=command, paths=paths, long_name=long_name, |
|
minimum_version=minimum_version) |
|
del command, long_name, minimum_version, paths # cleanup namespace |
|
|
|
|
|
CHECKER['make'] = MakeDependency(command='make', minimum_version=None) |
|
|
|
|
|
CHECKER['easy_install'] = EasyInstallDependency( |
|
command='easy_install', long_name='Setuptools easy_install', |
|
minimum_version=None) |
|
|
|
|
|
CHECKER['py.test'] = CommandDependency( |
|
command='py.test', version_stream='stderr', |
|
minimum_version=None) |
|
|
|
|
|
for paths,name,long_name in [ |
|
([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app', |
|
'Contents', 'version.plist')], |
|
'sublime-text', 'Sublime Text'), |
|
([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app', |
|
'Contents', 'version.plist')], |
|
'textmate', 'TextMate'), |
|
([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app', |
|
'Contents', 'version.plist')], |
|
'textwrangler', 'TextWrangler'), |
|
([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app', |
|
'Contents', 'version.plist')], |
|
'safari', 'Safari'), |
|
([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app', |
|
'Contents', 'version.plist'), # OS X >=1.7 |
|
_os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app', |
|
'Contents', 'version.plist'), # OS X 1.6, |
|
], |
|
'xcode', 'Xcode'), |
|
]: |
|
if not long_name: |
|
long_name = name |
|
CHECKER[name] = VersionPlistCommandDependency( |
|
command=None, paths=paths, name=name, long_name=long_name) |
|
del paths, name, long_name # cleanup namespace |
|
|
|
|
|
for package,name,long_name,minimum_version,and_dependencies in [ |
|
('nose', None, 'Nose Python package', |
|
CHECKER['nosetests'].minimum_version, None), |
|
('pytest', None, 'pytest Python package', |
|
CHECKER['py.test'].minimum_version, None), |
|
('jinja2', 'jinja', 'Jinja', (2, 6), None), |
|
('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None), |
|
('IPython', None, 'IPython Python package', |
|
CHECKER['ipython'].minimum_version, [ |
|
'jinja', |
|
'tornado', |
|
'pyzmq', |
|
VirtualDependency( |
|
name='virtual-browser-ipython', |
|
long_name='IPython-compatible web browser', |
|
or_dependencies=[ |
|
CommandDependency( |
|
command=CHECKER['firefox'].command, |
|
paths=CHECKER['firefox'].paths, |
|
name='{0}-for-ipython'.format( |
|
CHECKER['firefox'].name), |
|
long_name='{0} for IPython'.format( |
|
CHECKER['firefox'].long_name), |
|
minimum_version=(6, 0)), |
|
CommandDependency( |
|
command=CHECKER['google-chrome'].command, |
|
paths=CHECKER['google-chrome'].paths, |
|
name='{0}-for-ipython'.format( |
|
CHECKER['google-chrome'].name), |
|
long_name='{0} for IPython'.format( |
|
CHECKER['google-chrome'].long_name), |
|
minimum_version=(13, 0)), |
|
CommandDependency( |
|
command=CHECKER['chromium'].command, |
|
paths=CHECKER['chromium'].paths, |
|
name='{0}-for-ipython'.format( |
|
CHECKER['chromium'].name), |
|
long_name='{0} for IPython'.format( |
|
CHECKER['chromium'].long_name), |
|
minimum_version=(13, 0)), |
|
VersionPlistCommandDependency( |
|
command=CHECKER['safari'].command, |
|
paths=CHECKER['safari'].paths, |
|
key=CHECKER['safari'].key, |
|
name='{0}-for-ipython'.format( |
|
CHECKER['safari'].name), |
|
long_name='{0} for IPython'.format( |
|
CHECKER['safari'].long_name), |
|
minimum_version=(5, 0)), |
|
]), |
|
]), |
|
('argparse', None, 'Argparse', None, None), |
|
('numpy', None, 'NumPy', None, None), |
|
('scipy', None, 'SciPy', None, None), |
|
('matplotlib', None, 'Matplotlib', None, None), |
|
('pandas', None, 'Pandas', (0, 8), None), |
|
('sympy', None, 'SymPy', None, None), |
|
('Cython', None, None, None, None), |
|
('networkx', None, 'NetworkX', None, None), |
|
('mayavi.mlab', None, 'MayaVi', None, None), |
|
('setuptools', None, 'Setuptools', None, None), |
|
]: |
|
if not name: |
|
name = package |
|
if not long_name: |
|
long_name = name |
|
kwargs = {} |
|
if and_dependencies: |
|
kwargs['and_dependencies'] = and_dependencies |
|
CHECKER[name] = PythonPackageDependency( |
|
package=package, name=name, long_name=long_name, |
|
minimum_version=minimum_version, **kwargs) |
|
# cleanup namespace |
|
del package, name, long_name, minimum_version, and_dependencies, kwargs |
|
|
|
|
|
CHECKER['mercurial'] = MercurialPythonPackage( |
|
package='mercurial.util', name='mercurial', |
|
long_name='Mercurial Python package', |
|
minimum_version=CHECKER['hg'].minimum_version) |
|
|
|
|
|
CHECKER['tornado'] = TornadoPythonPackage( |
|
package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0)) |
|
|
|
|
|
CHECKER['sqlite3-python'] = SQLitePythonPackage( |
|
package='sqlite3', name='sqlite3-python', |
|
long_name='SQLite Python package', |
|
minimum_version=CHECKER['sqlite3'].minimum_version) |
|
|
|
|
|
CHECKER['other-editor'] = EditorTaskDependency( |
|
name='other-editor', long_name='') |
|
|
|
|
|
for name,long_name,dependencies in [ |
|
('virtual-shell', 'command line shell', ( |
|
'bash', |
|
'dash', |
|
'ash', |
|
'zsh', |
|
'ksh', |
|
'csh', |
|
'tcsh', |
|
'sh', |
|
)), |
|
('virtual-editor', 'text/code editor', ( |
|
'emacs', |
|
'xemacs', |
|
'vim', |
|
'vi', |
|
'nano', |
|
'gedit', |
|
'kate', |
|
'notepad++', |
|
'sublime-text', |
|
'textmate', |
|
'textwrangler', |
|
'other-editor', # last because it requires user interaction |
|
)), |
|
('virtual-browser', 'web browser', ( |
|
'firefox', |
|
'google-chrome', |
|
'chromium', |
|
'safari', |
|
)), |
|
('virtual-pypi-installer', 'PyPI installer', ( |
|
'pip', |
|
'easy_install', |
|
)), |
|
]: |
|
CHECKER[name] = VirtualDependency( |
|
name=name, long_name=long_name, or_dependencies=dependencies) |
|
del name, long_name, dependencies # cleanup namespace |
|
|
|
|
|
def _print_info(key, value, indent=19): |
|
print('{0}{1}: {2}'.format(key, ' '*(indent-len(key)), value)) |
|
|
|
def print_system_info(): |
|
print("If you do not understand why the above failures occurred,") |
|
print("copy and send the *entire* output (all info above and summary") |
|
print("below) to the instructor for help.") |
|
print() |
|
print('==================') |
|
print('System information') |
|
print('==================') |
|
_print_info('os.name', _os.name) |
|
_print_info('os.uname', _platform.uname()) |
|
_print_info('platform', _sys.platform) |
|
_print_info('platform+', _platform.platform()) |
|
for pversion in ( |
|
'linux_distribution', |
|
'mac_ver', |
|
'win32_ver', |
|
): |
|
value = getattr(_platform, pversion)() |
|
if value[0]: |
|
_print_info(pversion, value) |
|
_print_info('prefix', _sys.prefix) |
|
_print_info('exec_prefix', _sys.exec_prefix) |
|
_print_info('executable', _sys.executable) |
|
_print_info('version_info', _sys.version_info) |
|
_print_info('version', _sys.version) |
|
_print_info('environment', '') |
|
for key,value in sorted(_os.environ.items()): |
|
print(' {0}={1}'.format(key, value)) |
|
print('==================') |
|
|
|
def print_suggestions(instructor_fallback=True): |
|
print() |
|
print('For suggestions on installing missing packages, see') |
|
print('http://software-carpentry.org/setup/') |
|
print('') |
|
print('For instructings on installing a particular package,') |
|
print('see the failure message for that package printed above.') |
|
if instructor_fallback: |
|
print('') |
|
print('For help, email the *entire* output of this script to') |
|
print('your instructor.') |
|
|
|
|
|
if __name__ == '__main__': |
|
import optparse as _optparse |
|
|
|
parser = _optparse.OptionParser(usage='%prog [options] [check...]') |
|
epilog = __doc__ |
|
parser.format_epilog = lambda formatter: '\n' + epilog |
|
parser.add_option( |
|
'-v', '--verbose', action='store_true', |
|
help=('print additional information to help troubleshoot ' |
|
'installation issues')) |
|
options,args = parser.parse_args() |
|
try: |
|
passed = check(args) |
|
except InvalidCheck as e: |
|
print("I don't know how to check for {0!r}".format(e.check)) |
|
print('I do know how to check for:') |
|
for key,checker in sorted(CHECKER.items()): |
|
if checker.long_name != checker.name: |
|
print(' {0} {1}({2})'.format( |
|
key, ' '*(20-len(key)), checker.long_name)) |
|
else: |
|
print(' {0}'.format(key)) |
|
_sys.exit(1) |
|
if not passed: |
|
if options.verbose: |
|
print() |
|
print_system_info() |
|
print_suggestions(instructor_fallback=True) |
|
_sys.exit(1) |