on master help sign in
Make hie-core outside an IDE work better (#1895)

* Move the hie-core demo files around (they aren't really a demo anymore) * Split the command line parsing into a separate module * Give messages about how long starting something takes * Make the interactive mode say what it is doing a bit more * Add a --cwd flag to hie-core * Take a list of files and directories for hie-core * Update the readme to say how to test using hie-core * Fix up the bazel file * Add HLint exception

commit72593a285da3023ffba822f6a4cf23a0d56cc878 authorNeil Mitchell <35463327+neil-da@users.noreply.github.com> dateWed 26 Jun 2019, 17:07 (7 years ago) parent876e07d
5 files changed+116−41
BUILD.bazel+4−3 README.md+25−15 exe/Arguments.hs+27 test/Demo.hs → exe/Main.hs+54−19 hie-core.cabal+6−4
BUILD.bazel+4−3
@@ -66,22 +66,23 @@ da_haskell_library(
6666)
6767
6868da_haskell_binary(
69− name = "hie-core-demo",
70− srcs = glob(["test/**/*.hs"]),
69+ name = "hie-core-exe",
70+ srcs = glob(["exe/**/*.hs"]),
7171 hazel_deps = [
7272 "base",
7373 "containers",
7474 "data-default",
7575 "directory",
7676 "extra",
77+ "filepath",
7778 "ghc-paths",
7879 "ghc",
7980 "haskell-lsp",
8081 "hie-bios",
82+ "optparse-applicative",
8183 "shake",
8284 "text",
8385 ],
84− main_function = "Demo.main",
8586 src_strip_prefix = "test",
8687 visibility = ["//visibility:public"],
8788 deps = [
README.md+25−15
@@ -10,7 +10,9 @@ Our vision is that you should build an IDE by combining:
1010
1111There are more details about our approach [in this blog post](https://4ta.uk/p/shaking-up-the-ide).
1212
13−## How to use it
13+## Using it
14+
15+### Install `hie-core`
1416
1517First install the `hie-core` binary using `stack` or `cabal`, e.g.
1618
@@ -20,9 +22,27 @@ First install the `hie-core` binary using `stack` or `cabal`, e.g.
2022
2123It's important that `hie-core` is compiled with the same compiler you use to build your projects.
2224
23−Next, check that `hie-bios` is able to load your project. This step is currently a bit difficult.
25+### Test `hie-core`
26+
27+Next, check that `hie-core` is capable of loading your code. Change to the project directory and run `hie-core`, which will try and load everything using the same code as the IDE, but in a way that's much easier to understand. For example, taking the example of [`shake`](https://github.com/ndmitchell/shake), running `hie-core` gives some error messages and warnings before reporting at the end:
28+
29+```
30+Files that worked: 152
31+Files that failed: 6
32+ * .\model\Main.hs
33+ * .\model\Model.hs
34+ * .\model\Test.hs
35+ * .\model\Util.hs
36+ * .\output\docs\Main.hs
37+ * .\output\docs\Part_Architecture_md.hs
38+Done
39+```
40+
41+Of the 158 files in Shake, as of this moment, 152 can be loaded by the IDE, but 6 can't (error messages for the reasons they can't be loaded are given earlier). The failing files are all prototype work or test output, meaning I can confidently use Shake.
42+
43+The `hie-core` executable mostly relies on [`hie-bios`](https://github.com/mpickering/hie-bios) to do the difficult work of setting up your GHC environment. If it doesn't work, see [the `hie-bios` manual](https://github.com/mpickering/hie-bios#readme) to get it working. My default fallback is to figure it out by hand and create a `direct` style [`hie.yaml`](https://github.com/ndmitchell/shake/blob/master/hie.yaml) listing the command line arguments to load the project.
2444
25−Next, set up an extension for your editor.
45+Once you have got `hie-core` working outside the editor, the next step is to pick which editor to integrate with.
2646
2747### Using with VS Code
2848
@@ -36,7 +56,7 @@ Install the VS code extension
3656
3757Now openning a `.hs` file should work with `hie-core`.
3858
39−### Emacs
59+### Using with Emacs
4060
4161The frst step is to install required Emacs packages. If you don't already have [Melpa](https://melpa.org/#/) package installation configured in your `.emacs`, put this stanza at the top.
4262
@@ -63,7 +83,7 @@ There are two things you can do about this warning:
6383;; Remember : to avoid package-not-found errors, refresh the package
6484;; database now and then with M-x package-refresh-contents.
6585```
66−
86+
6787When this is in your `.emacs` and evaluated, `M-x package-refresh-contents` to get the package database downloaded and then `M-x package-list-packages` to display the available packages. Click on a package to install it. You'll need to install the following packages:
6888
6989* `lsp-haskell`
@@ -93,13 +113,3 @@ Optionally, you may wish to add the following conveniences:
93113(define-key flymake-mode-map (kbd "M-n") 'flymake-goto-next-error)
94114(define-key flymake-mode-map (kbd "M-p") 'flymake-goto-prev-error)
95115```
96−
97−### Testing
98−
99−For testing, I've been using the `ghc-lib-gen` target of the [`ghc-lib` project](https://github.com/digital-asset/ghc-lib). Navigate to the root of `ghc-lib` and create an `hie.yaml` file with contents
100−
101−```yaml
102−cradle: {cabal: {component: "exe:ghc-lib-gen"}}
103−```
104−
105−Invoke `cabal new-configure -w ~/.stack/programs/~/.stack/programs/x86_64-osx/ghc-8.6.5/bin/ghc` (this is the `ghc` used by `stack` to build `hie-core` - consult `//compiler/hie-core/stack.yaml` to help work out what you should write here). This last step will create a file `cabal.project.local` with contents pointing `cabal` to use the desired `ghc`. You can build `ghc-lib-gen` from the `ghc-lib` directory with the command `cabal new-build` as you like. After creating `cabal.project.local`, you should be all set. Open `ghc-lib/ghc-lib-gen/src/Main.hs` in a buffer and, for example, hover should bring up type/definition info.
exe/Arguments.hsnew file+27
@@ -0,0 +1,27 @@
1+-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2+-- SPDX-License-Identifier: Apache-2.0
3+
4+module Arguments(Arguments(..), getArguments) where
5+
6+import Options.Applicative
7+
8+
9+data Arguments = Arguments
10+ {argLSP :: Bool
11+ ,argsCwd :: Maybe FilePath
12+ ,argFiles :: [FilePath]
13+ }
14+
15+getArguments :: IO Arguments
16+getArguments = execParser opts
17+ where
18+ opts = info (arguments <**> helper)
19+ ( fullDesc
20+ <> progDesc "Used as a test bed to check your IDE will work"
21+ <> header "hie-core - the core of a Haskell IDE")
22+
23+arguments :: Parser Arguments
24+arguments = Arguments
25+ <$> switch (long "lsp" <> help "Start talking to an LSP server")
26+ <*> optional (strOption $ long "cwd" <> metavar "DIR" <> help "Change to this directory")
27+ <*> many (argument str (metavar "FILES/DIRS..."))
test/Demo.hs → exe/Main.hsrenamed+54−19
@@ -1,11 +1,14 @@
11-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
22-- SPDX-License-Identifier: Apache-2.0
33
4−module Demo(main) where
4+module Main(main) where
55
6+import Arguments
67import Data.Maybe
8+import Data.List.Extra
9+import System.FilePath
710import Control.Concurrent.Extra
8−import Control.Monad
11+import Control.Monad.Extra
912import Data.Default
1013import System.Time.Extra
1114import Development.IDE.Core.FileStore
@@ -22,7 +25,7 @@ import Development.IDE.Types.Logger
2225import qualified Data.Text.IO as T
2326import Language.Haskell.LSP.Messages
2427import Development.IDE.LSP.LanguageServer
25−import System.Directory
28+import System.Directory.Extra as IO
2629import System.Environment
2730import System.IO
2831import Development.Shake hiding (Env)
@@ -44,36 +47,68 @@ main :: IO ()
4447main = do
4548 -- WARNING: If you write to stdout before runLanguageServer
4649 -- then the language server will not work
47− hPutStrLn stderr "Starting hie-core Demo"
48− args <- getArgs
50+ hPutStrLn stderr "Starting hie-core"
51+ Arguments{..} <- getArguments
52+
4953 -- lock to avoid overlapping output on stdout
5054 lock <- newLock
5155 let logger = makeOneLogger $ withLock lock . T.putStrLn
5256
57+ whenJust argsCwd setCurrentDirectory
58+
5359 dir <- getCurrentDirectory
5460 hPutStrLn stderr dir
5561
56− cradle <- findCradle (dir <> "/")
57−
58− let options = defaultIdeOptions $ liftIO $ newSession' cradle
59−
60− if "--lsp" `elem` args then do
61− hPutStrLn stderr "Starting IDE server"
62+ if argLSP then do
63+ t <- offsetTime
64+ hPutStrLn stderr "Starting LSP server..."
6265 runLanguageServer def def $ \event vfs -> do
63− hPutStrLn stderr "Server started"
66+ t <- t
67+ hPutStrLn stderr $ "Started LSP server in " ++ showDuration t
68+ let options = defaultIdeOptions $ liftIO $ newSession' =<< findCradle (dir <> "/")
6469 initialise (mainRule >> action kick) event logger options vfs
6570 else do
66− let files = map toNormalizedFilePath $ filter (/= "--lsp") args
71+ putStrLn "[1/6] Finding hie-bios cradle"
72+ cradle <- findCradle (dir <> "/")
73+ print cradle
74+
75+ putStrLn "\n[2/6] Converting Cradle to GHC session"
76+ env <- newSession' cradle
77+
78+ putStrLn "\n[3/6] Initialising IDE session"
6779 vfs <- makeVFSHandle
68− ide <- initialise mainRule (showEvent lock) logger options vfs
69− setFilesOfInterest ide $ Set.fromList files
70− runAction ide kick
71− -- shake now writes an async message that it is completed with timing info,
72− -- so we sleep briefly to wait for it to have been written
73− sleep 0.01
80+ ide <- initialise mainRule (showEvent lock) logger (defaultIdeOptions $ return env) vfs
81+
82+ putStrLn "\n[4/6] Finding interesting files"
83+ files <- nubOrd <$> expandFiles (argFiles ++ ["." | null argFiles])
84+ putStrLn $ "Found " ++ show (length files) ++ " files"
85+
86+ putStrLn "\n[5/6] Setting interesting files"
87+ setFilesOfInterest ide $ Set.fromList $ map toNormalizedFilePath files
88+
89+ putStrLn "\n[6/6] Loading interesting files"
90+ results <- runActionSync ide $ uses TypeCheck $ map toNormalizedFilePath files
91+ let (worked, failed) = partition fst $ zip (map isJust results) files
92+ putStrLn $ "Files that worked: " ++ show (length worked)
93+ putStrLn $ "Files that failed: " ++ show (length failed)
94+ putStr $ unlines $ map ((++) " * " . snd) failed
95+
7496 putStrLn "Done"
7597
7698
99+expandFiles :: [FilePath] -> IO [FilePath]
100+expandFiles = concatMapM $ \x -> do
101+ b <- IO.doesFileExist x
102+ if b then return [x] else do
103+ let recurse "." = True
104+ recurse x | "." `isPrefixOf` takeFileName x = False -- skip .git etc
105+ recurse x = takeFileName x `notElem` ["dist","dist-newstyle"] -- cabal directories
106+ files <- filter (\x -> takeExtension x `elem` [".hs",".lhs"]) <$> listFilesInside (return . recurse) x
107+ when (null files) $
108+ fail $ "Couldn't find any .hs/.lhs files inside directory: " ++ x
109+ return files
110+
111+
77112kick :: Action ()
78113kick = do
79114 files <- getFilesOfInterest
hie-core.cabal+6−4
@@ -103,25 +103,27 @@ library
103103
104104executable hie-core
105105 default-language: Haskell2010
106− main-is: Demo.hs
107− ghc-options: -main-is Demo.main
106+ hs-source-dirs: exe
107+ main-is: Main.hs
108108 build-depends:
109109 base == 4.*,
110110 containers,
111111 directory,
112+ optparse-applicative,
112113 hie-bios,
113114 shake,
114115 data-default,
115116 ghc-paths,
116117 ghc,
117118 extra,
119+ filepath,
118120 haskell-lsp,
119121 text,
120122 hie-core
123+ other-modules:
124+ Arguments
121125
122126 default-extensions:
123127 TupleSections
124128 RecordWildCards
125129 ViewPatterns
126−
127− hs-source-dirs: test