Using CMake create a library, rpm package and source rpm package.

Using CMake create a library, rpm package and source rpm package.

When working with C/C++ libraries on RHEL-based systems like RedHat, Rocky Linux, and CentOS Stream, RPM packages are the go-to method. They simplify managing different versions, development files, and sources, making deployments a breeze with yum or dnf. RPM packages also ensure consistency across environments, which is crucial for stable production systems.

While the traditional approach involves rpmbuild and manually writing spec files, there's a modern alternative that developers love: CMake paired with cpack. This combo not only speeds up the process but also adds versatility. By using the CPACK_GENERATOR flag, you can package your library in various formats, such as ZIP, RPM, DEB, or TGZ, depending on your target OS. This flexibility means you’re not locked into a single format—perfect for cross-platform projects.

If you are maintaining libraries across different environments, this method saves a lot of time and reduces the potential for errors during packaging and deployment. It is a simple, yet powerful, way to streamline your library management.

For the Linux OS one can use RPM,DEB, TZ, TGZ based one OS distro.

For the Window OS once can select NSIS, ZIP

For MAC OS one can select PACKAGEMAKER, OSXX11 and etc.

Now, let’s walk through the steps to get this set up. I’ll be demonstrating on Rocky Linux 9 with CMake version 3.26.

Before we begin, make sure you have both CMake and rpmbuild installed on your system. If not, you can easily install them using the following command.

sudo dnf install gcc-c++ rpm-build cmake 

Let's write a simple add library in C++, it takes int, float, strings as input and return the result.

directory tree of this code -

.
├── build.sh
└── libadd
    ├── add.cpp
    ├── add.h
    └── CMakeLists.txt

1 directory, 4 files
// add.h
#ifndef ADD_H
#define ADD_H

#include <string>

int add(int a, int b);
float add(float a, float b);
double add(double a, double b);
std::string add(const std::string &a, const std::string &b);

#endif // ADD_H

add.h

// add.cpp
#include "add.h"

// Function to add integers
int add(int a, int b) {
    return a + b;
}

// Function to add floats
float add(float a, float b) {
    return a + b;
}

// Function to add doubles
double add(double a, double b) {
    return a + b;
}

// Function to add strings
std::string add(const std::string &a, const std::string &b) {
    return a + b;
}

add.cpp

cmake_minimum_required(VERSION 3.26)

project(libadd VERSION 1.0 LANGUAGES CXX)

add_library(add SHARED add.cpp)

target_include_directories(add PUBLIC $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>)

set_target_properties(add PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1)

install(TARGETS add 
    LIBRARY DESTINATION lib COMPONENT lib
    ARCHIVE DESTINATION lib COMPONENT devel
)

install(FILES add.h DESTINATION include COMPONENT devel)

# RPM-specific settings for CPack
set(CPACK_GENERATOR "RPM")

# Package information
set(CPACK_PACKAGE_NAME "libadd")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_RELEASE 1)
set(CPACK_PACKAGE_DESCRIPTION "A library for addition of integers, floats, doubles, and strings.")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A simple addition library.")
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_URL "www.trycatchexcept.com")

# Set RPM package architecture
set(CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64")

# Define dependencies
set(CPACK_RPM_PACKAGE_REQUIRES "gcc-c++")

# Packaging options for RPM: automatically detect requirements
set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)

# Optional: specify the file name format of the RPM package
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}.${CPACK_RPM_PACKAGE_ARCHITECTURE}")

# Rename the runtime component to "lib"
set(CPACK_RPM_RUNTIME_COMPONENT_INSTALL ON)
set(CPACK_RPM_RUNTIME_PACKAGE_NAME "libadd")

# Rename the development component to "devel"
set(CPACK_RPM_DEVELOPMENT_COMPONENT_INSTALL ON)
set(CPACK_DEVEL_PACKAGE_NAME "libadd-devel")
set(CPACK_DEVEL_PACKAGE_DESCRIPTION "Development package for libadd")

