---
date: 2022-09-08
myst:
  html_meta:
    "keywords": "tutorial, callPackage, override, package, customise, parameters, nix, nixpkgs"
---

(callpackage-tutorial)=
# `callPackage`를 이용한 패키지 매개변수와 오버라이드

```{contributors}
:authors: NobbZ
:editors: mmesch, fricklerhandwerk
```

Nix에는 패키지와 설정을 만들기 위한 특수 목적 프로그래밍 언어인 Nix 언어가 함께 제공됩니다. 이 언어는 {term}`Nixpkgs`로 알려진 Nix 패키지 모음을 빌드하는 데 사용됩니다.

Nix 언어는 순수 함수형 언어이므로 공통 패턴을 추상화하는 사용자 정의 함수를 선언할 수 있습니다. Nixpkgs에서 가장 두드러지는 패턴 중 하나는 패키지 레시피의 매개변수화입니다.

## 개요

Nixpkgs는 그 자체로 상당한 규모의 소프트웨어 프로젝트이며, 수년에 걸쳐 형성된 코딩 관례와 관용구를 가지고 있습니다. Nixpkgs는 [`callPackage`](https://github.com/NixOS/nixpkgs/commit/fd268b4852d39c18e604c584dd49a611dc795a9b)라는 함수를 통해 매개변수화된 패키지를 자동 설정으로 조합하는 [관례를 확립했습니다](https://github.com/nixos/nixpkgs/commit/d17f0f9cbca38fabb71624f069cd4c0d6feace92). 이 튜토리얼에서는 이 함수를 사용하는 방법과 그 이점을 보여 줍니다.

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

- `callPackage`를 사용하여 Nixpkgs 관례를 따르는 패키지 레시피 호출하기
- 패키지 매개변수 오버라이드하기
- 상호 의존적인 패키지 집합 만들기

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

- [Nix 언어](reading-nix-language)에 대한 이해
- [기존 소프트웨어 패키징](packaging-tutorial)에 대한 첫 경험

### 얼마나 걸립니까?

- 45분

## 자동 함수 호출

Nixpkgs에서 볼 수 있는 전형적인 패키지 레시피라고 할 수 있는 새 파일 `hello.nix`를 만드십시오. 이는 최상위 패키지 집합의 derivation에 대응하는 속성을 가진 속성 집합을 받아 derivation을 반환하는 함수입니다.

```{code-block} nix
:caption: hello.nix
{ writeShellScriptBin }:
writeShellScriptBin "hello" ''
  echo "Hello, world!"
''
```

:::{dropdown} 자세한 설명
`hello.nix`는 `writeShellScriptBin`이라는 요소 하나를 가진 속성 집합을 인수로 받는 함수를 선언합니다. [`writeShellScriptBin`](https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeShellScriptBin)은 마침 Nixpkgs에 존재하는 함수로, derivation을 반환하는 [빌드 헬퍼](https://nixos.org/manual/nixpkgs/unstable/#part-builders)입니다. 이 경우 derivation 출력에는 실행하면 "Hello world"를 출력하는 실행 가능한 셸 스크립트가 `$out/bin/hello`에 포함됩니다.
:::

이제 다음 내용으로 `default.nix` 파일을 만드십시오.

```{code-block} nix
:caption: default.nix
let
  pkgs = import <nixpkgs> { };
in
pkgs.callPackage ./hello.nix { }
```

`default.nix`의 derivation을 실현하고 생성된 실행 파일을 실행하십시오.

```shell-session
$ nix-build
$ ./result/bin/hello
Hello, world!
```

`hello.nix`의 함수가 평가될 때 인수 `writeShellScriptBin`이 자동으로 채워집니다. `callPackage`는 함수 인수의 모든 속성에 대해, `pkgs` 속성 집합에 해당 속성이 있으면 그 속성을 전달합니다.

이렇게 간단한 구성에서 패키지를 위해 별도의 파일 `hello.nix`를 만드는 것은 번거로워 보일 수 있습니다. 그렇게 한 이유는 Nixpkgs가 정확히 이런 방식으로 구성되어 있기 때문입니다. 모든 패키지 레시피는 함수를 선언하는 파일입니다. 이 함수는 패키지의 의존성을 인수로 받습니다.

## 매개변수화된 빌드

`default.nix`를 수정하여 derivation의 속성 집합을 만들고, 속성 `hello`에 원래 derivation이 담기도록 하십시오:

```{code-block} nix
:caption: default.nix
let
  pkgs = import <nixpkgs> { };
in
{
  hello = pkgs.callPackage ./hello.nix { };
}
```

[`-A` / `--attr` 옵션](https://nix.dev/manual/nix/2.19/command-ref/nix-build#opt-attr)으로 속성 `hello`에 접근하여 빌드하면 결과는 이전과 같습니다:

```shell-session
$ nix-build -A hello
$ ./result/bin/hello
Hello, world!
```

또한 `hello.nix`를 수정하여 기본값이 `"world"`인 매개변수 `audience`를 추가하십시오:

```{code-block} nix
:caption: hello.nix
{
  writeShellScriptBin,
  audience ? "world",
}:
writeShellScriptBin "hello" ''
  echo "Hello, ${audience}!"
''
```

이 변경도 결과를 바꾸지 않습니다.

이 새 인수를 사용하도록 `default.nix`를 수정하면 더 흥미로워집니다. `callPackage`의 두 번째 인수로 매개변수 `audience`를 전달하십시오:

```{code-block} diff
:caption: default.nix
 let
   pkgs = import <nixpkgs> { };
 in
 {
-  hello = pkgs.callPackage ./hello.nix { };
+  hello = pkgs.callPackage ./hello.nix { audience = "people"; };
 }
```

이 속성은 `hello.nix`에 정의된 함수의 인수로 전달됩니다. 같은 구문으로 `writeShellScriptBin`처럼 자동으로 찾아지는 인수를 명시적으로 설정할 수도 있지만, 여기서는 그렇게 할 필요가 없습니다.

직접 해 보십시오:

```shell-session
$ nix-build -A hello
$ ./result/bin/hello
Hello, people!
```

이 패턴은 Nixpkgs에서 널리 사용됩니다. 예를 들어 Go 프로그램을 나타내는 함수에는 `buildGoModule` 매개변수가 있는 경우가 많습니다. 기본 Go 컴파일러 버전을 바꾸기 위해 `callPackage ./go-program.nix { buildGoModule = buildGo116Module; }`와 같은 표현식을 쓰는 경우를 흔히 볼 수 있습니다. 따라서 Nixpkgs는 단순히 미리 설정된 패키지를 모아 둔 거대한 라이브러리가 아니라, 코드를 중복하지 않고도 패키지는 물론 생태계 전체(예: "내가 직접 설정한 인터프리터를 사용하는 모든 Python 패키지")를 즉석에서 사용자 정의할 수 있는 함수, 즉 패키지 *레시피*의 모음입니다.

# 오버라이드

`callPackage`는 반환된 derivation의 `override` 함수를 사용해 매개변수를 _나중에_ 사용자 정의할 수 있게 하여 편의성을 더합니다.

`default.nix`에 세 번째 속성 `hello-folks`를 추가하고, `audience`에 새 값을 넘겨 호출한 `hello.override`를 그 값으로 설정하십시오:

```{code-block} diff
:caption: default.nix
 let
   pkgs = import <nixpkgs> { };
 in
-{
+rec {
   hello = pkgs.callPackage ./hello.nix { audience = "people"; };
+  hello-folks = hello.override { audience = "folks"; };
 }
```

:::{note}
이제 결과 속성 집합은 (키워드 `rec`에 의해) 재귀적입니다. 즉, 속성 값이 같은 속성 집합 안의 이름을 참조할 수 있습니다.
:::

`override`는 `hello.nix`에 있는 원래 함수에 `audience`를 전달합니다. 이 값은 derivation `hello`를 만든 원래의 `callPackage`에 어떤 인수가 전달되었든 그 인수를 *오버라이드*합니다. 나머지 매개변수는 모두 그대로 유지됩니다. 이 기능은 특히 유용하며, 패키지를 사용자 정의할 수 있는 옵션을 많이 제공하는 패키지에서 자주 볼 수 있습니다.

`hello-folks` 속성을 빌드하고 결과 실행 파일을 실행하면 이번에도 새로운 버전의 스크립트가 만들어집니다:

```shell-session
$ nix-build -A hello-folks
$ ./result/bin/hello
Hello, folks!
```

실제 예로는 [`neovim`](https://search.nixos.org/packages?show=neovim) 패키지 레시피가 있습니다. 이 레시피에는 `extraLuaPackages`, `extraPythonPackages`, `withRuby`와 같이 오버라이드할 수 있는 인수가 있습니다. 현재 이러한 매개변수는 소스 코드를 읽어야만 알 수 있으며, 소스 코드는 [search.nixos.org/packages](https://search.nixos.org/packages)에서 📦 Source 링크를 따라가면 찾을 수 있습니다.

## 상호 의존하는 패키지 집합

실제로 `callPackage`를 직접 만들 수도 있습니다! 이는 레시피가 서로 의존하는 패키지 집합에서 유용합니다.

:::{note}
다음 예제에서는 "호출되는" 파일을 보여 주지 않습니다. 원리를 이해하는 데 필요하지 않기 때문입니다.
:::

다음과 같은 derivation의 재귀 속성 집합을 살펴보십시오:

```{code-block} nix
:caption: default.nix
let
  pkgs = import <nixpkgs> { };
in
rec {
  a = pkgs.callPackage ./a.nix { };
  b = pkgs.callPackage ./b.nix { inherit a; };
  c = pkgs.callPackage ./c.nix { inherit b; };
  d = pkgs.callPackage ./d.nix { };
  e = pkgs.callPackage ./e.nix { inherit c d; };
}
```

:::{note}
여기서 `inherit a;`는 `a = a;`와 같습니다.
:::

앞서 선언한 derivation은 `callPackage`를 통해 다른 derivation에 인자로 전달됩니다.

이 경우 각 패키지가 요구하는 인자 중 Nixpkgs에 없는 것은 모두 해당 {term}`Nix 파일 <Nix file>`에서 직접 지정해야 한다는 점을 기억해야 합니다. `./b.nix`가 인자 `a`를 요구하는데 `pkgs.a`가 없으면 함수 호출 시 오류가 발생합니다. 이 작업은 금세 상당히 번거로워질 수 있으며, 패키지 집합이 클수록 더욱 그렇습니다.

`lib.callPackageWith`를 사용하여 속성 집합을 기반으로 나만의 `callPackage`를 만드십시오.

```{code-block} nix
:caption: default.nix
let
  pkgs = import <nixpkgs> { };
  callPackage = pkgs.lib.callPackageWith (pkgs // packages);
  packages = {
    a = callPackage ./a.nix { };
    b = callPackage ./b.nix { };
    c = callPackage ./c.nix { };
    d = callPackage ./d.nix { };
    e = callPackage ./e.nix { };
  };
in
packages
```

이 부분은 약간의 설명이 필요합니다.

먼저, 이제는 재귀 속성 집합 대신 `let` 바인딩에서 우리가 다루는 이름을 할당한다는 점에 주목하십시오. 이 방식은 재귀 집합과 같은 성질을 가집니다. 즉, 등호(`=`) 왼쪽의 이름을 오른쪽 표현식에서 사용할 수 있습니다. 그래서 `//` 연산자로 `packages`의 내용을 기존 속성 집합 `pkgs`와 병합할 때 `packages`를 참조할 수 있습니다.

이제 직접 만든 `callPackages`는 호출된 패키지 함수에 `pkgs` *및* `packages`의 모든 속성을 제공하며(이름이 같으면 `packages`의 것이 우선합니다), `packages`는 호출될 때마다 재귀적으로 구성됩니다.

마지막 부분은 머리를 어지럽게 만들 수도 있습니다. 이러한 구성은 Nix 언어가 지연 평가되기 때문에 비로소 가능합니다. 즉, 값은 실제로 필요할 때에만 계산됩니다. 덕분에 `packages`를 완전히 정의하지 않은 상태에서도 이리저리 전달할 수 있습니다.

이제 이 수준에서는 각 패키지의 의존성이 암묵적이며(각 패키지 파일에서는 여전히 명시적입니다), `callPackage`가 이를 *알아서 척척* 해결합니다. 덕분에 의존성을 직접 처리할 필요가 없어지고, 긴 빌드 과정의 후반에야 드러날 수 있는 설정 오류를 미리 방지할 수 있습니다.

물론 이 작은 예제는 원래 형태로도 충분히 관리할 수 있습니다. 또한 암묵적으로 재귀적인 이 방식은 지연 평가에 익숙하지 않은 소프트웨어 개발자에게 구조를 불분명하게 만들어, 이전보다 읽기 어렵게 할 수 있습니다. 하지만 이 이점은 코드의 양 때문에 구조가 불분명해지고 수동 수정이 번거롭고 오류가 발생하기 쉬워지는 대규모 구성에서 진가를 발휘합니다.

## 요약

`callPackage`를 사용하면 Nixpkgs 관례를 따르게 되어 숙련된 Nix 사용자가 코드를 이해하기 쉬워질 뿐만 아니라, 다음과 같은 이점도 덤으로 얻을 수 있습니다.

1. 매개변수화된 빌드
2. 오버라이드 가능한 빌드
3. 상호 의존적인 패키지 집합의 간결한 구현

## 참고 자료

- [Nixpkgs 매뉴얼: `callPackageWith`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.customisation.callPackageWith)

## 다음 단계

- [](file-sets-tutorial) - Nix로 자신의 프로젝트를 패키지로 만드는 방법을 배웁니다
- [](module-system-deep-dive) - NixOS를 뒷받침하는 함수형 프로그래밍의 마법을 다루는 방법을 배웁니다
