about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/trivial-builders/default.nix
blob: d7438923a54b87a6b83229e9e7de5436c1336aee (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
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
{ lib, config, stdenv, stdenvNoCC, jq, lndir, runtimeShell, shellcheck-minimal }:

let
  inherit (lib)
    optionalAttrs
    warn
    ;
in

rec {

  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-runCommand
  runCommand = name: env: runCommandWith {
    stdenv = stdenvNoCC;
    runLocal = false;
    inherit name;
    derivationArgs = env;
  };
  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-runCommandLocal
  runCommandLocal = name: env: runCommandWith {
    stdenv = stdenvNoCC;
    runLocal = true;
    inherit name;
    derivationArgs = env;
  };
  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-runCommandCC
  runCommandCC = name: env: runCommandWith {
    stdenv = stdenv;
    runLocal = false;
    inherit name;
    derivationArgs = env;
  };
  # `runCommandCCLocal` left out on purpose.
  # We shouldn’t force the user to have a cc in scope.

  # TODO: Move documentation for runCommandWith to the Nixpkgs manual
  /*
    Generalized version of the `runCommand`-variants
    which does customized behavior via a single
    attribute set passed as the first argument
    instead of having a lot of variants like
    `runCommand*`. Additionally it allows changing
    the used `stdenv` freely and has a more explicit
    approach to changing the arguments passed to
    `stdenv.mkDerivation`.
   */
  runCommandWith =
    let
      # prevent infinite recursion for the default stdenv value
      defaultStdenv = stdenv;
    in
    {
      # which stdenv to use, defaults to a stdenv with a C compiler, pkgs.stdenv
      stdenv ? defaultStdenv
      # whether to build this derivation locally instead of substituting
    , runLocal ? false
      # extra arguments to pass to stdenv.mkDerivation
    , derivationArgs ? { }
      # name of the resulting derivation
    , name
      # TODO(@Artturin): enable strictDeps always
    }: buildCommand:
      stdenv.mkDerivation ({
        enableParallelBuilding = true;
        inherit buildCommand name;
        passAsFile = [ "buildCommand" ]
          ++ (derivationArgs.passAsFile or [ ]);
      }
      // lib.optionalAttrs (! derivationArgs?meta) {
        pos = let args = builtins.attrNames derivationArgs; in
          if builtins.length args > 0
          then builtins.unsafeGetAttrPos (builtins.head args) derivationArgs
          else null;
      }
      // (lib.optionalAttrs runLocal {
        preferLocalBuild = true;
        allowSubstitutes = false;
      })
      // builtins.removeAttrs derivationArgs [ "passAsFile" ]);


  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeTextFile
  writeTextFile =
    { name
    , text
    , executable ? false
    , destination ? ""
    , checkPhase ? ""
    , meta ? { }
    , allowSubstitutes ? false
    , preferLocalBuild ? true
    , derivationArgs ? { }
    }:
    let
      matches = builtins.match "/bin/([^/]+)" destination;
    in
    runCommand name
      ({
        inherit text executable checkPhase allowSubstitutes preferLocalBuild;
        passAsFile = [ "text" ]
          ++ derivationArgs.passAsFile or [ ];
        meta = lib.optionalAttrs (executable && matches != null)
          {
            mainProgram = lib.head matches;
          } // meta // derivationArgs.meta or {};
      } // removeAttrs derivationArgs [ "passAsFile" "meta" ])
      ''
        target=$out${lib.escapeShellArg destination}
        mkdir -p "$(dirname "$target")"

        if [ -e "$textPath" ]; then
          mv "$textPath" "$target"
        else
          echo -n "$text" > "$target"
        fi

        if [ -n "$executable" ]; then
          chmod +x "$target"
        fi

        eval "$checkPhase"
      '';

  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
  writeText = name: text: writeTextFile { inherit name text; };

  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
  writeTextDir = path: text: writeTextFile {
    inherit text;
    name = builtins.baseNameOf path;
    destination = "/${path}";
  };

  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
  writeScript = name: text: writeTextFile { inherit name text; executable = true; };

  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
  writeScriptBin = name: text: writeTextFile {
    inherit name text;
    executable = true;
    destination = "/bin/${name}";
  };

  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
  writeShellScript = name: text:
    writeTextFile {
      inherit name;
      executable = true;
      text = ''
        #!${runtimeShell}
        ${text}
      '';
      checkPhase = ''
        ${stdenv.shellDryRun} "$target"
      '';
    };

  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
  writeShellScriptBin = name: text:
    writeTextFile {
      inherit name;
      executable = true;
      destination = "/bin/${name}";
      text = ''
        #!${runtimeShell}
        ${text}
      '';
      checkPhase = ''
        ${stdenv.shellDryRun} "$target"
      '';
      meta.mainProgram = name;
    };

  # TODO: move parameter documentation to the Nixpkgs manual
  # See doc/build-helpers/trivial-build-helpers.chapter.md
  # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeShellApplication
  writeShellApplication =
    {
      /*
         The name of the script to write.

         Type: String
       */
      name,
      /*
         The shell script's text, not including a shebang.

         Type: String
       */
      text,
      /*
         Inputs to add to the shell script's `$PATH` at runtime.

         Type: [String|Derivation]
       */
      runtimeInputs ? [ ],
      /*
         Extra environment variables to set at runtime.

         Type: AttrSet
       */
      runtimeEnv ? null,
      /*
         `stdenv.mkDerivation`'s `meta` argument.

         Type: AttrSet
       */
      meta ? { },
      /*
         The `checkPhase` to run. Defaults to `shellcheck` on supported
         platforms and `bash -n`.

         The script path will be given as `$target` in the `checkPhase`.

         Type: String
       */
      checkPhase ? null,
      /*
         Checks to exclude when running `shellcheck`, e.g. `[ "SC2016" ]`.

         See <https://www.shellcheck.net/wiki/> for a list of checks.

         Type: [String]
       */
      excludeShellChecks ? [ ],
      /*
         Bash options to activate with `set -o` at the start of the script.

         Defaults to `[ "errexit" "nounset" "pipefail" ]`.

         Type: [String]
       */
      bashOptions ? [ "errexit" "nounset" "pipefail" ],
      /* Extra arguments to pass to `stdenv.mkDerivation`.

         :::{.caution}
         Certain derivation attributes are used internally,
         overriding those could cause problems.
         :::

         Type: AttrSet
       */
      derivationArgs ? { },
    }:
    writeTextFile {
      inherit name meta derivationArgs;
      executable = true;
      destination = "/bin/${name}";
      allowSubstitutes = true;
      preferLocalBuild = false;
      text = ''
        #!${runtimeShell}
        ${lib.concatMapStringsSep "\n" (option: "set -o ${option}") bashOptions}
      '' + lib.optionalString (runtimeEnv != null)
        (lib.concatStrings
          (lib.mapAttrsToList
            (name: value: ''
              ${lib.toShellVar name value}
              export ${name}
            '')
            runtimeEnv))
      + lib.optionalString (runtimeInputs != [ ]) ''

        export PATH="${lib.makeBinPath runtimeInputs}:$PATH"
      '' + ''

        ${text}
      '';

      checkPhase =
        # GHC (=> shellcheck) isn't supported on some platforms (such as risc-v)
        # but we still want to use writeShellApplication on those platforms
        let
          shellcheckSupported = lib.meta.availableOn stdenv.buildPlatform shellcheck-minimal.compiler;
          excludeOption = lib.optionalString (excludeShellChecks != [ ]) "--exclude '${lib.concatStringsSep "," excludeShellChecks}'";
          shellcheckCommand = lib.optionalString shellcheckSupported ''
            # use shellcheck which does not include docs
            # pandoc takes long to build and documentation isn't needed for just running the cli
            ${lib.getExe shellcheck-minimal} ${excludeOption} "$target"
          '';
        in
        if checkPhase == null then ''
          runHook preCheck
          ${stdenv.shellDryRun} "$target"
          ${shellcheckCommand}
          runHook postCheck
        ''
        else checkPhase;
    };

  # Create a C binary
  # TODO: add to writers? pkgs/build-support/writers
  writeCBin = pname: code:
    runCommandCC pname
      {
        inherit pname code;
        executable = true;
        passAsFile = [ "code" ];
        # Pointless to do this on a remote machine.
        preferLocalBuild = true;
        allowSubstitutes = false;
        meta = {
          mainProgram = pname;
        };
      }
      ''
        n=$out/bin/${pname}
        mkdir -p "$(dirname "$n")"
        mv "$codePath" code.c
        $CC -x c code.c -o "$n"
      '';

  # TODO: deduplicate with documentation in doc/build-helpers/trivial-build-helpers.chapter.md
  #       see also https://github.com/NixOS/nixpkgs/pull/249721
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-concatText
  /* concat a list of files to the nix store.
    The contents of files are added to the file in the store.

    Example:


    # Writes my-file to /nix/store/<store path>
    concatTextFile {
      name = "my-file";
      files = [ drv1 "${drv2}/path/to/file" ];
    }


    See also the `concatText` helper function below.


    # Writes executable my-file to /nix/store/<store path>/bin/my-file
    concatTextFile {
      name = "my-file";
      files = [ drv1 "${drv2}/path/to/file" ];
      executable = true;
      destination = "/bin/my-file";
    }


   */
  concatTextFile =
    { name # the name of the derivation
    , files
    , executable ? false # run chmod +x ?
    , destination ? ""   # relative path appended to $out eg "/bin/foo"
    , checkPhase ? ""    # syntax checks, e.g. for scripts
    , meta ? { }
    }:
    runCommandLocal name
      { inherit files executable checkPhase meta destination; }
      ''
        file=$out$destination
        mkdir -p "$(dirname "$file")"
        cat $files > "$file"

        if [ -n "$executable" ]; then
          chmod +x "$file"
        fi

        eval "$checkPhase"
      '';

  # TODO: deduplicate with documentation in doc/build-helpers/trivial-build-helpers.chapter.md
  #       see also https://github.com/NixOS/nixpkgs/pull/249721
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-concatText
  /*
    Writes a text file to nix store with no optional parameters available.

    Example:


    # Writes contents of files to /nix/store/<store path>
    concatText "my-file" [ file1 file2 ]


  */
  concatText = name: files: concatTextFile { inherit name files; };

  # TODO: deduplicate with documentation in doc/build-helpers/trivial-build-helpers.chapter.md
  #       see also https://github.com/NixOS/nixpkgs/pull/249721
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-concatText
  /*
    Writes a text file to nix store with and mark it as executable.

    Example:
    # Writes contents of files to /nix/store/<store path>
    concatScript "my-file" [ file1 file2 ]

  */
  concatScript = name: files: concatTextFile { inherit name files; executable = true; };


  /*
    TODO: Deduplicate this documentation.
    More docs in doc/build-helpers/trivial-build-helpers.chapter.md
    See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-symlinkJoin

    Create a forest of symlinks to the files in `paths`.

    This creates a single derivation that replicates the directory structure
    of all the input paths.

    BEWARE: it may not "work right" when the passed paths contain symlinks to directories.

    Example:


    # adds symlinks of hello to current build.
    symlinkJoin { name = "myhello"; paths = [ pkgs.hello ]; }




    # adds symlinks of hello and stack to current build and prints "links added"
    symlinkJoin { name = "myexample"; paths = [ pkgs.hello pkgs.stack ]; postBuild = "echo links added"; }


    This creates a derivation with a directory structure like the following:


    /nix/store/sglsr5g079a5235hy29da3mq3hv8sjmm-myexample
    |-- bin
    |   |-- hello -> /nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10/bin/hello
    |   `-- stack -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1/bin/stack
    `-- share
        |-- bash-completion
        |   `-- completions
        |       `-- stack -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1/share/bash-completion/completions/stack
        |-- fish
        |   `-- vendor_completions.d
        |       `-- stack.fish -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1/share/fish/vendor_completions.d/stack.fish
    ...


    symlinkJoin and linkFarm are similar functions, but they output
    derivations with different structure.

    symlinkJoin is used to create a derivation with a familiar directory
    structure (top-level bin/, share/, etc), but with all actual files being symlinks to
    the files in the input derivations.

    symlinkJoin is used many places in nixpkgs to create a single derivation
    that appears to contain binaries, libraries, documentation, etc from
    multiple input derivations.

    linkFarm is instead used to create a simple derivation with symlinks to
    other derivations.  A derivation created with linkFarm is often used in CI
    as a easy way to build multiple derivations at once.
   */
  symlinkJoin =
    args_@{ name
    , paths
    , preferLocalBuild ? true
    , allowSubstitutes ? false
    , postBuild ? ""
    , ...
    }:
    let
      args = removeAttrs args_ [ "name" "postBuild" ]
        // {
        inherit preferLocalBuild allowSubstitutes;
        passAsFile = [ "paths" ];
      }; # pass the defaults
    in
    runCommand name args
      ''
        mkdir -p $out
        for i in $(cat $pathsPath); do
          ${lndir}/bin/lndir -silent $i $out
        done
        ${postBuild}
      '';

  # TODO: move linkFarm docs to the Nixpkgs manual
  /*
    Quickly create a set of symlinks to derivations.

    This creates a simple derivation with symlinks to all inputs.

    entries can be a list of attribute sets like

    [ { name = "name" ; path = "/nix/store/..."; } ]


    or an attribute set name -> path like:

    { name = "/nix/store/..."; other = "/nix/store/..."; }


    Example:

    # Symlinks hello and stack paths in store to current $out/hello-test and
    # $out/foobar.
    linkFarm "myexample" [ { name = "hello-test"; path = pkgs.hello; } { name = "foobar"; path = pkgs.stack; } ]

    This creates a derivation with a directory structure like the following:

    /nix/store/qc5728m4sa344mbks99r3q05mymwm4rw-myexample
    |-- foobar -> /nix/store/6lzdpxshx78281vy056lbk553ijsdr44-stack-2.1.3.1
    `-- hello-test -> /nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10


    See the note on symlinkJoin for the difference between linkFarm and symlinkJoin.
   */
  linkFarm = name: entries:
    let
      entries' =
        if (lib.isAttrs entries) then entries
        # We do this foldl to have last-wins semantics in case of repeated entries
        else if (lib.isList entries) then lib.foldl (a: b: a // { "${b.name}" = b.path; }) { } entries
        else throw "linkFarm entries must be either attrs or a list!";

      linkCommands = lib.mapAttrsToList
        (name: path: ''
          mkdir -p "$(dirname ${lib.escapeShellArg "${name}"})"
          ln -s ${lib.escapeShellArg "${path}"} ${lib.escapeShellArg "${name}"}
        '')
        entries';
    in
    runCommand name
      {
        preferLocalBuild = true;
        allowSubstitutes = false;
        passthru.entries = entries';
      } ''
      mkdir -p $out
      cd $out
      ${lib.concatStrings linkCommands}
    '';

  # TODO: move linkFarmFromDrvs docs to the Nixpkgs manual
  /*
    Easily create a linkFarm from a set of derivations.

    This calls linkFarm with a list of entries created from the list of input
    derivations.  It turns each input derivation into an attribute set
    like { name = drv.name ; path = drv }, and passes this to linkFarm.

    Example:

    # Symlinks the hello, gcc, and ghc derivations in $out
    linkFarmFromDrvs "myexample" [ pkgs.hello pkgs.gcc pkgs.ghc ]

    This creates a derivation with a directory structure like the following:


    /nix/store/m3s6wkjy9c3wy830201bqsb91nk2yj8c-myexample
    |-- gcc-wrapper-9.2.0 -> /nix/store/fqhjxf9ii4w4gqcsx59fyw2vvj91486a-gcc-wrapper-9.2.0
    |-- ghc-8.6.5 -> /nix/store/gnf3s07bglhbbk4y6m76sbh42siym0s6-ghc-8.6.5
    `-- hello-2.10 -> /nix/store/k0ll91c4npk4lg8lqhx00glg2m735g74-hello-2.10

  */
  linkFarmFromDrvs = name: drvs:
    let mkEntryFromDrv = drv: { name = drv.name; path = drv; };
    in linkFarm name (map mkEntryFromDrv drvs);

  # TODO: move onlyBin docs to the Nixpkgs manual
  /*
    Produce a derivation that links to the target derivation's `/bin`,
    and *only* `/bin`.

    This is useful when your favourite package doesn't have a separate
    bin output and other contents of the package's output (e.g. setup
    hooks) cause trouble when used in your environment.
  */
  onlyBin = drv: runCommand "${drv.name}-only-bin" { } ''
    mkdir -p $out
    ln -s ${lib.getBin drv}/bin $out/bin
  '';


  # Docs in doc/builders/special/makesetuphook.section.md
  # See https://nixos.org/manual/nixpkgs/unstable/#sec-pkgs.makeSetupHook
  makeSetupHook =
    { name ? lib.warn "calling makeSetupHook without passing a name is deprecated." "hook"
    , deps ? [ ]
      # hooks go in nativeBuildInput so these will be nativeBuildInput
    , propagatedBuildInputs ? [ ]
      # these will be buildInputs
    , depsTargetTargetPropagated ? [ ]
    , meta ? { }
    , passthru ? { }
    , substitutions ? { }
    }:
    script:
    runCommand name
      (substitutions // {
        # TODO(@Artturin:) substitutions should be inside the env attrset
        # but users are likely passing non-substitution arguments through substitutions
        # turn off __structuredAttrs to unbreak substituteAll
        __structuredAttrs = false;
        inherit meta;
        inherit depsTargetTargetPropagated;
        propagatedBuildInputs =
          # remove list conditionals before 23.11
          lib.warnIf (!lib.isList deps) "'deps' argument to makeSetupHook must be a list. content of deps: ${toString deps}"
            (lib.warnIf (deps != [ ]) "'deps' argument to makeSetupHook is deprecated and will be removed in release 23.11., Please use propagatedBuildInputs instead. content of deps: ${toString deps}"
              propagatedBuildInputs ++ (if lib.isList deps then deps else [ deps ]));
        strictDeps = true;
        # TODO 2023-01, no backport: simplify to inherit passthru;
        passthru = passthru
          // optionalAttrs (substitutions?passthru)
          (warn "makeSetupHook (name = ${lib.strings.escapeNixString name}): `substitutions.passthru` is deprecated. Please set `passthru` directly."
            substitutions.passthru);
      })
      (''
        mkdir -p $out/nix-support
        cp ${script} $out/nix-support/setup-hook
        recordPropagatedDependencies
      '' + lib.optionalString (substitutions != { }) ''
        substituteAll ${script} $out/nix-support/setup-hook
      '');


  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeReferencesToFile
  # TODO: Convert to throw after Nixpkgs 24.05 branch-off.
  writeReferencesToFile = (if config.allowAliases then lib.warn else throw)
    "writeReferencesToFile is deprecated in favour of writeClosure"
    (path: writeClosure [ path ]);

  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeClosure
  writeClosure = paths: runCommand "runtime-deps"
    {
      # Get the cleaner exportReferencesGraph interface
      __structuredAttrs = true;
      exportReferencesGraph.graph = paths;
      nativeBuildInputs = [ jq ];
    }
    ''
      jq -r ".graph | map(.path) | sort | .[]" "$NIX_ATTRS_JSON_FILE" > "$out"
    '';

  # Docs in doc/build-helpers/trivial-build-helpers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-writeDirectReferencesToFile
  writeDirectReferencesToFile = path: runCommand "runtime-references"
    {
      exportReferencesGraph = [ "graph" path ];
      inherit path;
    }
    ''
      touch ./references
      while read p; do
        read dummy
        read nrRefs
        if [[ $p == $path ]]; then
          for ((i = 0; i < nrRefs; i++)); do
            read ref;
            echo $ref >>./references
          done
        else
          for ((i = 0; i < nrRefs; i++)); do
            read ref;
          done
        fi
      done < graph
      sort ./references >$out
    '';

  # TODO: move writeStringReferencesToFile docs to the Nixpkgs manual
  /*
    Extract a string's references to derivations and paths (its
    context) and write them to a text file, removing the input string
    itself from the dependency graph. This is useful when you want to
    make a derivation depend on the string's references, but not its
    contents (to avoid unnecessary rebuilds, for example).

    Note that this only works as intended on Nix >= 2.3.
   */
  writeStringReferencesToFile = string:
    /*
       The basic operation this performs is to copy the string context
       from `string` to a second string and wrap that string in a
       derivation. However, that alone is not enough, since nothing in the
       string refers to the output paths of the derivations/paths in its
       context, meaning they'll be considered build-time dependencies and
       removed from the wrapper derivation's closure. Putting the
       necessary output paths in the new string is however not very
       straightforward - the attrset returned by `getContext` contains
       only references to derivations' .drv-paths, not their output
       paths. In order to "convert" them, we try to extract the
       corresponding paths from the original string using regex.
    */
    let
      # Taken from https://github.com/NixOS/nix/blob/130284b8508dad3c70e8160b15f3d62042fc730a/src/libutil/hash.cc#L84
      nixHashChars = "0123456789abcdfghijklmnpqrsvwxyz";
      context = builtins.getContext string;
      derivations = lib.filterAttrs (n: v: v ? outputs) context;
      # Objects copied from outside of the store, such as paths and
      # `builtins.fetch*`ed ones
      sources = lib.attrNames (lib.filterAttrs (n: v: v ? path) context);
      packages =
        lib.mapAttrs'
          (name: value:
            {
              inherit value;
              name = lib.head (builtins.match "${builtins.storeDir}/[${nixHashChars}]+-(.*)\.drv" name);
            })
          derivations;
      # The syntax of output paths differs between outputs named `out`
      # and other, explicitly named ones. For explicitly named ones,
      # the output name is suffixed as `-name`, but `out` outputs
      # aren't suffixed at all, and thus aren't easily distinguished
      # from named output paths. Therefore, we find all the named ones
      # first so we can use them to remove false matches when looking
      # for `out` outputs (see the definition of `outputPaths`).
      namedOutputPaths =
        lib.flatten
          (lib.mapAttrsToList
            (name: value:
              (map
                (output:
                  lib.filter
                    lib.isList
                    (builtins.split "(${builtins.storeDir}/[${nixHashChars}]+-${name}-${output})" string))
                (lib.remove "out" value.outputs)))
            packages);
      # Only `out` outputs
      outputPaths =
        lib.flatten
          (lib.mapAttrsToList
            (name: value:
              if lib.elem "out" value.outputs then
                lib.filter
                  (x: lib.isList x &&
                    # If the matched path is in `namedOutputPaths`,
                    # it's a partial match of an output path where
                    # the output name isn't `out`
                    lib.all (o: !lib.hasPrefix (lib.head x) o) namedOutputPaths)
                  (builtins.split "(${builtins.storeDir}/[${nixHashChars}]+-${name})" string)
              else
                [ ])
            packages);
      allPaths = lib.concatStringsSep "\n" (lib.unique (sources ++ namedOutputPaths ++ outputPaths));
      allPathsWithContext = builtins.appendContext allPaths context;
    in
    if builtins ? getContext then
      writeText "string-references" allPathsWithContext
    else
      writeDirectReferencesToFile (writeText "string-file" string);


  # Docs in doc/build-helpers/fetchers.chapter.md
  # See https://nixos.org/manual/nixpkgs/unstable/#requirefile
  requireFile =
    { name ? null
    , sha256 ? null
    , sha1 ? null
    , hash ? null
    , url ? null
    , message ? null
    , hashMode ? "flat"
    }:
      assert (message != null) || (url != null);
      assert (sha256 != null) || (sha1 != null) || (hash != null);
      assert (name != null) || (url != null);
      let
        msg =
          if message != null then message
          else ''
            Unfortunately, we cannot download file ${name_} automatically.
            Please go to ${url} to download it yourself, and add it to the Nix store
            using either
              nix-store --add-fixed ${hashAlgo} ${name_}
            or
              nix-prefetch-url --type ${hashAlgo} file:///path/to/${name_}
          '';
        hashAlgo =
          if hash != null then (builtins.head (lib.strings.splitString "-" hash))
          else if sha256 != null then "sha256"
          else "sha1";
        hashAlgo_ = if hash != null then "" else hashAlgo;
        hash_ =
          if hash != null then hash
          else if sha256 != null then sha256
          else sha1;
        name_ = if name == null then baseNameOf (toString url) else name;
      in
      stdenvNoCC.mkDerivation {
        name = name_;
        outputHashMode = hashMode;
        outputHashAlgo = hashAlgo_;
        outputHash = hash_;
        preferLocalBuild = true;
        allowSubstitutes = false;
        builder = writeScript "restrict-message" ''
          source ${stdenvNoCC}/setup
          cat <<_EOF_

          ***
          ${msg}
          ***

          _EOF_
          exit 1
        '';
      };


  # TODO: move copyPathToStore docs to the Nixpkgs manual
  /*
    Copy a path to the Nix store.
    Nix automatically copies files to the store before stringifying paths.
    If you need the store path of a file, ${copyPathToStore <path>} can be
    shortened to ${<path>}.
  */
  copyPathToStore = builtins.filterSource (p: t: true);


  # TODO: move copyPathsToStore docs to the Nixpkgs manual
  /*
    Copy a list of paths to the Nix store.
  */
  copyPathsToStore = builtins.map copyPathToStore;

  # TODO: move applyPatches docs to the Nixpkgs manual
  /* Applies a list of patches to a source directory.

    Example:

    # Patching nixpkgs:

    applyPatches {
      src = pkgs.path;
      patches = [
        (pkgs.fetchpatch {
          url = "https://github.com/NixOS/nixpkgs/commit/1f770d20550a413e508e081ddc08464e9d08ba3d.patch";
          sha256 = "1nlzx171y3r3jbk0qhvnl711kmdk57jlq4na8f8bs8wz2pbffymr";
        })
      ];
    }

   */
  applyPatches =
    { src
    , name ? (if builtins.typeOf src == "path"
      then builtins.baseNameOf src
      else
        if builtins.isAttrs src && builtins.hasAttr "name" src
        then src.name
        else throw "applyPatches: please supply a `name` argument because a default name can only be computed when the `src` is a path or is an attribute set with a `name` attribute."
      ) + "-patched"
    , patches ? [ ]
    , prePatch ? ""
    , postPatch ? ""
    , ...
    }@args:
    if patches == [ ] && prePatch == "" && postPatch == ""
    then src # nothing to do, so use original src to avoid additional drv
    else stdenvNoCC.mkDerivation
      ({
        inherit name src patches prePatch postPatch;
        preferLocalBuild = true;
        allowSubstitutes = false;
        phases = "unpackPhase patchPhase installPhase";
        installPhase = "cp -R ./ $out";
      }
      # Carry `meta` information from the underlying `src` if present.
      // (optionalAttrs (src?meta) { inherit (src) meta; })
      // (removeAttrs args [ "src" "name" "patches" "prePatch" "postPatch" ]));

  # TODO: move docs to Nixpkgs manual
  /* An immutable file in the store with a length of 0 bytes. */
  emptyFile = runCommand "empty-file"
    {
      outputHashAlgo = "sha256";
      outputHashMode = "recursive";
      outputHash = "0ip26j2h11n1kgkz36rl4akv694yz65hr72q4kv4b3lxcbi65b3p";
      preferLocalBuild = true;
    } "touch $out";

  # TODO: move docs to Nixpkgs manual
  /* An immutable empty directory in the store. */
  emptyDirectory = runCommand "empty-directory"
    {
      outputHashAlgo = "sha256";
      outputHashMode = "recursive";
      outputHash = "0sjjj9z1dhilhpc8pq4154czrb79z9cm044jvn75kxcjv6v5l2m5";
      preferLocalBuild = true;
    } "mkdir $out";
}