From 07c6c92a52a684c520806ea542789a357ce7b431 Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 11:51:16 -0600 Subject: [PATCH 01/39] FEAT: add version and library type --- coincurve/_secp256k1_library_info.py | 2 ++ coincurve/_version.py | 11 +++++++++++ setup.py | 18 ++++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 coincurve/_secp256k1_library_info.py create mode 100644 coincurve/_version.py diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py new file mode 100644 index 000000000..8783e8bf5 --- /dev/null +++ b/coincurve/_secp256k1_library_info.py @@ -0,0 +1,2 @@ +SECP256K1_LIBRARY_TYPE = "INTERNAL" +SECP256K1_LIBRARY_NAME = "_TBD_" diff --git a/coincurve/_version.py b/coincurve/_version.py new file mode 100644 index 000000000..4a8889337 --- /dev/null +++ b/coincurve/_version.py @@ -0,0 +1,11 @@ +# This file is imported from __init__.py and exec'd from setup.py + +MAJOR = 19 +MINOR = 0 +MICRO = 1 +RELEASE = True + +__version__ = '%d.%d.%d' % (MAJOR, MINOR, MICRO) + +if not RELEASE: + __version__ += '.dev0' diff --git a/setup.py b/setup.py index 9e5908884..ed0bf52d4 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,9 @@ import tarfile from io import BytesIO import sys +from os.path import dirname, abspath, join +from sys import path as PATH + from setuptools import Distribution as _Distribution, setup, find_packages, __version__ as setuptools_version from setuptools._distutils import log @@ -24,7 +27,9 @@ except ImportError: _bdist_wheel = None -sys.path.append(os.path.abspath(os.path.dirname(__file__))) +COINCURVE_SRC_DIR = dirname(abspath(__file__)) +PATH.append(COINCURVE_SRC_DIR) + from setup_support import absolute, build_flags, detect_dll, has_system_lib BUILDING_FOR_WINDOWS = detect_dll() @@ -38,6 +43,15 @@ LIB_TARBALL_URL = f'https://github.com/bitcoin-core/secp256k1/archive/{UPSTREAM_REF}.tar.gz' +globals_ = {} +with open(join(COINCURVE_SRC_DIR, 'coincurve', '_version.py')) as fp: + exec(fp.read(), globals_) # noqa S102 + __version__ = globals_['__version__'] + +with open(join(COINCURVE_SRC_DIR, 'coincurve', '_secp256k1_library_info.py'), 'w') as fp: + fp.write(f'SECP256K1_LIBRARY_TYPE = "INTERNAL"\n') + fp.write('SECP256K1_LIBRARY_NAME = "_TBD_"\n') + # We require setuptools >= 3.3 if [int(i) for i in setuptools_version.split('.', 2)[:2]] < [3, 3]: raise SystemExit( @@ -331,7 +345,7 @@ def has_c_libraries(self): setup( name='coincurve', - version='19.0.1', + version=__version__, description='Cross-platform Python CFFI bindings for libsecp256k1', long_description=open('README.md', 'r').read(), From aca7cca78289cb86b9c87877bae0d3fbdf856fb4 Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 11:55:20 -0600 Subject: [PATCH 02/39] (fix) hasty push fails fmt --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ed0bf52d4..32abcd136 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ COINCURVE_SRC_DIR = dirname(abspath(__file__)) PATH.append(COINCURVE_SRC_DIR) -from setup_support import absolute, build_flags, detect_dll, has_system_lib +from setup_support import absolute, build_flags, detect_dll, has_system_lib # noqa: E402 BUILDING_FOR_WINDOWS = detect_dll() @@ -49,7 +49,7 @@ __version__ = globals_['__version__'] with open(join(COINCURVE_SRC_DIR, 'coincurve', '_secp256k1_library_info.py'), 'w') as fp: - fp.write(f'SECP256K1_LIBRARY_TYPE = "INTERNAL"\n') + fp.write('SECP256K1_LIBRARY_TYPE = "INTERNAL"\n') fp.write('SECP256K1_LIBRARY_NAME = "_TBD_"\n') # We require setuptools >= 3.3 From 84d75490f87f76c3d5620f129988f30a7c11915a Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 11:58:30 -0600 Subject: [PATCH 03/39] (fix) quotes mishap --- coincurve/_secp256k1_library_info.py | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py index 8783e8bf5..3b7e34a24 100644 --- a/coincurve/_secp256k1_library_info.py +++ b/coincurve/_secp256k1_library_info.py @@ -1,2 +1,2 @@ -SECP256K1_LIBRARY_TYPE = "INTERNAL" -SECP256K1_LIBRARY_NAME = "_TBD_" +SECP256K1_LIBRARY_TYPE = 'INTERNAL' +SECP256K1_LIBRARY_NAME = '_TBD_' diff --git a/setup.py b/setup.py index 32abcd136..1777dfd58 100644 --- a/setup.py +++ b/setup.py @@ -49,8 +49,8 @@ __version__ = globals_['__version__'] with open(join(COINCURVE_SRC_DIR, 'coincurve', '_secp256k1_library_info.py'), 'w') as fp: - fp.write('SECP256K1_LIBRARY_TYPE = "INTERNAL"\n') - fp.write('SECP256K1_LIBRARY_NAME = "_TBD_"\n') + fp.write('SECP256K1_LIBRARY_TYPE = \'INTERNAL\'\n') + fp.write('SECP256K1_LIBRARY_NAME = \'_TBD_\'\n') # We require setuptools >= 3.3 if [int(i) for i in setuptools_version.split('.', 2)[:2]] < [3, 3]: From 7a7ff10883c5f15f6637c93121ee9349a05c0821 Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 14:05:53 -0600 Subject: [PATCH 04/39] (fix) quotes --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1777dfd58..3bf4db737 100644 --- a/setup.py +++ b/setup.py @@ -49,8 +49,8 @@ __version__ = globals_['__version__'] with open(join(COINCURVE_SRC_DIR, 'coincurve', '_secp256k1_library_info.py'), 'w') as fp: - fp.write('SECP256K1_LIBRARY_TYPE = \'INTERNAL\'\n') - fp.write('SECP256K1_LIBRARY_NAME = \'_TBD_\'\n') + fp.write("SECP256K1_LIBRARY_TYPE = 'INTERNAL'\n") + fp.write("SECP256K1_LIBRARY_NAME = '_TBD_'\n") # We require setuptools >= 3.3 if [int(i) for i in setuptools_version.split('.', 2)[:2]] < [3, 3]: From 3bac0428c41a2de9c10bd92d8d4d6a78c28c00ee Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 14:43:55 -0600 Subject: [PATCH 05/39] (fix) ignore utility module in coverage --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index 42d5ef84e..8b7599ad1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,6 +6,8 @@ source = tests omit = + */_secp256k1_library_info.py + */_version.py */_windows_libsecp256k1.py */test_bench.py From 867219be12453e1d6566ad12c1d4c84fc2cd4a5a Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 15:35:32 -0600 Subject: [PATCH 06/39] (ref) update tests with pre-laoding of external libsecp256k1 --- coincurve/__init__.py | 29 +++++++++++++++++++++++++++++ tests/conftest.py | 7 +++++++ 2 files changed, 36 insertions(+) diff --git a/coincurve/__init__.py b/coincurve/__init__.py index 17c56a15e..4e3b0847b 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -1,12 +1,41 @@ +import ctypes +import os +import platform +import warnings + +from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE from coincurve.context import GLOBAL_CONTEXT, Context from coincurve.keys import PrivateKey, PublicKey, PublicKeyXOnly from coincurve.utils import verify_signature __all__ = [ 'GLOBAL_CONTEXT', + 'SECP256K1_LIBRARY_TYPE', + 'SECP256K1_LIBRARY_NAME', 'Context', 'PrivateKey', 'PublicKey', 'PublicKeyXOnly', 'verify_signature', ] + + +def load_secp256k1_conda_library(): + if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': + return + + if (conda := os.getenv('CONDA_PREFIX')) is None: + warnings.warn('This coincurve package requires a CONDA environment', stacklevel=2) + return + + if platform.system() == 'Windows': + library = os.path.join(conda, 'Library', 'bin', f'{SECP256K1_LIBRARY_NAME}.dll') + elif platform.system() == 'Darwin': + library = os.path.join(conda, 'lib', f'{SECP256K1_LIBRARY_NAME}.dylib') + else: + library = os.path.join(conda, 'lib', f'{SECP256K1_LIBRARY_NAME}.so') + + try: + ctypes.CDLL(library) + except Exception as e: + warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}.so/dylib/dll is not loaded.\n{e}', stacklevel=2) diff --git a/tests/conftest.py b/tests/conftest.py index b42b96084..fa829bf90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,3 +73,10 @@ def samples(): 'X_ONLY_PUBKEY': X_ONLY_PUBKEY, 'X_ONLY_PUBKEY_INVALID': X_ONLY_PUBKEY_INVALID, } + + +@pytest.fixture(autouse=True) +def load_secp256k1_library(): + from coincurve import load_secp256k1_conda_library + + load_secp256k1_conda_library() From 7b87106fb268255142c4989d1d81f555a27bd256 Mon Sep 17 00:00:00 2001 From: memento Date: Sat, 9 Mar 2024 10:47:57 -0600 Subject: [PATCH 07/39] (fix) add library loader at package initilization --- coincurve/__init__.py | 34 ++++++++++++++++------------ coincurve/_secp256k1_library_info.py | 2 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/coincurve/__init__.py b/coincurve/__init__.py index 4e3b0847b..324612cc4 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -4,24 +4,12 @@ import warnings from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE -from coincurve.context import GLOBAL_CONTEXT, Context -from coincurve.keys import PrivateKey, PublicKey, PublicKeyXOnly -from coincurve.utils import verify_signature - -__all__ = [ - 'GLOBAL_CONTEXT', - 'SECP256K1_LIBRARY_TYPE', - 'SECP256K1_LIBRARY_NAME', - 'Context', - 'PrivateKey', - 'PublicKey', - 'PublicKeyXOnly', - 'verify_signature', -] def load_secp256k1_conda_library(): + """Load the secp256k1 library from the conda environment.""" if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': + warnings.warn(f'DBG: {SECP256K1_LIBRARY_NAME}:{SECP256K1_LIBRARY_TYPE}', stacklevel=2) return if (conda := os.getenv('CONDA_PREFIX')) is None: @@ -39,3 +27,21 @@ def load_secp256k1_conda_library(): ctypes.CDLL(library) except Exception as e: warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}.so/dylib/dll is not loaded.\n{e}', stacklevel=2) + + +load_secp256k1_conda_library() + +from coincurve.context import GLOBAL_CONTEXT, Context # noqa: E402 +from coincurve.keys import PrivateKey, PublicKey, PublicKeyXOnly # noqa: E402 +from coincurve.utils import verify_signature # noqa: E402 + +__all__ = [ + 'GLOBAL_CONTEXT', + 'SECP256K1_LIBRARY_TYPE', + 'SECP256K1_LIBRARY_NAME', + 'Context', + 'PrivateKey', + 'PublicKey', + 'PublicKeyXOnly', + 'verify_signature', +] diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py index 3b7e34a24..b6bf4bb0f 100644 --- a/coincurve/_secp256k1_library_info.py +++ b/coincurve/_secp256k1_library_info.py @@ -1,2 +1,2 @@ SECP256K1_LIBRARY_TYPE = 'INTERNAL' -SECP256K1_LIBRARY_NAME = '_TBD_' +SECP256K1_LIBRARY_NAME = 'libsecp256k1-2' From d05ab644799cd85a973e24cdcebb3faf6db289ea Mon Sep 17 00:00:00 2001 From: memento Date: Sat, 9 Mar 2024 10:54:30 -0600 Subject: [PATCH 08/39] (fix) ignore conftest.py --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b0ae48c16..3079821fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ lint.unfixable = [ "F401", ] extend-exclude = [ - "_cffi_build/compose_cffi_files.py", + "tests/conftest.py", ] [tool.ruff.lint.isort] From 7bdcba826f03488ccc71612d33b515943f6d0dcb Mon Sep 17 00:00:00 2001 From: memento Date: Sat, 9 Mar 2024 13:10:00 -0600 Subject: [PATCH 09/39] (ref) lazy imports --- coincurve/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/coincurve/__init__.py b/coincurve/__init__.py index 324612cc4..739d15277 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -1,21 +1,21 @@ -import ctypes -import os -import platform -import warnings - -from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE - - def load_secp256k1_conda_library(): """Load the secp256k1 library from the conda environment.""" + import warnings + + from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE + if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': warnings.warn(f'DBG: {SECP256K1_LIBRARY_NAME}:{SECP256K1_LIBRARY_TYPE}', stacklevel=2) return + import os + if (conda := os.getenv('CONDA_PREFIX')) is None: warnings.warn('This coincurve package requires a CONDA environment', stacklevel=2) return + import platform + if platform.system() == 'Windows': library = os.path.join(conda, 'Library', 'bin', f'{SECP256K1_LIBRARY_NAME}.dll') elif platform.system() == 'Darwin': @@ -24,6 +24,8 @@ def load_secp256k1_conda_library(): library = os.path.join(conda, 'lib', f'{SECP256K1_LIBRARY_NAME}.so') try: + import ctypes + ctypes.CDLL(library) except Exception as e: warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}.so/dylib/dll is not loaded.\n{e}', stacklevel=2) @@ -37,8 +39,6 @@ def load_secp256k1_conda_library(): __all__ = [ 'GLOBAL_CONTEXT', - 'SECP256K1_LIBRARY_TYPE', - 'SECP256K1_LIBRARY_NAME', 'Context', 'PrivateKey', 'PublicKey', From 0d9d51b685b75773feae3d8ff07406eb229afd8b Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 14:12:27 -0500 Subject: [PATCH 10/39] (ref) reorganize preloading and conda specificity --- .github/workflows/build.yml | 39 ++++++++++++++++++++++++++++ coincurve/__init__.py | 34 ++++++++++++------------ coincurve/_secp256k1_library_info.py | 2 +- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 095909e6a..e2e74decf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -157,6 +157,45 @@ jobs: path: dist/* if-no-files-found: error + windows-wheels-conda-x86_64: + name: Build Windows (CONDA) wheels AMD64 + needs: + - test + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Miniconda + uses: conda-incubator/setup-miniconda@v3 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.5 + env: + CIBW_ENVIRONMENT: + COINCURVE_IGNORE_SYSTEM_LIB="0" + COINCURVE_UPSTREAM_REF="__no_tag__" + COINCURVE_SECP256K1_BUILD='SHARED' + CIBW_BUILD: "cp312-win_amd64" + CIBW_ARCHS_WINDOWS: "AMD64" + CIBW_BEFORE_ALL: > + conda install -c conda-forge pkg-config libsecp256k1 + CIBW_BUILD_VERBOSITY: 1 + # pytest needs to be defined here to find 'coincurve' package? + CIBW_TEST_REQUIRES: pytest pytest-benchmark + CIBW_TEST_COMMAND: > + conda install -c conda-forge libsecp256k1 && + python -c + "from coincurve import PrivateKey; + a=PrivateKey(); + b=PrivateKey(); + assert a.ecdh(b.public_key.format())==b.ecdh(a.public_key.format()) + linux-wheels-arm: name: Build Linux wheels for ARM needs: diff --git a/coincurve/__init__.py b/coincurve/__init__.py index 739d15277..e6e999de9 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -1,34 +1,34 @@ def load_secp256k1_conda_library(): """Load the secp256k1 library from the conda environment.""" - import warnings - from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': - warnings.warn(f'DBG: {SECP256K1_LIBRARY_NAME}:{SECP256K1_LIBRARY_TYPE}', stacklevel=2) + # coincurve was built with an internal library, either static or shared. It 'knows' where the library is. return import os - if (conda := os.getenv('CONDA_PREFIX')) is None: - warnings.warn('This coincurve package requires a CONDA environment', stacklevel=2) - return + try: + import ctypes.util import find_library - import platform + # Find the library in the typical installation paths + if lib := find_library(SECP256K1_LIBRARY_NAME): + ctypes.CDLL(lib) + return - if platform.system() == 'Windows': - library = os.path.join(conda, 'Library', 'bin', f'{SECP256K1_LIBRARY_NAME}.dll') - elif platform.system() == 'Darwin': - library = os.path.join(conda, 'lib', f'{SECP256K1_LIBRARY_NAME}.dylib') - else: - library = os.path.join(conda, 'lib', f'{SECP256K1_LIBRARY_NAME}.so') + # Find the library in the conda environment + if (conda := os.getenv('CONDA_PREFIX')) is not None: + import platform - try: - import ctypes + if platform.system() == 'Windows': + library = os.path.join(conda, 'Library', 'bin', SECP256K1_LIBRARY_NAME) + else: + library = os.path.join(conda, 'lib', SECP256K1_LIBRARY_NAME) - ctypes.CDLL(library) + ctypes.CDLL(library) except Exception as e: - warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}.so/dylib/dll is not loaded.\n{e}', stacklevel=2) + import warnings + warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.\n{e}', stacklevel=2) load_secp256k1_conda_library() diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py index b6bf4bb0f..391e02e00 100644 --- a/coincurve/_secp256k1_library_info.py +++ b/coincurve/_secp256k1_library_info.py @@ -1,2 +1,2 @@ SECP256K1_LIBRARY_TYPE = 'INTERNAL' -SECP256K1_LIBRARY_NAME = 'libsecp256k1-2' +SECP256K1_LIBRARY_NAME = 'libsecp256k1-2.dll' From fd83fe46b24bb8e8b3b076dd38ad7e19abe4896b Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 14:17:22 -0500 Subject: [PATCH 11/39] (fix) import snafu --- coincurve/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/coincurve/__init__.py b/coincurve/__init__.py index e6e999de9..e21ee3f9b 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -1,5 +1,7 @@ def load_secp256k1_conda_library(): """Load the secp256k1 library from the conda environment.""" + import warnings + from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': @@ -7,13 +9,13 @@ def load_secp256k1_conda_library(): return import os + from ctypes import CDLL + from ctypes.util import find_library try: - import ctypes.util import find_library - # Find the library in the typical installation paths - if lib := find_library(SECP256K1_LIBRARY_NAME): - ctypes.CDLL(lib) + if library := find_library(SECP256K1_LIBRARY_NAME): + CDLL(library) return # Find the library in the conda environment @@ -25,9 +27,9 @@ def load_secp256k1_conda_library(): else: library = os.path.join(conda, 'lib', SECP256K1_LIBRARY_NAME) - ctypes.CDLL(library) + CDLL(library) + return except Exception as e: - import warnings warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.\n{e}', stacklevel=2) From 658b2856e24fa5d1c80c15e1c4e3b1416b27bb74 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 14:35:04 -0500 Subject: [PATCH 12/39] (ref) lazy import warnings --- coincurve/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coincurve/__init__.py b/coincurve/__init__.py index e21ee3f9b..fcbd23969 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -1,7 +1,5 @@ def load_secp256k1_conda_library(): """Load the secp256k1 library from the conda environment.""" - import warnings - from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': @@ -30,6 +28,7 @@ def load_secp256k1_conda_library(): CDLL(library) return except Exception as e: + import warnings warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.\n{e}', stacklevel=2) From bf920e6705aae3a92c0968db4b886e9a019071f5 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 14:46:17 -0500 Subject: [PATCH 13/39] (feat) add needed helper function. engineer PKG_CONFIG_PATH --- coincurve/__init__.py | 1 + coincurve/_secp256k1_library_info.py | 4 +- setup_support.py | 73 ++++++++++++++++++++++++++-- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/coincurve/__init__.py b/coincurve/__init__.py index fcbd23969..f800c2887 100644 --- a/coincurve/__init__.py +++ b/coincurve/__init__.py @@ -29,6 +29,7 @@ def load_secp256k1_conda_library(): return except Exception as e: import warnings + warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.\n{e}', stacklevel=2) diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py index 391e02e00..5b8a5c087 100644 --- a/coincurve/_secp256k1_library_info.py +++ b/coincurve/_secp256k1_library_info.py @@ -1,2 +1,2 @@ -SECP256K1_LIBRARY_TYPE = 'INTERNAL' -SECP256K1_LIBRARY_NAME = 'libsecp256k1-2.dll' +SECP256K1_LIBRARY_NAME = 'libsecp256k1' +SECP256K1_LIBRARY_TYPE = 'EXTERNAL' diff --git a/setup_support.py b/setup_support.py index 0aad51b86..5393a3890 100644 --- a/setup_support.py +++ b/setup_support.py @@ -38,11 +38,16 @@ def _find_lib(): from cffi import FFI + update_pkg_config_path() + try: - cmd = ['pkg-config', '--cflags-only-I', 'libsecp256k1'] - includes = subprocess_run(cmd) + if os.name == 'nt': + cmd = ['pkg-config', '--libs-only-L', '--dont-define-prefix', 'libsecp256k1'] + else: + cmd = ['pkg-config', '--libs-only-L', 'libsecp256k1'] + lib_dir = subprocess_run(cmd) - return os.path.exists(os.path.join(includes[2:], 'secp256k1_ecdh.h')) + return verify_system_lib(lib_dir[2:].strip()) except (OSError, subprocess.CalledProcessError): if 'LIB_DIR' in os.environ: @@ -84,3 +89,65 @@ def subprocess_run(cmd, *, debug=False): logging.error(f'An error occurred during the command execution: {e}') logging.error(f'Command log:\n{e.stderr}') raise e + + +def update_pkg_config_path(path='.'): + """Updates the PKG_CONFIG_PATH environment variable to include the given path.""" + pkg_config_paths = [path, os.getenv('PKG_CONFIG_PATH', '').strip('"')] + logging.warning(f'env: {os.getenv("PKG_CONFIG_PATH")}') + + if cpf := os.getenv('CONDA_PREFIX'): + conda_paths = [os.path.join(cpf, sbd, 'pkgconfig') for sbd in ('lib', 'lib64', os.path.join('Library', 'lib'))] + pkg_config_paths.extend([p for p in conda_paths if os.path.isdir(p)]) + + if lbd := os.getenv('LIB_DIR'): + pkg_config_paths.append(os.path.join(lbd, 'pkgconfig')) + + logging.warning(f'env: {pkg_config_paths}') + # Update environment + os.environ['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_paths) + + +def verify_system_lib(lib_dir): + """Verifies that the system library is installed and of the expected type.""" + import ctypes + import platform + from ctypes.util import find_library + from pathlib import Path + + LIB_NAME = 'libsecp256k1' # noqa N806 + PKG_NAME = 'coincurve' # noqa N806 + SECP256K1_BUILD = os.getenv('COINCURVE_SECP256K1_BUILD') or 'STATIC' # noqa N806 + SYSTEM = platform.system() # noqa N806 + + def load_library(lib): + try: + return ctypes.CDLL(lib) + except OSError: + return None + + logging.warning(f'find_library: {find_library(LIB_NAME[3:])}') + lib_dir = Path(lib_dir).with_name('bin') if SYSTEM == 'Windows' else Path(lib_dir) + lib_ext = '.dll' if SYSTEM == 'Windows' else '.[sd][oy]*' + logging.warning(f'dir: {lib_dir}') + logging.warning(f'patt: *{LIB_NAME[3:]}{lib_ext}') + l_dyn = list(lib_dir.glob(f'*{LIB_NAME[3:]}*{lib_ext}')) + + # Evaluates the dynamic libraries found, + logging.warning(f'Found libraries: {l_dyn}') + dyn_lib = next((lib for lib in l_dyn if load_library(lib) is not None), False) + + found = any((dyn_lib and SECP256K1_BUILD == 'SHARED', not dyn_lib and SECP256K1_BUILD != 'SHARED')) + if not found: + logging.warning( + f'WARNING: {LIB_NAME} is installed, but it is not the expected type. ' + f'Please ensure that the {SECP256K1_BUILD} library is installed.' + ) + + if dyn_lib: + lib_base = dyn_lib.stem + # Update coincurve._secp256k1_library_info + info_file = Path(PKG_NAME, '_secp256k1_library_info.py') + info_file.write_text(f"SECP256K1_LIBRARY_NAME = '{lib_base}'\nSECP256K1_LIBRARY_TYPE = 'EXTERNAL'\n") + + return found From 1903289e1aac14cde49087c55de343f0809ab082 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 15:06:45 -0500 Subject: [PATCH 14/39] (fix) remove _TBD_ placeholder. fix detection of system lib. add lib verification --- coincurve/_secp256k1_library_info.py | 2 +- setup.py | 4 ---- setup_support.py | 14 ++++---------- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py index 5b8a5c087..6142884d3 100644 --- a/coincurve/_secp256k1_library_info.py +++ b/coincurve/_secp256k1_library_info.py @@ -1,2 +1,2 @@ -SECP256K1_LIBRARY_NAME = 'libsecp256k1' +SECP256K1_LIBRARY_NAME = 'libsecp256k1-2.dll' SECP256K1_LIBRARY_TYPE = 'EXTERNAL' diff --git a/setup.py b/setup.py index 3bf4db737..41d54c6ad 100644 --- a/setup.py +++ b/setup.py @@ -48,10 +48,6 @@ exec(fp.read(), globals_) # noqa S102 __version__ = globals_['__version__'] -with open(join(COINCURVE_SRC_DIR, 'coincurve', '_secp256k1_library_info.py'), 'w') as fp: - fp.write("SECP256K1_LIBRARY_TYPE = 'INTERNAL'\n") - fp.write("SECP256K1_LIBRARY_NAME = '_TBD_'\n") - # We require setuptools >= 3.3 if [int(i) for i in setuptools_version.split('.', 2)[:2]] < [3, 3]: raise SystemExit( diff --git a/setup_support.py b/setup_support.py index 5393a3890..7e425ec31 100644 --- a/setup_support.py +++ b/setup_support.py @@ -33,11 +33,9 @@ def build_flags(library, type_, path): def _find_lib(): - if 'COINCURVE_IGNORE_SYSTEM_LIB' in os.environ: + if os.getenv('COINCURVE_IGNORE_SYSTEM_LIB', '1') == '1': return False - from cffi import FFI - update_pkg_config_path() try: @@ -50,13 +48,9 @@ def _find_lib(): return verify_system_lib(lib_dir[2:].strip()) except (OSError, subprocess.CalledProcessError): - if 'LIB_DIR' in os.environ: - for path in glob.glob(os.path.join(os.environ['LIB_DIR'], '*secp256k1*')): - with suppress(OSError): - FFI().dlopen(path) - return True - # We couldn't locate libsecp256k1, so we'll use the bundled one - return False + from ctypes.util import find_library + + return bool(find_library('secp256k1')) _has_system_lib = None From 180ce4872bed2e00103e0120021dc9fc1f573730 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 15:11:34 -0500 Subject: [PATCH 15/39] (lint) clean imports --- setup_support.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup_support.py b/setup_support.py index 7e425ec31..1ed65a322 100644 --- a/setup_support.py +++ b/setup_support.py @@ -1,8 +1,6 @@ -import glob import logging import os import subprocess -from contextlib import suppress def absolute(*paths): From 5b597a29051fb76544647105a0643283677a8d76 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 15:27:30 -0500 Subject: [PATCH 16/39] (fix) pkg-config on windows need --dont-define-prefix --- setup.py | 2 +- setup_support.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 41d54c6ad..e4d8c5874 100644 --- a/setup.py +++ b/setup.py @@ -290,7 +290,7 @@ def has_c_libraries(self): if os.name == 'nt' or sys.platform == 'win32': # Apparently, the linker on Windows interprets -lxxx as xxx.lib, not libxxx.lib for i, v in enumerate(extension.__dict__.get('extra_link_args')): - extension.__dict__['extra_link_args'][i] = v.replace('-L', '/LIBPATH:') + extension.__dict__['extra_link_args'][i] = v.replace('-L', '/LIBPATH:').replace('Library/Library', 'Library') if v.startswith('-l'): v = v.replace('-l', 'lib') diff --git a/setup_support.py b/setup_support.py index 1ed65a322..3b21e7018 100644 --- a/setup_support.py +++ b/setup_support.py @@ -23,7 +23,10 @@ def build_flags(library, type_, path): os.environ['PKG_CONFIG_PATH'] = new_path + os.pathsep + os.environ.get('PKG_CONFIG_PATH', '') options = {'I': '--cflags-only-I', 'L': '--libs-only-L', 'l': '--libs-only-l'} - cmd = ['pkg-config', options[type_], library] + if os.name == 'nt': + cmd = ['pkg-config', options[type_], '--dont-define-prefix', library] + else: + cmd = ['pkg-config', options[type_], library] flags = subprocess_run(cmd) flags = list(flags.split()) From ace6dee5a450e3879b6f9658cfb8f35db8ee0dce Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 15:35:58 -0500 Subject: [PATCH 17/39] (fix) pkg-config on windows need --dont-define-prefix --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e4d8c5874..0f41f7063 100644 --- a/setup.py +++ b/setup.py @@ -290,7 +290,8 @@ def has_c_libraries(self): if os.name == 'nt' or sys.platform == 'win32': # Apparently, the linker on Windows interprets -lxxx as xxx.lib, not libxxx.lib for i, v in enumerate(extension.__dict__.get('extra_link_args')): - extension.__dict__['extra_link_args'][i] = v.replace('-L', '/LIBPATH:').replace('Library/Library', 'Library') + v_ = v.replace('-L', '/LIBPATH:').replace('Library/Library', 'Library') + extension.__dict__['extra_link_args'][i] = v_ if v.startswith('-l'): v = v.replace('-l', 'lib') From c52d91ca54135d9992ad446529409df06b26dabe Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 15:45:34 -0500 Subject: [PATCH 18/39] (fix) pkg-config on windows need --dont-define-prefix - hacky, final solution is better --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 0f41f7063..39c0e8b8e 100644 --- a/setup.py +++ b/setup.py @@ -297,6 +297,10 @@ def has_c_libraries(self): v = v.replace('-l', 'lib') extension.__dict__['extra_link_args'][i] = f'{v}.lib' + for i, v in enumerate(extension.__dict__.get('extra_compile_args')): + v_ = v.replace('Library/Library', 'Library') + extension.__dict__['extra_compile_args'][i] = v_ + setup_kwargs = dict( ext_modules=[extension], cmdclass={ From 34cca2d90009cf855bcc6600836cc33156e185ef Mon Sep 17 00:00:00 2001 From: memento Date: Tue, 12 Mar 2024 16:40:12 -0500 Subject: [PATCH 19/39] (feat) add macos build with external libsecp256k1 from CONDA --- .github/workflows/build.yml | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2e74decf..972f5eeca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -196,6 +196,55 @@ jobs: b=PrivateKey(); assert a.ecdh(b.public_key.format())==b.ecdh(a.public_key.format()) + macoss-wheels-conda-x86_64: + name: Build macOS (CONDA) wheels AMD64 + needs: + - test + runs-on: macos-latest + defaults: + run: + shell: bash -l {0} + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Miniconda + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ env.PYTHON_VERSION }} + auto-activate-base: false + + - name: Install cibuildwheel and twine + run: | + conda install -c conda-forge libsecp256k1 + pip install cibuildwheel twine + + - name: Build wheels + env: + CIBW_ENVIRONMENT: + COINCURVE_IGNORE_SYSTEM_LIB="0" + COINCURVE_UPSTREAM_REF="__no_tag__" + COINCURVE_SECP256K1_BUILD='SHARED' + PKG_CONFIG_PATH=$CONDA_PREFIX/lib/pkgconfig + DYLD_LIBRARY_PATH=$CONDA_PREFIX/lib + CIBW_BUILD_VERBOSITY: 1 + # pytest needs to be defined here to find 'coincurve' package? + CIBW_TEST_REQUIRES: pytest pytest-benchmark + CIBW_TEST_COMMAND: > + conda install -c conda-forge libsecp256k1 && + python -c + "from coincurve import PrivateKey; + a=PrivateKey(); + b=PrivateKey(); + assert a.ecdh(b.public_key.format())==b.ecdh(a.public_key.format()) + " + run: python -m cibuildwheel --output-dir wheelhouse + linux-wheels-arm: name: Build Linux wheels for ARM needs: From 6e629fd35e79583ae946be23622f0ea82ad2befd Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 16:12:14 -0500 Subject: [PATCH 20/39] (conflict) move coincurve to src --- {coincurve => src/coincurve}/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename {coincurve => src/coincurve}/__init__.py (91%) diff --git a/coincurve/__init__.py b/src/coincurve/__init__.py similarity index 91% rename from coincurve/__init__.py rename to src/coincurve/__init__.py index f800c2887..8cf831551 100644 --- a/coincurve/__init__.py +++ b/src/coincurve/__init__.py @@ -1,6 +1,9 @@ def load_secp256k1_conda_library(): """Load the secp256k1 library from the conda environment.""" - from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE + try: + from ._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE + except ImportError: + return if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': # coincurve was built with an internal library, either static or shared. It 'knows' where the library is. From 76e2af97cb8403cb6f65988d08c59c7cc4d45353 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 16:14:53 -0500 Subject: [PATCH 21/39] (conflict) move coincurve to src --- coincurve/_secp256k1_library_info.py | 2 -- {coincurve => src/coincurve}/_version.py | 0 2 files changed, 2 deletions(-) delete mode 100644 coincurve/_secp256k1_library_info.py rename {coincurve => src/coincurve}/_version.py (100%) diff --git a/coincurve/_secp256k1_library_info.py b/coincurve/_secp256k1_library_info.py deleted file mode 100644 index 6142884d3..000000000 --- a/coincurve/_secp256k1_library_info.py +++ /dev/null @@ -1,2 +0,0 @@ -SECP256K1_LIBRARY_NAME = 'libsecp256k1-2.dll' -SECP256K1_LIBRARY_TYPE = 'EXTERNAL' diff --git a/coincurve/_version.py b/src/coincurve/_version.py similarity index 100% rename from coincurve/_version.py rename to src/coincurve/_version.py From 773a337c8f6d1a3cb4a4c11d6e9deb3c347a2402 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 16:17:22 -0500 Subject: [PATCH 22/39] (conflict) move coincurve to src --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3e7fcc61c..723024ed1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ import sys import tarfile from io import BytesIO -import sys from os.path import dirname, abspath, join from sys import path as PATH @@ -44,7 +43,7 @@ LIB_TARBALL_URL = f'https://github.com/bitcoin-core/secp256k1/archive/{UPSTREAM_REF}.tar.gz' globals_ = {} -with open(join(COINCURVE_SRC_DIR, 'coincurve', '_version.py')) as fp: +with open(join(COINCURVE_SRC_DIR, 'src', 'coincurve', '_version.py')) as fp: exec(fp.read(), globals_) # noqa S102 __version__ = globals_['__version__'] From 095ed2b51821cecc7f99d6fc81f5dea48b24c7d9 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 18:01:37 -0500 Subject: [PATCH 23/39] (ref) avoid writing in src dir or have wrong info in lib_info --- setup.py | 33 ++++++++++++++++++++++++++++++++- setup_support.py | 8 ++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 723024ed1..fcc4f0572 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ from setuptools._distutils.errors import DistutilsError from setuptools.command.build_clib import build_clib as _build_clib from setuptools.command.build_ext import build_ext as _build_ext +from setuptools.command.build_py import build_py as _build_py from setuptools.command.develop import develop as _develop from setuptools.command.dist_info import dist_info as _dist_info from setuptools.command.egg_info import egg_info as _egg_info @@ -109,6 +110,31 @@ def run(self): _sdist.run(self) +class BuildLibInfo(_build_py): + """Create SECP256K1 library build info.""" + + def run(self): + import contextlib + from setup_support import update_pkg_config_path, verify_system_lib, subprocess_run + + update_pkg_config_path() + + with contextlib.suppress(Exception): + cmd = ( + [ + 'pkg-config', + '--libs-only-L', + '--dont-define-prefix', + 'libsecp256k1', + ] + if os.name == 'nt' + else ['pkg-config', '--libs-only-L', 'libsecp256k1'] + ) + lib_dir = subprocess_run(cmd) + verify_system_lib(lib_dir[2:].strip(), os.path.join(self.build_lib, 'coincurve')) + super().run() + + if _bdist_wheel: class bdist_wheel(_bdist_wheel): @@ -252,7 +278,7 @@ def run(self): _develop.run(self) -package_data = {'coincurve': ['py.typed']} +package_data = {'coincurve': ['py.typed', '_secp256k1__library_info.py']} class BuildCFFIForSharedLib(_build_ext): @@ -269,6 +295,7 @@ class Distribution(_Distribution): def has_c_libraries(self): return not has_system_lib() + # --- SECP256K1 package definitions --- secp256k1_package = 'libsecp256k1' @@ -303,6 +330,7 @@ def has_c_libraries(self): setup_kwargs = dict( ext_modules=[extension], cmdclass={ + 'build_py': BuildLibInfo, 'build_clib': build_clib, 'build_ext': BuildCFFIForSharedLib, 'develop': develop, @@ -334,6 +362,7 @@ def has_c_libraries(self): ext_package='coincurve', cffi_modules=['_cffi_build/build.py:ffi'], cmdclass={ + 'build_py': BuildLibInfo, 'build_clib': build_clib, 'build_ext': build_ext, 'develop': develop, @@ -358,6 +387,8 @@ def has_c_libraries(self): packages=['coincurve'], package_dir={'coincurve': 'src/coincurve'}, + package_data={'coincurve': ['_secp256k1__library_info.py']}, + include_package_data=True, distclass=Distribution, zip_safe=False, diff --git a/setup_support.py b/setup_support.py index 525d180bb..ccab4bbf6 100644 --- a/setup_support.py +++ b/setup_support.py @@ -46,7 +46,7 @@ def _find_lib(): cmd = ['pkg-config', '--libs-only-L', 'libsecp256k1'] lib_dir = subprocess_run(cmd) - return verify_system_lib(lib_dir[2:].strip()) + return verify_system_lib(lib_dir[2:].strip(), None) except (OSError, subprocess.CalledProcessError): from ctypes.util import find_library @@ -103,7 +103,7 @@ def update_pkg_config_path(path='.'): os.environ['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_paths) -def verify_system_lib(lib_dir): +def verify_system_lib(lib_dir, inst_dir): """Verifies that the system library is installed and of the expected type.""" import ctypes import platform @@ -139,10 +139,10 @@ def load_library(lib): f'Please ensure that the {SECP256K1_BUILD} library is installed.' ) - if dyn_lib: + if dyn_lib and inst_dir is not None: lib_base = dyn_lib.stem # Update coincurve._secp256k1_library_info - info_file = Path(PKG_NAME, '_secp256k1_library_info.py') + info_file = Path(inst_dir, '_secp256k1_library_info.py') info_file.write_text(f"SECP256K1_LIBRARY_NAME = '{lib_base}'\nSECP256K1_LIBRARY_TYPE = 'EXTERNAL'\n") return found From e7e392cb4bb2ad6a8a709994fd4da37f071c3d24 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 18:03:21 -0500 Subject: [PATCH 24/39] (fix) do not use include_package_data --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index fcc4f0572..0a14b7911 100644 --- a/setup.py +++ b/setup.py @@ -387,8 +387,6 @@ def has_c_libraries(self): packages=['coincurve'], package_dir={'coincurve': 'src/coincurve'}, - package_data={'coincurve': ['_secp256k1__library_info.py']}, - include_package_data=True, distclass=Distribution, zip_safe=False, From 3a9d65eaaea416859a4d852ee6a730b2cc41c7ff Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 18:30:25 -0500 Subject: [PATCH 25/39] (fix) use package_data --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0a14b7911..ea382708c 100644 --- a/setup.py +++ b/setup.py @@ -387,6 +387,7 @@ def has_c_libraries(self): packages=['coincurve'], package_dir={'coincurve': 'src/coincurve'}, + package_data={'coincurve': ['py.typed', '_secp256k1__library_info.py']}, distclass=Distribution, zip_safe=False, From 6116d7faf85b62bf8f93cb973bfe6282bcf4cc28 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 18:47:55 -0500 Subject: [PATCH 26/39] (fix) include_package_data? --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index ea382708c..216510856 100644 --- a/setup.py +++ b/setup.py @@ -388,6 +388,7 @@ def has_c_libraries(self): packages=['coincurve'], package_dir={'coincurve': 'src/coincurve'}, package_data={'coincurve': ['py.typed', '_secp256k1__library_info.py']}, + include_package_data=True, distclass=Distribution, zip_safe=False, From ce48c15048fca565c0f337222944ab26565f2042 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 19:05:26 -0500 Subject: [PATCH 27/39] (fix) create build_lib first --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 216510856..2d37d4b20 100644 --- a/setup.py +++ b/setup.py @@ -131,6 +131,7 @@ def run(self): else ['pkg-config', '--libs-only-L', 'libsecp256k1'] ) lib_dir = subprocess_run(cmd) + os.makedirs(self.build_lib, exist_ok=True) verify_system_lib(lib_dir[2:].strip(), os.path.join(self.build_lib, 'coincurve')) super().run() @@ -387,8 +388,8 @@ def has_c_libraries(self): packages=['coincurve'], package_dir={'coincurve': 'src/coincurve'}, - package_data={'coincurve': ['py.typed', '_secp256k1__library_info.py']}, - include_package_data=True, + package_data={'coincurve': ['py.typed', '_secp256k1_library_info.py']}, + # include_package_data=True, distclass=Distribution, zip_safe=False, From b5097d97548725a7931d90aaf2508f0002e951e9 Mon Sep 17 00:00:00 2001 From: memento Date: Thu, 14 Mar 2024 19:38:51 -0500 Subject: [PATCH 28/39] (fix) install in src to simplify the pain --- setup.py | 4 +--- setup_support.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 2d37d4b20..803d38351 100644 --- a/setup.py +++ b/setup.py @@ -132,7 +132,7 @@ def run(self): ) lib_dir = subprocess_run(cmd) os.makedirs(self.build_lib, exist_ok=True) - verify_system_lib(lib_dir[2:].strip(), os.path.join(self.build_lib, 'coincurve')) + verify_system_lib(lib_dir[2:].strip(), os.path.join('src', 'coincurve')) super().run() @@ -388,8 +388,6 @@ def has_c_libraries(self): packages=['coincurve'], package_dir={'coincurve': 'src/coincurve'}, - package_data={'coincurve': ['py.typed', '_secp256k1_library_info.py']}, - # include_package_data=True, distclass=Distribution, zip_safe=False, diff --git a/setup_support.py b/setup_support.py index ccab4bbf6..abe192119 100644 --- a/setup_support.py +++ b/setup_support.py @@ -139,10 +139,12 @@ def load_library(lib): f'Please ensure that the {SECP256K1_BUILD} library is installed.' ) + logging.warning(f' {inst_dir = }') if dyn_lib and inst_dir is not None: lib_base = dyn_lib.stem # Update coincurve._secp256k1_library_info info_file = Path(inst_dir, '_secp256k1_library_info.py') + logging.warning(f' {info_file = }') info_file.write_text(f"SECP256K1_LIBRARY_NAME = '{lib_base}'\nSECP256K1_LIBRARY_TYPE = 'EXTERNAL'\n") return found From 04d38a3b1588c9018ec677598fe037b1927bca3a Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 15 Mar 2024 09:07:47 -0500 Subject: [PATCH 29/39] (fix) MacOs DYLD_... prior to delocate --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 972f5eeca..93f31c3ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -231,8 +231,11 @@ jobs: COINCURVE_UPSTREAM_REF="__no_tag__" COINCURVE_SECP256K1_BUILD='SHARED' PKG_CONFIG_PATH=$CONDA_PREFIX/lib/pkgconfig - DYLD_LIBRARY_PATH=$CONDA_PREFIX/lib + REPAIR_LIBRARY_PATH=$CONDA_PREFIX/lib CIBW_BUILD_VERBOSITY: 1 + CIBW_REPAIR_WHEEL_COMMAND_MACOS: > + DYLD_LIBRARY_PATH=$REPAIR_LIBRARY_PATH delocate-listdeps {wheel} && + DYLD_LIBRARY_PATH=$REPAIR_LIBRARY_PATH delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel} # pytest needs to be defined here to find 'coincurve' package? CIBW_TEST_REQUIRES: pytest pytest-benchmark CIBW_TEST_COMMAND: > From bd256d785a83486e52b43d3e874f151834664017 Mon Sep 17 00:00:00 2001 From: memento Date: Fri, 8 Mar 2024 11:51:16 -0600 Subject: [PATCH 30/39] FEAT: add version and library type --- setup.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 803d38351..90feb84ac 100644 --- a/setup.py +++ b/setup.py @@ -279,7 +279,7 @@ def run(self): _develop.run(self) -package_data = {'coincurve': ['py.typed', '_secp256k1__library_info.py']} +package_data = {'coincurve': ['py.typed']} class BuildCFFIForSharedLib(_build_ext): @@ -296,7 +296,6 @@ class Distribution(_Distribution): def has_c_libraries(self): return not has_system_lib() - # --- SECP256K1 package definitions --- secp256k1_package = 'libsecp256k1' @@ -317,17 +316,12 @@ def has_c_libraries(self): if os.name == 'nt' or sys.platform == 'win32': # Apparently, the linker on Windows interprets -lxxx as xxx.lib, not libxxx.lib for i, v in enumerate(extension.__dict__.get('extra_link_args')): - v_ = v.replace('-L', '/LIBPATH:').replace('Library/Library', 'Library') - extension.__dict__['extra_link_args'][i] = v_ + extension.__dict__['extra_link_args'][i] = v.replace('-L', '/LIBPATH:') if v.startswith('-l'): v = v.replace('-l', 'lib') extension.__dict__['extra_link_args'][i] = f'{v}.lib' - for i, v in enumerate(extension.__dict__.get('extra_compile_args')): - v_ = v.replace('Library/Library', 'Library') - extension.__dict__['extra_compile_args'][i] = v_ - setup_kwargs = dict( ext_modules=[extension], cmdclass={ From f68c3f93e86aa54f55b1eeba989abaaae69fb084 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 14:17:22 -0500 Subject: [PATCH 31/39] (fix) import snafu --- src/coincurve/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/coincurve/__init__.py b/src/coincurve/__init__.py index 8cf831551..470640829 100644 --- a/src/coincurve/__init__.py +++ b/src/coincurve/__init__.py @@ -4,6 +4,9 @@ def load_secp256k1_conda_library(): from ._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE except ImportError: return + import warnings + + from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': # coincurve was built with an internal library, either static or shared. It 'knows' where the library is. From 3871c319d245d77f453492eaa176e1ecb16a63e5 Mon Sep 17 00:00:00 2001 From: memento Date: Sat, 16 Mar 2024 10:16:22 -0500 Subject: [PATCH 32/39] (fix) missing commits --- setup.py | 28 ++-------------------------- setup_tools/build_py.py | 30 ++++++++++++++++++++++++++++++ setup_tools/support.py | 1 - 3 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 setup_tools/build_py.py diff --git a/setup.py b/setup.py index 13acbe7a9..64a6c5eec 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ LIB_TARBALL_URL = f'https://github.com/bitcoin-core/secp256k1/archive/{UPSTREAM_REF}.tar.gz' globals_ = {} -with open(join(COINCURVE_SRC_DIR, 'src', 'coincurve', '_version.py')) as fp: +with open(join(COINCURVE_ROOT_DIR, 'src', 'coincurve', '_version.py')) as fp: exec(fp.read(), globals_) # noqa S102 __version__ = globals_['__version__'] @@ -42,37 +42,13 @@ f'to a newer version (>= 3.3).' ) -class BuildLibInfo(_build_py): - """Create SECP256K1 library build info.""" - - def run(self): - import contextlib - from setup_support import update_pkg_config_path, verify_system_lib, subprocess_run - - update_pkg_config_path() - - with contextlib.suppress(Exception): - cmd = ( - [ - 'pkg-config', - '--libs-only-L', - '--dont-define-prefix', - 'libsecp256k1', - ] - if os.name == 'nt' - else ['pkg-config', '--libs-only-L', 'libsecp256k1'] - ) - lib_dir = subprocess_run(cmd) - os.makedirs(self.build_lib, exist_ok=True) - verify_system_lib(lib_dir[2:].strip(), os.path.join('src', 'coincurve')) - super().run() - package_data = {'coincurve': ['py.typed']} def main(): from setup_tools.commands import BdistWheel, EggInfo, Sdist, Develop + from setup_tools.build_py import BuildLibInfo from setup_tools.build_clib import BuildClib from setup_tools.build_ext import BuildCFFIForSharedLib, BuildExt diff --git a/setup_tools/build_py.py b/setup_tools/build_py.py new file mode 100644 index 000000000..418221650 --- /dev/null +++ b/setup_tools/build_py.py @@ -0,0 +1,30 @@ +import os + +from setuptools.command import build_py + + +class BuildLibInfo(build_py.build_py): + """Create SECP256K1 library build info.""" + + def run(self): + import contextlib + + from setup_tools.support import subprocess_run, update_pkg_config_path, verify_system_lib + + update_pkg_config_path() + + with contextlib.suppress(Exception): + cmd = ( + [ + 'pkg-config', + '--libs-only-L', + '--dont-define-prefix', + 'libsecp256k1', + ] + if os.name == 'nt' + else ['pkg-config', '--libs-only-L', 'libsecp256k1'] + ) + lib_dir = subprocess_run(cmd) + os.makedirs(self.build_lib, exist_ok=True) + verify_system_lib(lib_dir[2:].strip(), os.path.join('src', 'coincurve')) + super().run() diff --git a/setup_tools/support.py b/setup_tools/support.py index cc8f369b1..4361b00ea 100644 --- a/setup_tools/support.py +++ b/setup_tools/support.py @@ -3,7 +3,6 @@ import shutil import subprocess import tarfile -from contextlib import suppress from io import BytesIO From e0dfa76f02d4ba1861554dbcd34c967844498529 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 10 Mar 2024 15:35:58 -0500 Subject: [PATCH 33/39] (fix) add temporary fix for pkg-config --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index 64a6c5eec..06c3542c1 100644 --- a/setup.py +++ b/setup.py @@ -76,11 +76,17 @@ def has_c_libraries(self): # Apparently, the linker on Windows interprets -lxxx as xxx.lib, not libxxx.lib for i, v in enumerate(extension.__dict__.get('extra_link_args')): extension.__dict__['extra_link_args'][i] = v.replace('-L', '/LIBPATH:') + v_ = v.replace('-L', '/LIBPATH:').replace('Library/Library', 'Library') + extension.__dict__['extra_link_args'][i] = v_ if v.startswith('-l'): v = v.replace('-l', 'lib') extension.__dict__['extra_link_args'][i] = f'{v}.lib' + for i, v in enumerate(extension.__dict__.get('extra_compile_args')): + v_ = v.replace('Library/Library', 'Library') + extension.__dict__['extra_compile_args'][i] = v_ + setup_kwargs = dict( ext_modules=[extension], cmdclass={ From 7f48a28cd71d8276af0ba0ca2c4cc135795b70c1 Mon Sep 17 00:00:00 2001 From: memento Date: Sat, 16 Mar 2024 13:50:55 -0500 Subject: [PATCH 34/39] (feat) update to call_pkg_config --- setup_tools/support.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/setup_tools/support.py b/setup_tools/support.py index 4361b00ea..b55295a60 100644 --- a/setup_tools/support.py +++ b/setup_tools/support.py @@ -28,11 +28,7 @@ def build_flags(library, type_, path): os.environ['PKG_CONFIG_PATH'] = new_path + os.pathsep + os.environ.get('PKG_CONFIG_PATH', '') options = {'I': '--cflags-only-I', 'L': '--libs-only-L', 'l': '--libs-only-l'} - if os.name == 'nt': - cmd = ['pkg-config', options[type_], '--dont-define-prefix', library] - else: - cmd = ['pkg-config', options[type_], library] - flags = subprocess_run(cmd) + flags = call_pkg_config([options[type_]], library) flags = list(flags.split()) return [flag.strip(f'-{type_}') for flag in flags] @@ -45,11 +41,8 @@ def _find_lib(): update_pkg_config_path() try: - if os.name == 'nt': - cmd = ['pkg-config', '--libs-only-L', '--dont-define-prefix', 'libsecp256k1'] - else: - cmd = ['pkg-config', '--libs-only-L', 'libsecp256k1'] - lib_dir = subprocess_run(cmd) + options = ['--libs-only-L'] + lib_dir = call_pkg_config(options, 'libsecp256k1') return verify_system_lib(lib_dir[2:].strip(), None) @@ -69,11 +62,8 @@ def has_system_lib(): return _has_system_lib -def detect_dll(root_dir): - for fn in os.listdir(os.path.join(root_dir)): - if fn.endswith('.dll'): - return True - return False +def detect_dll(root_dir: str): + return any(fn.endswith('.dll') for fn in os.listdir(os.path.join(root_dir))) def subprocess_run(cmd, *, debug=False): @@ -90,6 +80,20 @@ def subprocess_run(cmd, *, debug=False): raise e +def call_pkg_config(options, library, *, debug=False): + """Calls pkg-config with the given options and returns the output.""" + import shutil + from platform import system + + if system() == 'Windows': + options.append('--dont-define-prefix') + + pkg_config = shutil.which('pkg-config') + cmd = [pkg_config, *options, library] + + return subprocess_run(cmd, debug=debug) + + def download_library(command): if command.dry_run: return From 48ab544f513ed4b006b377fcb8cb685babb3d0b3 Mon Sep 17 00:00:00 2001 From: memento Date: Sat, 16 Mar 2024 15:26:45 -0500 Subject: [PATCH 35/39] (ref) add test for __init__.py (tested after install) --- setup_tools/support.py | 6 +++--- src/coincurve/__init__.py | 3 +-- tests/test_init.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 tests/test_init.py diff --git a/setup_tools/support.py b/setup_tools/support.py index b55295a60..d37249182 100644 --- a/setup_tools/support.py +++ b/setup_tools/support.py @@ -184,12 +184,12 @@ def load_library(lib): f'Please ensure that the {SECP256K1_BUILD} library is installed.' ) - logging.warning(f' {inst_dir = }') if dyn_lib and inst_dir is not None: - lib_base = dyn_lib.stem + # Why did I not want the extension + # lib_base = dyn_lib.stem + lib_base = dyn_lib.name # Update coincurve._secp256k1_library_info info_file = Path(inst_dir, '_secp256k1_library_info.py') - logging.warning(f' {info_file = }') info_file.write_text(f"SECP256K1_LIBRARY_NAME = '{lib_base}'\nSECP256K1_LIBRARY_TYPE = 'EXTERNAL'\n") return found diff --git a/src/coincurve/__init__.py b/src/coincurve/__init__.py index 470640829..97c90be1f 100644 --- a/src/coincurve/__init__.py +++ b/src/coincurve/__init__.py @@ -4,9 +4,8 @@ def load_secp256k1_conda_library(): from ._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE except ImportError: return - import warnings - from coincurve._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE + from ._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': # coincurve was built with an internal library, either static or shared. It 'knows' where the library is. diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 000000000..55ec2e71e --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,38 @@ +import os +from importlib import import_module +from unittest import mock + +import pytest + +import coincurve + + +def test_secp256k1_library_info_exists(): + # Check if the _secp256k1_library_info.py file exists in the installed package + file_path = os.path.join(os.path.dirname(coincurve.__file__), '_secp256k1_library_info.py') + + if os.path.exists(file_path): + # This test is run on the installed package, so the file may exist + # Verify that the information is as expected: Only EXTERNAL and a dynamic library + _secp256k1_library_info = import_module('coincurve._secp256k1_library_info') + + # import logging + # logging.warning(f'info: {_secp256k1_library_info.SECP256K1_LIBRARY_NAME}') + + assert 'secp256k1' in _secp256k1_library_info.SECP256K1_LIBRARY_NAME + assert any(ext in _secp256k1_library_info.SECP256K1_LIBRARY_NAME for ext in ('dll', 'so', 'dylib')) + assert _secp256k1_library_info.SECP256K1_LIBRARY_TYPE == 'EXTERNAL' + + else: + # If the file does not exist, test the load_secp256k1_conda_library function + with mock.patch('importlib.import_module') as mock_import_module: + mock_import_module.side_effect = ImportError + + # Verify that the function does not raise an exception + try: + coincurve.load_secp256k1_conda_library() + except Exception: + pytest.fail('load_secp256k1_conda_library() raised an exception') + + # Verify that the import was called + mock_import_module.assert_called_once_with('coincurve._secp256k1_library_info') From 129fe50e436b77cd66d19a1361ad667cb03d499c Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 17 Mar 2024 11:47:40 -0500 Subject: [PATCH 36/39] (ref) remove fixture --- tests/conftest.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fa829bf90..b42b96084 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,10 +73,3 @@ def samples(): 'X_ONLY_PUBKEY': X_ONLY_PUBKEY, 'X_ONLY_PUBKEY_INVALID': X_ONLY_PUBKEY_INVALID, } - - -@pytest.fixture(autouse=True) -def load_secp256k1_library(): - from coincurve import load_secp256k1_conda_library - - load_secp256k1_conda_library() From 2821ea0356c141ecf011f88a3ad1e64fea3c05a5 Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 17 Mar 2024 17:04:07 -0500 Subject: [PATCH 37/39] (feat) add tests for __init__.py. correct no-lib raise --- requirements-dev.txt | 1 + src/coincurve/__init__.py | 18 ++++---- tests/test_init.py | 88 +++++++++++++++++++++++++++++---------- 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a9973710e..0a43965cf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ coverage pytest pytest-benchmark +pytest-mock diff --git a/src/coincurve/__init__.py b/src/coincurve/__init__.py index 97c90be1f..0f72b7c14 100644 --- a/src/coincurve/__init__.py +++ b/src/coincurve/__init__.py @@ -3,9 +3,7 @@ def load_secp256k1_conda_library(): try: from ._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE except ImportError: - return - - from ._secp256k1_library_info import SECP256K1_LIBRARY_NAME, SECP256K1_LIBRARY_TYPE + raise if SECP256K1_LIBRARY_TYPE != 'EXTERNAL': # coincurve was built with an internal library, either static or shared. It 'knows' where the library is. @@ -32,13 +30,19 @@ def load_secp256k1_conda_library(): CDLL(library) return - except Exception as e: - import warnings - warnings.warn(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.\n{e}', stacklevel=2) + raise RuntimeError(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.') + + except Exception as _e: + raise RuntimeError(f'The required library {SECP256K1_LIBRARY_NAME}l is not loaded.') from _e -load_secp256k1_conda_library() +try: + load_secp256k1_conda_library() +except ImportError: + pass +except Exception as e: + raise e from coincurve.context import GLOBAL_CONTEXT, Context # noqa: E402 from coincurve.keys import PrivateKey, PublicKey, PublicKeyXOnly # noqa: E402 diff --git a/tests/test_init.py b/tests/test_init.py index 55ec2e71e..d30a77dc8 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,38 +1,82 @@ import os from importlib import import_module -from unittest import mock - -import pytest +from importlib.util import find_spec +from unittest.mock import patch import coincurve +import pytest def test_secp256k1_library_info_exists(): - # Check if the _secp256k1_library_info.py file exists in the installed package - file_path = os.path.join(os.path.dirname(coincurve.__file__), '_secp256k1_library_info.py') - - if os.path.exists(file_path): + """Test _secp256k1_library_info.py file has correct information.""" + if find_spec('coincurve._secp256k1_library_info'): # This test is run on the installed package, so the file may exist # Verify that the information is as expected: Only EXTERNAL and a dynamic library _secp256k1_library_info = import_module('coincurve._secp256k1_library_info') - # import logging - # logging.warning(f'info: {_secp256k1_library_info.SECP256K1_LIBRARY_NAME}') - assert 'secp256k1' in _secp256k1_library_info.SECP256K1_LIBRARY_NAME assert any(ext in _secp256k1_library_info.SECP256K1_LIBRARY_NAME for ext in ('dll', 'so', 'dylib')) assert _secp256k1_library_info.SECP256K1_LIBRARY_TYPE == 'EXTERNAL' + +def test_secp256k1_library_info_does_not_exists(): + """Test _secp256k1_library_info.py file has correct information. + """ + if not find_spec('coincurve._secp256k1_library_info'): + with pytest.raises(ImportError): + import coincurve + coincurve.load_secp256k1_conda_library() + + +@patch('ctypes.CDLL') +@patch('ctypes.util.find_library', return_value=None) +def test_load_secp256k1_conda_library_internal(mock_find_library, mock_cdll): + """Test loading the secp256k1 library.""" + if not find_spec('coincurve._secp256k1_library_info'): + with pytest.raises(ImportError): + coincurve.load_secp256k1_conda_library() else: - # If the file does not exist, test the load_secp256k1_conda_library function - with mock.patch('importlib.import_module') as mock_import_module: - mock_import_module.side_effect = ImportError - - # Verify that the function does not raise an exception - try: - coincurve.load_secp256k1_conda_library() - except Exception: - pytest.fail('load_secp256k1_conda_library() raised an exception') - - # Verify that the import was called - mock_import_module.assert_called_once_with('coincurve._secp256k1_library_info') + with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_TYPE', 'INTERNAL'): + coincurve.load_secp256k1_conda_library() + mock_find_library.assert_not_called() + mock_cdll.assert_not_called() + + +@patch('ctypes.CDLL') +@patch('ctypes.util.find_library', return_value=None) +def test_load_secp256k1_conda_library_external_nolib(mock_find_library, mock_cdll): + """Test loading the secp256k1 library.""" + import coincurve + if not find_spec('coincurve._secp256k1_library_info'): + with pytest.raises(ImportError): + coincurve.load_secp256k1_conda_library() + else: + with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_TYPE', 'EXTERNAL'): + with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_NAME', 'libname.so'): + with patch('os.getenv', return_value=None) as mock_getenv: + assert os.getenv('CONDA_PREFIX') is None + with pytest.raises(RuntimeError): + coincurve.load_secp256k1_conda_library() + mock_find_library.assert_called_once_with('libname.so') + mock_cdll.assert_not_called() + + +@patch('ctypes.CDLL') +@patch('ctypes.util.find_library', return_value=None) +def test_load_secp256k1_conda_library(mock_find_library, mock_cdll): + """Test loading the secp256k1 library.""" + import coincurve + if not find_spec('coincurve._secp256k1_library_info'): + with pytest.raises(ImportError): + coincurve.load_secp256k1_conda_library() + else: + with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_TYPE', 'EXTERNAL'): + with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_NAME', 'libname.so'): + with patch('os.getenv', return_value='/path/to/libname') as mock_getenv: + assert os.getenv('CONDA_PREFIX') == '/path/to/libname' + coincurve.load_secp256k1_conda_library() + mock_find_library.assert_called_once_with('libname.so') + mock_cdll.assert_called_once() + assert '/path/to/libname' in mock_cdll.call_args[0][0] + assert 'libname.so' in mock_cdll.call_args[0][0] + From a8956049f867dc8dcbd7e2a3636f8039a7a6c5fa Mon Sep 17 00:00:00 2001 From: memento Date: Sun, 17 Mar 2024 17:08:45 -0500 Subject: [PATCH 38/39] (fix) lint. remove unused pytest-mock --- requirements-dev.txt | 1 - tests/test_init.py | 21 +++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0a43965cf..a9973710e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,3 @@ coverage pytest pytest-benchmark -pytest-mock diff --git a/tests/test_init.py b/tests/test_init.py index d30a77dc8..d5e9bbf00 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -3,9 +3,17 @@ from importlib.util import find_spec from unittest.mock import patch -import coincurve import pytest +import coincurve + +# These tests will be run on the installed package. The module +# _secp256k1_library_info.py does not exist in the dist source code, +# but it may exist in the installed package and we need to test the +# behavior of the library loading in that case. + +# This requires to have conditional statement in the tests + def test_secp256k1_library_info_exists(): """Test _secp256k1_library_info.py file has correct information.""" @@ -20,11 +28,11 @@ def test_secp256k1_library_info_exists(): def test_secp256k1_library_info_does_not_exists(): - """Test _secp256k1_library_info.py file has correct information. - """ + """Test _secp256k1_library_info.py file has correct information.""" if not find_spec('coincurve._secp256k1_library_info'): with pytest.raises(ImportError): import coincurve + coincurve.load_secp256k1_conda_library() @@ -47,13 +55,14 @@ def test_load_secp256k1_conda_library_internal(mock_find_library, mock_cdll): def test_load_secp256k1_conda_library_external_nolib(mock_find_library, mock_cdll): """Test loading the secp256k1 library.""" import coincurve + if not find_spec('coincurve._secp256k1_library_info'): with pytest.raises(ImportError): coincurve.load_secp256k1_conda_library() else: with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_TYPE', 'EXTERNAL'): with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_NAME', 'libname.so'): - with patch('os.getenv', return_value=None) as mock_getenv: + with patch('os.getenv', return_value=None): assert os.getenv('CONDA_PREFIX') is None with pytest.raises(RuntimeError): coincurve.load_secp256k1_conda_library() @@ -66,17 +75,17 @@ def test_load_secp256k1_conda_library_external_nolib(mock_find_library, mock_cdl def test_load_secp256k1_conda_library(mock_find_library, mock_cdll): """Test loading the secp256k1 library.""" import coincurve + if not find_spec('coincurve._secp256k1_library_info'): with pytest.raises(ImportError): coincurve.load_secp256k1_conda_library() else: with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_TYPE', 'EXTERNAL'): with patch('coincurve._secp256k1_library_info.SECP256K1_LIBRARY_NAME', 'libname.so'): - with patch('os.getenv', return_value='/path/to/libname') as mock_getenv: + with patch('os.getenv', return_value='/path/to/libname'): assert os.getenv('CONDA_PREFIX') == '/path/to/libname' coincurve.load_secp256k1_conda_library() mock_find_library.assert_called_once_with('libname.so') mock_cdll.assert_called_once() assert '/path/to/libname' in mock_cdll.call_args[0][0] assert 'libname.so' in mock_cdll.call_args[0][0] - From b70cadbc7c4141405d991d400fa858a0ef8c11d8 Mon Sep 17 00:00:00 2001 From: memento Date: Mon, 18 Mar 2024 11:28:55 -0500 Subject: [PATCH 39/39] (fix) add omit test_init.py in .coveragerc --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index be03fbc06..8a89b7f5a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,10 +6,10 @@ source = tests omit = - */_secp256k1_library_info.py */_version.py */_windows_libsecp256k1.py */test_bench.py + */test_init.py [paths] source =