본문으로 건너뛰기
Buildroot Practical · 17/20

Buildroot SDK 생성·배포 — make sdk와 application 워크플로

· Hawk · 12분 읽기

#한 줄 요약

“SDK는 frozen toolchain입니다.” — Buildroot가 만든 toolchain·sysroot를 재배포 가능한 tarball로 묶어 application 개발자에게 넘기는 산출물입니다. 한 번 만들면 같은 ABI 안에서 분리된 두 팀이 병렬로 일할 수 있습니다.

#왜 SDK가 별도의 산출물인가

Buildroot 트리는 시스템 개발자의 환경입니다. 커널·rootfs·toolchain·패키지 트리를 모두 가지고 있으며, make를 돌리면 전체가 한꺼번에 빌드됩니다. 그런데 application 개발자는 그 트리 전체를 받을 필요가 없습니다. 그들이 원하는 것은 “내 application을 이 보드 ABI에 맞춰 cross-compile하고 싶다”입니다.

여기서 두 역할이 갈립니다. 시스템 팀은 Buildroot 트리를 유지하며 커널·base library를 정합니다. application 팀은 그 위에서 도는 application만 짭니다. 두 팀이 같은 Buildroot 트리를 공유하면 충돌이 생깁니다. 시스템 팀이 라이브러리 버전을 올렸는데 application 팀의 빌드가 깨지고, 반대로 application 팀이 임시로 시스템 패키지를 추가했다가 양산 image에 새 패키지가 섞입니다.

SDK가 이 둘을 분리합니다. 시스템 팀이 frozen toolchain + sysroot를 SDK로 패키징해 사내 artifact repo에 올리고, application 팀은 그것을 받아 일반 cross-compile 환경처럼 씁니다. 시스템 트리 전체를 보지 않아도 됩니다.

이 장은 make sdk가 만드는 산출물의 구조, relocatable toolchain의 한계, 외부 application을 빌드하는 4가지 워크플로, 그리고 IDE 통합까지 다룹니다.

#make sdk — 무엇이 생기는가

기본 명령은 단순합니다.

Terminal window
$ make sdk
>>> Generating SDK relocate scripts...
>>> Rendering the SDK relocatable
>>> Generating SDK tarball...
>>> SDK tarball generated: output/images/aarch64-buildroot-linux-gnu_sdk-buildroot.tar.gz

산출물은 output/images/<arch>-buildroot-linux-<libc>_sdk-buildroot.tar.gz 한 파일입니다. 내용은 host toolchain 전체 + target sysroot 전체입니다.

aarch64-buildroot-linux-gnu_sdk-buildroot/
├── bin/ ─ host cross binaries
│ ├── aarch64-buildroot-linux-gnu-gcc
│ ├── aarch64-buildroot-linux-gnu-g++
│ ├── aarch64-buildroot-linux-gnu-ld
│ └── ...
├── aarch64-buildroot-linux-gnu/
│ ├── bin/ ─ target-prefixed binaries
│ ├── include/ ─ toolchain 헤더
│ ├── lib/ ─ libc, libstdc++ (host-side)
│ └── sysroot/ ─ target sysroot
│ ├── usr/include/ ─ target 헤더 (커널 + 패키지)
│ ├── usr/lib/ ─ target .so + .a
│ └── usr/lib/pkgconfig/ ─ pkg-config metadata
├── libexec/gcc/... ─ GCC 내부 binary
├── share/ ─ man, docs
├── environment-setup ─ shell 환경 설정 스크립트
├── relocate-sdk.sh ─ 새 위치로 옮긴 후 경로 갱신
└── version ─ Buildroot revision

핵심은 두 부분입니다. host 쪽의 bin/aarch64-...-gcc는 application 개발자의 워크스테이션(보통 x86_64 Linux)에서 도는 cross compiler입니다. target sysroot는 그 cross compiler가 컴파일·링크 시 참조할 헤더와 라이브러리입니다.

