C++ 크로스 플랫폼 빌드 완벽 가이드 | CMake 툴체인·CPack·ABI 안정성·프로덕션 패턴

C++ 크로스 플랫폼 빌드 완벽 가이드 | CMake 툴체인·CPack·ABI 안정성·프로덕션 패턴

이 글의 핵심

C++ 크로스 플랫폼 빌드 입니다. CMake 툴체인, CPack 패키징, ABI 안정성, 프로덕션 배포 전략을 실전 예제로 다룹니다. 크로스 플랫폼 C++ 프로젝트에서는 플랫폼별 경로·라이브러리·컴파일러·ABI가 달라 빌드 실패, 런타임 크래시, 패키징 불일치가 빈번합니다. 비유하면 "각 나라마다 전압·플러그·규격이 다른 것"처럼,.

들어가며: “팀원마다 OS가 다른데 빌드가 매번 깨져요"

"Windows에서 빌드한 DLL을 Linux 서버에서 로드하려다 크래시했어요”

크로스 플랫폼 C++ 프로젝트에서는 플랫폼별 경로·라이브러리·컴파일러·ABI가 달라 빌드 실패, 런타임 크래시, 패키징 불일치가 빈번합니다. 비유하면 “각 나라마다 전압·플러그·규격이 다른 것”처럼, OS별로 빌드·실행·배포 환경이 다르기 때문에 툴체인·조건부 컴파일·ABI 안정화가 필수입니다.

문제의 핵심:

  • Windows: \ 경로, LoadLibrary, ws2_32.dll, MSVC/MinGW ABI 불일치
  • Linux: / 경로, dlopen, pthread, GCC/Clang, 심볼 버전 관리
  • macOS: dlopen, pthread, Clang, 코드 서명·Xcode 규칙
  • 모바일: iOS/Android 각각 별도 툴체인·SDK·ABI

이 글에서 다루는 것:

  • 문제 시나리오: 실무에서 겪는 크로스 플랫폼 빌드 고통 8가지
  • 완전한 CMake 툴체인 파일: Linux·Windows·MinGW·iOS·Android
  • CPack 패키징: DEB·RPM·NSIS·DMG·ZIP
  • ABI 안정성: PIMPL·extern C·심볼 버전 관리
  • 자주 발생하는 에러와 해결법
  • 모범 사례프로덕션 패턴

요구 환경: C++17 이상, CMake 3.16+


실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 문제 시나리오: 크로스 플랫폼 빌드가 깨지는 순간
  2. 기본 개념: 플랫폼 감지·툴체인 흐름
  3. 완전한 CMake 툴체인 파일
  4. CPack 크로스 플랫폼 패키징
  5. ABI 안정성: PIMPL·extern C·심볼 버전
  6. 자주 발생하는 에러와 해결법
  7. 모범 사례
  8. 프로덕션 패턴
  9. 정리

1. 문제 시나리오: 크로스 플랫폼 빌드가 깨지는 순간

시나리오 1: 경로 구분자로 인한 파일 열기 실패

문제: Windows에서 path/to/file.txt 같은 경로로 파일을 열면 std::ifstream이 실패합니다. Windows는 \를 구분자로 쓰지만, Linux는 /를 씁니다. 하드코딩된 경로로는 한 플랫폼에서만 동작합니다.

해결: std::filesystem::path를 사용하면 OS가 자동으로 올바른 구분자를 사용합니다.

시나리오 2: Windows에서만 “undefined reference to pthread”

문제: Linux에서 -lpthread로 링크한 코드가 Windows에서 빌드됩니다. Windows에는 pthread가 없고, MSVC는 스레드를 기본 지원합니다. CMake find_package(Threads)로 플랫폼별 스레드 라이브러리를 자동 링크합니다.

시나리오 3: 동적 라이브러리 확장자 불일치

문제: 플러그인을 로드할 때 plugin.so로만 찾습니다. Windows에서는 plugin.dll, macOS에서는 plugin.dylib이 필요합니다.

해결: CMAKE_SHARED_LIBRARY_SUFFIX 또는 플랫폼별 매크로로 확장자를 분기합니다.

시나리오 4: 모바일 크로스 컴파일 실패

문제: 데스크톱용 CMake로 iOS·Android 앱을 빌드하려 합니다. arm64·armv7 아키텍처, iOS SDK 경로, Android NDK 경로가 설정되지 않아 빌드가 실패합니다.

