aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Harder <radhermit@gmail.com>2016-02-22 17:36:44 -0500
committerTim Harder <radhermit@gmail.com>2016-02-22 17:36:44 -0500
commit3d78c9acc5b8fbce28faa7a51285869d198ad849 (patch)
treeb0ff85eb43801968c512b96efa04cb5facc1d121
parentpquery: use stripped IUSE for --has-use (diff)
downloadpkgcore-3d78c9acc5b8fbce28faa7a51285869d198ad849.tar.gz
pkgcore-3d78c9acc5b8fbce28faa7a51285869d198ad849.tar.bz2
pkgcore-3d78c9acc5b8fbce28faa7a51285869d198ad849.zip
update pkgdist
-rw-r--r--pkgdist.py160
1 files changed, 147 insertions, 13 deletions
diff --git a/pkgdist.py b/pkgdist.py
index bdc86155..8a7f5795 100644
--- a/pkgdist.py
+++ b/pkgdist.py
@@ -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