Repository: dftd4/dftd4 (language: python)

Issue:
Allow linking against custom linear algebra backend
Fixes OpenBLAS finder and allows `-Dlapack=custom -Dcustom_libraries=blis,lapack`.


Discussion / hints:

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index be369c24..0d327086 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,10 +26,6 @@ project(
 # Follow GNU conventions for installing directories
 include(GNUInstallDirs)
 
-# Collect subprojects
-set(lib-deps)
-add_subdirectory("subprojects" EXCLUDE_FROM_ALL)
-
 # General configuration information
 add_subdirectory("config")
 
@@ -38,14 +34,30 @@ if(NOT TARGET "OpenMP::OpenMP_Fortran" AND WITH_OpenMP)
 endif()
 
 if(NOT TARGET "BLAS::BLAS")
-  find_package("BLAS" REQUIRED)
+  find_package("custom-blas" REQUIRED)
+endif()
+
+# Collect subprojects
+if(NOT TARGET "mctc-lib::mctc-lib")
+  find_package("mctc-lib")
+endif()
+if(NOT TARGET "mstore::mstore")
+  find_package("mstore")
 endif()
+if(NOT TARGET "multicharge::multicharge")
+  find_package("multicharge")
+endif()
+set(
+  lib-deps
+  "mctc-lib::mctc-lib"
+  "multicharge::multicharge"
+)
 
 # Collect source of the project
 set(srcs)
 add_subdirectory("src")
 
-# multicharge library target
+# DFT-D4 library target
 add_library(
   "${PROJECT_NAME}-lib"
   "${srcs}"
@@ -68,6 +80,7 @@ target_link_libraries(
 target_include_directories(
   "${PROJECT_NAME}-lib"
   PUBLIC
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
   $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
   $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
   $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${module-dir}>
diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt
index 0c4789d7..4418b383 100644
--- a/config/CMakeLists.txt
+++ b/config/CMakeLists.txt
@@ -19,6 +19,12 @@ option(BUILD_SHARED_LIBS "Whether the libraries built should be shared" FALSE)
 option(WITH_API "Enable support for C API via iso_c_binding module" TRUE)
 option(WITH_OpenMP "Enable support for shared memory parallelisation with OpenMP" TRUE)
 option(WITH_API_V2 "Enable the compatibility layer for the dftd4 2.5.x API" FALSE)
+if(NOT DEFINED "${PROJECT_NAME}-dependeny-method")
+  set(
+    "${PROJECT_NAME}-dependency-method"
+    "subproject" "cmake" "pkgconf" "fetch"
+  )
+endif()
 
 set(
   module-dir
@@ -39,6 +45,14 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
   )
 endif()
 
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE)
+install(
+  DIRECTORY
+  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/"
+  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
+)
+
 include(CMakePackageConfigHelpers)
 configure_package_config_file(
   "${CMAKE_CURRENT_SOURCE_DIR}/template.cmake"
diff --git a/config/cmake/Findcustom-blas.cmake b/config/cmake/Findcustom-blas.cmake
new file mode 100644
index 00000000..781de208
--- /dev/null
+++ b/config/cmake/Findcustom-blas.cmake
@@ -0,0 +1,23 @@
+# This file is part of dftd4.
+# SPDX-Identifier: LGPL-3.0-or-later
+#
+# dftd4 is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dftd4 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with dftd4.  If not, see <https://www.gnu.org/licenses/>.
+
+if(NOT BLAS_FOUND)
+  find_package("BLAS")
+  if(NOT TARGET "BLAS::BLAS")
+    add_library("BLAS::BLAS" INTERFACE IMPORTED)
+    target_link_libraries("BLAS::BLAS" INTERFACE "${BLAS_LIBRARIES}")
+  endif()
+endif()
diff --git a/config/cmake/Findmctc-lib.cmake b/config/cmake/Findmctc-lib.cmake
new file mode 100644
index 00000000..2677b15c
--- /dev/null
+++ b/config/cmake/Findmctc-lib.cmake
@@ -0,0 +1,40 @@
+# This file is part of dftd4.
+# SPDX-Identifier: LGPL-3.0-or-later
+#
+# dftd4 is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dftd4 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with dftd4.  If not, see <https://www.gnu.org/licenses/>.
+
+set(_lib "mctc-lib")
+set(_pkg "MCTCLIB")
+set(_url "https://github.com/grimme-lab/mctc-lib")
+
+if(NOT DEFINED "${_pkg}_FIND_METHOD")
+  if(DEFINED "${PROJECT_NAME}-dependency-method")
+    set("${_pkg}_FIND_METHOD" "${${PROJECT_NAME}-dependency-method}")
+  else()
+    set("${_pkg}_FIND_METHOD" "cmake" "pkgconf" "subproject" "fetch")
+  endif()
+  set("_${_pkg}_FIND_METHOD")
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/dftd4-utils.cmake")
+
+dftd4_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}")
+
+if(DEFINED "_${_pkg}_FIND_METHOD")
+  unset("${_pkg}_FIND_METHOD")
+  unset("_${_pkg}_FIND_METHOD")
+endif()
+unset(_lib)
+unset(_pkg)
+unset(_url)
diff --git a/config/cmake/Findmstore.cmake b/config/cmake/Findmstore.cmake
new file mode 100644
index 00000000..c20a256c
--- /dev/null
+++ b/config/cmake/Findmstore.cmake
@@ -0,0 +1,40 @@
+# This file is part of dftd4.
+# SPDX-Identifier: LGPL-3.0-or-later
+#
+# dftd4 is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dftd4 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with dftd4.  If not, see <https://www.gnu.org/licenses/>.
+
+set(_lib "mstore")
+set(_pkg "MSTORE")
+set(_url "https://github.com/grimme-lab/mstore")
+
+if(NOT DEFINED "${_pkg}_FIND_METHOD")
+  if(DEFINED "${PROJECT_NAME}-dependency-method")
+    set("${_pkg}_FIND_METHOD" "${${PROJECT_NAME}-dependency-method}")
+  else()
+    set("${_pkg}_FIND_METHOD" "cmake" "pkgconf" "subproject" "fetch")
+  endif()
+  set("_${_pkg}_FIND_METHOD")
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/dftd4-utils.cmake")
+
+dftd4_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}")
+
+if(DEFINED "_${_pkg}_FIND_METHOD")
+  unset("${_pkg}_FIND_METHOD")
+  unset("_${_pkg}_FIND_METHOD")
+endif()
+unset(_lib)
+unset(_pkg)
+unset(_url)
diff --git a/config/cmake/Findmulticharge.cmake b/config/cmake/Findmulticharge.cmake
new file mode 100644
index 00000000..f9c5a139
--- /dev/null
+++ b/config/cmake/Findmulticharge.cmake
@@ -0,0 +1,40 @@
+# This file is part of dftd4.
+# SPDX-Identifier: LGPL-3.0-or-later
+#
+# dftd4 is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dftd4 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with dftd4.  If not, see <https://www.gnu.org/licenses/>.
+
+set(_lib "multicharge")
+set(_pkg "MULTICHARGE")
+set(_url "https://github.com/grimme-lab/multicharge")
+
+if(NOT DEFINED "${_pkg}_FIND_METHOD")
+  if(DEFINED "${PROJECT_NAME}-dependency-method")
+    set("${_pkg}_FIND_METHOD" "${${PROJECT_NAME}-dependency-method}")
+  else()
+    set("${_pkg}_FIND_METHOD" "cmake" "pkgconf" "subproject" "fetch")
+  endif()
+  set("_${_pkg}_FIND_METHOD")
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/dftd4-utils.cmake")
+
+dftd4_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}")
+
+if(DEFINED "_${_pkg}_FIND_METHOD")
+  unset("${_pkg}_FIND_METHOD")
+  unset("_${_pkg}_FIND_METHOD")
+endif()
+unset(_lib)
+unset(_pkg)
+unset(_url)
diff --git a/config/cmake/dftd4-utils.cmake b/config/cmake/dftd4-utils.cmake
new file mode 100644
index 00000000..18c7cc7e
--- /dev/null
+++ b/config/cmake/dftd4-utils.cmake
@@ -0,0 +1,116 @@
+# This file is part of dftd4.
+# SPDX-Identifier: LGPL-3.0-or-later
+#
+# dftd4 is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# dftd4 is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with dftd4.  If not, see <https://www.gnu.org/licenses/>.
+
+# Handling of subproject dependencies
+macro(
+  "dftd4_find_package"
+  package
+  methods
+  url
+)
+  string(TOLOWER "${package}" _pkg_lc)
+  string(TOUPPER "${package}" _pkg_uc)
+
+  foreach(method in ITEMS ${methods})
+
+    if(TARGET "${package}::${package}")
+      break()
+    endif()
+
+    if("${method}" STREQUAL "cmake")
+      message(STATUS "${package}: Find installed package")
+      find_package("${package}" CONFIG)
+      if("${package}_FOUND")
+        message(STATUS "${package}: Found installed package")
+        break()
+      endif()
+    endif()
+
+    if("${method}" STREQUAL "pkgconf")
+      find_package(PkgConfig QUIET)
+      pkg_check_modules("${_pkg_uc}" QUIET "${package}")
+      if("${_pkg_uc}_FOUND")
+        message(STATUS "Found ${package} via pkg-config")
+
+        add_library("${package}::${package}" INTERFACE IMPORTED)
+        target_link_libraries(
+          "${package}::${package}"
+          INTERFACE
+          "${${_pkg_uc}_LINK_LIBRARIES}"
+        )
+        target_include_directories(
+          "${package}::${package}"
+          INTERFACE
+          "${${_pkg_uc}_INCLUDE_DIRS}"
+        )
+        break()
+      endif()
+    endif()
+
+    if("${method}" STREQUAL "subproject")
+      set("${_pkg_uc}_SOURCE_DIR" "${PROJECT_SOURCE_DIR}/subprojects/${package}")
+      set("${_pkg_uc}_BINARY_DIR" "${PROJECT_BINARY_DIR}/subprojects/${package}")
+      if(EXISTS "${${_pkg_uc}_SOURCE_DIR}/CMakeLists.txt")
+        message(STATUS "Include ${package} from subprojects")
+        add_subdirectory(
+          "${${_pkg_uc}_SOURCE_DIR}"
+          "${${_pkg_uc}_BINARY_DIR}"
+        )
+
+        add_library("${package}::${package}" INTERFACE IMPORTED)
+        target_link_libraries("${package}::${package}" INTERFACE "${package}")
+
+        # We need the module directory in the subproject before we finish the configure stage
+        if(NOT EXISTS "${${_pkg_uc}_BINARY_DIR}/include")
+          make_directory("${${_pkg_uc}_BINARY_DIR}/include")
+        endif()
+
+        break()
+      endif()
+    endif()
+
+    if("${method}" STREQUAL "fetch")
+      message(STATUS "Retrieving ${package} from ${url}")
+      include(FetchContent)
+      FetchContent_Declare(
+        "${_pkg_lc}"
+        GIT_REPOSITORY "${url}"
+        GIT_TAG "HEAD"
+      )
+      FetchContent_MakeAvailable("${_pkg_lc}")
+
+      add_library("${package}::${package}" INTERFACE IMPORTED)
+      target_link_libraries("${package}::${package}" INTERFACE "${package}")
+
+      # We need the module directory in the subproject before we finish the configure stage
+      FetchContent_GetProperties("${_pkg_lc}" BINARY_DIR "${_pkg_uc}_BINARY_DIR")
+      if(NOT EXISTS "${${_pkg_uc}_BINARY_DIR}/include")
+        make_directory("${${_pkg_uc}_BINARY_DIR}/include")
+      endif()
+
+      break()
+    endif()
+
+  endforeach()
+
+  unset(_pkg_lc)
+  unset(_pkg_uc)
+
+  if(NOT TARGET "${package}::${package}")
+    message(FATAL_ERROR "Could not find dependency ${package}")
+  endif()
+
+endmacro()
diff --git a/config/meson.build b/config/meson.build
index 8506f69e..047f6ed3 100644
--- a/config/meson.build
+++ b/config/meson.build
@@ -103,39 +103,43 @@ else
 endif
 
 # Create the tool chain library as subproject
-mctc_prj = subproject(
-  'mctc-lib',
-  version: '>=0.1',
-  default_options: [
-    'default_library=static',
-  ],
-)
-mctc_dep = mctc_prj.get_variable('mctc_dep')
-lib_deps += mctc_dep
-inc_dirs += mctc_prj.get_variable('mctc_inc')
-
-if install
-  install_data(
-    mctc_prj.get_variable('mctc_lic'),
-    install_dir: get_option('datadir')/'licenses'/meson.project_name()/'mctc-lib'
+mctc_dep = dependency('mctc-lib', required: false)
+if not mctc_dep.found()
+  mctc_prj = subproject(
+    'mctc-lib',
+    version: '>=0.2',
+    default_options: [
+      'default_library=static',
+    ],
   )
+  mctc_dep = mctc_prj.get_variable('mctc_dep')
+
+  if install
+    install_data(
+      mctc_prj.get_variable('mctc_lic'),
+      install_dir: get_option('datadir')/'licenses'/meson.project_name()/'mctc-lib'
+    )
+  endif
 endif
+lib_deps += mctc_dep
 
 # Create the tool chain library as subproject
-multicharge_prj = subproject(
-  'multicharge',
-  version: '>=0.1',
-  default_options: [
-    'default_library=static',
-  ],
-)
-multicharge_dep = multicharge_prj.get_variable('multicharge_dep')
-lib_deps += multicharge_dep
-inc_dirs += multicharge_prj.get_variable('multicharge_inc')
-
-if install
-  install_data(
-    multicharge_prj.get_variable('multicharge_lic'),
-    install_dir: get_option('datadir')/'licenses'/meson.project_name()/'multicharge'
+multicharge_dep = dependency('multicharge', required: false)
+if not multicharge_dep.found()
+  multicharge_prj = subproject(
+    'multicharge',
+    version: '>=0.1',
+    default_options: [
+      'default_library=static',
+    ],
   )
+  multicharge_dep = multicharge_prj.get_variable('multicharge_dep')
+
+  if install
+    install_data(
+      multicharge_prj.get_variable('multicharge_lic'),
+      install_dir: get_option('datadir')/'licenses'/meson.project_name()/'multicharge'
+    )
+  endif
 endif
+lib_deps += multicharge_dep
diff --git a/config/template.cmake b/config/template.cmake
index 40eff55e..3df26164 100644
--- a/config/template.cmake
+++ b/config/template.cmake
@@ -1,7 +1,9 @@
 @PACKAGE_INIT@
 
 set("@PROJECT_NAME@_WITH_API" @WITH_API@)
+set("@PROJECT_NAME@_WITH_API_V2" @WITH_API_V2@)
 set("@PROJECT_NAME@_WITH_OpenMP" @WITH_OpenMP@)
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
 
 if(NOT TARGET "@PROJECT_NAME@::@PROJECT_NAME@")
   include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake")
@@ -15,4 +17,12 @@ if(NOT TARGET "@PROJECT_NAME@::@PROJECT_NAME@")
   if(NOT TARGET "BLAS::BLAS")
     find_dependency("BLAS")
   endif()
+
+  if(NOT TARGET "mctc-lib::mctc-lib")
+    find_dependency("mctc-lib")
+  endif()
+
+  if(NOT TARGET "multicharge::multicharge")
+    find_dependency("multicharge")
+  endif()
 endif()
diff --git a/subprojects/CMakeLists.txt b/subprojects/CMakeLists.txt
deleted file mode 100644
index 22879e10..00000000
--- a/subprojects/CMakeLists.txt
+++ /dev/null
@@ -1,78 +0,0 @@
-# This file is part of dftd4.
-# SPDX-Identifier: LGPL-3.0-or-later
-#
-# dftd4 is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# dftd4 is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with dftd4.  If not, see <https://www.gnu.org/licenses/>.
-
-# Include the mctc-lib project
-if(NOT TARGET mctc-lib)
-  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/mctc-lib/CMakeLists.txt)
-    add_subdirectory("mctc-lib")
-  else()
-    set("mctc-lib-url" "https://github.com/grimme-lab/mctc-lib")
-    message(STATUS "Retrieving mctc-lib from ${mctc-lib-url}")
-    include(FetchContent)
-    FetchContent_Declare(
-      "mctc-lib"
-      GIT_REPOSITORY "${mctc-lib-url}"
-      GIT_TAG "HEAD"
-    )
-    FetchContent_MakeAvailable("mctc-lib")
-  endif()
-endif()
-
-list(
-  APPEND lib-deps
-  "mctc-lib"
-)
-
-# Include the mstore project
-if(NOT TARGET mstore)
-  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/mstore/CMakeLists.txt)
-    add_subdirectory("mstore")
-  else()
-    set("mstore-url" "https://github.com/grimme-lab/mstore")
-    message(STATUS "Retrieving mstore from ${mstore-url}")
-    include(FetchContent)
-    FetchContent_Declare(
-      "mstore"
-      GIT_REPOSITORY "${mstore-url}"
-      GIT_TAG "HEAD"
-    )
-    FetchContent_MakeAvailable("mstore")
-  endif()
-endif()
-
-# Include the multicharge project
-if(NOT TARGET multicharge)
-  if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/multicharge/CMakeLists.txt)
-    add_subdirectory("multicharge")
-  else()
-    set("multicharge-url" "https://github.com/grimme-lab/multicharge")
-    message(STATUS "Retrieving multicharge from ${multicharge-url}")
-    include(FetchContent)
-    FetchContent_Declare(
-      "multicharge"
-      GIT_REPOSITORY "${multicharge-url}"
-      GIT_TAG "HEAD"
-    )
-    FetchContent_MakeAvailable("multicharge")
-  endif()
-endif()
-
-list(
-  APPEND lib-deps
-  "multicharge"
-)
-
-set(lib-deps "${lib-deps}" PARENT_SCOPE)


Test patch:
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 00bf41d2..db18ef71 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -41,7 +41,7 @@ target_link_libraries(
   "${PROJECT_NAME}-tester"
   PRIVATE
   "${PROJECT_NAME}-lib"
-  "mstore"
+  "mstore::mstore"
 )
 
 foreach(t IN LISTS tests)
diff --git a/test/unit/meson.build b/test/unit/meson.build
index 2a8b5615..98ea7d6d 100644
--- a/test/unit/meson.build
+++ b/test/unit/meson.build
@@ -25,7 +25,7 @@ mstore_prj = subproject(
 )
 # If we do not find mstore and are a subproject, we just skip testing
 if not mstore_prj.found()
-   subdir_done()
+  subdir_done()
 endif
 mstore_dep = mstore_prj.get_variable('mstore_dep')