해결: CMake 툴체인 파일로 iOS/Android 전용 컴파일러·SDK를 지정합니다.

시나리오 5: MSVC로 빌드한 DLL을 MinGW 앱에서 로드 시 크래시

문제: Windows에서 MSVC로 빌드한 DLL을 MinGW로 빌드한 앱에서 로드합니다. C++ ABI·표준 라이브러리(libstdc++ vs MSVCRT)가 달라 런타임 크래시가 발생합니다.

해결: C ABI로 경계를 만들거나, 같은 툴체인으로 빌드합니다. 플러그인·DLL은 extern "C"로 내보냅니다.

시나리오 6: 패키징 시 의존성 누락

문제: CPack으로 DEB를 만들었는데, 설치 후 실행 시 “libfoo.so.1: cannot open shared object file” 에러가 납니다. 런타임 의존성이 패키지에 포함되지 않았습니다.

해결: CPACK_INSTALL_CMAKE_PROJECTS, CPACK_DEBIAN_PACKAGE_DEPENDS 등으로 의존성을 명시하고, install(RUNTIME_DEPENDENCY_SET)으로 동적 라이브러리를 수집합니다.

시나리오 7: Linux에서 심볼 버전 충돌

문제: libstdc++를 업그레이드한 후, 구버전으로 빌드한 플러그인이 “version `GLIBCXX_3.4.30’ not found”로 로드 실패합니다.

해결: 플러그인에 심볼 버전 스크립트를 적용해 필요한 심볼만 내보내고, 배포 시 최소 glibc/libstdc++ 버전을 명시합니다.

시나리오 8: CI에서 플랫폼별 빌드 매트릭스 실패

문제: 로컬 macOS에서는 빌드가 되는데, GitHub Actions의 windows-latest에서만 실패합니다. MSVC 경로·vcpkg triplet·코드 페이지 설정이 다릅니다.

해결: 툴체인 파일을 표준화하고, CI 스크립트에서 플랫폼별로 동일한 CMake 옵션을 사용합니다.


2. 기본 개념: 플랫폼 감지·툴체인 흐름

플랫폼 감지 매크로

// platform_detect.hpp — 플랫폼별 매크로
#if defined(_WIN32) || defined(_WIN64)
    #define PLATFORM_WINDOWS 1
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    #if TARGET_OS_IPHONE
        #define PLATFORM_IOS 1
    #else
        #define PLATFORM_MACOS 1
    #endif
#elif defined(__linux__)
    #if defined(__ANDROID__)
        #define PLATFORM_ANDROID 1
    #else
        #define PLATFORM_LINUX 1
    #endif
#else
    #define PLATFORM_UNKNOWN 1
#endif

CMake 플랫폼 변수

# CMake에서 플랫폼 감지
if(WIN32)
    message(STATUS "Building for Windows")
elseif(APPLE)
    message(STATUS "Building for macOS or iOS")
elseif(UNIX AND NOT APPLE)
    message(STATUS "Building for Linux")
endif()

message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
# x86_64, AMD64, ARM64, aarch64 등

크로스 플랫폼 빌드 흐름

flowchart TB
    subgraph source[소스 코드]
        S1[공통 C++ 코드]
        S2[플랫폼별 #ifdef]
        S3[추상화 레이어]
    end

    subgraph cmake[CMake]
        C1[CMakeLists.txt]
        C2[툴체인 파일]
        C3[플랫폼별 옵션]
    end

    subgraph targets[타겟 플랫폼]
        T1[Windows .exe/.dll]
        T2[Linux .so]
        T3[macOS .dylib]
        T4[iOS/Android]
    end

    S1 --> C1
    S2 --> C1
    S3 --> C1
    C1 --> C2
    C1 --> C3
    C2 --> T1
    C2 --> T2
    C2 --> T3
    C2 --> T4

3. 완전한 CMake 툴체인 파일

3.1 프로젝트 구조

project/
├── toolchains/
│   ├── linux-x64.cmake
│   ├── mingw64.cmake
│   ├── ios.toolchain.cmake
│   └── android-arm64.cmake
├── CMakeLists.txt
└── src/

3.2 Linux x64 툴체인 (크로스 컴파일용)

# toolchains/linux-x64.cmake
# 사용: cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchains/linux-x64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# 크로스 컴파일러 지정 (선택)
# set(CMAKE_C_COMPILER /usr/bin/x86_64-linux-gnu-gcc)
# set(CMAKE_CXX_COMPILER /usr/bin/x86_64-linux-gnu-g++)

# sysroot (크로스 컴파일 시)
# set(CMAKE_SYSROOT /path/to/sysroot)

# 빌드 머신 도구는 호스트 것 사용
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

3.3 MinGW64 툴체인 (Linux/macOS에서 Windows 빌드)

# toolchains/mingw64.cmake
# 사용: cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# MinGW 경로 (환경에 맞게 수정)
set(MINGW_PREFIX /usr/local/x86_64-w64-mingw32
    CACHE PATH "MinGW-w64 installation prefix")

set(CMAKE_C_COMPILER ${MINGW_PREFIX}/bin/x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER ${MINGW_PREFIX}/bin/x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER ${MINGW_PREFIX}/bin/x86_64-w64-mingw32-windres)

set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# 정적 링크 옵션 (선택)
# set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")

3.4 iOS 툴체인

# toolchains/ios.toolchain.cmake
# 사용: cmake -B build-ios -DCMAKE_TOOLCHAIN_FILE=toolchains/ios.toolchain.cmake
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_SYSROOT iphoneos CACHE STRING "iOS SDK")
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "iOS architectures")
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "Minimum iOS version")

# 코드 서명 (개발 빌드)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" CACHE STRING "")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" CACHE STRING "")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" CACHE STRING "")

# 빌드 타입
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "")
endif()

3.5 Android NDK 툴체인

# toolchains/android-arm64.cmake
# 사용: cmake -B build-android \
#   -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
#   -DANDROID_ABI=arm64-v8a \
#   -DANDROID_PLATFORM=android-21
# (Android NDK는 자체 툴체인 제공 — android.toolchain.cmake 사용)

# 커스텀 옵션 예시
set(ANDROID_ABI arm64-v8a CACHE STRING "")
set(ANDROID_PLATFORM android-21 CACHE STRING "")
set(ANDROID_STL c++_shared CACHE STRING "")

3.6 메인 CMakeLists.txt — 툴체인 통합

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(CrossPlatformApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)

# 플랫폼별 라이브러리
if(WIN32)
    set(PLATFORM_LIBS ws2_32)
elseif(UNIX AND NOT APPLE)
    find_package(Threads REQUIRED)
    find_library(DL_LIBRARY dl)
    set(PLATFORM_LIBS Threads::Threads ${DL_LIBRARY})
elseif(APPLE)
    find_package(Threads REQUIRED)
    set(PLATFORM_LIBS Threads::Threads)
endif()

add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE ${PLATFORM_LIBS})

# 플랫폼별 컴파일 정의
if(WIN32)
    target_compile_definitions(app PRIVATE PLATFORM_WINDOWS=1)
elseif(APPLE)
    target_compile_definitions(app PRIVATE PLATFORM_APPLE=1)
else()
    target_compile_definitions(app PRIVATE PLATFORM_LINUX=1)
endif()

# 공유 라이브러리 (플러그인)
add_library(plugin SHARED src/plugin.cpp)
target_link_libraries(plugin PRIVATE ${PLATFORM_LIBS})
set_target_properties(plugin PROPERTIES
    POSITION_INDEPENDENT_CODE ON
    WINDOWS_EXPORT_ALL_SYMBOLS OFF
)
if(NOT WIN32)
    target_compile_definitions(plugin PRIVATE PLUGIN_EXPORT=__attribute__\(\(visibility\(\"default\"\)\)\))
endif()

4. CPack 크로스 플랫폼 패키징

4.1 CPack 기본 설정

# CMakeLists.txt 하단에 추가
include(InstallRequiredSystemLibraries)

set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cross-platform C++ Application")
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")

# 설치 규칙
install(TARGETS app RUNTIME DESTINATION bin)
install(TARGETS plugin LIBRARY DESTINATION lib RUNTIME DESTINATION bin)

# 플랫폼별 제너레이터
if(WIN32)
    set(CPACK_GENERATOR "NSIS;ZIP")
    set(CPACK_NSIS_MODIFY_PATH ON)
    set(CPACK_NSIS_INSTALLER_MUI_ICON "${CMAKE_SOURCE_DIR}/icon.ico")
elseif(APPLE)
    set(CPACK_GENERATOR "DragNDrop;TGZ")
    set(CPACK_DMG_DS_STORE_SET_OWNER "${CMAKE_INSTALL_PREFIX}")
else()
    set(CPACK_GENERATOR "DEB;RPM;TGZ")
    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "[email protected]")
    set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.27)")
    set(CPACK_RPM_PACKAGE_LICENSE "MIT")
endif()

include(CPack)

4.2 DEB 패키지 상세 설정

# DEB 전용 옵션
set(CPACK_DEBIAN_PACKAGE_NAME "myapp")
set(CPACK_DEBIAN_PACKAGE_VERSION "1.0.0")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.27), libstdc++6 (>= 9)")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")

4.3 NSIS (Windows 인스톨러) 설정

set(CPACK_NSIS_PACKAGE_NAME "MyApp")
set(CPACK_NSIS_DISPLAY_NAME "My Application")
set(CPACK_NSIS_HELP_LINK "https://example.com/support")
set(CPACK_NSIS_URL_INFO_ABOUT "https://example.com")
set(CPACK_NSIS_CONTACT "[email protected]")
set(CPACK_NSIS_MODIFY_PATH ON)

4.4 런타임 의존성 수집 (CMake 3.21+)

# 동적 라이브러리 의존성 자동 수집
include(GNUInstallDirs)
install(TARGETS app plugin
    RUNTIME
        COMPONENT runtime
        DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY
        COMPONENT runtime
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(RUNTIME_DEPENDENCY_SET app_deps
    TARGETS app
    PRE_INCLUDE_REGEXES ".*"
    PRE_EXCLUDE_REGEXES ".*system.*"
    POST_INCLUDE_REGEXES ".*"
    POST_EXCLUDE_REGEXES ""
    DIRECTORIES ${CMAKE_SOURCE_DIR}/lib
)

4.5 CPack 빌드 명령

# 빌드 후 패키징
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cd build && cpack -G DEB   # 또는 NSIS, ZIP, TGZ 등

5. ABI 안정성: PIMPL·extern C·심볼 버전

5.1 PIMPL로 ABI 고정

PIMPL(Pointer to Implementation)은 공개 클래스가 구현체 포인터만 갖고, 구현체 정의는 .cpp에만 두어 공개 클래스 크기를 고정합니다. 내부 구현 변경 시에도 ABI가 유지됩니다.

// widget.h — 공개 헤더 (ABI 고정)
#pragma once

#include <memory>
#include <string>

class WidgetImpl;

class Widget {
public:
    Widget();
    ~Widget();
    Widget(const Widget&);
    Widget& operator=(const Widget&);
    Widget(Widget&&) noexcept = default;
    Widget& operator=(Widget&&) noexcept = default;

    void setTitle(const std::string& title);
    [[nodiscard]] std::string getTitle() const;

private:
    std::unique_ptr<WidgetImpl> pImpl_;  // 크기 고정: 8바이트
};
// widget.cpp — 구현 (내부 변경 가능)
#include "widget.h"
#include <unordered_map>

class WidgetImpl {
public:
    std::string title;
    std::unordered_map<std::string, size_t> cache;  // v2에서 추가해도 ABI 유지
};

Widget::Widget() : pImpl_(std::make_unique<WidgetImpl>()) {}
Widget::~Widget() = default;

Widget::Widget(const Widget& other)
    : pImpl_(std::make_unique<WidgetImpl>(*other.pImpl_)) {}

Widget& Widget::operator=(const Widget& other) {
    if (this != &other) *pImpl_ = *other.pImpl_;
    return *this;
}

void Widget::setTitle(const std::string& title) { pImpl_->title = title; }
std::string Widget::getTitle() const { return pImpl_->title; }

5.2 extern “C” 플러그인 API

C++ name mangling은 컴파일러마다 다릅니다. extern “C”로 내보내면 심볼 이름이 고정되어, 다른 컴파일러로 빌드한 바이너리와도 링크할 수 있습니다.

// plugin_api.h — C 인터페이스 (ABI 최대 안정)
#pragma once

#include <cstdint>
#include <cstddef>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct PluginHandle* PluginHandlePtr;

PluginHandlePtr plugin_create(const char* config);
void plugin_destroy(PluginHandlePtr handle);
int plugin_process(PluginHandlePtr handle, const void* input, size_t input_size,
                   void* output, size_t output_size);

#ifdef __cplusplus
}
#endif
// plugin_impl.cpp — C++ 래퍼가 C API 구현
#include "plugin_api.h"
#include <string>
#include <cstring>

struct PluginImpl {
    std::string config;
};

extern "C" {

#if defined(_WIN32)
    #define PLUGIN_EXPORT __declspec(dllexport)
#else
    #define PLUGIN_EXPORT __attribute__((visibility("default")))
#endif

PLUGIN_EXPORT PluginHandlePtr plugin_create(const char* config) {
    auto* p = new PluginImpl;
    if (config) p->config = config;
    return p;
}

PLUGIN_EXPORT void plugin_destroy(PluginHandlePtr handle) {
    delete static_cast<PluginImpl*>(handle);
}

PLUGIN_EXPORT int plugin_process(PluginHandlePtr handle,
    const void* input, size_t input_size, void* output, size_t output_size) {
    (void)handle;
    (void)input;
    (void)input_size;
    (void)output;
    (void)output_size;
    return 0;
}

}

5.3 Linux 심볼 버전 관리

플러그인·라이브러리에서 필요한 심볼만 내보내고, 버전을 부여해 ABI 호환성을 관리합니다.

# plugin.ver — 심볼 버전 스크립트
PLUGIN_1.0 {
  global:
    plugin_create;
    plugin_destroy;
    plugin_process;
  local: *;
};

PLUGIN_1.1 {
  global:
    plugin_init;
} PLUGIN_1.0;
# CMakeLists.txt — Linux에서 심볼 버전 적용
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    target_link_options(plugin PRIVATE
        "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/plugin.ver"
    )
endif()
# 직접 링크 시
g++ -shared -Wl,--version-script=plugin.ver -o libplugin.so plugin.cpp

5.4 ABI 안정성 비교

기법ABI 안정성사용 편의성적용 시점
PIMPL높음중간공개 C++ 클래스
extern “C”최고낮음플러그인·DLL 경계
심볼 버전중간낮음Linux .so 버전 관리

6. 자주 발생하는 에러와 해결법

에러 1: “fatal error: ‘unistd.h’ file not found” (Windows)

원인: unistd.h는 POSIX 전용 헤더입니다. Windows(MSVC)에는 없습니다.

해결:

// ❌ 잘못된 코드
#include <unistd.h>

// ✅ 올바른 코드
#if defined(_WIN32)
    #include <io.h>
    #include <process.h>
    #define close _close
#else
    #include <unistd.h>
#endif

에러 2: “undefined reference to dlopen” (Linux)

원인: dlopenlibdl에 있습니다. 링크하지 않으면 에러가 납니다.

해결:

# CMakeLists.txt
if(UNIX AND NOT APPLE)
    target_link_libraries(app PRIVATE dl)
endif()

에러 3: “DLL not found” 또는 “dyld: Library not loaded” (런타임)

원인: 동적 라이브러리 경로가 PATH(Windows) 또는 LD_LIBRARY_PATH(Linux)·DYLD_LIBRARY_PATH(macOS)에 없습니다.

해결:

# Linux
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
./app

# macOS
export DYLD_LIBRARY_PATH=/path/to/libs:$DYLD_LIBRARY_PATH
./app

# Windows: 실행 파일와 같은 디렉터리에 DLL을 두거나 PATH에 추가

에러 4: “symbol not found” (macOS)

원인: macOS는 기본적으로 모든 심볼이 숨겨져 있습니다. 내보내려면 __attribute__((visibility("default")))가 필요합니다.

해결:

#if defined(__APPLE__)
    #define EXPORT_API __attribute__((visibility("default")))
#elif defined(_WIN32)
    #define EXPORT_API __declspec(dllexport)
#else
    #define EXPORT_API __attribute__((visibility("default")))
#endif

extern "C" EXPORT_API void my_exported_function();

에러 5: “LNK2019: unresolved external symbol” (Windows)

원인: __declspec(dllimport)/dllexport가 누락되었습니다.

해결:

// mylib.h
#if defined(_WIN32)
    #ifdef MYLIB_EXPORTS
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#else
    #define MYLIB_API
#endif

MYLIB_API void my_function();
add_library(mylib SHARED mylib.cpp)
target_compile_definitions(mylib PRIVATE MYLIB_EXPORTS)

에러 6: MinGW에서 “undefined reference to WinMain”

원인: 콘솔 앱인데 add_executable(app WIN32 ...)로 GUI 앱으로 빌드했습니다.

해결:

# 콘솔 앱
add_executable(app main.cpp)
# WIN32 제거

에러 7: “version `GLIBCXX_3.4.30’ not found”

