Introduction
In the last week there were a few questions about how to use ArrayFire with CMake on Linux on the ArrayFire Google Groups page. Although we have some quick notes about this in the documentation I thought it would be fun to provide a fully worked example that demonstrates a cool CMake trick to reduce compilation time by a factor of three when linking against multiple backends. At the bottom of this post is a link to a GitHub repository containing a minimal CMake project setup which links a program against ArrayFire’s CPU, OpenCL, and CUDA backends.
The instructions below will work on Linux, OSX, and Windows; however, you will need to use CMake generators if you want to use XCode or Visual Studio to compile your code. For Visual Studio specifically, we suggest you follow the documentation to set up Visual Studio projects rather than using CMake generators because it is a much more straightforward process.
Prerequisites
Foremost, make sure that you have both a C++11 compatible compiler and CMake 3.0 (or later) installed on your system. Please see the documentation for specific commands for Fedora, Debian, Ubuntu, and a special requirement for the Tegra TK1. Next, ensure that you have downloaded (or compiled ArrayFire from source) and installed it to your system. Pay close attention to the installation path as this may need to be manually specified at a later stage.
Creating a project
Set up a directory as you would any other project. Let’s say we have two sources files, fooA.cpp
and fooB.cpp
which we wish to compile into an executable called fooExe
and link against ArrayFire. In the top level folder of your project create a file called CMakeLists.txt
and fill it with the following contents:
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
PROJECT(foo)
FIND_PACKAGE(ArrayFire REQUIRED)
INCLUDE_DIRECTORIES(${ArrayFire_INCLUDE_DIRS})
ADD_EXECUTABLE(fooExe fooA.cpp fooB.cpp)
TARGET_LINK_LIBRARIES(fooExe ${ArrayFire_LIBRARIES} )
The FIND_PACKAGE
command will attempt to find the ArrayFire library using ArrayFire’s pre-built ArrayFireConfigure.cmake
script located in the share/ArrayFire/cmake
sub-folder of the ArrayFire installation root. The word REQUIRED
will cause CMake to fail during the setup stage if ArrayFire is not found. The ADD_EXECUTABLE
command specifies that we wish to create an executable called fooExe
which will be composed of the fooA.cpp
and fooB.cpp
source files. Next, the TARGET_LINK_LIBRARIES
command instructs CMake to link the fooExe
program with the most powerful ArrayFire backend library (typically CUDA). If you wish to link against a different backend, the ArrayFireConfigure.cmake
script also provides the following variables for use:
ArrayFire_CPU_FOUND
– True of the ArrayFire CPU library has been found.ArrayFire_CPU_LIBRARIES
– Location of ArrayFire’s CPU library, if found.ArrayFire_CUDA_FOUND
– True of the ArrayFire CUDA library has been found.ArrayFire_CUDA_LIBRARIES
– Location of ArrayFire’s CUDA library, if found.ArrayFire_OpenCL_FOUND
– True of the ArrayFire OpenCL library has been found.ArrayFire_OpenCL_LIBRARIES
– Location of ArrayFire’s OpenCL library, if found.See the CMake documentation for inquires about the other portions of the file.
Now, to build the project we execute the following commands:
cmake .
make
The cmake
command instructs CMake to parse the current directory’s CMakeLists.txt
file and generate the necessary files for compiling the program. If the ArrayFire library is not found, you may get an error like the following:
CMake Error at src/CMakeLists.txt:5 (FIND_PACKAGE):
By not providing "FindArrayFire.cmake" in CMAKE_MODULE_PATH this project
has asked CMake to find a package configuration file provided by
"ArrayFire", but CMake did not find one.
Could not find a package configuration file provided by "ArrayFire" with
any of the following names:
ArrayFireConfig.cmake
arrayfire-config.cmake
Add the installation prefix of "ArrayFire" to CMAKE_PREFIX_PATH or set
"ArrayFire_DIR" to a directory containing one of the above files. If
"ArrayFire" provides a separate development package or SDK, be sure it has
been installed.
In this case you need to specify the full path to the folder which contains the ArrayFireConfigure.cmake
script (found in the share/ArrayFire/cmake
sub-directory of the installation folder). For example, if ArrayFire were installed to /opt
this cmake
command above would be replaced with the following:
cmake -DArrayFire_DIR=/opt/share/ArrayFire/cmake .
If all goes well, then CMake will generate the necessary files and the compilation will be successful and you will have a project running with ArrayFire in only a few lines of CMake code!
Reduce compilation time when linking against multiple backends
If you are building a project that will link against multiple ArrayFire backends, here is a pretty awesome trick to reduce compilation times significantly. We will take advantage of CMake Object Libraries to compile source code that is common to all backends into object files and then link them together with a specific ArrayFire backend. Here is a quick demonstration of this in action in a CMakeLists.txt file:
...
FILE(GLOB SOURCE "*.cpp")
ADD_LIBRARY(FOO_CORE OBJECT ${SOURCE})
ADD_EXECUTABLE(fooExe $)
TARGET_LINK_LIBRARIES(fooExe ${ArrayFire_LIBRARIES} )
The FILE
operation isn’t suggested by the CMake folks, but it makes getting all of the source files in a directory very quick. The ADD_LIBRARY
command with the optional OBJECT
argument creates a library of object files called FOO_CORE
. Lastly, the ADD_EXECUTABLE
command with the $
argument uses the compiled objects to create the fooExe
executable.
This looks very similar to what we did before, but because the source files are pre-compiled, we can create several executables that link against different ArrayFire backends without re-compiling the source files. Consider the following CMakeLists.txt
file:
...
FILE(GLOB SOURCE "*.cpp")
ADD_LIBRARY(FOO_CORE OBJECT ${SOURCE})
ADD_EXECUTABLE(fooExe-cuda $)
TARGET_LINK_LIBRARIES(fooExe-cuda ${ArrayFire_CUDA_LIBRARIES} )
ADD_EXECUTABLE(fooExe-opencl $)
TARGET_LINK_LIBRARIES(fooExe-opencl ${ArrayFire_OpenCL_LIBRARIES} )
Here we create two executables, fooExe-cuda
and fooExe-opencl
, but only compile the fooA.cpp
and fooB.cpp
files once! If you were to link against all three backends, like the ArrayFire benchmark suite does, this simple trick could reduce your compilation times by about a factor of three!
A fully worked example
Please see my ArrayFire CMake Project Example for a fully worked example that efficiently compiles code and links it with all three of ArrayFire’s backends.
Comments 5
Nice example of usage of the object libraries!
Note that there seems to be a problem with the parsing of CMake generator expression “$” that only appear as a single “$”
sorry, capitalization is removed by discuss, and I don’t know how to prevent that!
Thank you very much. Somehow our blogging platform stripped out the text! I have restored it to its correct state.
hyperlink in “Please see my ArrayFire CMake Project Example for a fully worked example that efficiently compiles code and links it with all three of ArrayFire’s backends.” does not lead anywhere
Thanks for pointing that out. We have made it even easier to use ArrayFire with modern CMake in our latest release! Check out our latest blog post for an example. https://arrayfire.com/arrayfire-v3-6-release
You can also look at our examples on how to properly use the ArrayFire as part of a CMake project.