diff --git a/.github/actions/build-gk/action.yaml b/.github/actions/build-gk/action.yaml index d6656e5c..685bf440 100644 --- a/.github/actions/build-gk/action.yaml +++ b/.github/actions/build-gk/action.yaml @@ -24,7 +24,7 @@ runs: id: restore_gk_pkg uses: actions/cache/restore@v4 with: - key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'src/**', 'genome_kit/**', 'setup.py', 'setup/**', 'tests/**') }} + key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**', 'tests/**') }} path: | ~/conda-bld @@ -101,6 +101,6 @@ runs: id: save_gk_pkg uses: actions/cache/save@v4 with: - key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'src/**', 'genome_kit/**', 'setup.py', 'setup/**', 'tests/**') }} + key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**', 'tests/**') }} path: | ~/conda-bld diff --git a/.github/actions/curl-meta-yaml/action.yaml b/.github/actions/curl-meta-yaml/action.yaml index 8796ef85..008dec64 100644 --- a/.github/actions/curl-meta-yaml/action.yaml +++ b/.github/actions/curl-meta-yaml/action.yaml @@ -21,7 +21,7 @@ runs: curl -s -L -o conda-recipe/meta.yaml https://raw.githubusercontent.com/conda-forge/genomekit-feedstock/main/recipe/meta.yaml fi - export GK_VERSION=$(grep "version = " setup.py | awk -F'"' '{print $2}') + export GK_VERSION=$(grep -E "^version = " pyproject.toml | head -1 | awk -F'"' '{print $2}') export OS_TYPE=$(uname) if [[ "$OS_TYPE" == "Darwin" ]]; then @@ -38,5 +38,59 @@ runs: # set the version to the local release-please version (for docs and docker publish) $SED_CMD "s/{% set version = \"[^\"]*\" %}/{% set version = \"${GK_VERSION}\" %}/" conda-recipe/meta.yaml + python3 - <<'PY' + from pathlib import Path + + meta = Path("conda-recipe/meta.yaml") + text = meta.read_text() + pip_install = "{{ PYTHON }} -m pip install --no-deps --ignore-installed ." + pip_install_no_isolation = "{{ PYTHON }} -m pip install --no-deps --ignore-installed --no-build-isolation ." + if pip_install in text: + text = text.replace(pip_install, pip_install_no_isolation) + elif pip_install_no_isolation not in text: + raise SystemExit("Unable to find GenomeKit pip install command in conda-recipe/meta.yaml") + + def add_requirement(text, section, requirement): + lines = text.splitlines() + section_start = None + section_end = None + + in_requirements = False + for i, line in enumerate(lines): + if line == "requirements:": + in_requirements = True + continue + if not in_requirements: + continue + if line and not line.startswith(" "): + break + if line == f" {section}:": + section_start = i + for j in range(i + 1, len(lines)): + next_line = lines[j] + if next_line.startswith(" ") and not next_line.startswith(" "): + section_end = j + break + if next_line and not next_line.startswith(" "): + section_end = j + break + else: + section_end = len(lines) + break + + if section_start is None or section_end is None: + raise SystemExit(f"Unable to find requirements.{section} in conda-recipe/meta.yaml") + if any(line.strip() == f"- {requirement}" for line in lines[section_start + 1:section_end]): + return text + + lines.insert(section_end, f" - {requirement}") + return "\n".join(lines) + ("\n" if text.endswith("\n") else "") + + text = add_requirement(text, "build", "cmake") + text = add_requirement(text, "build", "ninja") + text = add_requirement(text, "host", "scikit-build-core") + meta.write_text(text) + PY + head -10 conda-recipe/meta.yaml set +x diff --git a/.github/workflows/build-wheels.yaml b/.github/workflows/build-wheels.yaml index 48f0513e..728cc02e 100644 --- a/.github/workflows/build-wheels.yaml +++ b/.github/workflows/build-wheels.yaml @@ -24,7 +24,8 @@ jobs: id: restore_linux_wheels uses: actions/cache/restore@v4 with: - key: linux-wheels-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} + # setup.py/setup/ were removed; wheel build config now lives in pyproject.toml and CMakeLists.txt. + key: linux-wheels-${{ matrix.arch }}-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} path: wheelhouse/*.whl # skip remaining steps on cache hit @@ -77,7 +78,7 @@ jobs: name: cache linux wheels uses: actions/cache/save@v4 with: - # hashFiles() would produce a different key due to setup/*.pyc files created + # Reuse the restore primary key so saved wheels match the restore key. key: ${{ steps.restore_linux_wheels.outputs.cache-primary-key }} path: wheelhouse/*.whl @@ -93,7 +94,7 @@ jobs: id: restore_macos_wheels uses: actions/cache/restore@v4 with: - key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} + key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} path: wheelhouse/*.whl # skip remaining steps on cache hit @@ -120,7 +121,7 @@ jobs: name: cache macos wheels uses: actions/cache/save@v4 with: - # hashFiles() would produce a different key due to setup/*.pyc files created + # Reuse the restore primary key so saved wheels match the restore key. key: ${{ steps.restore_macos_wheels.outputs.cache-primary-key }} path: wheelhouse/*.whl @@ -151,7 +152,7 @@ jobs: - name: Download built wheels uses: actions/cache/restore@v4 with: - key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} + key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} path: wheelhouse/*.whl fail-on-cache-miss: true @@ -193,7 +194,7 @@ jobs: # id: restore_linux_intel_wheels # uses: actions/cache/restore@v4 # with: -# key: linux-wheels-x86_64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} +# key: linux-wheels-x86_64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} # path: wheelhouse/*.whl # fail-on-cache-miss: true # @@ -201,7 +202,7 @@ jobs: # id: restore_linux_arm_wheels # uses: actions/cache/restore@v4 # with: -# key: linux-wheels-aarch64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} +# key: linux-wheels-aarch64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} # path: wheelhouse/*.whl # fail-on-cache-miss: true # @@ -209,7 +210,7 @@ jobs: # id: restore_macos_wheels # uses: actions/cache/restore@v4 # with: -# key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} +# key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} # path: wheelhouse/*.whl # fail-on-cache-miss: true # @@ -246,7 +247,7 @@ jobs: id: restore_linux_intel_wheels uses: actions/cache/restore@v4 with: - key: linux-wheels-x86_64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} + key: linux-wheels-x86_64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} path: wheelhouse/*.whl fail-on-cache-miss: true @@ -254,7 +255,7 @@ jobs: id: restore_linux_arm_wheels uses: actions/cache/restore@v4 with: - key: linux-wheels-aarch64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} + key: linux-wheels-aarch64-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} path: wheelhouse/*.whl fail-on-cache-miss: true @@ -262,7 +263,7 @@ jobs: id: restore_macos_wheels uses: actions/cache/restore@v4 with: - key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'src/**', 'genome_kit/**', 'pyproject.toml', 'setup.py', 'setup/**') }} + key: macos-wheels-${{ hashFiles('.github/workflows/build-wheels.yaml', '.github/actions/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**') }} path: wheelhouse/*.whl fail-on-cache-miss: true diff --git a/.github/workflows/dockerize.yaml b/.github/workflows/dockerize.yaml index 46da38e3..f4b4069c 100644 --- a/.github/workflows/dockerize.yaml +++ b/.github/workflows/dockerize.yaml @@ -30,7 +30,7 @@ jobs: id: restore_gk_pkg uses: actions/cache/restore@v4 with: - key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'src/**', 'genome_kit/**', 'setup.py', 'setup/**', 'tests/**') }} + key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**', 'tests/**') }} path: | ~/conda-bld fail-on-cache-miss: true diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml index 583bc17b..20708d1d 100644 --- a/.github/workflows/publish-docs.yaml +++ b/.github/workflows/publish-docs.yaml @@ -35,7 +35,7 @@ jobs: id: restore_gk_pkg uses: actions/cache/restore@v4 with: - key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'src/**', 'genome_kit/**', 'setup.py', 'setup/**', 'tests/**') }} + key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**', 'tests/**') }} path: | ~/conda-bld fail-on-cache-miss: true diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 588a1ae8..700371fb 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -7,8 +7,8 @@ on: - ".github/workflows/**" - "conda-recipe/**" - "genome_kit/**" - - "setup.py" - - "setup/**" + - "CMakeLists.txt" + - "pyproject.toml" - "src/**" - "tests/**" @@ -84,7 +84,7 @@ jobs: id: restore_gk_pkg uses: actions/cache/restore@v4 with: - key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'src/**', 'genome_kit/**', 'setup.py', 'setup/**', 'tests/**') }} + key: gk-tarballs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/run-tests.yaml', '.github/actions/**', 'conda-recipe/**', 'CMakeLists.txt', 'pyproject.toml', 'src/**', 'genome_kit/**', 'tests/**') }} path: | ~/conda-bld fail-on-cache-miss: true @@ -131,4 +131,4 @@ jobs: fi conda mambabuild --croot /tmp/conda-bld -t "${files[@]}" --extra-deps "${extra_deps[@]}" conda clean -it - set +x \ No newline at end of file + set +x diff --git a/CMakeLists.txt b/CMakeLists.txt index a8ec85a2..e3322d16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,23 +1,120 @@ cmake_minimum_required(VERSION 3.21) -project(GenomeKit) +project(GenomeKit LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +file(GLOB SOURCES CONFIGURE_DEPENDS src/*.cpp src/*.h) +file(GLOB GK_PY_EXTENSION_SOURCES CONFIGURE_DEPENDS src/*.cpp) +file(GLOB GK_HEADERS CONFIGURE_DEPENDS src/*.h) + +find_package(Python COMPONENTS Interpreter Development.Module NumPy REQUIRED) +find_package(ZLIB REQUIRED) + +if(NOT MSVC) + find_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + endif() +endif() + +Python_add_library(_cxx MODULE WITH_SOABI ${GK_PY_EXTENSION_SOURCES} ${GK_HEADERS}) +target_include_directories(_cxx PRIVATE src "${CMAKE_CURRENT_BINARY_DIR}/gen") +target_link_libraries(_cxx PRIVATE Python::Module Python::NumPy ZLIB::ZLIB) +target_compile_definitions(_cxx PRIVATE GKPY_LIBNAME=genome_kit) +set_target_properties(_cxx PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON +) + +if(MSVC) + target_compile_definitions(_cxx PRIVATE _CRT_SECURE_NO_WARNINGS) + target_compile_options(_cxx PRIVATE + /permissive- + /Zc:__cplusplus + /Zc:strictStrings- + /Zc:preprocessor + /W3 + /EHsc + /wd5033 + $<$:/GS> + $<$:/Zi> + $<$:/Od> + $<$>:/GL> + $<$>:/Gy> + $<$>:/Oy> + $<$>:/Oi> + ) + target_link_options(_cxx PRIVATE + $<$:/DEBUG> + $<$>:/LTCG> + "/PDB:$/_cxx.pdb" + ) +else() + target_compile_definitions(_cxx PRIVATE _FILE_OFFSET_BITS=64) + target_compile_options(_cxx PRIVATE + -Wall + -Wno-write-strings + -Wno-invalid-offsetof + ) + target_link_options(_cxx PRIVATE + $<$>:-Wl,-S> + $<$>:-Wl,-x> + ) + if(APPLE) + if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}) + set(GENOMEKIT_MACOSX_DEPLOYMENT_TARGET "$ENV{MACOSX_DEPLOYMENT_TARGET}") + else() + set(GENOMEKIT_MACOSX_DEPLOYMENT_TARGET "10.15") + endif() + if(DEFINED ENV{CONDA_BUILD_SYSROOT}) + set(GENOMEKIT_MACOS_SYSROOT "$ENV{CONDA_BUILD_SYSROOT}") + elseif(CMAKE_OSX_SYSROOT) + set(GENOMEKIT_MACOS_SYSROOT "${CMAKE_OSX_SYSROOT}") + else() + execute_process( + COMMAND xcrun --sdk macosx --show-sdk-path + OUTPUT_VARIABLE GENOMEKIT_MACOS_SYSROOT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + endif() + target_compile_definitions(_cxx PRIVATE _LIBCPP_DISABLE_AVAILABILITY) + target_compile_options(_cxx PRIVATE + "-mmacosx-version-min=${GENOMEKIT_MACOSX_DEPLOYMENT_TARGET}" + -stdlib=libc++ + -Wshorten-64-to-32 + -Wsign-compare + -Wconditional-uninitialized + -Wuninitialized + -Wno-unknown-warning-option + ) + target_link_options(_cxx PRIVATE + "-mmacosx-version-min=${GENOMEKIT_MACOSX_DEPLOYMENT_TARGET}" + ) + if(GENOMEKIT_MACOS_SYSROOT) + target_compile_options(_cxx PRIVATE "-isysroot${GENOMEKIT_MACOS_SYSROOT}") + target_link_options(_cxx PRIVATE "-isysroot${GENOMEKIT_MACOS_SYSROOT}") + endif() + endif() +endif() + +install(TARGETS _cxx DESTINATION genome_kit) -FILE(GLOB SOURCES src/*.cpp src/*.h) if(DEFINED ENV{IN_CLION}) execute_process( COMMAND $ENV{CONDA_PREFIX}/bin/python -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor), end='')" OUTPUT_VARIABLE PYTHON_VERSION ) - # Don't really need this lib (handled by setup.py), but required for CLion's search etc. + # Don't really need this lib for packaging, but required for CLion's search etc. # NOTE: Set env vars IN_CLION=1;CONDA_PREFIX=$HOME/conda/envs/genomekit_dev in CLion CMake preferences. # include numpy, Python.h include_directories( $ENV{CONDA_PREFIX}/lib/python${PYTHON_VERSION}/site-packages/numpy/core/include/ $ENV{CONDA_PREFIX}/include/python${PYTHON_VERSION}/ ) - find_package(ZLIB) add_library(gkdev ${SOURCES}) target_link_libraries(gkdev ZLIB::ZLIB) target_compile_definitions(gkdev PRIVATE -D_FILE_OFFSET_BITS=64 -DGKPY_LIBNAME=$ENV{CONDA_PREFIX}/lib -D_WANT_MAIN=1) @@ -27,12 +124,11 @@ endif() set(NON_PY_SOURCES ${SOURCES}) list(FILTER NON_PY_SOURCES EXCLUDE REGEX "py_.*") -add_executable(main ${NON_PY_SOURCES}) +add_executable(main EXCLUDE_FROM_ALL ${NON_PY_SOURCES}) target_compile_definitions(main PRIVATE -D_FILE_OFFSET_BITS=64 -D_FILE_ABS_PATH='\"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp\"' -D_WANT_MAIN=1) set_target_properties(main PROPERTIES LINKER_LANGUAGE CXX) -find_package(ZLIB) target_link_libraries(main ZLIB::ZLIB) include(CTest) -add_test(main unittestbuild/main) +add_test(NAME main COMMAND main) set_tests_properties(main PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 0e6f51f2..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -recursive-include src *.h diff --git a/docs-src/develop.rst b/docs-src/develop.rst index b508781a..f36abb71 100644 --- a/docs-src/develop.rst +++ b/docs-src/develop.rst @@ -31,10 +31,9 @@ Build the package in development mode:: pip install -e . -This builds the C++ extension and copies it into -your source tree (``genome_kit/_cxx.so``). -It also ensures that ``import genome_kit`` works from any directory -by linking your source tree from python's ``site-packages``. +This builds the C++ extension with CMake through scikit-build-core and installs +an editable wheel. Python files are loaded from your source tree, while the +compiled extension is loaded from the editable build output. .. note:: Windows Prerequisites @@ -63,7 +62,8 @@ In the CMake settings, set the following environment variables:: Making changes -------------- -If the C/C++ code changed, you must re-run the ``develop`` command:: +If the C/C++ code or build configuration changed, you must re-run the editable +install command:: pip install -e . diff --git a/genomekit_dev.yml b/genomekit_dev.yml index 2179c716..69c7b275 100644 --- a/genomekit_dev.yml +++ b/genomekit_dev.yml @@ -17,14 +17,16 @@ dependencies: # dev + test dependencies - ccache + - build - ipython + - ninja - pyperformance==1.0.4 - pytest + - scikit-build-core - sphinx - sphinx_rtd_theme - cmake - twobitreader>=3.1.0 - - setuptools<80 # Pinned to allow for `pip install -e .` See #167 for details. # mac/linux only dependencies - bcftools diff --git a/pyproject.toml b/pyproject.toml index 6069e17c..e1f11f1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,63 @@ [build-system] requires = [ - "setuptools>=61", - "wheel", + "scikit-build-core>=0.12", "numpy==2" ] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "scikit_build_core.build" + +[project] +name = "genomekit" +version = "7.4.4" +description = "GenomeKit is a Python library for fast and easy access to genomic resources such as sequence, data tracks, and annotations." +readme = "README.md" +requires-python = ">=3.9, <4" +license = "Apache-2.0" +license-files = [ + "COPYRIGHT.txt", + "LICENSE", +] +authors = [ + { name = "Deep Genomics", email = "info@deepgenomics.com" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "appdirs", + "numpy", + "google-cloud-storage", + "boto3", + "tqdm", + "importlib-metadata", + "typing-extensions", +] + +[project.optional-dependencies] +df = [ + "polars", + "polars-runtime-compat; sys_platform == 'darwin' and platform_machine == 'x86_64'", +] + +[project.urls] +Documentation = "https://deepgenomics.github.io/GenomeKit" +Repository = "https://github.com/deepgenomics/GenomeKit" + +[tool.scikit-build] +minimum-version = "build-system.requires" +cmake.build-type = "Release" +cmake.version = ">=3.21" +wheel.packages = ["genome_kit"] +sdist.include = [ + "CMakeLists.txt", + "COPYRIGHT.txt", + "LICENSE", + "README.md", + "genome_kit/**", + "src/*.cpp", + "src/*.h", + "tests/**", +] diff --git a/setup.py b/setup.py deleted file mode 100644 index 2413b60f..00000000 --- a/setup.py +++ /dev/null @@ -1,431 +0,0 @@ -# Copyright (C) 2016-2023 Deep Genomics Inc. All Rights Reserved. -import os -import platform -import shutil -import sys -import sysconfig -from glob import glob -from pathlib import Path -from traceback import extract_stack - -from setuptools import Extension -from setuptools import setup, find_packages -from setuptools._distutils import ccompiler -from setuptools.command.build_ext import build_ext -from setuptools.command.egg_info import egg_info - -COPYRIGHT_FILE = "COPYRIGHT.txt" -LICENSE_FILE = "LICENSE" - -tests_require = [ - "twobitreader>=3.1", -] - -version = "7.4.4" - -# See https://stackoverflow.com/questions/9977889/how-to-include-license-file-in-setup-py-script/66443941#66443941 -class egg_info_ex(egg_info): - """Includes license file into `.egg-info` folder.""" - - def run(self): - # don't duplicate license into `.egg-info` when building a distribution - if not self.distribution.have_run.get('install', True): - # `install` command is in progress, copy license - self.mkpath(self.egg_info) - self.copy_file(COPYRIGHT_FILE, self.egg_info) - self.copy_file(LICENSE_FILE, self.egg_info) - - egg_info.run(self) - - - -libname = "genome_kit" - -############################################################## -# Define the C Extension build config -############################################################## - -debug = False # Set to True for C++ debug symbols and extra memory/bounds checks -debug_info = debug -debug_objects = False # Enable printing of Python C++ instances being constructed/destructed -toolset = "msvc" if platform.system() == "Windows" else "gcc" - -gen_dir = os.path.join('build', 'gen') -include_dirs = [ - sys.prefix + "/include", - gen_dir, - ] -library_dirs = [sys.prefix + '/lib'] -runtime_library_dirs = [] - -if not [x for x in extract_stack(limit=20) if 'load_setup_py_data' in x[2]]: - # don't load numpy if we just need the version to fill the conda-build meta.yaml template - import numpy as np - include_dirs.append(np.get_include()) - -sources = glob("src/*.cpp") -headers = glob("src/*.h") -libraries = [] - -define_macros = [ - ("GKPY_LIBNAME", libname), -] - -if debug_objects: - define_macros += [ - ("GKPY_TRACE_MEM", None), # ctor/dtor notifications from C objects - ] - -extra_compile_args = [] -extra_link_args = [] - -if toolset == "gcc": - # Using multiprocessing and ccache massively speeds up incremental builds - ccache = shutil.which('ccache') - if ccache: - os.environ["CC"] = "{} {}".format(ccache, os.environ.get("CC", "gcc")) - # cannot use ccache in CXX since distutils hotpatches into LDSHARED: - # https://github.com/python/cpython/blob/069306312addf87252e2dbf250fc7632fc8b7da3/Lib/distutils/unixccompiler.py#L191 - os.environ["LDSHARED"] = sysconfig.get_config_var("LDSHARED") - else: - print("WARNING: did not find ccache installed; files will be built from scratch every time") - - define_macros += [ - ("_FILE_OFFSET_BITS", 64), - ] - if debug: - define_macros += [ - ("GK_DEBUG", None), # Enable debug assertions etc - ] - - libraries += [ - # python is not linked for conda's python - # https://github.com/ContinuumIO/anaconda-issues/issues/9078#issuecomment-378321357 - "z", - ] - - # GCC flags common to both debug and release modes - extra_compile_args += [ - "-std=c++20", - "-fvisibility=hidden", # reduce symbols for code size/load times - "-fvisibility-inlines-hidden", - "-Wall", - "-Wno-write-strings", # char* in Python API - "-Wno-invalid-offsetof", # offsetof non-POD GK types for Python API - ] - - if debug: - opt_args = [ - "-O0", - "-UNDEBUG", - ] - else: - opt_args = [ - "-O3", - ] - - if debug_info: - opt_args += [ - "-g3", - ] - else: - extra_link_args += [ - "-Wl,-S", - "-Wl,-x", - ] - - if platform.system() == "Darwin": - # >=10.15 required for std::filesystem::remove - osx_sdk = "-mmacosx-version-min={}".format(os.environ.get("MACOSX_DEPLOYMENT_TARGET", "10.15")) - - extra_compile_args += [ - osx_sdk, - "-isysroot{}".format(os.environ.get("CONDA_BUILD_SYSROOT", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")), - "-stdlib=libc++", - "-Wshorten-64-to-32", # catch implicit truncation as per MSVC - "-Wsign-compare", # match MSVC(/W3)/gcc(-Wall) - "-Wconditional-uninitialized", # gcc does better here but enable for safety - "-Wuninitialized", - "-Wno-unknown-warning-option", - ] - - extra_link_args += [ - osx_sdk, - "-isysroot{}".format(os.environ.get("CONDA_BUILD_SYSROOT", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")), - ] - define_macros += [ - # https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk - ("_LIBCPP_DISABLE_AVAILABILITY", None), - ] - - extra_compile_args += opt_args - extra_link_args += opt_args # required for LTO - - -elif toolset == "msvc": - - os.environ["DISTUTILS_USE_SDK"] = "1" - os.environ["MSSdk"] = "1" - - condalib_dir = sys.prefix + "/Library" - condalib_inc = condalib_dir + "/include" - condalib_lib = condalib_dir + "/lib" - include_dirs.append(condalib_inc) - library_dirs.append(condalib_lib) - - define_macros += [ - ("_CRT_SECURE_NO_WARNINGS", None), - ] - - libraries += [ - "zlib", - ] - - # VC flags common to both debug and release modes - extra_compile_args += [ - "/std:c++20", - "/permissive-", - "/Zc:__cplusplus", - "/Zc:strictStrings-", # don"t let strings be written to by default - # compatibility with __VA_OPT__ - # see https://devblogs.microsoft.com/cppblog/announcing-full-support-for-a-c-c-conformant-preprocessor-in-msvc/ - "/Zc:preprocessor", - "/W3", # Warning level 3 - "/EHsc", # Enable C++ and structured exception handling (e.g. catch access violations) - "/wd5033", # Python usage of deprecated register keyword - ] - - extra_link_args += [ - "/PDB:%s\\_cxx.pdb" % libname, # Put it right beside the .pyd/.so file in "develop" mode - ] - - if debug: - # do NOT define _DEBUG; need non-debug runtime to match NDEBUG Python distribution - # define_macros += [ - # ("_DEBUG", None), - # ] - extra_compile_args += [ - "/GS", # Enable buffer overrun checks - "/Zi", # Enable debug information .pdb - "/Od", # Disable optimizations - ] - extra_link_args += [ - "/DEBUG", - ] - else: - extra_compile_args += [ - "/GL", # Enable whole-program optimization - "/Gy", # Enable function-level linking - "/Oy", # Omit frame pointers - "/Oi", # Enable intrinsics - #"/Zi", # Enable debug information .pdb - ] - extra_link_args += [ - "/LTCG", # Enable link-time code generation - #"/DEBUG", - ] - - -class NoCWarningsBuildExt(build_ext): - def build_extensions(self): - for x in ["-Wstrict-prototypes"]: - try: - self.compiler.compiler_so.remove(x) - except (AttributeError, ValueError): - continue - build_ext.build_extensions(self) - - -extension = Extension( - libname + "._cxx", - sources=sources, - depends=headers, - include_dirs=include_dirs, - define_macros=define_macros, - library_dirs=library_dirs, - libraries=libraries, - runtime_library_dirs=runtime_library_dirs, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - ) - -############################################################## -# TODO convert to meson or scikit-build - -# monkey-patch for parallel compilation -# taken from http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils - -PARALLEL_JOBS = 4 # number of parallel compilations - - -def gcc_parallel_ccompile(self, - sources, - output_dir=None, - macros=None, - include_dirs=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - depends=None): - # those lines are copied from distutils.ccompiler.CCompiler directly - macros, objects, extra_postargs, pp_opts, build = self._setup_compile(output_dir, macros, include_dirs, sources, - depends, extra_postargs) - cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) - # parallel code - import multiprocessing.pool - - def _single_compile(obj): - try: - src, ext = build[obj] - except KeyError: - return - self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) - - # convert to list, imap is evaluated on-demand - list(multiprocessing.pool.ThreadPool(PARALLEL_JOBS).imap(_single_compile, objects)) - return objects - - -def windows_parallel_ccompile(self, - sources, - output_dir=None, - macros=None, - include_dirs=None, - debug=0, - extra_preargs=None, - extra_postargs=None, - depends=None): - if not self.initialized: - self.initialize() - compile_info = self._setup_compile(output_dir, macros, include_dirs, sources, depends, extra_postargs) - macros, objects, extra_postargs, pp_opts, build = compile_info - - from setuptools._distutils.errors import CompileError - from setuptools._distutils.errors import DistutilsExecError - - compile_opts = extra_preargs or [] - compile_opts.append("/c") - compile_opts.extend(self.compile_options_debug if debug else self.compile_options) - - def _compile_obj(obj): - try: - src, ext = build[obj] - except KeyError: - return - if debug: - # pass the full pathname to MSVC in debug mode, - # this allows the debugger to find the source file - # without asking the user to browse for it - src = os.path.abspath(src) - - if ext in self._c_extensions: - input_opt = "/Tc" + src - elif ext in self._cpp_extensions: - input_opt = "/Tp" + src - elif ext in self._rc_extensions: - # compile .RC to .RES file - input_opt = src - output_opt = "/fo" + obj - try: - self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt]) - except DistutilsExecError as msg: - raise CompileError(msg) - return - elif ext in self._mc_extensions: - # Compile .MC to .RC file to .RES file. - # * "-h dir" specifies the directory for the - # generated include file - # * "-r dir" specifies the target directory of the - # generated RC file and the binary message resource - # it includes - # - # For now (since there are no options to change this), - # we use the source-directory for the include file and - # the build directory for the RC file and message - # resources. This works at least for win32all. - h_dir = os.path.dirname(src) - rc_dir = os.path.dirname(obj) - try: - # first compile .MC to .RC and .H file - self.spawn([self.mc] + ["-h", h_dir, "-r", rc_dir] + [src]) - base, _ = os.path.splitext(os.path.basename(src)) - rc_file = os.path.join(rc_dir, base + ".rc") - # then compile .RC to .RES file - self.spawn([self.rc] + ["/fo" + obj] + [rc_file]) - - except DistutilsExecError as msg: - raise CompileError(msg) - return - else: - # how to handle this file? - raise CompileError("Don't know how to compile %s to %s" % (src, obj)) - - output_opt = "/Fo" + obj - try: - self.spawn([self.cc] + compile_opts + pp_opts + [input_opt, output_opt] + extra_postargs) - except DistutilsExecError as msg: - raise CompileError(msg) - - import multiprocessing.pool - list(multiprocessing.pool.ThreadPool(PARALLEL_JOBS).imap(_compile_obj, objects)) - - return objects - - -if sys.platform == "win32": - import setuptools._distutils._msvccompiler - setuptools._distutils._msvccompiler.MSVCCompiler.compile = windows_parallel_ccompile -else: - ccompiler.CCompiler.compile = gcc_parallel_ccompile - -if __name__ == "__main__": - setup( - author="Deep Genomics", - author_email="info@deepgenomics.com", - python_requires=">=3.9, <4", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - ], - description="GenomeKit is a Python library for fast and easy access to genomic resources such as sequence, data tracks, and annotations.", - long_description=(Path(__file__).parent / "README.md").read_text(), - long_description_content_type='text/markdown', - install_requires=[ - "appdirs", - "numpy", - "google-cloud-storage", - "boto3", - "tqdm", - "importlib-metadata", - "typing-extensions", - ], - extras_require={ - # install polars-runtime-compat if running on x86_64 Python on macOS - # required to run polars due to AVX features compatibility issues - "df": [ - "polars", - "polars-runtime-compat; sys_platform == 'darwin' and platform_machine == 'x86_64'", - ] - }, - license="Apache License 2.0", - license_files=(COPYRIGHT_FILE, LICENSE_FILE,), - name="genomekit", - packages=find_packages(include=["genome_kit", "genome_kit.*"]), - project_urls={ - "Documentation": "https://deepgenomics.github.io/GenomeKit" - }, - cmdclass={ - 'build_ext': NoCWarningsBuildExt, - 'egg_info': egg_info_ex - }, - ext_modules=[extension], - test_suite="tests", - tests_require=tests_require, - url=f"https://github.com/deepgenomics/GenomeKit", - version=version, - zip_safe=False, - )