hie-bios-0.20.0: src/HIE/Bios/Cradle.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE RecordWildCards #-}
module HIE.Bios.Cradle (
findCradle
, loadCradle
, loadImplicitCradle
, yamlConfig
, defaultCradle
, isCabalCradle
, isStackCradle
, isDirectCradle
, isBiosCradle
, isNoneCradle
, isMultiCradle
, isDefaultCradle
, isOtherCradle
, getCradle
, Process.readProcessWithOutputs
, Process.readProcessWithCwd
, makeCradleResult
-- | Cradle project configuration types
, CradleProjectConfig(..)
) where
import Colog.Core (LogAction (..), WithSeverity (..), Severity (..), (<&))
import Control.Applicative ((<|>))
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Maybe
import qualified Data.Yaml as Yaml
import Data.Version
import Data.Void
import Data.Bifunctor (first)
import Data.Conduit.Process
import Data.Maybe (fromMaybe)
import Data.List
import Data.Ord (Down(..))
import qualified Data.Text as T
import System.Environment
import System.Exit
import System.FilePath
import System.Directory
import System.IO (hClose, hPutStr)
import System.IO.Temp
import HIE.Bios.Config
import HIE.Bios.Types hiding (ActionName(..))
import qualified HIE.Bios.Process as Process
import qualified HIE.Bios.Types as Types
import qualified HIE.Bios.Ghc.Gap as Gap
import HIE.Bios.Cradle.ProjectConfig
import HIE.Bios.Cradle.Utils
import HIE.Bios.Cradle.Cabal as Cabal
import HIE.Bios.Cradle.Resolved
import HIE.Bios.Cradle.ProgramVersions
import Data.List.Extra (nubOrd)
----------------------------------------------------------------
-- | Given @root\/foo\/bar.hs@, return @root\/hie.yaml@, or wherever the yaml file was found.
--
-- Note, 'findCradle' used to **not** work for directories and required a Haskell file.
-- This has been fixed since @0.14.0@.
-- However, 'loadCradle' and 'loadImplicitCradle' still require a Haskell
-- source file and won't work properly with a directory parameter.
findCradle :: FilePath -> IO (Maybe FilePath)
findCradle wfile = do
wdir <- doesDirectoryExist wfile >>= \case
True -> pure wfile
False -> pure (takeDirectory wfile)
runMaybeT (yamlConfig wdir)
-- | Given root\/hie.yaml load the Cradle.
loadCradle :: LogAction IO (WithSeverity Log) -> FilePath -> IO (Cradle Void)
loadCradle l = loadCradleWithOpts l absurd
-- | Given root\/foo\/bar.hs, load an implicit cradle
loadImplicitCradle :: Show a => LogAction IO (WithSeverity Log) -> FilePath -> IO (Cradle a)
loadImplicitCradle l wfile = do
let wdir = takeDirectory wfile
cfg <- runMaybeT (implicitConfig wdir)
case cfg of
Just bc -> getCradle l absurd bc
Nothing -> return $ defaultCradle l wdir
-- | Finding 'Cradle'.
-- Find a cabal file by tracing ancestor directories.
-- Find a sandbox according to a cabal sandbox config
-- in a cabal directory.
loadCradleWithOpts :: (Yaml.FromJSON b, Show a) => LogAction IO (WithSeverity Log) -> (b -> CradleAction a) -> FilePath -> IO (Cradle a)
loadCradleWithOpts l buildCustomCradle wfile = do
cradleConfig <- readCradleConfig wfile
l <& WithSeverity (LogAny $ T.pack $ "Cradle Config: " ++ show cradleConfig) Debug
getCradle l buildCustomCradle (cradleConfig, takeDirectory wfile)
getCradle :: Show a => LogAction IO (WithSeverity Log) -> (b -> CradleAction a) -> (CradleConfig b, FilePath) -> IO (Cradle a)
getCradle l buildCustomCradle (cc, wdir) = do
rcs <- canonicalizeResolvedCradles wdir cs
liftIO $ l <& WithSeverity (LogAny . T.pack $ "Resolved Cradles " ++ show ((fmap . fmap) (const ()) rcs)) Debug
resolvedCradlesToCradle l buildCustomCradle wdir rcs
where
cs = resolveCradleTree wdir cc
addActionDeps :: [FilePath] -> CradleLoadResult ComponentOptions -> CradleLoadResult ComponentOptions
addActionDeps deps =
cradleLoadResult
CradleNone
(\err -> CradleFail (err { cradleErrorDependencies = cradleErrorDependencies err `union` deps }))
(\(ComponentOptions os' dir ds) -> CradleSuccess (ComponentOptions os' dir (ds `union` deps)))
resolvedCradlesToCradle :: Show a => LogAction IO (WithSeverity Log) -> (b -> CradleAction a) -> FilePath -> [ResolvedCradle b] -> IO (Cradle a)
resolvedCradlesToCradle logger buildCustomCradle root cs = mdo
let run_ghc_cmd args =
-- We're being lazy here and just returning the ghc path for the
-- first non-none cradle. This shouldn't matter in practice: all
-- sub cradles should be using the same ghc version!
case filter (notNoneType . actionName) $ map snd cradleActions of
[] -> return CradleNone
(act:_) ->
runGhcCmd
act
args
versions <- makeVersions logger root run_ghc_cmd
let rcs = ResolvedCradles root cs versions
cradleActions = [ (c, resolveCradleAction logger buildCustomCradle rcs root c) | c <- cs ]
err_msg (TargetWithContext fp fps)
= ["Multi Cradle: No prefixes matched"
, "pwd: " ++ root
, "filepath: " ++ fp
, "context: " ++ intercalate ", " fps
, "prefixes:"
] ++ [show (prefix pf, actionName cc) | (pf, cc) <- cradleActions]
pure $ Cradle
{ cradleRootDir = root
, cradleLogger = logger
, cradleOptsProg = CradleAction
{ actionName = multiActionName
, runCradle = \fpc prev -> do
absfp <- makeAbsolute (targetFilePath fpc)
case selectCradle (prefix . fst) absfp cradleActions of
Just (rc, act) -> do
addActionDeps (cradleDeps rc) <$> runCradle act fpc prev
Nothing -> return $ CradleFail $ CradleError [] ExitSuccess (err_msg fpc) (targetFilePath fpc: targetContext fpc)
, runGhcCmd = run_ghc_cmd
}
}
where
multiActionName
| all (\c -> isStackCradleConfig c || isNoneCradleConfig c) cs
= Types.Stack
| all (\c -> isCabalCradleConfig c || isNoneCradleConfig c) cs
= Types.Cabal
| [True] <- map isBiosCradleConfig $ filter (not . isNoneCradleConfig) cs
= Types.Bios
| [True] <- map isDirectCradleConfig $ filter (not . isNoneCradleConfig) cs
= Types.Direct
| otherwise
= Types.Multi
isStackCradleConfig cfg = case concreteCradle cfg of
ConcreteStack{} -> True
_ -> False
isCabalCradleConfig cfg = case concreteCradle cfg of
ConcreteCabal{} -> True
_ -> False
isBiosCradleConfig cfg = case concreteCradle cfg of
ConcreteBios{} -> True
_ -> False
isDirectCradleConfig cfg = case concreteCradle cfg of
ConcreteDirect{} -> True
_ -> False
isNoneCradleConfig cfg = case concreteCradle cfg of
ConcreteNone{} -> True
_ -> False
notNoneType Types.None = False
notNoneType _ = True
resolveCradleAction :: Show a => LogAction IO (WithSeverity Log) -> (b -> CradleAction a) -> ResolvedCradles b -> FilePath -> ResolvedCradle b -> CradleAction a
resolveCradleAction l buildCustomCradle cs root cradle = addLoadModeLogToCradleAction $
case concreteCradle cradle of
ConcreteCabal t -> cabalCradle l cs root (cabalComponent t) (projectConfigFromMaybe root (cabalProjectFile t))
ConcreteStack t -> stackCradle l cs root (stackComponent t) (projectConfigFromMaybe root (stackYaml t))
ConcreteBios bios deps mbGhc -> biosCradle l cs root bios deps mbGhc
ConcreteDirect xs -> directCradle l root xs
ConcreteNone -> noneCradle
ConcreteOther a -> buildCustomCradle a
where
-- Add a log message to each loading operation.
addLoadModeLogToCradleAction crdlAct = crdlAct
{ runCradle = \fpc ls -> do
l <& LogRequestedCradleLoadMode (T.pack $ show $ actionName crdlAct) fpc ls `WithSeverity` Debug
runCradle crdlAct fpc ls
}
resolveCradleTree :: FilePath -> CradleConfig a -> [ResolvedCradle a]
resolveCradleTree root (CradleConfig confDeps confTree) = go root confDeps confTree
where
go pfix deps tree = case tree of
Cabal t -> [ResolvedCradle pfix deps (ConcreteCabal t)]
Stack t -> [ResolvedCradle pfix deps (ConcreteStack t)]
Bios bios dcmd mbGhc -> [ResolvedCradle pfix deps (ConcreteBios bios dcmd mbGhc)]
Direct xs -> [ResolvedCradle pfix deps (ConcreteDirect xs)]
None -> [ResolvedCradle pfix deps ConcreteNone]
Other a _ -> [ResolvedCradle pfix deps (ConcreteOther a)]
CabalMulti dc xs -> [ResolvedCradle p deps (ConcreteCabal (dc <> c)) | (p, c) <- xs ]
StackMulti dc xs -> [ResolvedCradle p deps (ConcreteStack (dc <> c)) | (p, c) <- xs ]
Multi xs -> concat [ go pfix' (deps ++ deps') tree' | (pfix', CradleConfig deps' tree') <- xs]
-- | Try to infer an appropriate implicit cradle type from stuff we can find in the enclosing directories:
-- * If a .hie-bios file is found, we can treat this as a @Bios@ cradle
-- * If a stack.yaml file is found, we can treat this as a @Stack@ cradle
-- * If a cabal.project or an xyz.cabal file is found, we can treat this as a @Cabal@ cradle
inferCradleTree :: FilePath -> MaybeT IO (CradleTree a, FilePath)
inferCradleTree fp =
maybeItsBios
<|> maybeItsStack
<|> maybeItsCabal
-- <|> maybeItsObelisk
-- <|> maybeItsObelisk
where
maybeItsBios = (\wdir -> (Bios (Program $ wdir </> ".hie-bios") Nothing Nothing, wdir)) <$> biosWorkDir fp
maybeItsStack = stackExecutable >> (Stack $ StackType Nothing Nothing Nothing,) <$> stackWorkDir fp
maybeItsCabal = (Cabal $ CabalType Nothing Nothing Nothing,) <$> cabalWorkDir fp
-- maybeItsObelisk = (Obelisk,) <$> obeliskWorkDir fp
-- maybeItsBazel = (Bazel,) <$> rulesHaskellWorkDir fp
-- | Wraps up the cradle inferred by @inferCradleTree@ as a @CradleConfig@ with no dependencies
implicitConfig :: FilePath -> MaybeT IO (CradleConfig a, FilePath)
implicitConfig = (fmap . first) (CradleConfig noDeps) . inferCradleTree
where
noDeps :: [FilePath]
noDeps = []
yamlConfig :: FilePath -> MaybeT IO FilePath
yamlConfig fp = do
configDir <- yamlConfigDirectory fp
return (configDir </> configFileName)
yamlConfigDirectory :: FilePath -> MaybeT IO FilePath
yamlConfigDirectory = Process.findFileUpwards configFileName
readCradleConfig :: Yaml.FromJSON b => FilePath -> IO (CradleConfig b)
readCradleConfig yamlHie = do
cfg <- liftIO $ readConfig yamlHie
return (cradle cfg)
configFileName :: FilePath
configFileName = "hie.yaml"
-- | Pass '-dynamic' flag when GHC is built with dynamic linking.
--
-- Append flag to options of 'defaultCradle' and 'directCradle' if GHC is dynmically linked,
-- because unlike the case of using build tools, which means '-dynamic' can be set via
-- '.cabal' or 'package.yaml', users have to create an explicit hie.yaml to pass this flag.
argDynamic :: [String]
argDynamic = ["-dynamic" | Gap.hostIsDynamic ]
---------------------------------------------------------------
isCabalCradle :: Cradle a -> Bool
isCabalCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Cabal -> True
_ -> False
isStackCradle :: Cradle a -> Bool
isStackCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Stack -> True
_ -> False
isDirectCradle :: Cradle a -> Bool
isDirectCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Direct -> True
_ -> False
isBiosCradle :: Cradle a -> Bool
isBiosCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Bios -> True
_ -> False
isMultiCradle :: Cradle a -> Bool
isMultiCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Multi -> True
_ -> False
isNoneCradle :: Cradle a -> Bool
isNoneCradle crdl = case actionName (cradleOptsProg crdl) of
Types.None -> True
_ -> False
isDefaultCradle :: Cradle a -> Bool
isDefaultCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Default -> True
_ -> False
isOtherCradle :: Cradle a -> Bool
isOtherCradle crdl = case actionName (cradleOptsProg crdl) of
Types.Other _ -> True
_ -> False
---------------------------------------------------------------
-- | Default cradle has no special options, not very useful for loading
-- modules.
defaultCradle :: LogAction IO (WithSeverity Log) -> FilePath -> Cradle a
defaultCradle l cur_dir =
Cradle
{ cradleRootDir = cur_dir
, cradleLogger = l
, cradleOptsProg = CradleAction
{ actionName = Types.Default
, runCradle = \_ _ ->
return (CradleSuccess (ComponentOptions argDynamic cur_dir []))
, runGhcCmd = runGhcCmdOnPath l cur_dir
}
}
---------------------------------------------------------------
-- | The none cradle tells us not to even attempt to load a certain directory
noneCradle :: CradleAction a
noneCradle =
CradleAction
{ actionName = Types.None
, runCradle = \_ _ -> return CradleNone
, runGhcCmd = \_ -> return CradleNone
}
---------------------------------------------------------------
-- | The multi cradle selects a cradle based on the filepath
--
-- Canonicalize the relative paths present in the multi-cradle and
-- also order the paths by most specific first. In the cradle selection
-- function we want to choose the most specific cradle possible.
canonicalizeResolvedCradles :: FilePath -> [ResolvedCradle a] -> IO [ResolvedCradle a]
canonicalizeResolvedCradles cur_dir cs =
sortOn (Down . prefix)
<$> mapM (\c -> (\abs_fp -> c {prefix = abs_fp}) <$> makeAbsolute (cur_dir </> prefix c)) cs
-------------------------------------------------------------------------
directCradle :: LogAction IO (WithSeverity Log) -> FilePath -> [String] -> CradleAction a
directCradle l wdir args
= CradleAction
{ actionName = Types.Direct
, runCradle = \_ loadStyle -> do
logCradleHasNoSupportForLoadFileWithContext l loadStyle "direct"
return (CradleSuccess (ComponentOptions (args ++ argDynamic) wdir []))
, runGhcCmd = runGhcCmdOnPath l wdir
}
-------------------------------------------------------------------------
-- | Find a cradle by finding an executable `hie-bios` file which will
-- be executed to find the correct GHC options to use.
biosCradle :: LogAction IO (WithSeverity Log) -> ResolvedCradles b -> FilePath -> Callable -> Maybe Callable -> Maybe FilePath -> CradleAction a
biosCradle l rc wdir biosCall biosDepsCall mbGhc
= CradleAction
{ actionName = Types.Bios
, runCradle = biosAction rc wdir biosCall biosDepsCall l
, runGhcCmd = \args -> Process.readProcessWithCwd l wdir (fromMaybe "ghc" mbGhc) args ""
}
biosWorkDir :: FilePath -> MaybeT IO FilePath
biosWorkDir = Process.findFileUpwards ".hie-bios"
-- | shared between @biosDepsAction@ and @biosAction@.
biosFilePaths :: TargetWithContext -> LoadMode -> [FilePath]
biosFilePaths fpc = \case
LoadFile -> [targetFilePath fpc]
LoadFileWithContext -> targetAndContext fpc
-- Exhaustive matching to trigger type errors for datatype changes.
LoadUnitsInferred -> targetAndContext fpc
LoadUnitsFromCradle -> targetAndContext fpc
biosDepsAction :: LogAction IO (WithSeverity Log) -> FilePath -> Maybe Callable -> TargetWithContext -> LoadMode -> IO [FilePath]
biosDepsAction l wdir (Just biosDepsCall) fpc loadStyle = do
let fps = biosFilePaths fpc loadStyle
(ex, sout, serr, [(_, args)]) <-
runContT (withCallableToProcess biosDepsCall fps) $ \biosDeps' ->
Process.readProcessWithOutputs [hie_bios_output] l wdir biosDeps'
case ex of
ExitFailure _ -> error $ show (ex, sout, serr)
ExitSuccess -> return $ fromMaybe [] args
biosDepsAction _ _ Nothing _ _ = return []
biosAction
:: ResolvedCradles a
-> FilePath
-> Callable
-> Maybe Callable
-> LogAction IO (WithSeverity Log)
-> TargetWithContext
-> LoadMode
-> IO (CradleLoadResult ComponentOptions)
biosAction rc wdir bios bios_deps l fpc loadStyle = do
let fp = targetFilePath fpc
ghc_version <- liftIO $ runCachedIO $ ghcVersion $ cradleProgramVersions rc
let warnUnsupportedLoadMode msg =
liftIO $ l <& WithSeverity
(LogLoadModeUnsupported "bios" loadStyle (Just msg))
Warning
determinedLoadMode <- case ghc_version of
Just ghc
| ghc >= makeVersion [9,4] -> do
-- Multi-component supported from ghc 9.4
-- We trust the assertion for a bios program, as we have no way of
-- checking its version
pure $ case loadStyle of
LoadFile -> LoadFile
LoadFileWithContext -> LoadFileWithContext
LoadUnitsFromCradle -> LoadFileWithContext
LoadUnitsInferred -> LoadFileWithContext
| loadStyle /= LoadFile -> do
warnUnsupportedLoadMode "ghc version is too old. We require `ghc >= 9.4`"
pure LoadFile
_ -> do
warnUnsupportedLoadMode "Unable to determine ghc version. We require `ghc >= 9.4`"
pure LoadFile
let fps = biosFilePaths fpc determinedLoadMode
(ex, _stdo, std, [(_, res),(_, mb_deps)]) <-
runContT (withCallableToProcess bios fps) $ \bios' ->
Process.readProcessWithOutputs [hie_bios_output, hie_bios_deps] l wdir bios'
deps <- case mb_deps of
Just x -> return x
Nothing -> biosDepsAction l wdir bios_deps fpc determinedLoadMode
-- Output from the program should be written to the output file and
-- delimited by newlines.
-- Execute the bios action and add dependencies of the cradle.
-- Removes all duplicates.
return $ makeCradleResult (ex, std, wdir, fromMaybe [] res) deps [fp]
withCallableToProcess :: Callable -> [String] -> ContT a IO CreateProcess
withCallableToProcess (Command shellCommand) files = ContT $ \action -> do
old_env <- getEnvironment
case files of
[] -> action $ (shell shellCommand) {env = Nothing}
(x : _) ->
runContT (withHieBiosMultiArg files) $ \multi_file -> do
let updated_env = Just $
[ (hie_bios_multi_arg, multi_file)
, (hie_bios_arg, x)
] ++
old_env
action $ (shell shellCommand){env = updated_env}
withCallableToProcess (Program path) files = ContT $ \action -> do
canon_path <- canonicalizePath path
old_env <- getEnvironment
case files of
[] -> action $ (proc canon_path []){env = Nothing}
(x : _) ->
runContT (withHieBiosMultiArg files) $ \multi_file -> do
let updated_env = Just $
(hie_bios_multi_arg, multi_file) : old_env
action $ (proc canon_path [x]){env = updated_env}
withHieBiosMultiArg :: [String] -> ContT a IO FilePath
withHieBiosMultiArg files = ContT $ \action -> do
withSystemTempFile hie_bios_multi_arg $ \file h -> do
case files of
[] -> hClose h >> action file
(f0 : rest) -> do
hPutStr h f0
forM_ rest $ \f -> hPutStr h "\x00" >> hPutStr h f
hClose h
action file
------------------------------------------------------------------------
-- |Cabal Cradle
-- Works for new-build by invoking `v2-repl`.
cabalCradle :: LogAction IO (WithSeverity Log) -> ResolvedCradles b -> FilePath -> Maybe String -> CradleProjectConfig -> CradleAction a
cabalCradle l cs wdir mc projectFile
= CradleAction
{ actionName = Types.Cabal
, runCradle = \fpc -> runCradleResultT . cabalAction cs wdir mc l projectFile fpc
, runGhcCmd = runCabalGhcCmd cs wdir l projectFile
}
cabalWorkDir :: FilePath -> MaybeT IO FilePath
cabalWorkDir wdir =
Process.findFileUpwards "cabal.project" wdir
<|> Process.findFileUpwardsPredicate (\fp -> takeExtension fp == ".cabal") wdir
------------------------------------------------------------------------
stackYamlProcessArgs :: CradleProjectConfig -> [String]
stackYamlProcessArgs (ExplicitConfig yaml) = ["--stack-yaml", yaml]
stackYamlProcessArgs NoExplicitConfig = []
stackYamlLocationOrDefault :: CradleProjectConfig -> FilePath
stackYamlLocationOrDefault NoExplicitConfig = "stack.yaml"
stackYamlLocationOrDefault (ExplicitConfig yaml) = yaml
-- | Stack Cradle
-- Works for by invoking `stack repl` with a wrapper script
stackCradle :: LogAction IO (WithSeverity Log) -> ResolvedCradles b -> FilePath -> Maybe String -> CradleProjectConfig -> CradleAction a
stackCradle l cs wdir mc syaml =
CradleAction
{ actionName = Types.Stack
, runCradle = stackAction wdir mc syaml l cs
, runGhcCmd = \args -> runCradleResultT $ do
-- Setup stack silently, since stack might print stuff to stdout in some cases (e.g. on Win)
-- Issue 242 from HLS: https://github.com/haskell/haskell-language-server/issues/242
_ <- Process.readProcessWithCwd_ l wdir "stack" (stackYamlProcessArgs syaml <> ["setup", "--silent"]) ""
Process.readProcessWithCwd_ l wdir "stack"
(stackYamlProcessArgs syaml <> ["exec", "ghc", "--"] <> args)
""
}
-- | @'stackCradleDependencies' rootDir componentDir@.
-- Compute the dependencies of the stack cradle based
-- on the cradle root and the component directory.
--
-- Directory 'componentDir' is a sub-directory where we look for
-- package specific cradle dependencies, such as 'package.yaml' and
-- a '.cabal' file.
--
-- Found dependencies are relative to 'rootDir'.
stackCradleDependencies :: FilePath -> FilePath -> CradleProjectConfig -> IO [FilePath]
stackCradleDependencies wdir componentDir syaml = do
let relFp = makeRelative wdir componentDir
cabalFiles' <- findCabalFiles componentDir
let cabalFiles = map (relFp </>) cabalFiles'
return $ map normalise $
cabalFiles ++ [relFp </> "package.yaml", stackYamlLocationOrDefault syaml]
stackAction
:: FilePath
-> Maybe String
-> CradleProjectConfig
-> LogAction IO (WithSeverity Log)
-> ResolvedCradles a
-> TargetWithContext
-> LoadMode
-> IO (CradleLoadResult ComponentOptions)
stackAction workDir mc syaml l cs fpc loadStyle = do
let fp = targetFilePath fpc
logCradleHasNoSupportForLoadFileWithContext l loadStyle "stack"
let ghcProc args = proc "stack" (stackYamlProcessArgs syaml <> ["exec", "ghc", "--"] <> args)
-- Same wrapper works as with cabal
wrapper_fp <- withGhcWrapperTool l ghcProc workDir
let
fallback = [ comp | Just comp <- [mc] ]
componentsToLoad = nubOrd <$> mconcat
[ comps
| ResolvedCradle{concreteCradle = ConcreteStack
(StackType {stackComponentsToLoad = comps})} <- resolvedCradles cs ]
allComponents = [ comp
| ResolvedCradle{concreteCradle = ConcreteStack
(StackType {stackComponent = Just comp})} <- resolvedCradles cs ]
-- for stack we do not include --test --bench to avoid triggering stack repl limitations.
inferred = []
components = case loadStyle of
LoadUnitsFromCradle
| Just comps <- componentsToLoad -> comps
| null allComponents -> inferred
| otherwise -> allComponents
LoadUnitsInferred -> inferred
LoadFile -> fallback
LoadFileWithContext -> fallback
(ex1, _stdo, stde, [(_, maybeArgs)]) <-
Process.readProcessWithOutputs [hie_bios_output] l workDir
$ stackProcess syaml
$ ["repl", "--no-nix-pure", "--with-ghc", wrapper_fp]
<> components
(ex2, pkg_args, stdr, _) <-
Process.readProcessWithOutputs [hie_bios_output] l workDir
$ stackProcess syaml ["path", "--ghc-package-path"]
let split_pkgs = concatMap splitSearchPath pkg_args
pkg_ghc_args = concatMap (\p -> ["-package-db", p] ) split_pkgs
args = fromMaybe [] maybeArgs
case processCabalWrapperArgs args of
Nothing -> do
-- Best effort. Assume the working directory is the
-- the root of the component, so we are right in trivial cases at least.
deps <- stackCradleDependencies workDir workDir syaml
pure $ CradleFail
(CradleError deps ex1
([ "Failed to parse result of calling stack" ]
++ stde
++ args)
[fp]
)
Just (componentDir, ghc_args) -> do
deps <- stackCradleDependencies workDir componentDir syaml
pure $ makeCradleResult
( combineExitCodes [ex1, ex2]
, stde ++ stdr, componentDir
, ghc_args ++ pkg_ghc_args
)
deps
[fp]
stackProcess :: CradleProjectConfig -> [String] -> CreateProcess
stackProcess syaml args = proc "stack" $ stackYamlProcessArgs syaml <> args
combineExitCodes :: [ExitCode] -> ExitCode
combineExitCodes = foldr go ExitSuccess
where
go ExitSuccess b = b
go a _ = a
stackExecutable :: MaybeT IO FilePath
stackExecutable = MaybeT $ findExecutable "stack"
stackWorkDir :: FilePath -> MaybeT IO FilePath
stackWorkDir = Process.findFileUpwards "stack.yaml"
{-
-- Support removed for 0.3 but should be added back in the future
----------------------------------------------------------------------------
-- rules_haskell - Thanks for David Smith for helping with this one.
-- Looks for the directory containing a WORKSPACE file
--
rulesHaskellWorkDir :: FilePath -> MaybeT IO FilePath
rulesHaskellWorkDir fp =
findFileUpwards "WORKSPACE" fp
rulesHaskellCradle :: FilePath -> Cradle
rulesHaskellCradle wdir =
Cradle
{ cradleRootDir = wdir
, cradleOptsProg = CradleAction
{ actionName = "bazel"
, runCradle = rulesHaskellAction wdir
}
}
rulesHaskellCradleDependencies :: FilePath -> IO [FilePath]
rulesHaskellCradleDependencies _wdir = return ["BUILD.bazel", "WORKSPACE"]
bazelCommand :: String
bazelCommand = $(embedStringFile "wrappers/bazel")
rulesHaskellAction :: FilePath -> FilePath -> IO (CradleLoadResult ComponentOptions)
rulesHaskellAction workDir fp = do
wrapper_fp <- writeSystemTempFile "wrapper" bazelCommand
setFileMode wrapper_fp accessModes
let rel_path = makeRelative workDir fp
(ex, args, stde) <-
readProcessWithOutputFile workDir wrapper_fp [rel_path] []
let args' = filter (/= '\'') args
let args'' = filter (/= "\"$GHCI_LOCATION\"") (words args')
deps <- rulesHaskellCradleDependencies workDir
return $ makeCradleResult (ex, stde, args'') deps
------------------------------------------------------------------------------
-- Obelisk Cradle
-- Searches for the directory which contains `.obelisk`.
obeliskWorkDir :: FilePath -> MaybeT IO FilePath
obeliskWorkDir fp = do
-- Find a possible root which will contain the cabal.project
wdir <- findFileUpwards (== "cabal.project") fp
-- Check for the ".obelisk" folder in this directory
check <- liftIO $ doesDirectoryExist (wdir </> ".obelisk")
unless check (fail "Not obelisk dir")
return wdir
obeliskCradleDependencies :: FilePath -> IO [FilePath]
obeliskCradleDependencies _wdir = return []
obeliskCradle :: FilePath -> Cradle
obeliskCradle wdir =
Cradle
{ cradleRootDir = wdir
, cradleOptsProg = CradleAction
{ actionName = "obelisk"
, runCradle = obeliskAction wdir
}
}
obeliskAction :: FilePath -> FilePath -> IO (CradleLoadResult ComponentOptions)
obeliskAction workDir _fp = do
(ex, args, stde) <-
readProcessWithOutputFile workDir "ob" ["ide-args"] []
o_deps <- obeliskCradleDependencies workDir
return (makeCradleResult (ex, stde, words args) o_deps )
-}
makeCradleResult :: (ExitCode, [String], FilePath, [String]) -> [FilePath] -> [FilePath] -> CradleLoadResult ComponentOptions
makeCradleResult (ex, err, componentDir, gopts) deps loadingFiles =
case ex of
ExitFailure _ -> CradleFail (CradleError deps ex err loadingFiles)
_ ->
let compOpts = ComponentOptions gopts componentDir deps
in CradleSuccess compOpts
-- | Calls @ghc --print-libdir@, with just whatever's on the PATH.
runGhcCmdOnPath :: LogAction IO (WithSeverity Log) -> FilePath -> [String] -> IO (CradleLoadResult String)
runGhcCmdOnPath l wdir args = Process.readProcessWithCwd l wdir "ghc" args ""
-- | Log that the cradle has no supported for loading with context, if and only if
-- 'LoadFileWithContext' was requested.
logCradleHasNoSupportForLoadFileWithContext :: Applicative m => LogAction m (WithSeverity Log) -> LoadMode -> T.Text -> m ()
logCradleHasNoSupportForLoadFileWithContext l LoadFileWithContext crdlName =
l <& WithSeverity
(LogLoadModeUnsupported crdlName LoadFileWithContext
$ Just $ crdlName <> " doesn't support loading multiple components at once"
)
Info
logCradleHasNoSupportForLoadFileWithContext _ _ _ = pure ()