diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index dfccdcd..5d3cfa2 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -50,7 +50,7 @@ jobs: fail-fast: false matrix: platform: [windows-latest, macos-latest, ubuntu-latest] - python-version: ["3.7", "3.11"] #, 3.12 + python-version: ["3.8", "3.12"] runs-on: ${{ matrix.platform }} @@ -91,7 +91,6 @@ jobs: path: ${{ env.CONAN_HOME }} - name: Test - if: matrix.platform != 'windows-latest' run: python -m pytest test -v diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index aad9057..c1c6561 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -70,9 +70,14 @@ jobs: path: dist/*.tar.gz - build-wheels-linux: - name: Wheels on Linux - runs-on: ubuntu-latest + build-wheels: + name: Build Wheels + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-22.04, macos-12, macos-14, windows-2019] + fail-fast: false steps: - uses: actions/checkout@v4 @@ -80,149 +85,32 @@ jobs: fetch-depth: 0 - name: Set up QEMU + if: matrix.os == 'ubuntu-22.04' uses: docker/setup-qemu-action@v3 with: platforms: all - - name: Build wheels + - name: Build wheels (PR) uses: pypa/cibuildwheel@v2.16 + if: github.event_name == 'pull_request' env: - CIBW_ARCHS_LINUX: x86_64 - - - name: Verify clean directory - run: git diff --exit-code - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: wheels-linux - path: wheelhouse/*.whl - - build-wheels-macos: - name: Wheels on macOS - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Generate cache key - id: cache-key - run: | - hash="${{ hashFiles('conanfile.txt') }}" - - echo "conan-key=conan-macos-$hash" >> $GITHUB_OUTPUT - - - name: Restore Conan cache - id: cache-conan - uses: actions/cache/restore@v4 - with: - key: ${{ steps.cache-key.outputs.conan-key }} - path: ${{ env.CONAN_HOME }} + CIBW_ARCHS_LINUX: "x86_64" + CIBW_ARCHS_WINDOWS: "AMD64" - name: Build wheels + uses: pypa/cibuildwheel@v2.16 + if: github.event_name != 'pull_request' env: - MACOSX_DEPLOYMENT_TARGET: '10.15' - run: pip wheel . -vv --no-deps - - - name: Save Conan cache - uses: actions/cache/save@v4 - if: steps.cache-conan.outputs.cache-hit != 'true' - with: - key: ${{ steps.cache-key.outputs.conan-key }} - path: ${{ env.CONAN_HOME }} - - - name: Fix wheels - run: | - pip install delocate + CIBW_ARCHS_LINUX: "x86_64 aarch64" + CIBW_ARCHS_WINDOWS: "AMD64" - mkdir lib/ - find "$CONAN_HOME/p/" -name '*.dylib' -exec cp '{}' lib/ \; - - DYLD_LIBRARY_PATH="$PWD/lib/:/usr/local/lib:/usr/lib" \ - delocate-wheel --check-archs -v hictkpy*.whl - - - name: Test wheels - run: | - wheel=(hictkpy*.whl) - pip install "${wheel[@]}[test]" - pytest test -v - - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: wheels-macos-py${{ matrix.python-version }} - path: ./*.whl - - build-wheels-windows: - name: Wheels on Windows - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - python-version: [ '3.8', '3.9', '3.10', '3.11' ] #, '3.12' ] - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Generate cache key - id: cache-key - run: | - hash="${{ hashFiles('conanfile.txt') }}" - - echo "conan-key=conan-windows-$hash" >> $GITHUB_OUTPUT - - - name: Restore Conan cache - id: cache-conan - uses: actions/cache/restore@v4 - with: - key: ${{ steps.cache-key.outputs.conan-key }} - path: ${{ env.CONAN_HOME }} - - - name: Build wheels - run: pip wheel . -vv --no-deps - - - name: Save Conan cache - uses: actions/cache/save@v4 - if: steps.cache-conan.outputs.cache-hit != 'true' - with: - key: ${{ steps.cache-key.outputs.conan-key }} - path: ${{ env.CONAN_HOME }} - - - name: Fix wheels - run: | - pip install delvewheel - - mkdir dlls/ - find "$CONAN_HOME/p/" -type f -name '*.dll' -exec cp '{}' dlls/ \; - - delvewheel repair --add-path ./dlls/ hictkpy*.whl - - - name: Test wheels - run: | - wheel=(wheelhouse/hictkpy*.whl) - pip install "${wheel[@]}[test]" - pytest test -v + - name: Verify clean directory + run: git diff --exit-code - name: Upload wheels uses: actions/upload-artifact@v4 with: - name: wheels-windows-py${{ matrix.python-version }} + name: "wheels-${{ matrix.os }}" path: wheelhouse/*.whl package-artifacts: @@ -230,9 +118,7 @@ jobs: runs-on: ubuntu-latest needs: - build-sdist - - build-wheels-linux - - build-wheels-macos - - build-wheels-windows + - build-wheels steps: - name: Download artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index 76d7dee..86811cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,6 @@ FetchContent_Declare( URL_HASH "SHA256=c003924e92a9957a0cf43b85ae9ede29392be5227f427ca2fab9e420ea8217c9" SYSTEM) -set(BUILD_SHARED_LIBS ON) set(HICTK_ENABLE_TESTING OFF) set(HICTK_BUILD_EXAMPLES OFF) set(HICTK_BUILD_BENCHMARKS OFF) @@ -50,13 +49,13 @@ endif() add_library(hictkpy_project_warnings INTERFACE) target_compile_options( - hictkpy_project_warnings - INTERFACE # C++ warnings - $<$:${HICTKPY_PROJECT_WARNINGS_CXX}> - # C warnings - $<$:${HICTKPY_PROJECT_WARNINGS_C}> - # Cuda warnings - $<$:${HICTKPY_PROJECT_WARNINGS_CUDA}>) + hictkpy_project_warnings + INTERFACE # C++ warnings + $<$:${HICTKPY_PROJECT_WARNINGS_CXX}> + # C warnings + $<$:${HICTKPY_PROJECT_WARNINGS_C}> + # Cuda warnings + $<$:${HICTKPY_PROJECT_WARNINGS_CUDA}>) if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") diff --git a/cmake/conan_provider.cmake b/cmake/conan_provider.cmake new file mode 100644 index 0000000..c21ab38 --- /dev/null +++ b/cmake/conan_provider.cmake @@ -0,0 +1,627 @@ +set(CONAN_MINIMUM_VERSION 2.0.5) + + +function(detect_os OS OS_API_LEVEL OS_SDK OS_SUBSYSTEM OS_VERSION) + # it could be cross compilation + message(STATUS "CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}") + if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(${OS} Macos PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME STREQUAL "QNX") + set(${OS} Neutrino PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") + set(${OS} Windows PARENT_SCOPE) + set(${OS_SUBSYSTEM} cygwin PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME MATCHES "^MSYS") + set(${OS} Windows PARENT_SCOPE) + set(${OS_SUBSYSTEM} msys2 PARENT_SCOPE) + else() + set(${OS} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Android") + if(DEFINED ANDROID_PLATFORM) + string(REGEX MATCH "[0-9]+" _OS_API_LEVEL ${ANDROID_PLATFORM}) + elseif(DEFINED CMAKE_SYSTEM_VERSION) + set(_OS_API_LEVEL ${CMAKE_SYSTEM_VERSION}) + endif() + message(STATUS "CMake-Conan: android api level=${_OS_API_LEVEL}") + set(${OS_API_LEVEL} ${_OS_API_LEVEL} PARENT_SCOPE) + endif() + if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") + # CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja + # generators, but just has the original input string for Xcode. + if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) + set(_OS_SDK ${CMAKE_OSX_SYSROOT}) + else() + if(CMAKE_OSX_SYSROOT MATCHES Simulator) + set(apple_platform_suffix simulator) + else() + set(apple_platform_suffix os) + endif() + if(CMAKE_OSX_SYSROOT MATCHES AppleTV) + set(_OS_SDK "appletv${apple_platform_suffix}") + elseif(CMAKE_OSX_SYSROOT MATCHES iPhone) + set(_OS_SDK "iphone${apple_platform_suffix}") + elseif(CMAKE_OSX_SYSROOT MATCHES Watch) + set(_OS_SDK "watch${apple_platform_suffix}") + endif() + endif() + if(DEFINED _OS_SDK) + message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}") + set(${OS_SDK} ${_OS_SDK} PARENT_SCOPE) + endif() + if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) + message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}") + set(${OS_VERSION} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE) + endif() + endif() + endif() +endfunction() + + +function(detect_arch ARCH) + # CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one. + # Therefore this code only finds one. If the recipes support multiple architectures, the + # build will work. Otherwise, there will be a linker error for the missing architecture(s). + if(DEFINED CMAKE_OSX_ARCHITECTURES) + string(REPLACE " " ";" apple_arch_list "${CMAKE_OSX_ARCHITECTURES}") + list(LENGTH apple_arch_list apple_arch_count) + if(apple_arch_count GREATER 1) + message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.") + endif() + endif() + if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "") + set(host_arch ${CMAKE_OSX_ARCHITECTURES}) + elseif(MSVC) + set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) + else() + set(host_arch ${CMAKE_SYSTEM_PROCESSOR}) + endif() + if(host_arch MATCHES "aarch64|arm64|ARM64") + set(_ARCH armv8) + elseif(host_arch MATCHES "armv7|armv7-a|armv7l|ARMV7") + set(_ARCH armv7) + elseif(host_arch MATCHES armv7s) + set(_ARCH armv7s) + elseif(host_arch MATCHES "i686|i386|X86") + set(_ARCH x86) + elseif(host_arch MATCHES "AMD64|amd64|x86_64|x64") + set(_ARCH x86_64) + endif() + message(STATUS "CMake-Conan: cmake_system_processor=${_ARCH}") + set(${ARCH} ${_ARCH} PARENT_SCOPE) +endfunction() + + +function(detect_cxx_standard CXX_STANDARD) + set(${CXX_STANDARD} ${CMAKE_CXX_STANDARD} PARENT_SCOPE) + if(CMAKE_CXX_EXTENSIONS) + set(${CXX_STANDARD} "gnu${CMAKE_CXX_STANDARD}" PARENT_SCOPE) + endif() +endfunction() + + +macro(detect_gnu_libstdcxx) + # _CONAN_IS_GNU_LIBSTDCXX true if GNU libstdc++ + check_cxx_source_compiles(" + #include + #if !defined(__GLIBCXX__) && !defined(__GLIBCPP__) + static_assert(false); + #endif + int main(){}" _CONAN_IS_GNU_LIBSTDCXX) + + # _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI true if C++11 ABI + check_cxx_source_compiles(" + #include + static_assert(sizeof(std::string) != sizeof(void*), \"using libstdc++\"); + int main () {}" _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) + + set(_CONAN_GNU_LIBSTDCXX_SUFFIX "") + if(_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) + set(_CONAN_GNU_LIBSTDCXX_SUFFIX "11") + endif() + unset (_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) +endmacro() + + +macro(detect_libcxx) + # _CONAN_IS_LIBCXX true if LLVM libc++ + check_cxx_source_compiles(" + #include + #if !defined(_LIBCPP_VERSION) + static_assert(false); + #endif + int main(){}" _CONAN_IS_LIBCXX) +endmacro() + + +function(detect_lib_cxx LIB_CXX) + if(CMAKE_SYSTEM_NAME STREQUAL "Android") + message(STATUS "CMake-Conan: android_stl=${CMAKE_ANDROID_STL_TYPE}") + set(${LIB_CXX} ${CMAKE_ANDROID_STL_TYPE} PARENT_SCOPE) + return() + endif() + + include(CheckCXXSourceCompiles) + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + detect_gnu_libstdcxx() + set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") + set(${LIB_CXX} "libc++" PARENT_SCOPE) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + # Check for libc++ + detect_libcxx() + if(_CONAN_IS_LIBCXX) + set(${LIB_CXX} "libc++" PARENT_SCOPE) + return() + endif() + + # Check for libstdc++ + detect_gnu_libstdcxx() + if(_CONAN_IS_GNU_LIBSTDCXX) + set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) + return() + endif() + + # TODO: it would be an error if we reach this point + elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Do nothing - compiler.runtime and compiler.runtime_type + # should be handled separately: https://github.com/conan-io/cmake-conan/pull/516 + return() + else() + # TODO: unable to determine, ask user to provide a full profile file instead + endif() +endfunction() + + +function(detect_compiler COMPILER COMPILER_VERSION COMPILER_RUNTIME COMPILER_RUNTIME_TYPE) + if(DEFINED CMAKE_CXX_COMPILER_ID) + set(_COMPILER ${CMAKE_CXX_COMPILER_ID}) + set(_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) + else() + if(NOT DEFINED CMAKE_C_COMPILER_ID) + message(FATAL_ERROR "C or C++ compiler not defined") + endif() + set(_COMPILER ${CMAKE_C_COMPILER_ID}) + set(_COMPILER_VERSION ${CMAKE_C_COMPILER_VERSION}) + endif() + + message(STATUS "CMake-Conan: CMake compiler=${_COMPILER}") + message(STATUS "CMake-Conan: CMake compiler version=${_COMPILER_VERSION}") + + if(_COMPILER MATCHES MSVC) + set(_COMPILER "msvc") + string(SUBSTRING ${MSVC_VERSION} 0 3 _COMPILER_VERSION) + # Configure compiler.runtime and compiler.runtime_type settings for MSVC + if(CMAKE_MSVC_RUNTIME_LIBRARY) + set(_msvc_runtime_library ${CMAKE_MSVC_RUNTIME_LIBRARY}) + else() + set(_msvc_runtime_library MultiThreaded$<$:Debug>DLL) # default value documented by CMake + endif() + + set(_KNOWN_MSVC_RUNTIME_VALUES "") + list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded MultiThreadedDLL) + list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreadedDebug MultiThreadedDebugDLL) + list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded$<$:Debug> MultiThreaded$<$:Debug>DLL) + + # only accept the 6 possible values, otherwise we don't don't know to map this + if(NOT _msvc_runtime_library IN_LIST _KNOWN_MSVC_RUNTIME_VALUES) + message(FATAL_ERROR "CMake-Conan: unable to map MSVC runtime: ${_msvc_runtime_library} to Conan settings") + endif() + + # Runtime is "dynamic" in all cases if it ends in DLL + if(_msvc_runtime_library MATCHES ".*DLL$") + set(_COMPILER_RUNTIME "dynamic") + else() + set(_COMPILER_RUNTIME "static") + endif() + message(STATUS "CMake-Conan: CMake compiler.runtime=${_COMPILER_RUNTIME}") + + # Only define compiler.runtime_type when explicitly requested + # If a generator expression is used, let Conan handle it conditional on build_type + if(NOT _msvc_runtime_library MATCHES ":Debug>") + if(_msvc_runtime_library MATCHES "Debug") + set(_COMPILER_RUNTIME_TYPE "Debug") + else() + set(_COMPILER_RUNTIME_TYPE "Release") + endif() + message(STATUS "CMake-Conan: CMake compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") + endif() + + unset(_KNOWN_MSVC_RUNTIME_VALUES) + + elseif(_COMPILER MATCHES AppleClang) + set(_COMPILER "apple-clang") + string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) + list(GET VERSION_LIST 0 _COMPILER_VERSION) + elseif(_COMPILER MATCHES Clang) + set(_COMPILER "clang") + string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) + list(GET VERSION_LIST 0 _COMPILER_VERSION) + elseif(_COMPILER MATCHES GNU) + set(_COMPILER "gcc") + string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) + list(GET VERSION_LIST 0 _COMPILER_VERSION) + endif() + + message(STATUS "CMake-Conan: [settings] compiler=${_COMPILER}") + message(STATUS "CMake-Conan: [settings] compiler.version=${_COMPILER_VERSION}") + if (_COMPILER_RUNTIME) + message(STATUS "CMake-Conan: [settings] compiler.runtime=${_COMPILER_RUNTIME}") + endif() + if (_COMPILER_RUNTIME_TYPE) + message(STATUS "CMake-Conan: [settings] compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") + endif() + + set(${COMPILER} ${_COMPILER} PARENT_SCOPE) + set(${COMPILER_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) + set(${COMPILER_RUNTIME} ${_COMPILER_RUNTIME} PARENT_SCOPE) + set(${COMPILER_RUNTIME_TYPE} ${_COMPILER_RUNTIME_TYPE} PARENT_SCOPE) +endfunction() + + +function(detect_build_type BUILD_TYPE) + get_property(_MULTICONFIG_GENERATOR GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(NOT _MULTICONFIG_GENERATOR) + # Only set when we know we are in a single-configuration generator + # Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined + set(${BUILD_TYPE} ${CMAKE_BUILD_TYPE} PARENT_SCOPE) + endif() +endfunction() + +macro(set_conan_compiler_if_appleclang lang command output_variable) + if(CMAKE_${lang}_COMPILER_ID STREQUAL "AppleClang") + execute_process(COMMAND xcrun --find ${command} + OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE) + cmake_path(GET _xcrun_out PARENT_PATH _xcrun_toolchain_path) + cmake_path(GET CMAKE_${lang}_COMPILER PARENT_PATH _compiler_parent_path) + if ("${_xcrun_toolchain_path}" STREQUAL "${_compiler_parent_path}") + set(${output_variable} "") + endif() + unset(_xcrun_out) + unset(_xcrun_toolchain_path) + unset(_compiler_parent_path) + endif() +endmacro() + + +macro(append_compiler_executables_configuration) + set(_conan_c_compiler "") + set(_conan_cpp_compiler "") + if(CMAKE_C_COMPILER) + set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\",") + set_conan_compiler_if_appleclang(C cc _conan_c_compiler) + else() + message(WARNING "CMake-Conan: The C compiler is not defined. " + "Please define CMAKE_C_COMPILER or enable the C language.") + endif() + if(CMAKE_CXX_COMPILER) + set(_conan_cpp_compiler "\"cpp\":\"${CMAKE_CXX_COMPILER}\"") + set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler) + else() + message(WARNING "CMake-Conan: The C++ compiler is not defined. " + "Please define CMAKE_CXX_COMPILER or enable the C++ language.") + endif() + + if(NOT "x${_conan_c_compiler}${_conan_cpp_compiler}" STREQUAL "x") + string(APPEND PROFILE "tools.build:compiler_executables={${_conan_c_compiler}${_conan_cpp_compiler}}\n") + endif() + unset(_conan_c_compiler) + unset(_conan_cpp_compiler) +endmacro() + + +function(detect_host_profile output_file) + detect_os(MYOS MYOS_API_LEVEL MYOS_SDK MYOS_SUBSYSTEM MYOS_VERSION) + detect_arch(MYARCH) + detect_compiler(MYCOMPILER MYCOMPILER_VERSION MYCOMPILER_RUNTIME MYCOMPILER_RUNTIME_TYPE) + detect_cxx_standard(MYCXX_STANDARD) + detect_lib_cxx(MYLIB_CXX) + detect_build_type(MYBUILD_TYPE) + + set(PROFILE "") + string(APPEND PROFILE "[settings]\n") + if(MYARCH) + string(APPEND PROFILE arch=${MYARCH} "\n") + endif() + if(MYOS) + string(APPEND PROFILE os=${MYOS} "\n") + endif() + if(MYOS_API_LEVEL) + string(APPEND PROFILE os.api_level=${MYOS_API_LEVEL} "\n") + endif() + if(MYOS_VERSION) + string(APPEND PROFILE os.version=${MYOS_VERSION} "\n") + endif() + if(MYOS_SDK) + string(APPEND PROFILE os.sdk=${MYOS_SDK} "\n") + endif() + if(MYOS_SUBSYSTEM) + string(APPEND PROFILE os.subsystem=${MYOS_SUBSYSTEM} "\n") + endif() + if(MYCOMPILER) + string(APPEND PROFILE compiler=${MYCOMPILER} "\n") + endif() + if(MYCOMPILER_VERSION) + string(APPEND PROFILE compiler.version=${MYCOMPILER_VERSION} "\n") + endif() + if(MYCOMPILER_RUNTIME) + string(APPEND PROFILE compiler.runtime=${MYCOMPILER_RUNTIME} "\n") + endif() + if(MYCOMPILER_RUNTIME_TYPE) + string(APPEND PROFILE compiler.runtime_type=${MYCOMPILER_RUNTIME_TYPE} "\n") + endif() + if(MYCXX_STANDARD) + string(APPEND PROFILE compiler.cppstd=${MYCXX_STANDARD} "\n") + endif() + if(MYLIB_CXX) + string(APPEND PROFILE compiler.libcxx=${MYLIB_CXX} "\n") + endif() + if(MYBUILD_TYPE) + string(APPEND PROFILE "build_type=${MYBUILD_TYPE}\n") + endif() + + if(NOT DEFINED output_file) + set(_FN "${CMAKE_BINARY_DIR}/profile") + else() + set(_FN ${output_file}) + endif() + + string(APPEND PROFILE "[conf]\n") + string(APPEND PROFILE "tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\n") + + # propagate compilers via profile + append_compiler_executables_configuration() + + if(MYOS STREQUAL "Android") + string(APPEND PROFILE "tools.android:ndk_path=${CMAKE_ANDROID_NDK}\n") + endif() + + message(STATUS "CMake-Conan: Creating profile ${_FN}") + file(WRITE ${_FN} ${PROFILE}) + message(STATUS "CMake-Conan: Profile: \n${PROFILE}") +endfunction() + + +function(conan_profile_detect_default) + message(STATUS "CMake-Conan: Checking if a default profile exists") + execute_process(COMMAND ${CONAN_COMMAND} profile path default + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + ECHO_OUTPUT_VARIABLE + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(NOT ${return_code} EQUAL "0") + message(STATUS "CMake-Conan: The default profile doesn't exist, detecting it.") + execute_process(COMMAND ${CONAN_COMMAND} profile detect + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + ECHO_OUTPUT_VARIABLE + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() +endfunction() + + +function(conan_install) + cmake_parse_arguments(ARGS CONAN_ARGS ${ARGN}) + set(CONAN_OUTPUT_FOLDER ${CMAKE_BINARY_DIR}/conan) + # Invoke "conan install" with the provided arguments + set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER}) + message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}") + + + # In case there was not a valid cmake executable in the PATH, we inject the + # same we used to invoke the provider to the PATH + if(DEFINED PATH_TO_CMAKE_BIN) + set(_OLD_PATH $ENV{PATH}) + set(ENV{PATH} "$ENV{PATH}:${PATH_TO_CMAKE_BIN}") + endif() + + execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN} --format=json + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + if(DEFINED PATH_TO_CMAKE_BIN) + set(ENV{PATH} "${_OLD_PATH}") + endif() + + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan install failed='${return_code}'") + else() + # the files are generated in a folder that depends on the layout used, if + # one is specified, but we don't know a priori where this is. + # TODO: this can be made more robust if Conan can provide this in the json output + string(JSON CONAN_GENERATORS_FOLDER GET ${conan_stdout} graph nodes 0 generators_folder) + cmake_path(CONVERT ${CONAN_GENERATORS_FOLDER} TO_CMAKE_PATH_LIST CONAN_GENERATORS_FOLDER) + # message("conan stdout: ${conan_stdout}") + message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${CONAN_GENERATORS_FOLDER}") + set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${CONAN_GENERATORS_FOLDER}") + # reconfigure on conanfile changes + string(JSON CONANFILE GET ${conan_stdout} graph nodes 0 label) + message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${CONANFILE}") + set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${CONANFILE}") + # success + set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) + endif() +endfunction() + + +function(conan_get_version conan_command conan_current_version) + execute_process( + COMMAND ${conan_command} --version + OUTPUT_VARIABLE conan_output + RESULT_VARIABLE conan_result + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(conan_result) + message(FATAL_ERROR "CMake-Conan: Error when trying to run Conan") + endif() + + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) + set(${conan_current_version} ${conan_version} PARENT_SCOPE) +endfunction() + + +function(conan_version_check) + set(options ) + set(oneValueArgs MINIMUM CURRENT) + set(multiValueArgs ) + cmake_parse_arguments(CONAN_VERSION_CHECK + "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT CONAN_VERSION_CHECK_MINIMUM) + message(FATAL_ERROR "CMake-Conan: Required parameter MINIMUM not set!") + endif() + if(NOT CONAN_VERSION_CHECK_CURRENT) + message(FATAL_ERROR "CMake-Conan: Required parameter CURRENT not set!") + endif() + + if(CONAN_VERSION_CHECK_CURRENT VERSION_LESS CONAN_VERSION_CHECK_MINIMUM) + message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later") + endif() +endfunction() + + +macro(construct_profile_argument argument_variable profile_list) + set(${argument_variable} "") + if("${profile_list}" STREQUAL "CONAN_HOST_PROFILE") + set(_arg_flag "--profile:host=") + elseif("${profile_list}" STREQUAL "CONAN_BUILD_PROFILE") + set(_arg_flag "--profile:build=") + endif() + + set(_profile_list "${${profile_list}}") + list(TRANSFORM _profile_list REPLACE "auto-cmake" "${CMAKE_BINARY_DIR}/conan_host_profile") + list(TRANSFORM _profile_list PREPEND ${_arg_flag}) + set(${argument_variable} ${_profile_list}) + + unset(_arg_flag) + unset(_profile_list) +endmacro() + + +macro(conan_provide_dependency method package_name) + set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE) + get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS) + if(NOT _conan_install_success) + find_program(CONAN_COMMAND "conan" REQUIRED) + conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) + conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}) + message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan") + if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE) + conan_profile_detect_default() + endif() + if("auto-cmake" IN_LIST CONAN_HOST_PROFILE) + detect_host_profile(${CMAKE_BINARY_DIR}/conan_host_profile) + endif() + construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE) + construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE) + if(EXISTS "${CMAKE_SOURCE_DIR}/conanfile.py") + file(READ "${CMAKE_SOURCE_DIR}/conanfile.py" outfile) + if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") + message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile") + endif() + set(generator "") + elseif (EXISTS "${CMAKE_SOURCE_DIR}/conanfile.txt") + file(READ "${CMAKE_SOURCE_DIR}/conanfile.txt" outfile) + if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") + message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile. " + "Please define the generator as it will be mandatory in the future") + endif() + set(generator "-g;CMakeDeps") + endif() + get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(NOT _multiconfig_generator) + message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") + conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator}) + else() + message(STATUS "CMake-Conan: Installing both Debug and Release") + conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator}) + endif() + unset(_host_profile_flags) + unset(_build_profile_flags) + unset(_multiconfig_generator) + unset(_conan_install_success) + else() + message(STATUS "CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran") + unset(_conan_install_success) + endif() + + get_property(_conan_generators_folder GLOBAL PROPERTY CONAN_GENERATORS_FOLDER) + + # Ensure that we consider Conan-provided packages ahead of any other, + # irrespective of other settings that modify the search order or search paths + # This follows the guidelines from the find_package documentation + # (https://cmake.org/cmake/help/latest/command/find_package.html): + # find_package ( PATHS paths... NO_DEFAULT_PATH) + # find_package () + + # Filter out `REQUIRED` from the argument list, as the first call may fail + set(_find_args_${package_name} "${ARGN}") + list(REMOVE_ITEM _find_args_${package_name} "REQUIRED") + if(NOT "MODULE" IN_LIST _find_args_${package_name}) + find_package(${package_name} ${_find_args_${package_name}} BYPASS_PROVIDER PATHS "${_conan_generators_folder}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) + unset(_find_args_${package_name}) + endif() + + # Invoke find_package a second time - if the first call succeeded, + # this will simply reuse the result. If not, fall back to CMake default search + # behaviour, also allowing modules to be searched. + if(NOT ${package_name}_FOUND) + list(FIND CMAKE_MODULE_PATH "${_conan_generators_folder}" _index) + if(_index EQUAL -1) + list(PREPEND CMAKE_MODULE_PATH "${_conan_generators_folder}") + endif() + unset(_index) + find_package(${package_name} ${ARGN} BYPASS_PROVIDER) + list(REMOVE_ITEM CMAKE_MODULE_PATH "${_conan_generators_folder}") + endif() +endmacro() + + +cmake_language( + SET_DEPENDENCY_PROVIDER conan_provide_dependency + SUPPORTED_METHODS FIND_PACKAGE +) + + +macro(conan_provide_dependency_check) + set(_CONAN_PROVIDE_DEPENDENCY_INVOKED FALSE) + get_property(_CONAN_PROVIDE_DEPENDENCY_INVOKED GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED) + if(NOT _CONAN_PROVIDE_DEPENDENCY_INVOKED) + message(WARNING "Conan is correctly configured as dependency provider, " + "but Conan has not been invoked. Please add at least one " + "call to `find_package()`.") + if(DEFINED CONAN_COMMAND) + # supress warning in case `CONAN_COMMAND` was specified but unused. + set(_CONAN_COMMAND ${CONAN_COMMAND}) + unset(_CONAN_COMMAND) + endif() + endif() + unset(_CONAN_PROVIDE_DEPENDENCY_INVOKED) +endmacro() + + +# Add a deferred call at the end of processing the top-level directory +# to check if the dependency provider was invoked at all. +cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check) + +# Configurable variables for Conan profiles +set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") +set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") +set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install") + +find_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH) +if(NOT _cmake_program) + get_filename_component(PATH_TO_CMAKE_BIN "${CMAKE_COMMAND}" DIRECTORY) + set(PATH_TO_CMAKE_BIN "${PATH_TO_CMAKE_BIN}" CACHE INTERNAL "Path where the CMake executable is") +endif() + diff --git a/pyproject.toml b/pyproject.toml index 3753f7a..4f7ac70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,30 +4,56 @@ [build-system] requires = [ - "setuptools>=42", - "setuptools_scm[toml]>=6.2", - "wheel", - "ninja", - "cmake>=3.25", - "conan>=2" + "conan>=2.0.5", + "scikit-build-core>=0.8", ] -build-backend = "setuptools.build_meta" -[tool.setuptools_scm] -write_to = "src/_version.py" +build-backend = "scikit_build_core.build" -[tool.mypy] -files = "setup.py" -python_version = "3.7" -strict = true -show_error_codes = true -enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] -warn_unreachable = true +[project] +name = "hictkpy" +dynamic = ["version"] +description = "Blazing fast toolkit to work with .hic and .cool files" +readme = "README.md" +authors = [ + {name = "Roberto Rossini", email = "roberros@uio.no"} +] +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering :: Bio-Informatics", + "License :: OSI Approved :: MIT License" +] -[[tool.mypy.overrides]] -module = ["ninja"] -ignore_missing_imports = true +dependencies = [ + "numpy", + "pandas!=2.2.0", + "scipy", +] +optional-dependencies.test = [ + "pytest>=6.0" +] + +[tool.scikit-build] +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +sdist.include = ["src/_version.py"] +wheel.expand-macos-universal-tags = true +cmake.build-type = "Release" + +[tool.scikit-build.cmake.define] +CMAKE_OSX_DEPLOYMENT_TARGET = "10.15" +CMAKE_PROJECT_TOP_LEVEL_INCLUDES = "cmake/conan_provider.cmake" +HICTK_ENABLE_TESTING = "OFF" +HICTK_BUILD_EXAMPLES = "OFF" +HICTK_BUILD_BENCHMARKS = "OFF" +HICTK_BUILD_TOOLS = "OFF" +HICTK_ENABLE_GIT_VERSION_TRACKING = "OFF" +BUILD_SHARED_LIBS = "OFF" +CONAN_INSTALL_ARGS = "--settings=compiler.cppstd=17;--build=missing;--update;--options=*/*:shared=False" + +[tool.setuptools_scm] +write_to = "src/_version.py" [tool.pytest.ini_options] minversion = "7.0" @@ -40,26 +66,16 @@ filterwarnings = [ ] [tool.cibuildwheel] -skip = ["*musllinux*"] -test-command = "pytest {project}/test" +skip = ["*musllinux*", "cp38-macosx_arm64"] +test-command = "python -m pytest {project}/test" test-extras = ["test"] -test-skip = ["*universal2:arm64", "pp*"] - -# Setuptools bug causes collision between pypy and cpython artifacts -before-build = [ - "rm -rf '{project}/build'", -] +test-skip = ["*universal2", "pp*"] -[tool.cibuildwheel.macos] # Setuptools bug causes collision between pypy and cpython artifacts before-build = [ "rm -rf '{project}/build'", - 'pip3 install "conan>=2"', - "sudo conan profile detect --force", - "cd '{project}/devel/cibw/macos' && sudo ./cibw_setup_deps.sh" ] -environment = { HICTKPY_SETUP_SKIP_CONAN = '1', CMAKE_ARGS = '-DCMAKE_PREFIX_PATH=/usr/local/share', MACOSX_DEPLOYMENT_TARGET = '10.15' } - +environment = { PIP_VERBOSE=1 } [tool.ruff] extend-select = [ @@ -73,11 +89,11 @@ extend-select = [ extend-ignore = [ "E501", # Line too long ] -target-version = "py37" +target-version = "py38" [tool.black] line-length = 120 -target-version = ["py37"] +target-version = ["py38"] [tool.isort] profile = "black" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index cf08eb8..0000000 --- a/setup.cfg +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (C) 2023 Roberto Rossini -# -# SPDX-License-Identifier: MIT - -[metadata] -name = hictkpy -version = attr: hictkpy.__version__ -author = Roberto Rossini -author_email = roberros@uio.no -description = Blazing fast toolkit to work with .hic and .cool files -long_description = file: README.md, LICENSE -long_description_content_type = text/markdown -license = MIT -classifiers = - Programming Language :: Python :: 3 - Topic :: Scientific/Engineering :: Bio-Informatics - License :: OSI Approved :: MIT License - -[options] -zip_safe = False -python_requires = >=3.6 -install_requires = - msvc-runtime; os_name=="nt" - numpy - pandas!=2.2.0 - scipy - importlib-metadata; python_version<"3.8" - -[options.extras_require] -test = pytest>=6.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index a55110b..0000000 --- a/setup.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2023 Roberto Rossini -# -# SPDX-License-Identifier: MIT - -import os -import re -import subprocess -import sys -import textwrap -from pathlib import Path - -from setuptools import Extension, setup -from setuptools.command.build_ext import build_ext - -# Convert distutils Windows platform specifiers to CMake -A arguments -PLAT_TO_CMAKE = { - "win32": "Win32", - "win-amd64": "x64", - "win-arm32": "ARM", - "win-arm64": "ARM64", -} - - -# A CMakeExtension needs a sourcedir instead of a file list. -# The name must be the _single_ output extension from the CMake build. -# If you need multiple extensions, see scikit-build. -class CMakeExtension(Extension): - def __init__(self, name: str, sourcedir: str = "") -> None: - super().__init__(name, sources=[]) - self.sourcedir = os.fspath(Path(sourcedir).resolve()) - - -class CMakeBuild(build_ext): - def build_extension(self, ext: CMakeExtension) -> None: - # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ - ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) - extdir = ext_fullpath.parent.resolve() / ext.name - extdir.mkdir(exist_ok=True, parents=True) - with open(extdir / "__init__.py", "w") as f: - symbols = ", ".join(("File", "PixelSelector", "is_cooler", "is_hic", "__hictk_version__")) - f.write( - textwrap.dedent( - f""" - from .hictkpy import {symbols} - from .hictkpy import cooler - - try: - from importlib.metadata import version - except ModuleNotFoundError: - from importlib_metadata import version - - __version__ = version("hictkpy") - """ - ) - ) - - # Using this requires trailing slash for auto-detection & inclusion of - # auxiliary "native" libs - - debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug - cfg = "Debug" if debug else "Release" - - # CMake lets you override the generator - we need to check this. - # Can be set with Conda-Build, for example. - cmake_generator = os.environ.get("CMAKE_GENERATOR", "") - - cmake_args = [ - "-DHICTK_ENABLE_TESTING=OFF", - "-DHICTK_BUILD_EXAMPLES=OFF", - "-DHICTK_BUILD_BENCHMARKS=OFF", - "-DHICTK_BUILD_TOOLS=OFF", - "-DBUILD_SHARED_LIBS=ON", - "-DHICTK_ENABLE_GIT_VERSION_TRACKING=OFF", - f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", - f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm - ] - build_args = [] - # Adding CMake arguments set as environment variable - # (needed e.g. to build for ARM OSx on conda-forge) - if "CMAKE_ARGS" in os.environ: - cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] - - if self.compiler.compiler_type != "msvc": - # Using Ninja-build since it a) is available as a wheel and b) - # multithreads automatically. MSVC would require all variables be - # exported for Ninja to pick it up, which is a little tricky to do. - # Users can override the generator with CMAKE_GENERATOR in CMake - # 3.15+. - if not cmake_generator or cmake_generator == "Ninja": - try: - import ninja - - ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" - cmake_args += [ - "-GNinja", - f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", - ] - except ImportError: - pass - - else: - # Single config generators are handled "normally" - single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) - - # CMake allows an arch-in-generator style for backward compatibility - contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) - - # Specify the arch if using MSVC generator, but only if it doesn't - # contain a backward-compatibility arch spec already in the - # generator name. - if not single_config and not contains_arch: - cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] - - # Multi-config generators have a different way to specify configs - if not single_config: - cmake_args += [f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"] - build_args += ["--config", cfg] - - if sys.platform.startswith("darwin"): - # Cross-compile support for macOS - respect ARCHFLAGS if set - archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) - if archs: - cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] - - # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level - # across all generators. - if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: - # self.parallel is a Python 3 only way to set parallel jobs by hand - # using -j in the build_ext call, not supported by pip or PyPA-build. - if hasattr(self, "parallel") and self.parallel: - # CMake 3.12+ only. - build_args += [f"-j{self.parallel}"] - - build_temp = Path(self.build_temp) / ext.name - if not build_temp.exists(): - build_temp.mkdir(parents=True) - - if "HICTKPY_SETUP_SKIP_CONAN" not in os.environ: - # profile detect fails if profile already exists - subprocess.run(["conan", "profile", "detect"], check=False) - subprocess.run(["conan", "profile", "detect", "--name", self.plat_name], check=False) - subprocess.run( - [ - "conan", - "install", - ext.sourcedir, - f"-pr:b=default", - f"-pr:h={self.plat_name}", - "-s", - f"build_type={cfg}", - "-s", - "compiler.cppstd=17", - f"--output-folder={build_temp.absolute()}", - "-o", - "*/*:shared=True", - "--build=missing", - ], - check=True, - ) - cmake_args += [f"-DCMAKE_PREFIX_PATH={build_temp.absolute()}"] - - subprocess.run(["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True) - subprocess.run(["cmake", "--build", ".", *build_args], cwd=build_temp, check=True) - - -# The information here can also be placed in setup.cfg - better separation of -# logic and declaration, and simpler if you include description/version in a file. -setup( - ext_modules=[CMakeExtension("hictkpy")], - cmdclass={"build_ext": CMakeBuild}, -) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 77dce09..56071f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,16 +3,15 @@ # SPDX-License-Identifier: MIT find_package( - Python 3.6 + Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED) -# For some reason linking to std::filesystem breaks cibw builds for Apple Silicon find_package(Filesystem REQUIRED) - +set(PYBIND11_NEWPYTHON ON) find_package(pybind11 CONFIG REQUIRED) pybind11_add_module( - hictkpy + _hictkpy MODULE hictkpy.cpp hictkpy_file.cpp @@ -20,11 +19,13 @@ pybind11_add_module( hictkpy_pixel_selector.cpp hictkpy_singlecell_file.cpp) -target_include_directories(hictkpy PRIVATE include) +target_include_directories(_hictkpy PRIVATE include) target_link_libraries( - hictkpy + _hictkpy PRIVATE hictkpy_project_options hictkpy_project_warnings hictk::cooler hictk::file hictk::hic) + +install(TARGETS _hictkpy LIBRARY DESTINATION hictkpy) diff --git a/src/hictkpy.cpp b/src/hictkpy.cpp index c433f2c..86414d0 100644 --- a/src/hictkpy.cpp +++ b/src/hictkpy.cpp @@ -181,7 +181,7 @@ static void declare_singlecell_file_class(pybind11::module_ &m) { namespace py = pybind11; using namespace pybind11::literals; -PYBIND11_MODULE(hictkpy, m) { +PYBIND11_MODULE(_hictkpy, m) { [[maybe_unused]] auto np = py::module::import("numpy"); [[maybe_unused]] auto pd = py::module::import("pandas"); [[maybe_unused]] auto ss = py::module::import("scipy.sparse"); diff --git a/src/hictkpy/__init__.py b/src/hictkpy/__init__.py new file mode 100644 index 0000000..0709044 --- /dev/null +++ b/src/hictkpy/__init__.py @@ -0,0 +1,10 @@ +# Copyright (C) 2024 Roberto Rossini +# +# SPDX-License-Identifier: MIT + + +from ._hictkpy import __doc__, File, PixelSelector, is_cooler, is_hic, cooler, __hictk_version__ +from importlib.metadata import version + +__version__ = version("hictkpy") +__all__ = ["__doc__", "File", "PixelSelector", "is_cooler", "is_hic", "cooler", "__hictk_version__"] diff --git a/test/test_file_accessors.py b/test/test_file_accessors.py index 4237e47..5b50428 100644 --- a/test/test_file_accessors.py +++ b/test/test_file_accessors.py @@ -35,11 +35,11 @@ def test_attributes(self, file, resolution): assert f.attributes()["format"] == "HIC" def test_normalizations(self, file, resolution): - f = hictkpy.File(file, resolution) + f = hictkpy.File(file, resolution) - if f.is_cooler(): - assert f.avail_normalizations() == ["KR", "SCALE", "VC", "VC_SQRT", "weight"] - else: - assert f.avail_normalizations() == ["ICE"] + if f.is_cooler(): + assert f.avail_normalizations() == ["KR", "SCALE", "VC", "VC_SQRT", "weight"] + else: + assert f.avail_normalizations() == ["ICE"] - assert not f.has_normalization("foo") + assert not f.has_normalization("foo")