NeuroAgent

How to Configure CMake for Different Linux Distributions

Solution to the problem of different package names in CMakeLists.txt for Arch Linux and Debian. Using pkg-config, find_package, and cross-distribution build configuration.

Question

How do I properly add libraries to CMakeLists.txt when package names differ between Linux distributions?

I have a project originally configured for Arch Linux with the following code in CMakeLists:

cmake
# find c++utilities
find_package(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.20.0 REQUIRED)
use_cpp_utilities(VISIBILITY PUBLIC)

# find qtutilities
find_package(${PACKAGE_NAMESPACE_PREFIX}qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES} 6.13.0 REQUIRED)
use_qt_utilities()

In Debian, these packages are named libmartchus-c++utilities-dev and libmartchus-qtutilities-dev. When I try to substitute the names and remove the variables, I get an error:

CMake Error at src/CMakeLists.txt:137 (find_package):
  By not providing "Findlibmartchus-c++utilities-dev.cmake" in
  CMAKE_MODULE_PATH this project has asked CMake to find a package
  configuration file provided by "libmartchus-c++utilities-dev", but
  CMake did not find one.

  Could not find a package configuration file provided by
  "libmartchus-c++utilities-dev" with any of the following names:

    libmartchus-c++utilities-devConfig.cmake
    libmartchus-c++utilities-dev-config.cmake

  Add the installation prefix of "libmartchus-c++utilities-dev" to
  CMAKE_PREFIX_PATH or set "libmartchus-c++utilities-dev_DIR" to a
  directory containing one of the above files.  If
  "libmartchus-c++utilities-dev" provides a separate development
  package or SDK, be sure it has been installed.

-- Configuring incomplete, errors occurred!

How do I properly configure CMakeLists.txt to build the package in Debian?

NeuroAgent

Solving Package Naming Issues Between Linux Distributions in CMakeLists.txt

To solve the problem of different package names between Linux distributions in CMakeLists.txt, you need to use a combination of package search strategies and conditional compilation. The main issue is that different distributions use different package naming conventions and provide CMake configuration files differently.

Table of Contents

Understanding the Problem

The main issues are:

  1. Different naming conventions: Arch Linux uses c++utilities, while Debian uses libmartchus-c++utilities-dev
  2. Missing configuration files: Debian packages typically don’t provide *Config.cmake files by default
  3. Different installation paths: Packages may be located in different system paths
  4. Different dependencies: Different distributions may require different versions of dependencies

The error you’re getting occurs because CMake is looking for a package configuration file that isn’t provided in standard Debian packages.

Solution Strategies

There are several approaches to solve this problem:

1. Using Multiple find_package Calls

Check for packages with different names and use the first available one:

cmake
# Try Arch Linux style first, then Debian
if(NOT DEFINED CMAKE_CXXUTILITIES_FOUND)
    find_package(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.20.0 QUIET)
    if(NOT ${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX}_FOUND)
        find_package(libmartchus-c++utilities-dev 5.20.0 QUIET)
    endif()
    if(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX}_FOUND OR libmartchus-c++utilities-dev_FOUND)
        set(CMAKE_CXXUTILITIES_FOUND TRUE)
    endif()
endif()

if(CMAKE_CXXUTILITIES_FOUND)
    if(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX}_FOUND)
        use_cpp_utilities(VISIBILITY PUBLIC)
    elseif(libmartchus-c++utilities-dev_FOUND)
        # Alternative logic for Debian
        target_link_libraries(your_target PRIVATE libmartchus-c++utilities-dev)
    endif()
endif()

2. Creating Custom Find Modules

Create Find*.cmake modules for each distribution:

cmake/
├── FindC++Utilities.cmake
├── FindQtUtilities.cmake
└── Modules/
    └── FindDebianC++Utilities.cmake

3. Using pkg-config

Most libraries provide .pc files for pkg-config, which work across almost all distributions:

cmake
find_package(PkgConfig REQUIRED)

pkg_check_modules(CPPUTILITIES REQUIRED c++utilities)
pkg_check_modules(QTUTILITIES REQUIRED qtutilities)

The most reliable and portable solution is a combination of pkg-config and fallback search mechanisms:

cmake
# Function to search for libraries with different names
function(find_package_cross_distribution PACKAGE_NAME VERSION DEBIAN_NAME)
    # First try pkg-config
    find_package(PkgConfig QUIET)
    if(PkgConfig_FOUND)
        pkg_check_modules(${PACKAGE_NAME}_PKG ${PACKAGE_NAME} QUIET)
        if(${PACKAGE_NAME}_PKG_FOUND)
            set(${PACKAGE_NAME}_FOUND TRUE PARENT_SCOPE)
            return()
        endif()
    endif()
    
    # Try standard name
    find_package(${PACKAGE_NAME} ${VERSION} QUIET)
    if(${PACKAGE_NAME}_FOUND)
        return()
    endif()
    
    # Try Debian name
    if(DEFINED DEBIAN_NAME)
        find_package(${DEBIAN_NAME} ${VERSION} QUIET)
        if(${DEBIAN_NAME}_FOUND)
            set(${PACKAGE_NAME}_FOUND TRUE PARENT_SCOPE)
            set(${PACKAGE_NAME}_DEBIAN_NAME ${DEBIAN_NAME} PARENT_SCOPE)
            return()
        endif()
    endif()
    
    # If nothing found, show error
    message(FATAL_ERROR "Package ${PACKAGE_NAME} (version ${VERSION}) not found. "
                        "Please install it using your system package manager.")
