diff options
author | Tim Harder <radhermit@gmail.com> | 2016-02-22 17:36:44 -0500 |
---|---|---|
committer | Tim Harder <radhermit@gmail.com> | 2016-02-22 17:36:44 -0500 |
commit | 3d78c9acc5b8fbce28faa7a51285869d198ad849 (patch) | |
tree | b0ff85eb43801968c512b96efa04cb5facc1d121 | |
parent | pquery: use stripped IUSE for --has-use (diff) | |
download | pkgcore-3d78c9acc5b8fbce28faa7a51285869d198ad849.tar.gz pkgcore-3d78c9acc5b8fbce28faa7a51285869d198ad849.tar.bz2 pkgcore-3d78c9acc5b8fbce28faa7a51285869d198ad849.zip |
update pkgdist
-rw-r--r-- | pkgdist.py | 160 |
1 files changed, 147 insertions, 13 deletions
@@ -15,6 +15,7 @@ import io import math import os import re +import shlex import shutil import subprocess import sys @@ -28,7 +29,7 @@ from distutils.core import Command, Extension from distutils.errors import DistutilsExecError from distutils.command import ( sdist as dst_sdist, build_ext as dst_build_ext, build_py as dst_build_py, - build as dst_build, build_scripts as dst_build_scripts) + build as dst_build, build_scripts as dst_build_scripts, config as dst_config) # getting built by readthedocs READTHEDOCS = os.environ.get('READTHEDOCS', None) == 'True' @@ -102,7 +103,7 @@ def data_mapping(host_prefix, path, skip=None): class OptionalExtension(Extension): - """python extension that is optional to build. + """Python extension that is optional to build. If it's not required to have the exception built, just preferable, use this class instead of :py:class:`Extension` since the machinery @@ -405,7 +406,9 @@ class build_ext(dst_build_ext.build_ext): # add header install dir to the search path # (fixes virtualenv builds for consumer extensions) - self.set_undefined_options('install', ('install_headers', 'default_header_install_dir')) + self.set_undefined_options( + 'install', + ('install_headers', 'default_header_install_dir')) if self.default_header_install_dir: self.default_header_install_dir = os.path.dirname(self.default_header_install_dir) for e in self.extensions: @@ -413,18 +416,29 @@ class build_ext(dst_build_ext.build_ext): if self.default_header_install_dir not in e.include_dirs: e.include_dirs.append(self.default_header_install_dir) + def run(self): + # ensure that the platform checks were performed + self.run_command('config') + return dst_build_ext.build_ext.run(self) + def build_extensions(self): - if self.debug: - # say it with me kids... distutils sucks! - for x in ("compiler_so", "compiler", "compiler_cxx"): + # say it with me kids... distutils sucks! + for x in ("compiler_so", "compiler", "compiler_cxx"): + if self.debug: l = [y for y in getattr(self.compiler, x) if y != '-DNDEBUG'] l.append('-Wall') setattr(self.compiler, x, l) - if not self.disable_distutils_flag_fixing: - for x in ("compiler_so", "compiler", "compiler_cxx"): + if not self.disable_distutils_flag_fixing: val = getattr(self.compiler, x) if "-fno-strict-aliasing" not in val: val.append("-fno-strict-aliasing") + if getattr(self.distribution, 'check_defines', None): + val = getattr(self.compiler, x) + for d, result in self.distribution.check_defines.items(): + if result: + val.append('-D%s=1' % d) + else: + val.append('-U%s' % d) return dst_build_ext.build_ext.build_extensions(self) @@ -693,10 +707,14 @@ class PyTest(Command): sys.exit(1) # add custom pytest args - self.test_args.extend(self.pytest_args.split()) + self.test_args.extend(shlex.split(self.pytest_args)) def run(self): - import pytest + try: + import pytest + except ImportError: + sys.stderr.write('error: pytest is not installed\n') + sys.exit(1) # build extensions and byte-compile python build_ext = self.reinitialize_command('build_ext') @@ -712,12 +730,128 @@ class PyTest(Command): if self.coverage and os.path.exists(os.path.join(TOPDIR, '.coveragerc')): shutil.copyfile(os.path.join(TOPDIR, '.coveragerc'), os.path.join(builddir, '.coveragerc')) - os.chdir(builddir) - ret = subprocess.call([sys.executable, '-m', 'pytest'] + self.test_args) - os.chdir(TOPDIR) + ret = subprocess.call([sys.executable, '-m', 'pytest'] + self.test_args, cwd=builddir) sys.exit(ret) +def print_check(message, if_yes='found', if_no='not found'): + """Decorator to print pre/post-check messages.""" + def sub_decorator(f): + def sub_func(*args, **kwargs): + sys.stderr.write('-- %s\n' % (message,)) + result = f(*args, **kwargs) + sys.stderr.write( + '-- %s -- %s\n' % (message, if_yes if result else if_no)) + return result + sub_func.pkgdist_config_decorated = True + return sub_func + return sub_decorator + + +def cache_check(cache_key): + """Method decorate to cache check result.""" + def sub_decorator(f): + def sub_func(self, *args, **kwargs): + if cache_key in self.cache: + return self.cache[cache_key] + result = f(self, *args, **kwargs) + self.cache[cache_key] = result + return result + sub_func.pkgdist_config_decorated = True + return sub_func + return sub_decorator + + +def check_define(define_name): + """Method decorator to store check result.""" + def sub_decorator(f): + @cache_check(define_name) + def sub_func(self, *args, **kwargs): + result = f(self, *args, **kwargs) + self.check_defines[define_name] = result + return result + sub_func.pkgdist_config_decorated = True + return sub_func + return sub_decorator + + +class config(dst_config.config): + """Perform platform checks for extension build.""" + + user_options = dst_config.config.user_options + [ + ("cache-path", "C", "path to read/write configuration cache"), + ] + + def initialize_options(self): + self.cache_path = None + self.build_base = None + dst_config.config.initialize_options(self) + + def finalize_options(self): + if self.cache_path is None: + self.set_undefined_options( + 'build', + ('build_base', 'build_base')) + self.cache_path = os.path.join(self.build_base, 'config.cache') + dst_config.config.finalize_options(self) + + def _cache_env_key(self): + return (self.cc, self.include_dirs, self.libraries, self.library_dirs) + + @cache_check('_sanity_check') + @print_check('Performing basic C toolchain sanity check', 'works', 'broken') + def _sanity_check(self): + return self.try_link("int main(int argc, char *argv[]) { return 0; }") + + def run(self): + from snakeoil.pickling import dump, load + + # try to load the cached results + try: + with open(self.cache_path, 'rb') as f: + cache_db = load(f) + except (OSError, IOError): + cache_db = {} + else: + if self._cache_env_key() == cache_db.get('env_key'): + sys.stderr.write('-- Using cache from %s\n' % self.cache_path) + else: + sys.stderr.write('-- Build environment changed, discarding cache\n') + cache_db = {} + + self.cache = cache_db.get('cache', {}) + self.check_defines = {} + + if not self._sanity_check(): + sys.stderr.write('The C toolchain is unable to compile & link a simple C program!\n') + sys.exit(1) + + # run all decorated methods + for k in dir(self): + if k.startswith('_'): + continue + if hasattr(getattr(self, k), 'pkgdist_config_decorated'): + getattr(self, k)() + + # store results in Distribution instance + self.distribution.check_defines = self.check_defines + # store updated cache + cache_db = { + 'cache': self.cache, + 'env_key': self._cache_env_key(), + } + self.mkpath(os.path.dirname(self.cache_path)) + with open(self.cache_path, 'wb') as f: + dump(cache_db, f) + + # == methods for custom checks == + def check_struct_member(self, typename, member, headers=None, include_dirs=None, lang="c"): + """Check whether typename (must be struct or union) has the named member.""" + return self.try_compile( + 'int main() { %s x; (void) x.%s; return 0; }' + % (typename, member), headers, include_dirs, lang) + + # yes these are in snakeoil.compatibility; we can't rely on that module however # since snakeoil source is in 2k form, but this module is 2k/3k compatible. # in other words, it could be invoked by py3k to translate snakeoil to py3k |