구성 요소위치역할
Host toolchainbin/, libexec/워크스테이션에서 실행되는 cross compiler·linker
Target sysrootaarch64-.../sysroot/target 보드에서 도는 헤더·라이브러리
pkg-config 메타sysroot/usr/lib/pkgconfig/외부 application의 의존성 해결
environment-setuproot한 줄 source로 모든 환경 변수
relocate-sdk.shroot새 경로로 옮긴 후 절대 경로 fixup
versionrootBuildroot revision, 재현용 식별자

압축 풀이 직후는 SDK가 압축 시점의 경로에 묶여 있습니다. 이 상태로 사용하면 빌드가 깨집니다. 다음 절의 relocate가 필요합니다.

#relocatable toolchain — 어떻게 동작하는가

cross compiler는 자기 자신·라이브러리·sysroot의 위치를 절대 경로로 알고 있습니다. Buildroot가 처음 빌드한 위치는 보통 ~/buildroot/output/host/... 같은 시스템 팀 워크스테이션의 절대 경로입니다. 이 SDK를 그대로 /opt/sdk/myboard/에 풀면 컴파일러는 여전히 원래 경로를 찾으려 합니다.

relocate-sdk.sh는 이 문제를 해결합니다.

Terminal window
$ tar xzf aarch64-buildroot-linux-gnu_sdk-buildroot.tar.gz -C /opt/sdk/
$ cd /opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot
$ ./relocate-sdk.sh
Relocating SDK to /opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot
Patched 247 files

내부는 단순합니다. SDK 안의 script·config·pkg-config .pc 파일을 훑어 예전 경로현재 경로로 sed 치환합니다. ELF binary 안의 RPATH도 일부 patchelf로 수정합니다.

다만 한계가 있습니다.

  • RPATH가 길이를 못 넘김 — patchelf가 절대 경로를 원래 RPATH 길이 안에서만 갱신할 수 있는 경우가 있어 새 경로가 더 길면 실패합니다.
  • 하드코딩된 절대 경로 텍스트 — 일부 .la libtool 파일·.cmake config는 자체 sed가 잡지 못하는 형식의 절대 경로를 포함합니다.
  • bytecode·archive에 잠긴 경로 — Python·Ruby 패키지가 __pycache__.a에 절대 경로를 가지고 있으면 갱신 안 됨.

실무 권장은 SDK 설치 경로를 사내 표준으로 못 박는 것입니다. 예를 들어 모든 워크스테이션에서 /opt/sdk/<product>-<version>/로 통일하면 relocate 자체가 사실상 no-op이 됩니다.

또 하나의 한계는 host architecture 호환성입니다. SDK는 시스템 팀이 빌드한 host에 종속됩니다. 시스템 팀이 x86_64 Linux에서 만든 SDK는 ARM Mac에서 host 쪽 binary가 안 돕니다. target sysroot는 어차피 aarch64라 cross 자체는 OK이지만 cross compiler가 못 실행됩니다. 다른 host가 필요하면 Buildroot를 그 host에서 다시 빌드해야 합니다.

#environment-setup 스크립트

SDK 사용은 한 줄로 끝납니다.

Terminal window
$ source /opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/environment-setup
$ echo $CC
/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/bin/aarch64-buildroot-linux-gnu-gcc

스크립트 내용은 다음과 같은 형태입니다.

