skip to Main Content

I’m having trouble integrating the GMP library into my CMake project. Specifically, I’m struggling to configure CMake to automatically download and include GMP using FetchContent_Declare. I’m unsure about the necessary CMake configurations and whether additional steps like compilation or building are required for GMP.

To learn I am making a cryptography library in C++. Recently I implemented ECC/ECDSA using a small multi-precision library called InfInt. Which worked to make sure my logic worked, but it is quite slow. Using a profiler Tracy I found the bottleneck was the division arithmetic in InfInt, so I am trying to replace InfInt with GMP.

I have used GMP in the past by building it with MSYS2 and linking to it in my other projects. But since this library is open-source and I would hate if people download my library and can’t just use it out of the box, I want to use CMake to seamlessly compile and link GMP.

But when I try to use CMake’s FetchContent_Declare in a similar way I have used it for another library called googletest I get this error:

...
[build] LINK : fatal error LNK1104: cannot open file 'gmp.lib' [C:ProjectsCryptoLibrarybuildteststests.vcxproj]
[proc] The command: "C:Program FilesCMakebincmake.EXE" --build c:/Projects/CryptoLibrary/build --config Debug --target ALL_BUILD -j 14 -- exited with code: 1
[driver] Build completed: 00:00:07.522
[build] Build finished with exit code 1

I suspect the problem is that I am not properly telling CMake what the GMP library is, so it cannot compile the gmp.lib file like I was able to for google test and infint. But this is where I am clueless on where to continue to try to solve this if this even is the problem.

Project overview:

To develop my library I am using Visual Studio Code, I have been compiling my project using C++11 MSVC, but I am not partial to these, this is just what has worked so far. As I mentioned above I am using CMake’s FetchContent_Declare to retrieve googletest, but I followed a tutorial for.

Simple file system

CryptoLibrary/
│
├── CMakeLists.txt
├── src/
│   ├── ecc/
│   │   ├── ecc.cpp
│   │   ├── ecc.h
│   │   ├── standardCurves.h
│   │   └── ecdsa/
│   │       └── ecdsa.cpp
│   └── ...
│
├── include/
│   ├── CryptoLibrary/
│   │   ├── ecdsa.h
│   │   └── ...
│   └── ...
│
├── tests/
│   ├── CMakeLists.txt
│   ├── eccTests.cpp
│   ├── ecdsaTests.cpp
│   └── ...
│
├── external/
│   └── infint/
│       ├── CMakeLists.txt
│       ├── infint.h
│       └── main.cpp
│
├── build/
│   └── ...
│ 
└── .vscode/
    └── ...

Here is also a simplified version of my Root CMakeLists.txt:

cmake_minimum_required(VERSION 3.16.3)

set (CMAKE_C_STANDARD 99)
set (CMAKE_CXX_STANDARD 20)
set (CMAKE_POSITION_INDEPENDENT_CODE ON)

set (Project_Name CryptographyLibrary)
project (${Project_Name} VERSION 0.3.1 LANGUAGES C CXX)

set(FETCHCONTENT_BASE_DIR "${CMAKE_BINARY_DIR}/external")
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)

enable_testing ()

include(FetchContent)

FetchContent_Declare(
    googletest
    URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)

FetchContent_Declare(
  gmp
  URL https://gmplib.org/download/gmp/gmp-6.3.0.tar.lz
)

set (Sources
    src/ecc/ecc.cpp
    src/ecc/ecdsa/ecdsa.cpp
    #...
)

set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
FetchContent_MakeAvailable(gmp)

