packages feed

salmon-ops-0.1.0.0: src/Salmon/Builtin/Nodes/Cabal.hs

-- todo:
-- --cabal-file configuration
-- --optimization and profile modes
module Salmon.Builtin.Nodes.Cabal where

import Salmon.Builtin.Extension
import Salmon.Builtin.Nodes.Binary (Binary, Command (..), withBinary)
import qualified Salmon.Builtin.Nodes.Binary as Binary
import Salmon.Builtin.Nodes.Filesystem
import Salmon.Op.Ref
import Salmon.Op.Track
import Salmon.Reporter

import Data.Text (Text)
import qualified Data.Text as Text
import GHC.TypeLits (Symbol)

import System.FilePath (takeDirectory, (</>))
import System.Process.ByteString (readCreateProcessWithExitCode)
import System.Process.ListLike (CreateProcess, cwd, proc)

-------------------------------------------------------------------------------
data Report
    = CabalBuild !Cabal !Binary.Report
    | CabalInstall !Cabal !Binary.Report
    | CabalTest !Cabal !Binary.Report
    | CabalSDist !Cabal !Binary.Report
    | CabalUpload !CandidateStatus !FilePath !Binary.Report
    deriving (Show)

-------------------------------------------------------------------------------
data Cabal = Cabal {cabalDir :: FilePath, cabalTarget :: Text}
    deriving (Eq, Ord, Show)

type CabalFlags = [Flag]

data Flag
    = AllowNewer

data CabalRun
    = Build CabalFlags Cabal
    | Test Cabal
    | Install CabalFlags Cabal FilePath
    | SDist Cabal FilePath
    | Upload CandidateStatus FilePath

data CandidateStatus
    = Candidate
    | Public
    deriving (Show, Ord, Eq)

build :: Reporter Report -> Track' (Binary "cabal") -> CabalFlags -> Cabal -> Op
build r cabal flags c =
    withBinary cabal cabalRun (Build flags c) $ \up ->
        op "cabal-build" nodeps $ \actions ->
            actions
                { help = "cabal builds a target"
                , ref = mkRef "cabal-build" (show c)
                , up = up r'
                }
  where
    r' = contramap (CabalBuild c) r

install :: Reporter Report -> Track' (Binary "cabal") -> CabalFlags -> Cabal -> FilePath -> Op
install r cabal flags c installdir =
    withBinary cabal cabalRun (Install flags c installdir) $ \up ->
        op "cabal-install" previous $ \actions ->
            actions
                { help = "cabal builds a target"
                , ref = mkRef "cabal-install" (show c)
                , up = up r'
                }
  where
    r' = contramap (CabalInstall c) r
    previous = deps [dir (Directory installdir)]

test :: Reporter Report -> Track' (Binary "cabal") -> CabalFlags -> Cabal -> FilePath -> Op
test r cabal flags c installdir =
    withBinary cabal cabalRun (Test c) $ \up ->
        op "cabal-test" previous $ \actions ->
            actions
                { help = "cabal tests a target"
                , ref = mkRef "cabal-test" (show c)
                , up = up r'
                }
  where
    r' = contramap (CabalTest c) r
    previous = deps [dir (Directory installdir)]

sdist :: Reporter Report -> Track' (Binary "cabal") -> Cabal -> FilePath -> Op
sdist r cabal c dirpath =
    withBinary cabal cabalRun (SDist c dirpath) $ \up ->
        op "cabal-sdist" (deps [enclosingdir]) $ \actions ->
            actions
                { help = "cabal sdist a package"
                , ref = mkRef "cabal-sdist" (dirpath, c.cabalTarget)
                , up = up r'
                }
  where
    r' = contramap (CabalSDist c) r
    enclosingdir :: Op
    enclosingdir = dir (Directory dirpath)

upload :: Reporter Report -> Track' (Binary "cabal") -> Track' FilePath -> FilePath -> Op
upload r cabal mkTarfile path =
    withBinary cabal cabalRun (Upload Candidate path) $ \up ->
        op "cabal-upload" (deps [run mkTarfile path]) $ \actions ->
            actions
                { help = "cabal uploads a package"
                , ref = mkRef "cabal-upload" path
                , up = up r'
                }
  where
    r' = contramap (CabalUpload Candidate path) r

publish :: Reporter Report -> Track' (Binary "cabal") -> Track' FilePath -> FilePath -> Op
publish r cabal mkTarfile path =
    withBinary cabal cabalRun (Upload Public path) $ \up ->
        op "cabal-publishes" (deps [run mkTarfile path]) $ \actions ->
            actions
                { help = "cabal publishes a package"
                , ref = mkRef "cabal-publish" path
                , up = up r'
                }
  where
    r' = contramap (CabalUpload Public path) r

cabalRun :: Command "cabal" CabalRun
cabalRun = Command $ go
  where
    go (Build flags c) = (proc "cabal" (["build", Text.unpack c.cabalTarget] <> (extraArgs flags))){cwd = Just c.cabalDir}
    go (Test c) = (proc "cabal" ["test", Text.unpack c.cabalTarget]){cwd = Just c.cabalDir}
    go (Install flags c dir) = (proc "cabal" (["install", "--install-method=copy", "--overwrite-policy=always", "--installdir=" <> dir, Text.unpack c.cabalTarget] <> (extraArgs flags))){cwd = Just c.cabalDir}
    go (SDist c path) = (proc "cabal" ["sdist", "--output-directory=" <> path, Text.unpack c.cabalTarget]){cwd = Just c.cabalDir}
    go (Upload Candidate path) = (proc "cabal" ["upload", path])
    go (Upload Public path) = (proc "cabal" ["upload", "--publish", path])

    extraArgs :: CabalFlags -> [String]
    extraArgs xs = fmap extraArg xs

    extraArg :: Flag -> String
    extraArg AllowNewer = "--allow-newer"

data Instructions (s :: Symbol)
    = Instructions
    { installed_name :: Text
    , instructions_binary :: Track' (Binary "cabal")
    , instructions_cabal :: Cabal
    , instructions_cabal_flags :: CabalFlags
    , instructions_installdir :: FilePath
    }

data Installed (s :: Symbol)
    = Installed
    { installation_path :: FilePath
    }

installed :: Reporter Report -> Instructions a -> Tracked' (Installed a)
installed r instr =
    Tracked (Track $ const op) obj
  where
    op =
        install
            r
            instr.instructions_binary
            instr.instructions_cabal_flags
            instr.instructions_cabal
            instr.instructions_installdir
    installPath = instr.instructions_installdir </> Text.unpack instr.installed_name
    obj = Installed installPath