Rust 크로스 컴파일 — Windows/macOS/Linux 한 번에 빌드하기
Rust의 크로스 컴파일은 C++에 비하면 천국이다. 하지만 함정은 있다
실무 개발 경력 중 10년을 C++로 보냈다. 크로스 플랫폼 빌드는 악몽이었다. DLL vs SO, 아키텍처 차이, 컴파일러 호환성... 하루 종일 빌드만 했던 날도 있다.
Rust로 옮기고 나니 정말 쉽다. 같은 명령어로 Windows, macOS, Linux 바이너리가 나온다. 하지만 세부 사항을 모르면 함정에 빠진다.
기본: Rust 크로스 컴파일
설치:
cargo install cross
사용:
# Linux에서 Windows용 빌드
cross build --release --target x86_64-pc-windows-gnu
# macOS에서 Linux용 빌드
cross build --release --target x86_64-unknown-linux-gnu
# Linux에서 ARM 기기용 빌드 (라즈베리파이)
cross build --release --target armv7-unknown-linux-gnueabihf
끝이다. 정말로.
Target Triple 이해하기
Target triple은 빌드 대상을 정의한다: <cpu>-<vendor>-<os>-<env>
예:
- x86_64-pc-windows-msvc: Windows (MSVC 컴파일러)
- x86_64-pc-windows-gnu: Windows (MinGW 컴파일러)
- x86_64-apple-darwin: macOS Intel
- aarch64-apple-darwin: macOS Apple Silicon
- x86_64-unknown-linux-gnu: Linux x86_64
- aarch64-unknown-linux-gnu: Linux ARM64
전체 목록 확인:
rustc --print target-list | grep -i linux
rustc --print target-list | grep -i windows
GitHub Actions로 자동 빌드
모든 플랫폼을 자동으로 빌드하려면 GitHub Actions를 사용한다:
name: Cross-Platform Build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
# Windows
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: myapp.exe
# macOS Intel
- os: macos-latest
target: x86_64-apple-darwin
artifact_name: myapp
# macOS Apple Silicon
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: myapp
# Linux
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: myapp
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross
run: cargo install cross
- name: Build
run: cross build --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: myapp-${{ matrix.target }}
path: target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
조건부 컴파일
플랫폼별로 다른 코드를 실행해야 할 때:
#[cfg(target_os = "windows")]
fn get_config_path() -> PathBuf {
PathBuf::from(format!("{}\AppData\Local\myapp",
std::env::var("USERPROFILE").unwrap()))
}
#[cfg(target_os = "unix")]
fn get_config_path() -> PathBuf {
PathBuf::from(format!("{}/.config/myapp",
std::env::var("HOME").unwrap()))
}
#[cfg(target_arch = "x86_64")]
fn get_num_threads() -> usize {
8
}
#[cfg(target_arch = "arm")]
fn get_num_threads() -> usize {
4
}
의존성의 함정
모든 라이브러리가 크로스 플랫폼을 지원하는 건 아니다.
문제가 생기는 경우:
- 네이티브 코드 (C/C++ 바인딩)를 사용하는 라이브러리
- 윈도우 API (winapi)에 의존하는 라이브러리
- 특정 플랫폼만 지원하는 라이브러리
해결책:
[dependencies]
winapi = { version = "0.3", features = ["winuser"] }
[target.'cfg(windows)'.dependencies]
windows = "0.37"
[target.'cfg(unix)'.dependencies]
libc = "0.2"
테스트 전략
모든 플랫폼에서 테스트를 해야 한다:
# 로컬에서 특정 플랫폼으로 테스트
cross test --target x86_64-unknown-linux-gnu
# GitHub Actions에서 모든 플랫폼 테스트
- name: Test
run: cross test --release --target ${{ matrix.target }}
# 커버리지 (선택사항)
- name: Generate coverage
run: |
cargo install tarpaulin
cargo tarpaulin --out Xml --target ${{ matrix.target }}
실제 예제: 간단한 CLI 도구
// main.rs
use std::env;
#[cfg(target_os = "windows")]
const SEPARATOR: &str = "\";
#[cfg(target_os = "unix")]
const SEPARATOR: &str = "/";
fn main() {
let args: Vec<String> = env::args().collect();
println!("OS: {}", std::env::consts::OS);
println!("Arch: {}", std::env::consts::ARCH);
println!("Path separator: {}", SEPARATOR);
for arg in &args[1..] {
println!("Argument: {}", arg);
}
}
빌드 시간 최적화
크로스 컴파일은 느릴 수 있다. 최적화하려면:
# .cargo/config.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
# GitHub Actions 캐시
- uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
배포 자동화
빌드된 바이너리를 자동으로 릴리스하려면:
# Cargo.toml
[package]
name = "myapp"
version = "0.1.0"
# GitHub Actions
- name: Create Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: |
target/*/release/myapp
target/*/release/myapp.exe
실제 경험: 문제 해결
문제 1: Windows에서 OpenSSL 컴파일 실패
해결:
1. vcpkg 설치
2. VCPKG_ROOT 설정
3. OpenSSL 설치: vcpkg install openssl:x64-windows
문제 2: ARM 아키텍처에서 느린 빌드
해결:
1. 로컬 ARM 기기에서 빌드 (라즈베리파이)
2. Docker 사용
3. cargo-cross 사용 (권장)
결론
Rust의 크로스 컴파일은 정말 강력하다. C++과 비교하면 차원이 다르다.
하지만 주의할 점:
- 모든 의존성이 크로스 플랫폼을 지원하지는 않음
- 네이티브 코드가 있으면 복잡해짐
- 각 플랫폼에서 실제로 테스트해야 함
오랜 개발 경험에서 느낀 점: 이렇게 쉬운 크로스 컴파일이 C++에는 없었다. Rust가 정말 잘 설계되었다.