Skip to content
This repository has been archived by the owner on Oct 7, 2020. It is now read-only.

Load all possible haskell source files #1569

Merged
merged 5 commits into from
Jan 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions app/MainHie.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import qualified Control.Exception as E
import Control.Monad
import Data.Monoid ((<>))
import Data.Version (showVersion)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Yaml as Yaml
import HIE.Bios.Types
import Haskell.Ide.Engine.Cradle (findLocalCradle, cradleDisplay)
import Haskell.Ide.Engine.Cradle (findLocalCradle, cradleDisplay, getProjectGhcLibDir)
import Haskell.Ide.Engine.MonadFunctions
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.Options
Expand All @@ -20,11 +22,15 @@ import Options.Applicative.Simple
import qualified Paths_haskell_ide_engine as Meta
import System.Directory
import System.Environment
import System.FilePath ((</>))
import System.FilePath
import System.Info
import System.IO
import qualified System.Log.Logger as L

-- ---------------------------------------------------------------------

import RunTest

-- ---------------------------------------------------------------------
-- plugins

Expand Down Expand Up @@ -117,6 +123,8 @@ run opts = do
progName <- getProgName
args <- getArgs

let plugins' = plugins (optExamplePlugin opts)

if optLsp opts
then do
-- Start up in LSP mode
Expand All @@ -136,8 +144,6 @@ run opts = do
when (optExamplePlugin opts) $
logm "Enabling Example2 plugin, will insert constant diagnostics etc."

let plugins' = plugins (optExamplePlugin opts)

-- launch the dispatcher.
scheduler <- newScheduler plugins' initOpts
server scheduler origDir plugins' (optCaptureFile opts)
Expand All @@ -155,7 +161,35 @@ run opts = do
ecradle <- getCradleInfo origDir
case ecradle of
Left e -> cliOut $ "Could not get cradle:" ++ show e
Right cradle -> cliOut $ "Cradle:" ++ cradleDisplay cradle
Right cradle -> do
projGhc <- getProjectGhcVersion cradle
mlibdir <- getProjectGhcLibDir cradle
cliOut "\n\n###################################################\n"
cliOut $ "Cradle: " ++ cradleDisplay cradle
cliOut $ "Project Ghc version: " ++ projGhc
cliOut $ "Libdir: " ++ show mlibdir
cliOut "Searching for Haskell source files..."
targets <- case optFiles opts of
[] -> findAllSourceFiles origDir
xs -> concat <$> mapM findAllSourceFiles xs

cliOut $ "Found " ++ show (length targets) ++ " Haskell source files.\n"
cliOut "###################################################"
cliOut "\nFound the following files:\n"
mapM_ cliOut targets
cliOut ""

unless (optDryRun opts) $ do
cliOut "\nLoad them all now. This may take a very long time.\n"
loadDiagnostics <- runServer mlibdir plugins' targets

