---
myst:
  html_meta:
    "description lang=en": "Cross compilation tutorial using Nix"
    "keywords": "Nix, cross compilation, cross-compile, Nix"
---


(cross-compilation)=

# 크로스 컴파일

Nixpkgs는 다양한 시스템 유형을 대상으로 소프트웨어를 크로스 컴파일하는 도구를 제공합니다.

## 무엇이 필요합니까?


- C 컴파일러 사용 경험
- [Nix 언어](<reading-nix-language>)에 대한 기본 지식

## 플랫폼

코드를 컴파일할 때는 실행 파일이 *빌드되는* **빌드 플랫폼**과 컴파일된 실행 파일이 *실행되는* **호스트 플랫폼**을 구분합니다. [^id3]

**네이티브 컴파일**은 두 플랫폼이 같은 특수한 경우입니다. **크로스 컴파일**은 두 플랫폼이 서로 다른 일반적인 경우입니다.

크로스 컴파일은 호스트 플랫폼의 자원(CPU 등)이 제한적이거나 개발 용도로 호스트 플랫폼에 쉽게 접근할 수 없을 때 필요합니다.

Nixpkgs는 충분히 검증된 크로스 컴파일 지원을 제공합니다.

[^id3]: 크로스 컴파일 플랫폼을 가리키는 용어는 빌드 시스템마다 다릅니다. 이 문서에서는 [autoconf 용어](https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Hosts-and-Cross_002dCompilation.html)를 따르기로 했습니다.

## 타깃 플랫폼이란 무엇입니까?

플랫폼에는 **타깃 플랫폼**이라는 세 번째 개념도 있습니다.

타깃 플랫폼은 컴파일러 바이너리를 빌드하려는 경우에 관련이 있습니다. 이 경우 *빌드 플랫폼*에서 컴파일러를 빌드하고, 그 컴파일러를 *호스트 플랫폼*에서 실행하여 코드를 컴파일한 뒤, 최종 실행 파일을 *타깃 플랫폼*에서 실행합니다.

이런 경우는 드물기 때문에 이 튜토리얼에서는 타깃이 호스트와 같다고 가정합니다.

## 호스트 플랫폼 설정 알아내기

빌드 플랫폼은 configure 단계에서 Nix가 자동으로 결정합니다.

호스트 플랫폼은 호스트 플랫폼에서 다음 명령을 실행하여 알아내는 것이 가장 좋습니다.

```shell-session
$ $(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

:::{note}
macOS/Darwin은 OS 전체가 오픈 소스는 아니므로 특수한 경우입니다. 크로스 컴파일은 `aarch64-darwin`과 `x86_64-darwin` 사이에서만 가능합니다. `aarch64-darwin` 지원은 최근에 추가되었으므로 크로스 컴파일은 거의 검증되지 않았습니다.
:::

## Nix로 호스트 플랫폼 선택하기

`nixpkgs`에는 `pkgsCross`라는, 크로스 컴파일을 위해 미리 정의된 호스트 플랫폼 집합이 포함되어 있습니다.

`nix repl`에서 이를 살펴볼 수 있습니다:

:::{note}
[Nix 2.19부터](https://nix.dev/manual/nix/latest/release-notes/rl-2.19) `nix repl`에는 `-f` / `--file` 플래그가 필요합니다:
```shell-session
$ nix repl -f '<nixpkgs>' -I nixpkgs=channel:nixos-23.11
```
:::

```shell-session
$ 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`에서 가져올 수 있습니다:

```shell-session
nix-repl> pkgsCross.aarch64-multiplatform.stdenv.hostPlatform.config
"aarch64-unknown-linux-gnu"
```

찾는 호스트 플랫폼이 아직 정의되어 있지 않다면 [업스트림에 기여해 주십시오](https://github.com/NixOS/nixpkgs/blob/master/lib/systems/examples.nix).

## 호스트 플랫폼 지정하기

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

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

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

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

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

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

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

   ```nix
   let
     nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
     pkgs = import nixpkgs {};
   in
   pkgs.pkgsCross.aarch64-multiplatform.hello
   ```

2. `nixpkgs`를 임포트할 때 호스트 플랫폼을 `crossSystem`에 전달합니다. 이렇게 하면 `nixpkgs`의 모든 패키지가 호스트 플랫폼용으로 빌드되도록 설정됩니다:

   ```nix
   let
     nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
     pkgs = import nixpkgs { crossSystem = { config = "aarch64-unknown-linux-gnu"; }; };
   in
   pkgs.hello
   ```

   이와 동등하게, 호스트 플랫폼을 `nix-build`에 인자로 전달할 수도 있습니다:

   ```sh
   $ nix-build '<nixpkgs>' -I nixpkgs=channel:nixos-23.11 \
     --arg crossSystem '{ config = "aarch64-unknown-linux-gnu"; }' \
     -A hello
   ```

## 처음으로 크로스 컴파일하기

[hello](https://www.gnu.org/software/hello/) 같은 패키지를 교차 컴파일하려면 플랫폼 속성(이 예에서는 `aarch64-multiplatform`)을 선택하고 다음을 실행하십시오:

```shell-session
$ 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
```

:::{note}
스토어 경로에 있는 패키지의 해시는 채널이 업데이트되면 바뀝니다.
:::

빌드하려는 패키지를 찾으려면 [패키지를 검색](https://search.nixos.org/packages)하여 속성 이름을 확인하십시오.

## Hello World 예제의 실제 크로스 컴파일

다음 예제는 Hello World 프로그램을 `armv6l-unknown-linux-gnueabihf` 및 `x86_64-w64-mingw32`(Windows) 플랫폼용 정적 실행 파일로 교차 컴파일하고, 그 결과로 나온 실행 파일을 [에뮬레이터](https://en.wikipedia.org/wiki/Emulator)로 실행합니다.

다음과 같은 `cross-compile.nix`가 있다고 합시다:

```nix
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
  pkgs = import nixpkgs {};

  # Create a C program that prints Hello World
  helloWorld = pkgs.writeText "hello.c" ''
    #include <stdio.h>

    int main (void)
    {
      printf ("Hello, world!\n");
      return 0;
    }
  '';

  # A function that takes host platform packages
  crossCompileFor = hostPkgs:
    # Run a simple command with the compiler available
    hostPkgs.runCommandCC "hello-world-cross-test" {} ''
      # Wine requires home directory
      HOME=$PWD

      # Compile our example using the compiler specific to our host platform
      $CC ${helloWorld} -o hello

      # Run the compiled program using user mode emulation (Qemu/Wine)
      # buildPackages is passed so that emulation is built for the build platform
      ${hostPkgs.stdenv.hostPlatform.emulator hostPkgs.buildPackages} hello > $out

      # print to stdout
      cat $out
    '';
