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