build-env-1.2.0.0: app/Main.hs
{-# LANGUAGE DataKinds #-}
module Main ( main ) where
-- base
import Control.Monad
( guard )
import Data.Foldable
( for_ )
import System.IO
( BufferMode(..), hSetBuffering, stdout )
-- bytestring
import qualified Data.ByteString.Lazy as Lazy.ByteString
( readFile, writeFile )
-- containers
import qualified Data.Map as Map
( empty )
import qualified Data.Set as Set
( empty, member )
-- directory
import System.Directory
-- text
import qualified Data.Text as Text
( pack )
-- build-env
import BuildEnv.Build
import BuildEnv.CabalPlan
import BuildEnv.Config
import BuildEnv.File
( parseCabalDotConfigPkgs, parseSeedFile )
import BuildEnv.Options
import BuildEnv.Parse
( runOptionsParser )
import BuildEnv.Path
--------------------------------------------------------------------------------
main :: IO ()
main = do
( GlobalOpts
{ compiler, cabal
, verbosity, delTemp, workDir
, indexState = mbIndexState }
, mode )
<- runOptionsParser
hSetBuffering stdout $ BlockBuffering Nothing
case mode of
PlanMode { planModeInputs, planOutput } -> do
CabalPlanBinary planBinary <-
computePlanFromInputs
delTemp verbosity workDir compiler cabal
mbIndexState planModeInputs
normalMsg verbosity $
"Writing build plan to" <> Text.pack ( show planOutput )
Lazy.ByteString.writeFile ( interpretSymbolicPath workDir planOutput ) planBinary
FetchMode ( FetchDescription { fetchDir, fetchInputPlan } ) newOrUpd -> do
plan <- getPlan delTemp verbosity workDir compiler cabal mbIndexState fetchInputPlan
doFetch verbosity cabal workDir fetchDir mbIndexState True newOrUpd plan
BuildMode ( Build { buildBuildPlan
, buildStart
, buildStrategy
, buildRawPaths = rawPaths
, mbOnlyDepsOf
, eventLogDir
, userUnitArgs } ) -> do
plan <- getPlan delTemp verbosity workDir compiler cabal mbIndexState buildBuildPlan
( pathsForPrep@( Paths { fetchDir }), pathsForBuild )
<- canonicalizePaths compiler buildStrategy workDir rawPaths
case buildStart of
Fetch newOrUpd -> doFetch verbosity cabal workDir fetchDir mbIndexState False newOrUpd plan
_ -> return ()
let mbOnlyDepsOfUnits :: Maybe [UnitId]
mbOnlyDepsOfUnits =
case mbOnlyDepsOf of
Nothing -> Nothing
Just pkgs ->
let wantUnit :: PlanUnit -> Maybe UnitId
wantUnit pu = do
ConfiguredUnit { puPkgName, puId } <- configuredUnitMaybe pu
guard ( puPkgName `Set.member` pkgs )
return puId
in Just $ mapMaybePlanUnits wantUnit plan
let resumeBuild = case buildStart of { Resume -> True; _ -> False }
buildPlan verbosity workDir pathsForPrep pathsForBuild eventLogDir
buildStrategy resumeBuild mbOnlyDepsOfUnits userUnitArgs plan
-- | Generate the contents of @pkg.cabal@ and @cabal.project@ files, using
--
-- - a seed file containing packages to build (with constraints, flags
-- and allow-newer),
-- - a @cabal.config@ freeze file,
-- - explicit packages and allow-newer specified as command-line arguments.
parsePlanInputs :: Verbosity
-> SymbolicPath CWD ( Dir Project )
-> Maybe IndexState -> PlanInputs -> IO CabalFilesContents
parsePlanInputs verbosity workDir mbIndexState
( PlanInputs { planPins, planUnits, planAllowNewer } )
= do
( pkgs, fileAllowNewer ) <- parsePlanUnits verbosity workDir planUnits
let
allAllowNewer = fileAllowNewer <> planAllowNewer
-- NB: allow-newer specified in the command-line overrides
-- the allow-newer included in the seed file.
cabalContents = cabalFileContentsFromPackages pkgs
pins <-
case planPins of
Nothing -> return Map.empty
Just ( FromFile pinCabalConfig ) -> do
normalMsg verbosity $
"Reading 'cabal.config' file at " <> Text.pack ( show pinCabalConfig )
pins <- parseCabalDotConfigPkgs ( interpretSymbolicPath workDir pinCabalConfig )
return pins
Just ( Explicit pins ) -> do
return pins
projectContents <-
cabalProjectContentsFromPackages workDir pkgs pins allAllowNewer mbIndexState
return $ CabalFilesContents { cabalContents, projectContents }
-- | Retrieve the seed units we want to build, either from a seed file
-- or from explicit command line arguments.
parsePlanUnits :: Verbosity
-> SymbolicPath CWD ( Dir Project )
-> PackageData UnitSpecs -> IO ( UnitSpecs, AllowNewer )
parsePlanUnits _ _ ( Explicit units ) = return (units, AllowNewer Set.empty)
parsePlanUnits verbosity workDir ( FromFile fp ) = do
normalMsg verbosity $
"Reading seed packages from " <> Text.pack ( show fp ) <> ""
parseSeedFile ( interpretSymbolicPath workDir fp )
-- | Compute a build plan by calling @cabal build --dry-run@ with the generated
-- @pkg.cabal@ and @cabal.project@ files.
computePlanFromInputs :: TempDirPermanence
-> Verbosity
-> SymbolicPath CWD ( Dir Project )
-> Compiler
-> Cabal
-> Maybe IndexState
-> PlanInputs
-> IO CabalPlanBinary
computePlanFromInputs delTemp verbosity workDir comp cabal mbIndexState inputs = do
cabalFileContents <- parsePlanInputs verbosity workDir mbIndexState inputs
normalMsg verbosity "Computing build plan"
computePlan delTemp verbosity comp cabal workDir cabalFileContents
-- | Retrieve a cabal build plan, either by computing it or using
-- a pre-existing @plan.json@ file.
getPlan :: TempDirPermanence -> Verbosity
-> SymbolicPath CWD ( Dir Project )
-> Compiler -> Cabal -> Maybe IndexState -> Plan -> IO CabalPlan
getPlan delTemp verbosity workDir comp cabal mbIndexState planMode = do
planBinary <-
case planMode of
ComputePlan planInputs mbPlanOutputPath -> do
plan@( CabalPlanBinary planData ) <-
computePlanFromInputs delTemp verbosity workDir comp cabal mbIndexState planInputs
for_ mbPlanOutputPath \ planOutputPath ->
Lazy.ByteString.writeFile ( interpretSymbolicPath workDir planOutputPath ) planData
return plan
UsePlan { planJSONPath } -> do
normalMsg verbosity $
"Reading build plan from " <> Text.pack ( show planJSONPath )
CabalPlanBinary <$> Lazy.ByteString.readFile ( interpretSymbolicPath workDir planJSONPath )
return $ parsePlanBinary planBinary
-- | Fetch all packages in a cabal build plan.
doFetch :: Verbosity
-> Cabal
-> SymbolicPath CWD ( Dir Project )
-- ^ working directory
-> SymbolicPath Project ( Dir Fetch )
-- ^ fetch directory
-> Maybe IndexState
-> Bool -- ^ True <=> we are fetching (not building)
-- (only relevant for error messages)
-> NewOrExisting
-> CabalPlan
-> IO ()
doFetch verbosity cabal workDir fetchDir mbIndexState weAreFetching newOrUpd plan = do
fetchDirExists <- doesDirectoryExist $ interpretSymbolicPath workDir fetchDir
case newOrUpd of
New | fetchDirExists ->
error $ unlines $
"Fetch directory already exists." : existsMsg
++ [ "Fetch directory: " <> show fetchDir ]
Existing | not fetchDirExists ->
error $ unlines
[ "Fetch directory must already exist when using --update."
, "Fetch directory: " <> show fetchDir ]
_ -> return ()
createDirectoryIfMissing True $ interpretSymbolicPath workDir fetchDir
normalMsg verbosity $
"Fetching sources from build plan into directory " <> Text.pack ( show fetchDir )
fetchPlan verbosity cabal workDir mbIndexState fetchDir plan
where
existsMsg
| weAreFetching
= [ "Use --update to update an existing directory." ]
| otherwise
= [ "Use --prefetched to build using a prefetched source directory,"
, "or --update to continue fetching before building." ]