I have been trying to create a small C++ project using the Armadillo library as part of learning C++ and CMake - I'm new to both. I'm using a Windows computer with the MinGW compiler. After more hours than I'd care to admit, looking at a variety of answers to questions online, for example here and here, I finally got the following to work:
File / Folder structure
ProjectDIR
|
+-- CMakeLists.txt
+-- test.cxx
+-- testConfig.h.in
+-- thirdParty
|
+-- armadillo
|
+-- include
+-- lib_win64
test.cxx
#include <iostream>
#include <armadillo>
#include "testConfig.h"
int main()
{
arma::mat A = {{1.0, 2.0, 3.0},{4.0, 5.0, 6.0},{7.0, 8.0, 9.0}};
std::cout << "A:\n" << A << std::endl;
std::cout << "A.t()\n" << A.t() << std::endl;
std::cout << "A*A.t()\n" << A*A.t() << std::endl;
return 0;
}
testConfig.h.in
#define MyLatestFrustration_VERSION_MAJOR @MyLatestFrustration_VERSION_MAJOR@
#define MyLatestFrustration_VERSION_MINOR @MyLatestFrustration_VERSION_MINOR@
CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyLatestFrustration VERSION 0.1)
add_library(CompilerFlags INTERFACE)
target_compile_features(CompilerFlags INTERFACE cxx_std_11)
configure_file(testConfig.h.in testConfig.h)
add_executable(MyLatestFrustration test.cxx)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/armadillo/include)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/armadillo/lib_win64)
#add_subdirectory(custom)
target_link_libraries(MyLatestFrustration PUBLIC
CompilerFlags
${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/armadillo/lib_win64/libopenblas.lib
)
target_include_directories(MyLatestFrustration PUBLIC "${PROJECT_BINARY_DIR}")
In which the armadillo/include
folder is copied directly from the armadillo-14.0.3 download and armadillo/lib_win64
folder is copied from the examples provided with armadillo-14.0.3.
While this solution works for this basic implementation, I get the feeling that it isn't how it should be done. I confirmed my suspicions when I attempted to create and link a custom library which uses armadillo by adding a folder called custom
to ProjectDiR
containing
matrices.h
#pragma once
#include <armadillo>
arma::mat f1(arma::mat A);
matrices.cxx
#include "matrices.h"
arma::mat f1(arma::mat A)
{
arma::mat B = A*A.t();
return B;
}
CMakeLists.txt
add_library(MatrixFunctions matrices.cxx)
target_include_directories(MatrixFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(MatrixFunctions PUBLIC CompilerFlags)
as well as uncommenting add_subdirectory(custom)
and adding MatrixFunctions
into the target_link_libraries
in the top level CMakeLists.txt file, then modifying test.cxx to include matrices.h and make a call to f1
. This builds fine, but when attempting to run it produces the following error "The code execution cannot proceed because the libopenblas.dll was not found. Reinstalling the program may fix this problem."
So, I'd like to understand how to properly link to Armadillo using CMake and if it can be done while using the provided precompiled libopenblas.lib, or if I have to install BLAS and LAPACK or OPENBLAS; I have previously attempted to follow this route and ran into even more issues having clearly done something wrong.
Following the suggestions in the comments, I solved the problem by using CMake to copy the required file. In particular, I copied the libopenblas.dll to the build directory using the solution here. My CMakeLists.txt file is now
cmake_minimum_required(VERSION 3.15)
project(MyLatestFrustration VERSION 0.1)
add_library(CompilerFlags INTERFACE)
target_compile_features(CompilerFlags INTERFACE cxx_std_11)
configure_file(testConfig.h.in testConfig.h)
add_executable(MyLatestFrustration test.cxx)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/armadillo/include)
add_custom_command(TARGET MyLatestFrustration POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${PROJECT_SOURCE_DIR}/thirdParty/armadillo/lib_win64/libopenblas.dll"
$<TARGET_FILE_DIR:MyLatestFrustration>)
target_link_libraries(MyLatestFrustration PUBLIC
CompilerFlags
${CMAKE_CURRENT_SOURCE_DIR}/thirdParty/armadillo/lib_win64/libopenblas.lib
)
target_include_directories(MyLatestFrustration PUBLIC "${PROJECT_BINARY_DIR}")
in which the custom command copies the .dll file post build.
This solution doesn't feel ideal, but hopefully it will be useful to anyone facing a similar problem.
EDIT:
Following the example described in this youtube video I've implemented another solution using fetchContent which seems preferable to me.
include(FetchContent)
set(OPENBLAS_VERSION 0.3.30)
FetchContent_Declare(
openblas
URL https://github.com/OpenMathLib/OpenBLAS/releases/download//v${OPENBLAS_VERSION}/OpenBlas-${OPENBLAS_VERSION}.tar.gz
FIND_PACKAGE_ARGS ${OPENBLAS_VERSION}
)
FetchContent_MakeAvailable(openblas)
then include openblas
in the target_link_libraries
.
This will check if the specified openblas version is already part of the project, and if not download it from the specified location and compile it as part of the project. It does mean that the first time it's compiled it takes a long time as the whole of openblas is compiled.