CMake: Build C++ Project for Windows, Linux and macOS

Last week, I shared how to create a CMake project for Windows. Since CMake is a cross-platform software building tool, I am going to make my C/C++ project support Linux and macOS.

Download

In contrast to the C/C++ sample code for Windows, the code for Linux and macOS is a little bit different. My goal is to merge them into one .cxx file.

How to Make C/C++ Code Compatible with Multiple Platforms

I used the predefined macros to detect the operating system and customize relevant C/C++ code. Insert the following definitions to BarcodeReaderConfig.h.in:

#if defined(__linux) || defined(__linux__) || defined(linux)
# define LINUX
 
#elif defined(__APPLE__)
# define MACOS
 
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64)
# define WINDOWS
 
#endif

Use macros to control code logic in BarcodeReader.cxx.

How to Configure CMake Project

Windows

In my previous article, I didn’t mention how to select a generator. By default, CMake generates an x86 project in which I couldn’t link DynamsoftBarcodeReaderx64.dll. Let’s list the available generators on Windows:

cmake –help

cmake generator

According to the help information, we can specify a generator with Win64 architecture:

cmake -G"Visual Studio 14 2015 Win64" ..

Link the x64 library in CMakeLists.txt:

if(CMAKE_CL_64)
        target_link_libraries (BarcodeReader "DBRx64")
else()
        target_link_libraries (BarcodeReader "DBRx86")
endif()
 
if(CMAKE_CL_64)
        add_custom_command(TARGET BarcodeReader POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${PROJECT_SOURCE_DIR}/platforms/win/DynamsoftBarcodeReaderx64.dll"             
        $<TARGET_FILE_DIR:BarcodeReader>)
else()
        add_custom_command(TARGET BarcodeReader POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        "${PROJECT_SOURCE_DIR}/platforms/win/DynamsoftBarcodeReaderx86.dll"             
        $<TARGET_FILE_DIR:BarcodeReader>)
endif()
 
if(CMAKE_CL_64)
    install (FILES "${PROJECT_SOURCE_DIR}/platforms/win/DynamsoftBarcodeReaderx64.dll" DESTINATION bin)
else()
    install (FILES "${PROJECT_SOURCE_DIR}/platforms/win/DynamsoftBarcodeReaderx86.dll" DESTINATION bin)
endif()

Linux & macOS

Set the library search path:

if(LINUX)
    link_directories("${PROJECT_SOURCE_DIR}/platforms/linux")
elseif(MACOS)
    link_directories("${PROJECT_SOURCE_DIR}/platforms/macos")
endif()

Link shared libraries:

target_link_libraries (BarcodeReader "DynamsoftBarcodeReader")

Set install destination:

if(LINUX)
    install (FILES "${PROJECT_SOURCE_DIR}/platforms/linux/libDynamsoftBarcodeReader.so" DESTINATION lib)
elseif(MACOS)
    install (FILES "${PROJECT_SOURCE_DIR}/platforms/macos/libDynamsoftBarcodeReader.dylib" DESTINATION lib)
endif()

Build and install the executable file and library:

# Linux
sudo cmake --build . --target install
 
# macOS
cmake --build . --target install

Note: the default destinations are /usr/local/bin and /usr/local/lib. When you run the executable BarcodeReader, you will see the error message:

libDynamsoftBarcodeReader.so: cannot open shared object file: No such file or directory.

Can we fix the issue by changing the value of CMAKE_INSTALL_PREFIX to ‘/usr/lib’?

set(CMAKE_INSTALL_PREFIX "/usr")

It is okay for Linux, but not permitted on the latest version of macOS.

A usual solution is to export LD_LIBRARY_PATH:

export LD_LIBRARY_PATH =/usr/local/lib: $LD_LIBRARY_PATH

CMake provides a better way that changes RPATH.

set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)

Build and install the project again. We can now run barcode reader app with no errors.

cmake build install

Source Code

https://github.com/dynamsoft-dbr/cmake