Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Cross-Platform Build Orchestration Using CMake

Tech 1

CMake operates as a high-level build system generator that abstracts compiler-specific configurations. It enables development teams working across different languages or compiler toolchains to produce unified executables and shared libraries through a single declarative workflow. The core configuration relies entirely on parsing CMakeLists.txt scripts.

Initial Setup and Compilation Workflow

Begin by creating a source file named app_entry.cpp:

#include <iostream>

int main(int argc, char** argv) {
    std::cout << "Initialization sequence complete." << std::endl;
    return 0;
}

Next, define the build rules in CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
project(DemoWorkspace CXX)

set(MAIN_SOURCES app_entry.cpp)
message(STATUS "Build directory: ${DemoWorkspace_BINARY_DIR}")
message(STATUS "Source root: ${DemoWorkspace_SOURCE_DIR}")

add_executable(runtime_demo ${MAIN_SOURCES})

Running cmake . generates native build scripts (e.g., Makefile, cache files, and temporary directories). Executing make compiles the source, producing the runtime_demo binary. Running ./runtime_demo outputs the expected message to the terminal.

Core Directive Reference

project() Configuration This directive establishes the workspace name and default languages. project(DemoWorkspace CXX) restricts compilation to C++. It implicitly defines ${DemoWorkspace_BINARY_DIR} and ${DemoWorkspace_SOURCE_DIR}. To avoid naming conflicts during refactoring, use the generic ${PROJECT_BINARY_DIR} and ${PROJECT_SOURCE_DIR} variables instead, as they remain constant regardless of the project title.

Variable Assignment with set() Explicitly binds values to identifiers. set(MAIN_SOURCES app_entry.cpp) creates a list variable. Multiple files can be appended: set(MAIN_SOURCES app_entry.cpp helper.cpp utils.cpp).

Terminal Output via message() Emits logs during configuration. Status levels include:

  • STATUS: Informational prefix.
  • WARNING: Non-fatal alerts.
  • FATAL_ERROR: Halts configuration immediately.
  • SEND_ERROR: Generates an error but allows configuration to proceeed to the next phase.

Executable Generation with add_executable() Compiles specified sources into a runnable binary. add_executable(runtime_demo ${MAIN_SOURCES}) links the file list to the output target. Alternatively, add_executable(runtime_demo app_entry.cpp) works inline. The project name and executable name are entirely independent.

Syntax Conventions Variables expand via ${VAR} syntax, except within if() conditions where raw identifiers are used. Arguments use parentheses and are separated by spaces or semicolons. Directive names are case-insensitive, while variable names remain case-sensitive. Uppercase directives are standard practice. If filenames contain whitespace, wrap them in quotes: set(SRC "my source.cpp"). File extensions are typically resolved automatically, but explicit extensions prevent ambiguity.

Build Directory Isolation

Running configuration directly inside the source directory (in-source build) pollutes the workspace with generated artifacts. Out-of-source builds isolate compilation outputs. Create a dedicated folder: mkdir build && cd build. Execute cmake /path/to/source. All intermediate files populate the build directory. After generation, make compiles the project cleanly without altering the source tree.

Hierarchical Source Management

Complex projects separate source trees in to nested directories:

├── CMakeLists.txt
├── build/
└── modules/
    ├── CMakeLists.txt
    └── entry.cpp

Root CMakeLists.txt:

project(NestedWorkspace)
add_subdirectory(modules output_bin)

add_subdirectory() injects child directories into the build tree and maps their outputt to a specified binary folder (output_bin). Without the second argument, outputs default to build/<child_dir>.

Output routing can be controlled globally:

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)

Place these definitions in the CMakeLists.txt where path overrides are required.

Child directory (modules/CMakeLists.txt):

add_executable(module_runner entry.cpp)

Artifact Installation Rules

CMake handles deployment via install() directives. The default prefix is /usr/local, adjustable via CMAKE_INSTALL_PREFIX or runtime flags like DESTDIR=/tmp/staging.

Structure for deployment:

├── CMakeLists.txt
├── LICENSE.txt
├── docs/
└── modules/

Deploy documentation:

install(FILES LICENSE.txt DESTINATION share/doc/myproject)

Relative paths automatically resolve against ${CMAKE_INSTALL_PREFIX}.

Deploy executable scripts:

install(PROGRAMS deploy_script.sh DESTINATION bin)

This places the script into the system executable path.

Directory installation requires a trailing slash to copy contents rather than the folder itself:

install(DIRECTORY docs/ DESTINATION share/doc/myproject)

Execute make install after configuration to apply rules.

Shared & Static Library Creation

Libraries expose functions for reuse. Static archives (.a) embed directly into executables, while dynamic objects (.so) link at runtime.

Directory layout:

├── CMakeLists.txt
├── build/
└── lib_core/
    ├── CMakeLists.txt
    ├── core_ops.cpp
    └── core_ops.h

Root configuration adds the library module. Inside lib_core/CMakeLists.txt:

set(CORE_SOURCES core_ops.cpp)
add_library(core_ops_lib SHARED ${CORE_SOURCES})

The SHARED flag generates .so files. CMake automatically prefixes the target with lib.

Simultaneous Static and Dynamic Generation Defining two targets with identical base names causes overwrites. Resolve conflicts using set_target_properties():

set(CORE_SOURCES core_ops.cpp)
add_library(core_ops_static STATIC ${CORE_SOURCES})
set_target_properties(core_ops_static PROPERTIES OUTPUT_NAME "core_ops")
set_target_properties(core_ops_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

add_library(core_ops_shared SHARED ${CORE_SOURCES})
set_target_properties(core_ops_shared PROPERTIES OUTPUT_NAME "core_ops")
set_target_properties(core_ops_shared PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(core_ops_shared PROPERTIES VERSION 2.1 SOVERSION 2)

CLEAN_DIRECT_OUTPUT prevents CMake from deleting one archive when generating the other. VERSION and SOVERSION manage library ABI tracking.

Library and Header Deployment

install(FILES core_ops.h DESTINATION include/myproject)
install(TARGETS core_ops_shared core_ops_static
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)

Run cmake -DCMAKE_INSTALL_PREFIX=/usr .. followed by make install.

External Dependency Integration

To consume previously installed libraries, configure the consumer workspace:

├── CMakeLists.txt
├── build/
└── client/
    ├── CMakeLists.txt
    └── main.cpp

client/main.cpp:

#include <myproject/core_ops.h>

int main() {
    execute_core_logic();
    return 0;
}

client/CMakeLists.txt:

add_executable(client_app main.cpp)

Initial compilation fails due to missing headers and unresolved symbols.

Resolve header paths in the client configuration:

target_include_directories(client_app PRIVATE /usr/include/myproject)

Link the compiled binary against the archive:

target_link_libraries(client_app PRIVATE core_ops)

Insert linking commands after the executable definition.

Alternatively, configure global search paths via environment variables before invoking CMake:

export CMAKE_INCLUDE_PATH=/usr/include/myproject
export CMAKE_LIBRARY_PATH=/usr/lib

This approach avoids hardcoding absolute paths in build scripts.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.