in {
  # Statically compile our example using the two platform hosts
  rpi = crossCompileFor pkgs.pkgsCross.raspberryPi;
  windows = crossCompileFor pkgs.pkgsCross.mingwW64;
}
```

이 예제를 빌드하고 결과로 나온 두 derivation을 출력하면 각각 "Hello, world!"가 표시되어야 합니다:

```shell-session
$ cat $(nix-build cross-compile.nix)
Hello, world!
Hello, world!
```

## 교차 컴파일러를 갖춘 개발자 환경

{ref}`선언적이고 재현 가능한 환경에 관한 튜토리얼 <declarative-reproducible-envs>`에서는 Nix가 프로젝트에 필요한 도구와 시스템 라이브러리를 제공하는 데 어떻게 도움이 되는지 살펴보았습니다.

**musl을 사용해 정적 바이너리로 교차 컴파일**하도록 설정된 컴파일러를 갖춘 환경을 제공할 수도 있습니다.

다음과 같은 `shell.nix`가 있다고 합시다:

```nix
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/release-23.11";
  pkgs = (import nixpkgs {}).pkgsCross.aarch64-multiplatform;
in

# callPackage is needed due to https://github.com/NixOS/nixpkgs/pull/126844
pkgs.pkgsStatic.callPackage ({ mkShell, zlib, pkg-config, file }: mkShell {
  # these tools run on the build platform, but are configured to target the host platform
  nativeBuildInputs = [ pkg-config file ];
  # libraries needed for the host platform
  buildInputs = [ zlib ];
}) {}
```

그리고 `hello.c`입니다:

```{code-block} c hello.c
#include <stdio.h>

int main (void)
{
  printf ("Hello, world!\n");
  return 0;
}
```

이를 크로스 컴파일할 수 있습니다:

```shell-session
$ nix-shell --run '$CC hello.c -o hello' shell.nix
```

그리고 aarch64용인지 확인합니다:

```shell-session
$ 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
```

## 다음 단계

- [공식 바이너리 캐시](https://cache.nixos.org)에는 크로스 컴파일된 패키지의 바이너리가 많지 않으므로, 다시 컴파일하는 시간을 줄이려면 {ref}`자체 바이너리 캐시와 GitHub Actions를 사용한 CI <github-actions>`를 설정하십시오.

- Nixpkgs의 많은 컴파일러가 크로스 컴파일을 지원하지만, 모든 컴파일러가 지원하는 것은 아닙니다.

  또한 크로스 컴파일을 지원하는 일은 간단하지 않으며, 테스트해야 할 조합이 매우 많기 때문에 일부 패키지는 빌드되지 않을 수도 있습니다.

  [Nix에서 크로스 컴파일이 어떻게 구현되어 있는지에 대한 자세한 설명](https://nixos.org/manual/nixpkgs/stable/#chap-cross)이 이러한 문제를 해결하는 데 도움이 될 수 있습니다.

- Nix 커뮤니티에는 크로스 컴파일 관련 도움을 받을 수 있는 [전용 Matrix 방](https://matrix.to/#/#cross-compiling:nixos.org)이 있습니다.
