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)