Libmagicxx v10.0.3
A modern C++23 wrapper for libmagic — the library that powers the Unix file command.
Loading...
Searching...
No Matches
CMake Style Guide for Libmagicxx

Overview

This document defines the coding conventions and best practices for CMake files in the Libmagicxx project.

Table of Contents

General Principles

  1. Clarity over cleverness: Write CMake code that is easy to understand and maintain.
  2. Modern CMake: Use target-based approach (target_* commands) rather than directory-based commands.
  3. Consistency: Follow established patterns in existing CMake files.
  4. Modularity: Keep CMake modules focused on a single responsibility.

File Organization

Directory Structure

cmake/
├── STYLE_GUIDE.md # This file
├── MagicxxConfig.cmake.in # Package config template
├── options.cmake # Build options
├── directories.cmake # Path definitions
├── sources.cmake # Source file lists
├── files.cmake # Additional file definitions
├── dependencies.cmake # External dependencies
├── targets.cmake # Library target definitions
├── package_config.cmake # CMake package configuration
├── install.cmake # Installation rules
├── uninstall.cmake # Uninstall target
├── cpack.cmake # Packaging configuration
├── format_source_code.cmake # Code formatting target
├── generate_default_database_files.cmake # Database generation
└── toolchains/ # Toolchain files
├── linux-x86_64-clang.cmake
├── linux-x86_64-gcc.cmake
└── ...

Include Order

In CMakeLists.txt, include modules in dependency order:

include(cmake/options.cmake) # Build options first
include(cmake/directories.cmake) # Path setup
include(cmake/sources.cmake) # File lists (depends on directories)
include(cmake/files.cmake) # Additional files
include(cmake/dependencies.cmake) # External deps
include(cmake/targets.cmake) # Library targets (depends on above)
include(cmake/package_config.cmake) # Package config
include(cmake/install.cmake) # Installation
include(cmake/uninstall.cmake) # Uninstall target
include(cmake/cpack.cmake) # Packaging
include(cmake/format_source_code.cmake) # Dev tools
include(cmake/generate_default_database_files.cmake)

Naming Conventions

Variables

Category Convention Example
Project internal project_SUFFIX magicxx_SOURCE_DIR, magicxx_VERSION
External dependency dependency_SUFFIX magic_INCLUDE_DIR, gnurx_SOURCE_FILES
Cache options SCREAMING_SNAKE_CASE BUILD_MAGICXX_TESTS, INSTALL_MAGICXX
Exported/public SCREAMING_SNAKE_CASE MAGICXX_FOUND, MAGICXX_VERSION
Local (in functions) snake_case source_files, target_name

Functions and Macros

Use snake_case for custom functions and macros:

function(configure_magicxx_target target_name alias_name library_type)
# ...
endfunction()
macro(add_magicxx_test test_name)
# ...
endmacro()

Targets

Target Type Internal Name Exported Alias
Shared library magicxx Recognition::Magicxx
Static library magicxx_static Recognition::MagicxxStatic
Test executable magicxx_tests N/A
Example executable magicxx_examples N/A

Files

File Type Convention Example
Module files snake_case.cmake package_config.cmake
Config templates PascalCaseConfig.cmake.in MagicxxConfig.cmake.in
Toolchain files platform-arch-compiler.cmake linux-x86_64-clang.cmake

Formatting

Indentation

Use 4 spaces for indentation (no tabs):

