diff --git a/setup.py b/setup.py index cd2b9481..f5027633 100644 --- a/setup.py +++ b/setup.py @@ -33,10 +33,11 @@ import sys import tempfile from setuptools import setup, Extension -from setuptools.command.build_py import build_py as _build_py +from setuptools.command.build_py import build_py from setuptools.command.build_ext import build_ext from distutils.command.build import build -from distutils.errors import CompileError +from distutils import ccompiler, errors, sysconfig + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -85,6 +86,32 @@ def get_cpu_sse2_avx2(): # Plugins +def check_compile_flag(compiler, flag, extension='.c'): + """Try to compile an empty file to check for compiler args + + :param distutils.ccompiler.CCompiler compiler: The compiler to use + :param str flag: Flag argument to pass to compiler + :param str extension: Source file extension (default: '.c') + :returns: Whether or not compilation was successful + :rtype: bool + """ + if sys.version_info[0] < 3: + return False # Not implemented for Python 2.7 + + with tempfile.TemporaryDirectory() as tmp_dir: + # Create empty source file + tmp_file = os.path.join(tmp_dir, 'source' + extension) + with open(tmp_file, 'w') as f: + f.write('int main (int argc, char **argv) { return 0; }\n') + + try: + compiler.compile([tmp_file], output_dir=tmp_dir, extra_postargs=[flag]) + except errors.CompileError: + return False + else: + return True + + class Build(build): """Build command with extra options used by PluginBuildExt""" @@ -115,11 +142,65 @@ def initialize_options(self): self.avx2 = True self.cpp11 = True + def finalize_options(self): + build.finalize_options(self) + + # Check that build options are available + compiler = ccompiler.new_compiler(compiler=self.compiler, force=True) + sysconfig.customize_compiler(compiler) + + if self.cpp11: + if compiler.compiler_type == 'msvc': + self.cpp11 = sys.version_info[:2] >= (3, 5) + else: + self.cpp1 = check_compile_flag( + compiler, '-std=c++11', extension='.cc') + if not self.cpp11: + logger.warning("C++11 disabled: not available") + + if self.sse2: + if compiler.compiler_type == 'msvc': + self.sse2 = sys.version_info[0] >= 3 + else: + self.sse2 = check_compile_flag(compiler, '-msse2') + if not self.sse2: + logger.warning("SSE2 disabled: not available") + + if self.avx2: + if compiler.compiler_type == 'msvc': + self.avx2 = sys.version_info[:2] >= (3, 5) + else: + self.avx2 = check_compile_flag(compiler, '-mavx2') + if not self.avx2: + logger.warning("AVX2 disabled: not available") + + if self.openmp: + prefix = '/' if compiler.compiler_type == 'msvc' else '-' + self.openmp = check_compile_flag(compiler, prefix + 'openmp') + if not self.openmp: + logger.warning("OpenMP disabled: not available") + + if self.native: + is_cpu_sse2, is_cpu_avx2 = get_cpu_sse2_avx2() + self.sse2 = self.sse2 and is_cpu_sse2 + self.avx2 = self.avx2 and is_cpu_avx2 + + logger.info("Building with C++11: %r", bool(self.cpp11)) + logger.info('Building with native option: %r', bool(self.native)) + logger.info("Building with SSE2: %r", bool(self.sse2)) + logger.info("Building with AVX2: %r", bool(self.avx2)) + logger.info("Building with OpenMP: %r", bool(self.openmp)) + + # Filter out C++11 libraries if cpp11 option is False + self.distribution.libraries = [ + (name, info) for name, info in self.distribution.libraries + if self.cpp11 or '-std=c++11' not in info.get('cflags', [])] + class PluginBuildExt(build_ext): """Build extension command for DLLs that are not Python modules - This is actually only useful for Windows + It also handles extra compile arguments depending on the build options. """ def get_export_symbols(self, ext): @@ -145,73 +226,31 @@ def build_extensions(self): - Set hdf5 directory """ build_cmd = self.distribution.get_command_obj("build") - compiler_type = self.compiler.compiler_type - - # Check availability of compile flags - - if build_cmd.cpp11: - if compiler_type == 'msvc': - with_cpp11 = sys.version_info[:2] >= (3, 5) - else: - with_cpp11 = self.__check_compile_flag('-std=c++11', extension='.cpp') - else: - with_cpp11 = False - - if build_cmd.sse2: - if compiler_type == 'msvc': - with_sse2 = sys.version_info[0] >= 3 - else: - with_sse2 = self.__check_compile_flag('-msse2') - else: - with_sse2 = False - - if build_cmd.avx2: - if compiler_type == 'msvc': - with_avx2 = sys.version_info[:2] >= (3, 5) - else: - with_avx2 = self.__check_compile_flag('-mavx2') - else: - with_avx2 = False - - with_openmp = bool(build_cmd.openmp) and self.__check_compile_flag( - '/openmp' if compiler_type == 'msvc' else '-fopenmp') - - if build_cmd.native: - is_cpu_sse2, is_cpu_avx2 = get_cpu_sse2_avx2() - with_sse2 = with_sse2 and is_cpu_sse2 - with_avx2 = with_avx2 and is_cpu_avx2 - - logger.info("Building with C++11: %r", with_cpp11) - logger.info('Building with native option: %r', bool(build_cmd.native)) - logger.info("Building extensions with SSE2: %r", with_sse2) - logger.info("Building extensions with AVX2: %r", with_avx2) - logger.info("Building extensions with OpenMP: %r", with_openmp) - - prefix = '/' if compiler_type == 'msvc' else '-' + prefix = '/' if self.compiler.compiler_type == 'msvc' else '-' for e in self.extensions: if isinstance(e, HDF5PluginExtension): e.set_hdf5_dir(build_cmd.hdf5) - if with_cpp11: + if build_cmd.cpp11: for name, value in e.cpp11.items(): attribute = getattr(e, name) attribute += value # Enable SSE2/AVX2 if available and add corresponding resources - if with_sse2: + if build_cmd.sse2: e.extra_compile_args += ['-msse2'] # /arch:SSE2 is on by default for name, value in e.sse2.items(): attribute = getattr(e, name) attribute += value - if with_avx2: + if build_cmd.avx2: e.extra_compile_args += ['-mavx2', '/arch:AVX2'] for name, value in e.avx2.items(): attribute = getattr(e, name) attribute += value - if not with_openmp: # Remove OpenMP flags + if not build_cmd.openmp: # Remove OpenMP flags e.extra_compile_args = [ arg for arg in e.extra_compile_args if not arg.endswith('openmp')] e.extra_link_args = [ @@ -228,30 +267,6 @@ def build_extensions(self): build_ext.build_extensions(self) - def __check_compile_flag(self, flag, extension='.c'): - """Try to compile an empty file to check for compiler args - - :param str flag: Flag argument to pass to compiler - :param str extension: Source file extension (default: '.c') - :returns: Whether or not compilation was successful - :rtype: bool - """ - if sys.version_info[0] < 3: - return False # Not implemented for Python 2.7 - - with tempfile.TemporaryDirectory() as tmp_dir: - # Create empty source file - tmp_file = os.path.join(tmp_dir, 'source' + extension) - with open(tmp_file, 'w') as f: - f.write('int main (int argc, char **argv) { return 0; }\n') - - try: - self.compiler.compile([tmp_file], output_dir=tmp_dir, extra_postargs=[flag]) - except CompileError: - return False - else: - return True - class HDF5PluginExtension(Extension): """Extension adding specific things to build a HDF5 plugin""" @@ -367,10 +382,14 @@ def prefix(directory, files): define_macros.append(('HAVE_LZ4', 1)) # snappy -cpp11_kwargs = { +snappy_lib = ('snappy', { 'sources': glob(blosc_dir + 'internal-complibs/snappy*/*.cc'), 'include_dirs': glob(blosc_dir + 'internal-complibs/snappy*'), - 'extra_compile_args': ['-std=c++11', '-lstdc++'], + 'cflags': ['-std=c++11']}) + +cpp11_kwargs = { + 'include_dirs': glob(blosc_dir + 'internal-complibs/snappy*'), + 'extra_link_args': ['-lstdc++'], 'define_macros': [('HAVE_SNAPPY', 1)], } @@ -414,10 +433,12 @@ def prefix(directory, files): ) -extensions=[lz4_plugin, - bithsuffle_plugin, - blosc_plugin, - ] +libraries = [snappy_lib] + +extensions = [lz4_plugin, + bithsuffle_plugin, + blosc_plugin, + ] # setup @@ -434,12 +455,12 @@ def get_version(): return version.strictversion -class build_py(_build_py): +class BuildPy(build_py): """ Enhanced build_py which copies version.py to ._version.py """ def find_package_modules(self, package, package_dir): - modules = _build_py.find_package_modules(self, package, package_dir) + modules = build_py.find_package_modules(self, package, package_dir) if package == PROJECT: modules.append((PROJECT, '_version', 'version.py')) return modules @@ -471,11 +492,12 @@ def find_package_modules(self, package, package_dir): "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Software Development :: Libraries :: Python Modules", ] cmdclass = dict(build=Build, build_ext=PluginBuildExt, - build_py=build_py) + build_py=BuildPy) if BDistWheel is not None: cmdclass['bdist_wheel'] = BDistWheel @@ -494,5 +516,6 @@ def find_package_modules(self, package, package_dir): install_requires=['h5py'], setup_requires=['setuptools'], cmdclass=cmdclass, + libraries=libraries, )