Buildroot 외부 트리 — BR2_EXTERNAL 구성과 활용
#한 줄 요약
“Buildroot 본체에 patch를 쌓지 마라. 내 패키지·보드·defconfig는 별도 트리에 둔다.” —
BR2_EXTERNAL은 그 분리를 공식 메커니즘으로 만들어 줍니다.
Buildroot를 한두 달 쓰다 보면 반드시 마주치는 문제가 있습니다. 회사 전용 패키지가 늘어나고, 보드 한두 개가 추가되고, 매일 buildroot/에 commit이 쌓이기 시작합니다. 그러다 Buildroot LTS 업그레이드 시점이 오면 충돌이 폭발합니다. 내 변경과 upstream 변경이 같은 디렉터리에서 부딪히기 때문입니다.
이 장은 그 문제를 푸는 공식 답인 BR2_EXTERNAL을 다룹니다. 한 마디로 내 트리만 따로 두고, Buildroot는 read-only로 사용하는 방식입니다.
#어떤 문제를 푸는가
Buildroot를 fork해서 수정하는 흐름은 이렇게 무너집니다.
- 사내 패키지
acme-firmware를package/acme-firmware/에 추가. - 보드
acme-edge를board/acme-edge/와configs/acme_edge_defconfig에 추가. - busybox config 수정, post-build 스크립트 추가, 커널 patch 두어 개 추가.
- 6개월 뒤 Buildroot LTS 2026.02 → 2026.08 업그레이드.
git pull --rebase가 동일 파일에 충돌을 일으킴. - 패치를 다시 적용하면서 upstream과 내 변경의 경계가 사라짐.
BR2_EXTERNAL은 이 문제를 다음과 같이 풉니다.
- 내 트리는 독립된 디렉터리에 둔다. Buildroot는
git submodule이나 별도 clone으로 read-only. - Buildroot가 build 시점에 환경 변수 한 줄로 내 트리를 인식한다.
- 내 트리는 upstream의 분기가 아니라 add-on이 된다.
Yocto의 layer 개념과 비슷하다고 보면 됩니다. 차이는 Buildroot의 외부 트리가 훨씬 가볍다는 점입니다. 디렉터리 몇 개와 파일 세 개면 끝납니다.
#외부 트리의 기본 구조
가장 작은 외부 트리는 다음 셋입니다.
br2-acme/├── external.desc├── external.mk├── Config.in└── package/ └── acme-firmware/ ├── Config.in ├── acme-firmware.mk └── acme-firmware.hash각 파일의 역할:
| 파일 | 역할 |
|---|---|
external.desc | 이 트리가 누구인지 |
external.mk | 패키지의 .mk를 include |
Config.in | 패키지의 Config.in을 include |
#external.desc
가장 짧은 파일입니다. 트리의 이름과 설명을 선언합니다.
name: ACMEdesc: ACME corporation Buildroot treename 값은 곧 Kconfig 변수 prefix가 됩니다. 위 예시면 BR2_EXTERNAL_ACME_PATH가 자동으로 정의되고, .mk에서 $(BR2_EXTERNAL_ACME_PATH)로 내 트리의 절대 경로를 참조할 수 있습니다.
#external.mk
내 트리의 모든 패키지 .mk를 모아 include하는 진입점입니다.
include $(sort $(wildcard $(BR2_EXTERNAL_ACME_PATH)/package/*/*.mk))wildcard로 자동 수집하므로 패키지가 늘어도 external.mk는 수정하지 않습니다.
#Config.in
마찬가지로 모든 패키지의 Config.in을 모읍니다. Buildroot menuconfig의 최상위에 External options 메뉴가 생기고, 그 안에 내 트리의 옵션이 노출됩니다.
menu "ACME packages" source "$BR2_EXTERNAL_ACME_PATH/package/acme-firmware/Config.in" source "$BR2_EXTERNAL_ACME_PATH/package/acme-sensor/Config.in"endmenu여러 패키지가 있으면 source 줄이 늘어납니다. 자동 수집이 아니라 명시적으로 적는 이유는 메뉴 순서·그룹화를 통제하기 위해서입니다.
#첫 외부 트리 만들기
빈 디렉터리에서 출발하는 절차입니다.
mkdir -p ~/work/br2-acme/{package,board,configs}cd ~/work/br2-acme
cat > external.desc <<'EOF'name: ACMEdesc: ACME corporation Buildroot treeEOF
cat > external.mk <<'EOF'include $(sort $(wildcard $(BR2_EXTERNAL_ACME_PATH)/package/*/*.mk))EOF
touch Config.inConfig.in은 첫 단계에서 비워 둬도 됩니다.
이제 Buildroot에 이 트리를 알려 줍니다.
$ cd ~/work/buildroot$ make BR2_EXTERNAL=~/work/br2-acme qemu_aarch64_virt_defconfig이 명령은 영구적으로 외부 트리 경로를 output/.br-external.mk에 기록합니다. 다음부터는 make만 쳐도 외부 트리를 인식합니다. 즉 한 번만 BR2_EXTERNAL=...을 명시하면 됩니다.
make menuconfig최상위 메뉴 마지막에 External options 항목이 표시됩니다.
#디렉터리 규약
외부 트리는 Buildroot 본체와 완전히 같은 디렉터리 규약을 따릅니다. 즉 본체에서 package/htop/에 두던 것을 외부 트리에서는 <external>/package/htop/에 둡니다.
| 디렉터리 | 본체 위치 | 외부 트리 위치 |
|---|---|---|
| 패키지 | buildroot/package/<name>/ | <external>/package/<name>/ |
| 보드 자산 | buildroot/board/<vendor>/<board>/ | <external>/board/<vendor>/<board>/ |
| defconfig | buildroot/configs/<name>_defconfig | <external>/configs/<name>_defconfig |
| Linux fragment | buildroot/board/<vendor>/<board>/linux.config | <external>/board/<vendor>/<board>/linux.config |
| post-build | (어디든) | <external>/board/<vendor>/<board>/post-build.sh |
defconfig를 외부 트리에 두면 make list-defconfigs에 자동으로 표시됩니다.
$ make list-defconfigs...Built-in configs: qemu_aarch64_virt_defconfig - Build for qemu_aarch64_virt
External configs in $(BR2_EXTERNAL_ACME_PATH)/configs: acme_edge_defconfig acme_gateway_defconfig#패키지 추가 — acme-firmware 예시
package/acme-firmware/에 세 파일을 둡니다.
#package/acme-firmware/Config.in
config BR2_PACKAGE_ACME_FIRMWARE bool "acme-firmware" help ACME proprietary firmware loader. https://example.com/acme-firmware#package/acme-firmware/acme-firmware.mk
################################################################################## acme-firmware#################################################################################
ACME_FIRMWARE_VERSION = 1.2.3ACME_FIRMWARE_SITE = $(call github,acme-corp,acme-firmware,v$(ACME_FIRMWARE_VERSION))ACME_FIRMWARE_LICENSE = ProprietaryACME_FIRMWARE_LICENSE_FILES = LICENSEACME_FIRMWARE_REDISTRIBUTE = NO
ACME_FIRMWARE_DEPENDENCIES = libubox
define ACME_FIRMWARE_BUILD_CMDS $(MAKE) CC="$(TARGET_CC)" LD="$(TARGET_LD)" -C $(@D)endef
define ACME_FIRMWARE_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/acme-fw $(TARGET_DIR)/usr/bin/acme-fwendef
$(eval $(generic-package))#package/acme-firmware/acme-firmware.hash
sha256 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 acme-firmware-1.2.3.tar.gzsha256 abcd... LICENSE마지막으로 외부 트리 최상위의 Config.in에 한 줄 추가합니다.
source "$BR2_EXTERNAL_ACME_PATH/package/acme-firmware/Config.in"이제 make menuconfig에서 External options → ACME packages → acme-firmware로 활성화할 수 있습니다.
#여러 외부 트리 stacking
BR2_EXTERNAL은 콜론으로 구분된 여러 경로를 받습니다. 회사 트리 위에 프로젝트 트리를 얹는 흐름이 흔합니다.
$ make BR2_EXTERNAL=~/work/br2-acme:~/work/br2-project-x \ qemu_aarch64_virt_defconfig각 트리의 external.desc에 적힌 name이 prefix가 됩니다. 위 예시면 BR2_EXTERNAL_ACME_PATH와 BR2_EXTERNAL_PROJECT_X_PATH가 동시에 정의됩니다. 후자가 전자의 패키지를 override할 수도 있는데, 우선순위는 BR2_EXTERNAL 인자의 순서를 따릅니다. 뒤에 오는 트리가 이깁니다.
회사 공통은 br2-acme/에, 제품별 차이는 br2-product-x/에 두는 식의 2단 레이어가 실무에서 가장 흔한 구성입니다.
세 층의 관계를 그림으로 보면 다음과 같습니다.
#defconfig를 외부 트리로 옮기기
기존 defconfig를 외부 트리로 옮길 때 자주 빠지는 함정은 path가 외부 트리 안에 있어야 한다는 점입니다.
$ cp buildroot/configs/myboard_defconfig br2-acme/configs/acme_edge_defconfig$ git rm buildroot/configs/myboard_defconfig이전에 BR2_ROOTFS_OVERLAY="board/myboard/rootfs-overlay" 같이 상대 경로로 적혀 있던 항목은 $(BR2_EXTERNAL_ACME_PATH)/board/...로 바꿔야 합니다.
BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/rootfs-overlay"BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_ACME_PATH)/board/acme/edge/post-build.sh"defconfig 안에서도 Buildroot의 변수 expansion이 동작합니다. 따라서 외부 트리의 절대 경로를 하드코딩하지 말고 항상 $(BR2_EXTERNAL_<NAME>_PATH)를 사용합니다.
#검증 흐름
외부 트리가 제대로 인식됐는지 확인합니다.
$ cd ~/work/buildroot$ cat output/.br-external.mk# 첫 줄에 BR2_EXTERNAL = /home/user/work/br2-acme
$ make show-info | grep -i externalBR2_EXTERNAL_ACME_PATH=/home/user/work/br2-acme
$ make printvars VARS=BR2_EXTERNAL_ACME_PATHBR2_EXTERNAL_ACME_PATH=/home/user/work/br2-acme빌드를 돌려 패키지가 잡히는지 확인합니다.
make acme-firmware-sourcemake acme-firmwaremake acme-firmware-show-info순서대로 tarball 다운로드만, build + install, 메타데이터 확인입니다.
#Troubleshooting
#make 후 “External options” 메뉴가 안 보인다
원인은 거의 항상 두 가지입니다. external.desc의 name이 비어 있거나, 최상위 Config.in이 비어 있는 채로 패키지 Config.in을 source하지 않은 경우. menuconfig는 최상위 Config.in이 비어 있으면 메뉴 자체를 표시하지 않습니다.
#make 결과 패키지가 안 잡힌다
external.mk가 패키지 .mk를 include하지 못한 경우입니다. 패스를 확인합니다.
$ make printvars VARS=ACME_FIRMWARE_VERSIONACME_FIRMWARE_VERSION=1.2.3빈 출력이 나오면 external.mk의 wildcard 경로가 틀렸거나, 패키지 .mk 파일명이 잘못된 경우입니다. 디렉터리명과 .mk 파일명이 완전히 일치해야 합니다.
#두 외부 트리에서 같은 패키지 이름이 충돌
Kconfig 옵션 이름(BR2_PACKAGE_ACME_FIRMWARE)이 같으면 두 번째 정의가 무시됩니다. 외부 트리 이름을 패키지에 포함시키는 관례(acme-firmware, proj-x-firmware)로 회피합니다.
#Buildroot 본체와 외부 트리의 LTS 호환
외부 트리는 Buildroot 본체에 종속됩니다. 같은 Buildroot 본체에서 검증한 외부 트리를 다른 LTS로 옮기면 API 변경(예: host- prefix 정책, pkg-config 변경)으로 깨질 수 있습니다. 외부 트리 README에 호환 Buildroot 버전을 적어 두는 게 안전합니다.
#정리
BR2_EXTERNAL은 Buildroot 본체를 fork하지 않고 내 패키지·보드·defconfig만 분리하는 공식 메커니즘입니다.- 최소 구성은
external.desc,external.mk,Config.in셋입니다. - 외부 트리의 디렉터리 구조는 본체와 동일합니다.
package/,board/,configs/를 같은 규약으로 사용합니다. make BR2_EXTERNAL=...은 한 번만 명시하면output/.br-external.mk에 기록됩니다.- 콜론으로 여러 트리를 stacking할 수 있습니다. 뒤에 오는 트리가 우선순위에서 이깁니다.
- defconfig·overlay 경로는 항상
$(BR2_EXTERNAL_<NAME>_PATH)로 표기합니다. 절대 경로 하드코딩은 금지입니다.
#다음 장 예고
다음 편은 Ch 7: 보드 customize — overlay, post-build, post-image.
#관련 항목
Buildroot Practical · 6 of 20
- 1Buildroot가 푸는 문제 — Yocto와의 핵심 차이 분석
- 2Buildroot 디렉터리 구조 분해 — board·configs·dl·output
- 3Buildroot Kconfig 설정 — menuconfig와 defconfig 작성
- 4Buildroot 첫 빌드 — QEMU에서 동작하는 시스템 만들기
- 5Buildroot 패키지 시스템 분석 — .mk와 Config.in 동작 추적
- 6Buildroot 외부 트리 — BR2_EXTERNAL 구성과 활용
- 7Buildroot 보드 Customize — overlay·post-build·post-image 흐름
- 8Buildroot 출력 파일시스템 — initramfs·squashfs·ext4·cpio 선택
- 9Buildroot 새 패키지 작성 — autotools·cmake·python 통합
- 10Buildroot 실전 — BeagleBone Black 시스템 처음부터 끝까지
- 11Buildroot Toolchain 선택 — Internal vs External 비교
- 12Buildroot 커널 Customize — defconfig fragment와 DTS 통합
- 13Buildroot U-Boot 통합 — 빌드·env·fw_env 흐름
- 14Buildroot 빌드 캐싱 분석 — dl·ccache·per-package
- 15Buildroot post-build·post-image 심화 — rootfs 최종 수정 흐름
- 16Buildroot OTA 이미지 업데이트 — RAUC·swupdate 통합
- 17Buildroot SDK 생성·배포 — make sdk와 application 워크플로
- 18Buildroot Security·CVE 추적 — pkg-stats와 Reproducible Builds
- 19Buildroot CI/CD 구축 — Container Build와 Cache 공유
- 20Buildroot → Yocto 마이그레이션 — 언제·어떻게 옮길까
관련 글
Buildroot → Yocto 마이그레이션 — 언제·어떻게 옮길까
Buildroot가 한계에 도달하는 신호와 Yocto/OE로 점진 이전하는 패턴, meta-buildroot 같은 hybrid 옵션.
Buildroot CI/CD 구축 — Container Build와 Cache 공유
GitLab/GitHub Actions에서 Buildroot 트리를 컨테이너로 빌드하고 dl·ccache를 팀이 공유하는 패턴.
Buildroot Security·CVE 추적 — pkg-stats와 Reproducible Builds
Buildroot의 CVE 추적·legal info 산출·SBOM·reproducible build로 보안과 컴플라이언스를 관리하는 패턴.
이 글을 참조하는 글 (5)
- Buildroot 실전 — BeagleBone Black 시스템 처음부터 끝까지— Buildroot Practical
- Buildroot 새 패키지 작성 — autotools·cmake·python 통합— Buildroot Practical
- Buildroot 보드 Customize — overlay·post-build·post-image 흐름— Buildroot Practical
- Buildroot 패키지 시스템 분석 — .mk와 Config.in 동작 추적— Buildroot Practical
- Buildroot 디렉터리 구조 분해 — board·configs·dl·output— Buildroot Practical