diff --git a/setup/swc-installation-test-2.py b/setup/swc-installation-test-2.py index 9898d1b..bd7960d 100755 --- a/setup/swc-installation-test-2.py +++ b/setup/swc-installation-test-2.py @@ -22,6 +22,25 @@ version of a particular dependency, and you just want to re-test that dependency. """ +# Some details about the implementation: + +# The dependencies are divided into a hierarchy of classes rooted on +# Dependency class. You can refer to the code to see which package +# comes under which type of dependency. + +# The CHECKER dictionary stores information about all the dependencies +# and CHECKS stores list of the dependencies which are to be checked in +# the current workshop. + +# In the "__name__ == '__main__'" block, we launch all the checks with +# check() function, which prints information about the tests as they run +# and details about the failures after the tests complete. In case of +# failure, the functions print_system_info() and print_suggestions() +# are called after this, where the former prints information about the +# user's system for debugging purposes while the latter prints some +# suggestions to follow. + + from __future__ import print_function # for Python 2.6 compatibility import distutils.ccompiler as _distutils_ccompiler @@ -50,6 +69,7 @@ try: # Python 3.x import urllib.parse as _urllib_parse except ImportError: # Python 2.x import urllib as _urllib_parse # for quote() +import xml.etree.ElementTree as _element_tree if not hasattr(_shlex, 'quote'): # Python versions older than 3.3 @@ -95,10 +115,10 @@ CHECKS = [ 'scipy', 'matplotlib', 'pandas', - 'sympy', - 'Cython', - 'networkx', - 'mayavi.mlab', + #'sympy', + #'Cython', + #'networkx', + #'mayavi.mlab', ] CHECKER = {} @@ -378,21 +398,11 @@ class Dependency (object): return tuple(parsed_version) -class PythonDependency (Dependency): - def __init__(self, name='python', long_name='Python version', - minimum_version=(2, 6), **kwargs): - super(PythonDependency, self).__init__( - name=name, long_name=long_name, minimum_version=minimum_version, - **kwargs) - - def _get_version(self): - return _sys.version - - def _get_parsed_version(self): - return _sys.version_info - - -CHECKER['python'] = PythonDependency() +class VirtualDependency (Dependency): + def _check(self): + return '{0} {1}'.format( + self.or_pass['dependency'].full_name(), + self.or_pass['version']) class CommandDependency (Dependency): @@ -487,54 +497,120 @@ class CommandDependency (Dependency): return match.group(1) -def _program_files_paths(*args): - "Utility for generating MS Windows search paths" - pf = _os.environ.get('ProgramFiles', '/usr/bin') - pfx86 = _os.environ.get('ProgramFiles(x86)', pf) - paths = [_os.path.join(pf, *args)] - if pfx86 != pf: - paths.append(_os.path.join(pfx86, *args)) - return paths +class VersionPlistCommandDependency (CommandDependency): + """A command that doesn't support --version or equivalent options + On OS X, a command's executable may be hard to find, or not exist + in the PATH. Work around that by looking up the version + information in the package's version.plist file. + """ + def __init__(self, key='CFBundleShortVersionString', **kwargs): + super(VersionPlistCommandDependency, self).__init__(**kwargs) + self.key = key -for command,long_name,minimum_version,paths in [ - ('sh', 'Bourne Shell', None, None), - ('ash', 'Almquist Shell', None, None), - ('bash', 'Bourne Again Shell', None, None), - ('csh', 'C Shell', None, None), - ('ksh', 'KornShell', None, None), - ('dash', 'Debian Almquist Shell', None, None), - ('tcsh', 'TENEX C Shell', None, None), - ('zsh', 'Z Shell', None, None), - ('git', 'Git', (1, 7, 0), None), - ('hg', 'Mercurial', (2, 0, 0), None), - ('EasyMercurial', None, (1, 3), None), - ('pip', None, None, None), - ('sqlite3', 'SQLite 3', None, None), - ('nosetests', 'Nose', (1, 0, 0), None), - ('ipython', 'IPython script', (1, 0), None), - ('emacs', 'Emacs', None, None), - ('xemacs', 'XEmacs', None, None), - ('vim', 'Vim', None, None), - ('vi', None, None, None), - ('nano', 'Nano', None, None), - ('gedit', None, None, None), - ('kate', 'Kate', None, None), - ('notepad++', 'Notepad++', None, - _program_files_paths('Notepad++', 'notepad++.exe')), - ('firefox', 'Firefox', None, - _program_files_paths('Mozilla Firefox', 'firefox.exe')), - ('google-chrome', 'Google Chrome', None, - _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe') - ), - ('chromium', 'Chromium', None, None), - ]: - if not long_name: - long_name = command - CHECKER[command] = CommandDependency( - command=command, paths=paths, long_name=long_name, - minimum_version=minimum_version) -del command, long_name, minimum_version, paths # cleanup namespace + def _get_command_version_stream(self, *args, **kwargs): + raise NotImplementedError() + + def _get_version_stream(self, *args, **kwargs): + raise NotImplementedError() + + @staticmethod + def _get_parent(root, element): + """Returns the parent of this element or None for the root element + """ + for node in root.iter(): + if element in node: + return node + raise ValueError((root, element)) + + @classmethod + def _get_next(cls, root, element): + """Returns the following sibling of this element or None + """ + parent = cls._get_parent(root=root, element=element) + siblings = iter(parent) + for node in siblings: + if node == element: + try: + return next(siblings) + except StopIteration: + return None + return None + + def _get_version_from_plist(self, path): + """Parse the plist and return the value string for self.key + """ + tree = _element_tree.parse(source=path) + data = {} + for key in tree.findall('.//key'): + value = self._get_next(root=tree, element=key) + if value.tag != 'string': + raise ValueError((tree, key, value)) + data[key.text] = value.text + return data[self.key] + + def _get_version(self): + for path in self.paths: + if _os.path.exists(path): + return self._get_version_from_plist(path=path) + raise DependencyError( + checker=self, + message=( + 'nothing exists at any of the expected paths for {0}:\n {1}' + ).format( + self.full_name(), + '\n '.join(p for p in self.paths))) + + +class UserTaskDependency (Dependency): + "Prompt the user to complete a task and check for success" + def __init__(self, prompt, **kwargs): + super(UserTaskDependency, self).__init__(**kwargs) + self.prompt = prompt + + def _check(self): + if _sys.version_info >= (3, ): + result = input(self.prompt) + else: # Python 2.x + result = raw_input(self.prompt) + return self._check_result(result) + + def _check_result(self, result): + raise NotImplementedError() + + +class EditorTaskDependency (UserTaskDependency): + def __init__(self, **kwargs): + self.path = _os.path.expanduser(_os.path.join( + '~', 'swc-installation-test.txt')) + self.contents = 'Hello, world!' + super(EditorTaskDependency, self).__init__( + prompt=( + 'Open your favorite text editor and create the file\n' + ' {0}\n' + 'containing the line:\n' + ' {1}\n' + 'Press enter here after you have done this.\n' + 'You may remove the file after you have finished testing.' + ).format(self.path, self.contents), + **kwargs) + + def _check_result(self, result): + message = None + try: + with open(self.path, 'r') as f: + contents = f.read() + except IOError as e: + raise DependencyError( + checker=self, + message='could not open {0!r}: {1}'.format(self.path, e) + )# from e + if contents.strip() != self.contents: + raise DependencyError( + checker=self, + message=( + 'file contents ({0!r}) did not match the expected {1!r}' + ).format(contents, self.contents)) class MakeDependency (CommandDependency): @@ -569,9 +645,6 @@ class MakeDependency (CommandDependency): self.version_options = version_options -CHECKER['make'] = MakeDependency(command='make', minimum_version=None) - - class EasyInstallDependency (CommandDependency): def _get_version(self): try: @@ -587,65 +660,18 @@ class EasyInstallDependency (CommandDependency): self.version_stream = version_stream -CHECKER['easy_install'] = EasyInstallDependency( - command='easy_install', long_name='Setuptools easy_install', - minimum_version=None) - - -CHECKER['py.test'] = CommandDependency( - command='py.test', version_stream='stderr', - minimum_version=None) - - -class PathCommandDependency (CommandDependency): - """A command that doesn't support --version or equivalent options - - On some operating systems (e.g. OS X), a command's executable may - be hard to find, or not exist in the PATH. Work around that by - just checking for the existence of a characteristic file or - directory. Since the characteristic path may depend on OS, - installed version, etc., take a list of paths, and succeed if any - of them exists. - """ - def _get_command_version_stream(self, *args, **kwargs): - raise NotImplementedError() - - def _get_version_stream(self, *args, **kwargs): - raise NotImplementedError() +class PythonDependency (Dependency): + def __init__(self, name='python', long_name='Python version', + minimum_version=(2, 6), **kwargs): + super(PythonDependency, self).__init__( + name=name, long_name=long_name, minimum_version=minimum_version, + **kwargs) def _get_version(self): - for path in self.paths: - if _os.path.exists(path): - return None - raise DependencyError( - checker=self, - message=( - 'nothing exists at any of the expected paths for {0}:\n {1}' - ).format( - self.full_name(), - '\n '.join(p for p in self.paths))) - + return _sys.version -for paths,name,long_name in [ - ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app')], - 'sublime-text', 'Sublime Text'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app')], - 'textmate', 'TextMate'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app')], - 'textwrangler', 'TextWrangler'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app')], - 'safari', 'Safari'), - ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app'), # OS X >=1.7 - _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app' - ) # OS X 1.6, - ], - 'xcode', 'Xcode'), - ]: - if not long_name: - long_name = name - CHECKER[name] = PathCommandDependency( - command=None, paths=paths, name=name, long_name=long_name) -del paths, name, long_name # cleanup namespace + def _get_parsed_version(self): + return _sys.version_info class PythonPackageDependency (Dependency): @@ -681,6 +707,127 @@ class PythonPackageDependency (Dependency): return version +class MercurialPythonPackage (PythonPackageDependency): + def _get_version(self): + try: # mercurial >= 1.2 + package = _importlib.import_module('mercurial.util') + except ImportError as e: # mercurial <= 1.1.2 + package = self._get_package('mercurial.version') + return package.get_version() + else: + return package.version() + + +class TornadoPythonPackage (PythonPackageDependency): + def _get_version_from_package(self, package): + return package.version + + def _get_parsed_version(self): + package = self._get_package(self.package) + return package.version_info + + +class SQLitePythonPackage (PythonPackageDependency): + def _get_version_from_package(self, package): + return _sys.version + + def _get_parsed_version(self): + return _sys.version_info + + +def _program_files_paths(*args): + "Utility for generating MS Windows search paths" + pf = _os.environ.get('ProgramFiles', '/usr/bin') + pfx86 = _os.environ.get('ProgramFiles(x86)', pf) + paths = [_os.path.join(pf, *args)] + if pfx86 != pf: + paths.append(_os.path.join(pfx86, *args)) + return paths + + +CHECKER['python'] = PythonDependency() + + +for command,long_name,minimum_version,paths in [ + ('sh', 'Bourne Shell', None, None), + ('ash', 'Almquist Shell', None, None), + ('bash', 'Bourne Again Shell', None, None), + ('csh', 'C Shell', None, None), + ('ksh', 'KornShell', None, None), + ('dash', 'Debian Almquist Shell', None, None), + ('tcsh', 'TENEX C Shell', None, None), + ('zsh', 'Z Shell', None, None), + ('git', 'Git', (1, 7, 0), None), + ('hg', 'Mercurial', (2, 0, 0), None), + ('EasyMercurial', None, (1, 3), None), + ('pip', None, None, None), + ('sqlite3', 'SQLite 3', None, None), + ('nosetests', 'Nose', (1, 0, 0), None), + ('ipython', 'IPython script', (1, 0), None), + ('emacs', 'Emacs', None, None), + ('xemacs', 'XEmacs', None, None), + ('vim', 'Vim', None, None), + ('vi', None, None, None), + ('nano', 'Nano', None, None), + ('gedit', None, None, None), + ('kate', 'Kate', None, None), + ('notepad++', 'Notepad++', None, + _program_files_paths('Notepad++', 'notepad++.exe')), + ('firefox', 'Firefox', None, + _program_files_paths('Mozilla Firefox', 'firefox.exe')), + ('google-chrome', 'Google Chrome', None, + _program_files_paths('Google', 'Chrome', 'Application', 'chrome.exe') + ), + ('chromium', 'Chromium', None, None), + ]: + if not long_name: + long_name = command + CHECKER[command] = CommandDependency( + command=command, paths=paths, long_name=long_name, + minimum_version=minimum_version) +del command, long_name, minimum_version, paths # cleanup namespace + + +CHECKER['make'] = MakeDependency(command='make', minimum_version=None) + + +CHECKER['easy_install'] = EasyInstallDependency( + command='easy_install', long_name='Setuptools easy_install', + minimum_version=None) + + +CHECKER['py.test'] = CommandDependency( + command='py.test', version_stream='stderr', + minimum_version=None) + + +for paths,name,long_name in [ + ([_os.path.join(_ROOT_PATH, 'Applications', 'Sublime Text 2.app', + 'Contents', 'version.plist')], + 'sublime-text', 'Sublime Text'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'TextMate.app', + 'Contents', 'version.plist')], + 'textmate', 'TextMate'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'TextWrangler.app', + 'Contents', 'version.plist')], + 'textwrangler', 'TextWrangler'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'Safari.app', + 'Contents', 'version.plist')], + 'safari', 'Safari'), + ([_os.path.join(_ROOT_PATH, 'Applications', 'Xcode.app', + 'Contents', 'version.plist'), # OS X >=1.7 + _os.path.join(_ROOT_PATH, 'Developer', 'Applications', 'Xcode.app', + 'Contents', 'version.plist'), # OS X 1.6, + ], + 'xcode', 'Xcode'), + ]: + if not long_name: + long_name = name + CHECKER[name] = VersionPlistCommandDependency( + command=None, paths=paths, name=name, long_name=long_name) +del paths, name, long_name # cleanup namespace + + for package,name,long_name,minimum_version,and_dependencies in [ ('nose', None, 'Nose Python package', CHECKER['nosetests'].minimum_version, None), @@ -689,7 +836,49 @@ for package,name,long_name,minimum_version,and_dependencies in [ ('jinja2', 'jinja', 'Jinja', (2, 6), None), ('zmq', 'pyzmq', 'PyZMQ', (2, 1, 4), None), ('IPython', None, 'IPython Python package', - CHECKER['ipython'].minimum_version, ['jinja', 'tornado', 'pyzmq']), + CHECKER['ipython'].minimum_version, [ + 'jinja', + 'tornado', + 'pyzmq', + VirtualDependency( + name='virtual-browser-ipython', + long_name='IPython-compatible web browser', + or_dependencies=[ + CommandDependency( + command=CHECKER['firefox'].command, + paths=CHECKER['firefox'].paths, + name='{0}-for-ipython'.format( + CHECKER['firefox'].name), + long_name='{0} for IPython'.format( + CHECKER['firefox'].long_name), + minimum_version=(6, 0)), + CommandDependency( + command=CHECKER['google-chrome'].command, + paths=CHECKER['google-chrome'].paths, + name='{0}-for-ipython'.format( + CHECKER['google-chrome'].name), + long_name='{0} for IPython'.format( + CHECKER['google-chrome'].long_name), + minimum_version=(13, 0)), + CommandDependency( + command=CHECKER['chromium'].command, + paths=CHECKER['chromium'].paths, + name='{0}-for-ipython'.format( + CHECKER['chromium'].name), + long_name='{0} for IPython'.format( + CHECKER['chromium'].long_name), + minimum_version=(13, 0)), + VersionPlistCommandDependency( + command=CHECKER['safari'].command, + paths=CHECKER['safari'].paths, + key=CHECKER['safari'].key, + name='{0}-for-ipython'.format( + CHECKER['safari'].name), + long_name='{0} for IPython'.format( + CHECKER['safari'].long_name), + minimum_version=(5, 0)), + ]), + ]), ('argparse', None, 'Argparse', None, None), ('numpy', None, 'NumPy', None, None), ('scipy', None, 'SciPy', None, None), @@ -715,112 +904,26 @@ for package,name,long_name,minimum_version,and_dependencies in [ del package, name, long_name, minimum_version, and_dependencies, kwargs -class MercurialPythonPackage (PythonPackageDependency): - def _get_version(self): - try: # mercurial >= 1.2 - package = _importlib.import_module('mercurial.util') - except ImportError as e: # mercurial <= 1.1.2 - package = self._get_package('mercurial.version') - return package.get_version() - else: - return package.version() - - CHECKER['mercurial'] = MercurialPythonPackage( package='mercurial.util', name='mercurial', long_name='Mercurial Python package', minimum_version=CHECKER['hg'].minimum_version) -class TornadoPythonPackage (PythonPackageDependency): - def _get_version_from_package(self, package): - return package.version - - def _get_parsed_version(self): - package = self._get_package(self.package) - return package.version_info - - CHECKER['tornado'] = TornadoPythonPackage( package='tornado', name='tornado', long_name='Tornado', minimum_version=(2, 0)) -class SQLitePythonPackage (PythonPackageDependency): - def _get_version_from_package(self, package): - return _sys.version - - def _get_parsed_version(self): - return _sys.version_info - - CHECKER['sqlite3-python'] = SQLitePythonPackage( package='sqlite3', name='sqlite3-python', long_name='SQLite Python package', minimum_version=CHECKER['sqlite3'].minimum_version) -class UserTaskDependency (Dependency): - "Prompt the user to complete a task and check for success" - def __init__(self, prompt, **kwargs): - super(UserTaskDependency, self).__init__(**kwargs) - self.prompt = prompt - - def _check(self): - if _sys.version_info >= (3, ): - result = input(self.prompt) - else: # Python 2.x - result = raw_input(self.prompt) - return self._check_result(result) - - def _check_result(self, result): - raise NotImplementedError() - - -class EditorTaskDependency (UserTaskDependency): - def __init__(self, **kwargs): - self.path = _os.path.expanduser(_os.path.join( - '~', 'swc-installation-test.txt')) - self.contents = 'Hello, world!' - super(EditorTaskDependency, self).__init__( - prompt=( - 'Open your favorite text editor and create the file\n' - ' {0}\n' - 'containing the line:\n' - ' {1}\n' - 'Press enter here after you have done this.\n' - 'You may remove the file after you have finished testing.' - ).format(self.path, self.contents), - **kwargs) - - def _check_result(self, result): - message = None - try: - with open(self.path, 'r') as f: - contents = f.read() - except IOError as e: - raise DependencyError( - checker=self, - message='could not open {0!r}: {1}'.format(self.path, e) - )# from e - if contents.strip() != self.contents: - raise DependencyError( - checker=self, - message=( - 'file contents ({0!r}) did not match the expected {1!r}' - ).format(contents, self.contents)) - - CHECKER['other-editor'] = EditorTaskDependency( name='other-editor', long_name='') -class VirtualDependency (Dependency): - def _check(self): - return '{0} {1}'.format( - self.or_pass['dependency'].full_name(), - self.or_pass['version']) - - for name,long_name,dependencies in [ ('virtual-shell', 'command line shell', ( 'bash',