summaryrefslogtreecommitdiffstats
path: root/pkgs/shell/default.nix
blob: 119b23e66c34c307b428c0a747edb96c302f7191 (plain)
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
self: super:

let
  inherit (super) lib mylib;

  # Takes a path to a shellscript an builds it into a package.
  #
  # The shellscript may contain instructions for modifying the PATH variable:
  #   #!buildShellBin path=PKGS         resets the PATH variable to the specified packages
  #   #!buildShellBin append-path=PKGS  append the packages to the PATH variable
  #   #!buildShellBin prepend-path=PKGS prepend the packages to the PATH variable
  # where
  #   PKGS is a non-empty, colon-delimited list of package attribute names, e.g. coreutils:binutils:which
  buildShellBin = name: path:
    assert mylib.types.filename.check name; 
    assert isShellScript path;
    self.callPackage
      ({ pkgs }: pkgs.runCommand "${name}" {} /* sh */ ''
        mkdir -p $out/bin
        touch $out/bin/${name}
        chmod +x $out/bin/${name}
        exec > $out/bin/${name}
        echo '#! ${pkgs.dash}/bin/dash'
        echo ${mylib.shell.escape (lib.concatStringsSep "\n" (pathMods pkgs path))}
        cat ${path}
      '') {};

  # Determine if a line is an instruction to modify the PATH variable.
  readPathMod = pkgs: path: line: string: let
    match = builtins.match "^#! *buildShellBin ((prepend-|append-)?path)=(.*)" string;
  in
    lib.optionalAttrs (match != null) {
      inherit path line;
      method = builtins.elemAt match 0;
      packages = map (name: pkgs.${name}) (lib.splitString ":" (builtins.elemAt match 2));
    };

  # Generate shell assignment to modify the PATH variable.
  applyPathMod = { method, packages, ... }: {
    path = /* sh */ "PATH=${lib.makeBinPath packages}";
    append-path = /* sh */ "PATH=\${PATH+$PATH:}${lib.makeBinPath packages}";
    prepend-path = /* sh */ "PATH=${lib.makeBinPath packages}\${PATH+:$PATH}";
  }.${method};

  applyPathMods = mods:
    assert checkPathMods mods;
    map applyPathMod mods;

  # Check if a list of list of PATH modifications for sanity.
  checkPathMods = mods: let
    index = lib.lists.findFirstIndex (mod: mod.method == "path") 0 mods;
    mod = lib.elemAt mods index;
  in
    if index > 0 then
      builtins.trace "warning: resetting PATH in ${toString mod.path}:${toString mod.line}" true
    else
      true;

  pathMods = pkgs: path:
    applyPathMods
      (builtins.filter
        (x: x != {})
        (lib.imap1
          (readPathMod pkgs path)
          (lib.splitString "\n" (builtins.readFile path))));

  isShellScript = path:
    mylib.test "#! */bin/sh" (builtins.head (lib.splitString "\n" (builtins.readFile path)));

in

lib.mapAttrs
  (name: _: buildShellBin name (./. + "/${name}"))
  (lib.filterAttrs
    (name: _: name != "default.nix")
    (builtins.readDir ./.))