Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework build to build snappy as a static lib first #60

Merged
merged 6 commits into from
Dec 5, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 106 additions & 83 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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"""

Expand Down Expand Up @@ -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):
Expand All @@ -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 = [
Expand All @@ -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"""
Expand Down Expand Up @@ -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)],
}

Expand Down Expand Up @@ -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

Expand All @@ -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 <PROJECT>._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
Expand Down Expand Up @@ -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

Expand All @@ -494,5 +516,6 @@ def find_package_modules(self, package, package_dir):
install_requires=['h5py'],
setup_requires=['setuptools'],
cmdclass=cmdclass,
libraries=libraries,
)