about summary refs log tree commit diff
path: root/pkgs/development/misc/resholve/resholve-utils.nix
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/development/misc/resholve/resholve-utils.nix')
-rw-r--r--pkgs/development/misc/resholve/resholve-utils.nix169
1 files changed, 149 insertions, 20 deletions
diff --git a/pkgs/development/misc/resholve/resholve-utils.nix b/pkgs/development/misc/resholve/resholve-utils.nix
index 2d3c55b875631..27e347e7c4aa3 100644
--- a/pkgs/development/misc/resholve/resholve-utils.nix
+++ b/pkgs/development/misc/resholve/resholve-utils.nix
@@ -1,4 +1,4 @@
-{ lib, resholve, binlore }:
+{ lib, stdenv, resholve, binlore, writeTextFile }:
 
 rec {
   /* These functions break up the work of partially validating the
@@ -10,6 +10,7 @@ rec {
 
   # for brevity / line length
   spaces = l: builtins.concatStringsSep " " l;
+  colons = l: builtins.concatStringsSep ":" l;
   semicolons = l: builtins.concatStringsSep ";" l;
 
   /* Throw a fit with dotted attr path context */
@@ -17,58 +18,186 @@ rec {
     throw "${builtins.concatStringsSep "." path}: ${msg}";
 
   /* Special-case directive value representations by type */
-  makeDirective = solution: env: name: val:
+  phraseDirective = solution: env: name: val:
     if builtins.isInt val then builtins.toString val
     else if builtins.isString val then name
     else if true == val then name
     else if false == val then "" # omit!
     else if null == val then "" # omit!
-    else if builtins.isList val then "${name}:${semicolons val}"
+    else if builtins.isList val then "${name}:${semicolons (map lib.escapeShellArg val)}"
     else nope [ solution env name ] "unexpected type: ${builtins.typeOf val}";
 
   /* Build fake/fix/keep directives from Nix types */
-  makeDirectives = solution: env: val:
-    lib.mapAttrsToList (makeDirective solution env) val;
+  phraseDirectives = solution: env: val:
+    lib.mapAttrsToList (phraseDirective solution env) val;
+
+  /* Custom ~search-path routine to handle relative path strings */
+  relSafeBinPath = input:
+    if lib.isDerivation input then ((lib.getOutput "bin" input) + "/bin")
+    else if builtins.isString input then input
+    else throw "unexpected type for input: ${builtins.typeOf input}";
 
   /* Special-case value representation by type/name */
-  makeEnvVal = solution: env: val:
-    if env == "inputs" then lib.makeBinPath val
+  phraseEnvVal = solution: env: val:
+    if env == "inputs" then (colons (map relSafeBinPath val))
     else if builtins.isString val then val
     else if builtins.isList val then spaces val
-    else if builtins.isAttrs val then spaces (makeDirectives solution env val)
+    else if builtins.isAttrs val then spaces (phraseDirectives solution env val)
     else nope [ solution env ] "unexpected type: ${builtins.typeOf val}";
 
   /* Shell-format each env value */
   shellEnv = solution: env: value:
-    lib.escapeShellArg (makeEnvVal solution env value);
+    lib.escapeShellArg (phraseEnvVal solution env value);
 
   /* Build a single ENV=val pair */
-  makeEnv = solution: env: value:
+  phraseEnv = solution: env: value:
     "RESHOLVE_${lib.toUpper env}=${shellEnv solution env value}";
 
-  /* Discard attrs claimed by makeArgs */
-  removeCliArgs = value:
-    removeAttrs value [ "scripts" "flags" ];
+  /* Discard attrs:
+  - claimed by phraseArgs
+  - only needed for binlore.collect
+  */
+  removeUnneededArgs = value:
+    removeAttrs value [ "scripts" "flags" "unresholved" ];
 
   /* Verify required arguments are present */
   validateSolution = { scripts, inputs, interpreter, ... }: true;
 
   /* Pull out specific solution keys to build ENV=val pairs */
-  makeEnvs = solution: value:
-    spaces (lib.mapAttrsToList (makeEnv solution) (removeCliArgs value));
+  phraseEnvs = solution: value:
+    spaces (lib.mapAttrsToList (phraseEnv solution) (removeUnneededArgs value));
 
   /* Pull out specific solution keys to build CLI argstring */
-  makeArgs = { flags ? [ ], scripts, ... }:
+  phraseArgs = { flags ? [ ], scripts, ... }:
     spaces (flags ++ scripts);
 
+  phraseBinloreArgs = value:
+    let
+      hasUnresholved = builtins.hasAttr "unresholved" value;
+    in {
+      drvs = value.inputs ++
+        lib.optionals hasUnresholved [ value.unresholved ];
+      strip = if hasUnresholved then [ value.unresholved ] else [ ];
+    };
+
   /* Build a single resholve invocation */
-  makeInvocation = solution: value:
+  phraseInvocation = solution: value:
     if validateSolution value then
     # we pass resholve a directory
-      "RESHOLVE_LORE=${binlore.collect { drvs = value.inputs; } } ${makeEnvs solution value} ${resholve}/bin/resholve --overwrite ${makeArgs value}"
+      "RESHOLVE_LORE=${binlore.collect (phraseBinloreArgs value) } ${phraseEnvs solution value} ${resholve}/bin/resholve --overwrite ${phraseArgs value}"
     else throw "invalid solution"; # shouldn't trigger for now
 
+  injectUnresholved = solutions: unresholved: (builtins.mapAttrs (name: value: value // { inherit unresholved; } ) solutions);
+
   /* Build resholve invocation for each solution. */
-  makeCommands = solutions:
-    lib.mapAttrsToList makeInvocation solutions;
+  phraseCommands = solutions: unresholved:
+    builtins.concatStringsSep "\n" (
+      lib.mapAttrsToList phraseInvocation (injectUnresholved solutions unresholved)
+    );
+
+  /*
+    subshell/PS4/set -x and : command to output resholve envs
+    and invocation. Extra context makes it clearer what the
+    Nix API is doing, makes nix-shell debugging easier, etc.
+  */
+  phraseContext = { invokable, prep ? ''cd "$out"'' }: ''
+    (
+      ${prep}
+      PS4=$'\x1f'"\033[33m[resholve context]\033[0m "
+      set -x
+      : invoking resholve with PWD=$PWD
+      ${invokable}
+    )
+  '';
+  phraseContextForPWD = invokable: phraseContext { inherit invokable; prep = ""; };
+  phraseContextForOut = invokable: phraseContext { inherit invokable; };
+
+  phraseSolution = name: solution: (phraseContextForOut (phraseInvocation name solution));
+  phraseSolutions = solutions: unresholved:
+    phraseContextForOut (phraseCommands solutions unresholved);
+
+  writeScript = name: partialSolution: text:
+    writeTextFile {
+      inherit name text;
+      executable = true;
+      checkPhase = ''
+         ${(phraseContextForPWD (
+             phraseInvocation name (
+               partialSolution // {
+                 scripts = [ "${placeholder "out"}" ];
+               }
+             )
+           )
+         )}
+        ${partialSolution.interpreter} -n $out
+      '';
+    };
+  writeScriptBin = name: partialSolution: text:
+    writeTextFile rec {
+      inherit name text;
+      executable = true;
+      destination = "/bin/${name}";
+      checkPhase = ''
+        ${phraseContextForOut (
+            phraseInvocation name (
+              partialSolution // {
+                scripts = [ "bin/${name}" ];
+              }
+            )
+          )
+        }
+        ${partialSolution.interpreter} -n $out/bin/${name}
+      '';
+    };
+  mkDerivation = { pname
+    , src
+    , version
+    , passthru ? { }
+    , solutions
+    , ...
+    }@attrs:
+    let
+      inherit stdenv;
+
+      /*
+      Knock out our special solutions arg, but otherwise
+      just build what the caller is giving us. We'll
+      actually resholve it separately below (after we
+      generate binlore for it).
+      */
+      unresholved = (stdenv.mkDerivation ((removeAttrs attrs [ "solutions" ])
+        // {
+        inherit pname version src;
+      }));
+    in
+    /*
+    resholve in a separate derivation; some concerns:
+    - we aren't keeping many of the user's args, so they
+      can't readily set LOGLEVEL and such...
+    - not sure how this affects multiple outputs
+    */
+    lib.extendDerivation true passthru (stdenv.mkDerivation {
+      src = unresholved;
+      version = unresholved.version;
+      pname = "resholved-${unresholved.pname}";
+      buildInputs = [ resholve ];
+
+      # retain a reference to the base
+      passthru = unresholved.passthru // {
+        unresholved = unresholved;
+      };
+
+      # do these imply that we should use NoCC or something?
+      dontConfigure = true;
+      dontBuild = true;
+
+      installPhase = ''
+        cp -R $src $out
+      '';
+
+      # enable below for verbose debug info if needed
+      # supports default python.logging levels
+      # LOGLEVEL="INFO";
+      preFixup = phraseSolutions solutions unresholved;
+    });
 }