summary refs log tree commit diff
path: root/pkgs/common-updater
diff options
context:
space:
mode:
authorJan Tojnar <jtojnar@gmail.com>2022-09-04 11:53:26 +0200
committerJan Tojnar <jtojnar@gmail.com>2022-09-07 14:23:28 +0200
commit7d6b5d171adaf0a7d6808493629556d270c35c47 (patch)
treecc3ed30dda0485a3e7dea15650cdb4c0b337e92c /pkgs/common-updater
parentd4b1bbb7d3dbbcd2af90b845686ea2282c704a7e (diff)
downloadnixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.tar
nixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.tar.gz
nixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.tar.bz2
nixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.tar.lz
nixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.tar.xz
nixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.tar.zst
nixpkgs-7d6b5d171adaf0a7d6808493629556d270c35c47.zip
update-script-combinators: experimental init
The sequence combinator will be useful for updating packages consisting of multiple sources.
Diffstat (limited to 'pkgs/common-updater')
-rw-r--r--pkgs/common-updater/combinators.nix128
1 files changed, 128 insertions, 0 deletions
diff --git a/pkgs/common-updater/combinators.nix b/pkgs/common-updater/combinators.nix
new file mode 100644
index 00000000000..93fdac52f7c
--- /dev/null
+++ b/pkgs/common-updater/combinators.nix
@@ -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);
+}