The sequence combinator will be useful for updating packages consisting of multiple sources.main
parent
d4b1bbb7d3
commit
7d6b5d171a
@ -0,0 +1,128 @@ |
||||
{ lib |
||||
}: |
||||
|
||||
/* |
||||
This is a set of tools to manipulate update scripts as recognized by update.nix. |
||||
It is still very experimental with **instability** almost guaranteed so any use |
||||
outside Nixpkgs is discouraged. |
||||
|
||||
update.nix currently accepts the following type: |
||||
|
||||
type UpdateScript |
||||
// Simple path to script to execute script |
||||
= FilePath |
||||
// Path to execute plus arguments to pass it |
||||
| [ (FilePath | String) ] |
||||
// Advanced attribue set (experimental) |
||||
| { |
||||
// Script to execute (same as basic update script above) |
||||
command : (FilePath | [ (FilePath | String) ]) |
||||
// Features that the script supports |
||||
// - commit: (experimental) returns commit message in stdout |
||||
supportedFeatures : ?[ "commit" ] |
||||
// Override attribute path detected by update.nix |
||||
attrPath : ?String |
||||
} |
||||
*/ |
||||
|
||||
let |
||||
/* |
||||
type ShellArg = String | { __rawShell : String } |
||||
*/ |
||||
|
||||
/* |
||||
Quotes all arguments to be safely passed to the Bourne shell. |
||||
|
||||
escapeShellArgs' : [ShellArg] -> String |
||||
*/ |
||||
escapeShellArgs' = lib.concatMapStringsSep " " (arg: if arg ? __rawShell then arg.__rawShell else lib.escapeShellArg arg); |
||||
|
||||
/* |
||||
processArg : { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } → (String|FilePath) → { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } |
||||
Helper reducer function for building a command arguments where file paths are replaced with argv[x] reference. |
||||
*/ |
||||
processArg = |
||||
{ maxArgIndex, args, paths }: |
||||
arg: |
||||
if builtins.isPath arg then { |
||||
args = args ++ [ { __rawShell = "\"\$${builtins.toString maxArgIndex}\""; } ]; |
||||
maxArgIndex = maxArgIndex + 1; |
||||
paths = paths ++ [ arg ]; |
||||
} else { |
||||
args = args ++ [ arg ]; |
||||
inherit maxArgIndex paths; |
||||
}; |
||||
/* |
||||
extractPaths : Int → [ (String|FilePath) ] → { maxArgIndex : Int, args : [ShellArg], paths : [FilePath] } |
||||
Helper function that extracts file paths from command arguments and replaces them with argv[x] references. |
||||
*/ |
||||
extractPaths = maxArgIndex: command: builtins.foldl' processArg { inherit maxArgIndex; args = [ ]; paths = [ ]; } command; |
||||
/* |
||||
processCommand : { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } → [ (String|FilePath) ] → { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } |
||||
Helper reducer function for extracting file paths from individual commands. |
||||
*/ |
||||
processCommand = |
||||
{ maxArgIndex, commands, paths }: |
||||
command: |
||||
let |
||||
new = extractPaths maxArgIndex command; |
||||
in |
||||
{ |
||||
commands = commands ++ [ new.args ]; |
||||
paths = paths ++ new.paths; |
||||
maxArgIndex = new.maxArgIndex; |
||||
}; |
||||
/* |
||||
extractCommands : Int → [[ (String|FilePath) ]] → { maxArgIndex : Int, commands : [[ShellArg]], paths : [FilePath] } |
||||
Helper function for extracting file paths from a list of commands and replacing them with argv[x] references. |
||||
*/ |
||||
extractCommands = maxArgIndex: commands: builtins.foldl' processCommand { inherit maxArgIndex; commands = [ ]; paths = [ ]; } commands; |
||||
|
||||
/* |
||||
commandsToShellInvocation : [[ (String|FilePath) ]] → [ (String|FilePath) ] |
||||
Converts a list of commands into a single command by turning them into a shell script and passing them to `sh -c`. |
||||
*/ |
||||
commandsToShellInvocation = commands: |
||||
let |
||||
extracted = extractCommands 0 commands; |
||||
in |
||||
[ |
||||
"sh" |
||||
"-c" |
||||
(lib.concatMapStringsSep ";" escapeShellArgs' extracted.commands) |
||||
# We need paths as separate arguments so that update.nix can ensure they refer to the local directory |
||||
# rather than a store path. |
||||
] ++ extracted.paths; |
||||
in |
||||
rec { |
||||
/* |
||||
normalize : UpdateScript → UpdateScript |
||||
EXPERIMENTAL! Converts a basic update script to the experimental attribute set form. |
||||
*/ |
||||
normalize = updateScript: { |
||||
command = lib.toList (updateScript.command or updateScript); |
||||
supportedFeatures = updateScript.supportedFeatures or [ ]; |
||||
} // lib.optionalAttrs (updateScript ? attrPath) { |
||||
inherit (updateScript) attrPath; |
||||
}; |
||||
|
||||
/* |
||||
sequence : [UpdateScript] → UpdateScript |
||||
EXPERIMENTAL! Combines multiple update scripts to run in sequence. |
||||
*/ |
||||
sequence = |
||||
scripts: |
||||
|
||||
let |
||||
scriptsNormalized = builtins.map normalize scripts; |
||||
in |
||||
let |
||||
scripts = scriptsNormalized; |
||||
validateFeatures = ({ supportedFeatures, ... }: supportedFeatures == [ ]); |
||||
in |
||||
|
||||
assert lib.assertMsg (lib.all validateFeatures scripts) "Combining update scripts with features enabled is currently unsupported."; |
||||
assert lib.assertMsg (builtins.length (lib.unique (builtins.map ({ attrPath ? null, ... }: attrPath) scripts)) == 1) "Combining update scripts with different attr paths is currently unsupported."; |
||||
|
||||
commandsToShellInvocation (builtins.map ({ command, ... }: command) scripts); |
||||
} |
Loading…
Reference in new issue