at master 7.0 kB view raw
1{ 2 lib, 3}: 4 5/* 6 This is a set of tools to manipulate update scripts as recognized by update.nix. 7 It is still very experimental with **instability** almost guaranteed so any use 8 outside Nixpkgs is discouraged. 9 10 update.nix currently accepts the following type: 11 12 type UpdateScript 13 // Simple path to script to execute script 14 = FilePath 15 // Path to execute plus arguments to pass it 16 | [ (FilePath | String) ] 17 // Advanced attribute set (experimental) 18 | { 19 // Script to execute (same as basic update script above) 20 command : (FilePath | [ (FilePath | String) ]) 21 // Features that the script supports 22 // - commit: (experimental) returns commit message in stdout 23 // - silent: (experimental) returns no stdout 24 supportedFeatures : ?[ ("commit" | "silent") ] 25 // Override attribute path detected by update.nix 26 attrPath : ?String 27 } 28*/ 29 30let 31 # type ShellArg = String | { __rawShell : String } 32 33 /* 34 Quotes all arguments to be safely passed to the Bourne shell. 35 36 escapeShellArgs' : [ShellArg] -> String 37 */ 38 escapeShellArgs' = lib.concatMapStringsSep " " ( 39 arg: if arg ? __rawShell then arg.__rawShell else lib.escapeShellArg arg 40 ); 41 42 /* 43 processArg : { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } (String|FilePath) { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } 44 Helper reducer function for building a command arguments where file paths are replaced with argv[x] reference. 45 */ 46 processArg = 47 { 48 maxArgIndex, 49 args, 50 paths, 51 }: 52 arg: 53 if builtins.isPath arg then 54 { 55 args = args ++ [ { __rawShell = "\"\$${builtins.toString maxArgIndex}\""; } ]; 56 maxArgIndex = maxArgIndex + 1; 57 paths = paths ++ [ arg ]; 58 } 59 else 60 { 61 args = args ++ [ arg ]; 62 inherit maxArgIndex paths; 63 }; 64 /* 65 extractPaths : Int [ (String|FilePath) ] { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } 66 Helper function that extracts file paths from command arguments and replaces them with argv[x] references. 67 */ 68 extractPaths = 69 maxArgIndex: command: 70 builtins.foldl' processArg { 71 inherit maxArgIndex; 72 args = [ ]; 73 paths = [ ]; 74 } command; 75 /* 76 processCommand : { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } [ (String|FilePath) ] { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } 77 Helper reducer function for extracting file paths from individual commands. 78 */ 79 processCommand = 80 { 81 maxArgIndex, 82 commands, 83 paths, 84 }: 85 command: 86 let 87 new = extractPaths maxArgIndex command; 88 in 89 { 90 commands = commands ++ [ new.args ]; 91 paths = paths ++ new.paths; 92 maxArgIndex = new.maxArgIndex; 93 }; 94 /* 95 extractCommands : Int [[ (String|FilePath) ]] { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } 96 Helper function for extracting file paths from a list of commands and replacing them with argv[x] references. 97 */ 98 extractCommands = 99 maxArgIndex: commands: 100 builtins.foldl' processCommand { 101 inherit maxArgIndex; 102 commands = [ ]; 103 paths = [ ]; 104 } commands; 105 106 /* 107 commandsToShellInvocation : [[ (String|FilePath) ]] [ (String|FilePath) ] 108 Converts a list of commands into a single command by turning them into a shell script and passing them to `sh -c`. 109 */ 110 commandsToShellInvocation = 111 commands: 112 let 113 extracted = extractCommands 0 commands; 114 in 115 [ 116 "sh" 117 "-ec" 118 (lib.concatMapStringsSep ";" escapeShellArgs' extracted.commands) 119 # We need paths as separate arguments so that update.nix can ensure they refer to the local directory 120 # rather than a store path. 121 ] 122 ++ extracted.paths; 123in 124rec { 125 /* 126 normalize : UpdateScript UpdateScript 127 EXPERIMENTAL! Converts a basic update script to the experimental attribute set form. 128 */ 129 normalize = 130 updateScript: 131 { 132 command = lib.toList (updateScript.command or updateScript); 133 supportedFeatures = updateScript.supportedFeatures or [ ]; 134 } 135 // lib.optionalAttrs (updateScript ? attrPath) { 136 inherit (updateScript) attrPath; 137 }; 138 139 /* 140 sequence : [UpdateScript] UpdateScript 141 EXPERIMENTAL! Combines multiple update scripts to run in sequence. 142 */ 143 sequence = 144 scripts: 145 146 let 147 scriptsNormalized = builtins.map normalize scripts; 148 in 149 let 150 scripts = scriptsNormalized; 151 hasCommitSupport = 152 lib.findSingle ({ supportedFeatures, ... }: supportedFeatures == [ "commit" ]) null null scripts 153 != null; 154 hasSilentSupport = 155 lib.findFirst ({ supportedFeatures, ... }: supportedFeatures == [ "silent" ]) null scripts != null; 156 # Supported features currently only describe the format of the standard output of the update script. 157 # Here we ensure that the standard output of the combined update script is well formed. 158 validateFeatures = 159 if hasCommitSupport then 160 # Exactly one update script declares only “commit” feature and all the rest declare only “silent” feature. 161 ({ supportedFeatures, ... }: supportedFeatures == [ "commit" ] || supportedFeatures == [ "silent" ]) 162 else if hasSilentSupport then 163 # All update scripts declare only “silent” feature. 164 ({ supportedFeatures, ... }: supportedFeatures == [ "silent" ]) 165 else 166 # No update script declares any supported feature to fail loudly on unknown features rather than silently discard them. 167 ({ supportedFeatures, ... }: supportedFeatures == [ ]); 168 in 169 170 assert lib.assertMsg (lib.all validateFeatures scripts) 171 "Combining update scripts with features enabled (other than silent scripts and an optional single script with commit) is currently unsupported."; 172 173 assert lib.assertMsg ( 174 builtins.length ( 175 lib.unique ( 176 builtins.filter (attrPath: attrPath != null) ( 177 builtins.map ( 178 { 179 attrPath ? null, 180 ... 181 }: 182 attrPath 183 ) scripts 184 ) 185 ) 186 ) <= 1 187 ) "Combining update scripts with different attr paths is currently unsupported."; 188 189 { 190 command = commandsToShellInvocation (builtins.map ({ command, ... }: command) scripts); 191 supportedFeatures = 192 if hasCommitSupport then 193 [ "commit" ] 194 else if hasSilentSupport then 195 [ "silent" ] 196 else 197 [ ]; 198 }; 199 200 /* 201 copyAttrOutputToFile : String FilePath UpdateScript 202 EXPERIMENTAL! Simple update script that copies the output of Nix derivation built by `attr` to `path`. 203 */ 204 copyAttrOutputToFile = 205 attr: path: 206 207 { 208 command = [ 209 "sh" 210 "-c" 211 "cp --no-preserve=all \"$(nix-build -A ${attr})\" \"$0\" > /dev/null" 212 path 213 ]; 214 supportedFeatures = [ "silent" ]; 215 }; 216 217}