원인: 구버전 libstdc++로 빌드한 바이너리를 새 환경에서 실행하려 할 때, 또는 그 반대입니다.

해결: 동일한 툴체인으로 빌드하거나, -static-libstdc++로 정적 링크합니다. 배포 시 최소 glibc/libstdc++ 버전을 명시합니다.

에러 8: CPack DEB 빌드 시 “dpkg-shlibdeps: error”

원인: dpkg-shlibdeps가 패키지된 라이브러리의 의존성을 분석할 수 없습니다. 경로가 잘못되었거나 라이브러리가 누락되었습니다.

해결:

# CPACK_DEBIAN_PACKAGE_SHLIBDEPS를 OFF로 (권장하지 않음)
# 또는 install(RUNTIME_DEPENDENCY_SET)으로 의존성 수집
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)

에러 9: endianness·정렬 문제

원인: ARM과 x86의 바이트 순서가 다를 수 있습니다.

해결:

#include <cstdint>

inline uint32_t swapEndian(uint32_t x) {
    return ((x >> 24) & 0xff) | ((x >> 8) & 0xff00) |
           ((x << 8) & 0xff0000) | ((x << 24) & 0xff000000);
}

#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    #define TO_NETWORK_ORDER(x) (x)
#else
    #define TO_NETWORK_ORDER(x) swapEndian(x)