Terminal window
# environment-setup (generated by Buildroot)
SDK_PATH="/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot"
TARGET_PREFIX="aarch64-buildroot-linux-gnu"
SYSROOT="${SDK_PATH}/${TARGET_PREFIX}/sysroot"
export PATH="${SDK_PATH}/bin:${PATH}"
export CC="${TARGET_PREFIX}-gcc --sysroot=${SYSROOT}"
export CXX="${TARGET_PREFIX}-g++ --sysroot=${SYSROOT}"
export LD="${TARGET_PREFIX}-ld"
export AR="${TARGET_PREFIX}-ar"
export STRIP="${TARGET_PREFIX}-strip"
export RANLIB="${TARGET_PREFIX}-ranlib"
export READELF="${TARGET_PREFIX}-readelf"
export PKG_CONFIG_SYSROOT_DIR="${SYSROOT}"
export PKG_CONFIG_LIBDIR="${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig"
export PKG_CONFIG_PATH=""
export CFLAGS="-O2 -pipe"
export CXXFLAGS="-O2 -pipe"
export LDFLAGS="-L${SYSROOT}/usr/lib -Wl,-rpath-link,${SYSROOT}/usr/lib"
export CONFIGURE_FLAGS="--host=${TARGET_PREFIX} --with-sysroot=${SYSROOT}"

이 한 번의 source 이후 현재 shell에서 도는 모든 빌드 명령이 자동으로 cross-compile 모드로 동작합니다. application 개발자는 자기 빌드 시스템(autotools·cmake·meson·make)의 원래 명령을 그대로 쓰면 됩니다.

PKG_CONFIG_SYSROOT_DIRPKG_CONFIG_LIBDIR의 분리가 중요합니다. 전자는 pkg-config가 prefix를 sysroot로 접두시키게 하고, 후자는 host 시스템의 pkg-config 경로를 차단/usr/lib/pkgconfig가 섞이지 않게 합니다. 이 두 변수를 빠뜨리면 host의 라이브러리가 링크에 끼어들어 runtime에 보드에서 깨지는 사고가 납니다.

#외부 application 빌드 — 4가지 빌드 시스템 예

SDK를 source한 상태에서 각 빌드 시스템은 다음 invocation으로 cross 빌드를 합니다.

#Autotools

Terminal window
$ source /opt/sdk/.../environment-setup
$ cd ~/myapp
$ ./configure \
--host=aarch64-buildroot-linux-gnu \
--prefix=/usr \
--with-sysroot="${SYSROOT}"
$ make -j$(nproc)
$ make DESTDIR=$PWD/install install

--host가 결정적입니다. --build를 같이 지정하면 더 명시적이지만 autotools가 환경에서 추론합니다. --prefix=/usrtarget 보드의 설치 위치고, DESTDIRworkstation의 staging 위치입니다.

#CMake

CMake는 toolchain file을 통해 cross 설정을 받습니다. Buildroot SDK는 친절하게도 sysroot 안에 toolchain file을 만들어 둡니다.

Terminal window
$ source /opt/sdk/.../environment-setup
$ cd ~/myapp
$ mkdir build && cd build
$ cmake \
-DCMAKE_TOOLCHAIN_FILE="${SDK_PATH}/share/buildroot/toolchainfile.cmake" \
-DCMAKE_INSTALL_PREFIX=/usr \
..
$ cmake --build . -j$(nproc)
$ DESTDIR=$PWD/install cmake --install .

toolchain file의 내용은 다음과 같습니다.

toolchainfile.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_SYSROOT $ENV{SYSROOT})
set(CMAKE_C_COMPILER aarch64-buildroot-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-buildroot-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_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)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

FIND_ROOT_PATH_MODE_* 4개가 중요합니다. PROGRAM은 host에서 찾고(NEVER → sysroot 안에서 찾지 않음), LIBRARY/INCLUDE/PACKAGE는 sysroot에서만 찾습니다(ONLY). 이 분리가 안 되면 host의 /usr/lib에서 라이브러리가 잡혀 link만 되고 런타임에 깨집니다.

#Meson

Meson도 cross-file로 받습니다.

Terminal window
$ source /opt/sdk/.../environment-setup
$ meson setup build \
--cross-file "${SDK_PATH}/share/buildroot/cross-compilation.conf" \
--prefix=/usr
$ meson compile -C build
$ DESTDIR=$PWD/install meson install -C build

cross-file 내용 일부.

