diff --git a/README.rst b/README.rst index 1367cf3c..f1a71349 100644 --- a/README.rst +++ b/README.rst @@ -18,3 +18,12 @@ codecs for use in data storage and communication applications. .. image:: https://codecov.io/gh/zarr-developers/numcodecs/branch/main/graph/badge.svg :target: https://codecov.io/gh/zarr-developers/numcodecs + +--- +If you already have native Blosc, Zstd, and LZ4 installed on your system and want to use these system libraries instead of the vendored sources, you +should set the `NUMCODECS_USE_SYSTEM_LIBS=1` environment variable when building the wheel, like this: + + $ NUMCODECS_USE_SYSTEM_LIBS=1 pip install numcodecs --no-binary numcodecs + +Blosc, Zstd, and LZ4 are found via the `pkg-config` utility. Moreover, you must build all 3 `blosc`, `libzstd`, and `liblz4` +components. C-Blosc comes with full sources for LZ4, LZ4HC, Snappy, Zlib and Zstd and in general, you should not worry about not having (or CMake not finding) the libraries in your system because by default the included sources will be automatically compiled and included in the C-Blosc library. This effectively means that you can be confident in having a complete support for all the codecs in all the Blosc deployments (unless you are explicitly excluding support for some of them). To compile blosc, see these [instructions](https://github.com/Blosc/c-blosc?tab=readme-ov-file#compiling-the-blosc-library). \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7c0d1f31..6344909e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ requires = [ "Cython", "py-cpuinfo", "numpy>2", + "pkgconfig" ] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index b6db0797..429c7166 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ from glob import glob import cpuinfo +import pkgconfig from Cython.Distutils.build_ext import new_build_ext as build_ext from setuptools import Extension, setup from setuptools.errors import CCompilerError, ExecError, PlatformError @@ -17,6 +18,7 @@ have_avx2 = 'avx2' in flags disable_sse2 = 'DISABLE_NUMCODECS_SSE2' in os.environ disable_avx2 = 'DISABLE_NUMCODECS_AVX2' in os.environ +use_system_libraries = 'NUMCODECS_USE_SYSTEM_LIBS' in os.environ # setup common compile arguments have_cflags = 'CFLAGS' in os.environ @@ -49,8 +51,8 @@ def error(*msg): print('[numcodecs]', *msg, **kwargs) -def blosc_extension(): - info('setting up Blosc extension') +def _blosc_extension_with_vendored_libs(): + info('setting up Blosc extension from vendored sources') extra_compile_args = base_compile_args.copy() define_macros = [] @@ -125,8 +127,68 @@ def blosc_extension(): return extensions -def zstd_extension(): - info('setting up Zstandard extension') +def _blosc_extension_with_system_libs(): + info('setting up Blosc extension with system libraries') + + extra_compile_args = base_compile_args.copy() + + blosc_package_configuration = pkgconfig.parse("blosc") + + define_macros = blosc_package_configuration["define_macros"] + include_dirs = blosc_package_configuration["include_dirs"] + libraries = blosc_package_configuration["libraries"] + library_dirs = blosc_package_configuration["library_dirs"] + + # remove minizip because Python.h 3.8 tries to include crypt.h + include_dirs = [d for d in include_dirs if 'minizip' not in d] + + # define_macros += [('CYTHON_TRACE', '1')] + + # SSE2 + if have_sse2 and not disable_sse2: + info('compiling Blosc extension with SSE2 support') + extra_compile_args.append('-DSHUFFLE_SSE2_ENABLED') + if os.name == 'nt': + define_macros += [('__SSE2__', 1)] + else: + info('compiling Blosc extension without SSE2 support') + + # AVX2 + if have_avx2 and not disable_avx2: + info('compiling Blosc extension with AVX2 support') + extra_compile_args.append('-DSHUFFLE_AVX2_ENABLED') + if os.name == 'nt': + define_macros += [('__AVX2__', 1)] + else: + info('compiling Blosc extension without AVX2 support') + + sources = ['numcodecs/blosc.pyx'] + + # define extension module + extensions = [ + Extension( + 'numcodecs.blosc', + sources=sources, + include_dirs=include_dirs, + define_macros=define_macros, + extra_compile_args=extra_compile_args, + libraries=libraries, + library_dirs=library_dirs, + ), + ] + + return extensions + + +def blosc_extension(): + if use_system_libraries: + return _blosc_extension_with_system_libs() + else: + return _blosc_extension_with_vendored_libs() + + +def _zstd_extension_with_vendored_sources(): + info('setting up Zstandard extension from vendored sources') zstd_sources = [] extra_compile_args = base_compile_args.copy() @@ -167,8 +229,46 @@ def zstd_extension(): return extensions -def lz4_extension(): - info('setting up LZ4 extension') +def _zstd_extension_with_system_libs(): + info('setting up Zstandard extension with system libraries') + + extra_compile_args = base_compile_args.copy() + + zstd_package_configuration = pkgconfig.parse("libzstd") + include_dirs = zstd_package_configuration["include_dirs"] + define_macros = zstd_package_configuration["define_macros"] + libraries = zstd_package_configuration["libraries"] + library_dirs = zstd_package_configuration["library_dirs"] + + # define_macros += [('CYTHON_TRACE', '1')] + + sources = ['numcodecs/zstd.pyx'] + + # define extension module + extensions = [ + Extension( + 'numcodecs.zstd', + sources=sources, + include_dirs=include_dirs, + define_macros=define_macros, + extra_compile_args=extra_compile_args, + libraries=libraries, + library_dirs=library_dirs, + ), + ] + + return extensions + + +def zstd_extension(): + if use_system_libraries: + return _zstd_extension_with_system_libs() + else: + return _zstd_extension_with_vendored_sources() + + +def _lz4_extension_with_vendored_sources(): + info('setting up LZ4 extension from vendored sources') extra_compile_args = base_compile_args.copy() define_macros = [] @@ -195,6 +295,45 @@ def lz4_extension(): return extensions +def _lz4_extension_with_system_libs(): + info('setting up LZ4 extension with system libraries') + + extra_compile_args = base_compile_args.copy() + + lz4_package_configuration = pkgconfig.parse("liblz4") + include_dirs = lz4_package_configuration["include_dirs"] + define_macros = lz4_package_configuration["define_macros"] + libraries = lz4_package_configuration["libraries"] + library_dirs = lz4_package_configuration["library_dirs"] + + include_dirs += ['numcodecs'] + # define_macros += [('CYTHON_TRACE', '1')] + + sources = ['numcodecs/lz4.pyx'] + + # define extension module + extensions = [ + Extension( + 'numcodecs.lz4', + sources=sources, + include_dirs=include_dirs, + define_macros=define_macros, + extra_compile_args=extra_compile_args, + libraries=libraries, + library_dirs=library_dirs, + ), + ] + + return extensions + + +def lz4_extension(): + if use_system_libraries: + return _lz4_extension_with_system_libs() + else: + return _lz4_extension_with_vendored_sources() + + def vlen_extension(): info('setting up vlen extension') import numpy @@ -333,14 +472,14 @@ def run(self): build_ext.run(self) except PlatformError as e: error(e) - raise BuildFailed from e + raise BuildFailed def build_extension(self, ext): try: build_ext.build_extension(self, ext) except ext_errors as e: error(e) - raise BuildFailed from e + raise BuildFailed class Sclean(clean): @@ -368,7 +507,10 @@ def run_setup(with_extensions): + jenkins_extension() ) - cmdclass = dict(build_ext=ve_build_ext, clean=Sclean) + if use_system_libraries: + cmdclass = dict(build_ext=build_ext, clean=clean) + else: + cmdclass = dict(build_ext=ve_build_ext, clean=Sclean) else: ext_modules = [] cmdclass = {}