[med-svn] [Git][med-team/cyvcf2][master] 7 commits: New upstream version 0.30.28

Andreas Tille (@tille) gitlab at salsa.debian.org
Wed Feb 7 10:40:32 GMT 2024



Andreas Tille pushed to branch master at Debian Med / cyvcf2


Commits:
7c97cba8 by Andreas Tille at 2024-02-07T09:56:39+01:00
New upstream version 0.30.28
- - - - -
1c6721f6 by Andreas Tille at 2024-02-07T09:56:39+01:00
routine-update: New upstream version

- - - - -
c62c6f96 by Andreas Tille at 2024-02-07T09:56:39+01:00
Update upstream source from tag 'upstream/0.30.28'

Update to upstream version '0.30.28'
with Debian dir 2c6cbcd97d5d6a8aa3b547c8ea4c9bbbccd39069
- - - - -
07f5556f by Andreas Tille at 2024-02-07T11:15:12+01:00
Set CYVCF2_HTSLIB_MODE=EXTERNAL to build with Debian packaged htslib

- - - - -
94090a0b by Andreas Tille at 2024-02-07T11:33:08+01:00
Force copying tests dir into build_dir

- - - - -
3530f89b by Andreas Tille at 2024-02-07T11:35:55+01:00
Builds against libhts 19 from experimental

- - - - -
f957b105 by Andreas Tille at 2024-02-07T11:38:55+01:00
Upload to unstable

- - - - -


28 changed files:

- .github/workflows/build.yml
- .github/workflows/wheels.yml
- .gitignore
- CHANGES.md
- MANIFEST.in
- README.md
- + ci/Dockerfile.alpine.test
- ci/Dockerfile.test → ci/Dockerfile.slim.test
- ci/linux-deps
- − ci/osx-arm64-deps
- ci/osx-deps
- cyvcf2/__init__.py
- cyvcf2/cyvcf2.pxd
- cyvcf2/cyvcf2.pyx
- cyvcf2/helpers.c
- + cyvcf2/tests/multi-contig.bcf
- + cyvcf2/tests/multi-contig.bcf.csi
- + cyvcf2/tests/multi-contig.vcf.gz
- + cyvcf2/tests/multi-contig.vcf.gz.csi
- + cyvcf2/tests/multi-contig.vcf.gz.tbi
- cyvcf2/tests/test_reader.py
- debian/changelog
- debian/patches/i386.patch
- debian/patches/series
- debian/rules
- pyproject.toml
- requirements.txt
- setup.py


Changes:

=====================================
.github/workflows/build.yml
=====================================
@@ -3,14 +3,25 @@ name: Build
 on: [push, pull_request]
 
 jobs:
-  docker-build-and-test:
-    name: Docker Build and Test on ${{ matrix.platform }}
-    runs-on: ubuntu-latest
+  docker-build:
+    name: Docker Run Test on ${{ matrix.platform }}-${{ matrix.python_tag_type }}
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
         platform:
           - linux/amd64
           - linux/arm64
+        python_tag_type:
+          - slim
+          - alpine
+        exclude:
+          # amd64 with glibc have full direct test
+          - platform: linux/amd64
+            python_tag_type: slim
+
+          # test alpine only on amd64
+          - platform: linux/arm64
+            python_tag_type: alpine
 
     steps:
       - uses: actions/checkout at v4
@@ -20,51 +31,184 @@ jobs:
         uses: docker/setup-qemu-action at v3
       - name: Set up Docker Buildx
         uses: docker/setup-buildx-action at v3
-      - name: Docker Build and Push
+
+      - name: Docker Build
         uses: docker/build-push-action at v5
         with:
-          context: .  
-          file: ./ci/Dockerfile.test
+          context: .
+          file: ./ci/Dockerfile.${{ matrix.python_tag_type }}.test
           platforms: ${{ matrix.platform }}
-          tags: cyvcf2:test
+          tags: cyvcf2:${{ matrix.python_tag_type }}-test
           push: false
           load: true
-          build-args: |  
-            PYTHON_VERSION=slim
+          build-args: |
+            PYTHON_VERSION=${{ matrix.python_tag_type }}
 
       - name: Docker Run Tests
         run: |
-          docker run --rm --platform ${{ matrix.platform }} cyvcf2:test pytest --cov cyvcf2 --cov-report term-missing
+          docker run --rm --platform ${{ matrix.platform }} cyvcf2:${{ matrix.python_tag_type }}-test pytest --cov cyvcf2 --cov-report term-missing
 
   build:
