diff --git a/.travis.yml b/.travis.yml index aa49fc214..323d3eba5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -160,6 +160,7 @@ install: fi' - which python2 + - cmake --version script: @@ -229,7 +230,7 @@ script: - if [[ "$cmake" == "1" ]]; then export CXX=g++-5 && export CC=gcc-5 && - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Werror" -Dbuild_tests=ON -Dbuild_examples=ON -G "CodeBlocks - Unix Makefiles" .. && + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-Werror" -Dbuild_tests=ON -Dbuild_examples=ON -Dpython-bindings=ON -G "CodeBlocks - Unix Makefiles" .. && cmake --build . -- -j2; fi - cd .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 79eb5b59b..242ca4cf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) +cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR) project(libtorrent) set (SOVERSION "10") @@ -449,6 +449,7 @@ option(libiconv "enable linking against system libiconv" OFF) option(logging "build with logging" ON) option(build_tests "build tests" OFF) option(build_examples "build examples" OFF) +option(python-bindings "build python bindings" OFF) set(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo) @@ -683,6 +684,10 @@ endforeach (s) configure_file(libtorrent-rasterbar-cmake.pc.in libtorrent-rasterbar.pc) +include(CheckCXXCompilerFlag) + +add_subdirectory(bindings) + string (COMPARE EQUAL "${CMAKE_SIZEOF_VOID_P}" "8" IS64BITS) if (IS64BITS AND RESPECTLIB64) diff --git a/appveyor.yml b/appveyor.yml index b6286c17d..0aab46bb0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -96,7 +96,8 @@ build_script: - cd %ROOT_DIRECTORY% - mkdir build && cd build - if defined cmake ( - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="/WX" -G "Visual Studio 14 2015 Win64" .. && + set "PATH=c:\Python27-x64;%PATH%" && + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="/WX /wd4244 /wd4459" -Dpython-bindings=%python% -Dboost-python-module-name="python" -Dskip-python-runtime-test=true -DPython_ADDITIONAL_VERSIONS="2.7" -G "Visual Studio 14 2015 Win64" .. && cmake --build . ) diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 000000000..5dd618f7c --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,3 @@ +if (python-bindings) + add_subdirectory(python) +endif() diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt new file mode 100644 index 000000000..2ec652c14 --- /dev/null +++ b/bindings/python/CMakeLists.txt @@ -0,0 +1,143 @@ +# To build python bindings we need a python executable and boost python module. Unfortunately, +# their names might not be interlinked and we can not implement a general solution. +# The code below assumes default boost installation, when the module for python 2 is named +# 'python' and the module for python 3 is named 'python3'. +# To customize that one can provide a name for the Boost::python module via +# 'boost-python-module-name' variable when invoking cmake. +# E.g. on Gentoo with python 3.6 and Boost::python library name 'libboost_python-3.6.so' +# the parameter would be -Dboost-python-module-name="python-3.6". + +# The extension module and the cpython executable have to use the same C runtime library. On Windows +# Python is compiled with MSVC and we will test MSVC version to make sure that it is the same for +# the given Python version and our extension module. See https://wiki.python.org/moin/WindowsCompilers +# for details. We provide a flag to skip this test for whatever reason (pass -Dskip-python-runtime-test=True) + +# Sets _ret to a list of python versions (major.minor) that use the same MSVC runtime as this build does +# assumes MSVC was detected already +# See https://en.wikipedia.org/wiki/Microsoft_Visual_C++#Internal_version_numbering +function(_get_compatible_python_versions _ret) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16) + list(APPEND _tmp 2.6 2.7 3.0 3.1 3.2) + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 17) + list(APPEND _tmp 3.3 3.4) + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 20) + list(APPEND _tmp 3.5 3.6) + endif() + set(${_ret} ${_tmp} PARENT_SCOPE) +endfunction() + + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + _get_compatible_python_versions(Python_ADDITIONAL_VERSIONS) +endif() + +find_package(PythonInterp REQUIRED) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT skip-python-runtime-test) + message(STATUS "Testing found python version. Requested: ${Python_ADDITIONAL_VERSIONS}, found: ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}") + if (NOT "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" IN_LIST Python_ADDITIONAL_VERSIONS) + message(FATAL_ERROR "Incompatible Python and C runtime: MSVC ${CMAKE_CXX_COMPILER_VERSION} and Python ${PYTHON_VERSION_STRING}") + endif() +endif() + +set(Python_ADDITIONAL_VERSIONS "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}") +find_package(PythonLibs REQUIRED) + +if (NOT boost-python-module-name) + # use active python + if (PYTHON_VERSION_STRING VERSION_GREATER_EQUAL "3") + set(_boost-python-module-name "python${PYTHON_VERSION_MAJOR}") + else() + set(_boost-python-module-name "python") # to overwrite possible value from a previous run + endif() +endif() + +set(boost-python-module-name ${_boost-python-module-name} CACHE STRING "Boost:python module name, e.g. 'pythom-3.6'") + +find_package(Boost REQUIRED COMPONENTS ${boost-python-module-name}) + +python_add_module(python-libtorrent + src/module.cpp + src/sha1_hash.cpp + src/converters.cpp + src/create_torrent.cpp + src/fingerprint.cpp + src/utility.cpp + src/session.cpp + src/entry.cpp + src/torrent_info.cpp + src/string.cpp + src/torrent_handle.cpp + src/torrent_status.cpp + src/session_settings.cpp + src/version.cpp + src/alert.cpp + src/datetime.cpp + src/peer_info.cpp + src/ip_filter.cpp + src/magnet_uri.cpp + src/error_code.cpp +) + +set_target_properties(python-libtorrent + PROPERTIES + OUTPUT_NAME libtorrent +) + +target_include_directories(python-libtorrent PRIVATE ${PYTHON_INCLUDE_DIRS}) + +target_link_libraries(python-libtorrent + PRIVATE + torrent-rasterbar + Boost::${boost-python-module-name} + # Boost::python adds that but without a path to the library. Therefore we have to either + # provide the path (but, unfortunately, FindPythonLibs.cmake does not return the library dir), + # or give the full file name here (this FindPythonLibs.cmake provides to us). + ${PYTHON_LIBRARIES} +) + +# Bindings module uses deprecated libtorrent features, thus we disable these errors +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + check_cxx_compiler_flag("-Wno-error=deprecated-declarations" _WNO_ERROR_DEPRECATED_DECLARATIONS) + if (_WNO_ERROR_DEPRECATED_DECLARATIONS) + target_compile_options(python-libtorrent PRIVATE -Wno-error=deprecated-declarations) + endif() +endif() + +execute_process(COMMAND + ${PYTHON_EXECUTABLE} -c "import distutils.sysconfig; +print(';'.join(map(str, [ + distutils.sysconfig.get_python_lib(plat_specific=True, prefix=''), + distutils.sysconfig.get_config_var('EXT_SUFFIX') +])))" + OUTPUT_VARIABLE _python_sysconfig_vars OUTPUT_STRIP_TRAILING_WHITESPACE) + +list(GET _python_sysconfig_vars 0 PYTHON_SITE_PACKAGES) +list(GET _python_sysconfig_vars 1 PYTHON_EXT_SUFFIX) + +message(STATUS "Python site packages: ${PYTHON_SITE_PACKAGES}") +# python 2 does not provide the 'EXT_SUFFIX' sysconfig variable, so we use cmake default then +if (NOT "${PYTHON_EXT_SUFFIX}" STREQUAL "None") + message(STATUS "Python extension suffix: ${PYTHON_EXT_SUFFIX}") + # we mimic the name, created by setuptools + # example: libtorrent.cpython-36m-x86_64-linux-gnu.so + set_target_properties(python-libtorrent PROPERTIES SUFFIX ${PYTHON_EXT_SUFFIX}) +endif() + +set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.cmake.in") +set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") +set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/timestamp") +set(DEPS python-libtorrent "${SETUP_PY}") + +configure_file(${SETUP_PY_IN} ${SETUP_PY} @ONLY) + +add_custom_command(OUTPUT ${OUTPUT} + COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} build -b "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} egg_info -b "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} + DEPENDS ${DEPS}) + +add_custom_target(python_bindings ALL DEPENDS ${OUTPUT}) + + +install(TARGETS python-libtorrent DESTINATION "${PYTHON_SITE_PACKAGES}") +install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libtorrent.egg-info" DESTINATION "${PYTHON_SITE_PACKAGES}") diff --git a/bindings/python/setup.py.cmake.in b/bindings/python/setup.py.cmake.in new file mode 100644 index 000000000..42c08b4f1 --- /dev/null +++ b/bindings/python/setup.py.cmake.in @@ -0,0 +1,15 @@ +from setuptools import setup +import platform + +setup( + name='libtorrent', + version='@VERSION@', + author='Arvid Norberg', + author_email='arvid@libtorrent.org', + description='Python bindings for libtorrent-rasterbar', + long_description='Python bindings for libtorrent-rasterbar', + url='http://libtorrent.org', + platforms=[platform.system() + '-' + platform.machine()], + license='BSD', + package_dir = {'': '@CMAKE_CURRENT_BINARY_DIR@'} +) diff --git a/bindings/python/src/error_code.cpp b/bindings/python/src/error_code.cpp index bd799bd59..cd17cac84 100644 --- a/bindings/python/src/error_code.cpp +++ b/bindings/python/src/error_code.cpp @@ -64,7 +64,7 @@ namespace { struct ec_pickle_suite : boost::python::pickle_suite { static boost::python::tuple - getinitargs(error_code const& ec) + getinitargs(error_code const&) { return boost::python::tuple(); } diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp index bb4b49dbe..e5b69d9e2 100644 --- a/bindings/python/src/torrent_info.cpp +++ b/bindings/python/src/torrent_info.cpp @@ -162,8 +162,8 @@ namespace { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_complete; } int get_scrape_downloaded(announce_entry const& ae) { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_downloaded; } - int next_announce_in(announce_entry const& ae) { return 0; } - int min_announce_in(announce_entry const& ae) { return 0; } + int next_announce_in(announce_entry const&) { return 0; } + int min_announce_in(announce_entry const&) { return 0; } bool get_send_stats(announce_entry const& ae) { return ae.send_stats; } std::int64_t get_size(file_entry const& fe) { return fe.size; } std::int64_t get_offset(file_entry const& fe) { return fe.offset; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 515b52e0d..ceadb472f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,13 @@ cmake_minimum_required(VERSION 2.6) +# Our tests contain printf formating checks therefore we disable GCC format string errors: +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + check_cxx_compiler_flag("-Wno-error=format-truncation" _WNO_ERROR_FORMAT_TRUNCATION) + if (_WNO_ERROR_FORMAT_TRUNCATION) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=format-truncation") + endif() +endif() + file(GLOB tests "${CMAKE_CURRENT_SOURCE_DIR}/test_*.cpp") list(REMOVE_ITEM tests "${CMAKE_CURRENT_SOURCE_DIR}/test_natpmp.cpp") # doesn't build at time of writing list(REMOVE_ITEM tests "${CMAKE_CURRENT_SOURCE_DIR}/test_utils.cpp") # helper file, not a test