#endif

7. 모범 사례

7.1 플랫폼별 코드 최소화

// ❌ 나쁜 예: 플랫폼별 코드가 산재
void doWork() {
#ifdef _WIN32
    // Windows 전용 50줄
#else
    // Unix 전용 50줄
#endif
}

// ✅ 좋은 예: 추상화 레이어로 분리
void doWork() {
    platform::init();
    platform::execute();
    platform::cleanup();
}

7.2 std::filesystem 사용

// ❌ 나쁜 예
std::string path = "config" + std::string(1, '/') + "file.txt";

// ✅ 좋은 예
auto path = std::filesystem::path("config") / "file.txt";

7.3 고정 크기 정수 타입

// 플랫폼마다 long 크기가 다를 수 있음
#include <cstdint>
int64_t value;   // 항상 8바이트
uint32_t count;  // 항상 4바이트

7.4 CI에서 멀티 플랫폼 빌드

# .github/workflows/build.yml
jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-cpp@v1
        with:
          compiler: ${{ matrix.os == 'windows-latest' && 'msvc' || 'gcc' }}
      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release
      - name: Build
        run: cmake --build build
      - name: Package
        run: cd build && cpack -G ${{ matrix.os == 'ubuntu-latest' && 'DEB' || (matrix.os == 'windows-latest' && 'NSIS' || 'TGZ') }}