[binaries]
c = 'aarch64-buildroot-linux-gnu-gcc'
cpp = 'aarch64-buildroot-linux-gnu-g++'
ar = 'aarch64-buildroot-linux-gnu-ar'
strip = 'aarch64-buildroot-linux-gnu-strip'
pkg-config = 'pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'cortex-a72'
endian = 'little'

#직접 Make

custom Makefile만 있는 application은 환경변수만 잘 흘려보내면 됩니다.

Terminal window
$ source /opt/sdk/.../environment-setup
$ make CC="$CC" CXX="$CXX" CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" -j$(nproc)

Makefile이 ?=로 CC를 받는 정직한 구조라면 source environment-setup 후 그냥 make만 쳐도 됩니다.

빌드 시스템cross 진입점표준 SDK 파일
autotools./configure --host=...environment의 $CC·$CXX
cmake-DCMAKE_TOOLCHAIN_FILE=...share/buildroot/toolchainfile.cmake
meson--cross-file ...share/buildroot/cross-compilation.conf
makeCC=$CC ...environment-setup만 source

네 가지 모두 환경을 source한 뒤가 출발점입니다. SDK의 일관성이 빌드 시스템에 의존하지 않게 만들어 줍니다.

#IDE 통합

application 개발자가 GUI에서 코드를 짜고 싶다면 IDE에 SDK를 연결합니다.

#VS Code

.vscode/c_cpp_properties.json에 sysroot와 cross-compiler를 명시합니다.

{
"configurations": [
{
"name": "BuildrootAArch64",
"includePath": [
"${workspaceFolder}/**",
"/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/aarch64-buildroot-linux-gnu/sysroot/usr/include"
],
"compilerPath": "/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/bin/aarch64-buildroot-linux-gnu-gcc",
"compilerArgs": [
"--sysroot=/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/aarch64-buildroot-linux-gnu/sysroot"
],
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm64"
}
],
"version": 4
}

.vscode/settings.json은 빌드 task에서 SDK 환경을 source하도록 wrapper script를 부르는 패턴이 깔끔합니다.

{
"cmake.configureEnvironment": {
"SDK_PATH": "/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot"
},
"cmake.configureArgs": [
"-DCMAKE_TOOLCHAIN_FILE=/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/share/buildroot/toolchainfile.cmake"
]
}

#CLion

CLion은 toolchainCMake profile을 따로 정의합니다. Settings → Build → Toolchains에서 새 toolchain을 추가하고 다음을 지정합니다.

필드
NameBuildroot AArch64
CMakesystem default
Makesystem default
C Compiler/opt/sdk/.../bin/aarch64-buildroot-linux-gnu-gcc
C++ Compiler/opt/sdk/.../bin/aarch64-buildroot-linux-gnu-g++
Debugger/opt/sdk/.../bin/aarch64-buildroot-linux-gnu-gdb (또는 host gdb-multiarch)

그 다음 Settings → Build → CMake에 profile을 추가하고 CMake options에 toolchain file을 지정합니다.

-DCMAKE_TOOLCHAIN_FILE=/opt/sdk/aarch64-buildroot-linux-gnu_sdk-buildroot/share/buildroot/toolchainfile.cmake

원격 실행을 위해 Settings → Build → Deployment에 보드의 SSH 정보를 등록하고, Run/Debug Configuration에 Remote Debug를 만들면 target 보드에서 실제 실행·디버그가 됩니다.

IDEtoolchain 등록 위치sysroot 인지 방식
VS Codec_cpp_properties.json + CMake extensioncompilerArgs --sysroot
CLionSettings → ToolchainsCMake toolchain file 자동 인식
Qt CreatorKits 추가Qt cross + cmake/qmake 자동
Eclipse CDTCross GCC plugin수동 include path 등록

#버전 관리·배포

SDK를 사내에 배포하면 버전 식별이 핵심입니다. 같은 보드라도 시스템 팀이 매 sprint마다 SDK를 갱신하므로 application 팀은 어느 SDK로 빌드했는지 명확해야 합니다.

권장 패턴은 다음과 같습니다.

