Merge pull request #329823 from ExpidusOS/fix/pkgsllvm/elfutils
[NixPkgs.git] / pkgs / common-updater / combinators.nix
blobdadbb1e81ca49d267df03150eb686d0237071a86
1 { lib
2 }:
4 /*
5   This is a set of tools to manipulate update scripts as recognized by update.nix.
6   It is still very experimental with **instability** almost guaranteed so any use
7   outside Nixpkgs is discouraged.
9   update.nix currently accepts the following type:
11   type UpdateScript
12     // Simple path to script to execute script
13     = FilePath
14     // Path to execute plus arguments to pass it
15     | [ (FilePath | String) ]
16     // Advanced attribue set (experimental)
17     | {
18       // Script to execute (same as basic update script above)
19       command : (FilePath | [ (FilePath | String) ])
20       // Features that the script supports
21       // - commit: (experimental) returns commit message in stdout
22       // - silent: (experimental) returns no stdout
23       supportedFeatures : ?[ ("commit" | "silent") ]
24       // Override attribute path detected by update.nix
25       attrPath : ?String
26     }
29 let
30   /*
31     type ShellArg = String | { __rawShell : String }
32   */
34   /*
35     Quotes all arguments to be safely passed to the Bourne shell.
37     escapeShellArgs' : [ShellArg] -> String
38   */
39   escapeShellArgs' = lib.concatMapStringsSep " " (arg: if arg ? __rawShell then arg.__rawShell else lib.escapeShellArg arg);
41   /*
42     processArg : { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } → (String|FilePath) → { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] }
43     Helper reducer function for building a command arguments where file paths are replaced with argv[x] reference.
44   */
45   processArg =
46     { maxArgIndex, args, paths }:
47     arg:
48     if builtins.isPath arg then {
49       args = args ++ [ { __rawShell = "\"\$${builtins.toString maxArgIndex}\""; } ];
50       maxArgIndex = maxArgIndex + 1;
51       paths = paths ++ [ arg ];
52     } else {
53       args = args ++ [ arg ];
54       inherit maxArgIndex paths;
55     };
56   /*
57     extractPaths : Int → [ (String|FilePath) ] → { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] }
58     Helper function that extracts file paths from command arguments and replaces them with argv[x] references.
59   */
60   extractPaths = maxArgIndex: command: builtins.foldl' processArg { inherit maxArgIndex; args = [ ]; paths = [ ]; } command;
61   /*
62     processCommand : { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } → [ (String|FilePath) ] → { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] }
63     Helper reducer function for extracting file paths from individual commands.
64   */
65   processCommand =
66     { maxArgIndex, commands, paths }:
67     command:
68     let
69       new = extractPaths maxArgIndex command;
70     in
71     {
72       commands = commands ++ [ new.args ];
73       paths = paths ++ new.paths;
74       maxArgIndex = new.maxArgIndex;
75     };
76   /*
77     extractCommands : Int → [[ (String|FilePath) ]] → { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] }
78     Helper function for extracting file paths from a list of commands and replacing them with argv[x] references.
79   */
80   extractCommands = maxArgIndex: commands: builtins.foldl' processCommand { inherit maxArgIndex; commands = [ ]; paths = [ ]; } commands;
82   /*
83     commandsToShellInvocation : [[ (String|FilePath) ]] → [ (String|FilePath) ]
84     Converts a list of commands into a single command by turning them into a shell script and passing them to `sh -c`.
85   */
86   commandsToShellInvocation = commands:
87     let
88       extracted = extractCommands 0 commands;
89     in
90     [
91       "sh"
92       "-ec"
93       (lib.concatMapStringsSep ";" escapeShellArgs' extracted.commands)
94       # We need paths as separate arguments so that update.nix can ensure they refer to the local directory
95       # rather than a store path.
96     ] ++ extracted.paths;
98 rec {
99   /*
100     normalize : UpdateScript → UpdateScript
101     EXPERIMENTAL! Converts a basic update script to the experimental attribute set form.
102   */
103   normalize = updateScript: {
104     command = lib.toList (updateScript.command or updateScript);
105     supportedFeatures = updateScript.supportedFeatures or [ ];
106   } // lib.optionalAttrs (updateScript ? attrPath) {
107     inherit (updateScript) attrPath;
108   };
110   /*
111     sequence : [UpdateScript] → UpdateScript
112     EXPERIMENTAL! Combines multiple update scripts to run in sequence.
113   */
114   sequence =
115     scripts:
117     let
118       scriptsNormalized = builtins.map normalize scripts;
119     in
120     let
121       scripts = scriptsNormalized;
122       hasCommitSupport = lib.findSingle ({ supportedFeatures, ... }: supportedFeatures == [ "commit" ]) null null scripts != null;
123       validateFeatures =
124         if hasCommitSupport then
125           ({ supportedFeatures, ... }: supportedFeatures == [ "commit" ] || supportedFeatures == [ "silent" ])
126         else
127           ({ supportedFeatures, ... }: supportedFeatures == [ ]);
128     in
130     assert lib.assertMsg (lib.all validateFeatures scripts) "Combining update scripts with features enabled (other than a single script with “commit” and all other with “silent”) is currently unsupported.";
131     assert lib.assertMsg (builtins.length (lib.unique (builtins.map ({ attrPath ? null, ... }: attrPath) scripts)) == 1) "Combining update scripts with different attr paths is currently unsupported.";
133     {
134       command = commandsToShellInvocation (builtins.map ({ command, ... }: command) scripts);
135       supportedFeatures = lib.optionals hasCommitSupport [
136         "commit"
137       ];
138     };
140   /*
141     copyAttrOutputToFile : String → FilePath → UpdateScript
142     EXPERIMENTAL! Simple update script that copies the output of Nix derivation built by `attr` to `path`.
143   */
144   copyAttrOutputToFile =
145     attr:
146     path:
148     {
149       command = [
150         "sh"
151         "-c"
152         "cp --no-preserve=all \"$(nix-build -A ${attr})\" \"$0\" > /dev/null"
153         path
154       ];
155       supportedFeatures = [ "silent" ];
156     };