크로스 컴파일#

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"

찾는 호스트 플랫폼이 아직 정의되어 있지 않다면 업스트림에 기여해 주십시오.

호스트 플랫폼 지정하기#

크로스 컴파일을 설정하는 메커니즘은 다음과 같이 동작합니다:

  1. 빌드 플랫폼 설정을 가져와 관례적으로 pkgs라고 부르는 현재 패키지 집합에 적용합니다.

    pkgs = import <nixpkgs> {}에서 빌드 플랫폼은 암묵적으로 현재 시스템이 됩니다. 이렇게 하면 빌드 플랫폼에서 컴파일하는 데 필요한 모든 의존성을 갖춘 빌드 환경 pkgs.stdenv가 만들어집니다.

  2. pkgsCross의 모든 패키지에 적절한 호스트 플랫폼 설정을 적용합니다.

    pkgs.pkgsCross.<host>.hello를 사용하면 빌드 플랫폼에서 컴파일되어 <host> 플랫폼에서 실행되는 hello 패키지가 만들어집니다.

호스트 플랫폼을 대상으로 하는 패키지에 접근하는 동등한 방법은 여러 가지가 있습니다.

  1. 빌드 플랫폼 환경 안에서 호스트 플랫폼 패키지를 명시적으로 선택합니다:

    1let
    2  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
    3  pkgs = import nixpkgs {};
    4in
    5pkgs.pkgsCross.aarch64-multiplatform.hello
    
  2. 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

다음 단계#