core/pkgs/common-updater/combinators.nix

196 lines
5.9 KiB
Nix
Raw Normal View History

2024-06-30 08:16:52 +00:00
{ lib }:
2024-05-02 00:46:19 +00:00
/*
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
// - silent: (experimental) returns no stdout
supportedFeatures : ?[ ("commit" | "silent") ]
// Override attribute path detected by update.nix
attrPath : ?String
}
*/
let
2024-06-30 08:16:52 +00:00
# type ShellArg = String | { __rawShell : String }
2024-05-02 00:46:19 +00:00
/*
Quotes all arguments to be safely passed to the Bourne shell.
escapeShellArgs' : [ShellArg] -> String
*/
2024-06-30 08:16:52 +00:00
escapeShellArgs' = lib.concatMapStringsSep " " (
arg: if arg ? __rawShell then arg.__rawShell else lib.escapeShellArg arg
);
2024-05-02 00:46:19 +00:00
/*
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 =
2024-06-30 08:16:52 +00:00
{
maxArgIndex,
args,
paths,
}:
2024-05-02 00:46:19 +00:00
arg:
2024-06-30 08:16:52 +00:00
if builtins.isPath arg then
{
args = args ++ [ { __rawShell = "\"\$${builtins.toString maxArgIndex}\""; } ];
maxArgIndex = maxArgIndex + 1;
paths = paths ++ [ arg ];
}
else
{
args = args ++ [ arg ];
inherit maxArgIndex paths;
};
2024-05-02 00:46:19 +00:00
/*
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.
*/
2024-06-30 08:16:52 +00:00
extractPaths =
maxArgIndex: command:
builtins.foldl' processArg {
inherit maxArgIndex;
args = [ ];
paths = [ ];
} command;
2024-05-02 00:46:19 +00:00
/*
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 =
2024-06-30 08:16:52 +00:00
{
maxArgIndex,
commands,
paths,
}:
2024-05-02 00:46:19 +00:00
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.
*/
2024-06-30 08:16:52 +00:00
extractCommands =
maxArgIndex: commands:
builtins.foldl' processCommand {
inherit maxArgIndex;
commands = [ ];
paths = [ ];
} commands;
2024-05-02 00:46:19 +00:00
/*
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`.
*/
2024-06-30 08:16:52 +00:00
commandsToShellInvocation =
commands:
2024-05-02 00:46:19 +00:00
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.
2024-06-30 08:16:52 +00:00
]
++ extracted.paths;
2024-05-02 00:46:19 +00:00
in
rec {
/*
normalize : UpdateScript UpdateScript
EXPERIMENTAL! Converts a basic update script to the experimental attribute set form.
*/
2024-06-30 08:16:52 +00:00
normalize =
updateScript:
{
command = lib.toList (updateScript.command or updateScript);
supportedFeatures = updateScript.supportedFeatures or [ ];
}
// lib.optionalAttrs (updateScript ? attrPath) { inherit (updateScript) attrPath; };
2024-05-02 00:46:19 +00:00
/*
sequence : [UpdateScript] UpdateScript
EXPERIMENTAL! Combines multiple update scripts to run in sequence.
*/
sequence =
scripts:
let
scriptsNormalized = builtins.map normalize scripts;
in
let
scripts = scriptsNormalized;
2024-06-30 08:16:52 +00:00
hasCommitSupport =
lib.findSingle ({ supportedFeatures, ... }: supportedFeatures == [ "commit" ]) null null scripts
!= null;
2024-05-02 00:46:19 +00:00
validateFeatures =
if hasCommitSupport then
({ supportedFeatures, ... }: supportedFeatures == [ "commit" ] || supportedFeatures == [ "silent" ])
else
({ supportedFeatures, ... }: supportedFeatures == [ ]);
in
2024-06-30 08:16:52 +00:00
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.";
assert lib.assertMsg (
builtins.length (
lib.unique (
builtins.map (
{
attrPath ? null,
...
}:
attrPath
) scripts
)
) == 1
) "Combining update scripts with different attr paths is currently unsupported.";
2024-05-02 00:46:19 +00:00
{
command = commandsToShellInvocation (builtins.map ({ command, ... }: command) scripts);
2024-06-30 08:16:52 +00:00
supportedFeatures = lib.optionals hasCommitSupport [ "commit" ];
2024-05-02 00:46:19 +00:00
};
/*
copyAttrOutputToFile : String FilePath UpdateScript
EXPERIMENTAL! Simple update script that copies the output of Nix derivation built by `attr` to `path`.
*/
copyAttrOutputToFile =
2024-06-30 08:16:52 +00:00
attr: path:
2024-05-02 00:46:19 +00:00
{
command = [
"sh"
"-c"
"cp --no-preserve=all \"$(nix-build -A ${attr})\" \"$0\" > /dev/null"
path
];
supportedFeatures = [ "silent" ];
};
}