<commit_before>{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TypeFamilies #-}

-- | Types used while planning how to build everything in a project.
--
-- Primarily this is the 'ElaboratedInstallPlan'.
--
module Distribution.Client.ProjectPlanning.Types (
    SolverInstallPlan,

    -- * Elaborated install plan types
    ElaboratedInstallPlan,
    ElaboratedConfiguredPackage(..),

    elabDistDirParams,
    elabExeDependencyPaths,
    elabLibDependencies,
    elabOrderLibDependencies,
    elabExeDependencies,
    elabOrderExeDependencies,
    elabSetupDependencies,
    elabPkgConfigDependencies,
    elabInplaceDependencyBuildCacheFiles,
    elabRequiresRegistration,

    elabPlanPackageName,
    elabConfiguredName,
    elabComponentName,

    ElaboratedPackageOrComponent(..),
    ElaboratedComponent(..),
    ElaboratedPackage(..),
    pkgOrderDependencies,
    ElaboratedPlanPackage,
    ElaboratedSharedConfig(..),
    ElaboratedReadyPackage,
    BuildStyle(..),
    CabalFileText,

    -- * Build targets
    ComponentTarget(..),
    showComponentTarget,
    showTestComponentTarget,
    SubComponentTarget(..),

    isTestComponentTarget,

    -- * Setup script
    SetupScriptStyle(..),
  ) where

import           Distribution.Client.TargetSelector
                   ( SubComponentTarget(..) )
import           Distribution.Client.PackageHash

import           Distribution.Client.Types
import qualified Distribution.Client.InstallPlan as InstallPlan
import           Distribution.Client.InstallPlan
                   ( GenericInstallPlan, GenericPlanPackage(..) )
import           Distribution.Client.SolverInstallPlan
                   ( SolverInstallPlan )
import           Distribution.Client.DistDirLayout

import           Distribution.Backpack
import           Distribution.Backpack.ModuleShape

import           Distribution.Verbosity
import           Distribution.Text
import           Distribution.Types.ComponentRequestedSpec
import           Distribution.Package
                   hiding (InstalledPackageId, installedPackageId)
import           Distribution.System
import qualified Distribution.PackageDescription as Cabal
import           Distribution.InstalledPackageInfo (InstalledPackageInfo)
import           Distribution.Simple.Compiler
import qualified Distribution.Simple.BuildTarget as Cabal
import           Distribution.Simple.Program.Db
import           Distribution.ModuleName (ModuleName)
import           Distribution.Simple.LocalBuildInfo (ComponentName(..))
import qualified Distribution.Simple.InstallDirs as InstallDirs
import           Distribution.Simple.InstallDirs (PathTemplate)
import           Distribution.Version

import qualified Distribution.Solver.Types.ComponentDeps as CD
import           Distribution.Solver.Types.ComponentDeps (ComponentDeps)
import           Distribution.Solver.Types.OptionalStanza
import           Distribution.Compat.Graph (IsNode(..))
import           Distribution.Simple.Utils (ordNub)

import           Data.Map (Map)
import           Data.Set (Set)
import qualified Data.ByteString.Lazy as LBS
import           Distribution.Compat.Binary
import           GHC.Generics (Generic)
import qualified Data.Monoid as Mon
import           Data.Typeable
import           Control.Monad



-- | The combination of an elaborated install plan plus a
-- 'ElaboratedSharedConfig' contains all the details necessary to be able
-- to execute the plan without having to make further policy decisions.
--
-- It does not include dynamic elements such as resources (such as http
-- connections).
--
type ElaboratedInstallPlan
   = GenericInstallPlan InstalledPackageInfo
                        ElaboratedConfiguredPackage

type ElaboratedPlanPackage
   = GenericPlanPackage InstalledPackageInfo
                        ElaboratedConfiguredPackage

-- | User-friendly display string for an 'ElaboratedPlanPackage'.
elabPlanPackageName :: Verbosity -> ElaboratedPlanPackage -> String
elabPlanPackageName verbosity (PreExisting ipkg)
    | verbosity <= normal = display (packageName ipkg)
    | otherwise           = display (installedUnitId ipkg)
elabPlanPackageName verbosity (Configured elab)
    = elabConfiguredName verbosity elab
elabPlanPackageName verbosity (Installed elab)
    = elabConfiguredName verbosity elab

--TODO: [code cleanup] decide if we really need this, there's not much in it, and in principle
--      even platform and compiler could be different if we're building things
--      like a server + client with ghc + ghcjs
data ElaboratedSharedConfig
   = ElaboratedSharedConfig {

       pkgConfigPlatform      :: Platform,
       pkgConfigCompiler      :: Compiler, --TODO: [code cleanup] replace with CompilerInfo
       -- | The programs that the compiler configured (e.g. for GHC, the progs
       -- ghc & ghc-pkg). Once constructed, only the 'configuredPrograms' are
       -- used.
       pkgConfigCompilerProgs :: ProgramDb
     }
  deriving (Show, Generic, Typeable)
  --TODO: [code cleanup] no Eq instance

instance Binary ElaboratedSharedConfig

data ElaboratedConfiguredPackage
   = ElaboratedConfiguredPackage {
       -- | The 'UnitId' which uniquely identifies this item in a build plan
       elabUnitId        :: UnitId,

       elabComponentId :: ComponentId,
       elabInstantiatedWith :: Map ModuleName Module,
       elabLinkedInstantiatedWith :: Map ModuleName OpenModule,

       -- | This is true if this is an indefinite package, or this is a
       -- package with no signatures.  (Notably, it's not true for instantiated
       -- packages.)  The motivation for this is if you ask to build
       -- @foo-indef@, this probably means that you want to typecheck
       -- it, NOT that you want to rebuild all of the various
       -- instantiations of it.
       elabIsCanonical :: Bool,

       -- | The 'PackageId' of the originating package
       elabPkgSourceId    :: PackageId,

       -- | Shape of the package/component, for Backpack.
       elabModuleShape    :: ModuleShape,

       -- | A total flag assignment for the package.
       -- TODO: Actually this can be per-component if we drop
       -- all flags that don't affect a component.
       elabFlagAssignment   :: Cabal.FlagAssignment,

       -- | The original default flag assignment, used only for reporting.
       elabFlagDefaults     :: Cabal.FlagAssignment,

       elabPkgDescription :: Cabal.PackageDescription,

       -- | Where the package comes from, e.g. tarball, local dir etc. This
       --   is not the same as where it may be unpacked to for the build.
       elabPkgSourceLocation :: PackageLocation (Maybe FilePath),

       -- | The hash of the source, e.g. the tarball. We don't have this for
       -- local source dir packages.
       elabPkgSourceHash     :: Maybe PackageSourceHash,

       -- | Is this package one of the ones specified by location in the
       -- project file? (As opposed to a dependency, or a named package pulled
       -- in)
       elabLocalToProject         :: Bool,

       -- | Are we going to build and install this package to the store, or are
       -- we going to build it and register it locally.
       elabBuildStyle             :: BuildStyle,

       -- | Another way of phrasing 'pkgStanzasAvailable'.
       elabEnabledSpec      :: ComponentRequestedSpec,

       -- | Which optional stanzas (ie testsuites, benchmarks) can be built.
       -- This means the solver produced a plan that has them available.
       -- This doesn't necessary mean we build them by default.
       elabStanzasAvailable :: Set OptionalStanza,

       -- | Which optional stanzas the user explicitly asked to enable or
       -- to disable. This tells us which ones we build by default, and
       -- helps with error messages when the user asks to build something
       -- they explicitly disabled.
       --
       -- TODO: The 'Bool' here should be refined into an ADT with three
       -- cases: NotRequested, ExplicitlyRequested and
       -- ImplicitlyRequested.  A stanza is explicitly requested if
       -- the user asked, for this *specific* package, that the stanza
       -- be enabled; it's implicitly requested if the user asked for
       -- all global packages to have this stanza enabled.  The
       -- difference between an explicit and implicit request is
       -- error reporting behavior: if a user asks for tests to be
       -- enabled for a specific package that doesn't have any tests,
       -- we should warn them about it, but we shouldn't complain
       -- that a user enabled tests globally, and some local packages
       -- just happen not to have any tests.  (But perhaps we should
       -- warn if ALL local packages don't have any tests.)
       elabStanzasRequested :: Map OptionalStanza Bool,

       elabSetupPackageDBStack    :: PackageDBStack,
       elabBuildPackageDBStack    :: PackageDBStack,
       elabRegisterPackageDBStack :: PackageDBStack,

       elabPkgDescriptionOverride  :: Maybe CabalFileText,

       -- TODO: make per-component variants of these flags
       elabVanillaLib           :: Bool,
       elabSharedLib            :: Bool,
       elabDynExe               :: Bool,
       elabGHCiLib              :: Bool,
       elabProfLib              :: Bool,
       elabProfExe              :: Bool,
       elabProfLibDetail        :: ProfDetailLevel,
       elabProfExeDetail        :: ProfDetailLevel,
       elabCoverage             :: Bool,
       elabOptimization         :: OptimisationLevel,
       elabSplitObjs            :: Bool,
       elabStripLibs            :: Bool,
       elabStripExes            :: Bool,
       elabDebugInfo            :: DebugInfoLevel,

       elabProgramPaths          :: Map String FilePath,
       elabProgramArgs           :: Map String [String],
       elabProgramPathExtra      :: [FilePath],
       elabConfigureScriptArgs   :: [String],
       elabExtraLibDirs          :: [FilePath],
       elabExtraFrameworkDirs    :: [FilePath],
       elabExtraIncludeDirs      :: [FilePath],
       elabProgPrefix            :: Maybe PathTemplate,
       elabProgSuffix            :: Maybe PathTemplate,

       elabInstallDirs           :: InstallDirs.InstallDirs FilePath,

       elabHaddockHoogle         :: Bool,
       elabHaddockHtml           :: Bool,
       elabHaddockHtmlLocation   :: Maybe String,
       elabHaddockForeignLibs    :: Bool,
       elabHaddockExecutables    :: Bool,
       elabHaddockTestSuites     :: Bool,
       elabHaddockBenchmarks     :: Bool,
       elabHaddockInternal       :: Bool,
       elabHaddockCss            :: Maybe FilePath,
       elabHaddockHscolour       :: Bool,
       elabHaddockHscolourCss    :: Maybe FilePath,
       elabHaddockContents       :: Maybe PathTemplate,

       -- Setup.hs related things:

       -- | One of four modes for how we build and interact with the Setup.hs
       -- script, based on whether it's a build-type Custom, with or without
       -- explicit deps and the cabal spec version the .cabal file needs.
       elabSetupScriptStyle      :: SetupScriptStyle,

       -- | The version of the Cabal command line interface that we are using
       -- for this package. This is typically the version of the Cabal lib
       -- that the Setup.hs is built against.
       elabSetupScriptCliVersion :: Version,

       -- Build time related:
       elabBuildTargets          :: [ComponentTarget],
       elabTestTargets           :: [ComponentTarget],
       elabReplTarget            :: Maybe ComponentTarget,
       elabBuildHaddocks         :: Bool,

       --pkgSourceDir ? -- currently passed in later because they can use temp locations
       --pkgBuildDir  ? -- but could in principle still have it here, with optional instr to use temp loc

       -- | Component/package specific information
       elabPkgOrComp :: ElaboratedPackageOrComponent
   }
  deriving (Eq, Show, Generic, Typeable)

-- | The package/component contains/is a library and so must be registered
elabRequiresRegistration :: ElaboratedConfiguredPackage -> Bool
elabRequiresRegistration elab =
    case elabPkgOrComp elab of
        ElabComponent comp ->
            case compComponentName comp of
                Just cn -> is_lib cn && build_target
                _ -> False
        ElabPackage pkg ->
            -- Tricky! Not only do we have to test if the user selected
            -- a library as a build target, we also have to test if
            -- the library was TRANSITIVELY depended upon, since we will
            -- also require a register in this case.
            --
            -- NB: It would have been far nicer to just unconditionally
            -- register in all cases, but some Custom Setups will fall
            -- over if you try to do that, ESPECIALLY if there actually is
            -- a library but they hadn't built it.
            build_target || any (depends_on_lib pkg) (elabBuildTargets elab)
  where
    depends_on_lib pkg (ComponentTarget cn _) =
        not (null (CD.select (== CD.componentNameToComponent cn)
                             (pkgDependsOnSelfLib pkg)))
    build_target =
        if not (null (elabBuildTargets elab))
            then any is_lib_target (elabBuildTargets elab)
            -- Empty build targets mean we build /everything/;
            -- that means we have to look more carefully to see
            -- if there is anything to register
            else Cabal.hasLibs (elabPkgDescription elab)
    -- NB: this means we DO NOT reregister if you just built a
    -- single file
    is_lib_target (ComponentTarget cn WholeComponent) = is_lib cn
    is_lib_target _ = False
    is_lib CLibName = True
    is_lib (CSubLibName _) = True
    is_lib _ = False

instance Package ElaboratedConfiguredPackage where
  packageId = elabPkgSourceId

instance HasConfiguredId ElaboratedConfiguredPackage where
  configuredId elab =
    ConfiguredId (packageId elab) (elabComponentName elab) (elabComponentId elab)

instance HasUnitId ElaboratedConfiguredPackage where
  installedUnitId = elabUnitId

instance IsNode ElaboratedConfiguredPackage where
    type Key ElaboratedConfiguredPackage = UnitId
    nodeKey = elabUnitId
    nodeNeighbors = elabOrderDependencies

instance Binary ElaboratedConfiguredPackage

data ElaboratedPackageOrComponent
    = ElabPackage   ElaboratedPackage
    | ElabComponent ElaboratedComponent
  deriving (Eq, Show, Generic)

instance Binary ElaboratedPackageOrComponent

elabComponentName :: ElaboratedConfiguredPackage -> Maybe ComponentName
elabComponentName elab =
    case elabPkgOrComp elab of
        ElabPackage _      -> Just CLibName -- there could be more, but default this
        ElabComponent comp -> compComponentName comp

-- | A user-friendly descriptor for an 'ElaboratedConfiguredPackage'.
elabConfiguredName :: Verbosity -> ElaboratedConfiguredPackage -> String
elabConfiguredName verbosity elab
    | verbosity <= normal
    = (case elabPkgOrComp elab of
        ElabPackage _ -> ""
        ElabComponent comp ->
            case compComponentName comp of
                Nothing -> "setup from "
                Just CLibName -> ""
                Just cname -> display cname ++ " from ")
      ++ display (packageId elab)
    | otherwise
    = display (elabUnitId elab)

elabDistDirParams :: ElaboratedSharedConfig -> ElaboratedConfiguredPackage -> DistDirParams
elabDistDirParams shared elab = DistDirParams {
        distParamUnitId = installedUnitId elab,
        distParamComponentId = elabComponentId elab,
        distParamPackageId = elabPkgSourceId elab,
        distParamComponentName = case elabPkgOrComp elab of
            ElabComponent comp -> compComponentName comp
            ElabPackage _ -> Nothing,
        distParamCompilerId = compilerId (pkgConfigCompiler shared),
        distParamPlatform = pkgConfigPlatform shared,
        distParamOptimization = elabOptimization elab
    }

-- | The full set of dependencies which dictate what order we
-- need to build things in the install plan: "order dependencies"
-- balls everything together.  This is mostly only useful for
-- ordering; if you are, for example, trying to compute what
-- @--dependency@ flags to pass to a Setup script, you need to
-- use 'elabLibDependencies'.  This method is the same as
-- 'nodeNeighbors'.
--
-- NB: this method DOES include setup deps.
elabOrderDependencies :: ElaboratedConfiguredPackage -> [UnitId]
elabOrderDependencies elab =
    case elabPkgOrComp elab of
        -- Important not to have duplicates: otherwise InstallPlan gets
        -- confused.
        ElabPackage pkg    -> ordNub (CD.flatDeps (pkgOrderDependencies pkg))
        ElabComponent comp -> compOrderDependencies comp

-- | Like 'elabOrderDependencies', but only returns dependencies on
-- libraries.
elabOrderLibDependencies :: ElaboratedConfiguredPackage -> [UnitId]
elabOrderLibDependencies elab =
    case elabPkgOrComp elab of
        ElabPackage _      -> map (newSimpleUnitId . confInstId) (elabLibDependencies elab)
        ElabComponent comp -> compOrderLibDependencies comp

-- | The library dependencies (i.e., the libraries we depend on, NOT
-- the dependencies of the library), NOT including setup dependencies.
-- These are passed to the @Setup@ script via @--dependency@.<commit_msg>Don't filter out setup deps in D.C.ProjectPlanning.Types.elabOrderLibDependencies<commit_after>{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TypeFamilies #-}

-- | Types used while planning how to build everything in a project.
--
-- Primarily this is the 'ElaboratedInstallPlan'.
--
module Distribution.Client.ProjectPlanning.Types (
    SolverInstallPlan,

    -- * Elaborated install plan types
    ElaboratedInstallPlan,
    ElaboratedConfiguredPackage(..),

    elabDistDirParams,
    elabExeDependencyPaths,
    elabLibDependencies,
    elabOrderLibDependencies,
    elabExeDependencies,
    elabOrderExeDependencies,
    elabSetupDependencies,
    elabPkgConfigDependencies,
    elabInplaceDependencyBuildCacheFiles,
    elabRequiresRegistration,

    elabPlanPackageName,
    elabConfiguredName,
    elabComponentName,

    ElaboratedPackageOrComponent(..),
    ElaboratedComponent(..),
    ElaboratedPackage(..),
    pkgOrderDependencies,
    ElaboratedPlanPackage,
    ElaboratedSharedConfig(..),
    ElaboratedReadyPackage,
    BuildStyle(..),
    CabalFileText,

    -- * Build targets
    ComponentTarget(..),
    showComponentTarget,
    showTestComponentTarget,
    SubComponentTarget(..),

    isTestComponentTarget,

    -- * Setup script
    SetupScriptStyle(..),
  ) where

import           Distribution.Client.TargetSelector
                   ( SubComponentTarget(..) )
import           Distribution.Client.PackageHash

import           Distribution.Client.Types
import qualified Distribution.Client.InstallPlan as InstallPlan
import           Distribution.Client.InstallPlan
                   ( GenericInstallPlan, GenericPlanPackage(..) )
import           Distribution.Client.SolverInstallPlan
                   ( SolverInstallPlan )
import           Distribution.Client.DistDirLayout

import           Distribution.Backpack
import           Distribution.Backpack.ModuleShape

import           Distribution.Verbosity
import           Distribution.Text
import           Distribution.Types.ComponentRequestedSpec
import           Distribution.Package
                   hiding (InstalledPackageId, installedPackageId)
import           Distribution.System
import qualified Distribution.PackageDescription as Cabal
import           Distribution.InstalledPackageInfo (InstalledPackageInfo)
import           Distribution.Simple.Compiler
import qualified Distribution.Simple.BuildTarget as Cabal
import           Distribution.Simple.Program.Db
import           Distribution.ModuleName (ModuleName)
import           Distribution.Simple.LocalBuildInfo (ComponentName(..))
import qualified Distribution.Simple.InstallDirs as InstallDirs
import           Distribution.Simple.InstallDirs (PathTemplate)
import           Distribution.Version

import qualified Distribution.Solver.Types.ComponentDeps as CD
import           Distribution.Solver.Types.ComponentDeps (ComponentDeps)
import           Distribution.Solver.Types.OptionalStanza
import           Distribution.Compat.Graph (IsNode(..))
import           Distribution.Simple.Utils (ordNub)

import           Data.Map (Map)
import           Data.Set (Set)
import qualified Data.ByteString.Lazy as LBS
import           Distribution.Compat.Binary
import           GHC.Generics (Generic)
import qualified Data.Monoid as Mon
import           Data.Typeable
import           Control.Monad



-- | The combination of an elaborated install plan plus a
-- 'ElaboratedSharedConfig' contains all the details necessary to be able
-- to execute the plan without having to make further policy decisions.
--
-- It does not include dynamic elements such as resources (such as http
-- connections).
--
type ElaboratedInstallPlan
   = GenericInstallPlan InstalledPackageInfo
                        ElaboratedConfiguredPackage

type ElaboratedPlanPackage
   = GenericPlanPackage InstalledPackageInfo
                        ElaboratedConfiguredPackage

-- | User-friendly display string for an 'ElaboratedPlanPackage'.
elabPlanPackageName :: Verbosity -> ElaboratedPlanPackage -> String
elabPlanPackageName verbosity (PreExisting ipkg)
    | verbosity <= normal = display (packageName ipkg)
    | otherwise           = display (installedUnitId ipkg)
elabPlanPackageName verbosity (Configured elab)
    = elabConfiguredName verbosity elab
elabPlanPackageName verbosity (Installed elab)
    = elabConfiguredName verbosity elab

--TODO: [code cleanup] decide if we really need this, there's not much in it, and in principle
--      even platform and compiler could be different if we're building things
--      like a server + client with ghc + ghcjs
data ElaboratedSharedConfig
   = ElaboratedSharedConfig {

       pkgConfigPlatform      :: Platform,
       pkgConfigCompiler      :: Compiler, --TODO: [code cleanup] replace with CompilerInfo
       -- | The programs that the compiler configured (e.g. for GHC, the progs
       -- ghc & ghc-pkg). Once constructed, only the 'configuredPrograms' are
       -- used.
       pkgConfigCompilerProgs :: ProgramDb
     }
  deriving (Show, Generic, Typeable)
  --TODO: [code cleanup] no Eq instance

instance Binary ElaboratedSharedConfig

data ElaboratedConfiguredPackage
   = ElaboratedConfiguredPackage {
       -- | The 'UnitId' which uniquely identifies this item in a build plan
       elabUnitId        :: UnitId,

       elabComponentId :: ComponentId,
       elabInstantiatedWith :: Map ModuleName Module,
       elabLinkedInstantiatedWith :: Map ModuleName OpenModule,

       -- | This is true if this is an indefinite package, or this is a
       -- package with no signatures.  (Notably, it's not true for instantiated
       -- packages.)  The motivation for this is if you ask to build
       -- @foo-indef@, this probably means that you want to typecheck
       -- it, NOT that you want to rebuild all of the various
       -- instantiations of it.
       elabIsCanonical :: Bool,

       -- | The 'PackageId' of the originating package
       elabPkgSourceId    :: PackageId,

       -- | Shape of the package/component, for Backpack.
       elabModuleShape    :: ModuleShape,

       -- | A total flag assignment for the package.
       -- TODO: Actually this can be per-component if we drop
       -- all flags that don't affect a component.
       elabFlagAssignment   :: Cabal.FlagAssignment,

       -- | The original default flag assignment, used only for reporting.
       elabFlagDefaults     :: Cabal.FlagAssignment,

       elabPkgDescription :: Cabal.PackageDescription,

       -- | Where the package comes from, e.g. tarball, local dir etc. This
       --   is not the same as where it may be unpacked to for the build.
       elabPkgSourceLocation :: PackageLocation (Maybe FilePath),

       -- | The hash of the source, e.g. the tarball. We don't have this for
       -- local source dir packages.
       elabPkgSourceHash     :: Maybe PackageSourceHash,

       -- | Is this package one of the ones specified by location in the
       -- project file? (As opposed to a dependency, or a named package pulled
       -- in)
       elabLocalToProject         :: Bool,

       -- | Are we going to build and install this package to the store, or are
       -- we going to build it and register it locally.
       elabBuildStyle             :: BuildStyle,

       -- | Another way of phrasing 'pkgStanzasAvailable'.
       elabEnabledSpec      :: ComponentRequestedSpec,

       -- | Which optional stanzas (ie testsuites, benchmarks) can be built.
       -- This means the solver produced a plan that has them available.
       -- This doesn't necessary mean we build them by default.
       elabStanzasAvailable :: Set OptionalStanza,

       -- | Which optional stanzas the user explicitly asked to enable or
       -- to disable. This tells us which ones we build by default, and
       -- helps with error messages when the user asks to build something
       -- they explicitly disabled.
       --
       -- TODO: The 'Bool' here should be refined into an ADT with three
       -- cases: NotRequested, ExplicitlyRequested and
       -- ImplicitlyRequested.  A stanza is explicitly requested if
       -- the user asked, for this *specific* package, that the stanza
       -- be enabled; it's implicitly requested if the user asked for
       -- all global packages to have this stanza enabled.  The
       -- difference between an explicit and implicit request is
       -- error reporting behavior: if a user asks for tests to be
       -- enabled for a specific package that doesn't have any tests,
       -- we should warn them about it, but we shouldn't complain
       -- that a user enabled tests globally, and some local packages
       -- just happen not to have any tests.  (But perhaps we should
       -- warn if ALL local packages don't have any tests.)
       elabStanzasRequested :: Map OptionalStanza Bool,

       elabSetupPackageDBStack    :: PackageDBStack,
       elabBuildPackageDBStack    :: PackageDBStack,
       elabRegisterPackageDBStack :: PackageDBStack,

       elabPkgDescriptionOverride  :: Maybe CabalFileText,

       -- TODO: make per-component variants of these flags
       elabVanillaLib           :: Bool,
       elabSharedLib            :: Bool,
       elabDynExe               :: Bool,
       elabGHCiLib              :: Bool,
       elabProfLib              :: Bool,
       elabProfExe              :: Bool,
       elabProfLibDetail        :: ProfDetailLevel,
       elabProfExeDetail        :: ProfDetailLevel,
       elabCoverage             :: Bool,
       elabOptimization         :: OptimisationLevel,
       elabSplitObjs            :: Bool,
       elabStripLibs            :: Bool,
       elabStripExes            :: Bool,
       elabDebugInfo            :: DebugInfoLevel,

       elabProgramPaths          :: Map String FilePath,
       elabProgramArgs           :: Map String [String],
       elabProgramPathExtra      :: [FilePath],
       elabConfigureScriptArgs   :: [String],
       elabExtraLibDirs          :: [FilePath],
       elabExtraFrameworkDirs    :: [FilePath],
       elabExtraIncludeDirs      :: [FilePath],
       elabProgPrefix            :: Maybe PathTemplate,
       elabProgSuffix            :: Maybe PathTemplate,

       elabInstallDirs           :: InstallDirs.InstallDirs FilePath,

       elabHaddockHoogle         :: Bool,
       elabHaddockHtml           :: Bool,
       elabHaddockHtmlLocation   :: Maybe String,
       elabHaddockForeignLibs    :: Bool,
       elabHaddockExecutables    :: Bool,
       elabHaddockTestSuites     :: Bool,
       elabHaddockBenchmarks     :: Bool,
       elabHaddockInternal       :: Bool,
       elabHaddockCss            :: Maybe FilePath,
       elabHaddockHscolour       :: Bool,
       elabHaddockHscolourCss    :: Maybe FilePath,
       elabHaddockContents       :: Maybe PathTemplate,

       -- Setup.hs related things:

       -- | One of four modes for how we build and interact with the Setup.hs
       -- script, based on whether it's a build-type Custom, with or without
       -- explicit deps and the cabal spec version the .cabal file needs.
       elabSetupScriptStyle      :: SetupScriptStyle,

       -- | The version of the Cabal command line interface that we are using
       -- for this package. This is typically the version of the Cabal lib
       -- that the Setup.hs is built against.
       elabSetupScriptCliVersion :: Version,

       -- Build time related:
       elabBuildTargets          :: [ComponentTarget],
       elabTestTargets           :: [ComponentTarget],
       elabReplTarget            :: Maybe ComponentTarget,
       elabBuildHaddocks         :: Bool,

       --pkgSourceDir ? -- currently passed in later because they can use temp locations
       --pkgBuildDir  ? -- but could in principle still have it here, with optional instr to use temp loc

       -- | Component/package specific information
       elabPkgOrComp :: ElaboratedPackageOrComponent
   }
  deriving (Eq, Show, Generic, Typeable)

-- | The package/component contains/is a library and so must be registered
elabRequiresRegistration :: ElaboratedConfiguredPackage -> Bool
elabRequiresRegistration elab =
    case elabPkgOrComp elab of
        ElabComponent comp ->
            case compComponentName comp of
                Just cn -> is_lib cn && build_target
                _ -> False
        ElabPackage pkg ->
            -- Tricky! Not only do we have to test if the user selected
            -- a library as a build target, we also have to test if
            -- the library was TRANSITIVELY depended upon, since we will
            -- also require a register in this case.
            --
            -- NB: It would have been far nicer to just unconditionally
            -- register in all cases, but some Custom Setups will fall
            -- over if you try to do that, ESPECIALLY if there actually is
            -- a library but they hadn't built it.
            build_target || any (depends_on_lib pkg) (elabBuildTargets elab)
  where
    depends_on_lib pkg (ComponentTarget cn _) =
        not (null (CD.select (== CD.componentNameToComponent cn)
                             (pkgDependsOnSelfLib pkg)))
    build_target =
        if not (null (elabBuildTargets elab))
            then any is_lib_target (elabBuildTargets elab)
            -- Empty build targets mean we build /everything/;
            -- that means we have to look more carefully to see
            -- if there is anything to register
            else Cabal.hasLibs (elabPkgDescription elab)
    -- NB: this means we DO NOT reregister if you just built a
    -- single file
    is_lib_target (ComponentTarget cn WholeComponent) = is_lib cn
    is_lib_target _ = False
    is_lib CLibName = True
    is_lib (CSubLibName _) = True
    is_lib _ = False

instance Package ElaboratedConfiguredPackage where
  packageId = elabPkgSourceId

instance HasConfiguredId ElaboratedConfiguredPackage where
  configuredId elab =
    ConfiguredId (packageId elab) (elabComponentName elab) (elabComponentId elab)

instance HasUnitId ElaboratedConfiguredPackage where
  installedUnitId = elabUnitId

instance IsNode ElaboratedConfiguredPackage where
    type Key ElaboratedConfiguredPackage = UnitId
    nodeKey = elabUnitId
    nodeNeighbors = elabOrderDependencies

instance Binary ElaboratedConfiguredPackage

data ElaboratedPackageOrComponent
    = ElabPackage   ElaboratedPackage
    | ElabComponent ElaboratedComponent
  deriving (Eq, Show, Generic)

instance Binary ElaboratedPackageOrComponent

elabComponentName :: ElaboratedConfiguredPackage -> Maybe ComponentName
elabComponentName elab =
    case elabPkgOrComp elab of
        ElabPackage _      -> Just CLibName -- there could be more, but default this
        ElabComponent comp -> compComponentName comp

-- | A user-friendly descriptor for an 'ElaboratedConfiguredPackage'.
elabConfiguredName :: Verbosity -> ElaboratedConfiguredPackage -> String
elabConfiguredName verbosity elab
    | verbosity <= normal
    = (case elabPkgOrComp elab of
        ElabPackage _ -> ""
        ElabComponent comp ->
            case compComponentName comp of
                Nothing -> "setup from "
                Just CLibName -> ""
                Just cname -> display cname ++ " from ")
      ++ display (packageId elab)
    | otherwise
    = display (elabUnitId elab)

elabDistDirParams :: ElaboratedSharedConfig -> ElaboratedConfiguredPackage -> DistDirParams
elabDistDirParams shared elab = DistDirParams {
        distParamUnitId = installedUnitId elab,
        distParamComponentId = elabComponentId elab,
        distParamPackageId = elabPkgSourceId elab,
        distParamComponentName = case elabPkgOrComp elab of
            ElabComponent comp -> compComponentName comp
            ElabPackage _ -> Nothing,
        distParamCompilerId = compilerId (pkgConfigCompiler shared),
        distParamPlatform = pkgConfigPlatform shared,
        distParamOptimization = elabOptimization elab
    }

-- | The full set of dependencies which dictate what order we
-- need to build things in the install plan: "order dependencies"
-- balls everything together.  This is mostly only useful for
-- ordering; if you are, for example, trying to compute what
-- @--dependency@ flags to pass to a Setup script, you need to
-- use 'elabLibDependencies'.  This method is the same as
-- 'nodeNeighbors'.
--
-- NB: this method DOES include setup deps.
elabOrderDependencies :: ElaboratedConfiguredPackage -> [UnitId]
elabOrderDependencies elab =
    case elabPkgOrComp elab of
        -- Important not to have duplicates: otherwise InstallPlan gets
        -- confused.
        ElabPackage pkg    -> ordNub (CD.flatDeps (pkgOrderDependencies pkg))
        ElabComponent comp -> compOrderDependencies comp

-- | Like 'elabOrderDependencies', but only returns dependencies on
-- libraries.
elabOrderLibDependencies :: ElaboratedConfiguredPackage -> [UnitId]
elabOrderLibDependencies elab =
    case elabPkgOrComp elab of
        ElabPackage pkg    -> map (newSimpleUnitId . confInstId) $
                              ordNub $ CD.flatDeps (pkgLibDependencies pkg)
        ElabComponent comp -> compOrderLibDependencies comp

-- | The library dependencies (i.e., the libraries we depend on, NOT
-- the dependencies of the library), NOT including setup dependencies.
-- These are passed to the @Setup@ script via @--dependency@.
{'fraction_non_alphanumeric': 0.06388800875651698, 'fraction_numerical': 0.0, 'mean_word_length': 2.538320423970648, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3519631', 'n_tokens_mistral': 9121, 'n_tokens_neox': 8810, 'n_words': 3036}