7.5 툴체인 파일 분리

project/
├── toolchains/
│   ├── linux-x64.cmake
│   ├── mingw64.cmake
│   ├── ios.toolchain.cmake
│   └── android-arm64.cmake
├── CMakeLists.txt
└── src/

7.6 ABI 경계에서 C 타입 사용

// ❌ API 경계에 std::string
extern "C" void process(std::string input);

// ✅ C 타입 사용
extern "C" void process(const char* input, size_t len);

8. 프로덕션 패턴

8.1 통합 빌드 스크립트

#!/bin/bash
# build-all.sh — 모든 플랫폼 빌드
set -e

BUILD_DIR=build
for p in linux windows macos; do
    echo "Building for $p..."
    case $p in
        linux)
            cmake -B $BUILD_DIR/linux -DCMAKE_BUILD_TYPE=Release
            cmake --build $BUILD_DIR/linux
            ;;
        windows)
            cmake -B $BUILD_DIR/windows -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.cmake
            cmake --build $BUILD_DIR/windows
            ;;
        macos)
            cmake -B $BUILD_DIR/macos -DCMAKE_BUILD_TYPE=Release
            cmake --build $BUILD_DIR/macos
            ;;
    esac
done

8.2 버전·플랫폼 정보 주입

if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
    execute_process(
        COMMAND git rev-parse --short HEAD
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_HASH
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
else()
    set(GIT_HASH "unknown")
endif()

target_compile_definitions(app PRIVATE
    GIT_HASH="${GIT_HASH}"
    BUILD_PLATFORM="${CMAKE_SYSTEM_NAME}"
)

8.3 환경 변수 기반 툴체인 선택

#!/bin/bash
TARGET=${1:-native}

case $TARGET in
    native)
        cmake -B build
        ;;
    windows)
        cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.cmake
        ;;
    android)
        cmake -B build -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
              -DANDROID_ABI=arm64-v8a
        ;;
    *)
        echo "Unknown target: $TARGET"
        exit 1
        ;;
