2ae101fe823ef10fa8f88dd9a182f521daef6b3c
[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 # Shell
39     'bash',
40 # Version control
41     'git',
42     'hg',              # Command line tool
43     'mercurial',       # Python package
44 # Build tools and packaging
45     'make',
46     'easy_install',
47     'setuptools',
48 # Testing
49     'nosetests',       # Command line tool
50     'nose',            # Python package
51 # SQL
52     'sqlite3',         # Command line tool
53     'sqlite3-python',  # Python package
54 # Python
55     'python',
56     'IPython',
57     'numpy',
58     'scipy',
59     'matplotlib',
60     'sympy',
61     'Cython',
62     'networkx',
63     'mayavi.mlab',
64     ]
65
66 CHECKER = {}
67
68
69 class DependencyError (Exception):
70     def __init__(self, checker, message):
71         self.checker = checker
72         self.message = message
73
74     def __str__(self):
75         return 'check for {0} failed:\n{1}'.format(
76             self.checker.full_name(), self.message)
77
78
79 def check(checks=None):
80     failures = []
81     if not checks:
82         checks = CHECKS
83     for check in checks:
84         checker = CHECKER[check]
85         _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
86         try:
87             checker.check()
88         except DependencyError as e:
89             failures.append(e)
90             _sys.stdout.write('fail\n')
91         else:
92             _sys.stdout.write('pass\n')
93     if failures:
94         print('\nFailures:')
95         for failure in failures:
96             print()
97             print(failure)
98         return False
99     return True
100
101
102 class Dependency (object):
103     def __init__(self, name, long_name=None, minimum_version=None,
104                  version_delimiter='.'):
105         self.name = name
106         self.long_name = long_name or name
107         self.minimum_version = minimum_version
108         self.version_delimiter = version_delimiter
109
110     def __str__(self):
111         return '<{0} {1}>'.format(type(self).__name__, self.name)
112
113     def full_name(self):
114         if self.name == self.long_name:
115             return self.name
116         else:
117             return '{0} ({1})'.format(self.long_name, self.name)
118
119     def check(self):
120         version = self._get_version()
121         parsed_version = None
122         if hasattr(self, '_get_parsed_version'):
123             parsed_version = self._get_parsed_version()
124         if self.minimum_version:
125             self._check_version(version=version, parsed_version=parsed_version)
126
127     def _get_version(self):
128         raise NotImplementedError(self)
129
130     def _check_version(self, version, parsed_version=None):
131         if not parsed_version:
132             parsed_version = self._parse_version(version=version)
133         if parsed_version < self.minimum_version:
134             raise DependencyError(
135                 checker=self,
136                 message='outdated version of {0}: {1} (need >= {2})'.format(
137                     self.full_name(), version,
138                     self.version_delimiter.join(
139                         str(part) for part in self.minimum_version)))
140
141     def _parse_version(self, version):
142         parsed_version = []
143         for part in version.split(self.version_delimiter):
144             try:
145                 parsed_version.append(int(part))
146             except ValueError as e:
147                 raise NotImplementedError((version, part)) from e
148         return tuple(parsed_version)
149
150
151 class PythonDependency (Dependency):
152     def __init__(self, name='python', long_name='Python version',
153                  minimum_version=(2, 6), **kwargs):
154         super(PythonDependency, self).__init__(
155             name=name, long_name=long_name, minimum_version=minimum_version,
156             **kwargs)
157
158     def _get_version(self):
159         return _sys.version
160
161     def _get_parsed_version(self):
162         return _sys.version_info
163
164
165 CHECKER['python'] = PythonDependency()
166
167
168 class CommandDependency (Dependency):
169     def __init__(self, command, version_option='--version',
170                  version_regexp=None, version_stream='stdout', **kwargs):
171         if 'name' not in kwargs:
172             kwargs['name'] = command
173         super(CommandDependency, self).__init__(**kwargs)
174         self.command = command
175         self.version_option = version_option
176         if not version_regexp:
177             regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
178             version_regexp = _re.compile(regexp)
179         self.version_regexp = version_regexp
180         self.version_stream = version_stream
181
182     def _get_version_stream(self):
183         try:
184             p = _subprocess.Popen(
185                 [self.command, self.version_option],
186                 stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
187                 close_fds=True, shell=False, universal_newlines=True)
188         except OSError as e:
189             raise DependencyError(
190                 checker=self,
191                 message="could not find '{0}' executable".format(self.command),
192                 ) from e
193         stdout,stderr = p.communicate()
194         status = p.wait()
195         if status:
196             lines = [
197                 "failed to execute '{0} {1}':".format(
198                     self.command, self.version_option),
199                 'status: {0}'.format(status),
200                 ]
201             for name,string in [('stdout', stdout), ('stderr', stderr)]:
202                 if string:
203                     lines.extend([name + ':', string])
204             raise DependencyError(checker=self, message='\n'.join(lines))
205         for name,string in [('stdout', stdout), ('stderr', stderr)]:
206             if name == self.version_stream:
207                 return string
208         raise NotImplementedError(self.version_stream)
209
210     def _get_version(self):
211         version_stream = self._get_version_stream()
212         match = self.version_regexp.search(version_stream)
213         if not match:
214             raise DependencyError(
215                 checker=self,
216                 message='no version string in output:\n{0}'.format(
217                     version_stream))
218         return match.group(1)
219
220
221 for command,long_name,minimum_version in [
222         ('bash', 'Bourne Again Shell', (4, 0)),
223         ('easy_install', 'Setuptools easy_install', None),
224         ('git', 'Git', (1, 8, 0)),
225         ('hg', 'Mercurial', (2, 0, 0)),
226         ('make', None, None),
227         ('sqlite3', 'SQLite 3', None),
228         ('nosetests', 'Nose', (1, 0, 0)),
229         ('emacs', 'Emacs', None),
230         ('xemacs', 'XEmacs', None),
231         ('vim', 'Vim', None),
232         ('vi', None, None),
233         ('nano', 'Nano', None),
234         ('kate', 'Kate', None),
235         ('notepad++', 'Notepad++', None),
236         ('firefox', 'Firefox', None),
237         ('google-chrome', 'Google Chrome', None),
238         ('chromium', 'Chromium', None),
239         ]:
240     if not long_name:
241         long_name = command
242     CHECKER[command] = CommandDependency(
243         command=command, long_name=long_name, minimum_version=minimum_version)
244 del command, long_name, minimum_version  # cleanup namespace
245
246
247 class PythonPackageDependency (Dependency):
248     def __init__(self, package, **kwargs):
249         if 'name' not in kwargs:
250             kwargs['name'] = package
251         super(PythonPackageDependency, self).__init__(**kwargs)
252         self.package = package
253
254     def _get_version(self):
255         try:
256             package = _importlib.import_module(self.package)
257         except ImportError as e:
258             raise DependencyError(
259                 checker=self,
260                 message="could not import the '{0}' package".format(
261                     self.package),
262                 ) from e
263         try:
264             version = package.__version__
265         except AttributeError:
266             version = 'unknown'
267         return version
268
269
270 for package,name,long_name,minimum_version in [
271         ('mercurial', None, 'Mercurial Python package',
272          CHECKER['hg'].minimum_version),
273         ('nose', None, 'Nose Python package',
274          CHECKER['nosetests'].minimum_version),
275         ('sqlite3', 'sqlite3-python', 'SQLite Python package',
276          CHECKER['sqlite3'].minimum_version),
277         ('IPython', None, None, None),
278         ('numpy', None, 'NumPy', None),
279         ('scipy', None, 'SciPy', None),
280         ('matplotlib', None, 'Matplotlib', None),
281         ('sympy', None, 'SymPy', None),
282         ('Cython', None, None, None),
283         ('networkx', None, 'NetworkX', None),
284         ('mayavi.mlab', None, 'MayaVi', None),
285         ('setuptools', None, 'Setuptools', None),
286         ]:
287     if not name:
288         name = package
289     if not long_name:
290         long_name = name
291     CHECKER[name] = PythonPackageDependency(
292         package=package, name=name, long_name=long_name,
293         minimum_version=minimum_version)
294 del package, name, long_name, minimum_version  # cleanup namespace
295
296
297 def print_system_info():
298     print("If you do not understand why the above failures occurred,")
299     print("copy and send the *entire* output (all info above and summary")
300     print("below) to the instructor for help.")
301     print()
302     print('==================')
303     print('System information')
304     print('==================')
305     print('os.name      : {0}'.format(_os.name))
306     try:
307         print('os.uname     : {0}'.format(_os.uname()))
308     except:
309         pass
310     print('platform     : {0}'.format(_sys.platform))
311     print('platform+    : {0}'.format(_platform.platform()))
312     print('prefix       : {0}'.format(_sys.prefix))
313     print('exec_prefix  : {0}'.format(_sys.exec_prefix))
314     print('executable   : {0}'.format(_sys.executable))
315     print('version_info : {0}'.format(_sys.version_info))
316     print('version      : {0}'.format(_sys.version))
317     print('environment  :')
318     for key,value in sorted(_os.environ.items()):
319         print('  {0}={1}'.format(key, value))
320     print('==================')
321
322
323 if __name__ == '__main__':
324     if not check(_sys.argv[1:]):
325         print()
326         print_system_info()