Stop insulting your PC, you are in the right place. I know... Not being able to link with a given library is among the most awful feelings in computer programming. Fortunately, we are here to discuss this and to show how to link, in a proper way, with a C++ "library" called Gurobi.
For a ready-to-use FindGUROBI.cmake, please visit my FindGUROBI.cmake repo.
Gurobi is one of the most efficient mathematical optimization solver that exist in the world and is used by tons of people. Strangely enough, I have only seen little help on how to link with it using CMake. Even worse, the only examples I could found (e.g., searching for FindGUROBI.cmake
on GitHub) are simply... wrong. What I mean by wrong is a little bit subjective, I have to admit. But I say this in the sense that, even though the examples you find may work, none of them are safe and none of them use the Modern CMake paradigm. This is, in particular true for the official solution given by Gurobi itself!
For those of you who do not know what "Modern CMake" is. You can consider it as just a bunch of principles to be used when writting CMake files so that it is clean, elegant and powerful. It requires features that have been introduced in CMake versions 3.0.0 and more. The most important principles are as follows:
- Treat CMake code like production code ;
- Forget about
add_compile_options
,include_directories
,link_directories
,link_libraries
; - Be "target-oriented" instead.
For more details on this, you may refer to the gist of
mbinna or to the book Modern CMake for C++. To ease your task, I will start by showing you what we do not want (the ugly old-style CMake). Then, we will work on a better version. But, before that, we need to talk about how CMake finds new libraries in your system. Basically, what does find_package
do?
How does CMake finds packages
All right, let's start with a new CMake project. I will assume that you already have a valid main.cpp
. Let us start with the following standard CMakeLists.cmake
.
cmake_minimum_required(VERSION 3.19)
project(my_project)
add_executable(my_target main.cpp)
What this code does is rather simple. First, the cmake_minimum_required
command is called to specify CMake version requirements. I am using version 3.19
in this case (probably lower versions will work too). Then, a new project (i.e., a set of targets) is created with project
. Then, a new target called my_target
is created. This target will be an executable since the add_executable
command is being used. Following the name my_target
of the target, we have the list of source files which will be compiled for building the executable.
Note that if you do not know what a target is, you can simply view it as a "CMake object" with correspoding attributes and methods. For instance, you may use set_target_properties
to set your target's properties. Modern CMake asks for a target-oriented viewpoint while writting CMake files.
OK, now let's search for our package! This is done by using the find_package
command.
find_package(GUROBI REQUIRED)
The first parameter is the name of the package you are looking for, here, it is GUROBI
. Then, REQUIRED
tells CMake that the generation should stop if GUROBI
is not found, since it is REQUIRED
. Now let us try to run cmake
on this. (Easy steps are to create a new folder mkdir build
then to run cmake inside of it as follows: cd build && cmake ..
). We get the following error message.
CMake Error at CMakeLists.txt:14 (find_package):
By not providing "FindGUROBI.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "GUROBI", but
CMake did not find one.
Could not find a package configuration file provided by "GUROBI" with any
of the following names:
GUROBIConfig.cmake
gurobi-config.cmake
Add the installation prefix of "GUROBI" to CMAKE_PREFIX_PATH or set
"GUROBI_DIR" to a directory containing one of the above files. If "GUROBI"
provides a separate development package or SDK, be sure it has been
installed.
Let us try to understand this message. First, CMake tells us that it was looking for a file called FindGUROBI.cmake
but was unable to find it. It was looking for it in the CMAKE_MODULE_PATH
directories. Note that you can print the list of paths by executing message("${CMAKE_MODULE_PATH}")
. According to the documentation, this corresponds to the "Module" mode of find_package
. Simply put, CMake searches for a file called FindGUROBI.cmake
. If it cannot be found, an error is returned. If it is found, the file is executed and no error is returned. The library has been "found". From CMake's viewpoint, the hard part is to locate the file FindGUROBI.cmake
since this file is supposed to do all the work to actually provide informations on how to link with GUROBI
.
Then, CMake switches to the "Config" mode (only when the "Module" mode failed). In this mode, CMake searches for files named GUROBIConfig.cmake
or gurobi-config.cmake
in the paths listed in CMAKE_PREFIX_PATH
or in the GUROBI_DIR
. In principle, these files should have been provided by the package developpers during the installation process. If this were the case, a simple execution of find_package(GUROBI REQUIRED)
would have worked and be enough to "find" the package! Unfortunately, Gurobi does not provide such a file.
Thus, we will have to "manually" tell CMake how to find GUROBI
. Simply enough, we will do it by writing our own FindGUROBI.cmake
file. Create a new folder called cmake
with mkdir cmake
and create a new file cmake/FindGUROBI.cmake
. This is the file which we will be executed by CMake when calling find_package
. Now, remember that CMake only looks for FindGUROBI.cmake
in the list of directories which are in the CMAKE_MODULE_PATH
variable. Thus, let us add cmake/
as a potential location folder. This is done as follows.
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
From now on, CMake should be able to find our file. Remember that CMake considers that finding the FindGUROBI.cmake
file and executing it is enough to "actually" find the package. In our case, clearly, the FindGUROBI.cmake
file is empty and does nothing. We will have to write it! Before diving into it, I would like to give you an example of what we do not want as a result. That is, the old-style-CMake way of doing things.
The Ugly old-style CMake - What we do not want
When searching the internet of examples FindGUROBI.cmake
I have found a lot of files which could be used as follows.
find_package(GUROBI REQUIRED)
add_executable(my_target main.cpp)
include_directories(${GUROBI_INCLUDE_DIRS})
link_libraries(${GUROBI_LIBRARY})
This type of code poses several issues. First, it relies on "global variables" which have been created by the execution of FindGUROBI.cmake
. These variables are GUROBI_INCLUDE_DIRS
and GUROBI_LIBRARY
, for instance. These variables are globally accessible and non-const, meaning that they can be altered by any piece of CMake code by calling set(GUROBI_LIBRARY "my junk value")
. What's more, to make these variables "globally accessible", the FindGUROBI.cmake
(and therefore those who write this file) have to use the mark_as_advanced
command. Just by the name, you can tell you should not be using it.
Another issue is the use of include_directories
and link_libraries
which are non-target-based commands. In that sense, any given target will be linked with Gurobi and all targets will have access to the include directories. This is both unncessary and inelegant. Moreover, there is no control on how to propagate these dependencies. Consider creating a library which depends on other libraries, including Gurobi. How to tell CMake that a piece of code which links to your library should also link with Gurobi and other dependencies, and which dependencies should not be linked with?
I think you get my point, let's write our own Modern CMake FindGUROBI.cmake
file!
Modern CMake
OK, this part is dedicated to the actual writing of our FindGUROBI.cmake
file. At the end of this section, you will be able to write the following CMake code.
find_package(GUROBI REQUIRED)
add_executable(my_target main.cpp)
target_link_libraries(my_target PUBLIC gurobi)
This piece of code is beautiful. And I am not (only) saying this because it is mine, but because it is target-oriented, global-variable-free and controls dependency propagation. It is easily read (only one call to target_link_libraries
) and, even better, does not need an underlying FindGUROBI.cmake
which make use of any strange behaviour such as mark_as_advanced
commands. Let's start!
At this point, I will assume that Gurobi is installed in your machine, following these official guidelines or that the corresponding variables (GUROBI_HOME
, etc.) have been set accordingly. Once this is done, we can start writting our FindGUROBI.cmake
file! Roughly speakking, we need to do the following steps:
- Find the library files;
- Find the include directory;
- Check that everything has been found;
- Create a target named
gurobi
and set its properties;
Finding the library files
According to this official Gurobi page, for our C++ code to work with Gurobi we need to link with two different libraries: the Gurobi C++ library libgurobi_c++.a
and the Gurobi C library libgurobi95.so
. Thus, we will have to look for both of these files. Let us start with libgurobi95.so
. This is done as follows.
find_library(
GUROBI_LIBRARY
NAMES gurobi gurobi81 gurobi90 gurobi95 gurobi100
HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME}
PATH_SUFFIXES lib)
Some explanations on this command is needed. First, find_library
is broadly used to find the path of library files, e.g., .so or .a files, and stores the result in a cache variable. In our case, this variable's name is GUROBI_LIBRARY
. Then, we give some indications on the possible names of the file which we are looking for with the NAMES
keyword. Thus, we are telling CMake to search for a file with possible names gurobi
, gurobi81
, gurobi90
or gurobi95
. Then, we also need to provide some "hints" on where to find this file by using the HINTS
keyword. In our case, we are going to have a look to the directories path stored in the cache variable GUROBI_DIR
or the path inside the environment variable GUROBI_HOME
. If you installed Gurobi following the official guidelines, an appropriate environment variable should exists. Then, to each of these paths, we add the "lib" suffix by using the PATH_SUFFIXES
keyword. Indeed, by default, we have the environment variable GUROBI_HOME=/opt/gurobi951/linux64
(or alike - again, following the official installation guidelines). Yet, the library file we are looking for is located inside /opt/gurobi951/linux64/lib
. At this point, we should be able to run message("${GUROBI_LIBRARY}")
. This should print out something like /opt/gurobi951/linux64/lib/libgurobi95.so
.
OK, now we need to do the same for the C++ library libgurobi_c++.a
. The steps are very similar. For non-Visual-Studio-Code users, the following command will do the trick.
find_library(
GUROBI_CXX_LIBRARY
NAMES gurobi_c++
HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME}
PATH_SUFFIXES lib)
Again, we are looking for a library file named gurobi_c++
inside the folders with path contained in the cache variable GUROBI_DIR
or in the environment variable GUROBI_HOME
with a suffix lib
. For users of Visual Studio Code, I am relying on this "official Gurobi FindGUROBI.cmake file" which, as I anticipated, does not fulfill the Modern CMake standards and has the previously discussed drawbacks. All in all, we get the following code to search for libgurobi_c++.a
.
if(MSVC)
# determine Visual Studio year
if(MSVC_TOOLSET_VERSION EQUAL 142)
set(MSVC_YEAR "2019")
elseif(MSVC_TOOLSET_VERSION EQUAL 141)
set(MSVC_YEAR "2017")
elseif(MSVC_TOOLSET_VERSION EQUAL 140)
set(MSVC_YEAR "2015")
endif()
if(MT)
set(M_FLAG "mt")
else()
set(M_FLAG "md")
endif()
find_library(
GUROBI_CXX_LIBRARY
NAMES gurobi_c++${M_FLAG}${MSVC_YEAR}
HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME}
PATH_SUFFIXES lib)
else()
find_library(
GUROBI_CXX_LIBRARY
NAMES gurobi_c++
HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME}
PATH_SUFFIXES lib)
endif()
Finding the include directory
Quite similarly, we search for the include directories by using the find_path
command, which works much similarly to find_library
. Have a look at the self-explained piece of code.
find_path(
GUROBI_INCLUDE_DIRS
NAMES gurobi_c.h
HINTS ${GUROBI_DIR} $ENV{GUROBI_HOME}
PATH_SUFFIXES include)
Checking that everything has been found
We are now almost ready to create our gurobi
target with which our own target should link. Yet, we first need to check that everything has been found. Among other things, this can be done thanks to the find_package_handle_standard_args
command, which is defined in the FindPackageHandleStandardArgs
module. It is used as follows.
include(FindPackageHandleStandardArgs) # include the "FindPackageHandleStandardArgs" module
find_package_handle_standard_args(GUROBI DEFAULT_MSG GUROBI_LIBRARY GUROBI_CXX_LIBRARY GUROBI_INCLUDE_DIRS)
The find_package_handle_standard_args
command will have two main effects. First, it will check that variables GUROBI_LIBRARY
, GUROBI_CXX_LIBRARY
and GUROBI_INCLUDE_DIRS
have well been given a value. In other words, checks that we have found every path we needed. If this is the case, a variable GUROBI_FOUND
is created and set to true. Then, its second effect is to take into account the REQUIRED
or QUIET
arguments of the find_package
command. For instance, when we run find_package(gurobi REQUIRED)
, cmake will stop if the library cannot be found. All is clear, we are now ready to create our target!
Creating the gurobi target
If every path is found, we are good for creating our target. This target will be a "library" target, rather than an "executable" target. Thus, we will be using the add_library
command, instead of the add_executable
target. The C++ Gurobi library is a static library (its extension is .a) and we are going to "import" it. Thus, we do the following.
add_library(gurobi STATIC IMPORTED)
set_target_properties(gurobi PROPERTIES IMPORTED_LOCATION ${GUROBI_CXX_LIBRARY})
Quite simply, we first create an imported static library as a target named gurobi
, then set its IMPORTED_LOCATION
property to ${GUROBI_CXX_LIBRARY}
. Now, recall that any C++ program which is intended to work with gurobi should alos be linked with the C Gurobi library libgurobi95.so
. We will therefore use the "dependency propagation" feature of CMake. First, we will link our gurobi target to the C library by using target_link_libraries
command with the INTERFACE
keyword. By choosing the INTERFACE
keyword, CMake will automatically propagate this "linkage requirement" to any target linking with the gurobi
imported target itself. The same is done for the include directories which we may add by using the target_include_directories
command. This is done as follows.
if (GUROBI_FOUND)
add_library(gurobi STATIC IMPORTED)
set_target_properties(gurobi PROPERTIES IMPORTED_LOCATION ${GUROBI_CXX_LIBRARY})
target_link_libraries(gurobi INTERFACE ${GUROBI_LIBRARY})
target_include_directories(gurobi INTERFACE ${GUROBI_INCLUDE_DIRS})
endif()
That's it! We are now done with writting our FindGUROBI.cmake
file.
FAQ: How do I resolve "undefined reference" errors while linking Gurobi in C++?
This is a common issue. All you need to do is to re-compile the Gurobi library. Please, refer to this Official post from Gurobi.