esac

cmake --build build

8.4 플랫폼별 최적화 플래그

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
        target_compile_options(app PRIVATE -march=native)
    elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
        target_compile_options(app PRIVATE -mcpu=native)
    endif()
elseif(MSVC)
    target_compile_options(app PRIVATE /arch:AVX2)
endif()

8.5 빌드 검증 시퀀스

sequenceDiagram
    participant Dev as 개발자
    participant CMake as CMake
    participant CC as 컴파일러
    participant CI as CI

    Dev->>CMake: cmake -B build
    CMake->>CMake: 플랫폼 감지
    CMake->>CMake: 툴체인 설정
    CMake-->>Dev: Makefile/솔루션 생성
    Dev->>CC: cmake --build build
    CC-->>Dev: 바이너리
    Dev->>CI: push
    CI->>CMake: 매트릭스 빌드 (Win/Linux/macOS)
    CMake->>CC: 각 플랫폼 빌드
    CI-->>Dev: 빌드 결과

9. 정리

항목설명
플랫폼 감지_WIN32, __linux__, __APPLE__ 등 매크로
툴체인-DCMAKE_TOOLCHAIN_FILE로 크로스 컴파일
CPackDEB·RPM·NSIS·ZIP·TGZ 플랫폼별 패키징
ABI 안정성PIMPL·extern C·심볼 버전
에러unistd.h·dlopen·DLL 경로·visibility