cliOut ""
cliOut "###################################################"
cliOut "###################################################"
cliOut "\nDumping diagnostics:\n\n"
mapM_ (cliOut' . uncurry prettyPrintDiags) loadDiagnostics
cliOut "\n\nNote: loading of 'Setup.hs' is not supported."


-- ---------------------------------------------------------------------

Expand All @@ -170,4 +204,7 @@ getCradleInfo currentDir = do
cliOut :: String -> IO ()
cliOut = putStrLn

cliOut' :: T.Text -> IO ()
cliOut' = T.putStrLn

-- ---------------------------------------------------------------------
128 changes: 128 additions & 0 deletions app/RunTest.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
module RunTest
( findAllSourceFiles
, compileTarget
, runServer
, prettyPrintDiags
)
where

import GhcMonad
import qualified GHC
import Control.Monad
import qualified Control.Concurrent.STM as STM
import Data.List ( isPrefixOf )
import qualified Data.Text as T
import qualified Data.Map as Map
import Data.Default
import System.Directory ( doesDirectoryExist
, listDirectory
, canonicalizePath
, doesFileExist
)
import System.FilePath
import Language.Haskell.LSP.Core
import Language.Haskell.LSP.Types
import Haskell.Ide.Engine.PluginsIdeMonads
hiding ( withIndefiniteProgress
, withProgress
)
import Haskell.Ide.Engine.GhcModuleCache
import qualified Haskell.Ide.Engine.ModuleCache
as MC
import qualified Haskell.Ide.Engine.Ghc as Ghc

findAllSourceFiles :: FilePath -> IO [FilePath]
findAllSourceFiles fp = do
absFp <- canonicalizePath fp
isDir <- doesDirectoryExist absFp
if isDir
then findFilesRecursively
isHaskellSource
(\path -> any (\p -> p path) [isHidden, isSpecialDir])
absFp
else filterM doesFileExist [absFp]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silently discarding unknown filepaths does not seem pretty, but neither does throwing an exception.

where
isHaskellSource = (== ".hs") . takeExtension
isHidden = ("." `isPrefixOf`) . takeFileName
isSpecialDir = (== "dist-newstyle") . takeFileName

findFilesRecursively
:: (FilePath -> Bool) -> (FilePath -> Bool) -> FilePath -> IO [FilePath]
findFilesRecursively p exclude dir = do
dirContents' <- listDirectory dir
let dirContents = map (dir </>) dirContents'

files <- forM dirContents $ \fp -> do
isDirectory <- doesDirectoryExist fp
if isDirectory
then if not $ exclude fp
then findFilesRecursively p exclude fp
else return []
else if p fp then return [fp] else return []

return $ concat files


-- ---------------------------------------------------------------------

compileTarget
:: GHC.DynFlags
-> FilePath
-> IdeGhcM (IdeResult (Ghc.Diagnostics, Ghc.AdditionalErrs))
compileTarget dynFlags fp = do
let pubDiags _ _ _ = return ()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capturing this could make sense.

let defAction = return (mempty, mempty)
let action = Ghc.setTypecheckedModule (filePathToUri fp)
actionResult <- MC.runActionWithContext pubDiags
dynFlags
(Just fp)
defAction
action
return $ join actionResult

-- ---------------------------------------------------------------------

runServer
:: Maybe FilePath
-> IdePlugins
-> [FilePath]
-> IO [(FilePath, IdeResult (Ghc.Diagnostics, Ghc.AdditionalErrs))]
runServer mlibdir ideplugins targets = do
let initialState = IdeState emptyModuleCache Map.empty Map.empty Nothing
stateVar <- STM.newTVarIO initialState

runIdeGhcM mlibdir ideplugins dummyLspFuncs stateVar $ do
dynFlags <- getSessionDynFlags
mapM (\fp -> (fp, ) <$> compileTarget dynFlags fp) targets

-- ---------------------------------------------------------------------

prettyPrintDiags
:: FilePath -> IdeResult (Ghc.Diagnostics, Ghc.AdditionalErrs) -> T.Text
prettyPrintDiags fp diags = T.pack fp <> ": " <> case diags of
IdeResultFail IdeError { ideMessage } -> "FAILED\n\t" <> ideMessage
IdeResultOk (_diags, errs) ->
if null errs then "OK" else T.unlines (map (T.append "\t") errs)

-- ---------------------------------------------------------------------

dummyLspFuncs :: Default a => LspFuncs a
dummyLspFuncs = LspFuncs
{ clientCapabilities = def
, config = return (Just def)
, sendFunc = const (return ())
, getVirtualFileFunc = const (return Nothing)
, persistVirtualFileFunc = \uri ->
return (uriToFilePath (fromNormalizedUri uri))
, reverseFileMapFunc = return id
, publishDiagnosticsFunc = mempty
, flushDiagnosticsBySourceFunc = mempty
, getNextReqId = pure (IdInt 0)
, rootPath = Nothing
, getWorkspaceFolders = return Nothing
, withProgress = \_ _ f -> f (const (return ()))
, withIndefiniteProgress = \_ _ f -> f
}
8 changes: 7 additions & 1 deletion haskell-ide-engine.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,23 @@ library
executable hie
hs-source-dirs: app
main-is: MainHie.hs
other-modules: Paths_haskell_ide_engine
other-modules: Paths_haskell_ide_engine, RunTest
autogen-modules: Paths_haskell_ide_engine
build-depends: base
, containers
, data-default
, directory
, filepath
, ghc
, hie-bios
, haskell-ide-engine
, haskell-lsp
, haskell-lsp-types
, hie-plugin-api
, hslogger
, optparse-simple
, stm
, text
, yaml
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wredundant-constraints
-with-rtsopts=-T
Expand Down
14 changes: 13 additions & 1 deletion src/Haskell/Ide/Engine/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import Options.Applicative.Simple
data GlobalOpts = GlobalOpts
{ optDebugOn :: Bool
, optLogFile :: Maybe String
, optLsp :: Bool -- Kept for a while, to not break legacy clients
, optLsp :: Bool
, projectRoot :: Maybe String
, optBiosVerbose :: Bool
, optCaptureFile :: Maybe FilePath
, optExamplePlugin :: Bool
, optDryRun :: Bool
, optFiles :: [FilePath]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better name required.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optSanityCheck? optProjectLoadCheck?

Can you list directories as well, in optFiles, and have the recursive contents checked?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. If it is a directory, it is recursed into it and if it is a FilePath, its existence is checked and filtered if it does not exist. Which is maybe not the desired behaviour.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following question remain for me:
Should --dry-run be the default? So that it doesnt by default try to load every file in the project?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's that dangerous to load every file in the project, it's not like its destructively editing anything. I'm happy if it loads them by default

} deriving (Show)

globalOptsParser :: Parser GlobalOpts
Expand Down Expand Up @@ -53,3 +55,13 @@ globalOptsParser = GlobalOpts
<*> switch
( long "example"
<> help "Enable Example2 plugin. Useful for developers only")
<*> flag False True
( long "dry-run"
<> help "Perform a dry-run of loading files. Only searches for Haskell source files to load. Does nothing if run as LSP server."
)
<*> many
( argument str
( metavar "FILES..."
<> help "Directories and Filepaths to load. Does nothing if run as LSP server.")
)