guardian-0.4.0.0: src/Development/Guardian/Graph/Adapter/Cabal.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module Development.Guardian.Graph.Adapter.Cabal (
buildPackageGraph,
CustomPackageOptions (..),
Cabal,
) where
import qualified Algebra.Graph as G
import Control.Applicative (optional, (<|>))
import Control.Monad (when)
import Data.Aeson (FromJSON, withObject, (.:))
import qualified Data.Aeson.KeyMap as AKM
import Data.Aeson.Types (FromJSON (..))
import Data.Either (fromLeft)
import Data.Generics.Labels ()
import qualified Data.Map.Strict as Map
import Data.Maybe
import qualified Data.Set as Set
import Data.String (fromString)
import Development.Guardian.Graph.Adapter.Types (ComponentsOptions (..), CustomPackageOptions, PackageGraphOptions (..))
import Development.Guardian.Types (Overlayed (..), PackageGraph)
import qualified Development.Guardian.Types as Guard
import Distribution.Client.CmdUpdate (updateAction)
import Distribution.Client.InstallPlan (GenericPlanPackage (..), depends)
import qualified Distribution.Client.InstallPlan as Plan
import Distribution.Client.NixStyleOptions (defaultNixStyleFlags)
import Distribution.Client.ProjectConfig (ProjectRoot (..))
import Distribution.Client.ProjectOrchestration (CurrentCommand (..), ProjectBaseContext (..), establishProjectBaseContextWithRoot, withInstallPlan)
import Distribution.Client.ProjectPlanning (ElaboratedConfiguredPackage (..), elabLocalToProject)
import Distribution.Package (Package (..), packageName, unPackageName)
import Distribution.Simple.Flag (Flag (..))
import Distribution.Verbosity (silent)
import GHC.Generics (Generic)
import Lens.Micro
import Path
data Cabal deriving (Generic)
data instance CustomPackageOptions Cabal = CabalOptions {projectFile :: Maybe (Path Rel File), update :: Maybe (Either Bool String)}
deriving (Show, Eq, Ord, Generic)
instance FromJSON (CustomPackageOptions Cabal) where
parseJSON = withObject "{cabal: ...}" $ \obj ->
if AKM.member "cabal" obj
then do
dic <- obj .: "cabal"
projectFile <- optional $ dic .: "projectFile"
update <-
if AKM.member "update" dic
then
fmap Just $
Left <$> (dic .: "update")
<|> Right <$> (dic .: "update")
else pure Nothing
pure CabalOptions {..}
else pure (CabalOptions Nothing Nothing)
buildPackageGraph :: PackageGraphOptions Cabal -> IO PackageGraph
buildPackageGraph PackageGraphOptions {customOptions = CabalOptions {..}, ..} = do
let target = fromAbsDir targetPath
mproj = fromAbsFile . (targetPath </>) <$> projectFile
root = maybe (ProjectRootImplicit target) (ProjectRootExplicit target) mproj
when (maybe False (fromLeft True) update) $ do
let targets
| Just (Right idx) <- update = [idx]
| otherwise = []
updateAction (defaultNixStyleFlags ()) targets mempty
ctx0 <- establishProjectBaseContextWithRoot silent mempty root OtherCommand
let pjCfg' =
projectConfig ctx0
& #projectConfigLocalPackages . #packageConfigTests
.~ Flag (tests components)
& #projectConfigLocalPackages . #packageConfigBenchmarks
.~ Flag (benchmarks components)
-- ProjectBaseContext has no Generic instance...
ctx = ctx0 {projectConfig = pjCfg'}
withInstallPlan silent ctx $ \iplan _scfg -> do
let localPkgDic =
Map.mapMaybe
( \case
Configured pkg
| elabLocalToProject pkg -> pure pkg
Installed pkg
| elabLocalToProject pkg -> pure pkg
_ -> Nothing
)
$ Plan.toMap iplan
localUnitIds = Map.keysSet localPkgDic
gr =
getOverlayed $
foldMap
( \pkg ->
let srcPkg = packageName' pkg
deps =
filter (/= srcPkg)
$ mapMaybe
(fmap packageName' . (`Map.lookup` localPkgDic))
$ filter (`Set.member` localUnitIds)
$ depends pkg
in foldMap (Overlayed . G.edge srcPkg) deps
)
localPkgDic
pure gr
packageName' :: Package pkg => pkg -> Guard.PackageName
packageName' = fromString . unPackageName . packageName