핵심 원칙:

  1. 플랫폼별 코드 최소화: 추상화 레이어로 분리
  2. 표준 라이브러리 우선: std::filesystem, std::thread
  3. CMake 툴체인 활용: iOS·Android·MinGW 등
  4. ABI 경계: extern C 또는 PIMPL
  5. CI에서 멀티 플랫폼 빌드: 매트릭스 빌드로 검증

구현 체크리스트

  • std::filesystem으로 경로 처리
  • 플랫폼별 매크로로 #ifdef 분기
  • target_link_libraries에 플랫폼별 라이브러리 추가
  • 동적 로딩 시 확장자(.dll/.so/.dylib) 분기
  • extern "C"로 ABI 안정성 확보 (플러그인·DLL)
  • iOS·Android 툴체인 파일 준비
  • CPack으로 DEB·NSIS·TGZ 패키징
  • CI에서 Windows·Linux·macOS 빌드 검증
  • Linux 심볼 버전 스크립트 (선택)

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. 크로스 플랫폼 라이브러리 배포, 멀티 OS 지원 앱, 플러그인 시스템, CI/CD 파이프라인 구축 시 활용합니다. 본문의 툴체인·CPack·ABI 예제를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. CMake 기초(#04), CMake 고급(#17-1), 크로스 플랫폼(#55-4), ABI 안정성(#55-8)을 먼저 읽으면 이해가 쉽습니다.

Q. 프로덕션에서 주의할 점은?

A. CI에서 모든 지원 플랫폼을 매트릭스로 빌드 검증하고, ABI 경계는 extern C 또는 PIMPL로 고정하며, CPack으로 일관된 패키징을 적용합니다.

Q. vcpkg·Conan과 크로스 플랫폼 빌드를 함께 쓰려면?

A. vcpkg는 --triplet x64-windows 등으로 타겟을 지정합니다. Conan은 -s os=Linux -s arch=x86_64처럼 프로필로 지정합니다. CMake 툴체인과 triplet·프로필이 일치해야 합니다.


참고 자료

한 줄 요약: CMake 툴체인·CPack·ABI 안정성(PIMPL·extern C·심볼 버전)으로 Windows·Linux·macOS·모바일 크로스 플랫폼 빌드와 배포를 마스터할 수 있습니다.


관련 글

  • C++ 크로스 플랫폼 기초 완벽 가이드 | 플랫폼 감지·std::filesystem
  • C++ 크로스 플랫폼 테스트 완벽 가이드 | CI 매트릭스·Docker·엔디안·프로덕션 패턴 [실전]
  • CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
  • C++ CMake Presets 완벽 가이드 | 멀티 플랫폼·vcpkg·Conan·CI/CD 통합
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3