# Add an option to decide whether to build source RPM or binary RPM
option(GENERATE_SOURCE_RPM "Generate source RPM" OFF)

# RPM configuration for generating binary RPMs with components
if(NOT GENERATE_SOURCE_RPM)
    # Component-based binary RPMs (lib and devel)
    set(CPACK_COMPONENTS_ALL lib devel)
    set(CPACK_RPM_COMPONENT_INSTALL ON)
else()
    # Source RPM (disable component-based RPM)
    set(CPACK_RPM_PACKAGE_SOURCES ON)
endif()

# Include CPack for packaging
include(CPack)

CMakeLists.txt

#!/bin/bash

#define build paths
source_path=$(pwd)/libadd
source_rpm_path=$(pwd)/source-rpm
binary_rpm_path=$(pwd)/binary-rpm

if [ "$1" == "clean" ]
then
    rm -rf $source_rpm_path $binary_rpm_path
    exit 0
fi

#create new build paths
rm -rf $source_rpm_path $binary_rpm_path
mkdir $source_rpm_path $binary_rpm_path

#build and pacakge binary rpm
cmake -B$binary_rpm_path -S$source_path -DGENERATE_SOURCE_RPM=ON
cd $binary_rpm_path
make
cpack

if [ $? == 0 ]; then
    #build and package source rpm
    cmake -B$source_rpm_path -S$source_path -DGENERATE_SOURCE_RPM=OFF
    cd $source_rpm_path 
    cpack
fi

Using the build script, one should be able to generate the source as well as the binary rpm.

build script output

rohit@172:~/Blogging/libadd$ sh build.sh 
-- The CXX compiler identification is GNU 11.4.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/rohit/Blogging/libadd/binary-rpm
[ 50%] Building CXX object CMakeFiles/add.dir/add.cpp.o
[100%] Linking CXX shared library libadd.so
[100%] Built target add
CPack: Create package using RPM
CPack: Install projects
CPack: - Run preinstall target for: libadd
CPack: - Install project: libadd []
CPack: Create package
libadd-1.0-1.x86_64
libadd-1.0-1.x86_64/usr
libadd-1.0-1.x86_64/usr/lib
libadd-1.0-1.x86_64/usr/lib/libadd.so.1.0
libadd-1.0-1.x86_64/usr/lib/libadd.so.1
libadd-1.0-1.x86_64/usr/lib/libadd.so
libadd-1.0-1.x86_64/usr/include
libadd-1.0-1.x86_64/usr/include/add.h
CPackRPM: Will use GENERATED spec file: /home/rohit/Blogging/libadd/binary-rpm/_CPack_Packages/Linux/RPM/SPECS/libadd.spec
CPack: - package: /home/rohit/Blogging/libadd/binary-rpm/libadd-1.0-1.src.rpm generated.
-- The CXX compiler identification is GNU 11.4.1
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/rohit/Blogging/libadd/source-rpm
CPack: Create package using RPM
CPack: Install projects
CPack: - Run preinstall target for: libadd
CPack: - Install project: libadd []
CPack: -   Install component: lib
CPack: -   Install component: devel
CPack: Create package
CPackRPM: Will use GENERATED spec file: /home/rohit/Blogging/libadd/source-rpm/_CPack_Packages/Linux/RPM/SPECS/libadd-devel.spec
CPackRPM: Will use GENERATED spec file: /home/rohit/Blogging/libadd/source-rpm/_CPack_Packages/Linux/RPM/SPECS/libadd-lib.spec
CPack: - package: /home/rohit/Blogging/libadd/source-rpm/libadd-1.0-1.x86_64-devel.rpm generated.
CPack: - package: /home/rohit/Blogging/libadd/source-rpm/libadd-1.0-1.x86_64-lib.rpm generated.
rohit@172:~/Blogging/libadd$ 

Read more