AEGLOSLAB Arduino UNO Q 완전 가이드

제6부 개발 워크플로우: 크로스 컴파일

6.2 PC에서 Rust 크로스 컴파일과 배포

6.1에서 C/C++로 aarch64 크로스 컴파일을 다뤘다. 이 장은 같은 문제를 Rust로 푼다 — rustup으로 타깃을 추가하고 크로스 링커를 지정하는 표준 경로, Docker로 감싸 설정을 거의 없애는 cross 툴, 그리고 UNO Q 커뮤니티가 만든 스타터킷 unoq-starter-mpu의 실제 사용법까지 본다. 이 장에서 다루는 것은 MPU(QRB2210, Debian Linux) 쪽 Rust 바이너리다 — MCU(STM32U585, Zephyr) 쪽 no_std Rust 펌웨어는 별도 워크플로우(ADB·OpenOCD 플래싱)이며 3.2에서 다룬다.

6.2.1 rustup 타깃 추가와 크로스 링커

Rust 컴파일러(rustc)는 여러 타깃의 표준 라이브러리를 갖고 있지만, C 링커는 별도로 필요하다 — rustup target add는 core·std·alloc의 사전 컴파일 라이브러리만 받아올 뿐, glibc가 필요한 gnu 타깃에서는 여전히 시스템 링커를 호출한다.

# 1) aarch64 타깃 표준 라이브러리 추가
rustup target add aarch64-unknown-linux-gnu

# 2) 크로스 링커 설치 (Debian/Ubuntu — 6.1.2와 동일 패키지로 충분)
sudo apt install crossbuild-essential-arm64
# 또는 gcc-aarch64-linux-gnu 단독 설치도 가능

링커 위치를 Cargo에 알려준다. 프로젝트 루트(또는 홈 디렉터리)의 .cargo/config.toml:

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

빌드:

cargo build --release --target aarch64-unknown-linux-gnu
file target/aarch64-unknown-linux-gnu/release/myapp
# myapp: ELF 64-bit LSB pie executable, ARM aarch64, ...

6.2.2 링커를 지정하는 세 가지 방법

.cargo/config.toml이 표준 방법이지만 상황에 따라 다른 방법이 유리할 수 있다.

방법예시언제 쓰나
.cargo/config.toml[target.aarch64-unknown-linux-gnu]<br/>linker = "aarch64-linux-gnu-gcc"프로젝트에 고정 설정을 커밋해 팀·CI가 공유할 때(가장 일반적)
환경 변수CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc cargo build --target aarch64-unknown-linux-gnu일회성 빌드, CI 스크립트에서 설정을 파일로 남기고 싶지 않을 때
cargo --config 플래그cargo build --target aarch64-unknown-linux-gnu --config target.aarch64-unknown-linux-gnu.linker=\"aarch64-linux-gnu-gcc\"스크립트에서 임시로 오버라이드할 때

세 방법 모두 같은 값을 설정하는 것이므로 우선순위(환경 변수 > --config > config.toml)만 기억하면 충돌 걱정 없이 섞어 써도 된다.

6.2.3 glibc 버전 불일치와 musl 대안

Rust로 빌드한 바이너리도 결국 aarch64-linux-gnu-gcc가 시스템 링커 역할을 하므로, 6.1.5에서 다룬 sysroot·glibc 버전 불일치 문제가 그대로 적용된다. GLIBC_2.xx not found 같은 오류를 만나면 6.1.5의 대응(보드 sysroot 복제·정적 링크·Docker)을 그대로 시도한다.

Rust 생태계에는 한 가지 대안이 더 있다 — musl 타깃으로 완전 정적 바이너리를 만드는 것이다.

rustup target add aarch64-unknown-linux-musl
cargo build --release --target aarch64-unknown-linux-musl

이 타깃은 실행 시 어떤 시스템 glibc에도 의존하지 않는 단일 정적 바이너리를 만들어 sysroot 문제를 원천적으로 없앤다. 다만 aarch64용 musl 크로스 링커는 apt 기본 저장소에 없는 경우가 많아 별도 툴체인(예: musl.cc 배포 사전빌드본)을 받아 .cargo/config.toml에 경로를 지정해야 하는 번거로움이 있다 — 이 번거로움을 없애 주는 것이 다음 절의 cross다.

6.2.4 cross로 제로 설정 빌드

