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