summary refs log tree commit diff
path: root/doc/languages-frameworks/dhall.section.md
blob: 4b49908b0b0cbbf8af6e719f96d7a42083b6143b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# Dhall {#sec-language-dhall}

The Nixpkgs support for Dhall assumes some familiarity with Dhall's language
support for importing Dhall expressions, which is documented here:

* [`dhall-lang.org` - Installing packages](https://docs.dhall-lang.org/tutorials/Language-Tour.html#installing-packages)

## Remote imports {#ssec-dhall-remote-imports}

Nixpkgs bypasses Dhall's support for remote imports using Dhall's
semantic integrity checks.  Specifically, any Dhall import can be protected by
an integrity check like:

```dhall
https://prelude.dhall-lang.org/v20.1.0/package.dhall
  sha256:26b0ef498663d269e4dc6a82b0ee289ec565d683ef4c00d0ebdd25333a5a3c98
```

… and if the import is cached then the interpreter will load the import from
cache instead of fetching the URL.

Nixpkgs uses this trick to add all of a Dhall expression's dependencies into the
cache so that the Dhall interpreter never needs to resolve any remote URLs.  In
fact, Nixpkgs uses a Dhall interpreter with remote imports disabled when
packaging Dhall expressions to enforce that the interpreter never resolves a
remote import.  This means that Nixpkgs only supports building Dhall expressions
if all of their remote imports are protected by semantic integrity checks.

Instead of remote imports, Nixpkgs uses Nix to fetch remote Dhall code.  For
example, the Prelude Dhall package uses `pkgs.fetchFromGitHub` to fetch the
`dhall-lang` repository containing the Prelude.  Relying exclusively on Nix
to fetch Dhall code ensures that Dhall packages built using Nix remain pure and
also behave well when built within a sandbox.

## Packaging a Dhall expression from scratch {#ssec-dhall-packaging-expression}

We can illustrate how Nixpkgs integrates Dhall by beginning from the following
trivial Dhall expression with one dependency (the Prelude):

```dhall
-- ./true.dhall

let Prelude = https://prelude.dhall-lang.org/v20.1.0/package.dhall

in  Prelude.Bool.not False
```

As written, this expression cannot be built using Nixpkgs because the
expression does not protect the Prelude import with a semantic integrity
check, so the first step is to freeze the expression using `dhall freeze`,
like this:

```ShellSession
$ dhall freeze --inplace ./true.dhall
```

… which gives us:

```dhall
-- ./true.dhall

let Prelude =
      https://prelude.dhall-lang.org/v20.1.0/package.dhall
        sha256:26b0ef498663d269e4dc6a82b0ee289ec565d683ef4c00d0ebdd25333a5a3c98

in  Prelude.Bool.not False
```

To package that expression, we create a `./true.nix` file containing the
following specification for the Dhall package:

```nix
# ./true.nix

{ buildDhallPackage, Prelude }:

buildDhallPackage {
  name = "true";
  code = ./true.dhall;
  dependencies = [ Prelude ];
  source = true;
}
```

… and we complete the build by incorporating that Dhall package into the
`pkgs.dhallPackages` hierarchy using an overlay, like this:

```nix
# ./example.nix

let
  nixpkgs = builtins.fetchTarball {
    url    = "https://github.com/NixOS/nixpkgs/archive/94b2848559b12a8ed1fe433084686b2a81123c99.tar.gz";
    sha256 = "1pbl4c2dsaz2lximgd31m96jwbps6apn3anx8cvvhk1gl9rkg107";
  };

  dhallOverlay = self: super: {
    true = self.callPackage ./true.nix { };
  };

  overlay = self: super: {
    dhallPackages = super.dhallPackages.override (old: {
      overrides =
        self.lib.composeExtensions (old.overrides or (_: _: {})) dhallOverlay;
    });
  };

  pkgs = import nixpkgs { config = {}; overlays = [ overlay ]; };

in
  pkgs
```

… which we can then build using this command:

```ShellSession
$ nix build --file ./example.nix dhallPackages.true
```

## Contents of a Dhall package {#ssec-dhall-package-contents}

The above package produces the following directory tree:

```ShellSession
$ tree -a ./result
result
├── .cache
│   └── dhall
│       └── 122027abdeddfe8503496adeb623466caa47da5f63abd2bc6fa19f6cfcb73ecfed70
├── binary.dhall
└── source.dhall
```

… where:

* `source.dhall` contains the result of interpreting our Dhall package:

  ```ShellSession
  $ cat ./result/source.dhall
  True
  ```

* The `.cache` subdirectory contains one binary cache product encoding the
  same result as `source.dhall`:

  ```ShellSession
  $ dhall decode < ./result/.cache/dhall/122027abdeddfe8503496adeb623466caa47da5f63abd2bc6fa19f6cfcb73ecfed70
  True
  ```

* `binary.dhall` contains a Dhall expression which handles fetching and decoding
  the same cache product:

  ```ShellSession
  $ cat ./result/binary.dhall
  missing sha256:27abdeddfe8503496adeb623466caa47da5f63abd2bc6fa19f6cfcb73ecfed70
  $ cp -r ./result/.cache .cache

  $ chmod -R u+w .cache

  $ XDG_CACHE_HOME=.cache dhall --file ./result/binary.dhall
  True
  ```

The `source.dhall` file is only present for packages that specify
`source = true;`.  By default, Dhall packages omit the `source.dhall` in order
to conserve disk space when they are used exclusively as dependencies.  For
example, if we build the Prelude package it will only contain the binary
encoding of the expression:

```ShellSession
$ nix build --file ./example.nix dhallPackages.Prelude

$ tree -a result
result
├── .cache
│   └── dhall
│       └── 122026b0ef498663d269e4dc6a82b0ee289ec565d683ef4c00d0ebdd25333a5a3c98
└── binary.dhall

2 directories, 2 files
```

Typically, you only specify `source = true;` for the top-level Dhall expression
of interest (such as our example `true.nix` Dhall package).  However, if you
wish to specify `source = true` for all Dhall packages, then you can amend the
Dhall overlay like this:

```nix
  dhallOverrides = self: super: {
    # Enable source for all Dhall packages
    buildDhallPackage =
      args: super.buildDhallPackage (args // { source = true; });

    true = self.callPackage ./true.nix { };
  };
```

… and now the Prelude will contain the fully decoded result of interpreting
the Prelude:

```ShellSession
$ nix build --file ./example.nix dhallPackages.Prelude

$ tree -a result
result
├── .cache
│   └── dhall
│       └── 122026b0ef498663d269e4dc6a82b0ee289ec565d683ef4c00d0ebdd25333a5a3c98
├── binary.dhall
└── source.dhall

$ cat ./result/source.dhall
{ Bool =
  { and =
      \(_ : List Bool) ->
        List/fold Bool _ Bool (\(_ : Bool) -> \(_ : Bool) -> _@1 && _) True
  , build = \(_ : Type -> _ -> _@1 -> _@2) -> _ Bool True False
  , even =
      \(_ : List Bool) ->
        List/fold Bool _ Bool (\(_ : Bool) -> \(_ : Bool) -> _@1 == _) True
  , fold =
      \(_ : Bool) ->

```

## Packaging functions {#ssec-dhall-packaging-functions}

We already saw an example of using `buildDhallPackage` to create a Dhall
package from a single file, but most Dhall packages consist of more than one
file and there are two derived utilities that you may find more useful when
packaging multiple files:

* `buildDhallDirectoryPackage` - build a Dhall package from a local directory

* `buildDhallGitHubPackage` - build a Dhall package from a GitHub repository

The `buildDhallPackage` is the lowest-level function and accepts the following
arguments:

* `name`: The name of the derivation

* `dependencies`: Dhall dependencies to build and cache ahead of time

* `code`: The top-level expression to build for this package

  Note that the `code` field accepts an arbitrary Dhall expression.  You're
  not limited to just a file.

* `source`: Set to `true` to include the decoded result as `source.dhall` in the
  build product, at the expense of requiring more disk space

* `documentationRoot`: Set to the root directory of the package if you want
  `dhall-docs` to generate documentation underneath the `docs` subdirectory of
  the build product

The `buildDhallDirectoryPackage` is a higher-level function implemented in terms
of `buildDhallPackage` that accepts the following arguments:

* `name`: Same as `buildDhallPackage`

* `dependencies`: Same as `buildDhallPackage`

* `source`: Same as `buildDhallPackage`

* `src`: The directory containing Dhall code that you want to turn into a Dhall
  package

* `file`: The top-level file (`package.dhall` by default) that is the entrypoint
  to the rest of the package

* `document`: Set to `true` to generate documentation for the package

The `buildDhallGitHubPackage` is another higher-level function implemented in
terms of `buildDhallPackage` that accepts the following arguments:

* `name`: Same as `buildDhallPackage`

* `dependencies`: Same as `buildDhallPackage`

* `source`: Same as `buildDhallPackage`

* `owner`: The owner of the repository

* `repo`: The repository name

* `rev`: The desired revision (or branch, or tag)

* `directory`: The subdirectory of the Git repository to package (if a
  directory other than the root of the repository)

* `file`: The top-level file (`${directory}/package.dhall` by default) that is
  the entrypoint to the rest of the package

* `document`: Set to `true` to generate documentation for the package

Additionally, `buildDhallGitHubPackage` accepts the same arguments as
`fetchFromGitHub`, such as `sha256` or `fetchSubmodules`.

## `dhall-to-nixpkgs` {#ssec-dhall-dhall-to-nixpkgs}

You can use the `dhall-to-nixpkgs` command-line utility to automate
packaging Dhall code.  For example:

```ShellSession
$ nix-env --install --attr haskellPackages.dhall-nixpkgs

$ nix-env --install --attr nix-prefetch-git  # Used by dhall-to-nixpkgs

$ dhall-to-nixpkgs github https://github.com/Gabriel439/dhall-semver.git
{ buildDhallGitHubPackage, Prelude }:
  buildDhallGitHubPackage {
    name = "dhall-semver";
    githubBase = "github.com";
    owner = "Gabriel439";
    repo = "dhall-semver";
    rev = "2d44ae605302ce5dc6c657a1216887fbb96392a4";
    fetchSubmodules = false;
    sha256 = "0y8shvp8srzbjjpmnsvz9c12ciihnx1szs0yzyi9ashmrjvd0jcz";
    directory = "";
    file = "package.dhall";
    source = false;
    document = false;
    dependencies = [ (Prelude.overridePackage { file = "package.dhall"; }) ];
    }
```

The utility takes care of automatically detecting remote imports and converting
them to package dependencies.  You can also use the utility on local
Dhall directories, too:

```ShellSession
$ dhall-to-nixpkgs directory ~/proj/dhall-semver
{ buildDhallDirectoryPackage, Prelude }:
  buildDhallDirectoryPackage {
    name = "proj";
    src = ~/proj/dhall-semver;
    file = "package.dhall";
    source = false;
    document = false;
    dependencies = [ (Prelude.overridePackage { file = "package.dhall"; }) ];
    }
```

### Remote imports as fixed-output derivations {#ssec-dhall-remote-imports-as-fod}

`dhall-to-nixpkgs` has the ability to fetch and build remote imports as
fixed-output derivations by using their Dhall integrity check. This is
sometimes easier than manually packaging all remote imports.

This can be used like the following:

```ShellSession
$ dhall-to-nixpkgs directory --fixed-output-derivations ~/proj/dhall-semver
{ buildDhallDirectoryPackage, buildDhallUrl }:
  buildDhallDirectoryPackage {
    name = "proj";
    src = ~/proj/dhall-semver;
    file = "package.dhall";
    source = false;
    document = false;
    dependencies = [
      (buildDhallUrl {
        url = "https://prelude.dhall-lang.org/v17.0.0/package.dhall";
        hash = "sha256-ENs8kZwl6QRoM9+Jeo/+JwHcOQ+giT2VjDQwUkvlpD4=";
        dhallHash = "sha256:10db3c919c25e9046833df897a8ffe2701dc390fa0893d958c3430524be5a43e";
        })
      ];
    }
```

Here, `dhall-semver`'s `Prelude` dependency is fetched and built with the
`buildDhallUrl` helper function, instead of being passed in as a function
argument.

## Overriding dependency versions {#ssec-dhall-overriding-dependency-versions}

Suppose that we change our `true.dhall` example expression to depend on an older
version of the Prelude (19.0.0):

```dhall
-- ./true.dhall

let Prelude =
      https://prelude.dhall-lang.org/v19.0.0/package.dhall
        sha256:eb693342eb769f782174157eba9b5924cf8ac6793897fc36a31ccbd6f56dafe2

in  Prelude.Bool.not False
```

If we try to rebuild that expression the build will fail:

```ShellSession
$ nix build --file ./example.nix dhallPackages.true
builder for '/nix/store/0f1hla7ff1wiaqyk1r2ky4wnhnw114fi-true.drv' failed with exit code 1; last 10 log lines:

  Dhall was compiled without the 'with-http' flag.

  The requested URL was: https://prelude.dhall-lang.org/v19.0.0/package.dhall


  4│       https://prelude.dhall-lang.org/v19.0.0/package.dhall
  5│         sha256:eb693342eb769f782174157eba9b5924cf8ac6793897fc36a31ccbd6f56dafe2

  /nix/store/rsab4y99h14912h4zplqx2iizr5n4rc2-true.dhall:4:7
[1 built (1 failed), 0.0 MiB DL]
error: build of '/nix/store/0f1hla7ff1wiaqyk1r2ky4wnhnw114fi-true.drv' failed
```

… because the default Prelude selected by Nixpkgs revision
`94b2848559b12a8ed1fe433084686b2a81123c99is` is version 20.1.0, which doesn't
have the same integrity check as version 19.0.0.  This means that version
19.0.0 is not cached and the interpreter is not allowed to fall back to
importing the URL.

However, we can override the default Prelude version by using `dhall-to-nixpkgs`
to create a Dhall package for our desired Prelude:

```ShellSession
$ dhall-to-nixpkgs github https://github.com/dhall-lang/dhall-lang.git \
    --name Prelude \
    --directory Prelude \
    --rev v19.0.0 \
    > Prelude.nix
```

… and then referencing that package in our Dhall overlay, by either overriding
the Prelude globally for all packages, like this:

```nix
  dhallOverrides = self: super: {
    true = self.callPackage ./true.nix { };

    Prelude = self.callPackage ./Prelude.nix { };
  };
```

… or selectively overriding the Prelude dependency for just the `true` package,
like this:

```nix
  dhallOverrides = self: super: {
    true = self.callPackage ./true.nix {
      Prelude = self.callPackage ./Prelude.nix { };
    };
  };
```

## Overrides {#ssec-dhall-overrides}

You can override any of the arguments to `buildDhallGitHubPackage` or
`buildDhallDirectoryPackage` using the `overridePackage` attribute of a package.
For example, suppose we wanted to selectively enable `source = true` just for the Prelude.  We can do that like this:

```nix
  dhallOverrides = self: super: {
    Prelude = super.Prelude.overridePackage { source = true; };

    
  };
```

[semantic-integrity-checks]: https://docs.dhall-lang.org/tutorials/Language-Tour.html#installing-packages