hwm-0.4.0: src/HWM/Integrations/Toolchain/Nix.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoImplicitPrelude #-}
module HWM.Integrations.Toolchain.Nix (syncNixFile) where
import qualified Data.Text as T
import HWM.Core.Common (Name)
import HWM.Core.Formatting (Status, format, toCamelCase)
import HWM.Core.Options (Options (..))
import HWM.Core.Pkg (Pkg (..))
import HWM.Core.Version (Era (eraNixpkgs), formatNixGhc, selectEra)
import HWM.Domain.Config (Config (Config, cfgName))
import HWM.Domain.ConfigT (ConfigT, Env (..))
import HWM.Domain.Environments (BuildEnvironment (..), getBuildEnvironment, getBuildEnvironments)
import HWM.Runtime.Files (syncFile)
import Relude
syncNixFile :: ConfigT Status
syncNixFile = do
Config {..} <- asks config
ops <- asks options
benv <- getBuildEnvironment Nothing
benvs <- filter buildNix <$> getBuildEnvironments
syncFile (optionsNix ops) (deriveFlakeNix (cfgName, benv) (map (cfgName,) benvs))
renderName :: Context -> Text
renderName (name, BuildEnvironment {..}) = toCamelCase (name <> T.toTitle (format buildName) <> "WorkspacePackages")
pkgsNixName :: Context -> Text
pkgsNixName ctx = "pkgs." <> renderName ctx
type Context = (Name, BuildEnvironment)
generateOverlay :: [Context] -> [Text]
generateOverlay benvs =
["haskellOverlay = final: prev: {"]
<> concatMap rendergOverlayItem benvs
<> ["};"]
rendergOverlayItem :: Context -> [Text]
rendergOverlayItem ctx@(_, BuildEnvironment {..}) =
[ " " <> renderName ctx <> " = prev.haskell.packages." <> formatNixGhc buildGHC <> ".extend (hfinal: hprev: {"
]
<> map renderPackage buildPkgs
<> [" });"]
renderPackage :: Pkg -> Text
renderPackage pkg = " " <> format (pkgName pkg) <> " = hfinal.callCabal2nix \"" <> format (pkgName pkg) <> "\" ./" <> format (pkgDirPath pkg) <> " {};"
deriveFlakeNix :: Context -> [Context] -> Text
deriveFlakeNix ctx@(projectName, benv) ctxs =
T.unlines
$ braces
( [ "description = \"A Haskell " <> projectName <> " workspace generated by HWM(Haskell Workspace Manager)\";",
"inputs = {",
" nixpkgs.url = \"github:NixOS/nixpkgs/" <> eraNixpkgs (selectEra (buildGHC benv)) <> "\";",
"};",
"outputs = { self, nixpkgs }:"
]
<> letBlock
( [ "supportedSystems = [ \"x86_64-linux\" \"aarch64-linux\" \"x86_64-darwin\" \"aarch64-darwin\" ];",
"forAllSystems = nixpkgs.lib.genAttrs supportedSystems;"
]
<> generateOverlay ctxs
)
( forAllSystems "packages" (generatePublicPackages projectName benv (map snd ctxs))
<> forAllSystems "devShells" (generateDevShell True ctx <> concatMap (generateDevShell False) ctxs)
<> ["checks = forAllSystems (system: self.packages.${system});"]
)
False
)
braces :: [Text] -> [Text]
braces body =
["{"] <> map (" " <>) body <> ["}"]
-- Notice the new signature: we pass the default environment, then all environments
generatePublicPackages :: Name -> BuildEnvironment -> [BuildEnvironment] -> [Text]
generatePublicPackages projectName defaultEnv allEnvs =
defaultPkgAlias <> basePkgs <> matrixPkgs
where
defaultOverlay = renderName (projectName, defaultEnv)
defaultPkgAlias =
let defaultPkg = filter ((projectName ==) . format . pkgName) (buildPkgs defaultEnv)
in map (\pkg -> "default = pkgs." <> defaultOverlay <> "." <> format (pkgName pkg) <> ";") defaultPkg
basePkgs =
map (\pkg -> format (pkgName pkg) <> " = pkgs." <> defaultOverlay <> "." <> format (pkgName pkg) <> ";") (buildPkgs defaultEnv)
matrixPkgs = concatMap (generateMatrixPkgs . (projectName,)) allEnvs
individualPkg :: Text -> Text -> Pkg -> Text
individualPkg overlay envName pkg = format (pkgName pkg) <> "-" <> envName <> " = pkgs." <> overlay <> "." <> format (pkgName pkg) <> ";"
generateMatrixPkgs :: (Name, BuildEnvironment) -> [Text]
generateMatrixPkgs (projectName, env) =
let overlay = renderName (projectName, env)
envRaw = format (buildName env)
envName = toCamelCase envRaw
pathList = map (\pkg -> " pkgs." <> overlay <> "." <> format (pkgName pkg)) (buildPkgs env)
in map (individualPkg overlay envName) (buildPkgs env)
<> [ "env-" <> envName <> "-all = pkgs.symlinkJoin {",
" name = \"" <> envRaw <> "-workspace\";",
" paths = [ "
]
<> pathList
<> [ " ];",
"};"
]
generateDevShell :: Bool -> Context -> [Text]
generateDevShell _ (_, BuildEnvironment {buildPkgs = []}) = [] -- Handle empty workspace
generateDevShell isDefault (projectName, benv@BuildEnvironment {..}) =
[ name <> " = " <> pkgsNixName (projectName, benv) <> ".shellFor {",
" packages = p: [ " <> renderPackageList buildPkgs <> " ];",
" buildInputs = with " <> pkgsNixName (projectName, benv) <> "; ["
]
<> map (" " <>) libs
<> [ " ];",
"};"
]
where
name = if isDefault then "default" else toCamelCase (format buildName)
libs = ["cabal-install", "hlint"] <> ["stack" | buildStack] <> ["haskell-language-server" | buildHie]
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