Skip to main content

Creating a New Component

This tutorial walks through creating an Ideal component with a simple C library. By the end, you will have a project with strict compilation, sanitizer support, code coverage, and full installation rules — all driven by Ideal CMake Modules.

Prerequisites

  • ideal-cmake-modules installed
  • C23 compiler (GCC 14+ or Clang 18+)
  • CMake 3.21+

Scaffold the Project

Create the project directory and initialize the repository:

mkdir greeting && cd greeting
git init

Generate the project files:

make -C /path/to/ideal-cmake-modules init NAME=greeting DESC="A greeting library"

This creates a CMakeLists.txt, Makefile, CMakePresets.json, and other standard files. See Project Scaffolding for details on what is generated.

The scaffolded CMakeLists.txt looks like this:

cmake_minimum_required(VERSION 3.21...4.2)

project(
greeting
LANGUAGES C
DESCRIPTION "A greeting library"
HOMEPAGE_URL "https://git.sr.ht/~cpradog/greeting/"
)

set(APK_MAINTAINER "...")
find_package(IdealCmakeModules REQUIRED)

The find_package call loads every Ideal module — compiler settings, sanitizers, coverage, installation rules, versioning, and more. All of these are available immediately, but none take effect until you apply them to a target. The sections below walk through building up the rest of this file step by step.

Add a Library

Create the directory structure and source files for a small library.

include/greeting/greeting.h:

#ifndef GREETING_H
#define GREETING_H

/// Returns a greeting message for the given name.
/// The returned string is statically allocated.
const char *greeting_hello(const char *name);

#endif

src/greeting.c:

#include <greeting/greeting.h>
#include <stdio.h>

static char buffer[256];

const char *greeting_hello(const char *name) {
snprintf(buffer, sizeof(buffer), "Hello, %s!", name);
return buffer;
}

Define the Library Target

Add the library target to CMakeLists.txt, after the find_package line:

add_library(greeting src/greeting.c)

This tells CMake to compile src/greeting.c into a library named greeting. By default CMake creates a static library; the type can be overridden at build time with BUILD_SHARED_LIBS.

Expose the Public Headers

The library's public headers live in include/, and consumers need to find them when they link against greeting. Add the include directory to the target:

target_include_directories(greeting PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)

The $<BUILD_INTERFACE:...> generator expression ensures this path is only used when building the project — not when the library is consumed after installation. The install_library function (added later) reads this expression to determine which headers to install, so the path also serves as the source of truth for the public API surface.

Enable Strict C23 Compilation

Apply the compiler module's strict settings to the target:

target_enable_strict_c23(greeting)

This single call configures the target with:

  • C23 standard — required, not optional.
  • Strict warning flags-Wall, -Wextra, -Wpedantic, -Werror, plus additional checks for type conversions, null dereferences, format string vulnerabilities, and more. Compiler-specific flags are added automatically for GCC and Clang.
  • Link-time optimization (LTO) — enabled when the compiler supports it.
  • Hidden symbol visibility — for library targets, symbols are hidden by default. Only symbols explicitly exported in your headers are visible to consumers, which reduces binary size and avoids accidental ABI exposure.

See Compiler for the full list of warning flags and the VISIBILITY parameter.

Enable Sanitizers

Add runtime error detection:

target_enable_sanitizers(greeting)

This is a no-op by default — sanitizers are off unless explicitly activated via CMake options like -DSANITIZE_ADDRESS=ON or through the asan, tsan, and ubsan build presets. This makes it safe to call unconditionally: in a normal build it adds nothing, but when a developer wants to debug memory errors or undefined behavior, the instrumentation is ready.

See Sanitizers for details on each sanitizer and their mutual exclusivity rules.

Enable Code Coverage

Add coverage instrumentation:

target_enable_coverage(greeting)

Like sanitizers, this is a no-op unless ENABLE_COVERAGE=ON (or the coverage preset is selected). When active, it instruments the target for code coverage analysis and creates coverage and coverage-clean build targets for generating HTML reports. Both Clang and GCC are supported with their respective toolchains (llvm-cov / lcov).

See Coverage for the full workflow.

Install the Library

Add installation rules:

install_library(greeting)

This generates everything a consumer needs to use the library as a dependency:

  • The compiled library and public headers are installed to standard GNU directories (lib/, include/).
  • A CMake package config is generated (GreetingConfig.cmake), so consumers can use find_package(greeting) and link with greeting::greeting.
  • A pkg-config file is generated (greeting.pc) for build systems that use pkg-config instead of CMake.
  • Version compatibility uses SameMajorVersion — consumers that request version 1.x will accept any 1.y.z but not 2.0.

See Install for the NAME parameter and multi-target support.

The Complete CMakeLists.txt

Here is the full file with everything in place:

cmake_minimum_required(VERSION 3.21...4.2)

project(
greeting
LANGUAGES C
DESCRIPTION "A greeting library"
HOMEPAGE_URL "https://git.sr.ht/~cpradog/greeting/"
)

set(APK_MAINTAINER "...")
find_package(IdealCmakeModules REQUIRED)

add_library(greeting src/greeting.c)

target_include_directories(greeting PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)

target_enable_strict_c23(greeting)
target_enable_sanitizers(greeting)
target_enable_coverage(greeting)

install_library(greeting)

Every target_enable_* function is safe to call unconditionally — when the corresponding feature is disabled, the call is a no-op. This pattern keeps the build file simple: the same CMakeLists.txt works for a quick debug build and for a CI pipeline with sanitizers and coverage.

Build

make build

This compiles the library with strict C23 warnings and LTO. The configuration summary is printed at the end:

   greeting summary:

preset: release
prefix: /usr/local
version: 0.0.0-unknown

generator: Ninja
build type: Release
build docs: OFF
build tests: OFF
with coverage: OFF

sanitize undefined: OFF
sanitize address: OFF
sanitize memory: OFF
sanitize thread: OFF

The version is 0.0.0-unknown because there are no Git tags yet. Once you tag a release with v1.0.0, the version module will pick it up automatically.

Summary

You now have a component with:

  • Strict C23 compilation with comprehensive warnings, LTO, and hidden symbol visibility
  • Sanitizer and coverage support, activated on demand via presets
  • Installation rules for the library, headers, CMake package config, and pkg-config