about summary refs log tree commit diff
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2021-01-10T19·56+0100
committerProfpatsch <mail@profpatsch.de>2021-01-10T20·03+0000
commit2f807d7f141068d2d60676a89213eaa5353ca6e0 (patch)
tree4f2b13aab630c4febb5d9ccd3d701565009b6c39
parentc1cb4c260c97ad83a68f323dfeb4534f972c375a (diff)
feat(users/Profpatsch): add a rewriter for lib.stdenv changes r/2070
This is in order to advance the rewriting from stdenv.lib to lib.
https://github.com/NixOS/nixpkgs/issues/108938

The hard part about changing the argument is that a package might not
include lib in its arguments, which is why I use hnix to check whether
lib is included and add it to the import list if it doesn’t already
exist there.

So far, only the really common pattern of

    meta = with stdenv.lib;

is rewritten.

Change-Id: I370f0a321b0e5a5bd21ec21fc7cefdd65ec845ed
Reviewed-on: https://cl.tvl.fyi/c/depot/+/2345
Tested-by: BuildkiteCI
Reviewed-by: Profpatsch <mail@profpatsch.de>
-rw-r--r--users/Profpatsch/lib.nix23
-rw-r--r--users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs80
-rw-r--r--users/Profpatsch/nixpkgs-rewriter/default.nix84
3 files changed, 187 insertions, 0 deletions
diff --git a/users/Profpatsch/lib.nix b/users/Profpatsch/lib.nix
new file mode 100644
index 000000000000..8120a01d5bce
--- /dev/null
+++ b/users/Profpatsch/lib.nix
@@ -0,0 +1,23 @@
+{ depot, pkgs, ... }:
+let
+  bins = depot.nix.getBins pkgs.coreutils ["printf" "echo"];
+
+  debugExec = msg: depot.nix.writeExecline "debug-exec" {} [
+    "if" [
+      "fdmove" "-c" "1" "2"
+      "if" [ bins.printf "%s: " msg ]
+      "if" [ bins.echo "$@" ]
+    ]
+    "$@"
+  ];
+
+  eprintf = depot.nix.writeExecline "eprintf" {} [
+    "fdmove" "-c" "1" "2" bins.printf "%s" "$@"
+  ];
+
+in {
+  inherit
+    debugExec
+    eprintf
+    ;
+}
diff --git a/users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs b/users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs
new file mode 100644
index 000000000000..3ed96a7b6eac
--- /dev/null
+++ b/users/Profpatsch/nixpkgs-rewriter/MetaStdenvLib.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NamedFieldPuns #-}
+import Nix.Parser
+import Nix.Expr.Types
+import Nix.Expr.Types.Annotated
+import System.Environment (getArgs)
+import System.Exit (die)
+import Data.Fix (Fix(..))
+import qualified Data.Text as Text
+import qualified Data.ByteString.Lazy.Char8 as BL
+import qualified Data.Aeson as A
+import qualified Data.Aeson.Encoding as A
+import Data.Function ((&))
+import qualified System.IO as IO
+import qualified Text.Megaparsec.Pos as MP
+
+main = do
+  (nixFile:_) <- getArgs
+  (parseNixFileLoc nixFile :: IO _) >>= \case
+    Failure err -> do
+      ePutStrLn $ show err
+      die "oh no"
+    Success expr -> do
+      case snd $ match expr of
+        NoArguments -> do
+          ePutStrLn $ "NoArguments in " <> nixFile
+          printPairs mempty
+        YesLib vars -> do
+          ePutStrLn $ "lib in " <> show vars <> " in " <> nixFile
+          printPairs mempty
+        NoLib vars srcSpan -> do
+          ePutStrLn $ nixFile <> " needs lib added"
+          printPairs
+            $ "fileName" A..= nixFile
+            <> "fromLine" A..= (srcSpan & spanBegin & sourceLine)
+            <> "fromColumn" A..= (srcSpan & spanBegin & sourceColumn)
+            <> "toLine" A..= (srcSpan & spanEnd & sourceLine)
+            <> "toColumn" A..= (srcSpan & spanEnd & sourceColumn)
+
+printPairs pairs = BL.putStrLn $ A.encodingToLazyByteString $ A.pairs pairs
+
+ePutStrLn = IO.hPutStrLn IO.stderr
+
+data Descend = YesDesc | NoDesc
+  deriving Show
+data Matched =  NoArguments | NoLib [VarName] SrcSpan | YesLib [VarName]
+  deriving Show
+
+match :: Fix (Compose (Ann SrcSpan) NExprF) -> (Descend, Matched)
+match = \case
+  (AnnE outerSpan (NAbs (ParamSet params _ _) (AnnE innerSpan _))) -> (NoDesc,
+    let vars = map fst params in
+    case (any (== "lib") vars) of
+      True -> YesLib vars
+      False ->
+          -- The span of the arglist is from the beginning of the match
+          -- to the beginning of the inner expression
+          let varSpan = SrcSpan
+                { spanBegin = outerSpan & spanBegin
+                -- -1 to prevent the spans from overlapping
+                , spanEnd = sourcePosMinus1 (innerSpan & spanBegin) }
+          in NoLib vars varSpan)
+  _ -> (NoDesc, NoArguments)
+
+-- | Remove one from a source positon.
+--
+-- That means if the current position is at the very beginning of a line,
+-- jump to the previous line.
+sourcePosMinus1 :: SourcePos -> SourcePos
+sourcePosMinus1 src@(SourcePos { sourceLine, sourceColumn }) =
+  let
+    col = MP.mkPos $ max (MP.unPos sourceColumn - 1) 1
+    line = MP.mkPos $ case MP.unPos sourceColumn of
+      1 -> max (MP.unPos sourceLine - 1) 1
+      _ -> MP.unPos sourceLine
+  in src
+    { sourceLine = line
+    , sourceColumn = col }
diff --git a/users/Profpatsch/nixpkgs-rewriter/default.nix b/users/Profpatsch/nixpkgs-rewriter/default.nix
new file mode 100644
index 000000000000..ab86ff32090c
--- /dev/null
+++ b/users/Profpatsch/nixpkgs-rewriter/default.nix
@@ -0,0 +1,84 @@
+{ depot, pkgs, ... }:
+let
+  inherit (depot.nix)
+    writeExecline
+    ;
+  inherit (depot.users.Profpatsch.lib)
+    debugExec
+    eprintf
+    ;
+
+  export-json-object = pkgs.writers.writePython3 "export-json-object" {} ''
+    import json
+    import sys
+    import os
+
+    d = json.load(sys.stdin)
+
+    if d == {}:
+        sys.exit(0)
+
+    for k, v in d.items():
+        os.environ[k] = str(v)
+
+    os.execvp(sys.argv[1], sys.argv[1:])
+  '';
+
+  meta-stdenv-lib = pkgs.writers.writeHaskell "meta-stdenv-lib" {
+    libraries = [
+      pkgs.haskellPackages.hnix
+      pkgs.haskellPackages.aeson
+    ];
+  } ./MetaStdenvLib.hs;
+
+  replace-between-lines = writeExecline "replace-between-lines" { readNArgs = 1; } [
+    "importas" "-ui" "file" "fileName"
+    "importas" "-ui" "from" "fromLine"
+    "importas" "-ui" "to" "toLine"
+    "if" [ eprintf "\${from}-\${to}" ]
+    (debugExec "adding lib")
+    "sed"
+      "-e" "\${from},\${to} \${1}"
+      "-i" "$file"
+  ];
+
+  add-lib-if-necessary = writeExecline "add-lib-if-necessary" { readNArgs = 1; } [
+    "pipeline" [ meta-stdenv-lib "$1" ]
+     export-json-object
+     # first replace any stdenv.lib mentions in the arg header
+     # if this is not done, the replace below kills these.
+     # Since we want it anyway ultimately, let’s do it here.
+     "if" [ replace-between-lines "s/stdenv\.lib/lib/" ]
+     # then add the lib argument
+     # (has to be before stdenv, otherwise default arguments might be in the way)
+     replace-between-lines "s/stdenv/lib, stdenv/"
+  ];
+
+  metaString = ''meta = with stdenv.lib; {'';
+
+  replace-stdenv-lib = pkgs.writers.writeBash "replace-stdenv-lib" ''
+    set -euo pipefail
+    sourceDir="$1"
+    for file in $(
+      ${pkgs.ripgrep}/bin/rg \
+        --files-with-matches \
+        --fixed-strings \
+        -e '${metaString}' \
+        "$sourceDir"
+    )
+    do
+      echo "replacing stdenv.lib meta in $file" >&2
+      sed -e '/${metaString}/ s/stdenv.lib/lib/' \
+          -i "$file"
+      ${add-lib-if-necessary} "$file"
+    done
+  '';
+
+in {
+  # requires hnix, which we don’t want in tvl for now
+  # uncomment manually if you want to use it.
+  # inherit
+  #   meta-stdenv-lib
+  #   replace-stdenv-lib
+  #   ;
+}