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