---
myst:
  html_meta:
    "description lang=en": "Packaging Existing Software With Nix"
    "keywords": "Nix, packaging"
---

(packaging-tutorial)=
# Nix로 기존 소프트웨어 패키징하기

```{contributors}
:authors: proofconstruction
:editors: fricklerhandwerk
```

Nix의 주요 사용 사례 중 하나는 의존성을 지정하고 가져오는 일처럼 소프트웨어를 패키징할 때 흔히 겪는 어려움을 해결하는 것입니다.

장기적으로 Nix는 이러한 문제를 덜어 줍니다. 하지만 기존 소프트웨어를 Nix로 *처음* 패키징할 때는 이해하기 어려워 보이는 오류를 마주치는 일이 흔합니다.

## 소개

이 튜토리얼에서는 C/C++ 소프트웨어를 패키지로 만들기 위한 첫 [Nix derivation](https://nix.dev/manual/nix/stable/language/derivations)을 작성합니다. 관련 작업의 상당 부분을 자동화해 주는 [Nixpkgs 표준 환경](https://nixos.org/manual/nixpkgs/stable/#part-stdenv)(`stdenv`)을 활용합니다.

### 무엇을 배우게 됩니까?

튜토리얼은 `stdenv`가 이미 제공하는 의존성만 필요로 하는 "hello world" 구현체인 `hello`로 시작합니다. 이어서 자체 의존성을 가진 더 복잡한 패키지를 빌드하며, 이 과정에서 추가적인 derivation 기능을 사용하게 됩니다.

Nix 오류 메시지, 빌드 실패를 비롯한 여러 문제를 마주하고 해결하면서 반복적인 디버깅 기법을 익혀 나가게 됩니다.

### 무엇이 필요합니까?

- Unix 셸과 일반 텍스트 편집기에 대한 익숙함
- [Nix 언어 읽기](reading-nix-language)에 자신이 있어야 합니다. 필요하다면 언제든 돌아가서 해당 튜토리얼을 먼저 진행하십시오.

### 얼마나 걸립니까?

모든 단계를 꼼꼼히 진행하면 약 60분이 걸립니다.

## 첫 번째 패키지

:::{note}
<!--
TODO: link to the Nix manual glossary entry once it's in a released build:
https://hydra.nixos.org/job/nix/master/build.x86_64-linux/latest/download/manual/glossary.html#package
-->
*패키지*는 느슨하게 정의된 개념으로, 파일과 기타 데이터의 모음을 가리키거나, 그러한 모음이 만들어지기 전에 이를 나타내는 {term}`Nix 표현식 <Nix expression>`을 가리킵니다. Nixpkgs의 패키지는 관례적인 구조를 갖추고 있어, 검색으로 찾을 수 있고 다른 패키지와 함께 환경에 조합할 수 있습니다.

이 튜토리얼에서 "패키지"는 derivation으로 평가되는 Nix 언어 함수를 말합니다. 이는 "기존 소프트웨어를 Nix로 패키징한" 결과로서, 여러분이나 다른 사람이 실제로 사용할 수 있는 결과물을 만들어 낼 수 있게 해 줍니다.
:::

먼저 다음과 같은 뼈대 derivation을 살펴보십시오.

```nix
{ stdenv }:

stdenv.mkDerivation {	}
```

이것은 `stdenv`를 포함하는 속성 집합을 받아 derivation을 만들어 내는 함수입니다(현재는 아무 일도 하지 않습니다).

### 패키지 함수

GNU Hello는 "hello world" 프로그램의 구현체로, 소스 코드는 [GNU 프로젝트의 FTP 서버에서](https://ftp.gnu.org/gnu/hello/) 받을 수 있습니다.

먼저 `mkDerivation`에 전달하는 집합에 `pname` 속성을 추가하십시오. 모든 패키지에는 이름과 버전이 필요하며, 이것이 없으면 Nix는 `error: derivation name missing` 오류를 발생시킵니다.

```diff

stdenv.mkDerivation {
+ pname = "hello";
+ version = "2.12.1";

```

다음으로 `hello`의 최신 버전에 대한 의존성을 선언하고, Nix가 `fetchzip`을 사용해 [소스 코드 아카이브](https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz)를 내려받도록 지시하십시오.

:::{note}
`fetchzip`은 zip 파일뿐만 아니라 [더 많은 종류의 아카이브](https://nixos.org/manual/nixpkgs/stable/#fetchurl)도 가져올 수 있습니다!
:::

해시는 아카이브를 다운로드하고 압축을 푼 뒤에야 알 수 있습니다. `fetchzip`에 전달한 해시가 올바르지 않으면 Nix가 오류를 보고합니다. `hash` 속성을 빈 문자열로 설정한 다음, 그 결과로 나오는 오류 메시지를 이용해 올바른 해시를 확인하십시오:

```nix
# hello.nix
{
  stdenv,
  fetchzip,
}:

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
    sha256 = "";
  };
}
```

이 파일을 `hello.nix`로 저장하고 `nix-build`를 실행하여 첫 번째 빌드 실패를 확인하십시오:

```console
$ nix-build hello.nix
error: cannot evaluate a function that has an argument without a value ('stdenv')
       Nix attempted to evaluate a function as a top level expression; in
       this case it must have its arguments supplied either by default
       values, or passed explicitly with '--arg' or '--argstr'. See
       https://nix.dev/manual/nix/stable/language/constructs.html#functions.

       at /home/nix-user/hello.nix:3:3:

            2| {
            3|   stdenv,
             |   ^
            4|   fetchzip,
```

문제: `hello.nix`의 표현식은 *함수*이며, 올바른 *인자*를 전달받아야만 의도한 출력을 만들어 냅니다.

### `nix-build`로 빌드하기

`stdenv`는 [`nixpkgs`](https://github.com/NixOS/nixpkgs/)에서 가져올 수 있으며, 이를 이 derivation에 인자로 전달하려면 다른 Nix 표현식에서 임포트해야 합니다.

권장하는 방법은 `hello.nix`와 같은 디렉터리에 다음 내용으로 `default.nix` 파일을 만드는 것입니다:

```nix
# default.nix
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
  hello = pkgs.callPackage ./hello.nix { };
}
```

이렇게 하면 현재 Nixpkgs에서 사용하는 관례와 비슷하게 `nix-build -A hello`를 실행하여 `hello.nix`의 derivation을 실현할 수 있습니다.

:::{note}
`callPackage`는 `pkgs`의 속성이 주어진 함수의 인자 속성 집합에서 요구하는 속성과 일치하면, 그 속성을 함수에 자동으로 전달합니다. 이 경우 `callPackage`는 `hello.nix`에 정의된 함수에 `stdenv`와 `fetchzip`를 전달합니다.

[](./callpackage.md) 튜토리얼에서 이 동작 방식을 자세히 설명합니다.
:::

이제 새 인자를 붙여 `nix-build` 명령을 실행하십시오:

```console
$ nix-build -A hello
error: hash mismatch in fixed-output derivation '/nix/store/pd2kiyfa0c06giparlhd1k31bvllypbb-source.drv':
         specified: sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
            got:    sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=
error: 1 dependencies of derivation '/nix/store/b4mjwlv73nmiqgkdabsdjc4zq9gnma1l-hello-2.12.1.drv' failed to build
```

### 파일 해시 찾기
예상대로 잘못된 파일 해시 때문에 오류가 발생했고, Nix가 올바른 해시를 친절하게 알려 주었습니다. `hello.nix`에서 빈 문자열을 올바른 해시로 바꾸십시오:

```nix
# hello.nix
{
  stdenv,
  fetchzip,
}:

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
    sha256 = "sha256-1kJjhtlsAkpNB7f6tZEs+dbKd8z7KoNHyDHEJ0tmhnc=";
  };
}
```

이제 앞의 명령을 다시 실행하십시오:

```console
$ nix-build -A hello
this derivation will be built:
  /nix/store/rbq37s3r76rr77c7d8x8px7z04kw2mk7-hello.drv
building '/nix/store/rbq37s3r76rr77c7d8x8px7z04kw2mk7-hello.drv'...
...
configuring
...
configure: creating ./config.status
config.status: creating Makefile
...
building
... <many more lines omitted>
```
derivation이 성공적으로 빌드되었습니다.

콘솔 출력을 보면 `configure`가 호출되어 `Makefile`을 생성했고, 이 파일로 프로젝트를 빌드했음을 알 수 있습니다. 이번에는 빌드 명령을 따로 작성할 필요가 없었습니다. `stdenv` 빌드 시스템이 [GNU Autoconf](https://www.gnu.org/software/autoconf/)를 기반으로 하며, 이것이 프로젝트 디렉터리의 구조를 자동으로 감지했기 때문입니다.

### 빌드 결과
작업 디렉터리에서 결과를 확인하십시오:

```console
$ ls
default.nix hello.nix  result
```

이 `result`는 빌드된 바이너리가 들어 있는 Nix 스토어 위치를 가리키는 [심볼릭 링크](https://en.wikipedia.org/wiki/Symbolic_link)입니다. `./result/bin/hello`를 호출하여 이 프로그램을 실행할 수 있습니다:

```console
$ ./result/bin/hello
Hello, world!
```

축하합니다. Nix로 첫 번째 프로그램을 성공적으로 패키징했습니다!

다음으로 `stdenv` 외부의 의존성이 있는 다른 소프트웨어를 패키지로 만들어 보겠습니다. 이러한 의존성은 새로운 과제를 제기하므로 더 많은 `mkDerivation` 기능을 활용해야 합니다.

## 의존성이 있는 패키지

이제 조금 더 복잡한 두 번째 프로그램인 [`icat`](https://github.com/atextor/icat)(터미널에 이미지를 표시하는 프로그램)을 추가하십시오.

앞 절의 `default.nix`에 `icat`을 위한 새 속성을 추가하십시오:

```nix
# default.nix
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-24.05";
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
  hello = pkgs.callPackage ./hello.nix { };
  icat = pkgs.callPackage ./icat.nix { };
}
```

`hello.nix`를 새 파일 `icat.nix`로 복사하고, 그 파일의 `pname` 및 `version` 속성을 수정하십시오:

```nix
# icat.nix
{
  stdenv,
  fetchzip,
}:

stdenv.mkDerivation {
  pname = "icat";
  version = "v0.5";

  src = fetchzip {
    # ...
  };
}
```

이제 소스 코드를 다운로드할 차례입니다. `icat`의 업스트림 저장소는 [GitHub](https://github.com/atextor/icat)에 호스팅되어 있으므로 이전의 [소스 페처](https://nixos.org/manual/nixpkgs/stable/#chap-pkgs-fetchers)를 교체해야 합니다. 이번에는 함수의 인자 속성 집합을 그에 맞게 수정하여 `fetchzip` 대신 [`fetchFromGitHub`](https://nixos.org/manual/nixpkgs/stable/#fetchfromgithub)를 사용하십시오:

```nix
# icat.nix
{
  stdenv,
  fetchFromGitHub,
}:

stdenv.mkDerivation {
  pname = "icat";
  version = "v0.5";

  src = fetchFromGitHub {
    # ...
  };
}
```

### GitHub에서 소스 가져오기
`fetchzip`에는 `url`과 `sha256` 인자가 필요했지만, [`fetchFromGitHub`](https://nixos.org/manual/nixpkgs/stable/#fetchfromgithub)에는 더 많은 인자가 필요합니다.

소스 URL은 `https://github.com/atextor/icat`이며, 여기에서 처음 두 인자를 바로 알 수 있습니다:
- `owner`: 저장소를 관리하는 계정의 이름

  ```
  owner = "atextor";
  ```
- `repo`: 가져올 저장소의 이름

  ```
  repo = "icat";
  ``````

프로젝트의 [Tags 페이지](https://github.com/atextor/icat/tags)로 이동하여 가져오려는 릴리스에 해당하는 Git 커밋 해시나 태그(예: `v1.0`)와 같은 적절한 [Git 리비전](https://git-scm.com/docs/revisions)(`rev`)을 찾으십시오.

이 경우 최신 릴리스 태그는 `v0.5`입니다.

`hello` 예제에서와 마찬가지로 해시도 지정해야 합니다. 이번에는 빈 문자열을 넣고 `nix-build`가 오류 메시지로 올바른 해시를 알려 주게 하는 대신, `nix-prefetch-url` 명령으로 처음부터 올바른 해시를 가져올 수 있습니다.

필요한 것은 tarball 파일 자체의 해시가 아니라 tarball *내용*의 SHA256 해시입니다. 따라서 `--unpack`과 `--type sha256` 인자를 전달하십시오:

```console
$ nix-prefetch-url --unpack https://github.com/atextor/icat/archive/refs/tags/v0.5.tar.gz --type sha256
path is '/nix/store/p8jl1jlqxcsc7ryiazbpm7c1mqb6848b-v0.5.tar.gz'
0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka
```

`fetchFromGitHub`에 올바른 해시를 설정하십시오:

```nix
# icat.nix
{
  stdenv,
  fetchFromGitHub,
}:

stdenv.mkDerivation {
  pname = "icat";
  version = "v0.5";

  src = fetchFromGitHub {
    owner = "atextor";
    repo = "icat";
    rev = "v0.5";
    sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
  };
}
```

### 누락된 의존성

새로 추가한 `icat` 속성에 대해서만 `nix-build`를 실행하면 완전히 새로운 문제가 보고됩니다:

```console
$ nix-build -A icat
these 2 derivations will be built:
  /nix/store/86q9x927hsyyzfr4lcqirmsbimysi6mb-source.drv
  /nix/store/l5wz9inkvkf0qhl8kpl39vpg2xfm2qpy-icat.drv
...
error: builder for '/nix/store/l5wz9inkvkf0qhl8kpl39vpg2xfm2qpy-icat.drv' failed with exit code 2;
       last 10 log lines:
       >                  from /nix/store/hkj250rjsvxcbr31fr1v81cv88cdfp4l-glibc-2.37-8-dev/include/stdio.h:27,
       >                  from icat.c:31:
       > /nix/store/hkj250rjsvxcbr31fr1v81cv88cdfp4l-glibc-2.37-8-dev/include/features.h:195:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wcpp-Wcpp8;;]
       >   195 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
       >       |   ^~~~~~~
       > icat.c:39:10: fatal error: Imlib2.h: No such file or directory
       >    39 | #include <Imlib2.h>
       >       |          ^~~~~~~~~~
       > compilation terminated.
       > make: *** [Makefile:16: icat.o] Error 1
       For full logs, run 'nix log /nix/store/l5wz9inkvkf0qhl8kpl39vpg2xfm2qpy-icat.drv'.
```

컴파일러 오류입니다. GitHub에서 `icat` 소스를 가져왔고 Nix가 이를 빌드하려고 했지만, 의존성인 `imlib2` 헤더가 없어서 컴파일에 실패했습니다.

[search.nixos.org에서 `imlib2`를 검색](https://search.nixos.org/packages?query=imlib2)하면 `imlib2`가 이미 Nixpkgs에 있음을 확인할 수 있습니다.

`icat.nix`에 있는 함수의 인자에 `imlib2`를 추가하여 이 패키지를 빌드 환경에 추가하십시오. 그런 다음 인자 값 `imlib2`를 `stdenv.mkDerivation`의 `buildInputs` 목록에 추가하십시오:

```nix
# icat.nix
{
  stdenv,
  fetchFromGitHub,
  imlib2,
}:

stdenv.mkDerivation {
  pname = "icat";
  version = "v0.5";

  src = fetchFromGitHub {
    owner = "atextor";
    repo = "icat";
    rev = "v0.5";
    sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
  };

  buildInputs = [ imlib2 ];
}
```

`nix-build -A icat`을 다시 실행하면 또 다른 오류가 발생하지만, 이번에는 컴파일이 더 진행됩니다:

```console
$ nix-build -A icat
this derivation will be built:
  /nix/store/bw2d4rp2k1l5rg49hds199ma2mz36x47-icat.drv
...
error: builder for '/nix/store/bw2d4rp2k1l5rg49hds199ma2mz36x47-icat.drv' failed with exit code 2;
       last 10 log lines:
       >                  from icat.c:31:
       > /nix/store/hkj250rjsvxcbr31fr1v81cv88cdfp4l-glibc-2.37-8-dev/include/features.h:195:3: warning: #warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE" [8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wcpp-Wcpp8;;]
       >   195 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
       >       |   ^~~~~~~
       > In file included from icat.c:39:
       > /nix/store/4fvrh0sjc8sbkbqda7dfsh7q0gxmnh9p-imlib2-1.11.1-dev/include/Imlib2.h:45:10: fatal error: X11/Xlib.h: No such file or directory
       >    45 | #include <X11/Xlib.h>
       >       |          ^~~~~~~~~~~~
       > compilation terminated.
       > make: *** [Makefile:16: icat.o] Error 1
       For full logs, run 'nix log /nix/store/bw2d4rp2k1l5rg49hds199ma2mz36x47-icat.drv'.
```

업스트림 코드에서 수정해야 할 경고가 몇 개 보입니다. 하지만 이 튜토리얼에서 중요한 부분은 `fatal error: X11/Xlib.h: No such file or directory`입니다. 또 다른 의존성이 누락되었다는 뜻입니다.

## 패키지 찾기

패키지 이름이 라이브러리나 프로그램 이름과 항상 일치하지는 않기 때문에, 의존성을 어디에서 가져와야 하는지 알아내는 일은 현재 다소 복잡합니다.

`X11` C 패키지의 `Xlib.h` 헤더가 필요합니다. 이에 해당하는 Nixpkgs derivation은 `libX11`이며, `xorg` 패키지 집합에 들어 있습니다. 이를 알아내는 방법은 여러 가지입니다:

### `search.nixos.org`

:::{tip}
필요한 것을 찾는 가장 쉬운 방법은 [search.nixos.org/packages](https://search.nixos.org/packages)를 이용하는 것입니다.
:::

안타깝게도 이 경우에는 X11이 워낙 널리 쓰이기 때문에 [`x11`을 검색](https://search.nixos.org/packages?query=x11)하면 관련 없는 결과가 너무 많이 나옵니다. 왼쪽 사이드바에 패키지 집합 목록이 있으며, [`xorg`를 선택](https://search.nixos.org/packages?buckets={%22package_attr_set%22%3A[%22xorg%22]%2C%22package_license_set%22%3A[]%2C%22package_maintainers_set%22%3A[]%2C%22package_platforms%22%3A[]}&query=x11)하면 쓸 만한 결과가 보입니다.

다른 방법이 모두 통하지 않을 때는 [Nixpkgs 소스 코드](https://github.com/nixos/nixpkgs)에서 키워드로 검색하는 데 익숙해지는 것이 도움이 됩니다.

### 로컬 코드 검색

소스에서 이름 할당을 찾으려면 `"<keyword> ="`를 검색하십시오. 예를 들어, 다음은 Github에서 [`"x11 = "`](https://github.com/search?q=repo%3ANixOS%2Fnixpkgs+%22x11+%3D%22&type=code) 또는 [`"libx11 ="`](https://github.com/search?q=repo%3ANixOS%2Fnixpkgs+%22libx11+%3D%22&type=code)를 검색한 결과입니다.

또는 [Nixpkgs 저장소](https://github.com/nixos/nixpkgs)를 클론하여 로컬에서 코드를 검색하십시오.

필요한 도구를 사용할 수 있는 셸을 시작하십시오. 버전 관리에는 `git`을, 코드 검색에는 [`ripgrep` 패키지](https://search.nixos.org/packages?show=ripgrep)에서 제공하는 `rg`를 사용합니다:
```console
$ nix-shell -p git ripgrep
[nix-shell:~]$
```

Nixpkgs 저장소는 매우 큽니다. 전체 클론을 오래 기다리지 않도록 최신 리비전만 클론하십시오:

```console
[nix-shell:~]$ git clone https://github.com/NixOS/nixpkgs --depth 1
...
[nix-shell:~]$ cd nixpkgs/
```

결과 범위를 좁히려면 모든 패키지 레시피가 들어 있는 `pkgs` 하위 디렉터리만 검색하십시오:

```console
[nix-shell:~]$ rg "x11 =" pkgs
pkgs/tools/X11/primus/default.nix
21:  primus = if useNvidia then primusLib_ else primusLib_.override { nvidia_x11 = null; };
22:  primus_i686 = if useNvidia then primusLib_i686_ else primusLib_i686_.override { nvidia_x11 = null; };

pkgs/applications/graphics/imv/default.nix
38:    x11 = [ libGLU xorg.libxcb xorg.libX11 ];

pkgs/tools/X11/primus/lib.nix
14:    if nvidia_x11 == null then libGL

pkgs/top-level/linux-kernels.nix
573:    ati_drivers_x11 = throw "ati drivers are no longer supported by any kernel >=4.1"; # added 2021-05-18;
... <a lot more results>
```

`rg`는 기본적으로 대소문자를 구분하므로, 빠뜨리는 것이 없도록 `-i`를 추가하십시오:

```
[nix-shell:~]$ rg -i "libx11 =" pkgs
pkgs/applications/version-management/monotone-viz/graphviz-2.0.nix
55:    ++ lib.optional (libX11 == null) "--without-x";

pkgs/top-level/all-packages.nix
14191:    libX11 = xorg.libX11;

pkgs/servers/x11/xorg/default.nix
1119:  libX11 = callPackage ({ stdenv, pkg-config, fetchurl, xorgproto, libpthreadstubs, libxcb, xtrans, testers }: stdenv.mkDerivation (finalAttrs: {

pkgs/servers/x11/xorg/overrides.nix
147:  libX11 = super.libX11.overrideAttrs (attrs: {
```

### 로컬 derivation 검색

명령줄에서 derivation을 검색하려면 [`nix-index`](https://github.com/nix-community/nix-index)의 `nix-locate`를 사용하십시오.

### 패키지 집합을 의존성으로 추가하기

derivation의 입력 속성 집합에 `xorg`를 추가하고 `buildInputs`에서 `xorg.libX11`을 사용하십시오:

```nix
# icat.nix
{
  stdenv,
  fetchFromGitHub,
  imlib2,
  xorg,
}:

stdenv.mkDerivation {
  pname = "icat";
  version = "v0.5";

  src = fetchFromGitHub {
    owner = "atextor";
    repo = "icat";
    rev = "v0.5";
    sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
  };

  buildInputs = [ imlib2 xorg.libX11 ];
}
```

:::{note}
Nix 언어는 지연 평가되므로, `xorg.libX11`에만 접근하면 `xorg` 속성 집합의 나머지 내용은 전혀 처리되지 않습니다.
:::

## 빌드 실패 해결하기

마지막 명령을 다시 실행하십시오:

```console
$ nix-build -A icat
this derivation will be built:
  /nix/store/x1d79ld8jxqdla5zw2b47d2sl87mf56k-icat.drv
...
error: builder for '/nix/store/x1d79ld8jxqdla5zw2b47d2sl87mf56k-icat.drv' failed with exit code 2;
       last 10 log lines:
       >   195 | # warning "_BSD_SOURCE and _SVID_SOURCE are deprecated, use _DEFAULT_SOURCE"
       >       |   ^~~~~~~
       > icat.c: In function 'main':
       > icat.c:319:33: warning: ignoring return value of 'write' declared with attribute 'warn_unused_result' [8;;https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wunused-result-Wunused-result8;;]
       >   319 |                                 write(tempfile, &buf, 1);
       >       |                                 ^~~~~~~~~~~~~~~~~~~~~~~~
       > gcc -o icat icat.o -lImlib2
       > installing
       > install flags: SHELL=/nix/store/8fv91097mbh5049i9rglc73dx6kjg3qk-bash-5.2-p15/bin/bash install
       > make: *** No rule to make target 'install'.  Stop.
       For full logs, run 'nix log /nix/store/x1d79ld8jxqdla5zw2b47d2sl87mf56k-icat.drv'.
```

누락된 의존성 오류는 해결되었지만, 이제 다른 문제가 있습니다: `make: *** No rule to make target 'install'.  Stop.`

### `installPhase`
`stdenv`는 `icat`에 포함된 `Makefile`을 자동으로 사용합니다. 콘솔 출력을 보면 `configure`와 `make`가 문제없이 실행되므로, `icat` 바이너리는 성공적으로 컴파일되고 있습니다.

실패는 `stdenv`가 `make install`을 실행하려고 할 때 발생합니다. 프로젝트에 포함된 `Makefile`에는 공교롭게도 `install` 타깃이 없습니다. `icat` 저장소의 `README`는 도구를 빌드할 때 `make`를 사용하라고만 언급하며, 설치 단계는 사용자에게 맡기고 있습니다.

이 단계를 derivation에 추가하려면 [`installPhase` 속성](https://nixos.org/manual/nixpkgs/stable/#ssec-install-phase)을 사용하십시오. 이 속성에는 설치를 수행하기 위해 실행되는 명령 문자열 목록이 들어 있습니다.

`make`가 성공적으로 완료되므로, 빌드 디렉터리에서 `icat` 실행 파일을 사용할 수 있습니다. 그곳에서 출력 디렉터리로 복사하기만 하면 됩니다.

Nix에서 출력 디렉터리는 `$out` 변수에 저장됩니다. 이 변수는 derivation의 [`builder` 실행 환경](https://nix.dev/manual/nix/2.19/language/derivations#builder-execution)에서 접근할 수 있습니다. `$out` 디렉터리 안에 `bin` 디렉터리를 만들고 `icat` 바이너리를 그곳에 복사하십시오:

```nix
# icat.nix
{
  stdenv,
  fetchFromGitHub,
  imlib2,
  xorg,
}:

stdenv.mkDerivation {
  pname = "icat";
  version = "v0.5";

  src = fetchFromGitHub {
    owner = "atextor";
    repo = "icat";
    rev = "v0.5";
    sha256 = "0wyy2ksxp95vnh71ybj1bbmqd5ggp13x3mk37pzr99ljs9awy8ka";
  };

  buildInputs = [ imlib2 xorg.libX11 ];

  installPhase = ''
    mkdir -p $out/bin
    cp icat $out/bin
  '';
}
```

### 단계와 훅

Nixpkgs의 `stdenv.mkDerivation` derivation은 여러 [단계](https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases)로 나뉩니다. 각 단계는 빌드 과정의 특정 측면을 제어하기 위한 것입니다.

앞서 `stdenv.mkDerivation`이 프로젝트의 `Makefile`에 `install` 타깃이 있기를 기대하며, 타깃이 없으면 실패하는 것을 확인했습니다. 이를 해결하기 위해 `icat` 바이너리를 올바른 출력 위치로 복사하는 명령을 담은 사용자 정의 `installPhase`를 정의하여, 사실상 바이너리를 설치했습니다. 그때까지는 `stdenv.mkDerivation`이 `icat` 패키지의 `buildPhase` 정보를 자동으로 결정했습니다.

derivation 실현 중에는 각 derivation 단계에서 실행될 수 있는 여러 셸 함수(Nixpkgs에서는 "훅"이라고 부릅니다)가 있습니다. 훅은 변수 설정, 파일 source, 디렉터리 생성 등의 작업을 수행합니다.

훅은 단계마다 고유하며, 해당 단계의 실행 전과 후에 모두 실행됩니다. 훅은 빌드 중 공통 작업을 위해 빌드 환경을 수정합니다.

직접 사용하지 않더라도, 정의하는 derivation 단계에 이러한 훅 호출을 포함하십시오. 이렇게 하면 나중에 derivation의 특정 부분을 쉽게 [오버라이드](https://nixos.org/manual/nixpkgs/stable/#chap-overrides)할 수 있습니다. 또한 코드를 깔끔하게 유지하고 읽기 쉽게 만들어 줍니다.

적절한 훅을 호출하도록 `installPhase`를 수정하십시오:

```nix
# icat.nix

# ...

  installPhase = ''
    runHook preInstall
    mkdir -p $out/bin
    cp icat $out/bin
    runHook postInstall
  '';

# ...

```

## 빌드 성공

`nix-build -A icat` 명령을 한 번 더 실행하면 마침내 원하는 작업이 반복 가능하게 수행됩니다. 로컬 디렉터리에서 `ls`를 호출하면 Nix 스토어의 위치를 가리키는 `result` 심볼릭 링크를 찾을 수 있습니다:

```console
$ ls
default.nix hello.nix icat.nix result
```

`result/bin/icat`은 앞서 빌드한 실행 파일입니다. 성공입니다!

속성을 지정하지 않고 `nix-build`를 실행하면 모든 속성을 한 번에 빌드합니다. 첫 번째(`hello`)는 `result/bin/` 아래에, 두 번째(`icat`)는 `result-2/bin/` 아래에 나타납니다. 속성을 더 추가하면 `result-n` 심볼릭 링크가 추가로 생성됩니다.

## 참고 자료

- [Nixpkgs 매뉴얼 - 표준 환경](https://nixos.org/manual/nixpkgs/unstable/#part-stdenv)

## 다음 단계

- [](callpackage-tutorial)
- [](sharing-dependencies)
- [](automatic-direnv)
- [](python-dev-environment)
- [Nixpkgs에 직접 만든 새 패키지 추가하기](https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md)
  - [](../contributing/how-to-contribute.md)
  - [](../contributing/how-to-get-help.md)