cross(cross-rs 프로젝트, 구 rust-embedded/cross)는 타깃별로 이미 크로스 컴파일러·musl 툴체인이 갖춰진 Docker 이미지를 대신 써서, 호스트에 아무것도 설치하지 않고 크로스 빌드를 수행한다.

6.2.4.1 설치와 기본 사용

# git 최신판 설치 (릴리스 바이너리·cargo-binstall 설치도 가능)
cargo install cross --git https://github.com/cross-rs/cross

# aarch64-unknown-linux-gnu 빌드 — Docker/Podman 필요
cross build --release --target aarch64-unknown-linux-gnu

cross는 ghcr.io/cross-rs/aarch64-unknown-linux-gnu 같은 이미지를 로컬에 없으면 자동으로 받아, 소스를 컨테이너에 마운트하고 그 안의 aarch64-linux-gnu-gcc로 링크한다 — 결과물은 호스트의 target/aarch64-unknown-linux-gnu/release/에 그대로 나온다. aarch64-unknown-linux-musl 타깃도 같은 방식으로 별도 musl 링커 설치 없이 바로 된다.

요구사항: Docker(≥20.10) 또는 Podman(≥3.4.0). Linux 호스트는 binfmt_misc 커널 지원과, 비-sudo 사용자라면 docker 그룹 소속(또는 rootless Docker)이 필요하다. Windows는 WSL2 + Docker Desktop(WSL2 백엔드) 조합을 권장한다.

6.2.4.2 Cross.toml로 커스터마이징

프로젝트에 추가 시스템 라이브러리(예: OpenSSL)가 필요하면 Cross.toml의 pre-build 훅으로 컨테이너 안에서 설치한다:

[target.aarch64-unknown-linux-gnu]
pre-build = [
    "dpkg --add-architecture $CROSS_DEB_ARCH",
    "apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH",
]

더 복잡한 요구가 있으면 target.<triple>.dockerfile로 커스텀 이미지를 지정할 수 있다 — 이때도 FROM ghcr.io/cross-rs/aarch64-unknown-linux-gnu:latest 위에 필요한 RUN만 추가하는 편이 크로스 C 툴체인을 처음부터 직접 구성하는 수고를 줄인다.

6.2.4.3 직접 툴체인 vs cross 비교

기준직접 설치(6.2.1)cross
초기 설정apt 한 줄 + .cargo/config.tomlDocker/Podman 설치만
musl 타깃별도 툴체인 필요(번거로움)그대로 됨
여러 타깃 병행타깃마다 apt 패키지 추가이미지가 타깃별로 이미 분리됨
빌드 속도빠름(네이티브 프로세스)컨테이너 오버헤드(대개 미미하지만 최초 이미지 다운로드는 느림)
CI 재현성러너 환경에 의존이미지 버전 고정으로 재현성 높음

견해: 개인 PC에서 가끔 빌드한다면 직접 설치가 더 간편하다. CI 파이프라인이나, musl·여러 타깃을 자주 오간다면 cross가 유지보수 부담을 크게 줄인다.

6.2.5 커뮤니티 스타터킷 unoq-starter-mpu (비공식)

커뮤니티 프로젝트 — Arduino 공식 지원이 아니다. crates.io에 unoq-starter-mpu(집필 시점 v0.1.0)로 배포되어 있고, 소스는 GitHub teenygrad/unoq-starter-rs에 공개돼 있다. 초기 버전(0.1.0)이므로 Makefile 타깃·구조가 바뀔 수 있다 — 실제 사용 전 저장소의 최신 README를 확인하라.

6.2.5.1 워크스페이스 구조

UNO Q의 두 프로세서에 맞춰 하나의 Cargo 워크스페이스에 두 크레이트를 둔 구조다.

크레이트대상 프로세서Rust 타깃성격
mpuQRB2210(Cortex-A53)aarch64-unknown-linux-gnuDebian Linux 사용자 공간 바이너리 — 이 장의 주제
mcuSTM32U585(Cortex-M33)thumbv8m.main-none-eabihfZephyr 기반 bare-metal no_std 펌웨어 — 3.2 참조

두 타깃 모두 미리 추가해 둔다:

rustup target add thumbv8m.main-none-eabihf
rustup target add aarch64-unknown-linux-gnu

MPU 쪽 크로스 컴파일은 6.2.1과 동일하게 gcc-aarch64-linux-gnu(또는 cross)로 처리한다.