if(BUILD_MAGICXX_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

Line Length

Prefer lines under 100 characters. Break long commands across multiple lines:

target_compile_definitions(${target_name}
PRIVATE
HAVE_CONFIG_H
HAVE_STDINT_H
MAGIC_DEFAULT_DATABASE_FILE="${magicxx_INSTALLED_DEFAULT_DATABASE_FILE}"
)

Argument Alignment

Align arguments vertically when breaking across lines:

set(magicxx_HEADER_FILES
${magicxx_INCLUDE_DIR}/magic.hpp
${magicxx_INCLUDE_DIR}/magic_exception.hpp
${magicxx_INCLUDE_DIR}/percentage.hpp
${magicxx_INCLUDE_DIR}/progress_tracker.hpp
${magicxx_INCLUDE_DIR}/utility.hpp
)

Parentheses

No space before opening parenthesis, no space after:

# Good
if(BUILD_MAGICXX_TESTS)
set(variable value)
function(my_function arg1 arg2)
# Bad
if ( BUILD_MAGICXX_TESTS )
set (variable value)
function( my_function arg1 arg2 )

Control Flow Statements

Use empty parentheses for endif(), endfunction(), endforeach(), etc.:

# Good
if(BUILD_MAGICXX_TESTS)
enable_testing()
endif()
function(configure_target name)
# ...
endfunction()
# Bad - Legacy style with repeated condition
if(BUILD_MAGICXX_TESTS)
enable_testing()
endif(BUILD_MAGICXX_TESTS)

Commands and Functions

Command Case

Use lowercase for all CMake commands:

# Good
cmake_minimum_required(VERSION 3.31.6 FATAL_ERROR)
project(magicxx VERSION 9.1.1)
add_library(magicxx SHARED)
target_link_libraries(target PRIVATE dependency)
# Bad
CMAKE_MINIMUM_REQUIRED(VERSION 3.31.6)
PROJECT(magicxx)
ADD_LIBRARY(magicxx SHARED)

Keyword Case

Use UPPERCASE for CMake keywords:

# Good
target_sources(${target_name}
PUBLIC
FILE_SET ${file_set_name}
TYPE HEADERS
PRIVATE
${source_files}
)
# Bad
target_sources(${target_name}
public
file_set ${file_set_name}
)

Target Configuration

Target Properties

Use target_* commands instead of global commands:

# Good - Target-based (Modern CMake)
target_compile_features(magicxx PUBLIC cxx_std_23)
target_include_directories(magicxx PUBLIC ${include_dir})
target_compile_definitions(magicxx PRIVATE HAVE_CONFIG_H)
# Bad - Directory-based (Legacy CMake)
set(CMAKE_CXX_STANDARD 23)
include_directories(${include_dir})
add_definitions(-DHAVE_CONFIG_H)

Generator Expressions

Use generator expressions for build/install interface differences:

target_include_directories(magicxx
PUBLIC
$<BUILD_INTERFACE:${magicxx_INCLUDE_DIR}>
$<INSTALL_INTERFACE:${magicxx_INSTALL_INCLUDE_DIR}>
)

Variable Usage

Setting Variables

Use descriptive names and group related variables:

# Directory variables
set(magicxx_INCLUDE_DIR
${magicxx_SOURCE_DIR}/include/magicxx
)
set(magicxx_SOURCES_DIR
${magicxx_SOURCE_DIR}/sources
)

Quoting

Quote variable references that may contain spaces:

# Good
set(path "${CMAKE_CURRENT_SOURCE_DIR}/path with spaces")
message(STATUS "Path: ${path}")
# Required for paths, optional for simple values
target_compile_definitions(target PRIVATE
DATABASE_PATH="${database_path}"
)

Lists

Define lists with one item per line for readability:

set(source_files
${src_dir}/file1.cpp
${src_dir}/file2.cpp
${src_dir}/file3.cpp
)

Options

Defining Options

Use option() with clear descriptions:

option(BUILD_MAGICXX_TESTS
"Build the unit tests"
OFF
)
option(BUILD_MAGICXX_SHARED_LIB
"Build Recognition::Magicxx shared library"
ON
)

Feature Summary

Use FeatureSummary for option reporting:

include(FeatureSummary)
add_feature_info("Unit Tests" BUILD_MAGICXX_TESTS
"Build unit tests using GoogleTest"
)
feature_summary(
WHAT ENABLED_FEATURES DISABLED_FEATURES
DESCRIPTION "Build configuration:"
)

Comments

Section Headers

Use section headers to organize CMake files:

# -----------------------------------------------------------------------------
# Build shared library
# -----------------------------------------------------------------------------
if(BUILD_MAGICXX_SHARED_LIB)
configure_magicxx_target(magicxx Magicxx SHARED)
endif()

File Headers

Include SPDX license headers:

# SPDX-FileCopyrightText: Copyright (c) 2022-2026 Oğuz Toraman <oguz.toraman@tutanota.com>
# SPDX-License-Identifier: LGPL-3.0-only

Inline Comments

Use inline comments sparingly for non-obvious logic:

# Windows requires additional regex library
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_dependencies(configure_file configure_gnurx)
endif()

Best Practices

1. Use Helper Functions

Create functions to reduce duplication:

function(configure_magicxx_target target_name alias_name library_type)
add_library(${target_name} ${library_type})
add_library(Recognition::${alias_name} ALIAS ${target_name})
# Common configuration...
endfunction()

2. Prefer FATAL_ERROR

Use FATAL_ERROR with cmake_minimum_required:

cmake_minimum_required(VERSION 3.31.6 FATAL_ERROR)

3. Use Presets

Prefer CMake presets over command-line options for reproducible builds:

cmake --workflow --preset linux-x86_64-clang-tests

4. Validate Requirements

Check for required tools and fail early:

find_program(CLANG_FORMAT clang-format)
if(NOT CLANG_FORMAT)
message(FATAL_ERROR "clang-format not found!")
endif()

5. Component-Based Installation

Use components for granular installation:

install(
TARGETS magicxx
EXPORT MagicxxTargets
LIBRARY DESTINATION ${lib_dir}
COMPONENT Library
)
install(
FILES ${header_files}
DESTINATION ${include_dir}
COMPONENT Development
)

6. Export Targets Properly

Use namespaced aliases for exported targets:

install(
EXPORT MagicxxTargets
NAMESPACE Recognition::
DESTINATION ${cmake_dir}
)

References