Terminal window
$ make sdk
$ mv output/images/aarch64-buildroot-linux-gnu_sdk-buildroot.tar.gz \
output/images/sdk-myproduct-v1.4.2.tar.gz
$ sha256sum output/images/sdk-myproduct-v1.4.2.tar.gz \
> output/images/sdk-myproduct-v1.4.2.tar.gz.sha256
$ aws s3 cp output/images/sdk-myproduct-v1.4.2.tar.gz \
s3://company-artifacts/sdk/

파일명에 제품·버전을 명시하고, 체크섬을 동봉하며, 사내 artifact repo(S3·Artifactory·Nexus 등)에 올립니다. application 팀은 다음과 같이 받습니다.

Terminal window
$ wget https://artifacts.example.com/sdk/sdk-myproduct-v1.4.2.tar.gz
$ wget https://artifacts.example.com/sdk/sdk-myproduct-v1.4.2.tar.gz.sha256
$ sha256sum -c sdk-myproduct-v1.4.2.tar.gz.sha256
sdk-myproduct-v1.4.2.tar.gz: OK
$ tar xzf sdk-myproduct-v1.4.2.tar.gz -C /opt/sdk/
$ /opt/sdk/sdk-myproduct-v1.4.2/relocate-sdk.sh

SDK 안의 version 파일에 Buildroot revision (git describe)을 적어 두면 문제 발생 시 정확히 어느 트리·commit에서 만든 SDK인지 추적 가능합니다. CI에서 SDK 빌드 job을 분리해 매 tagged release마다 자동 발급하는 게 표준 패턴입니다 (자세한 흐름은 Ch 19: CI/CD).

#Yocto SDK와의 차이

Yocto 생태계에도 SDK가 있고 흐름이 비슷해 보이지만 차이가 있습니다.

항목Buildroot SDKYocto SDK (populate_sdk)Yocto eSDK
생성 명령make sdkbitbake -c populate_sdk <image>bitbake -c populate_sdk_ext <image>
구성host toolchain + target sysroothost toolchain + target sysroot + meta-toolchain위 모두 + devtool + recipe 추가
recipe 추가불가 (Buildroot 트리 따로 빌드)불가 (다시 populate 필요)가능 (devtool add)
인스톨러tarball + relocate-sdk.shself-extracting .sh (메뉴 대화형)self-extracting .sh
크기200 ~ 500 MB300 ~ 800 MB1 ~ 3 GB
재현성Buildroot revision으로 확정layer revision·hash 매트릭스위와 동일

Yocto의 eSDK는 application 개발자가 자기 환경에서 새 recipe를 추가할 수 있게 해 줍니다. 시스템 팀에 가지 않고도 새 라이브러리를 SDK에 끼워 빌드해 보고, 검증되면 시스템 팀에 PR로 올리는 흐름입니다. Buildroot SDK는 static이라 이 흐름이 안 됩니다. 새 라이브러리가 필요하면 시스템 팀이 Buildroot 트리에 추가 후 SDK를 재발급해야 합니다.

이 차이는 팀 구조에서 의미가 있습니다. application 팀이 빈번하게 새 의존성을 요구하는 환경이라면 Yocto eSDK가 마찰을 줄여 줍니다. 의존성이 비교적 안정적이고 시스템 팀이 통제권을 갖는 환경(임베디드·산업 제품 다수)이라면 Buildroot SDK의 단순함이 더 큰 장점입니다.

#흔한 실수

다음은 SDK 배포에서 반복적으로 나오는 사고들입니다.

relocate를 안 한 채 쓴다. 압축 풀고 바로 source environment-setup을 하면 원래 빌드 머신의 절대 경로가 들어 있어 cross-compile이 깨집니다. tar 풀이 직후 ./relocate-sdk.sh를 반드시 실행하는 게 표준 절차입니다.

다른 host architecture에서 사용한다. 시스템 팀이 x86_64 Linux로 만든 SDK를 ARM Mac 개발자가 받아 풀면 cross compiler 자체가 실행 안 됩니다. host architecture별로 SDK를 따로 발급하거나, 사내 표준 host를 하나로 통일해야 합니다.