-    name: Run tests on Python ${{ matrix.python-version }}
-    runs-on: ubuntu-20.04
+    name: Run tests on Python ${{ matrix.python-version }} ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-22.04, macos-12]
+        python-version:
+          ["pypy3.10", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+        exclude:
+          # Run only the latest versions on macOS and windows
+          - os: macos-12
+            python-version: "pypy3.10"
+          - os: macos-12
+            python-version: "3.7"
+          - os: macos-12
+            python-version: "3.8"
+          - os: macos-12
+            python-version: "3.9"
+          - os: macos-12
+            python-version: "3.10"
+          - os: macos-12
+            python-version: "3.11"
+
+    steps:
+      - uses: actions/checkout at v4
+        with:
+          submodules: recursive
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python at v5
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Set macOS env
+        if: runner.os == 'macOS'
+        run: |
+          # building options
+          echo "MACOSX_DEPLOYMENT_TARGET=10.9" >> "$GITHUB_ENV"
+          echo "ARCHFLAGS=-arch x86_64" >> "$GITHUB_ENV"
+
+      - name: Install Linux build prerequisites
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends libcurl4-openssl-dev zlib1g-dev libssl-dev liblzma-dev \
+            libbz2-dev libdeflate-dev
+
+      - name: Install macOS build prerequisites
+        if: runner.os == 'macOS'
+        run: |
+          brew install automake libdeflate
+
+      - name: Install
+        run: |
+          pip install -r requirements.txt
+          pip install pytest pytest-cov
+          CYVCF2_HTSLIB_CONFIGURE_OPTIONS="--enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate" \
+            CYTHONIZE=1 python setup.py build_ext -i
+
+      - name: Test
+        run: |
+          pytest --cov cyvcf2 --cov-report term-missing
+
+  windows_build:
+    name: Run tests on Python windows-2022 MSYS2 UCRT64
+    runs-on: windows-2022
+    defaults:
+      run:
+        shell: msys2 {0}
+
+    steps:
+      - uses: actions/checkout at v4
+        with:
+          submodules: recursive
+
+      - name: "Setup MSYS2"
+        uses: msys2/setup-msys2 at v2
+        with:
+          msystem: UCRT64
+          path-type: inherit
+          install: >-
+            mingw-w64-ucrt-x86_64-gcc
+            mingw-w64-ucrt-x86_64-make
+            mingw-w64-ucrt-x86_64-libdeflate
+            mingw-w64-ucrt-x86_64-xz
+            mingw-w64-ucrt-x86_64-curl
+            mingw-w64-ucrt-x86_64-zlib
+            mingw-w64-ucrt-x86_64-bzip2
+            mingw-w64-ucrt-x86_64-tools-git
+            mingw-w64-ucrt-x86_64-python-pkgconfig
+            mingw-w64-ucrt-x86_64-pkg-config
+            mingw-w64-ucrt-x86_64-ninja
+            mingw-w64-ucrt-x86_64-python
+            mingw-w64-ucrt-x86_64-python-pip
+            make
+            automake
+            autoconf
+            git
+
+      - name: Install Windows build prerequisites
+        run: |
+          cd htslib
+          autoreconf -i
+          ./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate
+          make
+          make install
+
+      - name: Install
+        run: |
+          pip install -r requirements.txt
+          pip install pytest pytest-cov
+          CYTHONIZE=1 python setup.py build_ext -i
+
+      - name: Test
+        run: |
+          pytest --cov cyvcf2 --cov-report term-missing
+
+  sdist:
+    runs-on: ubuntu-22.04
     strategy:
       matrix:
-        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
+        python-version: ["3.12"]
 
     steps:
-    - uses: actions/checkout at v3
-      with:
-        submodules: true
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python at v4
-      with:
-        python-version: ${{ matrix.python-version }}
-    - name: Install
-      run: |
-        sudo apt-get update
-        sudo apt-get install libcurl4-openssl-dev
-        pip install -r requirements.txt
-        pip install pytest pytest-cov
-        git submodule update --init --recursive
-        cd htslib
-        autoheader && autoconf && autoreconf --install
-        ./configure --enable-s3 --disable-lzma --disable-bz2
-        make
-        cd ..
-        CYTHONIZE=1 python setup.py build_ext -i
-    - name: Test
-      run: |
-        pytest --cov cyvcf2 --cov-report term-missing
+      - uses: actions/checkout at v4
+        with:
+          submodules: recursive
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python at v5
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install sdist prerequisites
+        run: |
+          pip install -r requirements.txt
+
+      - name: Create source distribution
+        run: CYTHONIZE=1 python setup.py sdist
+
+      - name: Install Linux build prerequisites
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends libcurl4-openssl-dev zlib1g-dev libssl-dev liblzma-dev \
+            libbz2-dev libdeflate-dev
 
+      - name: Build (via sdist tarball)
+        run: pip install --verbose --no-deps --no-binary='cyvcf2' cyvcf2-*.tar.gz
+        working-directory: dist
+
+      - name: Install test prerequisites
+        run: |
+          pip install pytest pytest-cov
+
+      - name: Test
+        run: |
+          pytest --import-mode importlib --cov cyvcf2 --cov-report term-missing
+
+      - name: Upload sdist tarball
+        if: runner.os == 'Linux'
+        uses: actions/upload-artifact at v4
+        with:
+          name: sdist
+          path: dist/cyvcf2-*.tar.gz
+          retention-days: 7


=====================================
.github/workflows/wheels.yml
=====================================
@@ -5,138 +5,141 @@ on:
     branches:
       - main
     tags:
-      - 'v*.*.*'
+      - "v*.*.*"
   workflow_dispatch:
     inputs:
       debug_enabled:
-        description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
+        description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
         required: false
         default: false
   schedule:
     # run weekly on a Monday
-    - cron: '0 0 * * 1'
+    - cron: "0 0 * * 1"
 
 jobs:
   build_wheels:
-    name: Build wheels on ${{ matrix.os }}
-    runs-on: ${{ matrix.os }}
+    name: Build wheels for  ${{ matrix.python-version }}-${{ matrix.buildplat[1] }}
+    runs-on: ${{ matrix.buildplat[0] }}
     strategy:
       matrix:
-        os: [ubuntu-20.04, macos-latest]
+        buildplat:
+          - [ubuntu-22.04, manylinux_x86_64]
+          - [ubuntu-22.04, musllinux_x86_64]
+          - [ubuntu-22.04, manylinux_aarch64]
+          - [ubuntu-22.04, musllinux_aarch64]
+          - [macos-12, macosx_x86_64]
+          - [macos-12, macosx_arm64]
+        python-version: [pp310, cp37, cp38, cp39, cp310, cp311, cp312]
+        exclude:
+          # pp310, cp37, cp38 on musllinux is not support
+          # cp39, cp310 on musllinux_aarch64, wheel building may hangup, ignore it
+          - buildplat: [ubuntu-22.04, musllinux_x86_64]
+            python-version: cp37
+          - buildplat: [ubuntu-22.04, musllinux_x86_64]
+            python-version: cp38
+          - buildplat: [ubuntu-22.04, musllinux_x86_64]
+            python-version: pp310
+          - buildplat: [ubuntu-22.04, musllinux_aarch64]
+            python-version: cp37
+          - buildplat: [ubuntu-22.04, musllinux_aarch64]
+            python-version: cp38
+          - buildplat: [ubuntu-22.04, musllinux_aarch64]
+            python-version: cp39
+          - buildplat: [ubuntu-22.04, musllinux_aarch64]
+            python-version: cp310
+          - buildplat: [ubuntu-22.04, musllinux_aarch64]
+            python-version: pp310
+          # cp37, pp310 on macos arm64 is not supported
+          - buildplat: [macos-12, macosx_arm64]
+            python-version: cp37
+          - buildplat: [macos-12, macosx_arm64]
+            python-version: pp310
 
     steps:
-    - uses: actions/checkout at v3
-      with:
-        submodules: true
-    - uses: actions/setup-python at v4
-      name: Install Python
-      with:
-        python-version: "3.12"
-
-    - name: Install cibuildwheel
-      run: |
-        python -m pip install -U cibuildwheel
-
-    - name: Build wheels for Linux
-      if: matrix.os == 'ubuntu-20.04'
-      run: |
-        python -m cibuildwheel --output-dir wheelhouse
-      env:
-        CIBW_SKIP: "pp* *i686* *musllinux*"
-        CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
-        CIBW_BEFORE_BUILD_LINUX: "{project}/ci/linux-deps"
-        CIBW_TEST_COMMAND: "{project}/ci/test"
-        CIBW_ENVIRONMENT: "CYTHONIZE=1 LIBDEFLATE=1 LDFLAGS='-L/usr/lib64/openssl11' CPPFLAGS='-I/usr/include/openssl11' C_INCLUDE_PATH='/root/include' LIBRARY_PATH='/root/lib'"
-        CIBW_REPAIR_WHEEL_COMMAND_LINUX: LD_LIBRARY_PATH='/root/lib' auditwheel repair -w {dest_dir} {wheel}
-
-    - name: Build wheels for Mac OS x86
-      if: matrix.os == 'macos-latest'
-      run: |
-        python -m cibuildwheel --output-dir wheelhouse
-      env:
-        CIBW_SKIP: "pp* *i686*"
-        CIBW_ARCHS_MACOS: "x86_64"
-        CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
-        CIBW_BEFORE_BUILD_MACOS: "{project}/ci/osx-deps"
-        CIBW_TEST_COMMAND: "{project}/ci/test"
-        CIBW_ENVIRONMENT: "CYTHONIZE=1 LIBDEFLATE=1 C_INCLUDE_PATH='/usr/local/include' LIBRARY_PATH='/usr/local/lib'"
-        # https://cibuildwheel.readthedocs.io/en/stable/faq/#macos-passing-dyld_library_path-to-delocate
-        CIBW_REPAIR_WHEEL_COMMAND_MACOS: >
-          DYLD_LIBRARY_PATH=/usr/local/lib delocate-listdeps {wheel} &&
-          DYLD_LIBRARY_PATH=/usr/local/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}
-        LDFLAGS: "-L/usr/local/opt/openssl at 1.1/lib"
-        CPPFLAGS: "-I/usr/local/opt/openssl at 1.1/include"
-        PKG_CONFIG_PATH: "/usr/local/opt/openssl at 1.1/lib/pkgconfig"
-
-    - name: Build wheels for Mac OS arm64
-      # don't build with libdeflate, see https://github.com/brentp/cyvcf2/issues/252
-      if: matrix.os == 'macos-latest'
-      run: |
-        python -m cibuildwheel --output-dir wheelhouse
-      env:
-        CIBW_SKIP: "pp* *i686*"
-        CIBW_ARCHS_MACOS: "arm64"
-        CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
-        CIBW_BEFORE_BUILD_MACOS: "{project}/ci/osx-arm64-deps"
-        CIBW_TEST_COMMAND: "{project}/ci/test"
-        CIBW_TEST_SKIP: "*-macosx_arm64"
-        CIBW_ENVIRONMENT: "CYTHONIZE=1 C_INCLUDE_PATH='/usr/local/include' LIBRARY_PATH='/usr/local/lib'"
-        # https://cibuildwheel.readthedocs.io/en/stable/faq/#macos-passing-dyld_library_path-to-delocate
-        CIBW_REPAIR_WHEEL_COMMAND_MACOS: >
-          DYLD_LIBRARY_PATH=/usr/local/lib delocate-listdeps {wheel} &&
-          DYLD_LIBRARY_PATH=/usr/local/lib delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}
-        LDFLAGS: "-L/usr/local/opt/openssl at 1.1/lib"
-        CPPFLAGS: "-I/usr/local/opt/openssl at 1.1/include"
-        PKG_CONFIG_PATH: "/usr/local/opt/openssl at 1.1/lib/pkgconfig"
-
-    # Enable tmate debugging of manually-triggered workflows if the input option was provided
-    - name: Setup tmate session
-      if: ${{ always() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
-      uses: mxschmitt/action-tmate at v3
-
-    - uses: actions/upload-artifact at v2
-      with:
-        path: ./wheelhouse/*.whl
+      - uses: actions/checkout at v4
+        with:
+          submodules: recursive
+
+      - name: Set up QEMU
+        if: runner.os == 'Linux'
+        uses: docker/setup-qemu-action at v3
+
+      - name: Build wheels
+        uses: pypa/cibuildwheel at v2.16.4
+        with:
+          package-dir: .
+          output-dir: wheelhouse
+          config-file: "{package}/pyproject.toml"
+        env:
+          # select
+          CIBW_BUILD: ${{ matrix.python-version }}-${{ matrix.buildplat[1] }}
+
+          # linux
+          CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
+          # manylinux2014 can't build on aarch64
+          CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
+          CIBW_MUSLLINUX_X86_64_IMAGE: musllinux_1_2
+          CIBW_MUSLLINUX_AARCH64_IMAGE: musllinux_1_2
+          CIBW_ARCHS_LINUX: auto64 aarch64
+          CIBW_BEFORE_BUILD_LINUX: "{project}/ci/linux-deps"
+          CIBW_REPAIR_WHEEL_COMMAND_LINUX: 'LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib64" && auditwheel repair -w {dest_dir} {wheel}'
+
+          # macos
+          CIBW_ARCHS_MACOS: auto64 arm64
+          CIBW_BEFORE_BUILD_MACOS: "{project}/ci/osx-deps"
+
+          # build
+          CIBW_ENVIRONMENT: >-
+            CYVCF2_HTSLIB_CONFIGURE_OPTIONS="--enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate"
+            CYTHONIZE=1
+          CIBW_TEST_COMMAND: "{project}/ci/test"
+          # macOS build arm64 on x86_64 with cross-compiler, not support test
+          CIBW_TEST_SKIP: "*-macosx_arm64"
+
+      # Enable tmate debugging of manually-triggered workflows if the input option was provided
+      - name: Setup tmate session
+        if: ${{ always() && github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
+        uses: mxschmitt/action-tmate at v3
+
+      - uses: actions/upload-artifact at v4
+        with:
+          name: artifact-${{ matrix.python-version }}-${{ matrix.buildplat[1] }}
+          path: ./wheelhouse/*.whl
 
   build_sdist:
     name: Build source distribution
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     steps:
-      - uses: actions/checkout at v2
-
-      - uses: actions/setup-python at v4
+      - uses: actions/checkout at v4
+        with:
+          submodules: recursive
+      - uses: actions/setup-python at v5
         name: Install Python
         with:
           python-version: "3.12"
 
       - name: Install dependencies
         run: |
-          sudo apt-get update
-          sudo apt-get install libcurl4-openssl-dev libbz2-dev liblzma-dev libssl-dev
-          git submodule update --init --recursive
-          cd htslib
-          autoheader && autoconf && autoreconf --install
-          ./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2
-          make
-          cd ..
           pip install -r requirements.txt
 
       - name: Build sdist
         run: CYTHONIZE=1 python setup.py sdist
 
-      - uses: actions/upload-artifact at v2
+      - uses: actions/upload-artifact at v4
         with:
+          name: artifact-sdist
           path: dist/*.tar.gz
 
   upload_pypi:
     needs: [build_wheels, build_sdist]
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-22.04
     # upload to PyPI on every tag starting with 'v'
     if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
     steps:
-      - uses: actions/download-artifact at v2
+      - uses: actions/download-artifact at v4
         with:
-          name: artifact
+          pattern: artifact-*
+          merge-multiple: true
           path: dist
 
       - uses: pypa/gh-action-pypi-publish at release/v1


=====================================
.gitignore
=====================================
@@ -19,3 +19,4 @@ _templates
 setup-requires/*
 .cache/v/cache/lastfailed
 .idea
+**/__pycache__


=====================================
CHANGES.md
=====================================
@@ -1,3 +1,8 @@
+# v0.30.27
+
++ support num_records using index stats (#295 from @jeromekelleher)
++ don't remove htslib/config.status (#296)
+
 # v0.30.25
 + bump to get new release for CI/wheel fixes. thanks to @graphenn (#286) and @oyvinev (#297)
 


=====================================
MANIFEST.in
=====================================
@@ -1,12 +1,18 @@
-include cyvcf2/tests/*
 include LICENSE
+include *.md
 include cyvcf2/*.pyx
 include cyvcf2/*.pxd
-include cyvcf2/*.c
-include cyvcf2/*.h
-include *.md
-include htslib/*.c
-include htslib/*.h
-include htslib/htslib/*.h
-include htslib/cram/*.c
-include htslib/cram/*.h
+include cyvcf2/*.[ch]
+
+# exclude tests from pypi because no data file
+prune cyvcf2/tests
+
+# htslib
+include htslib/LICENSE htslib/README
+recursive-include htslib *.[ch]
+exclude htslib/*config*.h
+
+include htslib/configure.ac htslib/m4/*.m4 htslib/*.in
+include htslib/configure htslib/version.sh
+include htslib/Makefile htslib/*.mk
+exclude htslib/config.mk htslib/htscodecs.mk


=====================================
README.md
=====================================
@@ -14,7 +14,7 @@ If you use cyvcf2, please cite the [paper](https://academic.oup.com/bioinformati
 Fast python **(2 and 3)** parsing of VCF and BCF including region-queries.
 
 
-[![Build Status](https://github.com/brentp/cyvcf2/workflows/Build/badge.svg)](https://github.com/brentp/cyvcf2/actions?query=workflow%3ABuild)
+[![Build](https://github.com/brentp/cyvcf2/actions/workflows/build.yml/badge.svg)](https://github.com/brentp/cyvcf2/actions/workflows/build.yml)
 
 cyvcf2 is a cython wrapper around [htslib](https://github.com/samtools/htslib) built for fast parsing of [Variant Call Format](https://en.m.wikipedia.org/wiki/Variant_Call_Format) (VCF) files.
 
@@ -70,24 +70,35 @@ for v in vcf('11:435345-556565'):
 Installation
 ============
 
-## pip (assuming you have htslib < 1.10 installed)
+## pip with bundled htslib
 ```
 pip install cyvcf2
 ```
 
+## pip with system htslib
+
+Assuming you have already built and installed htslib version 1.12 or higher.
+```
+CYVCF2_HTSLIB_MODE=EXTERNAL pip install --no-binary cyvcf2 cyvcf2
+```
+
+## windows (experimental, only test on MSYS2)
+
+Assuming you have already built and installed htslib.
+```
+SETUPTOOLS_USE_DISTUTILS=stdlib pip install cyvcf2
+```
+
 ## github (building htslib and cyvcf2 from source)
 
 ```
 git clone --recursive https://github.com/brentp/cyvcf2
-cd cyvcf2/htslib
-autoheader
-autoconf
-./configure --enable-libcurl
-make
-
-cd ..
 pip install -r requirements.txt
-CYTHONIZE=1 pip install -e .
+# sometimes it can be required to remove old files:
+# python setup.py clean_ext
+CYVCF2_HTSLIB_MODE=BUILTIN CYTHONIZE=1 python setup.py install
+# or to use a system htslib.so
+CYVCF2_HTSLIB_MODE=EXTERNAL python setup.py install
 ```
 
 On **OSX**, using brew, you may have to set the following as indicated by the brew install:


=====================================
ci/Dockerfile.alpine.test
=====================================
@@ -0,0 +1,15 @@
+ARG PYTHON_VERSION=alpine 
+
+FROM python:${PYTHON_VERSION}
+
+WORKDIR /workspace
+
+RUN apk add --no-cache build-base autoconf automake git xz-dev curl-dev libdeflate-dev bzip2-dev
+
+COPY . .
+
+RUN pip install -r requirements.txt && pip install pytest pytest-cov
+
+# build cyvcf2
+RUN CYVCF2_HTSLIB_CONFIGURE_OPTIONS="--enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate" \
+    CYTHONIZE=1 python setup.py build_ext -i


=====================================
ci/Dockerfile.test → ci/Dockerfile.slim.test
=====================================
@@ -4,17 +4,14 @@ FROM python:${PYTHON_VERSION}
 
 WORKDIR /workspace
 
-RUN apt-get update && apt-get install --no-install-recommends -y autoconf automake gcc \
-    libcurl4-openssl-dev make git libc6-dev zlib1g-dev libssl-dev liblzma-dev libbz2-dev && \
+RUN apt-get update && apt-get install --no-install-recommends -y autoconf automake gcc libcurl4-openssl-dev \
+    make git libc6-dev zlib1g-dev libssl-dev liblzma-dev libbz2-dev libdeflate-dev && \
     rm -rf /var/lib/apt/lists/*
 
 COPY . .
 
-# build htslib
-RUN cd htslib && autoheader && autoconf && autoreconf --install && \
-    ./configure --enable-s3 --disable-lzma --disable-bz2 && make
-
 RUN pip install -r requirements.txt && pip install pytest pytest-cov
 
 # build cyvcf2
-RUN CYTHONIZE=1 python setup.py build_ext -i
+RUN CYVCF2_HTSLIB_CONFIGURE_OPTIONS="--enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --with-libdeflate" \
+    CYTHONIZE=1 python setup.py build_ext -i


=====================================
ci/linux-deps
=====================================
@@ -1,44 +1,43 @@
 #!/bin/bash
 
-set -e
-
-yum install -y openssl11-devel
-yum install -y libffi libffi-devel zlib-devel
-yum install -y bzip2-devel bzip2-libs xz-devel xz-libs
-
-git submodule init
-git submodule update
-git submodule update --init --recursive
-
-curl -L -O https://www.libssh2.org/download/libssh2-1.9.0.tar.gz
-tar xzf libssh2-1.9.0.tar.gz
-cd libssh2-1.9.0
-./configure --prefix=/root
-make
-make install
-cd ..
-rm -rf libssh2-1.9.0
-
-curl -L -O https://curl.se/download/curl-7.73.0.tar.gz
-tar xzf curl-7.73.0.tar.gz
-cd curl-7.73.0
-./configure --with-libssh2 --prefix=/root
-make
-make install
-cd ..
-rm -rf curl-7.73.0
-
-curl -L -o libdeflate-v1.8.tar.gz https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.8.tar.gz
-tar xzf libdeflate-v1.8.tar.gz
-cd libdeflate-1.8
-make
-make install PREFIX=/root
-cd ..
-
-cd htslib
-# configure fails with autoconf 2.71 (in /usr/local/bin), so use 2.69 (in /usr/bin)
-/usr/bin/autoconf -V
-/usr/bin/autoheader
-/usr/bin/autoconf
-./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2
-make
+# Configure the libraries needed to build wheel packages on linux.
+# This script is designed to be used by cibuildwheel as CIBW_BEFORE_ALL_LINUX
+
+set -euo pipefail
+
+# dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+source /etc/os-release
+
+echo "manylinux image woking on $ID"
+
+# Install cyvcf2 development files.
+case "$ID" in
+almalinux)
+    dnf install -y bzip2-devel xz-devel libcurl-devel openssl-devel openblas-devel epel-release
+
+    # packages at epel
+    dnf install -y libdeflate-devel
+    ;;
+
+alpine)
+    apk add --no-cache xz-dev curl-dev libdeflate-dev
+    ;;
+
+centos)
+    yum install -y bzip2-devel xz-devel libcurl-devel openssl-devel
+
+    LIBDEFLATE_VERSION=1.19
+    curl -L -o libdeflate-v"$LIBDEFLATE_VERSION".tar.gz https://github.com/ebiggers/libdeflate/archive/refs/tags/v"$LIBDEFLATE_VERSION".tar.gz
+    tar xzf libdeflate-v"$LIBDEFLATE_VERSION".tar.gz
+    cd libdeflate-"$LIBDEFLATE_VERSION"
+    cmake -B build && cmake --build build
+    cd ./build/ && make install
+    cd ../..
+    ;;
+
+*)
+    echo "$0: unexpected Linux distribution: '$ID'" >&2
+    exit 1
+    ;;
+esac


=====================================
ci/osx-arm64-deps deleted
=====================================
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-set -e
-
-git submodule init
-git submodule update
-git submodule update --init --recursive
-
-# configure fails with autoconf 2.71, so downgrade
-# see https://github.com/asdf-vm/asdf-erlang/issues/195
-brew install autoconf at 2.69 && \
-brew link --overwrite autoconf at 2.69 && \
-autoconf -V
-
-cd htslib
-autoheader
-autoconf
-./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2 --without-libdeflate
-make


=====================================
ci/osx-deps
=====================================
@@ -1,26 +1,26 @@
 #!/bin/bash
 
-set -e
+set -euo pipefail
 
-git submodule init
-git submodule update
-git submodule update --init --recursive
+export DYLD_LIBRARY_PATH=/usr/local/lib
+# same with python
+export MACOSX_DEPLOYMENT_TARGET=10.9
 
-# configure fails with autoconf 2.71, so downgrade
-# see https://github.com/asdf-vm/asdf-erlang/issues/195
-brew install autoconf at 2.69 && \
-brew link --overwrite autoconf at 2.69 && \
-autoconf -V
+brew install automake
+brew unlink xz
 
-curl -L -o libdeflate-v1.8.tar.gz https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.8.tar.gz
-tar xzf libdeflate-v1.8.tar.gz
-cd libdeflate-1.8
-make
-make install
-cd ..
+# build liblzma and libdelfate for muti arch
 
-cd htslib
-autoheader
-autoconf
-./configure --enable-libcurl --enable-s3 --enable-lzma --enable-bz2
-make
+XZ_VERSION=5.4.5
+curl -L -o xz-$XZ_VERSION.tar.gz "https://github.com/tukaani-project/xz/releases/download/v$XZ_VERSION/xz-$XZ_VERSION.tar.gz"
+tar -xf xz-$XZ_VERSION.tar.gz
+cd xz-$XZ_VERSION
+cmake -B build && cmake --build build
+cd ./build && make install
+
+LIBDEFLATE_VERSION=1.19
+curl -L -o libdeflate-v"$LIBDEFLATE_VERSION".tar.gz https://github.com/ebiggers/libdeflate/archive/refs/tags/v"$LIBDEFLATE_VERSION".tar.gz
+tar xzf libdeflate-v"$LIBDEFLATE_VERSION".tar.gz
+cd libdeflate-"$LIBDEFLATE_VERSION"
+cmake -B build && cmake --build build
+cd ./build && make install


=====================================
cyvcf2/__init__.py
=====================================
@@ -2,4 +2,4 @@ from .cyvcf2 import (VCF, Variant, Writer, r_ as r_unphased, par_relatedness,
                      par_het)
 Reader = VCFReader = VCF
 
-__version__ = "0.30.25"
+__version__ = "0.30.28"


=====================================
cyvcf2/cyvcf2.pxd
=====================================
@@ -1,8 +1,11 @@
-from libc.stdint cimport int64_t, int32_t, uint32_t, int8_t, int16_t, uint8_t
+from libc.stdint cimport int64_t, uint64_t, int32_t, uint32_t, int8_t, int16_t, uint8_t
 import numpy as np
 cimport numpy as np
 np.import_array()
 
+cdef extern from "string.h":  
+    void* memcpy(void* dest, const void* src, size_t n)
+
 cdef extern from "relatedness.h":
     int related(int *gt_types, double *asum, int32_t *N, int32_t *ibs0,
                 int32_t *ibs2, int32_t n_samples)
@@ -67,15 +70,23 @@ cdef extern from "htslib/hts.h":
 
     hts_idx_t *bcf_index_load(char *fn)
     hts_idx_t *hts_idx_load2(const char *fn, const char *fnidx);
+    int hts_idx_nseq(const hts_idx_t *idx);
+    int hts_idx_get_stat(const hts_idx_t* idx, int tid, uint64_t* mapped,
+            uint64_t* unmapped);
 
     #int hts_itr_next(BGZF *fp, hts_itr_t *iter, void *r, void *data);
     void hts_itr_destroy(hts_itr_t *iter);
     void hts_idx_destroy(hts_idx_t *idx);
 
 cdef extern from "htslib/tbx.h":
+    ctypedef struct tbx_conf_t:
+        pass
 
+    # Expose details of tbx_t so that we can access the idx field
     ctypedef struct tbx_t:
-        pass
+        tbx_conf_t conf
+        hts_idx_t *idx
+        void *dict
 
     tbx_t *tbx_index_load(const char *fn);
     tbx_t *tbx_index_load2(const char *fn, const char *fnidx);


=====================================
cyvcf2/cyvcf2.pyx
=====================================
@@ -124,6 +124,24 @@ def r_(int32_t[::view.contiguous] a_gts, int32_t[::view.contiguous] b_gts, float
     return r_unphased(&a_gts[0], &b_gts[0], f, n_samples)
 
 
+cdef char* pystring_to_cstring(str py_string):
+    # Convert Python string to byte string using UTF-8 encoding
+    cdef bytes byte_string = py_string.encode('utf-8')  # Convert Python string to bytes
+    # Convert Python bytes to a C-compatible pointer
+    cdef const char* byte_string_ptr = byte_string
+
+    # Allocate memory for C string with size equal to byte string length plus null terminator
+    cdef size_t length = len(byte_string) + 1  # Plus 1 for the null terminator
+    cdef char* c_string = <char*>stdlib.malloc(length)
+    # return NULL if memory allocation fails
+    if c_string == NULL:
+        return c_string
+    # Copy byte string to C string using memcpy and add null terminator
+    memcpy(c_string, byte_string_ptr, length - 1)  # Use memcpy to copy bytes to C string
+    c_string[length - 1] = b'\0'  # Add null terminator to the end of the C string
+    return c_string
+
+
 cdef set_constants(VCF v):
     v.HOM_REF = 0
     v.HET = 1
@@ -199,12 +217,14 @@ cdef class HTSFile:
                     "%s is not valid text_format or binary_format (format: %s mode: %s)" % (fname, self.hts.format.format, mode)
                 )
 
-    def close(self):
+    cdef _c_close(self):
         if self.hts != NULL:
             if self.from_path:
                 hts_close(self.hts)
             self.hts = NULL
 
+    def close(self):
+        self._c_close()
 
 cdef class VCF(HTSFile):
     """
@@ -385,6 +405,14 @@ cdef class VCF(HTSFile):
             raise Exception("unable to update to header")
 
     def set_index(self, index_path=""):
+        # Clear any existing indexes
+        if self.idx != NULL:
+            tbx_destroy(self.idx)
+            self.idx = NULL
+        if self.hidx != NULL:
+            hts_idx_destroy(self.hidx)
+            self.hidx = NULL
+
         if index_path.endswith(".tbi"):
             self.idx = tbx_index_load2(to_bytes(self.fname), to_bytes(index_path))
             if self.idx != NULL:
@@ -567,7 +595,9 @@ cdef class VCF(HTSFile):
         if self.hdr != NULL:
             bcf_hdr_destroy(self.hdr)
             self.hdr = NULL
-        self.close()
+
+        # Shouldn't call Python-space functions here
+        self._c_close()
         if self.idx != NULL:
             tbx_destroy(self.idx)
         if self.hidx != NULL:
@@ -626,28 +656,73 @@ cdef class VCF(HTSFile):
             stdlib.free(sls)
             return self._seqlens
 
+    cdef _open_index(self):
+        """
+        Try to open an index, if not open already.
+        """
+        if self.hidx == NULL and self.idx == NULL:
+            if self.fname.decode(ENC).endswith(('.bcf', '.bcf.gz')):
+                self.hidx = bcf_index_load(self.fname)
+            else:
+                self.idx = tbx_index_load(to_bytes(self.fname))
+
     property seqnames:
         "list of chromosomes in the VCF"
         def __get__(self):
             if len(self._seqnames) > 0: return self._seqnames
-            cdef char **cnames
+            cdef const char **cnames
             cdef int i, n = 0
             cnames = bcf_hdr_seqnames(self.hdr, &n)
-            if n == 0 and self.fname.decode(ENC).endswith(('.bcf', '.bcf.gz')):
-                if self.hidx == NULL:
-                    self.hidx = bcf_index_load(self.fname)
+            if n == 0:
+                self._open_index()
                 if self.hidx != NULL:
                     cnames = bcf_index_seqnames(self.hidx, self.hdr, &n)
-            elif n == 0:
-                if self.idx == NULL:
-                    self.idx = tbx_index_load(to_bytes(self.fname))
                 if self.idx !=NULL:
                     cnames = tbx_seqnames(self.idx, &n)
-
             self._seqnames = [cnames[i].decode() for i in range(n)]
             stdlib.free(cnames)
             return self._seqnames
 
+    cdef _num_records(self):
+        cdef uint64_t total, records, v;
+        cdef int ret, tid, nseq;
+        cdef hts_idx_t *idx = NULL;
+
+        self._open_index()
+        if self.hidx != NULL:
+            idx = self.hidx
+            assert self.idx == NULL
+        if self.idx != NULL:
+            idx = self.idx.idx
+            assert self.hidx == NULL
+
+        if idx == NULL:
+            raise ValueError(
+                "File must be indexed to compute num_records (tip: use bcftools index)")
+
+        nseq = hts_idx_nseq(idx)
+        total = 0;
+        for tid in range(nseq):
+            # NOTE: the return value here doesn't seem to indicate an error
+            # condition, and correct values are computed when it returns < 0.
+            # bcftools index -n doesn't strictly check the output.
+            hts_idx_get_stat(idx, tid, &records, &v);
+            total += records
+        return total
+
+    property num_records:
+        """
+        The number of VCF records in the file, computed from the index.
+        If the file is not indexed (or an index has not been set using 
+        ``set_index``) a ValueError is raised.
+        
+        Note that incorrect values may be returned if a mismatched 
+        index file (i.e., the index for a different VCF file) is used.
+        This is not detected as an error condition.
+        """
+        def __get__(self):
+            return self._num_records()
+
     def plot_relatedness(self, riter):
         import pandas as pd
         from matplotlib import pyplot as plt
@@ -2379,19 +2454,29 @@ cdef class Writer(VCF):
     def variant_from_string(self, variant_string):
         cdef bcf1_t *b = bcf_init()
         cdef kstring_t s
-        tmp = to_bytes(variant_string)
-        s.s = tmp
-        s.l = len(variant_string)
-        s.m = len(variant_string)
+
+        # vcf_parse may realloc s.s, so the memory must be created by malloc
+        s.s = pystring_to_cstring(variant_string)
+        if s.s == NULL:
+            bcf_destroy(b)
+            raise MemoryError("Failed to allocate memory")
+
+        # plus 1 for '\0' of cstring
+        s.l = len(variant_string) + 1
+        s.m = len(variant_string) + 1
+
         ret = vcf_parse(&s, self.hdr, b)
         if ret > 0:
             bcf_destroy(b)
+            ks_free(&s)
             raise Exception("error parsing:" + variant_string + " return value:" + ret)
 
         var = newVariant(b, self)
         if var.b.errcode == BCF_ERR_CTG_UNDEF:
             self.add_to_header("##contig=<ID=%s>" % var.CHROM)
             var.b.errcode = 0
+        
+        ks_free(&s)
         return var
 
     def write_header(Writer self):


=====================================
cyvcf2/helpers.c
=====================================
@@ -4,10 +4,9 @@
 
 int as_gts(int32_t *gts, int num_samples, int ploidy, int strict_gt, int HOM_ALT, int UNKNOWN) {
     int j = 0, i, k;
-    int missing= 0, found=0;
+    int missing= 0;
     for (i = 0; i < ploidy * num_samples; i += ploidy){
         missing = 0;
-    found = 0;
         for (k = 0; k < ploidy; k++) {
             if bcf_gt_is_missing(gts[i+k])  {
                 missing += 1;


=====================================
cyvcf2/tests/multi-contig.bcf
=====================================
Binary files /dev/null and b/cyvcf2/tests/multi-contig.bcf differ


=====================================
cyvcf2/tests/multi-contig.bcf.csi
=====================================
Binary files /dev/null and b/cyvcf2/tests/multi-contig.bcf.csi differ


=====================================
cyvcf2/tests/multi-contig.vcf.gz
=====================================
Binary files /dev/null and b/cyvcf2/tests/multi-contig.vcf.gz differ


=====================================
cyvcf2/tests/multi-contig.vcf.gz.csi
=====================================
Binary files /dev/null and b/cyvcf2/tests/multi-contig.vcf.gz.csi differ


=====================================
cyvcf2/tests/multi-contig.vcf.gz.tbi
=====================================
Binary files /dev/null and b/cyvcf2/tests/multi-contig.vcf.gz.tbi differ


=====================================
cyvcf2/tests/test_reader.py
=====================================
@@ -1,5 +1,6 @@
 from __future__ import print_function
 import os.path
+import platform
 import tempfile
 import sys
 import os
@@ -965,13 +966,20 @@ def test_alt_homozygous_gt():
     assert v.gt_bases[0] == '<*:DEL>/<*:DEL>'
 
 def test_write_missing_contig():
-    input_vcf = VCF('{}/seg.vcf.gz'.format(HERE))
-    output_vcf = Writer('/dev/null', input_vcf)
+    input_vcf = VCF("{}/seg.vcf.gz".format(HERE))
+    if platform.system() == "Windows":
+        output_vcf = "__o.vcf"
+    else:
+        output_vcf = "/dev/null"
+    output_vcf = Writer(output_vcf, input_vcf)
     for v in input_vcf:
-        v.genotypes = [[1,1,False]]
+        v.genotypes = [[1, 1, False]]
         output_vcf.write_record(v)
     output_vcf.close()
 
+    if platform.system() == "Windows":
+        os.unlink("__o.vcf")
+
 def test_set_samples():
     vcf = VCF(VCF_PATH)
     assert len(vcf.samples) == 189, len(vcf.samples)
@@ -1000,9 +1008,14 @@ def test_issue44():
     #           "./."            "."          ".|."           "0|0"
     expected = [[-1, -1, False], [-1, False], [-1, -1, True], [0, 0, True]]
     #print("", file=sys.stderr)
-    for i, v in enumerate(VCF('__o.vcf')):
+
+    t = VCF('__o.vcf')
+    for i, v in enumerate(t):
         #print(v.genotypes, file=sys.stderr)
         assert v.genotypes == [expected[i]], (i, v.genotypes, expected[i])
+
+    # file should be closed before delete
+    t.close()
     os.unlink("__o.vcf")
 
 def test_id_field_updates():
@@ -1283,7 +1296,7 @@ def test_genotypes():
     [0, 0, 1, 1],
     [1, 1, 0, 0],
     [1, 1, 0, 0],
-    ] 
+    ]
 
     strict_exp_num = [x[:] for x in non_strict_exp_num]
     strict_exp_num[1] = [0, 0, 2, 0] # both unknown
@@ -1323,3 +1336,77 @@ def test_issue17_no_gt():
     with pytest.raises(Exception):
         for v in vcf:
             v.num_called  # Used to give segmentation fault
+
+
+ at pytest.mark.parametrize("path", [
+    "test.vcf.gz",
+    "test-multiallelic-homozygous-alt.vcf.gz",
+    "test-strict-gt-option-flag.vcf.gz",
+    "test-strict-gt-option-flag.vcf.gz",
+    "multi-contig.vcf.gz",
+    "multi-contig.bcf",
+    "test.snpeff.bcf",
+    ])
+def test_num_records_indexed(path):
+    vcf = VCF(os.path.join(HERE, path))
+    n = len(list(vcf))
+    assert n == vcf.num_records
+    vcf = VCF(os.path.join(HERE, path))
+    assert n == vcf.num_records
+
+ at pytest.mark.parametrize("suffix", ["csi", "tbi"])
+def test_num_records_indexed_csi_tabix(suffix):
+    path = "multi-contig.vcf.gz"
+    index_file = os.path.join(HERE, "multi-contig.vcf.gz.{}".format(suffix))
+    vcf = VCF(os.path.join(HERE, path))
+    n = len(list(vcf))
+    # Explicitly set the index
+    vcf.set_index(index_file)
+    assert n == vcf.num_records
+    vcf = VCF(os.path.join(HERE, path))
+    vcf.set_index(index_file)
+    assert n == vcf.num_records
+
+def test_num_records_set_index_multiple_times():
+    path = os.path.join(HERE, "multi-contig.vcf.gz")
+    csi_index = path + ".csi"
+    tbi_index = path + ".tbi"
+    vcf = VCF(path)
+    n = len(list(vcf))
+    assert n == vcf.num_records
+    vcf.set_index(csi_index)
+    assert n == vcf.num_records
+
+    vcf = VCF(path)
+    assert n == vcf.num_records
+    vcf.set_index(tbi_index)
+    assert n == vcf.num_records
+
+    vcf = VCF(path)
+    vcf.set_index(csi_index)
+    assert n == vcf.num_records
+
+    vcf = VCF(path)
+    for _ in range(10):
+        vcf.set_index(csi_index)
+        assert n == vcf.num_records
+        vcf.set_index(tbi_index)
+        assert n == vcf.num_records
+
+def test_num_records_set_wrong_index():
+    path = os.path.join(HERE, "multi-contig.vcf.gz")
+    index = os.path.join(HERE, "test.vcf.gz.tbi")
+    vcf = VCF(path)
+    vcf.set_index(index)
+    # We compute the number of records from the index, and don't report an
+    # error
+    assert vcf.num_records == 115
+    assert vcf.num_records != len(list(vcf))
+
+ at pytest.mark.parametrize("path", [
+    "test-genotypes.vcf",
+    ])
+def test_num_records_no_index(path):
+    vcf = VCF(os.path.join(HERE, path))
+    with pytest.raises(ValueError, match="must be indexed"):
+        vcf.num_records


=====================================
debian/changelog
=====================================
@@ -1,3 +1,12 @@
+cyvcf2 (0.30.28-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream version
+    Closes: #1058768
+  * Set CYVCF2_HTSLIB_MODE=EXTERNAL to build with Debian packaged htslib
+
+ -- Andreas Tille <tille at debian.org>  Wed, 07 Feb 2024 11:37:34 +0100
+
 cyvcf2 (0.30.25-1) unstable; urgency=medium
 
   * Team upload.


=====================================
debian/patches/i386.patch
=====================================
@@ -1,7 +1,7 @@
 Description: I386
 --- a/cyvcf2/tests/test_reader.py
 +++ b/cyvcf2/tests/test_reader.py
-@@ -15,6 +15,8 @@
+@@ -16,6 +16,8 @@ import pytest
  
  from ..cyvcf2 import VCF, Variant, Writer
  
@@ -10,7 +10,7 @@ Description: I386
  
  HERE = os.path.dirname(__file__)
  VCF_PATH = os.path.join(HERE, "test.vcf.gz")
-@@ -1241,6 +1243,7 @@
+@@ -1254,6 +1256,7 @@ def test_no_seqlen():
      with pytest.raises(AttributeError):
          vcf.seqlens
  


=====================================
debian/patches/series
=====================================
@@ -1,2 +1,2 @@
-add_htslib_link_dependency.patch
+#add_htslib_link_dependency.patch
 i386.patch


=====================================
debian/rules
=====================================
@@ -1,15 +1,17 @@
 #!/usr/bin/make -f
 
-#export DH_VERBOSE = 1
+export DH_VERBOSE = 1
 
 include /usr/share/dpkg/default.mk
 export LC_ALL=C.UTF-8
 
 export PYBUILD_NAME=cyvcf2
+PYBUILD_BEFORE_TEST=cp -a $(PYBUILD_NAME)/tests {build_dir}/$(PYBUILD_NAME);
 
 export DEB_BUILD_MAINT_OPTIONS=hardening=+all
 
 export CYTHONIZE=1
+export CYVCF2_HTSLIB_MODE=EXTERNAL
 
 %:
 	dh $@ --buildsystem=pybuild
@@ -20,6 +22,10 @@ override_dh_install:
 
 override_dh_auto_test:
 ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+	echo "$(PYBUILD_BEFORE_TEST) seems to be ignored - copying tests dir manually"
+	for tdir in .pybuild/cpython3_* ; do \
+	    cp -a $(PYBUILD_NAME)/tests $${tdir}/build/$(PYBUILD_NAME) ; \
+	done
 	dh_auto_test
 	# clean up after testing
 	find . -name out.vcf -delete


=====================================
pyproject.toml
=====================================
@@ -1,2 +1,8 @@
 [build-system]
-requires = ["setuptools", "wheel", "cython>=0.23.3", "oldest-supported-numpy"]
+requires = [
+    "setuptools",
+    "wheel",
+    "cython>=0.23.3",
+    'oldest-supported-numpy; os_name != "nt"',
+    'numpy; os_name == "nt"'
+]


=====================================
requirements.txt
=====================================
@@ -1,5 +1,6 @@
 cython>=0.23.3
-numpy
 coloredlogs
 click
 setuptools
+numpy; os_name == 'nt'  
+oldest-supported-numpy; os_name != 'nt'


=====================================
setup.py
=====================================
@@ -1,13 +1,20 @@
-import os
+import ctypes
 import glob
+import os
+import platform
+import shutil
 import sys
 import subprocess
-import platform
 
-from setuptools import setup, Extension
+from setuptools import setup, Extension, Command
+from setuptools.command.build_ext import build_ext
+from setuptools.command.sdist import sdist
 
 if sys.version_info.major == 2 and sys.version_info.minor != 7:
-    sys.stderr.write("ERROR: cyvcf2 is only for python 2.7 or greater you are running %d.%d\n", (sys.version_info.major, sys.version_info.minor))
+    sys.stderr.write(
+        "ERROR: cyvcf2 is only for python 2.7 or greater you are running %d.%d\n",
+        (sys.version_info.major, sys.version_info.minor),
+    )
     sys.exit(1)
 
 import numpy as np
@@ -18,15 +25,17 @@ def get_version():
     import ast
 
     with open(os.path.join("cyvcf2", "__init__.py"), "r") as init_file:
-      module = ast.parse(init_file.read())
+        module = ast.parse(init_file.read())
 
-    version = (ast.literal_eval(node.value) for node in ast.walk(module)
-         if isinstance(node, ast.Assign)
-         and node.targets[0].id == "__version__")
+    version = (
+        ast.literal_eval(node.value)
+        for node in ast.walk(module)
+        if isinstance(node, ast.Assign) and node.targets[0].id == "__version__"
+    )
     try:
-      return next(version)
+        return next(version)
     except StopIteration:
-          raise ValueError("version could not be located")
+        raise ValueError("version could not be located")
 
 
 def no_cythonize(extensions, **_ignore):
@@ -41,30 +50,210 @@ def no_cythonize(extensions, **_ignore):
     return extensions
 
 
+def check_libhts():
+    os_type = platform.system()
+    if os_type == "Linux":
+        lib_name = "libhts.so"
+    elif os_type == "Darwin":  # macOS
+        lib_name = "libhts.dylib"
+    elif os_type == "Windows":
+        lib_name = "hts-3.dll"
+    else:
+        return False  # Unsupported OS
+
+    try:
+        ctypes.CDLL(lib_name)
+        return True
+    except Exception:
+        return False
+
+
+def build_htslib(htslib_configure_options, static_mode):
+    current_directory = os.getcwd()
+    os.chdir(os.path.join(current_directory, "htslib"))
+
+    if os.path.exists("config.status"):
+        print("# cyvcf2: config.status exists, skip configure htslib")
+    else:
+        subprocess.run(["autoreconf", "-i"], check=True)
+
+        configure_args = ["./configure"]
+        if static_mode:
+            configure_args.append("CFLAGS=-fPIC")
+        if htslib_configure_options:
+            configure_args.extend(htslib_configure_options.split())
+
+        subprocess.run(configure_args, check=True)
+    subprocess.run(["make"], check=True)
+
+    os.chdir(current_directory)
+
+
+def pre_sdist():
+    current_directory = os.getcwd()
+    os.chdir(os.path.join(current_directory, "htslib"))
+
+    # generate version.h
+    subprocess.run(["make", "htscodecs/htscodecs/version.h"], check=True)
+
+    # remove redundant file
+    redudant_files = ["htscodecs.mk"]
+    for redudant_file in redudant_files:
+        if os.path.exists(redudant_file):
+            os.remove(redudant_file)
+
+    os.chdir(current_directory)
+
+
+class cyvcf2_build_ext(build_ext):
+    def run(self):
+        print("# cyvcf2: htslib mode is {}".format(CYVCF2_HTSLIB_MODE))
+        if CYVCF2_HTSLIB_MODE == "BUILTIN" or not check_libhts():
+            if platform.system() == "Windows":
+                # Windows htslib can't be built internall
+                raise RuntimeError(
+                    "Required library 'hts-3.dll' not found. Please install htslib first."
+                )
+
+            print(
+                "# cyvcf2: htslib configure options is {}".format(
+                    CYVCF2_HTSLIB_CONFIGURE_OPTIONS
+                )
+            )
+            build_htslib(
+                CYVCF2_HTSLIB_CONFIGURE_OPTIONS, CYVCF2_HTSLIB_MODE == "BUILTIN"
+            )
+
+        if CYVCF2_HTSLIB_MODE == "BUILTIN":
+            # add htslib linked libraries
+            # libcrypto is bundled with libssl, capabale to replace libssl
+            all_dynamic_libs = {
+                "z",
+                "bz2",
+                "lzma",
+                "curl",
+                "deflate",
+                "crypto",
+            }
+
+            extra_libs = []
+            # read the htslib config.status file to get the linked libraries
+            with open(os.path.join("htslib", "config.status"), "r") as f:
+                for line in f:
+                    if 'S["static_LIBS"]' in line:
+                        linked_libs_str = line.split("=")[1].strip()[1:-1]
+                        print("# cyvcf2: htslib librarys is {}".format(linked_libs_str))
+                        linked_libs = linked_libs_str.split()
+                        for lib in linked_libs:
+                            if lib[2:] in all_dynamic_libs:  # remove -l prefix
+                                extra_libs.append(lib[2:])
+
+            self.extensions[0].libraries = self.extensions[0].libraries + extra_libs
+
+        super().run()
+
+
+class cyvcf2_sdist(sdist):
+    def run(self):
+        if platform.system() == "Windows":
+            raise RuntimeError("cyvcf2 Can't build sdist on Windows")
+
+        pre_sdist()
+
+        super().run()
+
+
+class clean_ext(Command):
+    description = "clean up Cython temporary files and htslib build files"
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        print("cleaning build files")
+        if os.path.exists("build"):
+            shutil.rmtree("build")
+
+        if os.path.exists("cyvcf2.egg-info"):
+            shutil.rmtree("cyvcf2.egg-info")
+
+        print("cleaning Cython temporary files")
+        cyvcf2_c_path = os.path.join("cyvcf2", "cyvcf2.c")
+        if os.path.exists(cyvcf2_c_path):
+            os.remove(cyvcf2_c_path)
+
+        lib_files = glob.glob("cyvcf2/cyvcf2.cpython-*")
+        for file in lib_files:
+            os.remove(file)
+
+        if platform.system() != "Windows":
+            print("cleaning htslib build files")
+            current_directory = os.getcwd()
+            os.chdir(os.path.join(current_directory, "htslib"))
+            subprocess.run(["make", "distclean"], check=True)
+            os.chdir(current_directory)
+
+
+# How to link against HTSLIB
+# BUILTIN:  build and static link against htslib from
+#           builtin htslib code (default)
+# EXTERNAL: use shared libhts.so compiled outside of
+#           cyvcf2
+if platform.system() == "Windows":
+    # can't static link to htslib on Windows
+    htslib_mode_default = "EXTERNAL"
+else:
+    htslib_mode_default = "BUILTIN"
+
+CYVCF2_HTSLIB_MODE = os.environ.get("CYVCF2_HTSLIB_MODE", htslib_mode_default)
+
+if platform.system() == "Windows" and CYVCF2_HTSLIB_MODE == "BUILTIN":
+    print(
+        "# cyvcf2 WARNING: The use of cyvcf2 on Windows is experimental. It will not work when statically linked to htslib. Fallback to htslib EXTERNAL mode"
+    )
+    CYVCF2_HTSLIB_MODE = "EXTERNAL"
+
+CYVCF2_HTSLIB_CONFIGURE_OPTIONS = os.environ.get(
+    "CYVCF2_HTSLIB_CONFIGURE_OPTIONS", None
+)
+
+htslib_objects = []
+htslib_librarys = []
+htslib_library_dirs = []
+
+if CYVCF2_HTSLIB_MODE == "BUILTIN":
+    htslib_objects = ["htslib/libhts.a"]
+else:
+    htslib_librarys = ["hts"]
+    if not check_libhts():
+        htslib_library_dirs = ["htslib"]
+
+htslib_include_dirs = ["htslib", "htslib/htslib"]
+
 # Build the Cython extension by statically linking to the bundled htslib
-sources = [
-    x for x in glob.glob('htslib/*.c')
-    if not any(e in x for e in ['irods', 'plugin'])
+sources = ["cyvcf2/cyvcf2.pyx", "cyvcf2/helpers.c"]
+
+extensions = [
+    Extension(
+        "cyvcf2.cyvcf2",
+        sources,
+        extra_objects=htslib_objects,
+        libraries=htslib_librarys,
+        extra_compile_args=[
+            "-Wno-sign-compare",
+            "-Wno-unused-function",
+            "-Wno-strict-prototypes",
+            "-Wno-unused-result",
+            "-Wno-discarded-qualifiers",
+        ],
+        include_dirs=["cyvcf2", np.get_include()] + htslib_include_dirs,
+        library_dirs=htslib_library_dirs,
+    )
 ]
-sources += glob.glob('htslib/cram/*.c')
-sources += glob.glob('htslib/htscodecs/htscodecs/*.c')
-# Exclude the htslib sources containing main()'s
-sources = [x for x in sources if not x.endswith(('htsfile.c', 'tabix.c', 'bgzip.c'))]
-sources.append('cyvcf2/helpers.c')
-
-extra_libs = []
-if platform.system() != 'Darwin':
-    extra_libs.append('crypt')
-if bool(int(os.getenv("LIBDEFLATE", 0))):
-    extra_libs.append('deflate')
-
-extensions = [Extension("cyvcf2.cyvcf2",
-                        ["cyvcf2/cyvcf2.pyx"] + sources,
-                        libraries=['z', 'bz2', 'lzma', 'curl', 'ssl'] + extra_libs,
-                        extra_compile_args=["-Wno-sign-compare", "-Wno-unused-function",
-                            "-Wno-strict-prototypes",
-                            "-Wno-unused-result", "-Wno-discarded-qualifiers"],
-                        include_dirs=['htslib', 'cyvcf2', np.get_include()])]
 
 
 CYTHONIZE = bool(int(os.getenv("CYTHONIZE", 0)))
@@ -94,14 +283,19 @@ setup(
     author_email="bpederse at gmail.com",
     version=get_version(),
     ext_modules=extensions,
-    packages=['cyvcf2', 'cyvcf2.tests'],
+    packages=["cyvcf2"],
     entry_points=dict(
         console_scripts=[
-            'cyvcf2 = cyvcf2.__main__:cli',
+            "cyvcf2 = cyvcf2.__main__:cli",
         ],
     ),
     python_requires=">=3.7",
-    install_requires=['numpy', 'coloredlogs', 'click'],
+    install_requires=["numpy", "coloredlogs", "click"],
     include_package_data=True,
     zip_safe=False,
+    cmdclass={
+        "clean_ext": clean_ext,
+        "build_ext": cyvcf2_build_ext,
+        "sdist": cyvcf2_sdist,
+    },
 )



View it on GitLab: https://salsa.debian.org/med-team/cyvcf2/-/compare/ad4fe3d00ebe7805931e81ff2416b2e5125e4935...f957b105421a30a2ce002d99b91b7201db059df0

-- 
View it on GitLab: https://salsa.debian.org/med-team/cyvcf2/-/compare/ad4fe3d00ebe7805931e81ff2416b2e5125e4935...f957b105421a30a2ce002d99b91b7201db059df0
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20240207/5b458f55/attachment-0001.htm>


More information about the debian-med-commit mailing list