모범 사례#

Author:

Domen Kožar @domenkozar

Editors:

Valentin Gagarin @fricklerhandwerk, Silvan Mosberger @infinisil

URL#

Nix 언어 문법은 따옴표 없는 URL을 지원하므로 "https://example.com" 대신 https://example.com처럼 쓸 수도 있습니다.

따옴표 없는 URL을 폐기하자는 RFC 45가 채택되었으며, 이 RFC는 이 기능이 득보다 실이 많은 이유를 여러 가지 제시합니다.

팁

URL은 항상 따옴표로 감싸십시오.

재귀 속성 집합 rec { ... }#

rec을 사용하면 같은 속성 집합 안의 이름을 참조할 수 있습니다.

예시:

rec {
  a = 1;
  b = a + 2;
}
{ a = 1; b = 3; }

흔히 빠지는 함정은 이름을 가릴(shadowing) 때 디버깅하기 어려운 infinite recursion 오류를 일으키는 것입니다. 가장 간단한 예시는 다음과 같습니다.

let a = 1; in rec { a = a; }

팁

rec을 피하십시오. let ... in을 사용하십시오.

예시:

let
  a = 1;
in {
  a = a;
  b = a + 2;
}

팁

속성 집합에 명시적으로 이름을 붙이면 자기 참조를 할 수 있습니다.

let
  argset = {
    a = 1;
    b = argset.a + 2;
  };
in
  argset

with 스코프#

실제 코드에서는 여전히 다음과 같은 표현식을 흔히 볼 수 있습니다.

with (import <nixpkgs> {});

# ... lots of code

이렇게 하면 가져온 표현식의 모든 속성이 현재 표현식의 스코프로 들어옵니다.

이 방식에는 다음과 같은 문제가 있습니다.

  • 어떤 이름이 스코프에 있는지 알려면 이 파일을 실제로 평가해야 하므로, 정적 분석으로는 코드를 추론할 수 없습니다.

  • with를 두 개 이상 사용하면 이름이 어디에서 오는지 더 이상 분명하지 않습니다.

  • with의 스코프 규칙은 직관적이지 않습니다. 자세한 내용은 이 Nix 이슈를 참고하십시오.

팁

Nix 파일의 최상단에서 with를 사용하지 마십시오. let 표현식에서 이름을 명시적으로 할당하십시오.

예시:

let
  pkgs = import <nixpkgs> {};
  inherit (pkgs) curl jq;
in

# ...

스코프가 작으면 대개 문제가 덜하지만, 스코프 규칙 때문에 여전히 예상치 못한 결과가 생길 수 있습니다.

팁

with를 아예 피하고 싶다면 다음 형태의 표현식을

buildInputs = with pkgs; [ curl jq ];

다음과 같이 바꿔 보십시오.

buildInputs = builtins.attrValues {
  inherit (pkgs) curl jq;
};

<...> 조회 경로#

<nixpkgs>를 참조하는 Nix 언어 코드 예시를 자주 보게 될 것입니다.

<...>는 환경 변수 $NIX_PATH의 값에 편리하게 접근하려고 2011년에 도입된 특수 문법입니다.

즉, 조회 경로의 값은 외부 시스템 상태에 따라 달라집니다. 조회 경로를 사용하면 같은 Nix 표현식이 다른 결과를 낼 수 있습니다.

대부분의 경우 $NIX_PATH는 Nix를 설치할 때 최신 채널로 설정되므로 머신마다 다를 가능성이 높습니다.

참고

채널은 원격 Nix 표현식을 참조하고 그 최신 버전을 가져오는 메커니즘입니다.

구독한 채널의 상태는 그 채널에 의존하는 Nix 표현식의 외부에 있습니다. 이 상태는 머신 간에 쉽게 옮길 수 없습니다. 이는 재현성을 제한할 수 있습니다.

예를 들어, 서로 다른 머신을 쓰는 두 개발자의 <nixpkgs>는 Nixpkgs 저장소의 서로 다른 리비전을 가리킬 가능성이 높습니다. 빌드가 한쪽에서는 성공하고 다른 쪽에서는 실패하여 혼란을 일으킬 수 있습니다.

