diff --git a/cmake/BornAgain/multipython/MakePythonAPI.cmake b/cmake/BornAgain/multipython/MakePythonAPI.cmake new file mode 100644 index 0000000000000000000000000000000000000000..281444d1d878edd03924c999a761af72303e1eb7 --- /dev/null +++ b/cmake/BornAgain/multipython/MakePythonAPI.cmake @@ -0,0 +1,123 @@ +# make_python_api defines a shared library with a given name +# from given Python-dependent/independent object files, declares the +# required properties and makes the Python API for C++ libraries. +# NOTE: `make_python_api` is only called from MakeSharedLib module. + +include(BornAgain/multipython/MakeSwigLib) +include(BornAgain/multipython/MakePythonWheel) + +function(make_python_api name) + + cmake_parse_arguments("_" #[[ prefix ]] + "" #[[ options ]] + "LIBNAME;BASENAME;LIBDIR;DESTINATION_INCLUDE;DESTINATION_LIB;TMPDIR;IS_MAIN;PYTAG" + #[[ oneValueArgs ]] + "" #[[ multiValueArgs]] + ${ARGN}) + + if(__PYTAG) + set(pysuffix _${pytag}) # eg. '_py38' + else() + set(pysuffix "") + endif() + + set(_libsuffix ${libsuffix}) + if(WIN32) + # under Windows, .pyd files are needed as the + # Python extension; see + # <https://docs.python.org/3/faq/windows.html#is-a-pyd-file-the-same-as-a-dll> + set(_libsuffix ".pyd") + endif(WIN32) + + # correct the properties for the case of WITH_PYTHON_API = TRUE + set_target_properties(${__LIBNAME} + PROPERTIES + PREFIX "${libprefix}" SUFFIX "${_libsuffix}" + # custom properties + _FULLNAME "${libprefix}${__LIBNAME}${_libsuffix}" # eg., 'libBornAgainBase.so' + ) + + #-- make the Python API via SWIG + # NOTE: TMPDIR is only needed in make_swig_lib + make_swig_lib(${name} ${__LIBNAME} + BASENAME ${__BASENAME} + IS_MAIN ${__IS_MAIN} + LIBDIR ${__LIBDIR} + TMPDIR ${__TMPDIR} + PYTAG ${__PYTAG} + ) + + # TODO: Check this + if(__IS_MAIN AND __DESTINATION_LIB) + install(FILES ${__LIBDIR}/lib${__BASENAME}.py + DESTINATION ${__DESTINATION_LIB} COMPONENT Libraries) # required by swig + endif() + + if(WIN32) + # NOTE: Windows needs DLLs to be on the current working directory + # or in environment variable `PATH`. The following copy is needed + # for executing tests. + + get_target_property(_libname_full ${__LIBNAME} _FULLNAME) + set(_lib ${__LIBDIR}/${_libname_full}) + + if(__IS_MAIN) + # main version + set(_dst ${TEST_EXE_DIR}) + + # installation of the main version + if(__DESTINATION_LIB) + install(FILES ${__LIBDIR}/${_libname_full} + DESTINATION ${__DESTINATION_LIB} COMPONENT Libraries) + endif() + else() + # Python packages + set(_dst ${${__PYTAG}_TEST_EXE_DIR}) + endif() + + add_custom_command( + TARGET ${__LIBNAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${_lib} + ${_dst}/${_libname_full} + COMMENT "Copying '${__LIBNAME}' to '${_dst}'...") + + if((NOT __IS_MAIN) AND BORNAGAIN_PYTHON_PACKAGE_TESTS) + # copy required DLLs to test directory of the Python package (only once), + # eg. `python_packages/tests/py38`. + set(test_dlls_py test_dlls_${__PYTAG}) + if(NOT TARGET ${test_dlls_py}) + add_custom_target(${test_dlls_py} + COMMAND ${CMAKE_COMMAND} -E copy + $<TARGET_FILE:gmock> + $<TARGET_FILE:gmock_main> + $<TARGET_FILE:gtest> + $<TARGET_FILE:gtest_main> + ${_dst} + COMMAND ${CMAKE_COMMAND} -E copy + ${Python3_LIBRARY_DLL_${__PYTAG}} + ${_dst} + COMMENT "Test.${__PYTAG}: Copying required DLLs to '${_dst}'..." + DEPENDS + gmock gmock_main gtest gtest_main + ${Python3_LIBRARY_DLL_${__PYTAG}} + ) + + add_dependencies(${__LIBNAME} ${test_dlls_py}) + endif() + + endif() + + endif(WIN32) + + if(NOT __IS_MAIN) + #-- make the Python wheel + make_python_wheel(${libname} + PYTAG ${__PYTAG} + Py3_EXECUTABLE "${Python3_EXECUTABLE${pysuffix}}" + Py3_INCLUDE_DIRS "${Python3_INCLUDE_DIRS${pysuffix}}" + Py3_LIBRARY_DIRS "${Python3_LIBRARY_DIRS${pysuffix}}" + ) + endif() + +endfunction(make_python_api) diff --git a/cmake/BornAgain/multipython/MakeSharedLib.cmake b/cmake/BornAgain/multipython/MakeSharedLib.cmake index 0dfdbbfe0239ec622348f4a1e63a2832b2953ff3..8303136b2e009c20a4ded95cd561690091c441f4 100644 --- a/cmake/BornAgain/multipython/MakeSharedLib.cmake +++ b/cmake/BornAgain/multipython/MakeSharedLib.cmake @@ -1,13 +1,12 @@ -# Configure one component library -include(BornAgain/multipython/MakeSwigLib) - # make_shared_lib defines a shared library with a given name +# from given Python-dependent/independent object files. +# The Python API is produced via make_shared_lib_with_python, if required. function(make_shared_lib name) cmake_parse_arguments("_" #[[ prefix ]] "" #[[ options ]] - "LIBNAME;BASENAME;DESTINATION_INCLUDE;DESTINATION_LIB;TMPDIR;IS_MAIN;PYTAG" + "LIBNAME;BASENAME;OUTPUT_DIR;DESTINATION_INCLUDE;DESTINATION_LIB;TMPDIR;IS_MAIN;WITH_PYTHON_API;PYTAG" #[[ oneValueArgs ]] "" #[[ multiValueArgs]] ${ARGN}) @@ -16,31 +15,47 @@ function(make_shared_lib name) # according to the particular version of Python, if given. if(__PYTAG) set(libdir "${${__PYTAG}_LIBRARY_OUTPUT_DIR}") + set(pysuffix _${pytag}) # eg. '_py38' else() set(libdir "${CMAKE_BINARY_DIR}/lib") + set(pysuffix "") endif() - # descriptive message about the shared library - if(__IS_MAIN) - set(msg_main "Main ") + # if an output directory is explicitly given for the library, then + # override the default output directory. + if(__OUTPUT_DIR) + set(libdir "${__OUTPUT_DIR}") endif() + + # set the required properties if(BORNAGAIN_PYTHON) - if(__PYTAG) - set(msg_pytag " ${__PYTAG}") - endif() - set(msg_pyapi "with Python API${msg_pytag}") - endif() - message(STATUS "MakeSharedLib '${name}' (${msg_main}${msg_pyapi})\n" - " in directory '${libdir}'") + # add the required properties to the library and + # its Python-dependent objects lib - # set required properties - get_target_property(objlib ${__LIBNAME} _OBJECTLIB) - get_target_property(objlib_py ${__LIBNAME} _OBJECTLIB_PY) + get_target_property(objlib ${__LIBNAME} _OBJECTLIB) + get_target_property(objlib_py ${__LIBNAME} _OBJECTLIB_PY) - if(BORNAGAIN_PYTHON) foreach(_lib IN ITEMS ${__LIBNAME} ${objlib} ${objlib_py}) - target_compile_definitions(${_lib} PUBLIC -DBORNAGAIN_PYTHON) + target_compile_definitions(${_lib} PUBLIC BORNAGAIN_PYTHON) endforeach() + + # TODO: Add Python includes and libraries, only if needed + if(TARGET ${objlib_py}) + target_include_directories(${objlib_py} + PRIVATE + "${Python3_INCLUDE_DIRS${pysuffix}}" + "${Python3_NumPy_INCLUDE_DIRS${pysuffix}}" + ) + endif() + + target_include_directories(${__LIBNAME} + PRIVATE + "${Python3_INCLUDE_DIRS${pysuffix}}" + "${Python3_NumPy_INCLUDE_DIRS${pysuffix}}" + ) + + target_link_libraries(${__LIBNAME} + PUBLIC "${Python3_LIBRARY_RELEASE${pysuffix}}") endif() if(BORNAGAIN_MPI) @@ -49,17 +64,7 @@ function(make_shared_lib name) target_link_libraries(${__LIBNAME} PRIVATE ${MPI_LIBRARIES}) endif() - # TODO: What happens if BORNAGAIN_PYTHON is NOT set? Then library `libname` will have no sources - # solution(?): add `$<TARGET_OBJECTS:${__OBJECTSLIB}>` in place of source (New in version 3.21) - - set(_libsuffix ${libsuffix}) if(WIN32) - if (BORNAGAIN_PYTHON) - # under Windows, .pyd files are needed as the - # Python extension; see - # <https://docs.python.org/3/faq/windows.html#is-a-pyd-file-the-same-as-a-dll> - set(_libsuffix ".pyd") - endif() # for Windows: # see <https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#runtime-output-artifacts> set_target_properties(${__LIBNAME} @@ -68,12 +73,13 @@ function(make_shared_lib name) ) endif(WIN32) + # set required properties set_target_properties(${__LIBNAME} PROPERTIES - PREFIX "${libprefix}" SUFFIX "${_libsuffix}" + PREFIX "${libprefix}" SUFFIX "${libsuffix}" LIBRARY_OUTPUT_DIRECTORY ${libdir} # custom properties - _FULLNAME "${libprefix}${__LIBNAME}${_libsuffix}" # eg., 'libBornAgainBase.so' + _FULLNAME "${libprefix}${__LIBNAME}${libsuffix}" # eg., 'libBornAgainBase.so' _BASENAME ${__BASENAME} _PYTAG "${__PYTAG}" _LIBDIR "${libdir}" @@ -95,27 +101,47 @@ function(make_shared_lib name) # -- install # TODO: Check the install part vvv - if(__IS_MAIN) + if(__IS_MAIN AND __DESTINATION_LIB) # install only the main version of the library install(TARGETS ${__LIBNAME} DESTINATION ${__DESTINATION_LIB} COMPONENT Libraries) - foreach(file ${${__BASENAME}_INCLUDES}) get_filename_component(dir ${file} DIRECTORY) install(FILES ${file} DESTINATION ${__DESTINATION_INCLUDE}/${name}/${dir}) endforeach() endif() - #-- Python API via SWIG, if required - if(BORNAGAIN_PYTHON) - # NOTE: TMPDIR is needed here only - make_swig_lib(${name} ${__LIBNAME} + # descriptive message about the shared library + if(__IS_MAIN) + set(msg_main "Main ") + set(msg_install "installation folders: {\ +library => '${__DESTINATION_LIB}', \ +includes => '${__DESTINATION_INCLUDE}/${name}'}") + endif() + if(__PYTAG) + set(msg_pytag ".${__PYTAG}") + endif() + if(__WITH_PYTHON_API) + set(msg_pyapi "with Python API") + endif() + message(STATUS "MakeSharedLib '${name}' " + "(${msg_main}${msg_pyapi}${msg_pytag})\n" + " in directory '${libdir}';\n" + " ${msg_install}") + + # perform Python-related configuration, if required + if(__WITH_PYTHON_API) + include(BornAgain/multipython/MakePythonAPI) + + make_python_api(${name} + LIBNAME ${__LIBNAME} BASENAME ${__BASENAME} - IS_MAIN ${__IS_MAIN} + IS_MAIN "${__IS_MAIN}" LIBDIR ${libdir} - DESTINATION_LIB ${__DESTINATION_LIB} - TMPDIR ${__TMPDIR} - PYTAG ${__PYTAG} + DESTINATION_INCLUDE "${__DESTINATION_INCLUDE}" + DESTINATION_LIB "${__DESTINATION_LIB}" + TMPDIR "${__TMPDIR}" + PYTAG "${__PYTAG}" ) endif() diff --git a/cmake/BornAgain/multipython/MakeSwigLib.cmake b/cmake/BornAgain/multipython/MakeSwigLib.cmake index eaa35fca1625fa3a4dbe8c89b464fa1964d64cb6..53aaf7976687cd2cd55e4fc8736b8c4bf3ca5208 100644 --- a/cmake/BornAgain/multipython/MakeSwigLib.cmake +++ b/cmake/BornAgain/multipython/MakeSwigLib.cmake @@ -1,17 +1,17 @@ # Configure Python bindings for a single component library. -include(BornAgain/multipython/MakePythonWheel) include(BornAgain/multipython/ConfigureSwig) -# NOTE: `make_swig_lib` is called _only_ from function `make_shared_lib`. +# NOTE: `make_swig_lib` is called _only_ from function MakePythonAPI module. function(make_swig_lib name libname) cmake_parse_arguments("_" #[[ prefix ]] "" #[[ options ]] - "BASENAME;IS_MAIN;DESTINATION_LIB;TMPDIR;LIBDIR;PYTAG" #[[ oneValueArgs ]] + "BASENAME;IS_MAIN;TMPDIR;LIBDIR;PYTAG" #[[ oneValueArgs ]] "" #[[ multiValueArgs]] ${ARGN}) + # NOTE: TMPDIR is only needed by `configure_swig` if(CONFIGURE_BINDINGS) configure_swig(${libname} ${name} ${__BASENAME} ${__TMPDIR}) add_dependencies(${libname} SwigConfigured_${__BASENAME}) @@ -47,13 +47,6 @@ function(make_swig_lib name libname) configure_file(${SWIG_DIR}/configure_wrappers.cmake.in ${swig_conf} @ONLY) - # Python wheel - make_python_wheel(${libname} - PYTAG ${__PYTAG} - Py3_EXECUTABLE "${Python3_EXECUTABLE${pysuffix}}" - Py3_INCLUDE_DIRS "${Python3_INCLUDE_DIRS${pysuffix}}" - Py3_LIBRARY_DIRS "${Python3_LIBRARY_DIRS${pysuffix}}" - ) else() # --- Main version @@ -92,8 +85,7 @@ function(make_swig_lib name libname) ) # add the SWIG-produced wrapper as the library source - set_target_properties(${libname} PROPERTIES - SOURCES ${CPP_WRAPPER_OUT}) + target_sources(${libname} PRIVATE ${CPP_WRAPPER_OUT}) # add the required properties to the library and # its Python-dependent objects lib @@ -116,28 +108,4 @@ function(make_swig_lib name libname) target_link_libraries(${libname} PUBLIC "${Python3_LIBRARY_RELEASE${pysuffix}}") - # TODO: Check this - if(__IS_MAIN) - install(FILES ${__LIBDIR}/lib${__BASENAME}.py - DESTINATION ${__DESTINATION_LIB} COMPONENT Libraries) # required by swig - endif() - - if(WIN32) - # Python in Windows required .pyd extension for the library name - # See <https://docs.python.org/3/faq/windows.html#is-a-pyd-file-the-same-as-a-dll> - if(__IS_MAIN) - get_target_property(_libname_full ${libname} _FULLNAME) - set(_lib ${__LIBDIR}/${_libname_full}) - set(_dst ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - add_custom_command( - TARGET ${libname} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${_lib} ${_dst}/${_libname_full} - COMMENT "Copying '${libname}' to '${_dst}'..." - ) - install(FILES ${_dst}/${_libname_full} - DESTINATION ${__DESTINATION_LIB} COMPONENT Libraries) - endif() - endif(WIN32) - endfunction(make_swig_lib)