ebca9b11d445b962fd9fa5c92a15990fece0dec4
[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 How to get a command line:
10
11 - On OSX run this with the Terminal application.
12
13 - On Windows, go to the Start menu, select 'Run' and type 'cmd'
14 (without the quotes) to run the 'cmd.exe' Windows Command Prompt.
15
16 - On Linux, either use your login shell directly, or run one of a
17   number of graphical terminals (e.g. 'xterm', 'gnome-terminal', ...).
18
19 Run the script and follow the instructions it prints at the end.
20
21 This script requires at least Python 2.6.  You can check the version
22 of Python that you have installed with 'swc-installation-test-1.py'.
23 """
24
25 import importlib as _importlib
26 import logging as _logging
27 import os as _os
28 import platform as _platform
29 import re as _re
30 import subprocess as _subprocess
31 import sys as _sys
32
33
34 __version__ = '0.1'
35
36 # Comment out any entries you don't need
37 CHECKS = [
38     'python',
39     'bash',
40     'easy_install',
41     'git',
42     'hg',              # Command line tool
43     'mercurial',       # Python package
44     'make',
45     'nosetests',       # Command line tool
46     'nose',            # Python package
47     'sqlite3',         # Command line tool
48     'sqlite3-python',  # Python package
49     'IPython',
50     'numpy',
51     'scipy',
52     'matplotlib',
53     'sympy',
54     'Cython',
55     'networkx',
56     'mayavi.mlab',
57     'setuptools',
58     ]
59
60 CHECKER = {}
61
62
63 class DependencyError (Exception):
64     def __init__(self, checker, message):
65         self.checker = checker
66         self.message = message
67
68     def __str__(self):
69         return 'check for {0} failed:\n{1}'.format(
70             self.checker.full_name(), self.message)
71
72
73 def check(checks=None):
74     failures = []
75     if not checks:
76         checks = CHECKS
77     for check in checks:
78         checker = CHECKER[check]
79         _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
80         try:
81             checker.check()
82         except DependencyError as e:
83             failures.append(e)
84             _sys.stdout.write('fail\n')
85         else:
86             _sys.stdout.write('pass\n')
87     if failures:
88         print('\nFailures:')
89         for failure in failures:
90             print()
91             print(failure)
92         return False
93     return True
94
95
96 class Dependency (object):
97     def __init__(self, name, long_name=None, minimum_version=None,
98                  version_delimiter='.'):
99         self.name = name
100         self.long_name = long_name or name
101         self.minimum_version = minimum_version
102         self.version_delimiter = version_delimiter
103
104     def __str__(self):
105         return '<{0} {1}>'.format(type(self).__name__, self.name)
106
107     def full_name(self):
108         if self.name == self.long_name:
109             return self.name
110         else:
111             return '{0} ({1})'.format(self.long_name, self.name)
112
113     def check(self):
114         version = self._get_version()
115         parsed_version = None
116         if hasattr(self, '_get_parsed_version'):
117             parsed_version = self._get_parsed_version()
118         if self.minimum_version:
119             self._check_version(version=version, parsed_version=parsed_version)
120
121     def _get_version(self):
122         raise NotImplementedError(self)
123
124     def _check_version(self, version, parsed_version=None):
125         if not parsed_version:
126             parsed_version = self._parse_version(version=version)
127         if parsed_version < self.minimum_version:
128             raise DependencyError(
129                 checker=self,
130                 message='outdated version of {0}: {1} (need >= {2})'.format(
131                     self.full_name(), version,
132                     self.version_delimiter.join(
133                         str(part) for part in self.minimum_version)))
134
135     def _parse_version(self, version):
136         parsed_version = []
137         for part in version.split(self.version_delimiter):
138             try:
139                 parsed_version.append(int(part))
140             except ValueError as e:
141                 raise NotImplementedError((version, part)) from e
142         return tuple(parsed_version)
143
144
145 class PythonDependency (Dependency):
146     def __init__(self, name='python', long_name='Python version',
147                  minimum_version=(2, 6), **kwargs):
148         super(PythonDependency, self).__init__(
149             name=name, long_name=long_name, minimum_version=minimum_version,
150             **kwargs)
151
152     def _get_version(self):
153         return _sys.version
154
155     def _get_parsed_version(self):
156         return _sys.version_info
157
158
159 CHECKER['python'] = PythonDependency()
160
161
162 class CommandDependency (Dependency):
163     def __init__(self, command, version_option='--version',
164                  version_regexp=None, version_stream='stdout', **kwargs):
165         if 'name' not in kwargs:
166             kwargs['name'] = command
167         super(CommandDependency, self).__init__(**kwargs)
168         self.command = command
169         self.version_option = version_option
170         if not version_regexp:
171             regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
172             version_regexp = _re.compile(regexp)
173         self.version_regexp = version_regexp
174         self.version_stream = version_stream
175
176     def _get_version_stream(self):
177         try:
178             p = _subprocess.Popen(
179                 [self.command, self.version_option],
180                 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
181                 close_fds=True, shell=False, universal_newlines=True)
182         except OSError as e:
183             raise DependencyError(
184                 checker=self,
185                 message="could not find '{0}' executable".format(self.command),
186                 ) from e
187         stdout,stderr = p.communicate()
188         status = p.wait()
189         if status:
190             lines = [
191                 "failed to execute '{0} {1}':".format(
192                     self.command, self.version_option),
193                 'status: {0}'.format(status),
194                 ]
195             for name,string in [('stdout', stdout), ('stderr', stderr)]:
196                 if string:
197                     lines.extend([name + ':', string])
198             raise DependencyError(checker=self, message='\n'.join(lines))
199         for name,string in [('stdout', stdout), ('stderr', stderr)]:
200             if name == self.version_stream:
201                 return string
202         raise NotImplementedError(self.version_stream)
203
204     def _get_version(self):
205         version_stream = self._get_version_stream()
206         match = self.version_regexp.search(version_stream)
207         if not match:
208             raise DependencyError(
209                 checker=self,
210                 message='no version string in output:\n{0}'.format(
211                     version_stream))
212         return match.group(1)
213
214
215 for command,long_name,minimum_version in [
216         ('bash', 'Bourne Again Shell', (4, 0)),
217         ('easy_install', 'Setuptools easy_install', None),
218         ('git', 'Git', (1, 8, 0)),
219         ('hg', 'Mercurial', (2, 0, 0)),
220         ('make', None, None),
221         ('sqlite3', 'SQLite 3', None),
222         ('nosetests', 'Nose', (1, 0, 0)),
223         ]:
224     if not long_name:
225         long_name = command
226     CHECKER[command] = CommandDependency(
227         command=command, long_name=long_name, minimum_version=minimum_version)
228 del command, long_name, minimum_version  # cleanup namespace
229
230
231 class PythonPackageDependency (Dependency):
232     def __init__(self, package, **kwargs):
233         if 'name' not in kwargs:
234             kwargs['name'] = package
235         super(PythonPackageDependency, self).__init__(**kwargs)
236         self.package = package
237
238     def _get_version(self):
239         try:
240             package = _importlib.import_module(self.package)
241         except ImportError as e:
242             raise DependencyError(
243                 checker=self,
244                 message="could not import the '{0}' package".format(
245                     self.package),
246                 ) from e
247         try:
248             version = package.__version__
249         except AttributeError:
250             version = 'unknown'
251         return version
252
253
254 for package,name,long_name,minimum_version in [
255         ('mercurial', None, 'Mercurial Python package',
256          CHECKER['hg'].minimum_version),
257         ('nose', None, 'Nose Python package',
258          CHECKER['nosetests'].minimum_version),
259         ('sqlite3', 'sqlite3-python', 'SQLite Python package',
260          CHECKER['sqlite3'].minimum_version),
261         ('IPython', None, None, None),
262         ('numpy', None, 'NumPy', None),
263         ('scipy', None, 'SciPy', None),
264         ('matplotlib', None, 'Matplotlib', None),
265         ('sympy', None, 'SymPy', None),
266         ('Cython', None, None, None),
267         ('networkx', None, 'NetworkX', None),
268         ('mayavi.mlab', None, 'MayaVi', None),
269         ('setuptools', None, 'Setuptools', None),
270         ]:
271     if not name:
272         name = package
273     if not long_name:
274         long_name = name
275     CHECKER[name] = PythonPackageDependency(
276         package=package, name=name, long_name=long_name,
277         minimum_version=minimum_version)
278 del package, name, long_name, minimum_version  # cleanup namespace
279
280
281 def print_system_info():
282     print("If you do not understand why the above failures occurred,")
283     print("copy and send the *entire* output (all info above and summary")
284     print("below) to the instructor for help.")
285     print()
286     print('==================')
287     print('System information')
288     print('==================')
289     print('os.name      : {0}'.format(_os.name))
290     try:
291         print('os.uname     : {0}'.format(_os.uname()))
292     except:
293         pass
294     print('platform     : {0}'.format(_sys.platform))
295     print('platform+    : {0}'.format(_platform.platform()))
296     print('prefix       : {0}'.format(_sys.prefix))
297     print('exec_prefix  : {0}'.format(_sys.exec_prefix))
298     print('executable   : {0}'.format(_sys.executable))
299     print('version_info : {0}'.format(_sys.version_info))
300     print('version      : {0}'.format(_sys.version))
301     print('environment  :')
302     for key,value in sorted(_os.environ.items()):
303         print('  {0}={1}'.format(key, value))
304     print('==================')
305
306
307 if __name__ == '__main__':
308     if not check(_sys.argv[1:]):
309         print()
310         print_system_info()