{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module Stack.Build.Haddock
( generateDepsHaddockIndex
, generateLocalHaddockIndex
, generateSnapHaddockIndex
, openHaddocksInBrowser
, shouldHaddockDeps
, shouldHaddockPackage
, generateLocalHaddockForHackageArchives
) where
import qualified Codec.Archive.Tar as Tar
import qualified Codec.Compression.GZip as GZip
import qualified Data.Foldable as F
import qualified Data.HashSet as HS
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import Distribution.Text ( display )
import Path
( (</>), addExtension, dirname, fileExtension, filename
, fromAbsDir, fromAbsFile, fromRelDir, parent, parseRelDir
, parseRelFile
)
import Path.Extra
( parseCollapsedAbsFile, toFilePathNoTrailingSep
, tryGetModificationTime
)
import Path.IO
( copyDirRecur, copyDirRecur', doesDirExist, doesFileExist
, ensureDir, ignoringAbsence, listDir, removeDirRecur
)
import qualified RIO.ByteString.Lazy as BL
import RIO.List ( intercalate, intersperse )
import RIO.Process ( HasProcessContext, withWorkingDir )
import Stack.Constants
( docDirSuffix, htmlDirSuffix, relDirAll, relFileIndexHtml )
import Stack.Constants.Config ( distDirFromDir )
import Stack.Prelude hiding ( Display (..) )
import Stack.Types.Build.Exception ( BuildException (..) )
import Stack.Types.CompilerPaths
( CompilerPaths (..), HasCompiler (..) )
import Stack.Types.ConfigureOpts ( BaseConfigOpts (..) )
import Stack.Types.BuildOpts ( BuildOpts (..), HaddockOpts (..) )
import Stack.Types.BuildOptsCLI ( BuildOptsCLI (..), BuildSubset (BSOnlyDependencies, BSOnlySnapshot) )
import Stack.Types.DumpPackage ( DumpPackage (..) )
import Stack.Types.EnvConfig ( EnvConfig (..), HasEnvConfig (..) )
import Stack.Types.GhcPkgId ( GhcPkgId )
import Stack.Types.InterfaceOpt ( InterfaceOpt (..) )
import Stack.Types.Package
( InstallLocation (..), LocalPackage (..), Package (..) )
import qualified System.FilePath as FP
import Web.Browser ( openBrowser )
import RIO.FilePath (dropTrailingPathSeparator)
openHaddocksInBrowser ::
HasTerm env
=> BaseConfigOpts
-> Map PackageName (PackageIdentifier, InstallLocation)
-> Set PackageName
-> RIO env ()
openHaddocksInBrowser :: forall env.
HasTerm env =>
BaseConfigOpts
-> Map PackageName (PackageIdentifier, InstallLocation)
-> Set PackageName
-> RIO env ()
openHaddocksInBrowser BaseConfigOpts
bco Map PackageName (PackageIdentifier, InstallLocation)
pkgLocations Set PackageName
buildTargets = do
let cliTargets :: [Text]
cliTargets = BaseConfigOpts
bco.buildOptsCLI.targetsCLI
getDocIndex :: RIO env (Path Abs File)
getDocIndex = do
let localDocs :: Path Abs File
localDocs = Path Abs Dir -> Path Abs File
haddockIndexFile (BaseConfigOpts -> Path Abs Dir
localDepsDocDir BaseConfigOpts
bco)
localExists <- Path Abs File -> RIO env Bool
forall (m :: * -> *) b. MonadIO m => Path b File -> m Bool
doesFileExist Path Abs File
localDocs
if localExists
then pure localDocs
else do
let snapDocs = Path Abs Dir -> Path Abs File
haddockIndexFile (BaseConfigOpts -> Path Abs Dir
snapDocDir BaseConfigOpts
bco)
snapExists <- doesFileExist snapDocs
if snapExists
then pure snapDocs
else throwIO HaddockIndexNotFound
docFile <-
case ([Text]
cliTargets, (PackageName -> Maybe (PackageIdentifier, InstallLocation))
-> [PackageName] -> [Maybe (PackageIdentifier, InstallLocation)]
forall a b. (a -> b) -> [a] -> [b]
map (PackageName
-> Map PackageName (PackageIdentifier, InstallLocation)
-> Maybe (PackageIdentifier, InstallLocation)
forall k a. Ord k => k -> Map k a -> Maybe a
`Map.lookup` Map PackageName (PackageIdentifier, InstallLocation)
pkgLocations) (Set PackageName -> [PackageName]
forall a. Set a -> [a]
Set.toList Set PackageName
buildTargets)) of
([Text
_], [Just (PackageIdentifier
pkgId, InstallLocation
iloc)]) -> do
pkgRelDir <- (FilePath -> RIO env (Path Rel Dir)
forall (m :: * -> *). MonadThrow m => FilePath -> m (Path Rel Dir)
parseRelDir (FilePath -> RIO env (Path Rel Dir))
-> (PackageIdentifier -> FilePath)
-> PackageIdentifier
-> RIO env (Path Rel Dir)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageIdentifier -> FilePath
packageIdentifierString) PackageIdentifier
pkgId
let docLocation =
case InstallLocation
iloc of
InstallLocation
Snap -> BaseConfigOpts -> Path Abs Dir
snapDocDir BaseConfigOpts
bco
InstallLocation
Local -> BaseConfigOpts -> Path Abs Dir
localDocDir BaseConfigOpts
bco
let docFile = Path Abs Dir -> Path Abs File
haddockIndexFile (Path Abs Dir
docLocation Path Abs Dir -> Path Rel Dir -> Path Abs Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
pkgRelDir)
exists <- doesFileExist docFile
if exists
then pure docFile
else do
prettyWarnL
[ flow "Expected to find documentation at"
, pretty docFile <> ","
, flow "but that file is missing. Opening doc index instead."
]
getDocIndex
([Text], [Maybe (PackageIdentifier, InstallLocation)])
_ -> RIO env (Path Abs File)
getDocIndex
prettyInfo $ "Opening" <+> pretty docFile <+> "in the browser."
void $ liftIO $ openBrowser (toFilePath docFile)
shouldHaddockPackage ::
BuildOpts
-> Set PackageName
-> PackageName
-> Bool
shouldHaddockPackage :: BuildOpts -> Set PackageName -> PackageName -> Bool
shouldHaddockPackage BuildOpts
bopts Set PackageName
wanted PackageName
name =
if PackageName -> Set PackageName -> Bool
forall a. Ord a => a -> Set a -> Bool
Set.member PackageName
name Set PackageName
wanted
then BuildOpts
bopts.buildHaddocks
else BuildOpts -> Bool
shouldHaddockDeps BuildOpts
bopts
shouldHaddockDeps :: BuildOpts -> Bool
shouldHaddockDeps :: BuildOpts -> Bool
shouldHaddockDeps BuildOpts
bopts = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe BuildOpts
bopts.buildHaddocks BuildOpts
bopts.haddockDeps
generateLocalHaddockIndex ::
(HasCompiler env, HasProcessContext env, HasTerm env)
=> BaseConfigOpts
-> Map GhcPkgId DumpPackage
-> [LocalPackage]
-> RIO env ()
generateLocalHaddockIndex :: forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
BaseConfigOpts
-> Map GhcPkgId DumpPackage -> [LocalPackage] -> RIO env ()
generateLocalHaddockIndex BaseConfigOpts
bco Map GhcPkgId DumpPackage
localDumpPkgs [LocalPackage]
locals = do
let dumpPackages :: [DumpPackage]
dumpPackages =
(LocalPackage -> Maybe DumpPackage)
-> [LocalPackage] -> [DumpPackage]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe
( \LocalPackage {package :: LocalPackage -> Package
package = Package {PackageName
name :: PackageName
name :: Package -> PackageName
name, Version
version :: Version
version :: Package -> Version
version}} ->
(DumpPackage -> Bool)
-> Map GhcPkgId DumpPackage -> Maybe DumpPackage
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
F.find
( \DumpPackage
dp -> DumpPackage
dp.packageIdent PackageIdentifier -> PackageIdentifier -> Bool
forall a. Eq a => a -> a -> Bool
==
PackageName -> Version -> PackageIdentifier
PackageIdentifier PackageName
name Version
version
)
Map GhcPkgId DumpPackage
localDumpPkgs
)
[LocalPackage]
locals
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
generateHaddockIndex
Text
"project packages"
BaseConfigOpts
bco
[DumpPackage]
dumpPackages
FilePath
"."
(BaseConfigOpts -> Path Abs Dir
localDocDir BaseConfigOpts
bco)
generateDepsHaddockIndex ::
(HasCompiler env, HasProcessContext env, HasTerm env)
=> BaseConfigOpts
-> Map GhcPkgId DumpPackage
-> Map GhcPkgId DumpPackage
-> Map GhcPkgId DumpPackage
-> [LocalPackage]
-> RIO env ()
generateDepsHaddockIndex :: forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
BaseConfigOpts
-> Map GhcPkgId DumpPackage
-> Map GhcPkgId DumpPackage
-> Map GhcPkgId DumpPackage
-> [LocalPackage]
-> RIO env ()
generateDepsHaddockIndex BaseConfigOpts
bco Map GhcPkgId DumpPackage
globalDumpPkgs Map GhcPkgId DumpPackage
snapshotDumpPkgs Map GhcPkgId DumpPackage
localDumpPkgs [LocalPackage]
locals = do
let deps :: [DumpPackage]
deps = ( (GhcPkgId -> Maybe DumpPackage) -> [GhcPkgId] -> [DumpPackage]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe
(GhcPkgId -> [Map GhcPkgId DumpPackage] -> Maybe DumpPackage
`lookupDumpPackage` [Map GhcPkgId DumpPackage]
allDumpPkgs)
([GhcPkgId] -> [DumpPackage])
-> ([LocalPackage] -> [GhcPkgId])
-> [LocalPackage]
-> [DumpPackage]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [GhcPkgId] -> [GhcPkgId]
forall a. Ord a => [a] -> [a]
nubOrd
([GhcPkgId] -> [GhcPkgId])
-> ([LocalPackage] -> [GhcPkgId]) -> [LocalPackage] -> [GhcPkgId]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [GhcPkgId] -> [GhcPkgId]
findTransitiveDepends
([GhcPkgId] -> [GhcPkgId])
-> ([LocalPackage] -> [GhcPkgId]) -> [LocalPackage] -> [GhcPkgId]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (LocalPackage -> Maybe GhcPkgId) -> [LocalPackage] -> [GhcPkgId]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe LocalPackage -> Maybe GhcPkgId
getGhcPkgId
) [LocalPackage]
locals
depDocDir :: Path Abs Dir
depDocDir = BaseConfigOpts -> Path Abs Dir
localDepsDocDir BaseConfigOpts
bco
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
generateHaddockIndex
Text
"project packages and dependencies"
BaseConfigOpts
bco
[DumpPackage]
deps
FilePath
".."
Path Abs Dir
depDocDir
where
getGhcPkgId :: LocalPackage -> Maybe GhcPkgId
getGhcPkgId :: LocalPackage -> Maybe GhcPkgId
getGhcPkgId LocalPackage {package :: LocalPackage -> Package
package = Package {PackageName
name :: Package -> PackageName
name :: PackageName
name, Version
version :: Package -> Version
version :: Version
version}} =
let pkgId :: PackageIdentifier
pkgId = PackageName -> Version -> PackageIdentifier
PackageIdentifier PackageName
name Version
version
mdpPkg :: Maybe DumpPackage
mdpPkg = (DumpPackage -> Bool)
-> Map GhcPkgId DumpPackage -> Maybe DumpPackage
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
F.find (\DumpPackage
dp -> DumpPackage
dp.packageIdent PackageIdentifier -> PackageIdentifier -> Bool
forall a. Eq a => a -> a -> Bool
== PackageIdentifier
pkgId) Map GhcPkgId DumpPackage
localDumpPkgs
in (DumpPackage -> GhcPkgId) -> Maybe DumpPackage -> Maybe GhcPkgId
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (.ghcPkgId) Maybe DumpPackage
mdpPkg
findTransitiveDepends :: [GhcPkgId] -> [GhcPkgId]
findTransitiveDepends :: [GhcPkgId] -> [GhcPkgId]
findTransitiveDepends = (HashSet GhcPkgId -> HashSet GhcPkgId -> [GhcPkgId]
`go` HashSet GhcPkgId
forall a. HashSet a
HS.empty) (HashSet GhcPkgId -> [GhcPkgId])
-> ([GhcPkgId] -> HashSet GhcPkgId) -> [GhcPkgId] -> [GhcPkgId]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [GhcPkgId] -> HashSet GhcPkgId
forall a. (Eq a, Hashable a) => [a] -> HashSet a
HS.fromList
where
go :: HashSet GhcPkgId -> HashSet GhcPkgId -> [GhcPkgId]
go HashSet GhcPkgId
todo HashSet GhcPkgId
checked =
case HashSet GhcPkgId -> [GhcPkgId]
forall a. HashSet a -> [a]
HS.toList HashSet GhcPkgId
todo of
[] -> HashSet GhcPkgId -> [GhcPkgId]
forall a. HashSet a -> [a]
HS.toList HashSet GhcPkgId
checked
(GhcPkgId
ghcPkgId:[GhcPkgId]
_) ->
let deps :: HashSet GhcPkgId
deps = case GhcPkgId -> [Map GhcPkgId DumpPackage] -> Maybe DumpPackage
lookupDumpPackage GhcPkgId
ghcPkgId [Map GhcPkgId DumpPackage]
allDumpPkgs of
Maybe DumpPackage
Nothing -> HashSet GhcPkgId
forall a. HashSet a
HS.empty
Just DumpPackage
pkgDP -> [GhcPkgId] -> HashSet GhcPkgId
forall a. (Eq a, Hashable a) => [a] -> HashSet a
HS.fromList DumpPackage
pkgDP.depends
deps' :: HashSet GhcPkgId
deps' = HashSet GhcPkgId
deps HashSet GhcPkgId -> HashSet GhcPkgId -> HashSet GhcPkgId
forall a. (Eq a, Hashable a) => HashSet a -> HashSet a -> HashSet a
`HS.difference` HashSet GhcPkgId
checked
todo' :: HashSet GhcPkgId
todo' = GhcPkgId -> HashSet GhcPkgId -> HashSet GhcPkgId
forall a. (Eq a, Hashable a) => a -> HashSet a -> HashSet a
HS.delete GhcPkgId
ghcPkgId (HashSet GhcPkgId
deps' HashSet GhcPkgId -> HashSet GhcPkgId -> HashSet GhcPkgId
forall a. Eq a => HashSet a -> HashSet a -> HashSet a
`HS.union` HashSet GhcPkgId
todo)
checked' :: HashSet GhcPkgId
checked' = GhcPkgId -> HashSet GhcPkgId -> HashSet GhcPkgId
forall a. (Eq a, Hashable a) => a -> HashSet a -> HashSet a
HS.insert GhcPkgId
ghcPkgId HashSet GhcPkgId
checked
in HashSet GhcPkgId -> HashSet GhcPkgId -> [GhcPkgId]
go HashSet GhcPkgId
todo' HashSet GhcPkgId
checked'
allDumpPkgs :: [Map GhcPkgId DumpPackage]
allDumpPkgs = [Map GhcPkgId DumpPackage
localDumpPkgs, Map GhcPkgId DumpPackage
snapshotDumpPkgs, Map GhcPkgId DumpPackage
globalDumpPkgs]
generateSnapHaddockIndex ::
(HasCompiler env, HasProcessContext env, HasTerm env)
=> BaseConfigOpts
-> Map GhcPkgId DumpPackage
-> Map GhcPkgId DumpPackage
-> RIO env ()
generateSnapHaddockIndex :: forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
BaseConfigOpts
-> Map GhcPkgId DumpPackage
-> Map GhcPkgId DumpPackage
-> RIO env ()
generateSnapHaddockIndex BaseConfigOpts
bco Map GhcPkgId DumpPackage
globalDumpPkgs Map GhcPkgId DumpPackage
snapshotDumpPkgs =
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
generateHaddockIndex
Text
"snapshot packages"
BaseConfigOpts
bco
(Map GhcPkgId DumpPackage -> [DumpPackage]
forall k a. Map k a -> [a]
Map.elems Map GhcPkgId DumpPackage
snapshotDumpPkgs [DumpPackage] -> [DumpPackage] -> [DumpPackage]
forall a. [a] -> [a] -> [a]
++ Map GhcPkgId DumpPackage -> [DumpPackage]
forall k a. Map k a -> [a]
Map.elems Map GhcPkgId DumpPackage
globalDumpPkgs)
FilePath
"."
(BaseConfigOpts -> Path Abs Dir
snapDocDir BaseConfigOpts
bco)
generateHaddockIndex ::
(HasCompiler env, HasProcessContext env, HasTerm env)
=> Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
generateHaddockIndex :: forall env.
(HasCompiler env, HasProcessContext env, HasTerm env) =>
Text
-> BaseConfigOpts
-> [DumpPackage]
-> FilePath
-> Path Abs Dir
-> RIO env ()
generateHaddockIndex Text
descr BaseConfigOpts
bco [DumpPackage]
dumpPackages FilePath
docRelFP Path Abs Dir
destDir = do
Path Abs Dir -> RIO env ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir Path Abs Dir
destDir
interfaceOpts <-
(IO [InterfaceOpt] -> RIO env [InterfaceOpt]
forall a. IO a -> RIO env a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [InterfaceOpt] -> RIO env [InterfaceOpt])
-> ([DumpPackage] -> IO [InterfaceOpt])
-> [DumpPackage]
-> RIO env [InterfaceOpt]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([InterfaceOpt] -> [InterfaceOpt])
-> IO [InterfaceOpt] -> IO [InterfaceOpt]
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [InterfaceOpt] -> [InterfaceOpt]
forall a. Ord a => [a] -> [a]
nubOrd (IO [InterfaceOpt] -> IO [InterfaceOpt])
-> ([DumpPackage] -> IO [InterfaceOpt])
-> [DumpPackage]
-> IO [InterfaceOpt]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (DumpPackage -> IO (Maybe InterfaceOpt))
-> [DumpPackage] -> IO [InterfaceOpt]
forall (m :: * -> *) a b.
Monad m =>
(a -> m (Maybe b)) -> [a] -> m [b]
mapMaybeM DumpPackage -> IO (Maybe InterfaceOpt)
toInterfaceOpt) [DumpPackage]
dumpPackages
unless (null interfaceOpts) $ do
let destIndexFile = Path Abs Dir -> Path Abs File
haddockIndexFile Path Abs Dir
destDir
prettyDescr = Style -> StyleDoc -> StyleDoc
style Style
Current (FilePath -> StyleDoc
forall a. IsString a => FilePath -> a
fromString (FilePath -> StyleDoc) -> FilePath -> StyleDoc
forall a b. (a -> b) -> a -> b
$ Text -> FilePath
T.unpack Text
descr)
needUpdate <- liftIO (tryGetModificationTime destIndexFile) <&> \case
Left ()
_ -> Bool
True
Right UTCTime
indexModTime ->
[Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
or [ UTCTime
mt UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
> UTCTime
indexModTime
| UTCTime
mt <- (InterfaceOpt -> UTCTime) -> [InterfaceOpt] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
map (.srcInterfaceFileModTime) [InterfaceOpt]
interfaceOpts
]
if needUpdate
then do
prettyInfo $
fillSep
[ flow "Updating Haddock index for"
, prettyDescr
, "in:"
]
<> line
<> pretty destIndexFile
liftIO (mapM_ copyPkgDocs interfaceOpts)
haddockExeName <- view $ compilerPathsL . to (toFilePath . (.haddock))
withWorkingDir (toFilePath destDir) $ readProcessNull
haddockExeName
( map
(("--optghc=-package-db=" ++ ) . toFilePathNoTrailingSep)
[bco.snapDB, bco.localDB]
++ bco.buildOpts.haddockOpts.additionalArgs
++ ["--gen-contents", "--gen-index"]
++ [x | xs <- map (.readInterfaceArgs) interfaceOpts, x <- xs]
)
else
prettyInfo $
fillSep
[ flow "Haddock index for"
, prettyDescr
, flow "already up to date at:"
]
<> line
<> pretty destIndexFile
where
toInterfaceOpt ::
DumpPackage
-> IO (Maybe InterfaceOpt)
toInterfaceOpt :: DumpPackage -> IO (Maybe InterfaceOpt)
toInterfaceOpt DumpPackage
dp =
case DumpPackage
dp.haddockInterfaces of
[] -> Maybe InterfaceOpt -> IO (Maybe InterfaceOpt)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe InterfaceOpt
forall a. Maybe a
Nothing
FilePath
srcInterfaceFP:[FilePath]
_ -> do
srcInterfaceFile <- FilePath -> IO (Path Abs File)
forall (m :: * -> *). MonadThrow m => FilePath -> m (Path Abs File)
parseCollapsedAbsFile FilePath
srcInterfaceFP
let (PackageIdentifier name _) = dp.packageIdent
srcInterfaceDir = Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent Path Abs File
srcInterfaceFile
compInterfaceDirsAndFiles <- do
(srcInterfaceSubDirs, _) <- doesDirExist srcInterfaceDir >>= \case
Bool
True -> Path Abs Dir -> IO ([Path Abs Dir], [Path Abs File])
forall (m :: * -> *) b.
MonadIO m =>
Path b Dir -> m ([Path Abs Dir], [Path Abs File])
listDir Path Abs Dir
srcInterfaceDir
Bool
False -> ([Path Abs Dir], [Path Abs File])
-> IO ([Path Abs Dir], [Path Abs File])
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([], [])
let isCompInterfaceDir Path b Dir
dir = do
(_, files) <- Path b Dir -> m ([Path Abs Dir], [Path Abs File])
forall (m :: * -> *) b.
MonadIO m =>
Path b Dir -> m ([Path Abs Dir], [Path Abs File])
listDir Path b Dir
dir
pure $ (dir, ) <$> F.find isInterface files
where
isInterface :: Path b File -> Bool
isInterface Path b File
file = Path b File -> Maybe FilePath
forall (m :: * -> *) b. MonadThrow m => Path b File -> m FilePath
fileExtension Path b File
file Maybe FilePath -> Maybe FilePath -> Bool
forall a. Eq a => a -> a -> Bool
== FilePath -> Maybe FilePath
forall a. a -> Maybe a
Just FilePath
".haddock"
mapMaybeM isCompInterfaceDir srcInterfaceSubDirs
let liftcompInterfaceDir Path b Dir
dir Path b File
file = do
let parentDir :: Path b Dir
parentDir = Path b Dir -> Path b Dir
forall b t. Path b t -> Path b Dir
parent Path b Dir
dir
parentName :: Path Rel Dir
parentName = Path b Dir -> Path Rel Dir
forall b. Path b Dir -> Path Rel Dir
dirname Path b Dir
parentDir
compName :: Path Rel Dir
compName = Path b Dir -> Path Rel Dir
forall b. Path b Dir -> Path Rel Dir
dirname Path b Dir
dir
uniqueName <- do
let parentName' :: FilePath
parentName' =
FilePath -> FilePath
dropTrailingPathSeparator (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ Path Rel Dir -> FilePath
forall b t. Path b t -> FilePath
toFilePath Path Rel Dir
parentName
compName' :: FilePath
compName' =
FilePath -> FilePath
dropTrailingPathSeparator (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ Path Rel Dir -> FilePath
forall b t. Path b t -> FilePath
toFilePath Path Rel Dir
compName
FilePath -> m (Path Rel Dir)
forall (m :: * -> *). MonadThrow m => FilePath -> m (Path Rel Dir)
parseRelDir (FilePath -> m (Path Rel Dir)) -> FilePath -> m (Path Rel Dir)
forall a b. (a -> b) -> a -> b
$ FilePath
parentName' FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
"_" FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
compName'
let destCompDir = Path b Dir -> Path b Dir
forall b t. Path b t -> Path b Dir
parent Path b Dir
parentDir Path b Dir -> Path Rel Dir -> Path b Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
uniqueName
destCompFile = Path b Dir
destCompDir Path b Dir -> Path Rel File -> Path b File
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path b File -> Path Rel File
forall b. Path b File -> Path Rel File
filename Path b File
file
ignoringAbsence (removeDirRecur destCompDir)
ensureDir destCompDir
onException
(copyDirRecur dir destCompDir)
(ignoringAbsence (removeDirRecur destCompDir))
pure (destCompFile, uniqueName)
destInterfaceRelFP =
FilePath
docRelFP FilePath -> FilePath -> FilePath
FP.</>
PackageIdentifier -> FilePath
packageIdentifierString DumpPackage
dp.packageIdent FilePath -> FilePath -> FilePath
FP.</>
(PackageName -> FilePath
packageNameString PackageName
name FilePath -> FilePath -> FilePath
FP.<.> FilePath
"haddock")
docPathRelFP =
(FilePath -> FilePath) -> Maybe FilePath -> Maybe FilePath
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((FilePath
docRelFP FilePath -> FilePath -> FilePath
FP.</>) (FilePath -> FilePath)
-> (FilePath -> FilePath) -> FilePath -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
FP.takeFileName) DumpPackage
dp.haddockHtml
mkInterface :: Maybe FilePath -> FilePath -> String
mkInterface Maybe FilePath
mDocPath FilePath
file =
FilePath -> [FilePath] -> FilePath
forall a. [a] -> [[a]] -> [a]
intercalate FilePath
"," ([FilePath] -> FilePath) -> [FilePath] -> FilePath
forall a b. (a -> b) -> a -> b
$ Maybe FilePath -> [FilePath] -> [FilePath]
forall a. Maybe a -> [a] -> [a]
mcons Maybe FilePath
mDocPath [FilePath
file]
compInterface :: (Path Abs Dir, Path Abs File) -> IO String
compInterface (Path Abs Dir
dir, Path Abs File
file) = do
(file', uniqueName) <- Path Abs Dir -> Path Abs File -> IO (Path Abs File, Path Rel Dir)
forall {m :: * -> *} {b} {b}.
(MonadCatch m, MonadUnliftIO m) =>
Path b Dir -> Path b File -> m (Path b File, Path Rel Dir)
liftcompInterfaceDir Path Abs Dir
dir Path Abs File
file
let compDir = FilePath -> FilePath
dropTrailingPathSeparator (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ Path Rel Dir -> FilePath
forall b t. Path b t -> FilePath
toFilePath Path Rel Dir
uniqueName
docDir = FilePath
docRelFP FilePath -> FilePath -> FilePath
FP.</> FilePath
compDir
pure $ mkInterface (Just docDir) (toFilePath file')
interfaces = Maybe FilePath -> FilePath -> FilePath
mkInterface Maybe FilePath
docPathRelFP FilePath
srcInterfaceFP
compInterfaces <- forM compInterfaceDirsAndFiles compInterface
let readInterfaceArgs =
FilePath
"-i" FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
: FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
intersperse FilePath
"-i" (FilePath
interfaces FilePath -> [FilePath] -> [FilePath]
forall a. a -> [a] -> [a]
: [FilePath]
compInterfaces)
destInterfaceFile <-
parseCollapsedAbsFile (toFilePath destDir FP.</> destInterfaceRelFP)
tryGetModificationTime srcInterfaceFile <&> \case
Left ()
_ -> Maybe InterfaceOpt
forall a. Maybe a
Nothing
Right UTCTime
srcInterfaceFileModTime ->
InterfaceOpt -> Maybe InterfaceOpt
forall a. a -> Maybe a
Just InterfaceOpt
{ [FilePath]
readInterfaceArgs :: [FilePath]
readInterfaceArgs :: [FilePath]
readInterfaceArgs
, UTCTime
srcInterfaceFileModTime :: UTCTime
srcInterfaceFileModTime :: UTCTime
srcInterfaceFileModTime
, Path Abs File
srcInterfaceFile :: Path Abs File
srcInterfaceFile :: Path Abs File
srcInterfaceFile
, Path Abs File
destInterfaceFile :: Path Abs File
destInterfaceFile :: Path Abs File
destInterfaceFile
}
copyPkgDocs :: InterfaceOpt -> IO ()
copyPkgDocs :: InterfaceOpt -> IO ()
copyPkgDocs InterfaceOpt
opts =
Path Abs File -> IO (Either () UTCTime)
forall (m :: * -> *).
MonadIO m =>
Path Abs File -> m (Either () UTCTime)
tryGetModificationTime InterfaceOpt
opts.destInterfaceFile IO (Either () UTCTime) -> (Either () UTCTime -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left ()
_ -> IO ()
doCopy
Right UTCTime
destInterfaceModTime
| UTCTime
destInterfaceModTime UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
< InterfaceOpt
opts.srcInterfaceFileModTime -> IO ()
doCopy
| Bool
otherwise -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
where
doCopy :: IO ()
doCopy = do
IO () -> IO ()
forall (m :: * -> *) a. (MonadIO m, MonadCatch m) => m a -> m ()
ignoringAbsence (Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
removeDirRecur Path Abs Dir
destHtmlAbsDir)
Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
ensureDir Path Abs Dir
destHtmlAbsDir
IO () -> IO () -> IO ()
forall (m :: * -> *) a b. MonadUnliftIO m => m a -> m b -> m a
onException
(Path Abs Dir -> Path Abs Dir -> IO ()
forall (m :: * -> *) b0 b1.
MonadIO m =>
Path b0 Dir -> Path b1 Dir -> m ()
copyDirRecur' (Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent InterfaceOpt
opts.srcInterfaceFile) Path Abs Dir
destHtmlAbsDir)
(IO () -> IO ()
forall (m :: * -> *) a. (MonadIO m, MonadCatch m) => m a -> m ()
ignoringAbsence (Path Abs Dir -> IO ()
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m ()
removeDirRecur Path Abs Dir
destHtmlAbsDir))
destHtmlAbsDir :: Path Abs Dir
destHtmlAbsDir = Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent InterfaceOpt
opts.destInterfaceFile
lookupDumpPackage ::
GhcPkgId
-> [Map GhcPkgId DumpPackage]
-> Maybe DumpPackage
lookupDumpPackage :: GhcPkgId -> [Map GhcPkgId DumpPackage] -> Maybe DumpPackage
lookupDumpPackage GhcPkgId
ghcPkgId [Map GhcPkgId DumpPackage]
dumpPkgs =
[DumpPackage] -> Maybe DumpPackage
forall a. [a] -> Maybe a
listToMaybe ([DumpPackage] -> Maybe DumpPackage)
-> [DumpPackage] -> Maybe DumpPackage
forall a b. (a -> b) -> a -> b
$ (Map GhcPkgId DumpPackage -> Maybe DumpPackage)
-> [Map GhcPkgId DumpPackage] -> [DumpPackage]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (GhcPkgId -> Map GhcPkgId DumpPackage -> Maybe DumpPackage
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup GhcPkgId
ghcPkgId) [Map GhcPkgId DumpPackage]
dumpPkgs
haddockIndexFile :: Path Abs Dir -> Path Abs File
haddockIndexFile :: Path Abs Dir -> Path Abs File
haddockIndexFile Path Abs Dir
destDir = Path Abs Dir
destDir Path Abs Dir -> Path Rel File -> Path Abs File
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel File
relFileIndexHtml
localDocDir :: BaseConfigOpts -> Path Abs Dir
localDocDir :: BaseConfigOpts -> Path Abs Dir
localDocDir BaseConfigOpts
bco = BaseConfigOpts
bco.localInstallRoot Path Abs Dir -> Path Rel Dir -> Path Abs Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
docDirSuffix
localDepsDocDir :: BaseConfigOpts -> Path Abs Dir
localDepsDocDir :: BaseConfigOpts -> Path Abs Dir
localDepsDocDir BaseConfigOpts
bco = BaseConfigOpts -> Path Abs Dir
localDocDir BaseConfigOpts
bco Path Abs Dir -> Path Rel Dir -> Path Abs Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
relDirAll
snapDocDir :: BaseConfigOpts -> Path Abs Dir
snapDocDir :: BaseConfigOpts -> Path Abs Dir
snapDocDir BaseConfigOpts
bco = BaseConfigOpts
bco.snapInstallRoot Path Abs Dir -> Path Rel Dir -> Path Abs Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
docDirSuffix
generateLocalHaddockForHackageArchives ::
(HasEnvConfig env, HasTerm env)
=> [LocalPackage]
-> RIO env ()
generateLocalHaddockForHackageArchives :: forall env.
(HasEnvConfig env, HasTerm env) =>
[LocalPackage] -> RIO env ()
generateLocalHaddockForHackageArchives [LocalPackage]
lps = do
buildSubset <- Getting BuildSubset env BuildSubset -> RIO env BuildSubset
forall s (m :: * -> *) a. MonadReader s m => Getting a s a -> m a
view (Getting BuildSubset env BuildSubset -> RIO env BuildSubset)
-> Getting BuildSubset env BuildSubset -> RIO env BuildSubset
forall a b. (a -> b) -> a -> b
$ (EnvConfig -> Const BuildSubset EnvConfig)
-> env -> Const BuildSubset env
forall env. HasEnvConfig env => Lens' env EnvConfig
Lens' env EnvConfig
envConfigL ((EnvConfig -> Const BuildSubset EnvConfig)
-> env -> Const BuildSubset env)
-> ((BuildSubset -> Const BuildSubset BuildSubset)
-> EnvConfig -> Const BuildSubset EnvConfig)
-> Getting BuildSubset env BuildSubset
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (EnvConfig -> BuildSubset) -> SimpleGetter EnvConfig BuildSubset
forall s a. (s -> a) -> SimpleGetter s a
to (.buildOptsCLI.buildSubset)
let localsExcluded =
BuildSubset
buildSubset BuildSubset -> BuildSubset -> Bool
forall a. Eq a => a -> a -> Bool
== BuildSubset
BSOnlyDependencies Bool -> Bool -> Bool
|| BuildSubset
buildSubset BuildSubset -> BuildSubset -> Bool
forall a. Eq a => a -> a -> Bool
== BuildSubset
BSOnlySnapshot
unless localsExcluded $
forM_ lps $ \LocalPackage
lp ->
let pkg :: Package
pkg = LocalPackage
lp.package
pkgId :: PackageIdentifier
pkgId = PackageName -> Version -> PackageIdentifier
PackageIdentifier Package
pkg.name Package
pkg.version
pkgDir :: Path Abs Dir
pkgDir = Path Abs File -> Path Abs Dir
forall b t. Path b t -> Path b Dir
parent LocalPackage
lp.cabalFP
in Bool -> RIO env () -> RIO env ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when LocalPackage
lp.wanted (RIO env () -> RIO env ()) -> RIO env () -> RIO env ()
forall a b. (a -> b) -> a -> b
$
Path Abs Dir -> PackageIdentifier -> RIO env ()
forall env.
(HasEnvConfig env, HasTerm env) =>
Path Abs Dir -> PackageIdentifier -> RIO env ()
generateLocalHaddockForHackageArchive Path Abs Dir
pkgDir PackageIdentifier
pkgId
generateLocalHaddockForHackageArchive ::
(HasEnvConfig env, HasTerm env)
=> Path Abs Dir
-> PackageIdentifier
-> RIO env ()
generateLocalHaddockForHackageArchive :: forall env.
(HasEnvConfig env, HasTerm env) =>
Path Abs Dir -> PackageIdentifier -> RIO env ()
generateLocalHaddockForHackageArchive Path Abs Dir
pkgDir PackageIdentifier
pkgId = do
distDir <- Path Abs Dir -> RIO env (Path Abs Dir)
forall env (m :: * -> *).
(HasEnvConfig env, MonadReader env m, MonadThrow m) =>
Path Abs Dir -> m (Path Abs Dir)
distDirFromDir Path Abs Dir
pkgDir
let pkgIdName = PackageIdentifier -> FilePath
forall a. Pretty a => a -> FilePath
display PackageIdentifier
pkgId
name = FilePath
pkgIdName FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
"-docs"
(nameRelDir, tarGzFileName) = fromMaybe
(error "impossible")
( do relDir <- parseRelDir name
nameRelFile <- parseRelFile name
tarGz <- addExtension ".gz" =<< addExtension ".tar" nameRelFile
pure (relDir, tarGz)
)
tarGzFile = Path Abs Dir
distDir Path Abs Dir -> Path Rel File -> Path Abs File
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel File
tarGzFileName
docDir = Path Abs Dir
distDir Path Abs Dir -> Path Rel Dir -> Path Abs Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
docDirSuffix Path Rel Dir -> Path Rel Dir -> Path Rel Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
htmlDirSuffix
tarGzFileCreated <- createTarGzFile tarGzFile docDir nameRelDir
if tarGzFileCreated
then
prettyInfo $
fillSep
[ flow "Archive of Haddock documentation for Hackage for"
, style Current (fromString pkgIdName)
, flow "created at:"
]
<> line
<> pretty tarGzFile
else
prettyWarnL
[ flow "No Haddock documentation for Hackage available for"
, style Error (fromString pkgIdName) <> "."
]
createTarGzFile ::
Path Abs File
-> Path Abs Dir
-> Path Rel Dir
-> RIO env Bool
createTarGzFile :: forall env.
Path Abs File -> Path Abs Dir -> Path Rel Dir -> RIO env Bool
createTarGzFile Path Abs File
tar Path Abs Dir
base Path Rel Dir
dir = do
dirExists <- Path Abs Dir -> RIO env Bool
forall (m :: * -> *) b. MonadIO m => Path b Dir -> m Bool
doesDirExist (Path Abs Dir -> RIO env Bool) -> Path Abs Dir -> RIO env Bool
forall a b. (a -> b) -> a -> b
$ Path Abs Dir
base Path Abs Dir -> Path Rel Dir -> Path Abs Dir
forall b t. Path b Dir -> Path Rel t -> Path b t
</> Path Rel Dir
dir
if dirExists
then do
entries <- liftIO $ Tar.pack base' [dir']
if null entries
then pure False
else do
ensureDir $ parent tar
BL.writeFile tar' $ GZip.compress $ Tar.write entries
pure True
else pure False
where
base' :: FilePath
base' = Path Abs Dir -> FilePath
fromAbsDir Path Abs Dir
base
dir' :: FilePath
dir' = Path Rel Dir -> FilePath
fromRelDir Path Rel Dir
dir
tar' :: FilePath
tar' = Path Abs File -> FilePath
fromAbsFile Path Abs File
tar