endfunction()

# Using the function
find_package_cross_distribution(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.20.0 libmartchus-c++utilities-dev)
find_package_cross_distribution(${PACKAGE_NAMESPACE_PREFIX}qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES} 6.13.0 libmartchus-qtutilities-dev)

# Conditional usage logic
if(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX}_FOUND)
    use_cpp_utilities(VISIBILITY PUBLIC)
elseif(DEFINED c++utilities_DEBIAN_NAME)
    # For Debian, use direct linking
    target_link_libraries(your_target PRIVATE ${c++utilities_DEBIAN_NAME})
endif()

if(${PACKAGE_NAMESPACE_PREFIX}qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES}_FOUND)
    use_qt_utilities()
elseif(DEFINED qtutilities_DEBIAN_NAME)
    target_link_libraries(your_target PRIVATE ${qtutilities_DEBIAN_NAME})
endif()

Implementation Example

Here’s a complete example for your CMakeLists.txt:

cmake
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0)

# Configure variables
set(PACKAGE_NAMESPACE_PREFIX "")
set(CONFIGURATION_PACKAGE_SUFFIX "")
set(CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES "")

# Detect distribution
if(EXISTS /etc/debian_version)
    set(DEBIAN_PACKAGE_NAMES TRUE)
    message(STATUS "Detected Debian-based system")
elseif(EXISTS /etc/arch-release)
    message(STATUS "Detected Arch Linux")
elseif(EXISTS /etc/redhat-release)
    message(STATUS "Detected RedHat-based system")
endif()

