C — Build Systems & CMake
Before diving into CMake, a quick refresher on Make fundamentals. Make is the classic Unix build tool — it reads a Makefile, determines what needs rebuilding based on file timestamps, and executes the necessary recipes. Understanding Make helps you appreciate why CMake exists and what problems it solves.
A Makefile consists of targets, dependencies, and recipes. Each target depends on prerequisite files. When a prerequisite is newer than the target, Make runs the recipe to rebuild it.
| 1 | # Basic Makefile structure |
| 2 | CC = gcc |
| 3 | CFLAGS = -Wall -Wextra -std=c17 |
| 4 | |
| 5 | # Target: dependencies |
| 6 | # recipe (must start with tab) |
| 7 | all: server client |
| 8 | |
| 9 | server: server.o network.o utils.o |
| 10 | $(CC) $(CFLAGS) -o $@ $^ |
| 11 | |
| 12 | client: client.o network.o utils.o |
| 13 | $(CC) $(CFLAGS) -o $@ $^ |
| 14 | |
| 15 | # Pattern rule: compile .c to .o |
| 16 | %.o: %.c |
| 17 | $(CC) $(CFLAGS) -c $< -o $@ |
| 18 | |
| 19 | clean: |
| 20 | rm -f *.o server client |
| 21 | |
| 22 | .PHONY: all clean |
Make provides automatic variables that simplify recipes. $@ expands to the target name, $< is the first dependency, and $^ is all dependencies. Pattern rules with %: allow generic compilation rules.
For larger projects, recursive make invokes sub-makefiles in subdirectories. Non-recursive make keeps everything in one file but can become unwieldy. Both approaches have trade-offs that CMake attempts to resolve.
| 1 | # Recursive make: each subdir has its own Makefile |
| 2 | SUBDIRS = src/network src/utils src/core |
| 3 | |
| 4 | all: |
| 5 | @for dir in $(SUBDIRS); do \ |
| 6 | $(MAKE) -C $$dir; \ |
| 7 | done |
| 8 | |
| 9 | clean: |
| 10 | @for dir in $(SUBDIRS); do \ |
| 11 | $(MAKE) -C $$dir clean; \ |
| 12 | done |
| 13 | |
| 14 | # Non-recursive: collect all sources in one file |
| 15 | SRCS = $(wildcard src/network/*.c) \ |
| 16 | $(wildcard src/utils/*.c) \ |
| 17 | $(wildcard src/core/*.c) |
| 18 | OBJS = $(SRCS:.c=.o) |
warning
Makefiles are powerful but have significant limitations for modern cross-platform development. Writing platform-specific build rules, handling compiler differences, and managing complex dependency graphs becomes error-prone and repetitive. CMake is a meta-build system — it doesn't build your code directly. Instead, it generates the native build system for your platform: Makefiles on Linux, Ninja files for fast builds, Visual Studio solutions on Windows, or Xcode projects on macOS.
With a single CMakeLists.txt, you can target every major platform. CMake handles compiler detection, library discovery, cross-compilation, and installation. The build process becomes: write CMakeLists.txt, run cmake to generate build files, then use the generated build system to compile.
| Feature | Make | CMake | Autotools | Meson |
|---|---|---|---|---|
| Cross-platform | No | Yes | Partial | Yes |
| Learning curve | Low | Medium | High | Low |
| Dependency management | Manual | find_package / FetchContent | m4 macros | wrap files |
| Out-of-source builds | Manual | Default | Yes | Default |
| Generator output | N/A | Make, Ninja, VS, Xcode | Makefiles | Ninja only |
| IDE integration | Poor | Excellent | Poor | Good |
| Test integration | Manual | CTest built-in | make check | Meson test |
| Install rules | Manual | install() commands | make install | install() |
Modern CMake (3.x+) emphasizes a target-based approach. Instead of setting global variables, you define targets and attach properties to them. This encapsulation makes large projects more maintainable and reduces unintended side effects.
info
Every CMake project starts with a CMakeLists.txt in the root directory. This file is processed top-to-bottom by CMake during configuration. The key commands you need to know are listed below.
| 1 | cmake_minimum_required(VERSION 3.20) |
| 2 | |
| 3 | project(MyApp |
| 4 | VERSION 1.0.0 |
| 5 | DESCRIPTION "A sample C project" |
| 6 | LANGUAGES C |
| 7 | ) |
| 8 | |
| 9 | # Set C standard |
| 10 | set(CMAKE_C_STANDARD 17) |
| 11 | set(CMAKE_C_STANDARD_REQUIRED ON) |
| 12 | set(CMAKE_C_EXTENSIONS OFF) |
| 13 | |
| 14 | # Add an executable |
| 15 | add_executable(myapp |
| 16 | src/main.c |
| 17 | src/utils.c |
| 18 | src/config.c |
| 19 | ) |
| 20 | |
| 21 | # Add include directories to the target |
| 22 | target_include_directories(myapp PRIVATE |
| 23 | ${CMAKE_CURRENT_SOURCE_DIR}/include |
| 24 | ) |
| 25 | |
| 26 | # Add compile options |
| 27 | target_compile_options(myapp PRIVATE |
| 28 | -Wall -Wextra -Wpedantic |
| 29 | -O2 |
| 30 | ) |
| 31 | |
| 32 | # Link libraries |
| 33 | target_link_libraries(myapp PRIVATE m pthread) |
cmake_minimum_required() ensures the user has a compatible CMake version. project() declares the project name, version, and language. add_executable() creates a build target from source files. target_include_directories() and target_link_libraries() are the modern way to configure how a target is built and linked.
The visibility keywords PRIVATE, PUBLIC, and INTERFACE control how properties propagate. PRIVATE applies only to the target itself. PUBLIC applies to the target and anything that links against it. INTERFACE applies only to consumers (useful for header-only libraries).
| 1 | # Library types |
| 2 | add_library(mylib STATIC |
| 3 | src/mylib.c |
| 4 | src/mylib_internal.c |
| 5 | ) |
| 6 | |
| 7 | add_library(myshared SHARED |
| 8 | src/mylib.c |
| 9 | src/mylib_internal.c |
| 10 | ) |
| 11 | |
| 12 | # Header-only (INTERFACE) library |
| 13 | add_library(headerlib INTERFACE) |
| 14 | target_include_directories(headerlib INTERFACE |
| 15 | ${CMAKE_CURRENT_SOURCE_DIR}/include |
| 16 | ) |
| 17 | |
| 18 | # Set library version |
| 19 | set_target_properties(myshared PROPERTIES |
| 20 | VERSION 1.0.0 |
| 21 | SOVERSION 1 |
| 22 | ) |
| 23 | |
| 24 | # Alias for use in subdirectories or after install |
| 25 | add_library(MyProject::mylib ALIAS mylib) |
best practice
CMake variables control build behavior. The set() command assigns values, and option() creates boolean cache entries that users can toggle from the command line or GUI. CACHE variables persist across configuration runs and can be set via -D flags.
| 1 | # Basic variable assignment |
| 2 | set(SERVER_PORT 8080) |
| 3 | set(LOG_LEVEL "DEBUG") |
| 4 | |
| 5 | # CACHE variables (persistent, user-configurable) |
| 6 | set(MAX_CONNECTIONS 100 CACHE STRING "Maximum concurrent connections") |
| 7 | set(ENABLE_SSL ON CACHE BOOL "Enable SSL/TLS support") |
| 8 | set(DATA_DIR "/var/data" CACHE PATH "Data storage directory") |
| 9 | |
| 10 | # Option: boolean toggle |
| 11 | option(ENABLE_TESTS "Build test suite" ON) |
| 12 | option(ENABLE_BENCHMARKS "Build benchmarks" OFF) |
| 13 | option(USE_SYSTEM_DEPS "Use system-installed dependencies" OFF) |
| 14 | |
| 15 | # Environment variables |
| 16 | message(STATUS "Home: $ENV{HOME}") |
| 17 | message(STATUS "CC: $ENV{CC}") |
| 18 | |
| 19 | # Conditional logic |
| 20 | if(ENABLE_SSL) |
| 21 | find_package(OpenSSL REQUIRED) |
| 22 | target_compile_definitions(myapp PRIVATE ENABLE_SSL) |
| 23 | target_link_libraries(myapp PRIVATE OpenSSL::SSL) |
| 24 | endif() |
| 25 | |
| 26 | # Generator expressions (evaluated at build time, not configure time) |
| 27 | target_compile_definitions(myapp PRIVATE |
| 28 | BUILD_TYPE=${CMAKE_BUILD_TYPE} |
| 29 | VERSION_MAJOR=${PROJECT_VERSION_MAJOR} |
| 30 | ) |
Generator expressions are powerful but syntactically awkward. They use the dollar-brace syntax and are evaluated during build generation rather than during configuration. They are essential for conditional properties in modern CMake.
| 1 | # Common built-in variables |
| 2 | message(STATUS "Source dir: ${CMAKE_CURRENT_SOURCE_DIR}") |
| 3 | message(STATUS "Build dir: ${CMAKE_CURRENT_BINARY_DIR}") |
| 4 | message(STATUS "Compiler: ${CMAKE_C_COMPILER_ID}") |
| 5 | message(STATUS "System: ${CMAKE_SYSTEM_NAME}") |
| 6 | message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") |
| 7 | |
| 8 | # Platform detection |
| 9 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux") |
| 10 | target_compile_definitions(myapp PRIVATE PLATFORM_LINUX) |
| 11 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") |
| 12 | target_compile_definitions(myapp PRIVATE PLATFORM_MACOS) |
| 13 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") |
| 14 | target_compile_definitions(myapp PRIVATE PLATFORM_WINDOWS) |
| 15 | endif() |
| 16 | |
| 17 | # Compiler detection |
| 18 | if(CMAKE_C_COMPILER_ID STREQUAL "GNU") |
| 19 | target_compile_options(myapp PRIVATE -Wall -Wextra) |
| 20 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") |
| 21 | target_compile_options(myapp PRIVATE -Wall -Wextra -Werror) |
| 22 | elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") |
| 23 | target_compile_options(myapp PRIVATE /W4) |
| 24 | endif() |
CMake uses an out-of-source build model. You create a separate build directory and run CMake from there. This keeps build artifacts separate from source code, making it easy to clean up or maintain multiple build configurations.
| 1 | # Standard build workflow |
| 2 | mkdir build && cd build |
| 3 | cmake .. # Configure (generate build system) |
| 4 | cmake --build . # Build (compile all targets) |
| 5 | cmake --install . # Install (copy files to prefix) |
| 6 | |
| 7 | # Specify build type (single-config generators like Make) |
| 8 | cmake -DCMAKE_BUILD_TYPE=Release .. |
| 9 | cmake -DCMAKE_BUILD_TYPE=Debug .. |
| 10 | |
| 11 | # Specify generator |
| 12 | cmake -G Ninja .. # Use Ninja instead of Make |
| 13 | cmake -G "Unix Makefiles" .. |
| 14 | |
| 15 | # Specify install prefix |
| 16 | cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. |
| 17 | |
| 18 | # Build specific target |
| 19 | cmake --build . --target myapp |
| 20 | |
| 21 | # Clean build |
| 22 | rm -rf build && mkdir build && cd build |
| 23 | cmake -DCMAKE_BUILD_TYPE=Release .. 2>&1 | tee cmake.log |
| 24 | cmake --build . -- -j$(nproc) |
Build types control optimization and debug information. Debug includes symbols and no optimization. Release enables full optimization without debug symbols. RelWithDebInfo is a middle ground: optimized with debug symbols, useful for profiling. MinSizeRel optimizes for binary size.
| Build Type | Optimization | Debug Symbols | Typical Use |
|---|---|---|---|
| Debug | -O0 | Yes (-g) | Development, debugging |
| Release | -O3 | No | Production deployment |
| RelWithDebInfo | -O2 | Yes (-g) | Profiling, testing |
| MinSizeRel | -Os | No | Embedded, size-constrained |
| 1 | # Multi-config generators (Visual Studio, Xcode, Ninja Multi-Config) |
| 2 | # Build type is specified at build time, not configure time |
| 3 | cmake -G "Ninja Multi-Config" .. |
| 4 | cmake --build . --config Release |
| 5 | cmake --build . --config Debug |
| 6 | |
| 7 | # Check compilation database (useful for IDEs and tools) |
| 8 | cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. |
| 9 | # Produces compile_commands.json for clangd, etc. |
| 10 | |
| 11 | # Run tests |
| 12 | cmake --build . --target test |
| 13 | # Or directly: |
| 14 | ctest --test-dir build --output-on-failure |
Real projects have multiple directories with separate concerns. CMake handles this with add_subdirectory(), which processes a CMakeLists.txt in a subdirectory. Targets defined in subdirectories are automatically visible to the parent.
| 1 | # Root CMakeLists.txt |
| 2 | cmake_minimum_required(VERSION 3.20) |
| 3 | project(Server VERSION 2.1.0 LANGUAGES C) |
| 4 | |
| 5 | set(CMAKE_C_STANDARD 17) |
| 6 | set(CMAKE_C_STANDARD_REQUIRED ON) |
| 7 | |
| 8 | # Project-wide options |
| 9 | option(ENABLE_TESTS "Build tests" ON) |
| 10 | |
| 11 | # Subdirectories |
| 12 | add_subdirectory(src/core) |
| 13 | add_subdirectory(src/network) |
| 14 | add_subdirectory(src/utils) |
| 15 | |
| 16 | # Main executable |
| 17 | add_executable(server src/main.c) |
| 18 | target_link_libraries(server PRIVATE |
| 19 | core |
| 20 | network |
| 21 | utils |
| 22 | ) |
| 23 | |
| 24 | if(ENABLE_TESTS) |
| 25 | enable_testing() |
| 26 | add_subdirectory(tests) |
| 27 | endif() |
| 1 | # src/core/CMakeLists.txt |
| 2 | add_library(core |
| 3 | connection.c |
| 4 | session.c |
| 5 | config.c |
| 6 | ) |
| 7 | |
| 8 | target_include_directories(core PUBLIC |
| 9 | ${CMAKE_CURRENT_SOURCE_DIR}/include |
| 10 | ) |
| 11 | |
| 12 | target_link_libraries(core PRIVATE utils) |
When core exposes its headers as PUBLIC, anything that links against core automatically gets the include path. This is the power of target-based CMake: dependencies and their transitive properties propagate automatically.
| 1 | # INTERFACE library for header-only code |
| 2 | add_library(headers INTERFACE) |
| 3 | target_include_directories(headers INTERFACE |
| 4 | ${CMAKE_CURRENT_SOURCE_DIR}/include |
| 5 | ) |
| 6 | |
| 7 | # Usage: just link against headers |
| 8 | target_link_libraries(myapp PRIVATE headers) |
| 9 | |
| 10 | # Finding external packages |
| 11 | find_package(Threads REQUIRED) |
| 12 | find_package(CURL 7.50.0 REQUIRED) |
| 13 | find_package(PkgConfig REQUIRED) |
| 14 | pkg_check_modules(JSON_C REQUIRED json-c) |
| 15 | |
| 16 | target_link_libraries(myapp PRIVATE |
| 17 | Threads::Threads |
| 18 | CURL::libcurl |
| 19 | ${JSON_C_LIBRARIES} |
| 20 | ) |
| 21 | target_include_directories(myapp PRIVATE |
| 22 | ${JSON_C_INCLUDE_DIRS} |
| 23 | ) |
note
| 1 | # FetchContent for downloading dependencies at configure time |
| 2 | include(FetchContent) |
| 3 | |
| 4 | FetchContent_Declare( |
| 5 | unity |
| 6 | GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git |
| 7 | GIT_TAG v2.5.2 |
| 8 | ) |
| 9 | |
| 10 | FetchContent_MakeAvailable(unity) |
| 11 | |
| 12 | # Now you can link against unity |
| 13 | target_link_libraries(tests PRIVATE unity) |
Here are complete, production-ready CMake configurations for common project structures. Each example demonstrates best practices and idiomatic CMake.
Example 1: Simple Single-File Project
| 1 | cmake_minimum_required(VERSION 3.20) |
| 2 | project(hello VERSION 1.0.0 LANGUAGES C) |
| 3 | |
| 4 | set(CMAKE_C_STANDARD 17) |
| 5 | set(CMAKE_C_STANDARD_REQUIRED ON) |
| 6 | |
| 7 | add_executable(hello src/hello.c) |
| 8 | |
| 9 | # Install rules |
| 10 | install(TARGETS hello DESTINATION bin) |
Example 2: Project with Static Library
| 1 | cmake_minimum_required(VERSION 3.20) |
| 2 | project(parser VERSION 2.0.0 LANGUAGES C) |
| 3 | |
| 4 | set(CMAKE_C_STANDARD 17) |
| 5 | set(CMAKE_C_STANDARD_REQUIRED ON) |
| 6 | |
| 7 | # Shared library |
| 8 | add_library(parser SHARED |
| 9 | src/parser.c |
| 10 | src/tokenizer.c |
| 11 | src/ast.c |
| 12 | ) |
| 13 | |
| 14 | target_include_directories(parser PUBLIC |
| 15 | ${CMAKE_CURRENT_SOURCE_DIR}/include |
| 16 | ) |
| 17 | |
| 18 | target_compile_options(parser PRIVATE -Wall -Wextra) |
| 19 | |
| 20 | # Executable using the library |
| 21 | add_executable(parse_cli src/main.c) |
| 22 | target_link_libraries(parse_cli PRIVATE parser) |
| 23 | |
| 24 | # Install library and headers |
| 25 | install(TARGETS parser |
| 26 | LIBRARY DESTINATION lib |
| 27 | ARCHIVE DESTINATION lib |
| 28 | RUNTIME DESTINATION bin |
| 29 | ) |
| 30 | |
| 31 | install(DIRECTORY include/ DESTINATION include) |
Example 3: Project with Tests
| 1 | # tests/CMakeLists.txt |
| 2 | find_package(unity REQUIRED) |
| 3 | |
| 4 | # Collect test sources |
| 5 | file(GLOB TEST_SOURCES test_*.c) |
| 6 | |
| 7 | # Create one test executable per test file |
| 8 | foreach(test_source ${TEST_SOURCES}) |
| 9 | get_filename_component(test_name ${test_source} NAME_WE) |
| 10 | |
| 11 | add_executable(${test_name} ${test_source}) |
| 12 | target_link_libraries(${test_name} PRIVATE parser unity) |
| 13 | |
| 14 | add_test( |
| 15 | NAME ${test_name} |
| 16 | COMMAND ${test_name} |
| 17 | ) |
| 18 | endforeach() |
| 19 | |
| 20 | # Custom test with arguments |
| 21 | add_test( |
| 22 | NAME integration_tests |
| 23 | COMMAND parse_cli ${CMAKE_CURRENT_SOURCE_DIR}/test_data/input.json |
| 24 | ) |
| 25 | set_tests_properties(integration_tests PROPERTIES |
| 26 | PASS_REGULAR_EXPRESSION "OK" |
| 27 | TIMEOUT 30 |
| 28 | ) |
Example 4: Project with Precompiled Headers
| 1 | # Precompiled headers speed up compilation |
| 2 | target_precompile_headers(myapp PRIVATE |
| 3 | src/pch.h |
| 4 | ) |
| 5 | |
| 6 | # Or using a target to export PCH to dependents |
| 7 | add_library(pch INTERFACE) |
| 8 | target_precompile_headers(pch INTERFACE |
| 9 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pch.h |
| 10 | ) |
| 11 | target_link_libraries(mylib PUBLIC pch) |
Example 5: Custom CMake Module
| 1 | # cmake/FindMyLib.cmake |
| 2 | # Custom find module for a library not shipped with CMake |
| 3 | |
| 4 | find_path(MYLIB_INCLUDE_DIR |
| 5 | NAMES mylib.h |
| 6 | PATHS /usr/local/include /usr/include |
| 7 | ) |
| 8 | |
| 9 | find_library(MYLIB_LIBRARY |
| 10 | NAMES mylib |
| 11 | PATHS /usr/local/lib /usr/lib |
| 12 | ) |
| 13 | |
| 14 | include(FindPackageHandleStandardArgs) |
| 15 | find_package_handle_standard_args(MyLib |
| 16 | REQUIRED_VARS MYLIB_LIBRARY MYLIB_INCLUDE_DIR |
| 17 | ) |
| 18 | |
| 19 | if(MyLib_FOUND AND NOT TARGET MyLib::MyLib) |
| 20 | add_library(MyLib::MyLib UNKNOWN IMPORTED) |
| 21 | set_target_properties(MyLib::MyLib PROPERTIES |
| 22 | IMPORTED_LOCATION "${MYLIB_LIBRARY}" |
| 23 | INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}" |
| 24 | ) |
| 25 | endif() |
| 26 | |
| 27 | mark_as_advanced(MYLIB_INCLUDE_DIR MYLIB_LIBRARY) |
pro tip
CMake includes CTest, a built-in testing driver. After configuring your project, run ctest from the build directory to execute all registered tests. CTest supports parallel execution, output capture, and integration with CDash for continuous testing dashboards.
| 1 | # Enable testing in the project |
| 2 | enable_testing() |
| 3 | |
| 4 | # Simple test |
| 5 | add_test(NAME unit_tests COMMAND test_runner) |
| 6 | |
| 7 | # Test with arguments |
| 8 | add_test(NAME parse_test |
| 9 | COMMAND parse_cli --test --file ${CMAKE_CURRENT_SOURCE_DIR}/test.json |
| 10 | ) |
| 11 | |
| 12 | # Set test properties |
| 13 | set_tests_properties(parse_test PROPERTIES |
| 14 | TIMEOUT 10 |
| 15 | PASS_REGULAR_EXPRESSION "All tests passed" |
| 16 | FAIL_REGULAR_EXPRESSION "FAIL" |
| 17 | ENVIRONMENT "TESTING=1;LOG_LEVEL=debug" |
| 18 | ) |
| 19 | |
| 20 | # Test with fixtures (CMake 3.7+) |
| 21 | set_tests_properties(parse_test PROPERTIES |
| 22 | FIXTURES_SETUP test_data |
| 23 | ) |
| 24 | set_tests_properties(cleanup_test PROPERTIES |
| 25 | FIXTURES_CLEANUP test_data |
| 26 | ) |
| 27 | |
| 28 | # Run tests |
| 29 | # ctest --output-on-failure |
| 30 | # ctest -N (list tests without running) |
| 31 | # ctest -R "parse" (run matching tests) |
| 32 | # ctest --parallel 4 (run 4 tests in parallel) |
CMake provides install() commands to specify where files should be placed during installation. You can also generate packaging configurations for CPack to create .deb, .rpm, .dmg, or .msi packages.
| 1 | # Install executable |
| 2 | install(TARGETS myapp |
| 3 | RUNTIME DESTINATION bin |
| 4 | ) |
| 5 | |
| 6 | # Install library |
| 7 | install(TARGETS mylib |
| 8 | LIBRARY DESTINATION lib |
| 9 | ARCHIVE DESTINATION lib |
| 10 | PUBLIC_HEADER DESTINATION include/mylib |
| 11 | ) |
| 12 | |
| 13 | # Install headers |
| 14 | install(DIRECTORY include/mylib/ |
| 15 | DESTINATION include/mylib |
| 16 | FILES_MATCHING PATTERN "*.h" |
| 17 | ) |
| 18 | |
| 19 | # Install config files |
| 20 | install(FILES mylib.conf |
| 21 | DESTINATION etc/mylib |
| 22 | ) |
| 23 | |
| 24 | # Install with permissions |
| 25 | install(PROGRAMS scripts/setup.sh |
| 26 | DESTINATION bin |
| 27 | PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE |
| 28 | GROUP_READ GROUP_EXECUTE |
| 29 | ) |
| 30 | |
| 31 | # Generate package config for downstream CMake projects |
| 32 | install(EXPORT mylibTargets |
| 33 | FILE mylibTargets.cmake |
| 34 | NAMESPACE MyLib:: |
| 35 | DESTINATION lib/cmake/mylib |
| 36 | ) |
| 37 | |
| 38 | include(CMakePackageConfigHelpers) |
| 39 | write_basic_package_version_file( |
| 40 | ${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake |
| 41 | VERSION ${PROJECT_VERSION} |
| 42 | COMPATIBILITY SameMajorVersion |
| 43 | ) |