From de325a0aea1f428a52639668d6cfa15cdac13e00 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Sat, 10 Jan 2015 15:53:48 +0700 Subject: [PATCH 1/2] Issue #26 Use pure python on compilation failure Allow the compilation of the extension to fail, and switch to pure python mode. --- setup.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 68943ac..226c1cc 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import os import sys if (sys.version_info[0] == 2 and sys.version_info[1] < 6) or \ @@ -39,7 +40,57 @@ tokenizer = Extension("mwparserfromhell.parser._tokenizer", sources=["mwparserfromhell/parser/tokenizer.c"], depends=["mwparserfromhell/parser/tokenizer.h"]) -setup( + +def optional_compile_setup(func=setup, use_ext=True, *args, **kwargs): + """ + Wrap setup to allow optional compilation of extensions. + + Falls back to pure python mode (no extensions) + if compilation of extensions fails. + """ + extensions = kwargs.get('ext_modules', None) + + if use_ext and extensions: + try: + func(*args, **kwargs) + return + except (Exception, SystemExit) as e: + print('Building extension failed: %s' % repr(e)) + + if extensions: + if use_ext: + print('Falling back to pure python mode.') + else: + print('Using pure python mode.') + + del kwargs['ext_modules'] + + # Basic algorithm to push the extension sources into + # the package as data. + ext_files = [(ext, filename) + for ext in extensions + for filename in ext.sources + ext.depends] + + pkg_data = kwargs.get('package_data', {}) + for ext, filename in ext_files: + ext_name_parts = ext.name.split('.') + pkg_name = '.'.join(ext_name_parts[0:-1]) + pkg = pkg_data.setdefault(pkg_name, []) + # This assumes the extension's package name + # is the same prefix as the filename. + pkg.append(os.path.basename(filename)) + + kwargs['package_data'] = pkg_data + + # Ensure the extension package is in the main packages list. + for name in pkg_data.keys(): + if name not in kwargs['packages']: + kwargs['packages'].append(name) + + func(*args, **kwargs) + + +optional_compile_setup( name = "mwparserfromhell", packages = find_packages(exclude=("tests",)), ext_modules = [tokenizer], From 4e8ce523858fb5d8777b4ab2ee89635fa721a08f Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 12 Jan 2015 18:33:26 +1100 Subject: [PATCH 2/2] Support 'setup.py test' and test without extension 'setup.py test' also uses SystemExit, with args[0] as False. Detect and re-raise. Add support for building without extension even when compiler is functional, and set up extension-less travis builds. --- .travis.yml | 5 +++++ setup.py | 59 ++++++++++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8dbb88..daa31ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,3 +12,8 @@ script: - coverage run --source=mwparserfromhell setup.py -q test after_success: - coveralls + +env: + matrix: + - WITHOUT_EXTENSION=0 + - WITHOUT_EXTENSION=1 diff --git a/setup.py b/setup.py index 226c1cc..761bb40 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,9 @@ if (sys.version_info[0] == 2 and sys.version_info[1] < 6) or \ (sys.version_info[1] == 3 and sys.version_info[1] < 2): raise Exception("mwparserfromhell needs Python 2.6+ or 3.2+") +if sys.version_info >= (3, 0): + basestring = (str, ) + from setuptools import setup, find_packages, Extension from mwparserfromhell import __version__ @@ -40,8 +43,25 @@ tokenizer = Extension("mwparserfromhell.parser._tokenizer", sources=["mwparserfromhell/parser/tokenizer.c"], depends=["mwparserfromhell/parser/tokenizer.h"]) +use_extension=True + +# Allow env var WITHOUT_EXTENSION and args --with[out]-extension +if '--without-extension' in sys.argv: + use_extension = False +elif '--with-extension' in sys.argv: + pass +elif os.environ.get('WITHOUT_EXTENSION', '0') == '1': + use_extension = False + +# Remove the command line argument as it isnt understood by +# setuptools/distutils +sys.argv = [arg for arg in sys.argv + if not arg.startswith('--with') + and not arg.endswith('-extension')] -def optional_compile_setup(func=setup, use_ext=True, *args, **kwargs): + +def optional_compile_setup(func=setup, use_ext=use_extension, + *args, **kwargs): """ Wrap setup to allow optional compilation of extensions. @@ -54,8 +74,19 @@ def optional_compile_setup(func=setup, use_ext=True, *args, **kwargs): try: func(*args, **kwargs) return - except (Exception, SystemExit) as e: - print('Building extension failed: %s' % repr(e)) + except SystemExit as e: + assert(e.args) + if e.args[0] is False: + raise + elif isinstance(e.args[0], basestring): + if e.args[0].startswith('usage: '): + raise + else: + # Fallback to pure python mode + print('setup with extension failed: %s' % repr(e)) + pass + except Exception as e: + print('setup with extension failed: %s' % repr(e)) if extensions: if use_ext: @@ -65,28 +96,6 @@ def optional_compile_setup(func=setup, use_ext=True, *args, **kwargs): del kwargs['ext_modules'] - # Basic algorithm to push the extension sources into - # the package as data. - ext_files = [(ext, filename) - for ext in extensions - for filename in ext.sources + ext.depends] - - pkg_data = kwargs.get('package_data', {}) - for ext, filename in ext_files: - ext_name_parts = ext.name.split('.') - pkg_name = '.'.join(ext_name_parts[0:-1]) - pkg = pkg_data.setdefault(pkg_name, []) - # This assumes the extension's package name - # is the same prefix as the filename. - pkg.append(os.path.basename(filename)) - - kwargs['package_data'] = pkg_data - - # Ensure the extension package is in the main packages list. - for name in pkg_data.keys(): - if name not in kwargs['packages']: - kwargs['packages'].append(name) - func(*args, **kwargs)