팁

재현성을 향하여: Nixpkgs 고정하기에 나온 기법을 사용하여 의존성을 명시적으로 선언하십시오.

최소한의 예제가 아니라면 조회 경로를 사용하지 마십시오.

일부 도구는 조회 경로가 설정되어 있을 것을 전제로 합니다. 이 경우에는 다음과 같이 하십시오.

팁

버전 관리되는 중앙 위치에서 $NIX_PATH를 알려진 값으로 설정하십시오.

NixOS

NixOS에서는 nix.nixPath 옵션으로 $NIX_PATH를 영구적으로 설정할 수 있습니다.

재현 가능한 Nixpkgs 설정#

시연용 패키지를 빠르게 얻기 위해 다음과 같은 간결한 패턴을 사용합니다.

1import <nixpkgs> {}

그러나 재현성을 향하여: Nixpkgs 고정하기에 나온 대로 <nixpkgs>를 대체하더라도 결과가 완전히 재현 가능하지 않을 수 있습니다. 역사적인 이유로 Nixpkgs 최상위 표현식이 설정 매개변수를 얻기 위해 기본적으로 파일 시스템을 비순수하게 읽기 때문입니다. 해당 파일이 채워져 있는 시스템에서는 다른 결과가 나올 수 있습니다.

이는 기존 설정을 깨뜨리지 않고는 해결할 수 없는 잘 알려진 문제입니다.

팁

Nixpkgs를 가져올 때 config와 overlays를 명시적으로 설정하십시오.

1import <nixpkgs> { config = {}; overlays = []; }

튜토리얼에서는 예제가 정확히 기대한 대로 동작하도록 이렇게 합니다. 최소한의 예제에서는 주의를 흩뜨리지 않도록 이를 생략합니다.

중첩된 속성 집합 업데이트하기#

속성 집합 업데이트 연산자는 두 속성 집합을 병합합니다.

예시:

{ a = 1; b = 2; } // { b = 3; c = 4; }
{ a = 1; b = 3; c = 4; }

그러나 오른쪽의 이름이 우선하며, 업데이트는 얕게 이루어집니다.

예시:

{ a = { b = 1; }; } // { a = { c = 3; }; }
{ a = { c = 3; }; }

여기서는 a 값 전체가 대체되었기 때문에 키 b가 완전히 제거되었습니다.

팁

Nixpkgs 함수 pkgs.lib.recursiveUpdate를 사용하십시오.

let pkgs = import <nixpkgs> {}; in
pkgs.lib.recursiveUpdate { a = { b = 1; }; } { a = { c = 3;}; }
{ a = { b = 1; c = 3; }; }

재현 가능한 소스 경로#

let pkgs = import <nixpkgs> {}; in

pkgs.stdenv.mkDerivation {
  name = "foo";
  src = ./.;
}

이 표현식을 담은 Nix 파일이 /home/myuser/myproject에 있다면 src의 스토어 경로는 /nix/store/<hash>-myproject가 됩니다.

문제는 이제 빌드가 상위 디렉터리 이름에 의존하므로 더 이상 재현 가능하지 않다는 점입니다. 이는 소스 코드에 선언할 수 없으며, 결과적으로 비순수성을 초래합니다.

누군가 다른 이름의 디렉터리에서 프로젝트를 빌드하면 src와 이에 의존하는 모든 것에 대해 다른 스토어 경로를 얻게 됩니다. 이는 불필요한 재빌드의 원인이 될 수 있습니다.

팁

name 속성을 고정된 값으로 설정한 속성 집합과 함께 builtins.path를 사용하십시오.

이렇게 하면 스토어 경로의 심볼릭 이름이 작업 디렉터리 대신 name에서 도출됩니다:

let pkgs = import <nixpkgs> {}; in

pkgs.stdenv.mkDerivation {
  name = "foo";
  src = builtins.path { path = ./.; name = "myproject"; };
}