diff --git a/.gitignore b/.gitignore index a81c8ee..7fcec70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +src/soxr/_csoxr_version.c +src/soxr/_version.py + + +### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -94,7 +99,22 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# PEP 582; used by e.g. github.com/David-OConnor/pyflow +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff @@ -136,3 +156,20 @@ dmypy.json # Cython debug symbols cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json diff --git a/BUILDING.md b/BUILDING.md index b9abbe0..cbfa422 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -23,11 +23,18 @@ python -m build libsoxr should be installed before building. (e.g. `sudo apt install libsoxr-dev`) ``` -python -m build -C=--global-option=--use-system-libsoxr +python -m build -C--build-option=--use-system-libsoxr ``` It will link libsoxr dynamically and libsoxr won't bundled in the wheel package. +## Install +Install built .whl package(not .tar.gz sdist). +``` +pip install dist/soxr-[...].whl +``` + + ## Test ``` # Install dependencies diff --git a/MANIFEST.in b/MANIFEST.in index 64e7c8b..42c55a5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include libsoxr/src/*.c include libsoxr/src/*.h include libsoxr/COPYING.LGPL include libsoxr/LICENCE +include src/soxr/csoxr_version.c include src/soxr/*.h include src/soxr/*.pxd include src/soxr/*.py diff --git a/pyproject.toml b/pyproject.toml index 26c966b..77c7184 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,10 @@ requires = [ "setuptools>=42", "wheel", "Cython>=3.0a7", - "setuptools_scm[toml]>=3.4", + "setuptools_scm[toml]>=6.2", "oldest-supported-numpy" ] build-backend = "setuptools.build_meta" [tool.setuptools_scm] -write_to = "src/soxr/version.py" +write_to = "src/soxr/_version.py" diff --git a/setup.py b/setup.py index faea915..c5a67c0 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,23 @@ +""" +Python-SoXR setup.py for Cython build + +Please refer to BUILDING.md for building instructions. +Do not run setup.py directly for the build process. +""" + +import logging +import subprocess import sys import sysconfig -from setuptools import setup, Extension - from distutils.ccompiler import get_default_compiler +from setuptools import setup, Extension +from setuptools.command.sdist import sdist SYS_LIBSOXR = False -# python -m build -C=--global-option=--use-system-libsoxr +# python -m build -C=--build-option=--use-system-libsoxr if '--use-system-libsoxr' in sys.argv: sys.argv.remove('--use-system-libsoxr') SYS_LIBSOXR = True @@ -28,7 +37,37 @@ def include_dirs(self): def include_dirs(self, dirs): self._include = dirs -src_libsoxr = [ + +def get_git_version(cwd=''): + try: + result = subprocess.run( + ['git', 'describe', '--tags', '--always', '--dirty'], + cwd=cwd, capture_output=True, check=True, text=True) + + ver = result.stdout.strip() + return ver + except Exception as e: + logging.warning(f'Error retrieving submodule version: {e}') + return 'unknown' + + +CSOXR_VERSION_C = ''' +#include "csoxr_version.h" +const char * libsoxr_version() { return "%s"; } +''' + + +class SDistBundledCommand(sdist): + def run(self): + ver = get_git_version('libsoxr') + with open(f'src/soxr/_csoxr_version.c', 'wt') as f: + f.write(CSOXR_VERSION_C % (ver)) + logging.info(f'libsoxr version: {ver}') + + super().run() + + +src_static = [ 'libsoxr/src/soxr.c', 'libsoxr/src/data-io.c', 'libsoxr/src/dbesi0.c', @@ -55,11 +94,16 @@ def include_dirs(self, dirs): # 'libsoxr/src/cr64s.c', # 'libsoxr/src/pffft64s.c', # 'libsoxr/src/util64s.c', + + # Cython wrapper + 'src/soxr/cysoxr.pyx', + 'src/soxr/_csoxr_version.c', # csoxr libsoxr_version() ] -src = [ +src_dynamic = [ # Cython wrapper - 'src/soxr/cysoxr.pyx' + 'src/soxr/cysoxr.pyx', + 'src/soxr/csoxr_version.c', # libsoxr soxr_version() ] compile_args = ['-DSOXR_LIB'] @@ -76,14 +120,14 @@ def include_dirs(self, dirs): extensions = [ CySoxrExtension( "soxr.cysoxr", - src_libsoxr + src, + src_static, include_dirs=['libsoxr/src', 'src/soxr'], language="c", extra_compile_args=compile_args) ] extensions_dynamic = [ - CySoxrExtension('soxr.cysoxr', src, language='c', libraries=['soxr']) + CySoxrExtension('soxr.cysoxr', src_dynamic, language='c', libraries=['soxr']) ] @@ -91,10 +135,13 @@ def include_dirs(self, dirs): from Cython.Build import cythonize if SYS_LIBSOXR: + logging.info('Building Python-SoXR using system libsoxr...') setup( ext_modules=cythonize(extensions_dynamic, language_level='3'), ) else: + logging.info('Building Python-SoXR using bundled libsoxr...') setup( + cmdclass={'sdist': SDistBundledCommand}, ext_modules=cythonize(extensions, language_level='3'), ) diff --git a/src/soxr/__init__.py b/src/soxr/__init__.py index afb01c4..8c5ca02 100644 --- a/src/soxr/__init__.py +++ b/src/soxr/__init__.py @@ -7,13 +7,12 @@ import numpy as np -from .cysoxr import CySoxr -from .cysoxr import cysoxr_divide_proc -from .cysoxr import cysoxr_oneshot +from . import cysoxr from .cysoxr import QQ, LQ, MQ, HQ, VHQ +from ._version import __version__ -from .version import version as __version__ +__libsoxr_version__ = cysoxr.libsoxr_version() # libsoxr locates memory per each channel. # Too much channels will cause memory error. @@ -83,7 +82,7 @@ def __init__(self, q = _quality_to_enum(quality) - self._cysoxr = CySoxr(in_rate, out_rate, num_channels, self._type.type, q) + self._cysoxr = cysoxr.CySoxr(in_rate, out_rate, num_channels, self._type.type, q) def resample_chunk(self, x, last=False): """ Resample chunk with streaming resampler @@ -157,14 +156,14 @@ def resample(x, in_rate: float, out_rate: float, quality='HQ'): x = np.ascontiguousarray(x) # make array C-contiguous if x.ndim == 1: - y = cysoxr_divide_proc(in_rate, out_rate, x[:, np.newaxis], q) + y = cysoxr.cysoxr_divide_proc(in_rate, out_rate, x[:, np.newaxis], q) return np.squeeze(y, axis=1) elif x.ndim == 2: num_channels = x.shape[1] if num_channels < 1 or _CH_LIMIT < num_channels: raise ValueError(_CH_EXEED_ERR_STR.format(num_channels)) - return cysoxr_divide_proc(in_rate, out_rate, x, q) + return cysoxr.cysoxr_divide_proc(in_rate, out_rate, x, q) else: raise ValueError('Input must be 1-D or 2-D array') @@ -175,4 +174,4 @@ def _resample_oneshot(x, in_rate: float, out_rate: float, quality='HQ'): `soxr_oneshot()` becomes slow with long input. This function exists for test purpose. """ - return cysoxr_oneshot(in_rate, out_rate, x, _quality_to_enum(quality)) + return cysoxr.cysoxr_oneshot(in_rate, out_rate, x, _quality_to_enum(quality)) diff --git a/src/soxr/_csoxr_version.c b/src/soxr/_csoxr_version.c new file mode 100644 index 0000000..a5785fa --- /dev/null +++ b/src/soxr/_csoxr_version.c @@ -0,0 +1,5 @@ +#include "csoxr_version.h" + +const char * libsoxr_version() { + return "undefined"; +} diff --git a/src/soxr/csoxr.pxd b/src/soxr/csoxr.pxd index 8103c52..d3d0246 100644 --- a/src/soxr/csoxr.pxd +++ b/src/soxr/csoxr.pxd @@ -58,3 +58,7 @@ cdef extern from 'soxr.h': cdef unsigned long SOXR_VHQ cdef soxr_io_spec_t soxr_io_spec(soxr_datatype_t itype, soxr_datatype_t otype) + + +cdef extern from 'csoxr_version.h': + cdef const char * libsoxr_version() diff --git a/src/soxr/csoxr_version.c b/src/soxr/csoxr_version.c new file mode 100644 index 0000000..7045381 --- /dev/null +++ b/src/soxr/csoxr_version.c @@ -0,0 +1,6 @@ +#include +#include "csoxr_version.h" + +const char * libsoxr_version() { + return soxr_version(); +} diff --git a/src/soxr/csoxr_version.h b/src/soxr/csoxr_version.h new file mode 100644 index 0000000..87d1951 --- /dev/null +++ b/src/soxr/csoxr_version.h @@ -0,0 +1 @@ +const char * libsoxr_version(); diff --git a/src/soxr/cysoxr.pyx b/src/soxr/cysoxr.pyx index 85d61f2..f8f52f4 100644 --- a/src/soxr/cysoxr.pyx +++ b/src/soxr/cysoxr.pyx @@ -24,6 +24,10 @@ ctypedef fused datatype_t: cython.short +cpdef str libsoxr_version(): + return csoxr.libsoxr_version().decode('UTF-8') + + # NumPy scalar type to soxr_io_spec_t cdef csoxr.soxr_io_spec_t to_io_spec(type ntype): cdef csoxr.soxr_datatype_t io_type