add_library (${Project_Name} STATIC ${Sources})
# Specify the directories where header files are located
target_include_directories(${Project_Name} PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_include_directories(${Project_Name} PRIVATE ${PROJECT_SOURCE_DIR}/external)
target_include_directories(${Project_Name} PRIVATE ${PROJECT_SOURCE_DIR}/tools)

add_subdirectory(external/infint)

target_link_libraries(${Project_Name} PRIVATE gtest_main)
target_link_libraries(${Project_Name} PRIVATE gmp)

add_subdirectory(tests)

And here are the two CMakeLists.txt inside my tests/ and external/infint/ directories:
tests/CMakeLists.txt

cmake_minimum_required(VERSION 3.16.3)

include(GoogleTest)

set (This tests)

set (Sources
    eccTests.cpp
    ecdsaTests.cpp
    ...
)

add_executable (${This} ${Sources})

target_link_libraries (${This} PRIVATE
    GTest::gtest_main
    CryptographyLibrary
)

add_test (
    NAME ${This}
    COMMAND ${This}
)

include(GoogleTest)
gtest_discover_tests(${This})

external/infint/CMakeLists.txt

set (This infint)
add_library(${This} STATIC main.cpp)
target_link_libraries (${This} PRIVATE CryptographyLibrary)
target_include_directories(${This} PUBLIC ${PROJECT_SOURCE_DIR})

You may have noticed I am not using FetchContent_Declare to get InfInt. This is because it is a small two file library, and never planned to use it when I merge to main. So I just bundled it and add the executable to my project using CMake. This is not something that I believe would be a good solution to also include GMP.

I included what I thought would be useful for context here, but let me know if there are any other parts that would be useful to see to help me solve this.

What I tried

Using FetchContent_Declare in root CMakeLists.txt

This is the approach that is shown in the project overview above. I initially attempted to use CMake’s FetchContent_Declare to fetch and include the GMP library directly into my project. This approach involved specifying the GMP library URL and adding it as a dependency in my root CMakeLists.txt file.

Manual Installation of GMP

As an alternative, I considered manually bundleing a trimmed version of the GMP library in the library and then linking it to our project. But I quickly found out how large the GMP library is, so it would add a lot of code to my library.

Using ExternalProject_Add in root CMakeLists.txt

Another approach I considered was using CMake’s ExternalProject_Add to download, build, and install the GMP library as an external project during the build process of our library. This approach I didn’t dig to much into because it seemed similar to FetchContenct_Declare, and I prefered using FetchContent_Declare. But if I am missing something that would make this approach better I am not dismissing it.

Using CMake’s find_package

I followed this stackoverflow question, but as someone would need to install GMP on their computer and follow the configuration to compile GMP themselves this is not an ideal solution.

Ideal outcome

The most ideal outcome would be a seamless integration of GMP into the project via CMake, where developers can simply include the library without needing to manually download, configure, or link against GMP, streamlining the development process and reducing potential errors or compatibility issues.

How I envision others using the library, and how I have used the library in demonstrations is also using CMakes FetchContent_Declare for my library from GitHub. Once they do that then they can just do #include <CryptographyLibrary/*.h> and it just works.

A few observations

Just something I noticed while looking at the actual repo of the GMP library that I downloaded to my computer. I could not find the gmp.h file in the repo, but I could only find the gmpxx.h file.

Other considerations

I am not dead set on only using GMP, or only solving this problem with FetchContent_Declare from CMake. But I am dead set on using CMake. So if you know another optimized Multi-precision library that I could use for the purpose of cryptography (which has its own special security considerations) then I am very open to looking into those!

I have also attempted to use another library ctbignum but had problems switching my build system to use C++20 gcc which seems required for ctbignum and would limit where and how my library can be used.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to those who gave me suggestions

    To do this I discovered two things:

    1. The MPIR library
    2. CMakes ExternalProject_Add

    First off, GMP as I found out does not compile easily on Windows devices (which I am working from). That is where the MPIR library comes in. As I understand it the MPIR library is a fork of GMP which is easier to build on Windows because it has Visual Studio solution you can run. If I wanted to use GMP I would have needed to use the MSYS2 shell, which I have, but would need to find a way to have CMake interface with that to build it. So using MPIR was easier for me. In actuality I'm using a fork of the MPIR, so a fork of a fork. This fork I am using has a CMake tar of MPIR that I could use instead of making my own.

    Second, I used the ExternalProject_Add from CMake instead of FetchContent_Declare, I'm sure there is a way to do this with FetchContent_Declare, this was just easier for me. I ended up using ExternalProject_Add as it allowed me to more easily set environment variables and arguments for MPIR.

    For those looking to do something similar, here is what I added to my root CMakeLists.txt, but this can also be easily added to a subdirectory CMakeLists.txt:

    set (CMAKE_C_STANDARD 99)
    set (CMAKE_CXX_STANDARD 11)
    set (CMAKE_POSITION_INDEPENDENT_CODE ON)
    
    set (Project_Name library)
    project (${Project_Name} VERSION 0.3.1 LANGUAGES C CXX)
    
    # Include MPIR library
    include(ExternalProject)
    
    set(prefix "${CMAKE_BINARY_DIR}/deps")
    set(MPIR_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}mpir${CMAKE_STATIC_LIBRARY_SUFFIX}")
    set(MPIR_INCLUDE_DIR "${prefix}/include")
    
    ExternalProject_Add(mpir
        PREFIX "${prefix}"
        DOWNLOAD_NAME mpir-cmake.tar.gz
        DOWNLOAD_NO_PROGRESS TRUE
        URL https://github.com/chfast/mpir/archive/cmake.tar.gz
        URL_HASH SHA256=d32ea73cb2d8115a8e59b244f96f29bad7ff03367162b660bae6495826811e06
        CMAKE_ARGS
            -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
            -DCMAKE_INSTALL_LIBDIR=lib
            -DCMAKE_BUILD_TYPE=Release
            -DMPIR_GMP=On
        BUILD_BYPRODUCTS "${MPIR_LIBRARY}"
    )
    
    # Specify the directories where header files are located
    target_include_directories(${Project_Name} PRIVATE ${MPIR_INCLUDE_DIR})
    
    # Link MPIR library
    add_dependencies(${Project_Name} mpir)
    target_link_libraries(${Project_Name} PRIVATE ${MPIR_LIBRARY})
    

    That was it. Now anywhere I need GMP I can include the gmp header #include <gmp.h> and it just works.

    No need for packet managers, and if someone downloads my library CMake will automagically download, compile and link the GMP library.


  2. Looking through GMP library from the path you provided in your CMakeLists.txt, GMP does not seem that the project even uses CMake, it uses make.

    Without a CMakeLists.txt there cannot be a CMake project, and therefor no cmake configurator file, so I am not sure how you expect to call find_package on it.

    You continuing by saying
    "..However, I encountered errors related to missing CMakeLists.txt files and configuration issues, leading to build failures"

    Yeah, well how are we supposed to help you if you don’t provide us with relevant error messages?

    If you want to include GMP in your CMake project, you need to configure it for you project.

    I just skimmed through this post, but it seems to explain how you could go about including GMP in you project.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search