sysroot의 라이브러리를 수동으로 수정한다. “이 라이브러리만 살짝 newer 버전으로 바꿔 보자”라고 sysroot 안 .so를 손으로 교체하면, 보드의 실제 rootfs는 예전 버전이라 빌드와 런타임이 어긋납니다. sysroot는 읽기 전용 참조 자료이며, 변경이 필요하면 시스템 팀이 Buildroot 트리에서 바꿔 SDK를 재발급해야 합니다.

PKG_CONFIG_SYSROOT_DIR을 빠뜨린다. PKG_CONFIG_LIBDIR만 sysroot로 가리키고 PKG_CONFIG_SYSROOT_DIR을 비워두면 .pc 안의 prefix가 워크스테이션 절대 경로로 해석돼 host 라이브러리가 섞입니다. 두 변수는 항상 짝으로 갑니다. environment-setup이 이미 둘을 같이 설정하므로 개인 셸 설정에서 덮어쓰지 않으면 됩니다.

SDK 버전과 보드 image 버전이 어긋난다. v1.4.2 SDK로 application을 빌드해 v1.3.0 보드에 올리면 glibc·라이브러리 버전 mismatch가 납니다. SDK 발급 시 어느 image와 짝인지 명시하고, CI에서 image와 SDK를 같은 job에서 생성하도록 묶는 게 안전합니다.

relocate 후 다시 옮긴다. 한 번 relocate-sdk.sh로 경로를 박은 SDK를 또 다른 위치로 옮기면 또 깨집니다. relocate는 멱등이지만 현재 경로 기준으로 다시 돌려야 합니다. 옮긴 후 반드시 한 번 더 실행하는 습관을 들이거나, 사내 표준 경로(/opt/sdk/...)를 정해 옮길 일이 없게 합니다.

#정리

  • SDK는 Buildroot가 만든 frozen toolchain + sysroot를 하나의 tarball로 묶은 산출물입니다. 시스템 팀과 application 팀을 분리합니다.
  • make sdkoutput/images/<arch>-buildroot-linux-<libc>_sdk-buildroot.tar.gz를 생성합니다. host cross compiler + target sysroot + environment-setup + relocate-sdk.sh가 핵심 구성입니다.
  • relocatable toolchain은 압축 해제 후 반드시 relocate-sdk.sh로 경로를 새 위치로 갱신해야 합니다. RPATH 길이·libtool .la·bytecode가 한계점입니다.
  • environment-setup을 source하면 CC·CXX·PKG_CONFIG_*·LDFLAGS·CFLAGS가 한꺼번에 설정됩니다. PKG_CONFIG_SYSROOT_DIRPKG_CONFIG_LIBDIR 짝이 결정적입니다.
  • 외부 application은 autotools(--host=), cmake(-DCMAKE_TOOLCHAIN_FILE=), meson(--cross-file), make(환경변수)의 4가지 패턴 중 하나로 cross 빌드합니다.
  • VS Code는 c_cpp_properties.json, CLion은 Toolchains + CMake profile로 SDK를 인지시킵니다.
  • 배포는 제품·버전 명시 + SHA256 체크섬 + artifact repo가 표준입니다. SDK의 version 파일에 Buildroot revision을 기록합니다.
  • Yocto SDK는 메타 정보까지 묶고 eSDK는 recipe 추가가 가능합니다. Buildroot SDK는 static이라 의존성 변경 시 시스템 팀의 재발급이 필요합니다.
  • 흔한 사고는 relocate 누락, host architecture mismatch, sysroot 수동 수정, SDK·image 버전 어긋남입니다.

#다음 장 예고

다음 편은 Ch 18: Security·CVE 추적. SDK·image를 만든 뒤 유지보수 기간 내내 따라붙는 보안 패치와 CVE 모니터링을 다룹니다.

#관련 항목