크로스 컴파일#
Nixpkgs는 다양한 시스템 유형을 대상으로 소프트웨어를 크로스 컴파일하는 도구를 제공합니다.
무엇이 필요합니까?#
C 컴파일러 사용 경험
Nix 언어에 대한 기본 지식
플랫폼#
코드를 컴파일할 때는 실행 파일이 빌드되는 빌드 플랫폼과 컴파일된 실행 파일이 실행되는 호스트 플랫폼을 구분합니다. [1]
네이티브 컴파일은 두 플랫폼이 같은 특수한 경우입니다. 크로스 컴파일은 두 플랫폼이 서로 다른 일반적인 경우입니다.
크로스 컴파일은 호스트 플랫폼의 자원(CPU 등)이 제한적이거나 개발 용도로 호스트 플랫폼에 쉽게 접근할 수 없을 때 필요합니다.
Nixpkgs는 충분히 검증된 크로스 컴파일 지원을 제공합니다.
타깃 플랫폼이란 무엇입니까?#
플랫폼에는 타깃 플랫폼이라는 세 번째 개념도 있습니다.
타깃 플랫폼은 컴파일러 바이너리를 빌드하려는 경우에 관련이 있습니다. 이 경우 빌드 플랫폼에서 컴파일러를 빌드하고, 그 컴파일러를 호스트 플랫폼에서 실행하여 코드를 컴파일한 뒤, 최종 실행 파일을 타깃 플랫폼에서 실행합니다.
이런 경우는 드물기 때문에 이 튜토리얼에서는 타깃이 호스트와 같다고 가정합니다.
호스트 플랫폼 설정 알아내기#
빌드 플랫폼은 configure 단계에서 Nix가 자동으로 결정합니다.
호스트 플랫폼은 호스트 플랫폼에서 다음 명령을 실행하여 알아내는 것이 가장 좋습니다.
$ $(nix-build '<nixpkgs>' -I nixpkgs=channel:nixos-23.11 -A gnu-config)/config.guess
aarch64-unknown-linux-gnu
이것이 불가능한 경우(예를 들어 개발 용도로 호스트 플랫폼에 쉽게 접근할 수 없는 경우)에는 다음 템플릿에 따라 플랫폼 설정을 직접 구성해야 합니다.
<cpu>-<vendor>-<os>-<abi>
이 문자열 표현은 역사적인 이유로 nixpkgs에서 사용됩니다.
<vendor>는 unknown인 경우가 많고 <abi>는 선택 사항이라는 점에 유의하십시오. 또한 플랫폼을 가리키는 고유한 식별자는 없습니다. 예를 들어 unknown과 pc는 서로 바꿔 쓸 수 있습니다(그래서 스크립트 이름이 config.guess입니다).
Nix를 설치할 수 없다면 호스트 플랫폼에서 실행할 수 있는 OS에서 config.guess(보통 autoconf 패키지에 포함되어 있음)를 실행할 방법을 찾으십시오.
그 밖의 흔한 플랫폼 설정 예시는 다음과 같습니다.
aarch64-apple-darwin14
aarch64-pc-linux-gnu
x86_64-w64-mingw32
aarch64-apple-ios
참고
macOS/Darwin은 OS 전체가 오픈 소스는 아니므로 특수한 경우입니다. 크로스 컴파일은 aarch64-darwin과 x86_64-darwin 사이에서만 가능합니다. aarch64-darwin 지원은 최근에 추가되었으므로 크로스 컴파일은 거의 검증되지 않았습니다.
Nix로 호스트 플랫폼 선택하기#
nixpkgs에는 pkgsCross라는, 크로스 컴파일을 위해 미리 정의된 호스트 플랫폼 집합이 포함되어 있습니다.
nix repl에서 이를 살펴볼 수 있습니다:
참고
Nix 2.19부터 nix repl에는 -f / --file 플래그가 필요합니다:
$ nix repl -f '<nixpkgs>' -I nixpkgs=channel:nixos-23.11
$ nix repl '<nixpkgs>' -I nixpkgs=channel:nixos-23.11
Welcome to Nix 2.18.1. Type :? for help.
Loading '<nixpkgs>'...
Added 14200 variables.
nix-repl> pkgsCross.<TAB>
pkgsCross.aarch64-android pkgsCross.musl-power
pkgsCross.aarch64-android-prebuilt pkgsCross.musl32
pkgsCross.aarch64-darwin pkgsCross.musl64
pkgsCross.aarch64-embedded pkgsCross.muslpi
pkgsCross.aarch64-multiplatform pkgsCross.or1k
pkgsCross.aarch64-multiplatform-musl pkgsCross.pogoplug4
pkgsCross.aarch64be-embedded pkgsCross.powernv
pkgsCross.amd64-netbsd pkgsCross.ppc-embedded
pkgsCross.arm-embedded pkgsCross.ppc64
pkgsCross.armhf-embedded pkgsCross.ppc64-musl
pkgsCross.armv7a-android-prebuilt pkgsCross.ppcle-embedded
pkgsCross.armv7l-hf-multiplatform pkgsCross.raspberryPi
pkgsCross.avr pkgsCross.remarkable1
pkgsCross.ben-nanonote pkgsCross.remarkable2
pkgsCross.fuloongminipc pkgsCross.riscv32
pkgsCross.ghcjs pkgsCross.riscv32-embedded
pkgsCross.gnu32 pkgsCross.riscv64
pkgsCross.gnu64 pkgsCross.riscv64-embedded
pkgsCross.i686-embedded pkgsCross.scaleway-c1
pkgsCross.iphone32 pkgsCross.sheevaplug
pkgsCross.iphone32-simulator pkgsCross.vc4
pkgsCross.iphone64 pkgsCross.wasi32
pkgsCross.iphone64-simulator pkgsCross.x86_64-embedded
pkgsCross.mingw32 pkgsCross.x86_64-netbsd
pkgsCross.mingwW64 pkgsCross.x86_64-netbsd-llvm
pkgsCross.mmix pkgsCross.x86_64-unknown-redox
pkgsCross.msp430
크로스 컴파일 패키지의 이러한 속성 이름은 오랜 시간에 걸쳐 다소 자유롭게 정해졌습니다. 대개 이 이름들은 대응하는 플랫폼 설정 문자열과 일치하지 않습니다.
플랫폼 문자열은 pkgsCross.<platform>.stdenv.hostPlatform.config에서 가져올 수 있습니다:
nix-repl> pkgsCross.aarch64-multiplatform.stdenv.hostPlatform.config
"aarch64-unknown-linux-gnu"
찾는 호스트 플랫폼이 아직 정의되어 있지 않다면 업스트림에 기여해 주십시오.
호스트 플랫폼 지정하기#
크로스 컴파일을 설정하는 메커니즘은 다음과 같이 동작합니다:
빌드 플랫폼 설정을 가져와 관례적으로
pkgs라고 부르는 현재 패키지 집합에 적용합니다.pkgs = import <nixpkgs> {}에서 빌드 플랫폼은 암묵적으로 현재 시스템이 됩니다. 이렇게 하면 빌드 플랫폼에서 컴파일하는 데 필요한 모든 의존성을 갖춘 빌드 환경pkgs.stdenv가 만들어집니다.pkgsCross의 모든 패키지에 적절한 호스트 플랫폼 설정을 적용합니다.pkgs.pkgsCross.<host>.hello를 사용하면 빌드 플랫폼에서 컴파일되어<host>플랫폼에서 실행되는hello패키지가 만들어집니다.
호스트 플랫폼을 대상으로 하는 패키지에 접근하는 동등한 방법은 여러 가지가 있습니다.
빌드 플랫폼 환경 안에서 호스트 플랫폼 패키지를 명시적으로 선택합니다:
1let 2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11"; 3 pkgs = import nixpkgs {}; 4in 5pkgs.pkgsCross.aarch64-multiplatform.hello
nixpkgs를 임포트할 때 호스트 플랫폼을crossSystem에 전달합니다. 이렇게 하면nixpkgs의 모든 패키지가 호스트 플랫폼용으로 빌드되도록 설정됩니다:1let 2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11"; 3 pkgs = import nixpkgs { crossSystem = { config = "aarch64-unknown-linux-gnu"; }; }; 4in 5pkgs.hello
이와 동등하게, 호스트 플랫폼을
nix-build에 인자로 전달할 수도 있습니다:$ nix-build '<nixpkgs>' -I nixpkgs=channel:nixos-23.11 \ --arg crossSystem '{ config = "aarch64-unknown-linux-gnu"; }' \ -A hello
처음으로 크로스 컴파일하기#
hello 같은 패키지를 교차 컴파일하려면 플랫폼 속성(이 예에서는 aarch64-multiplatform)을 선택하고 다음을 실행하십시오:
$ nix-build '<nixpkgs>' -I nixpkgs=channel:nixos-23.11 \
-A pkgsCross.aarch64-multiplatform.hello
...
/nix/store/1dx87l5rav8679lqigf9xxkb7wvh2m4k-hello-aarch64-unknown-linux-gnu-2.12.1
참고
스토어 경로에 있는 패키지의 해시는 채널이 업데이트되면 바뀝니다.
빌드하려는 패키지를 찾으려면 패키지를 검색하여 속성 이름을 확인하십시오.
Hello World 예제의 실제 크로스 컴파일#
다음 예제는 Hello World 프로그램을 armv6l-unknown-linux-gnueabihf 및 x86_64-w64-mingw32(Windows) 플랫폼용 정적 실행 파일로 교차 컴파일하고, 그 결과로 나온 실행 파일을 에뮬레이터로 실행합니다.
다음과 같은 cross-compile.nix가 있다고 합시다:
1let
2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
3 pkgs = import nixpkgs {};
4
5 # Create a C program that prints Hello World
6 helloWorld = pkgs.writeText "hello.c" ''
7 #include <stdio.h>
8
9 int main (void)
10 {
11 printf ("Hello, world!\n");
12 return 0;
13 }
14 '';
15
16 # A function that takes host platform packages
17 crossCompileFor = hostPkgs:
18 # Run a simple command with the compiler available
19 hostPkgs.runCommandCC "hello-world-cross-test" {} ''
20 # Wine requires home directory
21 HOME=$PWD
22
23 # Compile our example using the compiler specific to our host platform
24 $CC ${helloWorld} -o hello
25
26 # Run the compiled program using user mode emulation (Qemu/Wine)
27 # buildPackages is passed so that emulation is built for the build platform
28 ${hostPkgs.stdenv.hostPlatform.emulator hostPkgs.buildPackages} hello > $out
29
30 # print to stdout
31 cat $out
32 '';
33in {
34 # Statically compile our example using the two platform hosts
35 rpi = crossCompileFor pkgs.pkgsCross.raspberryPi;
36 windows = crossCompileFor pkgs.pkgsCross.mingwW64;
37}
이 예제를 빌드하고 결과로 나온 두 derivation을 출력하면 각각 “Hello, world!”가 표시되어야 합니다:
$ cat $(nix-build cross-compile.nix)
Hello, world!
Hello, world!
교차 컴파일러를 갖춘 개발자 환경#
선언적이고 재현 가능한 환경에 관한 튜토리얼에서는 Nix가 프로젝트에 필요한 도구와 시스템 라이브러리를 제공하는 데 어떻게 도움이 되는지 살펴보았습니다.
musl을 사용해 정적 바이너리로 교차 컴파일하도록 설정된 컴파일러를 갖춘 환경을 제공할 수도 있습니다.
다음과 같은 shell.nix가 있다고 합시다:
1let
2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
3 pkgs = (import nixpkgs {}).pkgsCross.aarch64-multiplatform;
4in
5
6# callPackage is needed due to https://github.com/NixOS/nixpkgs/pull/126844
7pkgs.pkgsStatic.callPackage ({ mkShell, zlib, pkg-config, file }: mkShell {
8 # these tools run on the build platform, but are configured to target the host platform
9 nativeBuildInputs = [ pkg-config file ];
10 # libraries needed for the host platform
11 buildInputs = [ zlib ];
12}) {}
그리고 hello.c입니다:
#include <stdio.h>
int main (void)
{
printf ("Hello, world!\n");
return 0;
}
이를 크로스 컴파일할 수 있습니다:
$ nix-shell --run '$CC hello.c -o hello' shell.nix
그리고 aarch64용인지 확인합니다:
$ nix-shell --run 'file hello' shell.nix
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped
다음 단계#
공식 바이너리 캐시에는 크로스 컴파일된 패키지의 바이너리가 많지 않으므로, 다시 컴파일하는 시간을 줄이려면 자체 바이너리 캐시와 GitHub Actions를 사용한 CI를 설정하십시오.
Nixpkgs의 많은 컴파일러가 크로스 컴파일을 지원하지만, 모든 컴파일러가 지원하는 것은 아닙니다.
또한 크로스 컴파일을 지원하는 일은 간단하지 않으며, 테스트해야 할 조합이 매우 많기 때문에 일부 패키지는 빌드되지 않을 수도 있습니다.
Nix에서 크로스 컴파일이 어떻게 구현되어 있는지에 대한 자세한 설명이 이러한 문제를 해결하는 데 도움이 될 수 있습니다.
Nix 커뮤니티에는 크로스 컴파일 관련 도움을 받을 수 있는 전용 Matrix 방이 있습니다.