# Function to search for libraries
function(find_library_cross_distribution LIB_NAME VERSION ARCH_NAME DEBIAN_NAME)
    # First try pkg-config
    find_package(PkgConfig QUIET)
    if(PkgConfig_FOUND)
        string(TOLOWER "${LIB_NAME}" LIB_LOWER)
        pkg_check_modules(${LIB_NAME}_PKG ${LIB_LOWER} QUIET)
        if(${LIB_NAME}_PKG_FOUND)
            set(${LIB_NAME}_FOUND TRUE PARENT_SCOPE)
            return()
        endif()
    endif()
    
    # Try Arch Linux name
    find_package(${ARCH_NAME} ${VERSION} QUIET)
    if(${ARCH_NAME}_FOUND)
        set(${LIB_NAME}_FOUND TRUE PARENT_SCOPE)
        set(${LIB_NAME}_PACKAGE_NAME ${ARCH_NAME} PARENT_SCOPE)
        return()
    endif()
    
    # Try Debian name
    if(DEBIAN_PACKAGE_NAMES)
        find_package(${DEBIAN_NAME} ${VERSION} QUIET)
        if(${DEBIAN_NAME}_FOUND)
            set(${LIB_NAME}_FOUND TRUE PARENT_SCOPE)
            set(${LIB_NAME}_PACKAGE_NAME ${DEBIAN_NAME} PARENT_SCOPE)
            return()
        endif()
    endif()
    
    # Try to find manually via pkg-config files
    find_file(${LIB_NAME}_PC_FILE 
        NAMES ${LIB_NAME}.pc
        PATHS /usr/lib/pkgconfig /usr/lib/*/pkgconfig /usr/share/pkgconfig
    )
    
    if(${LIB_NAME}_PC_FILE)
        set(${LIB_NAME}_FOUND TRUE PARENT_SCOPE)
        return()
    endif()
    
    # If nothing found
    set(${LIB_NAME}_FOUND FALSE PARENT_SCOPE)
    message(WARNING "Could not find ${LIB_NAME} (version ${VERSION})")
endfunction()

# Search for c++utilities
find_library_cross_distribution(
    CXX_UTILITIES 
    5.20.0 
    ${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX}
    libmartchus-c++utilities-dev
)

# Search for qtutilities
find_library_cross_distribution(
    QT_UTILITIES 
    6.13.0 
    ${PACKAGE_NAMESPACE_PREFIX}qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES}
    libmartchus-qtutilities-dev
)

# Check found packages
if(CXX_UTILITIES_FOUND)
    message(STATUS "Found c++utilities: ${CXX_UTILITIES_PACKAGE_NAME}")
    if(DEFINED CXX_UTILITIES_PACKAGE_NAME)
        if(CXX_UTILITIES_PACKAGE_NAME STREQUAL "libmartchus-c++utilities-dev")
            # For Debian, use direct linking
            find_library(CXX_UTILITIES_LIB 
                NAMES c++utilities
                PATHS /usr/lib /usr/lib64 /usr/local/lib
            )
            if(CXX_UTILITIES_LIB)
                target_link_libraries(your_target PRIVATE ${CXX_UTILITIES_LIB})
            endif()
        else()
            # For Arch, use use_cpp_utilities
            use_cpp_utilities(VISIBILITY PUBLIC)
        endif()
    else()
        use_cpp_utilities(VISIBILITY PUBLIC)
    endif()
else()
    message(FATAL_ERROR "c++utilities not found. Please install it.")
endif()

if(QT_UTILITIES_FOUND)
    message(STATUS "Found qtutilities: ${QT_UTILITIES_PACKAGE_NAME}")
    if(DEFINED QT_UTILITIES_PACKAGE_NAME)
        if(QT_UTILITIES_PACKAGE_NAME STREQUAL "libmartchus-qtutilities-dev")
            # For Debian, use direct linking
            find_library(QT_UTILITIES_LIB 
                NAMES qtutilities
                PATHS /usr/lib /usr/lib64 /usr/local/lib
            )
            if(QT_UTILITIES_LIB)
                target_link_libraries(your_target PRIVATE ${QT_UTILITIES_LIB})
            endif()
        else()
            # For Arch, use use_qt_utilities
            use_qt_utilities()
        endif()
    else()
        use_qt_utilities()
    endif()
else()
    message(FATAL_ERROR "qtutilities not found. Please install it.")
endif()

Additional Methods

1. Using Environment Variables

Add support for environment variables to override package names:

cmake
# Allow users to override package names
if(DEFINED ENV{CMAKE_CXXUTILITIES_NAME})
    set(CXXUTILITIES_CUSTOM_NAME $ENV{CMAKE_CXXUTILITIES_NAME})
endif()

if(DEFINED CXXUTILITIES_CUSTOM_NAME)
    find_package(${CXXUTILITIES_CUSTOM_NAME} 5.20.0 REQUIRED)
else()
    # Use standard search logic
endif()

2. Creating Configuration Files

Create config.cmake or config.h.in files to define available features:

cmake
# config.cmake
option(USE_CXX_UTILITIES "Enable c++utilities support" ON)
option(USE_QT_UTILITIES "Enable qtutilities support" ON)

if(USE_CXX_UTILITIES)
    # c++utilities search logic
endif()

if(USE_QT_UTILITIES)
    # qtutilities search logic
endif()

3. Using Conan or vcpkg

For even better portability, consider using package managers:

cmake
find_package(CONAN REQUIRED)
conan_cmake_run(REQUIRES c++utilities/5.20.0 qtutilities/6.13.0
                BASIC_SETUP
                BUILD_MISSING)

Testing and Validation

To test your CMakeLists.txt on different distributions:

  1. Create Docker containers with different distributions:

    bash
    # Dockerfile for Debian test
    FROM debian:bullseye
    RUN apt-get update && apt-get install -y cmake libmartchus-c++utilities-dev libmartchus-qtutilities-dev
    
    # Dockerfile for Arch test
    FROM archlinux:latest
    RUN pacman -Syu --noconfirm cmake c++utilities qtutilities
    
  2. Use CI/CD for automated testing:

    yaml
    # .github/workflows/ci.yml
    name: Cross-distribution Build Test
    on: [push, pull_request]
    jobs:
      build:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            distribution: [ubuntu-20.04, ubuntu-22.04, archlinux]
        steps:
          - uses: actions/checkout@v2
          - name: Setup ${{ matrix.distribution }}
            uses: vmactions/archlinux@v0.1.4
          - name: Install dependencies
            run: |
              # Install dependencies for specific distribution
          - name: Configure
            run: cmake .
          - name: Build
            run: cmake --build .
    
  3. Add package availability tests:

    cmake
    # In CMakeLists.txt
    add_test(NAME CheckCXXUtilities COMMAND ${CMAKE_COMMAND} -E echo "Testing c++utilities")
    add_test(NAME CheckQtUtilities COMMAND ${CMAKE_COMMAND} -E echo "Testing qtutilities")
    
    # Or more complex tests
    function(test_package_availability PACKAGE_NAME)
        find_package(${PACKAGE_NAME} QUIET)
        if(${PACKAGE_NAME}_FOUND)
            message(STATUS "✓ ${PACKAGE_NAME} is available")
        else()
            message(WARNING "✗ ${PACKAGE_NAME} is not available")
        endif()
    endfunction()
    
    test_package_availability(c++utilities)
    test_package_availability(qtutilities)
    

Conclusion

To properly configure CMakeLists.txt to work across different Linux distributions:

  1. Use a combination of search strategies: pkg-config, find_package with different names, manual search
  2. Create abstract functions for package search with handling for different distributions
  3. Implement conditional logic for linking libraries differently based on found packages
  4. Add environment variable support for manually overriding package names
  5. Test on different distributions using Docker and CI/CD

Key principles:

  • Flexibility: Allow the project to work on different distributions
  • Reliability: Provide multiple ways to search for packages
  • Convenience: Give users the ability to override behavior
  • Testability: Ensure automated testing across different platforms

This approach ensures that your project will successfully build on Arch Linux, Debian, Ubuntu, and other Linux distributions without needing to manually edit CMakeLists.txt for each platform.