packages feed

hwm-0.5.0: src/HWM/Domain/Build.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}

module HWM.Domain.Build
  ( Builder (..),
    BuilderCommand (..),
    BuildFlag (..),
    toExec,
    comandLabel,
  )
where

import Control.Monad.Error.Class (MonadError)
import Control.Monad.Except (throwError)
import Data.Aeson (FromJSON (..), ToJSON (toJSON))
import Data.Aeson.Types (Value (..))
import HWM.Core.Common (Name)
import HWM.Core.Formatting (Format (..))
import HWM.Core.Parsing (Parse (..))
import HWM.Core.Pkg (Pkg (..))
import HWM.Core.Result (Issue)
import HWM.Domain.Schema (TargetScope (..))
import HWM.Integrations.Toolchain.Nix.Build (NixEnv (..), inNixDevelop, nixBuild)
import HWM.Runtime.Process (EnvVars, Exec (..), mkExec)
import Relude

data Builder
  = CabalBuilder {inNixDevelopment :: Bool}
  | StackBuilder
  | NixBuilder
  deriving (Generic, Show, Ord, Eq)

instance FromJSON Builder where
  parseJSON (String s) = parse s
  parseJSON _ = fail "Invalid builder. Expected 'cabal', 'stack', or 'nix'."

instance Parse Builder where
  parse "cabal" = pure CabalBuilder {inNixDevelopment = False}
  parse "nix/cabal" = pure CabalBuilder {inNixDevelopment = True}
  parse "stack" = pure StackBuilder
  parse "nix" = pure NixBuilder
  parse _ = fail "Invalid builder. Expected 'cabal', 'stack', 'nix', or 'nix/cabal'."

instance ToJSON Builder where
  toJSON = String . format

instance Format Builder where
  format (CabalBuilder False) = "cabal"
  format (CabalBuilder True) = "nix/cabal"
  format StackBuilder = "stack"
  format NixBuilder = "nix"

data Cmd = ExecCmd Text [Text] EnvVars | CusomCmd Text
  deriving (Eq, Show)

comandLabel :: BuilderCommand -> Text
comandLabel Build {} = "build"
comandLabel Test {} = "test"
comandLabel Install {} = "build"
comandLabel BuildArtifact {} = "build"

data BuilderCommand
  = Build
  | Test
  | Install {dirPath :: FilePath}
  | BuildArtifact {dirPath :: FilePath}
  deriving (Eq, Show)

data BuildFlag
  = CustomBuildFlag Text
  | BuildFastFlag
  | GHCOptionsFlag Text
  deriving (Eq, Show)

formatFlag :: Builder -> BuildFlag -> [Text]
-- Nix does not accept '--ghc-options' via CLI; it must be set in the flake.
formatFlag NixBuilder _ = []
formatFlag CabalBuilder {} BuildFastFlag = ["--disable-optimization"]
formatFlag StackBuilder BuildFastFlag = ["--fast"]
formatFlag _ (GHCOptionsFlag xs) = ["--ghc-options=" <> xs]
formatFlag _ (CustomBuildFlag txt) = [txt]

toExec :: (MonadIO m, MonadError Issue m) => Name -> Builder -> BuilderCommand -> TargetScope -> [BuildFlag] -> [(String, String)] -> m (Exec m)
toExec envName builder cmd scope flags envs = do
  Exec {..} <- toAction (NixEnv envName) builder cmd scope
  pure $ inNixDevelop envName nixEnabled $ Exec execCmd (execArgs <> concatMap (formatFlag builder) flags) envs postCommand
  where
    nixEnabled = CabalBuilder {inNixDevelopment = True} == builder

handleScope :: Maybe Text -> TargetScope -> [Text]
handleScope fb ScopeGlobal = maybeToList fb
handleScope _ (ScopePkgs pkgs) = map (format . pkgName) pkgs

mkStack :: (Applicative m) => Text -> TargetScope -> [Text] -> m (Exec m)
mkStack cmd scope ops = mkExec "stack" ([cmd] <> handleScope Nothing scope <> ops)

mkCabal :: (Applicative m) => Bool -> Text -> TargetScope -> [Text] -> m (Exec m)
mkCabal install cmd scope ops = mkExec "cabal" ([cmd] <> handleScope (if install then Just "all:exes" else Just "all") scope <> ops)

installCabal :: (Applicative m, Format a) => TargetScope -> a -> m (Exec m)
installCabal scope dirPath = mkCabal True "install" scope ["--install-method=copy", "--installdir", format dirPath, "--overwrite-policy=always"]

installStack :: (Applicative m, Format a) => TargetScope -> a -> m (Exec m)
installStack scope dirPath = mkStack "install" scope ["--local-bin-path", format dirPath]

toAction :: (MonadError Issue m, MonadIO m) => NixEnv -> Builder -> BuilderCommand -> TargetScope -> m (Exec m)
-- Stack
toAction _ StackBuilder Build scope = mkStack "build" scope []
toAction _ StackBuilder Test scope = mkStack "test" scope []
-- Cabal
toAction _ CabalBuilder {} Build scope = mkCabal False "build" scope []
toAction _ CabalBuilder {} Test scope = mkCabal False "test" scope []
-- Nix
toAction ctx NixBuilder Build scope = nixBuild ctx scope
toAction ctx NixBuilder Test scope = nixBuild ctx scope
-- INSTALL
toAction _ CabalBuilder {..} Install {..} scope
  | inNixDevelopment = throwError "Install command with Nix development environment is not supported"
  | otherwise = installCabal scope dirPath
toAction _ StackBuilder Install {..} scope = installStack scope dirPath
toAction _ NixBuilder Install {} _ = throwError "Install command with Nix builder is not supported"
--
toAction _ NixBuilder BuildArtifact {} _ = throwError "Nix Binaries is not supported yet"
toAction _ StackBuilder BuildArtifact {..} scope = installStack scope dirPath
toAction _ CabalBuilder {} BuildArtifact {..} scope = installCabal scope dirPath