7e1f731044efcfcfae8ad64b0297875635b5fc24
[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         ('sqlite3', 'sqlite3-python', 'SQLite Python package',
359          CHECKER['sqlite3'].minimum_version),
360         ('IPython', None, None, None),
361         ('numpy', None, 'NumPy', None),
362         ('scipy', None, 'SciPy', None),
363         ('matplotlib', None, 'Matplotlib', None),
364         ('sympy', None, 'SymPy', None),
365         ('Cython', None, None, None),
366         ('networkx', None, 'NetworkX', None),
367         ('mayavi.mlab', None, 'MayaVi', None),
368         ('setuptools', None, 'Setuptools', None),
369         ]:
370     if not name:
371         name = package
372     if not long_name:
373         long_name = name
374     CHECKER[name] = PythonPackageDependency(
375         package=package, name=name, long_name=long_name,
376         minimum_version=minimum_version)
377 del package, name, long_name, minimum_version  # cleanup namespace
378
379
380 class MercurialPythonPackage (PythonPackageDependency):
381     def _get_version(self):
382         try:  # mercurial >= 1.2
383             package = _importlib.import_module('mercurial.util')
384         except ImportError as e:  # mercurial <= 1.1.2
385             package = self._get_package('mercurial.version')
386             return package.get_version()
387         else:
388             return package.version()
389
390
391 CHECKER['mercurial'] = MercurialPythonPackage(
392     package='mercurial.util', name='mercurial',
393     long_name='Mercurial Python package',
394     minimum_version=CHECKER['hg'].minimum_version)
395
396
397 class VirtualDependency (Dependency):
398     def _check(self):
399         return '{0} {1}'.format(
400             self.or_pass['dependency'].full_name(),
401             self.or_pass['version'])
402
403
404 for name,dependencies in [
405         ('virtual-editor', (
406             'emacs',
407             'xemacs',
408             'vim',
409             'vi',
410             'nano',
411             'kate',
412             'notepad++',
413             )),
414         ('virtual-browser', (
415             'firefox',
416             'google-chrome',
417             'chromium',
418             )),
419         ]:
420     CHECKER[name] = VirtualDependency(
421         name=name, long_name=name, or_dependencies=dependencies)
422 del name, dependencies  # cleanup namespace
423
424
425 def print_system_info():
426     print("If you do not understand why the above failures occurred,")
427     print("copy and send the *entire* output (all info above and summary")
428     print("below) to the instructor for help.")
429     print()
430     print('==================')
431     print('System information')
432     print('==================')
433     print('os.name      : {0}'.format(_os.name))
434     try:
435         print('os.uname     : {0}'.format(_os.uname()))
436     except:
437         pass
438     print('platform     : {0}'.format(_sys.platform))
439     print('platform+    : {0}'.format(_platform.platform()))
440     print('prefix       : {0}'.format(_sys.prefix))
441     print('exec_prefix  : {0}'.format(_sys.exec_prefix))
442     print('executable   : {0}'.format(_sys.executable))
443     print('version_info : {0}'.format(_sys.version_info))
444     print('version      : {0}'.format(_sys.version))
445     print('environment  :')
446     for key,value in sorted(_os.environ.items()):
447         print('  {0}={1}'.format(key, value))
448     print('==================')
449
450
451 if __name__ == '__main__':
452     if not check(_sys.argv[1:]):
453         print()
454         print_system_info()