feat: the nix language

This commit is contained in:
Ryan Yin 2023-07-08 17:22:49 +08:00
parent c6b61ab558
commit 99a07b8b28

View File

@ -1,6 +1,5 @@
# Nix 语言入门
Nix 语言是 Nix 包管理器的基础,要想玩得转 NixOS 与 Nix Flakes享受到它们带来的诸多好处就必须学会这门语言。
Nix 是一门比较简单的函数式语言,在已有一定编程基础的情况下,过一遍这些语法用时应该在 2 个小时以内,本文假设你具有一定编程基础(也就是说写得不会很细)。
@ -18,7 +17,7 @@ Nix 是一门比较简单的函数式语言,在已有一定编程基础的情
先把语法过一遍,有个大概的印象就行,后面需要用到时再根据右侧目录回来复习。
## 1. 基础数据类型一览 {#basic-data-types}
## 基础数据类型一览 {#basic-data-types}
下面通过一个 attribute set (这类似 json 或者其他语言中的 map/dict来简要说明所有基础数据类型
@ -52,7 +51,7 @@ Nix 是一门比较简单的函数式语言,在已有一定编程基础的情
bool -> bool
```
## 2. let ... in ... {#let-in}
## let ... in ... {#let-in}
Nix 的 `let ... in ...` 语法被称作「let 表达式」或者「let 绑定」,它用于创建临时使用的局部变量:
@ -65,7 +64,24 @@ a + a # 结果是 2
let 表达式中的变量只能在 `in` 之后的表达式中使用,理解成临时变量就行。
## 3. attribute set 说明 {#attribute-set}
## if...then...else... {#if-then-else}
if...then...else... 用于条件判断,它是一个有返回值的表达式,语法如下:
```nix
if 3 > 4 then "yes" else "no" # 结果为 "no"
```
也可以与 let...in... 一起使用:
```nix
let
x = 3;
in
if x > 4 then "yes" else "no" # 结果为 "no"
```
## attribute set 说明 {#attribute-set}
花括号 `{}` 用于创建 attribute set也就是 key-value 对的集合,类似于 JSON 中的对象。
@ -123,7 +139,7 @@ a?b # 结果是 true因为 a.b 这个属性确实存在
has attribute 操作符在 nixpkgs 库中常被用于检测处理 `args?system` 等参数,以 `(args?system)``(! args?system)` 的形式作为函数参数使用(叹号表示对 bool 值取反,是常见 bool 值运算符)。
## 4. with 语句 {#with-statement}
## with 语句 {#with-statement}
with 语句的语法如下:
@ -144,7 +160,7 @@ in
with a; [ x y z ] # 结果是 [ 1 2 3 ], 等价于 [ a.x a.y a.z ]
```
## 5. 继承 inherit ... {#inherit}
## 继承 inherit ... {#inherit}
`inherit` 语句用于从 attribute set 中继承成员,同样是一个简化代码的语法糖,比如:
@ -173,7 +189,7 @@ in
} # 结果是 { x = 1; y = 2; }
```
## 6. ${ ... } 字符串插值 {#string-interpolation}
## ${ ... } 字符串插值 {#string-interpolation}
`${ ... }` 用于字符串插值,懂点编程的应该都很容易理解这个,比如:
@ -184,11 +200,11 @@ in
"the value of a is ${a}" # 结果是 "the value of a is 1"
```
## 7. 文件系统路径 {#file-system-path}
## 文件系统路径 {#file-system-path}
Nix 中不带引号的字符串会被解析为文件系统路径,路径的语法与 Unix 系统相同。
## 8. 搜索路径 {#search-path}
## 搜索路径 {#search-path}
> 请不要使用这个功能,它会导致不可预期的行为。
@ -198,7 +214,7 @@ Nix 会在看到 `<nixpkgs>` 这类三角括号语法时,会在 `NIX_PATH` 环
在这里做个介绍,只是为了让你在看到别人使用类似的语法时不至于抓瞎。
## 9. 多行字符串 {#multi-line-string}
## 多行字符串 {#multi-line-string}
多行字符串的语法为 `''`,比如:
@ -210,7 +226,7 @@ Nix 会在看到 `<nixpkgs>` 这类三角括号语法时,会在 `NIX_PATH` 环
''
```
## 10. 函数 {#nix-function}
## 函数 {#nix-function}
函数的声明语法为:
@ -297,7 +313,7 @@ pkgs.lib.strings.toUpper "search paths considered harmful" # 结果是 "SEARCH
可以通过 [Nixpkgs Library Functions - Nixpkgs Manual](https://nixos.org/manual/nixpkgs/stable/#sec-functions-library) 查看 lib 函数包的详细内容。
## 11. 不纯Impurities {#impurities}
## 不纯Impurities {#impurities}
Nix 语言本身是纯函数式的,是纯的,「纯」是指它就跟数学中的函数一样,同样的输入永远得到同样的输出。
@ -307,7 +323,7 @@ Nix 有两种构建输入,一种是从文件系统路径等输入源中读取
> Nix 中的搜索路径与 `builtins.currentSystem` 也是不纯的,但是这两个功能都不建议使用,所以这里略过了。
## 12. Fetchers {#fetchers}
## Fetchers {#fetchers}
构建输入除了直接来自文件系统路径之外,还可以通过 Fetchers 来获取Fetcher 是一种特殊的函数,它的输入是一个 attribute set输出是 Nix Store 中的一个系统路径。
@ -328,34 +344,68 @@ builtins.fetchTarball "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc6343
# result example(auto unzip the tarball) => "/nix/store/d59llm96vgis5fy231x6m7nrijs0ww36-source"
```
## 13. Derivations {#derivations}
## Derivations {#derivations}
一个构建动作的 Nix 语言描述被称做一个 Derivation它描述了如何构建一个软件包它的构建结果是一个 Store Object.
Derivation 描述了如何构建一个软件包,是一个软件包构建流程的 Nix 语言描述,它声明了构建时需要有哪些依赖项、需要什么构建工具链、要设置哪些环境变量、哪些构建参数、先干啥后干啥等等。
Derivation 的构建结果是一个 Store Object其中包含了软件包的所有二进制程序、配置文件等等内容。
Store Object 的存放路径格式为 `/nix/store/<hash>-<name>`,其中 `<hash>` 是构建结果的 hash 值,`<name>` 是它的名字。路径 hash 值确保了每个构建结果都是唯一的,因此可以多版本共存,而且不会出现依赖冲突的问题。
`/nix/store` 被称为 Store存放所有的 Store Objects这个路径被设置为只读只有 Nix 本身才能修改这个路径下的内容,以保证系统的可复现性。
`/nix/store` 是一个特殊的文件路径,它被称为 Store存放所有的 Store Objects这个路径被设置为只读只有 Nix 本身才能修改这个路径下的内容,以保证系统的可复现性。
在 Nix 语言的最底层,一个构建任务就是使用 builtins 中的不纯函数 `derivation` 创建的,我们实际使用的 `stdenv.mkDerivation` 就是它的一个 wrapper屏蔽了底层的细节简化了用法。
Derivation 实质上只是一个 attribute setNix 底层会使用内置函数 `builtins.derivation` 将这个 attribute set 构建为一个 Store Object。
我们实际编写 Derivation 时,通常使用的是 `stdenv.mkDerivation`,它只前述内置函数 `builtins.derivation` 的 Nix 语言 wrapper屏蔽了底层的细节简化了用法。
## 14. if...then...else... {#if-then-else}
if...then...else... 用于条件判断,它是一个有返回值的表达式,语法如下:
一个简单的 Derivation 如下,它声明了一个名为 hello 的应用程序(摘抄自 [nixpkgs/pkgs/hello](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/hello/default.nix)
```nix
if 3 > 4 then "yes" else "no" # 结果为 "no"
{ callPackage
, lib
, stdenv
, fetchurl
, nixos
, testers
, hello
}:
stdenv.mkDerivation (finalAttrs: {
pname = "hello";
version = "2.12.1";
src = fetchurl {
url = "mirror://gnu/hello/hello-${finalAttrs.version}.tar.gz";
sha256 = "sha256-jZkUKv2SV28wsM18tCqNxoCZmLxdYH2Idh9RLibH2yA=";
};
doCheck = true;
passthru.tests = {
version = testers.testVersion { package = hello; };
invariant-under-noXlibs =
testers.testEqualDerivation
"hello must not be rebuilt when environment.noXlibs is set."
hello
(nixos { environment.noXlibs = true; }).pkgs.hello;
};
passthru.tests.run = callPackage ./test.nix { hello = finalAttrs.finalPackage; };
meta = with lib; {
description = "A program that produces a familiar, friendly greeting";
longDescription = ''
GNU Hello is a program that prints "Hello, world!" when you run it.
It is fully customizable.
'';
homepage = "https://www.gnu.org/software/hello/manual/";
changelog = "https://git.savannah.gnu.org/cgit/hello.git/plain/NEWS?h=v${finalAttrs.version}";
license = licenses.gpl3Plus;
maintainers = [ maintainers.eelco ];
platforms = platforms.all;
};
})
```
也可以与 let...in... 一起使用:
```nix
let
x = 3;
in
if x > 4 then "yes" else "no" # 结果为 "no"
```
## 参考
- [Nix language basics - nix.dev](https://nix.dev/tutorials/first-steps/nix-language)