6.2.5.2 Makefile 타깃 — MPU(이 장의 범위)

스타터킷은 make 타깃으로 빌드·전송·실행을 하나로 묶는다.

타깃동작
make mpu-buildaarch64용 크로스 컴파일만 수행
make mpu-install빌드 후 scp로 바이너리를 보드에 복사
make mpu-run / make mpu-deploy빌드 + 설치 + ssh로 보드에서 실행까지 한 번에

기본 대상 호스트는 unoq1.local이며, 환경 변수로 덮어쓴다:

make mpu-run DEVICE=unoq1.local DEVICE_USER=arduino

DEVICE·DEVICE_USER·DEVICE_DIR 세 변수로 각각 호스트명(또는 IP)·SSH 사용자·보드 쪽 배포 경로를 바꿀 수 있다. 이는 6.2.1~6.2.4의 rustup target add + 크로스 링커(또는 cross) 조합을 Makefile 한 계층으로 감싼 것일 뿐이므로, 내부적으로 무엇이 실행되는지 이해하고 있으면 실패했을 때 직접 단계별로 되짚어 디버깅할 수 있다.

6.2.5.3 참고 — MCU 쪽은 범위 밖

이 스타터킷의 mcu 크레이트는 make mcu-flash·make mcu-debug 등으로 STM32U585에 펌웨어를 플래시한다. 그런데 STM32U585의 SWD 디버그 핀은 UNO Q 보드 밖으로 노출돼 있지 않다 — 대신 QRB2210 MPU가 내부 SWD 디버그 브리지 역할을 하며 OpenOCD를 구동하고, PC에서는 ADB(USB)로 그 OpenOCD 포트에 접근한다. 즉 MCU 배포는 이 장의 SSH 기반 워크플로우가 아니라 ADB + OpenOCD 경로다 — MCU 실시간 개발은 3.2(Zephyr 기반 Arduino Core)·제8부(실시간 시스템 응용)에서 별도로 다룬다.

6.2.6 스타터킷 없이 수동으로 SSH 배포하기

스타터킷을 쓰지 않고 직접 스크립트를 짜도 어렵지 않다 — mpu-run이 하는 일을 그대로 풀어 쓴 것뿐이다.

#!/usr/bin/env bash
# deploy.sh — Rust 프로젝트를 UNO Q로 빌드·배포·실행
set -euo pipefail

TARGET=aarch64-unknown-linux-gnu
DEVICE="${DEVICE:-unoq.local}"
DEVICE_USER="${DEVICE_USER:-arduino}"
BIN_NAME=myapp

cargo build --release --target "$TARGET"
scp "target/$TARGET/release/$BIN_NAME" "$DEVICE_USER@$DEVICE:/home/$DEVICE_USER/"
ssh "$DEVICE_USER@$DEVICE" "chmod +x ~/$BIN_NAME && ~/$BIN_NAME"

SSH 키 기반 인증(ssh-copy-id)을 미리 설정해 두면 매번 비밀번호를 묻지 않는다 — 설정 방법은 5.2을 참고한다. rsync 기반의 더 견고한 배포 스크립트와 원격 디버깅은 6.3에서 다룬다.

6.2.7 정리

cargo build --target aarch64-unknown-linux-gnu 링커 확보 방법 직접 설치 gcc-aarch64-linux-gnu cross build (Docker, 제로 설정) aarch64 바이너리 scp/ssh 배포 (직접 스크립트 또는 unoq-starter-mpu) 보드에서 실행
  • Rust 크로스 컴파일의 기본기는 rustup target add + 크로스 링커 지정 세 가지 방법(6.2.1·6.2.2)이며, sysroot·glibc 문제는 6.1.5와 동일하게 적용된다(6.2.3).
  • cross는 Docker 이미지로 링커·musl 툴체인을 미리 갖춰 설정을 거의 없앤다(6.2.4).
  • 커뮤니티 스타터킷 unoq-starter-mpu는 이 워크플로우를 Makefile로 감싸 make mpu-run 한 줄로 만든다 — 공식 지원이 아니며 초기 버전임을 감안한다(6.2.5).
  • 다음 6.3에서는 Docker 크로스 빌드 이미지(dockcross류)와 QEMU 에뮬레이션, 그리고 rsync/gdbserver를 이용한 본격적인 SSH 배포·원격 디버깅 워크플로우로 이 두 장을 마무리한다.