packages feed

hwm-0.2.0: src/HWM/Integrations/Toolchain/Nix.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}

module HWM.Integrations.Toolchain.Nix (syncNixFile) where

import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import HWM.Core.Common (Name)
import HWM.Core.Formatting (format, toCamelCase)
import HWM.Core.Options (Options (..))
import HWM.Core.Pkg (Pkg (..))
import HWM.Core.Version (Era (eraNixpkgs), Version, formatNixGhc, selectEra)
import HWM.Domain.Config (Config (Config, cfgName))
import HWM.Domain.ConfigT (ConfigT, Env (..))
import HWM.Domain.Environments (BuildEnvironment (..), getBuildEnvironment)
import Relude

syncNixFile :: ConfigT ()
syncNixFile = do
  Config {..} <- asks config
  ops <- asks options
  BuildEnvironment {buildPkgs, buildGHC} <- getBuildEnvironment Nothing
  liftIO $ TIO.writeFile (optionsNix ops) (deriveFlakeNix cfgName buildGHC buildPkgs)

renderNixName :: Text -> Text
renderNixName name = toCamelCase name <> "WorkspacePackages"

pkgsNixName :: Text -> Text
pkgsNixName name = "pkgs." <> renderNixName name

generateOverlay :: Name -> Version -> [Pkg] -> [Text]
generateOverlay projectName ghc pkgs =
  [ "haskellOverlay = final: prev: {",
    "      " <> renderNixName projectName <> " = prev.haskell.packages." <> formatNixGhc ghc <> ".extend (hfinal: hprev: {"
  ]
    <> map renderPackage pkgs
    <> [ "      });",
         "};"
       ]
  where
    renderPackage pkg =
      "        " <> format (pkgName pkg) <> " = hfinal.callCabal2nix \"" <> format (pkgName pkg) <> "\" ./" <> format (pkgDirPath pkg) <> " {};"

deriveFlakeNix :: Name -> Version -> [Pkg] -> Text
deriveFlakeNix projectName version pkgs =
  T.unlines
    $ braces
      ( [ "description = \"A Haskell " <> projectName <> " workspace generated by HWM(Haskell Workspace Manager)\";",
          "inputs = {",
          "  nixpkgs.url = \"github:NixOS/nixpkgs/" <> eraNixpkgs (selectEra version) <> "\";",
          "};",
          "outputs = { self, nixpkgs }:"
        ]
          <> letBlock
            ( [ "supportedSystems = [ \"x86_64-linux\" \"aarch64-linux\" \"x86_64-darwin\" \"aarch64-darwin\" ];",
                "forAllSystems = nixpkgs.lib.genAttrs supportedSystems;"
              ]
                <> generateOverlay projectName version pkgs
            )
            ( forAllSystems "packages" (generatePublicPackages projectName pkgs)
                <> forAllSystems "devShells" (generateDevShell projectName pkgs)
            )
            False
      )

braces :: [Text] -> [Text]
braces body =
  ["{"] <> map ("  " <>) body <> ["}"]

generatePublicPackages :: Name -> [Pkg] -> [Text]
generatePublicPackages _ [] = []
generatePublicPackages projectName pkgs = map renderPublicPackage pkgs <> renderDefaultPackage
  where
    renderPublicPackage pkg = format (pkgName pkg) <> " = " <> pkgsNixName projectName <> "." <> format (pkgName pkg) <> ";"
    renderDefaultPackage =
      let defaultPkg = maybeToList $ find ((projectName ==) . format . pkgName) pkgs
       in map (\pkg -> "default = " <> pkgsNixName projectName <> "." <> format (pkgName pkg) <> ";") defaultPkg

generateDevShell :: Name -> [Pkg] -> [Text]
generateDevShell _ [] = [] -- Handle empty workspace
generateDevShell projectName pkgs =
  [ "default = " <> pkgsNixName projectName <> ".shellFor {",
    "  packages = p: [ " <> renderPackageList pkgs <> " ];",
    "  buildInputs = with " <> pkgsNixName projectName <> "; [",
    "    cabal-install",
    "    haskell-language-server",
    "    hlint",
    "  ];",
    "};"
  ]
  where
    renderPackageList = T.intercalate " " . map (\pkg -> "p." <> format (pkgName pkg))

letBlock :: [Text] -> [Text] -> Bool -> [Text]
letBlock h body end =
  ["  let"]
    <> map ("    " <>) h
    <> [ "  in",
         "  {"
       ]
    <> map ("    " <>) body
    <> if end then ["  });"] else ["  };"]

forAllSystems :: Text -> [Text] -> [Text]
forAllSystems system body =
  [system <> " = forAllSystems (system:"]
    <> letBlock
      ["pkgs = import nixpkgs { inherit system; overlays = [ haskellOverlay ]; };"]
      body
      True