packages feed

hie-bios 0.5.1 → 0.6.0

raw patch · 65 files changed

+1412/−417 lines, 65 filesdep +hspec-expectationsdep +tasty-expected-failuredep ~temporaryPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec-expectations, tasty-expected-failure

Dependency ranges changed: temporary

API changes (from Hackage documentation)

- HIE.Bios.Config: [depsProg] :: CradleType a -> Maybe FilePath
- HIE.Bios.Config: [prog] :: CradleType a -> FilePath
- HIE.Bios.Environment: getSystemLibDir :: IO (Maybe FilePath)
- HIE.Bios.Ghc.Api: getSystemLibDir :: IO (Maybe FilePath)
- HIE.Bios.Ghc.Api: withGHC :: FilePath -> Ghc a -> IO a
- HIE.Bios.Ghc.Api: withGHC' :: Ghc a -> IO a
- HIE.Bios.Ghc.Api: withGhcT :: (ExceptionMonad m, MonadIO m, Monad m) => GhcT m a -> m a
+ HIE.Bios: [cradleErrorDependencies] :: CradleError -> [FilePath]
+ HIE.Bios: [cradleErrorExitCode] :: CradleError -> ExitCode
+ HIE.Bios: [cradleErrorStderr] :: CradleError -> [String]
+ HIE.Bios.Config: Command :: String -> Callable
+ HIE.Bios.Config: Program :: FilePath -> Callable
+ HIE.Bios.Config: [call] :: CradleType a -> Callable
+ HIE.Bios.Config: [depsCall] :: CradleType a -> Maybe Callable
+ HIE.Bios.Config: data Callable
+ HIE.Bios.Config: instance GHC.Classes.Eq HIE.Bios.Config.Callable
+ HIE.Bios.Config: instance GHC.Show.Show HIE.Bios.Config.Callable
+ HIE.Bios.Environment: getRuntimeGhcLibDir :: Cradle a -> IO (CradleLoadResult FilePath)
+ HIE.Bios.Environment: getRuntimeGhcVersion :: Cradle a -> IO (CradleLoadResult String)
+ HIE.Bios.Environment: makeTargetsAbsolute :: FilePath -> [Target] -> [Target]
+ HIE.Bios.Types: [cradleErrorDependencies] :: CradleError -> [FilePath]
+ HIE.Bios.Types: [cradleErrorExitCode] :: CradleError -> ExitCode
+ HIE.Bios.Types: [cradleErrorStderr] :: CradleError -> [String]
+ HIE.Bios.Types: [runGhcCmd] :: CradleAction a -> [String] -> IO (CradleLoadResult String)
+ HIE.Bios.Types: instance GHC.Classes.Eq HIE.Bios.Types.CradleError
+ HIE.Bios.Types: instance GHC.Classes.Eq r => GHC.Classes.Eq (HIE.Bios.Types.CradleLoadResult r)
- HIE.Bios: CradleError :: ExitCode -> [String] -> CradleError
+ HIE.Bios: CradleError :: [FilePath] -> ExitCode -> [String] -> CradleError
- HIE.Bios.Config: Bios :: FilePath -> Maybe FilePath -> CradleType a
+ HIE.Bios.Config: Bios :: Callable -> Maybe Callable -> CradleType a
- HIE.Bios.Cradle: readProcessWithOutputFile :: LoggingFunction -> FilePath -> FilePath -> [String] -> IO (ExitCode, [String], [String], [String])
+ HIE.Bios.Cradle: readProcessWithOutputFile :: LoggingFunction -> FilePath -> CreateProcess -> IO (ExitCode, [String], [String], [String])
- HIE.Bios.Types: CradleAction :: ActionName a -> (LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions)) -> CradleAction a
+ HIE.Bios.Types: CradleAction :: ActionName a -> (LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions)) -> ([String] -> IO (CradleLoadResult String)) -> CradleAction a
- HIE.Bios.Types: CradleError :: ExitCode -> [String] -> CradleError
+ HIE.Bios.Types: CradleError :: [FilePath] -> ExitCode -> [String] -> CradleError

Files

− ChangeLog
@@ -1,93 +0,0 @@-2020-06-26 - 0.5.1--	* Fix printing of current directory in wrapper script [#206](https://github.com/mpickering/hie-bios/pull/206)-	* Export Cradle utilizes [#189](https://github.com/mpickering/hie-bios/pull/189)--2020-05-08 - 0.5.0--	* Add cabal.project.local to cabal cradle dependencies [#184](https://github.com/mpickering/hie-bios/pull/184)-	* Remove unused environment variables to simplify code. [#182](https://github.com/mpickering/hie-bios/pull/182)-	* Clean up hie-bios wrapper scripts after they are used. [#179](https://github.com/mpickering/hie-bios/pull/179)-	* Avoid error in windows due to temp file being locked. [#175](https://github.com/mpickering/hie-bios/pull/175)-	* Get building with ghc-8.10. [#173](https://github.com/mpickering/hie-bios/pull/173)-	* Add getCompilerOptionsWithLogger convenience function.-	* Add componentRoot to ComponentOptions. [#166](https://github.com/mpickering/hie-bios/pull/166)-		Options may be relative to the componentRoot.-	* Add makeDynFlagsAbsolute to fix mangling of ghc options starting with "-i". [#166](https://github.com/mpickering/hie-bios/pull/166)-		Breaks backwards-compatibility, because ComponentOptions now may contain-		filepaths relative to the component root directory.-		This function needs to be invoked on the parsed 'DynFlags' to normalise the filepaths.-	* Fix Ghci Script parses space in Filepath as Module (#162)-	* Correct path to .hie-bios example in readme (#159)-	* Relax upper bound for 'extra' (#161)--2020-01-29 - 0.4.0--	* Return CompilerOptions in initialization (#130)-	* Implement hook into config parser (#131)-	* Enable GHC 8.8.1 windows ci (#128)-	* Catch permission errors in cradle discovery (#127)-	* Add explicit cradle predicates and multi cradle depend on its cradles (#119)-	* Fix outdated direct cradle in README (#124)-	* Pass filepath to cabal v2-repl when getting flags (#123)-	* CPP for GHC 8.10 compatibility (#134)-	* Derive Ord for ComponentOptions (#133)-	* Lower the required version of the GHC dependency (#138)-	* Add tests for implicit cradles (#135)-	* Add Functor instance for Cradle and ActionName (#140)-	* Remove Show instance from public API (#146)-	* Add Show instance for CradleLoadResult (#145)-	* Typo in debug message (#144)-	* Add lower bound for aeson and clean-up API (#142)--2019-12-19 - 0.3.2--	* Compile windows wrapper script in a a more appropiate directory. (#109)-	* Fix situation in wrapper script when environmental variable wasn't set. (#109)--2019-12-18 - 0.3.1--	* Fix bug in the windows wrapper script (#108)---2019-12-15 - 0.3.0--	* Add multi cradle, cabal multi cradle and none cradle-	* Remove obelisk, bazel and default cradle types-	* bios program now expects arguments to be separated by newlines rather than-		spaces. (#80)-	* Only try to use stack cradle if `stack` is executable.-	* Filter out `-w -v0` from cabal output when using cabal cradle.-	* Initialise plugins when loading a module.-	* Interface file cache persists between loads -- this greatly speeds up-	reloading a project if the options don't change.-	* Reuse wrapper executable on windows if one already exists.-	* Make stack cradle work more like the cabal cradle-		- Syntax for specifying a specific component-		- Targets are read from the ghci script file-	* Cradles now use a temporary file to communicate arguments to hie-bios.-	bios cradles should consult the HIE_BIOS_OUTPUT envvar for the filepath to-	write the arguments seperated by newlines.--2019-09-19 - 0.2.1--	* Make stack cradle use the same wrappers as cabal cradle. Fixes some issues-		on windows.--2019-09-18 - 0.2.0--	* Compat with 8.2 and 8.8-	* Add support for explicitly specifying dependencies for a cradle-	* Separate arguments by null bytes, so arguments can contain spaces-	(cabal/stack wrapper)-	* Add --help to CLI-	* Fix the directories that certain processes run in--2019-09-07 - 0.1.1--	* Compat with GHC 8.4-	* Fix long paths issue on windows-	* Handle projects with .o files--2019-09-06 - 0.1.0-	* First release
+ ChangeLog.md view
@@ -0,0 +1,117 @@+# ChangeLog hie-bios++## 2020-07-12 - 0.6.0++### New Features:++* Add getRuntimeGhcLibDir and getRuntimeGhcVersion functions through a new runGhcCmd API [#207](https://github.com/mpickering/hie-bios/pull/207) [#224](https://github.com/mpickering/hie-bios/pull/224)++### Bug Fixes:++* Improve the README [#225](https://github.com/mpickering/hie-bios/pull/225)+* Detect implicit cabal cradle in the absence of cabal.project [#221](https://github.com/mpickering/hie-bios/pull/221)+* Dont resolve symlinks in cradle discovery [#219](https://github.com/mpickering/hie-bios/pull/219)+* Make Cradle dependencies for stack and cabal more reasonable [#209](https://github.com/mpickering/hie-bios/pull/209)+* This ships with a known bug: `stack` lists cradle dependencies from+	sub-directories incorrectly.+* Fix absolute mains [#205](https://github.com/mpickering/hie-bios/pull/205)+* Improve filtering of rts arguments from stack and cabal cradles [#197](https://github.com/mpickering/hie-bios/pull/197)+* Make package db paths absolute [#193](https://github.com/mpickering/hie-bios/pull/193)+* Add shell and dependency-shell attributes to bios cradle type [#188](https://github.com/mpickering/hie-bios/pull/188)+* Store dependencies in CradleError [#186](https://github.com/mpickering/hie-bios/pull/186)+* Add cabal.project.local to cabal cradle dependencies [#184](https://github.com/mpickering/hie-bios/pull/184)+* Remove outdated reference to $HIE_BIOS_GHC[_ARGS]++## 2020-06-26 - 0.5.1++* Fix printing of current directory in wrapper script [#206](https://github.com/mpickering/hie-bios/pull/206)+* Export Cradle utilizes [#189](https://github.com/mpickering/hie-bios/pull/189)++## 2020-05-08 - 0.5.0++* Add cabal.project.local to cabal cradle dependencies [#184](https://github.com/mpickering/hie-bios/pull/184)+* Remove unused environment variables to simplify code. [#182](https://github.com/mpickering/hie-bios/pull/182)+* Clean up hie-bios wrapper scripts after they are used. [#179](https://github.com/mpickering/hie-bios/pull/179)+* Avoid error in windows due to temp file being locked. [#175](https://github.com/mpickering/hie-bios/pull/175)+* Get building with ghc-8.10. [#173](https://github.com/mpickering/hie-bios/pull/173)+* Add getCompilerOptionsWithLogger convenience function.+* Add componentRoot to ComponentOptions. [#166](https://github.com/mpickering/hie-bios/pull/166)+Options may be relative to the componentRoot.+* Add makeDynFlagsAbsolute to fix mangling of ghc options starting with "-i". [#166](https://github.com/mpickering/hie-bios/pull/166)+Breaks backwards-compatibility, because ComponentOptions now may contain+filepaths relative to the component root directory.+This function needs to be invoked on the parsed 'DynFlags' to normalise the filepaths.+* Fix Ghci Script parses space in Filepath as Module (#162)+* Correct path to .hie-bios example in readme (#159)+* Relax upper bound for 'extra' (#161)++## 2020-01-29 - 0.4.0++* Return CompilerOptions in initialization (#130)+* Implement hook into config parser (#131)+* Enable GHC 8.8.1 windows ci (#128)+* Catch permission errors in cradle discovery (#127)+* Add explicit cradle predicates and multi cradle depend on its cradles (#119)+* Fix outdated direct cradle in README (#124)+* Pass filepath to cabal v2-repl when getting flags (#123)+* CPP for GHC 8.10 compatibility (#134)+* Derive Ord for ComponentOptions (#133)+* Lower the required version of the GHC dependency (#138)+* Add tests for implicit cradles (#135)+* Add Functor instance for Cradle and ActionName (#140)+* Remove Show instance from public API (#146)+* Add Show instance for CradleLoadResult (#145)+* Typo in debug message (#144)+* Add lower bound for aeson and clean-up API (#142)++## 2019-12-19 - 0.3.2++* Compile windows wrapper script in a a more appropiate directory. (#109)+* Fix situation in wrapper script when environmental variable wasn't set. (#109)++## 2019-12-18 - 0.3.1++* Fix bug in the windows wrapper script (#108)++## 2019-12-15 - 0.3.0++* Add multi cradle, cabal multi cradle and none cradle+* Remove obelisk, bazel and default cradle types+* bios program now expects arguments to be separated by newlines rather than+spaces. (#80)+* Only try to use stack cradle if `stack` is executable.+* Filter out `-w -v0` from cabal output when using cabal cradle.+* Initialise plugins when loading a module.+* Interface file cache persists between loads -- this greatly speeds up+reloading a project if the options don't change.+* Reuse wrapper executable on windows if one already exists.+* Make stack cradle work more like the cabal cradle+- Syntax for specifying a specific component+- Targets are read from the ghci script file+* Cradles now use a temporary file to communicate arguments to hie-bios.+bios cradles should consult the HIE_BIOS_OUTPUT envvar for the filepath to+write the arguments seperated by newlines.++## 2019-09-19 - 0.2.1++* Make stack cradle use the same wrappers as cabal cradle. Fixes some issues+on windows.++## 2019-09-18 - 0.2.0++* Compat with 8.2 and 8.8+* Add support for explicitly specifying dependencies for a cradle+* Separate arguments by null bytes, so arguments can contain spaces+(cabal/stack wrapper)+* Add --help to CLI+* Fix the directories that certain processes run in++## 2019-09-07 - 0.1.1++* Compat with GHC 8.4+* Fix long paths issue on windows+* Handle projects with .o files++## 2019-09-06 - 0.1.0++* First release
README.md view
@@ -1,12 +1,40 @@ # hie-bios +- [hie-bios](#hie-bios)+  - [Explicit Configuration](#explicit-configuration)+    - [Stack](#stack)+      - [Debugging a `stack` cradle](#debugging-a-stack-cradle)+      - [Ignoring directories](#ignoring-directories)+      - [Internal Libraries](#internal-libraries)+    - [Cabal](#cabal)+      - [Debugging a `cabal` cradle](#debugging-a-cabal-cradle)+      - [Ignoring directories](#ignoring-directories-1)+    - [Bios](#bios)+      - [Debugging a `bios` cradle](#debugging-a-bios-cradle)+    - [Direct](#direct)+      - [Debugging a `direct` cradle](#debugging-a-direct-cradle)+    - [None](#none)+  - [Multi-Cradle](#multi-cradle)+  - [Cradle Dependencies](#cradle-dependencies)+  - [Configuration specification](#configuration-specification)+  - [Testing your configuration](#testing-your-configuration)+  - [Implicit Configuration](#implicit-configuration)+    - [Priority](#priority)+    - [`cabal-install`](#cabal-install)+    - [`stack`](#stack-1)+    - [`bios`](#bios-1)+  - [Supporting Bazel and Obelisk](#supporting-bazel-and-obelisk)+ `hie-bios` is the way to specify how-[`hie`](https://github.com/haskell/haskell-ide-engine) and-[`ghcide`](https://github.com/digital-asset/ghcide) sets up a GHC API session.+[`haskell-language-server`](https://github.com/haskell/haskell-language-server)+and [`ghcide`](https://github.com/digital-asset/ghcide) set up a GHC API session.  Given a Haskell project that is managed by Stack, Cabal, or other package tools,-`hie` needs to know the full set of flags to pass to GHC in order to build the-project. `hie-bios` satisfies this need.+`haskell-language-server` needs to know the full set of flags to pass to GHC in+order to build the project. These flags might contain some compilation options+like `-O2`, but a lot of the time they are package dependencies such as+`-package-id directory-1.3.6.0`, which also need to be built beforehand.+`hie-bios` satisfies both these needs.  Its design is motivated by the guiding principle: @@ -16,65 +44,347 @@ Using this principle, it is possible to easily support a wide range of tools including `cabal-install`, `stack`, `rules_haskell`, `hadrian` and `obelisk` without major contortions.-`hie-bios` does not depend on the `Cabal` library nor does not+`hie-bios` does not depend on the `Cabal` library nor does it read any complicated build products and so on.  How does a tool specify a session? A session is fully specified by a set of standard GHC flags. Most tools already produce this information if they support a `repl` command. Launching a repl is achieved by calling `ghci` with the right flags to specify the package database. `hie-bios` needs a way to get-these flags and then it can set up GHC API session correctly.+these flags and then it can set up a GHC API session correctly. -Futher it means that any failure to set up the API session is the responsibility+Further it means that any failure to set up the API session is the responsibility of the build tool. It is up to them to provide the correct information if they want the tool to work correctly.  ## Explicit Configuration  The user can place a `hie.yaml` file in the root of the workspace which-describes how to setup the environment. For example, to explicitly state-that you want to use `stack` then the configuration file would look like:+describes how to set up the environment. There are several supported ways to setup the environment. +### Stack++To explicitly state that you want to use `stack`, the basic configuration `hie.yaml` would look like:+ ```yaml-cradle: {stack: {component: "haskell-ide-engine:lib" }}+cradle:+  stack: ``` -While the component is optional, this is recommended to make sure the correct-component is loaded.+This configuration suffices if your whole project can be loaded by the command `stack repl`. As a rule of thumb, this works if the project consists of only one executable, one library and one test-suite. -To use `cabal`, the explicit configuration looks similar.-Note that `cabal` and `stack` have different way of specifying their+If your project is more complicated, you need to specify which [components](https://docs.haskellstack.org/en/stable/build_command/#components) you want to load. A component is, roughly speaking, a library/executable/test-suite or benchmark in `stack`. You can view the components/targets of a stack project by executing the command:+``` sh+$ stack ide targets+```++Since we have two test-suites, one executable and a library, for `hie-bios`, this would output the following:++``` sh+$ stack ide targets+hie-bios:lib+hie-bios:exe:hie-bios+hie-bios:test:bios-tests+hie-bios:test:parser-tests+```++For an explanation of the target syntax, we refer to the documentation of the [target syntax](https://docs.haskellstack.org/en/stable/build_command/#target-syntax).++To tell `hie-bios` which component it should load, the following `hie.yaml` can be used:++```yaml+cradle:+  stack:+    component: "<component name>"+```++where `<component name>` is the name of the component/target you want to load.+While the component is optional, this is recommended to make sure the correct component is loaded.++Why is this not enough? Usually, you have multiple components with different dependencies. Your library won't depend on `tasty` or `hspec`, but your test-suite might. With this setup, you would only be able to load files from the given component.++Since you rarely only want to load a single component in a `stack` project, we have special syntax to be able to conveniently specify which directory belongs to which component. It is basically a [multi-cradle](#multi-cradle).++```yaml+cradle:+  stack:+    - path: "./src"+      component: "hie-bios:lib"+    - path: "./exe"+      component: "hie-bios:exe:hie-bios"+    - path: "./tests/BiosTests.hs"+      component: "hie-bios:test:hie-bios"+    - path: "./tests/ParserTests.hs"+      component: "hie-bios:test:parser-tests"+```++Here you can see two important features:+  * We provide a mapping from a filepath to component.+    * That way, we specify that a file such as `./src/HIE/Bios.hs` belongs to the component `hie-bios:lib`.+  * The filepath can be a file.+    * This is convenient if components are overlapping.++This way we specified which component needs to be compiled given a source file for our whole project.++#### Debugging a `stack` cradle++If you find that `hie-bios` can't load a certain component or file, run `stack repl` and `stack repl <component name>` to see if `stack` succeeds in building your project. Chances are that there is a problem in your project and if you fix that, `hie-bios` will succeed to load it.++Also, see notes for [testing your configuration](#testing-your-configuration).++Otherwise, please open an [issue](https://github.com/mpickering/hie-bios/issues/new).++#### Ignoring directories++You can combine the [multi-cradle](#multi-cradle) with a [none-cradle](#none-cradle) to ignore all source files in a certain directory. The syntax is a bit verbose:++```yaml+cradle:+  multi:+    - path: "./tests/projects"+      config:+        cradle:+          none:+    - path: "./"+      config:+        cradle:+          stack:+            - path: "./src"+              component: "hie-bios:lib"+            - path: "./exe"+              component: "hie-bios:exe:hie-bios"+            - path: "./tests/BiosTests.hs"+              component: "hie-bios:test:hie-bios"+            - path: "./tests/ParserTests.hs"+              component: "hie-bios:test:parser-tests"+```++This way, we specify that we do not want to load any files in our test project directories.++#### Internal Libraries++Internal libraries are not well supported in `stack`. Since the syntax `stack repl <internal library name>` [doesn't work](https://github.com/commercialhaskell/stack/issues/4564), `hie-bios` will generally not work with internal libraries using `stack`.++### Cabal++To use `cabal`, the basic explicit configuration looks similar to `stack`'s configuration.++```yaml+cradle:+  cabal:+```++The implication of this configuration is a bit different, though. Given a source file to load, we will use `cabal repl <filename>` to find the component of the given filepath.++This configuration should work in (almost) every standard project setup, since `cabal` finds the component associated to a given source file.+However, due to an unfortunate [bug](https://github.com/haskell/cabal/issues/6622), this fails on some files with `cabal` versions older than `3.4`.+So, to make your project loadable by older `cabal` versions, you can specify a component to load.+A [component](https://cabal.readthedocs.io/en/stable/nix-local-build.html?highlight=component#cabal-v2-build) is roughly speaking a library, executable, test-suite or benchmark in `cabal`.+The `hie.yaml` file looks like this:++```yaml+cradle:+  cabal:+    component: <component name>+```++This tells `hie-bios` that whichever source file it tries to load, this source file should be handled as if it belongs to `<component name>`.++As an example, to load the library of `hie-bios`, the following `hie.yaml` can be used:++```yaml+cradle:+  cabal:+    component: "lib:hie-bios"+```++The component syntax `"lib:hie-bios"` refers to the library of the package `hie-bios`. For a complete reference of the component syntax, we refer to the [documentation](https://cabal.readthedocs.io/en/stable/nix-local-build.html?highlight=component#cabal-v2-build).++Note that `cabal` and `stack` have different ways of specifying their components. ++If we only specify a single component, then we can only load source files from this component. This is unsatisfactory as we want to be able to navigate our project freely and work on multiple components (test-suite, library, executable, etc...) in parallel.++In a project such as `hie-bios`, we have more than one component, in particular we have four:++ * An executable+ * A library+ * Two test-suites++The component syntax can easily be extracted from the `hie-bios.cabal` file. Relevant sections are:++```cabal+...+Name:                   hie-bios+...++Library+  ...+  HS-Source-Dirs:       src++Executable hie-bios+  ...+  Main-Is:              Main.hs+  HS-Source-Dirs:       exe++test-suite parser-tests+  ...+  hs-source-dirs: tests/+  main-is: ParserTests.hs++test-suite bios-tests+  ...+  hs-source-dirs: tests/+  main-is: BiosTests.hs+```++Using the documentation of cabal, we extract the four component names of the `hie-bios` project:++* `lib:hie-bios`+* `exe:hie-bios`+* `test:bios-tests`+* `test:parser-tests`++Since you rarely only want to load a single component in a `cabal` project, we have special syntax to be able to conveniently specify which directory belongs to which component. It is basically a [multi-cradle](#multi-cradle).+ ```yaml-cradle: {cabal: {component: "lib:haskell-ide-engine"}}+cradle:+  cabal:+    - path: "./src"+      component: "lib:hie-bios"+    - path: "./exe"+      component: "exe:hie-bios"+    - path: "./tests/BiosTests.hs"+      component: "test:hie-bios"+    - path: "./tests/ParserTests.hs"+      component: "test:parser-tests" ``` -Or you can explicitly state the program which should be used to collect-the options by supplying the path to the program. It is interpreted-relative to the current working directory if it is not an absolute path.-The bios program should consult the `HIE_BIOS_OUTPUT` env var and write a list of-options to this file separated by newlines. Once the program finishes running `hie-bios`-reads this file and uses the arguments to set up the GHC session. This is how GHC's-build system is able to support `hie-bios`.+Here you can see two important features:+  * We provide a mapping from filepath to component.+    * That way, we specify that a file such as `./src/HIE/Bios.hs` belongs to the component `lib:hie-bios`.+  * The filepath can be a file.+    * This is convenient if components are overlapping. +This way we specified which component needs to be compiled given a certain source file for our whole project.++#### Debugging a `cabal` cradle++If you find that `hie-bios` can't load a certain component or file, you may run `cabal repl <filename>` and `cabal repl <component name>` to see if `cabal` succeeds in building the components. Chances are that there is a problem and if you fix that, `hie-bios` will succeed to load the project.++Also, see notes for [testing your configuration](#testing-your-configuration).++Otherwise, please open an [issue](https://github.com/mpickering/hie-bios/issues/new).++#### Ignoring directories++You can combine the [multi-cradle](#multi-cradle) with a [none-cradle](#none-cradle) to ignore all source files in a certain directory. The syntax is a bit verbose:+ ```yaml-cradle: {bios: {program: ".hie-bios"}}+cradle:+  multi:+    - path: "./tests/projects"+      config:+        cradle:+          none:+    - path: "./"+      config:+        cradle:+          cabal:+            - path: "./src"+              component: "lib:hie-bios"+            - path: "./exe"+              component: "exe:hie-bios"+            - path: "./tests/BiosTests.hs"+              component: "test:hie-bios"+            - path: "./tests/ParserTests.hs"+              component: "test:parser-tests" ``` +This way, we specify that we do not want to load any files in our test project directories.++### Bios++Alternatively you can explicitly state a `program` or shell command which should+be used to collect the options. This is the most general approach and can be extended to handle arbitrary build systems.++The path of the `program` attribute is interpreted relative to the current+working directory if it isn't absolute. A program is passed the file to+return options for as its first argument, and a shell command will have it+available in the `HIE_BIOS_ARG` environment variable.++There are two important environment variables:++* `HIE_BIOS_OUTPUT`: describes the filepath the options should be written to. If this file does not exist, the `program` should create it.+* `HIE_BIOS_ARG`: the source file that we want to load. Options returned by the `program` should be able to compile the given source file.+  * This environment variable is *only* available if a shell program is given.++The program flow is roughly as follows:+The process must consult the `HIE_BIOS_OUTPUT` environment variable and write a+list of options to this file, separated by newlines. Once the process finishes+running, `hie-bios` reads this file and uses the arguments to set up the GHC+session. This is how GHC's build system is able to support `hie-bios`.+Note, the `program` is intended to produce the build flags to compile the *whole* component the given source file belongs to. This entails that the `program` lists all of the component's module- and file targets.++A good guiding specification for this file is that the following commands+should work for any file in your project.++``` sh+$ export HIE_BIOS_OUTPUT=./options.txt # this is usually some temporary file+$ ./<program> /path/to/foo.hs+$ ghci $(cat $HIE_BIOS_OUTPUT | tr '\n' ' ')+```++where `HIE_BIOS_OUTPUT` is some chosen output file and `HIE_BIOS_ARG` contains the file parameter.++The `hie.yaml` configuration looks like this:++```yaml+cradle:+  bios:+    program: "<program>"+```++Alternatively, you may specify shell code directly.+This is helpful, if your `program` executable consists of only a single call to another executable.++```yaml+cradle:+  bios:+    shell: "<build-tool flags $HIE_BIOS_ARG>"+```++#### Debugging a `bios` cradle++The most common error in creating `bios` cradle is to not list all targets of the component. Please make sure, that you always list all targets of the component, associated with the filepath you want to load.++Also, see notes for [testing your configuration](#testing-your-configuration).++### Direct+ The `direct` cradle allows you to specify exactly the GHC options that should be used to load a project. This is good for debugging but not a very good approach in general as the set of options will quickly get out of sync with a cabal file.  ```yaml-cradle: {direct: { arguments: [arg1, arg2]} }+cradle:+  direct:+    arguments: [arg1, arg2] ``` +#### Debugging a `direct` cradle++The arguments of a `direct` cradle will be passed almost directly to `ghc`. If the command `ghc <cradle arguments>` succeeds, then `hie-bios` can load the project.++### None+ The `none` cradle says that the IDE shouldn't even try to load the project. It-is most useful when combined with the multi-cradle which is specified in the next section.+is most useful when combined with the [multi-cradle](#multi-cradle) which is specified in the next section.  ```yaml-cradle: {none: }+cradle:+  none: ```  ## Multi-Cradle@@ -84,17 +394,23 @@  The multi-cradle is a list of relative paths and cradle configurations. The path is relative to the configuration file and specifies the scope of-the cradle. For example, this configuration specificies that files in the+the cradle. For example, this configuration specifies that files in the `src` subdirectory should be handled with the `lib:hie-bios` component and-files in the `test` directory using the `test` component.+files in the `test` directory using the `test:bios-tests` component.  ```yaml cradle:   multi:     - path: "./src"-      config: { cradle: {cabal: {component: "lib:hie-bios"}} }+      config:+        cradle:+          cabal:+            component: "lib:hie-bios"     - path: "./test"-      config: { cradle: {cabal: {component: "test"}} }+      config:+        cradle:+          cabal:+            component: "test:bios-tests" ```  If a file matches multiple prefixes, the most specific one is chosen.@@ -116,7 +432,7 @@     - path: "./src"       config: { cradle: {cabal: {component: "lib:hie-bios"}} }     - path: "./test"-      config: { cradle: {cabal: {component: "test"}} }+      config: { cradle: {cabal: {component: "test:bios-tests"}} }     - path: "./test/test-files"       config: { cradle: { none: } } ```@@ -141,7 +457,7 @@       component: "hie-bios:test:bios-tests" ``` -Remember you can combine this shorthand with more complicated configuration+Remember you can combine this shorthand with more complicated configurations as well.  ```yaml@@ -155,7 +471,7 @@                             , { path: "./tests", component: "parser-tests" } ] } } ``` -### Cradle Dependencies+## Cradle Dependencies  Sometimes it is necessary to reload a component, for example when a package dependency is added to the project. Each type of cradle defines a list of@@ -168,8 +484,8 @@ However, if there are files that are not covered by the cradle dependency resolution, you can add these files explicitly to `hie.yaml`.-These files are not required to actually exist, since it can be useful-to know when these files are created, e.g. if there was no `cabal.project`+The file dependencies are not required to actually exist, since it can be useful+to know when they are created, e.g. if there was no `cabal.project` in the project before and now there is, it might change how a file in the project is compiled. @@ -187,8 +503,8 @@   - default.nix ``` -For the `Bios` cradle type, there is an optional field to specify a program-to obtain cradle dependencies from:+For the `Bios` cradle type, there is an optional field for specifying a program+or shell command to obtain cradle dependencies from:  ```yaml cradle:@@ -196,11 +512,19 @@     program: ./flags.sh     dependency-program: ./dependency.sh ```+```yaml+cradle:+  bios:+    shell: build-tool flags $HIE_BIOS_ARG+    dependency-shell: build-tool dependencies+``` -The program `./dependency.sh` is executed with no paramaters and it is+The dependency program or command is executed with no parameters and it is expected to output on stdout on each line exactly one filepath relative to the root of the cradle, not relative to the location of the program. +Programs and shell commands for flags and dependencies can be mixed and matched.+ ## Configuration specification  The complete configuration is a subset of@@ -214,6 +538,8 @@   bios:     program: "program to run"     dependency-program: "optional program to run"+    shell: build-tool flags $HIE_BIOS_ARG+    dependency-shell: build-tool dependencies   direct:     arguments: ["list","of","ghc","arguments"]   none:@@ -226,32 +552,38 @@  ## Testing your configuration -The provided `hie-bios` executable is provided to test your configuration.+The given `hie-bios` executable is provided to test your configuration.  The `flags` command will print out the options that `hie-bios` thinks you will need to load a file. -```-hie-bios flags exe/Main.hs+``` sh+$ hie-bios flags exe/Main.hs ```  The `check` command will try to use these flags to load the module into the GHC API. +``` sh+$ hie-bios check exe/Main.hs ```-hie-bios check exe/Main.hs++The `debug` command prints verbose information about the cradle, such as where the `hie.yaml` file was found, which file is loaded and the options that will eventually be used for loading a session.++``` sh+$ hie-bios debug exe/Main.hs ```  ## Implicit Configuration -There are several built in modes which captures most common Haskell development+There are several built in modes which capture the most common Haskell development scenarios. If no `hie.yaml` configuration file is found then an implicit configuration is searched for. It is strongly recommended to just explicitly configure your project.  ### Priority -The targets are searched for in following order.+The targets are searched for in the following order. -1. A specific `hie-bios` file.+1. A specific `.hie-bios` file. 2. A `stack` project 3. A `cabal` project 4. The direct cradle which has no specific options.@@ -260,32 +592,36 @@  The workspace root is the first folder containing a `cabal.project` file. -The arguments are collected by running `cabal v2-repl`.+The arguments are collected by running `cabal v2-repl <filename>`. -If `cabal v2-repl` fails, then the user needs to configure the correct+If `cabal v2-repl <filename>` fails, then the user needs to configure the correct target to use by writing a `hie.yaml` file.  ### `stack`  The workspace root is the first folder containing a `stack.yaml` file. -The arguments are collected by executing `stack repl`.+The arguments are collected by executing `stack repl`. If this fails, the user needs to configure the correct target to use by writing a `hie.yaml` file.  ### `bios` -The most general form is the `bios` mode which allows a user to specify themselves-which flags to provide.+The most general form is the `bios` mode which allows users to specify+which flags to provide themselves. -In this mode, an executable file called `.hie-bios` is placed in the root-of the workspace directory. The script takes one argument, the filepath-to the current file we want to load into the session. The script returns-a list of GHC arguments separated by newlines which will setup the correct session.+The program will receive the file to return options for as its first argument. -A good guiding specification for this file is that the following command-should work for any file in your project.+The program flow is roughly as follows:+The process must consult the `HIE_BIOS_OUTPUT` environment variable and write a+list of options to the file pointed to by `HIE_BIOS_OUTPUT`, separated by newlines. Once the process finishes running, `hie-bios` reads this file and uses the arguments to set up the GHC+session. This is how GHC's build system is able to support `hie-bios`.+Note, the `program` is intended to produce the build flags to compile the *whole* component the given source file belongs to. This entails that the `program` lists all of the component's module- and file targets. -```-ghci $(./.hie-bios /path/to/foo.hs | tr '\n' ' ') /path/to/foo.hs+A good guiding specification for this file is that the following commands+should work for any file in your project.+``` sh+$ export HIE_BIOS_OUTPUT=./options.txt # this is usually some temporary file+$ ./.hie-bios /path/to/foo.hs+$ ghci $(cat $HIE_BIOS_OUTPUT | tr '\n' ' ') ```  This is useful if you are designing a new build system or the other modes@@ -295,5 +631,4 @@ ## Supporting Bazel and Obelisk  In previous versions of `hie-bios` there was also support for projects using `rules_haskell` and `obelisk`.-This was removed in the 0.3 release as they were unused and broken. There is no conceptual barrier to adding-back support but it requires a user of these two approaches to maintain them.+This was removed in the 0.3 release as they were unused and broken. There is no conceptual barrier to adding back support but it requires a user of these two approaches to maintain them.
exe/Main.hs view
@@ -80,7 +80,7 @@         res <- forM remainingArgs $ \fp -> do                 res <- getCompilerOptions fp cradle                 case res of-                    CradleFail (CradleError _ex err) ->+                    CradleFail (CradleError _deps _ex err) ->                       return $ "Failed to show flags for \""                                                 ++ fp                                                 ++ "\": " ++ show err
hie-bios.cabal view
@@ -1,6 +1,6 @@ Cabal-Version:          2.2 Name:                   hie-bios-Version:                0.5.1+Version:                0.6.0 Author:                 Matthew Pickering <matthewtpickering@gmail.com> Maintainer:             Matthew Pickering <matthewtpickering@gmail.com> License:                BSD-3-Clause@@ -12,13 +12,16 @@ Category:               Development Build-Type:             Simple -- No glob syntax until GHC 8.6 because of stack-Extra-Source-Files:     ChangeLog+Extra-Source-Files:     ChangeLog.md                         README.md                         wrappers/cabal                         wrappers/cabal.hs                         tests/configs/bazel.yaml                         tests/configs/bios-1.yaml                         tests/configs/bios-2.yaml+                        tests/configs/bios-3.yaml+                        tests/configs/bios-4.yaml+                        tests/configs/bios-5.yaml                         tests/configs/cabal-1.yaml                         tests/configs/cabal-multi.yaml                         tests/configs/ch-cabal.yaml@@ -34,6 +37,8 @@                         tests/configs/obelisk.yaml                         tests/configs/stack-config.yaml                         tests/configs/stack-multi.yaml+                        tests/projects/symlink-test/a/A.hs+                        tests/projects/symlink-test/hie.yaml                         tests/projects/multi-direct/A.hs                         tests/projects/multi-direct/B.hs                         tests/projects/multi-direct/hie.yaml@@ -43,16 +48,53 @@                         tests/projects/multi-cabal/hie.yaml                         tests/projects/multi-cabal/multi-cabal.cabal                         tests/projects/multi-cabal/src/Lib.hs+                        tests/projects/monorepo-cabal/cabal.project+                        tests/projects/monorepo-cabal/hie.yaml+                        tests/projects/monorepo-cabal/A/Main.hs+                        tests/projects/monorepo-cabal/A/A.cabal+                        tests/projects/monorepo-cabal/A/Setup.hs+                        tests/projects/monorepo-cabal/B/MyLib.hs+                        tests/projects/monorepo-cabal/B/B.cabal+                        tests/projects/monorepo-cabal/B/Setup.hs                         tests/projects/multi-stack/Setup.hs                         tests/projects/multi-stack/app/Main.hs                         tests/projects/multi-stack/cabal.project                         tests/projects/multi-stack/hie.yaml                         tests/projects/multi-stack/multi-stack.cabal                         tests/projects/multi-stack/src/Lib.hs+                        tests/projects/failing-bios/A.hs+                        tests/projects/failing-bios/B.hs+                        tests/projects/failing-bios/hie.yaml+                        tests/projects/failing-cabal/failing-cabal.cabal+                        tests/projects/failing-cabal/hie.yaml+                        tests/projects/failing-cabal/MyLib.hs+                        tests/projects/failing-cabal/Setup.hs+                        tests/projects/failing-stack/failing-stack.cabal+                        tests/projects/failing-stack/hie.yaml+                        tests/projects/failing-stack/src/Lib.hs+                        tests/projects/failing-stack/Setup.hs+                        tests/projects/nested-cabal/nested-cabal.cabal+                        tests/projects/nested-cabal/cabal.project+                        tests/projects/nested-cabal/hie.yaml+                        tests/projects/nested-cabal/MyLib.hs+                        tests/projects/nested-cabal/Setup.hs+                        tests/projects/nested-cabal/sub-comp/sub-comp.cabal+                        tests/projects/nested-cabal/sub-comp/Lib.hs+                        tests/projects/nested-cabal/sub-comp/Setup.hs+                        tests/projects/nested-stack/nested-stack.cabal+                        tests/projects/nested-stack/hie.yaml+                        tests/projects/nested-stack/MyLib.hs+                        tests/projects/nested-stack/Setup.hs+                        tests/projects/nested-stack/sub-comp/sub-comp.cabal+                        tests/projects/nested-stack/sub-comp/Lib.hs+                        tests/projects/nested-stack/sub-comp/Setup.hs                         tests/projects/simple-bios/A.hs                         tests/projects/simple-bios/B.hs                         tests/projects/simple-bios/hie-bios.sh                         tests/projects/simple-bios/hie.yaml+                        tests/projects/simple-bios-shell/A.hs+                        tests/projects/simple-bios-shell/B.hs+                        tests/projects/simple-bios-shell/hie.yaml                         tests/projects/simple-cabal/A.hs                         tests/projects/simple-cabal/B.hs                         tests/projects/simple-cabal/CHANGELOG.md@@ -79,6 +121,14 @@                         tests/projects/implicit-cabal/cabal.project                         tests/projects/implicit-cabal/implicit-cabal.cabal                         tests/projects/implicit-cabal/Main.hs+                        tests/projects/implicit-cabal-no-project/implicit-cabal-no-project.cabal+                        tests/projects/implicit-cabal-no-project/Main.hs+                        tests/projects/implicit-cabal-deep-project/README+                        tests/projects/implicit-cabal-deep-project/implicit-cabal-deep-project.cabal+                        tests/projects/implicit-cabal-deep-project/Main.hs+                        tests/projects/implicit-cabal-deep-project/cabal.project+                        tests/projects/implicit-cabal-deep-project/foo/foo.cabal+                        tests/projects/implicit-cabal-deep-project/foo/Main.hs                         tests/projects/implicit-stack/Setup.hs                         tests/projects/implicit-stack/implicit-stack.cabal                         tests/projects/implicit-stack/Main.hs@@ -177,9 +227,12 @@       extra,       tasty,       tasty-hunit,+      tasty-expected-failure,+      hspec-expectations,       hie-bios,       filepath,       directory,+      temporary,       ghc    hs-source-dirs: tests/
src/HIE/Bios/Config.hs view
@@ -7,7 +7,8 @@     readConfig,     Config(..),     CradleConfig(..),-    CradleType(..)+    CradleType(..),+    Callable(..)     ) where  import qualified Data.Text as T@@ -39,6 +40,9 @@         }         deriving (Show, Eq, Functor) +data Callable = Program FilePath | Command String+    deriving (Show, Eq)+ data CradleType a     = Cabal { component :: Maybe String }     | CabalMulti [ (FilePath, String) ]@@ -49,13 +53,12 @@ --    | Bazel --    | Obelisk     | Bios-        { prog :: FilePath-        -- ^ Path to program that retrieves options to compile a file-        , depsProg :: Maybe FilePath-        -- ^ Optional Path to program to obtain cradle dependencies.+        { call :: Callable+        -- ^ Path to program or shell command that retrieves options to compile a file+        , depsCall :: Maybe Callable+        -- ^ Optional path to program or shell command to obtain cradle dependencies.         -- Each cradle dependency is to be expected to be on a separate line-        -- and relative to the root dir of the cradle, not relative-        -- to the location of this program.+        -- and relative to the root dir of the cradle.         }     | Direct { arguments :: [String] }     | None@@ -72,7 +75,7 @@     show (CabalMulti a) = "CabalMulti " ++ show a     show (Stack comp) = "Stack {component = " ++ show comp ++ "}"     show (StackMulti a) = "StackMulti " ++ show a-    show Bios { prog, depsProg } = "Bios {prog = " ++ show prog ++ ", depsProg = " ++ show depsProg ++ "}"+    show Bios { call, depsCall } = "Bios {call = " ++ show call ++ ", depsCall = " ++ show depsCall ++ "}"     show (Direct args) = "Direct {arguments = " ++ show args ++ "}"     show None = "None"     show (Multi a) = "Multi " ++ show a@@ -127,16 +130,28 @@ parseBios :: Value -> Parser (CradleType a) parseBios (Object x)     | 2 == Map.size x-    , Just (String biosProgram) <- Map.lookup "program" x-    , Just (String biosDepsProgram) <- Map.lookup "dependency-program" x-    = return $ Bios (T.unpack biosProgram) (Just (T.unpack biosDepsProgram))+    , Just biosCallable <- exclusive (stringTypeFromMap Program "program") (stringTypeFromMap Command "shell")+    , Just biosDepsCallable <- exclusive (stringTypeFromMap Program "dependency-program") (stringTypeFromMap Command "dependency-shell")+    = return $ Bios biosCallable (Just biosDepsCallable)      | 1 == Map.size x-    , Just (String biosProgram) <- Map.lookup "program" x-    = return $ Bios (T.unpack biosProgram) Nothing+    , Just biosCallable <- exclusive (stringTypeFromMap Program "program") (stringTypeFromMap Command "shell")+    = return $ Bios biosCallable Nothing      | otherwise-    = fail "Not a valid Bios Configuration type, following keys are allowed: program, dependency-program"+    = fail "Not a valid Bios Configuration type, following keys are allowed: program or shell, dependency-program or dependency-shell"++    where+        exclusive :: Maybe a -> Maybe a -> Maybe a+        exclusive (Just _) (Just _) = Nothing+        exclusive l Nothing = l+        exclusive Nothing r = r+        stringTypeFromMap :: (String -> t) -> T.Text -> Maybe t+        stringTypeFromMap constructor name = constructor <$> (intoString =<< Map.lookup name x)+        intoString :: Value -> Maybe String+        intoString (String s) = Just (T.unpack s)+        intoString _ = Nothing+ parseBios _ = fail "Bios Configuration is expected to be an object."  parseDirect :: Value -> Parser (CradleType a)
src/HIE/Bios/Cradle.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE BangPatterns #-}@@ -36,7 +37,7 @@ import System.Info.Extra import Control.Monad.IO.Class import System.Environment-import Control.Applicative ((<|>))+import Control.Applicative ((<|>), optional) import System.IO.Temp import System.IO.Error (isPermissionError) import Data.List@@ -52,8 +53,9 @@ import qualified Data.Conduit as C import qualified Data.Conduit.Text as C import qualified Data.Text as T-import           Data.Maybe (fromMaybe)+import           Data.Maybe (fromMaybe, maybeToList) import           GHC.Fingerprint (fingerprintString)+ ----------------------------------------------------------------  -- | Given root\/foo\/bar.hs, return root\/hie.yaml, or wherever the yaml file was found.@@ -118,9 +120,17 @@     addActionDeps :: CradleAction a -> CradleAction a     addActionDeps ca =       ca { runCradle = \l fp ->-            (fmap (\(ComponentOptions os' dir ds) -> ComponentOptions os' dir (ds `union` deps)))-              <$> runCradle ca l fp }+            runCradle ca l fp+              >>= \case+                CradleSuccess (ComponentOptions os' dir ds) ->+                  pure $ CradleSuccess (ComponentOptions os' dir (ds `union` deps))+                CradleFail err ->+                  pure $ CradleFail+                    (err { cradleErrorDependencies = cradleErrorDependencies err `union` deps })+                CradleNone -> pure CradleNone+         } + implicitConfig :: FilePath -> MaybeT IO (CradleConfig a, FilePath) implicitConfig fp = do   (crdType, wdir) <- implicitConfig' fp@@ -128,7 +138,7 @@  implicitConfig' :: FilePath -> MaybeT IO (CradleType a, FilePath) implicitConfig' fp = (\wdir ->-         (Bios (wdir </> ".hie-bios") Nothing, wdir)) <$> biosWorkDir fp+         (Bios (Program $ wdir </> ".hie-bios") Nothing, wdir)) <$> biosWorkDir fp   --   <|> (Obelisk,) <$> obeliskWorkDir fp   --   <|> (Bazel,) <$> rulesHaskellWorkDir fp      <|> (stackExecutable >> (Stack Nothing,) <$> stackWorkDir fp)@@ -203,12 +213,14 @@     { cradleRootDir = cur_dir     , cradleOptsProg = CradleAction         { actionName = Types.Default-        , runCradle = \_ _ -> return (CradleSuccess (ComponentOptions [] cur_dir []))+        , runCradle = \_ _ ->+            return (CradleSuccess (ComponentOptions [] cur_dir []))+        , runGhcCmd = runGhcCmdOnPath cur_dir         }     }  ------------------------------------------------------------------ The none cradle tells us not to even attempt to load a certain directory+-- | The none cradle tells us not to even attempt to load a certain directory  noneCradle :: FilePath -> Cradle a noneCradle cur_dir =@@ -217,11 +229,12 @@     , cradleOptsProg = CradleAction         { actionName = Types.None         , runCradle = \_ _ -> return CradleNone+        , runGhcCmd = \_   -> return CradleNone         }     }  ------------------------------------------------------------------ The multi cradle selects a cradle based on the filepath+-- | The multi cradle selects a cradle based on the filepath  multiCradle :: (b -> Cradle a) -> FilePath -> [(FilePath, CradleConfig b)] -> Cradle a multiCradle buildCustomCradle cur_dir cs =@@ -229,7 +242,15 @@     { cradleRootDir  = cur_dir     , cradleOptsProg = CradleAction         { actionName = multiActionName-        , runCradle  = \l fp -> canonicalizePath fp >>= multiAction buildCustomCradle cur_dir cs l+        , runCradle  = \l fp -> makeAbsolute fp >>= multiAction buildCustomCradle cur_dir cs l+        , runGhcCmd = \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 (not . isNoneCradleConfig) $ map snd cs of+              [] -> return CradleNone+              (cfg:_) -> flip runGhcCmd args $ cradleOptsProg $+                getCradle buildCustomCradle (cfg, cur_dir)         }     }   where@@ -280,10 +301,10 @@     canonicalizeCradles :: IO [(FilePath, CradleConfig b)]     canonicalizeCradles =       sortOn (Down . fst)-        <$> mapM (\(p, c) -> (,c) <$> (canonicalizePath (cur_dir </> p))) cs+        <$> mapM (\(p, c) -> (,c) <$> (makeAbsolute (cur_dir </> p))) cs      selectCradle [] =-      return (CradleFail (CradleError ExitSuccess err_msg))+      return (CradleFail (CradleError [] ExitSuccess err_msg))     selectCradle ((p, c): css) =         if p `isPrefixOf` cur_fp           then runCradle@@ -296,12 +317,14 @@ -------------------------------------------------------------------------  directCradle :: FilePath -> [String] -> Cradle a-directCradle wdir args  =+directCradle wdir args =   Cradle     { cradleRootDir = wdir     , cradleOptsProg = CradleAction         { actionName = Types.Direct-        , runCradle = \_ _ -> return (CradleSuccess (ComponentOptions args wdir []))+        , runCradle = \_ _ ->+            return (CradleSuccess (ComponentOptions args wdir []))+        , runGhcCmd = runGhcCmdOnPath wdir         }     } @@ -310,37 +333,38 @@  -- | Find a cradle by finding an executable `hie-bios` file which will -- be executed to find the correct GHC options to use.-biosCradle :: FilePath -> FilePath -> Maybe FilePath -> Cradle a-biosCradle wdir biosProg biosDepsProg =+biosCradle :: FilePath -> Callable -> Maybe Callable -> Cradle a+biosCradle wdir biosCall biosDepsCall =   Cradle     { cradleRootDir    = wdir     , cradleOptsProg   = CradleAction         { actionName = Types.Bios-        , runCradle = biosAction wdir biosProg biosDepsProg+        , runCradle = biosAction wdir biosCall biosDepsCall+        , runGhcCmd = runGhcCmdOnPath wdir         }     }  biosWorkDir :: FilePath -> MaybeT IO FilePath biosWorkDir = findFileUpwards (".hie-bios" ==) -biosDepsAction :: LoggingFunction -> FilePath -> Maybe FilePath -> IO [FilePath]-biosDepsAction l wdir (Just biosDepsProg) = do-  biosDeps' <- canonicalizePath biosDepsProg-  (ex, sout, serr, args) <- readProcessWithOutputFile l wdir biosDeps' []+biosDepsAction :: LoggingFunction -> FilePath -> Maybe Callable -> IO [FilePath]+biosDepsAction l wdir (Just biosDepsCall) = do+  biosDeps' <- callableToProcess biosDepsCall Nothing+  (ex, sout, serr, args) <- readProcessWithOutputFile l wdir biosDeps'   case ex of     ExitFailure _ ->  error $ show (ex, sout, serr)     ExitSuccess -> return args biosDepsAction _ _ Nothing = return []  biosAction :: FilePath-           -> FilePath-           -> Maybe FilePath+           -> Callable+           -> Maybe Callable            -> LoggingFunction            -> FilePath            -> IO (CradleLoadResult ComponentOptions) biosAction wdir bios bios_deps l fp = do-  bios' <- canonicalizePath bios-  (ex, _stdo, std, res) <- readProcessWithOutputFile l wdir bios' [fp]+  bios' <- callableToProcess bios (Just fp)+  (ex, _stdo, std, res) <- readProcessWithOutputFile l wdir bios'   deps <- biosDepsAction l wdir bios_deps         -- Output from the program should be written to the output file and         -- delimited by newlines.@@ -348,10 +372,19 @@         -- Removes all duplicates.   return $ makeCradleResult (ex, std, wdir, res) deps +callableToProcess :: Callable -> Maybe String -> IO CreateProcess+callableToProcess (Command shellCommand) file = do+  old_env <- getEnvironment+  return $ (shell shellCommand) { env = (: old_env) <$> (,) hieBiosArg <$> file }+    where+      hieBiosArg = "HIE_BIOS_ARG"+callableToProcess (Program path) file = do+  canon_path <- canonicalizePath path+  return $ proc canon_path (maybeToList file)+ --------------------------------------------------------------------------- Cabal Cradle--- Works for new-build by invoking `v2-repl` does not support components--- yet.+-- |Cabal Cradle+-- Works for new-build by invoking `v2-repl`. cabalCradle :: FilePath -> Maybe String -> Cradle a cabalCradle wdir mc =   Cradle@@ -359,14 +392,38 @@     , cradleOptsProg   = CradleAction         { actionName = Types.Cabal         , runCradle = cabalAction wdir mc+        , runGhcCmd = \args -> do+            -- Workaround for a cabal-install bug on 3.0.0.0:+            -- ./dist-newstyle/tmp/environment.-24811: createDirectory: does not exist (No such file or directory)+            -- (It's ok to pass 'dist-newstyle' here, as it can only be changed+            -- with the --builddir flag and not cabal.project, which we aren't+            -- using in our call to v2-exec)+            createDirectoryIfMissing True (wdir </> "dist-newstyle" </> "tmp")+            -- Need to pass -v0 otherwise we get "resolving dependencies..."+            readProcessWithCwd+              wdir "cabal" (["v2-exec", "ghc", "-v0", "--"] ++ args) ""         }     } -cabalCradleDependencies :: FilePath -> IO [FilePath]-cabalCradleDependencies rootDir = do-    cabalFiles <- findCabalFiles rootDir-    return $ cabalFiles ++ ["cabal.project", "cabal.project.local"]+-- | @'cabalCradleDependencies' rootDir componentDir@.+-- Compute the dependencies of the cabal 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 a '.cabal' file.+--+-- Found dependencies are relative to 'rootDir'.+cabalCradleDependencies :: FilePath -> FilePath -> IO [FilePath]+cabalCradleDependencies rootDir componentDir = do+    let relFp = makeRelative rootDir componentDir+    cabalFiles' <- findCabalFiles componentDir+    let cabalFiles = map (relFp </>) cabalFiles'+    return $ map normalise $ cabalFiles ++ ["cabal.project", "cabal.project.local"] +-- |Find .cabal files in the given directory.+--+-- Might return multiple results, as we can not know in advance+-- which one is important to the user. findCabalFiles :: FilePath -> IO [FilePath] findCabalFiles wdir = do   dirContent <- listDirectory wdir@@ -390,7 +447,7 @@ -- arguments to the executable. type GhcProc = (FilePath, [String]) --- generate a fake GHC that can be passed to cabal+-- | Generate a fake GHC that can be passed to cabal -- when run with --interactive, it will print out its -- command-line arguments and exit withCabalWrapperTool :: GhcProc -> FilePath -> (FilePath -> IO a) -> IO a@@ -428,15 +485,20 @@   withCabalWrapperTool ("ghc", []) work_dir $ \wrapper_fp -> do     let cab_args = ["v2-repl", "--with-compiler", wrapper_fp, fromMaybe (fixTargetPath fp) mc]     (ex, output, stde, args) <--      readProcessWithOutputFile l work_dir "cabal" cab_args-    deps <- cabalCradleDependencies work_dir+      readProcessWithOutputFile l work_dir (proc "cabal" cab_args)     case processCabalWrapperArgs args of-        Nothing -> pure $ CradleFail (CradleError ex+        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 <- cabalCradleDependencies work_dir work_dir+          pure $ CradleFail (CradleError deps ex                     ["Failed to parse result of calling cabal"                      , unlines output                      , unlines stde                      , unlines args])-        Just (componentDir, final_args) -> pure $ makeCradleResult (ex, stde, componentDir, final_args) deps+        Just (componentDir, final_args) -> do+          deps <- cabalCradleDependencies work_dir componentDir+          pure $ makeCradleResult (ex, stde, componentDir, final_args) deps   where     -- Need to make relative on Windows, due to a Cabal bug with how it     -- parses file targets with a C: drive in it@@ -447,28 +509,48 @@ removeInteractive :: [String] -> [String] removeInteractive = filter (/= "--interactive") --- Strip out any ["+RTS", ..., "-RTS"] sequences in the command string list.+-- | Strip out any ["+RTS", ..., "-RTS"] sequences in the command string list.+data InRTS = OutsideRTS | InsideRTS++-- | Strip out any ["+RTS", ..., "-RTS"] sequences in the command string list.+--+-- >>> removeRTS ["option1", "+RTS -H32m -RTS", "option2"]+-- ["option1", "option2"]+--+-- >>> removeRTS ["option1", "+RTS", "-H32m", "-RTS", "option2"]+-- ["option1", "option2"]+--+-- >>> removeRTS ["option1", "+RTS -H32m"]+-- ["option1"]+--+-- >>> removeRTS ["option1", "+RTS -H32m", "-RTS", "option2"]+-- ["option1", "option2"]+--+-- >>> removeRTS ["option1", "+RTS -H32m", "-H32m -RTS", "option2"]+-- ["option1", "option2"] removeRTS :: [String] -> [String]-removeRTS ("+RTS" : xs)  =-  case dropWhile (/= "-RTS") xs of-    [] -> []-    (_ : ys) -> removeRTS ys-removeRTS (y:ys)         = y : removeRTS ys-removeRTS []             = []+removeRTS = go OutsideRTS+  where+    go :: InRTS -> [String] -> [String]+    go _ [] = []+    go OutsideRTS (y:ys)+      | "+RTS" `isPrefixOf` y = go (if "-RTS" `isSuffixOf` y then OutsideRTS else InsideRTS) ys+      | otherwise = y : go OutsideRTS ys+    go InsideRTS (y:ys) = go (if "-RTS" `isSuffixOf` y then OutsideRTS else InsideRTS) ys + removeVerbosityOpts :: [String] -> [String] removeVerbosityOpts = filter ((&&) <$> (/= "-v0") <*> (/= "-w"))   cabalWorkDir :: FilePath -> MaybeT IO FilePath-cabalWorkDir = findFileUpwards isCabal-  where-    isCabal name = name == "cabal.project"+cabalWorkDir wdir =+      findFileUpwards (== "cabal.project") wdir+  <|> findFileUpwards (\fp -> takeExtension fp == ".cabal") wdir  --------------------------------------------------------------------------- Stack Cradle+-- | Stack Cradle -- Works for by invoking `stack repl` with a wrapper script- stackCradle :: FilePath -> Maybe String -> Cradle a stackCradle wdir mc =   Cradle@@ -476,13 +558,27 @@     , cradleOptsProg   = CradleAction         { actionName = Types.Stack         , runCradle = stackAction wdir mc+        , runGhcCmd = \args ->+            readProcessWithCwd+              wdir "stack" (["exec", "--silent", "ghc", "--"] <> args) ""         }     } -stackCradleDependencies :: FilePath-> IO [FilePath]-stackCradleDependencies wdir = do-  cabalFiles <- findCabalFiles wdir-  return $ cabalFiles ++ ["package.yaml", "stack.yaml"]+-- | @'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 -> IO [FilePath]+stackCradleDependencies wdir componentDir = do+  let relFp = makeRelative wdir componentDir+  cabalFiles' <- findCabalFiles componentDir+  let cabalFiles = map (relFp </>) cabalFiles'+  return $ map normalise $ cabalFiles ++ [relFp </> "package.yaml", "stack.yaml"]  stackAction :: FilePath -> Maybe String -> LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions) stackAction work_dir mc l _fp = do@@ -490,22 +586,34 @@   -- Same wrapper works as with cabal   withCabalWrapperTool ghcProcArgs work_dir $ \wrapper_fp -> do     (ex1, _stdo, stde, args) <--      readProcessWithOutputFile l work_dir-              "stack" $ ["repl", "--no-nix-pure", "--with-ghc", wrapper_fp]-                        ++ [ comp | Just comp <- [mc] ]+      readProcessWithOutputFile l work_dir $+        proc "stack" $ ["repl", "--no-nix-pure", "--with-ghc", wrapper_fp]+                       ++ [ comp | Just comp <- [mc] ]     (ex2, pkg_args, stdr, _) <--      readProcessWithOutputFile l work_dir "stack" ["path", "--ghc-package-path"]+      readProcessWithOutputFile l work_dir $+        proc "stack" ["path", "--ghc-package-path"]     let split_pkgs = concatMap splitSearchPath pkg_args         pkg_ghc_args = concatMap (\p -> ["-package-db", p] ) split_pkgs-    deps <- stackCradleDependencies work_dir-    return $ case processCabalWrapperArgs args of-        Nothing -> CradleFail (CradleError ex1 $-                    ("Failed to parse result of calling stack":-                      stde)-                     ++ args)+    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 work_dir work_dir+          pure $ CradleFail+                    (CradleError deps ex1 $+                      [ "Failed to parse result of calling stack" ]+                      ++ stde+                      ++ args+                    ) -        Just (componentDir, ghc_args) ->-          makeCradleResult (combineExitCodes [ex1, ex2], stde ++ stdr, componentDir, ghc_args ++ pkg_ghc_args) deps+        Just (componentDir, ghc_args) -> do+          deps <- stackCradleDependencies work_dir componentDir+          pure $ makeCradleResult+                    ( combineExitCodes [ex1, ex2]+                    , stde ++ stdr, componentDir+                    , ghc_args ++ pkg_ghc_args+                    )+                    deps  combineExitCodes :: [ExitCode] -> ExitCode combineExitCodes = foldr go ExitSuccess@@ -626,30 +734,27 @@     getFiles = filter p <$> getDirectoryContents dir     doesPredFileExist file = doesFileExist $ dir </> file --- | Call a process with the given arguments.+-- | Call a given process. -- * A special file is created for the process to write to, the process can discover the name of -- the file by reading the @HIE_BIOS_OUTPUT@ environment variable. The contents of this file is -- returned by the function. -- * The logging function is called every time the process emits anything to stdout or stderr. -- it can be used to report progress of the process to a user. -- * The process is executed in the given directory.--- * The path to the GHC version to use is supplied in the environment variable @HIE_BIOS_GHC@.---   Additionally, arguments to ghc are supplied via @HIE_BIOS_GHC_ARGS@ readProcessWithOutputFile   :: LoggingFunction -- ^ Output of the process is streamed into this function.   -> FilePath -- ^ Working directory. Process is executed in this directory.-  -> FilePath -- ^ Process to call.-  -> [String] -- ^ Arguments to the process.+  -> CreateProcess -- ^ Parameters for the process to be executed.   -> IO (ExitCode, [String], [String], [String])-readProcessWithOutputFile l work_dir fp args = do+readProcessWithOutputFile l work_dir cp = do   old_env <- getEnvironment    withHieBiosOutput old_env $ \output_file -> do     -- Pipe stdout directly into the logger-    let process = (readProcessInDirectory work_dir fp args)-                      { env = Just+    let process = cp { env = Just                               $ (hieBiosOutput, output_file)-                              : old_env+                              : (fromMaybe old_env (env cp)),+                       cwd = Just work_dir                       }      -- Windows line endings are not converted so you have to filter out `'r` characters@@ -664,8 +769,8 @@      where       withHieBiosOutput :: [(String,String)] -> (FilePath -> IO a) -> IO a-      withHieBiosOutput env action = do-        let mbHieBiosOut = lookup hieBiosOutput env+      withHieBiosOutput env' action = do+        let mbHieBiosOut = lookup hieBiosOutput env'         case mbHieBiosOut of           Just file@(_:_) -> action file           _ -> withSystemTempFile "hie-bios" $@@ -673,13 +778,28 @@        hieBiosOutput = "HIE_BIOS_OUTPUT" -readProcessInDirectory :: FilePath -> FilePath -> [String] -> CreateProcess-readProcessInDirectory wdir p args = (proc p args) { cwd = Just wdir }- makeCradleResult :: (ExitCode, [String], FilePath, [String]) -> [FilePath] -> CradleLoadResult ComponentOptions makeCradleResult (ex, err, componentDir, gopts) deps =   case ex of-    ExitFailure _ -> CradleFail (CradleError ex err)+    ExitFailure _ -> CradleFail (CradleError deps ex err)     _ ->         let compOpts = ComponentOptions gopts componentDir deps         in CradleSuccess compOpts++-- | Calls @ghc --print-libdir@, with just whatever's on the PATH.+runGhcCmdOnPath :: FilePath -> [String] -> IO (CradleLoadResult String)+runGhcCmdOnPath wdir args = readProcessWithCwd wdir "ghc" args ""+  -- case mResult of+  --   Nothing++-- | Wrapper around 'readCreateProcess' that sets the working directory+readProcessWithCwd :: FilePath -> FilePath -> [String] -> String -> IO (CradleLoadResult String)+readProcessWithCwd dir cmd args stdi = do+  let createProc = (proc cmd args) { cwd = Just dir }+  mResult <- optional $ readCreateProcessWithExitCode createProc stdi+  case mResult of+    Just (ExitSuccess, stdo, _) -> pure $ CradleSuccess stdo+    Just (exitCode, stdo, stde) -> pure $ CradleFail $+      CradleError [] exitCode ["Error when calling " <> cmd <> " " <> unwords args, stdo, stde]+    Nothing -> pure $ CradleFail $+      CradleError [] ExitSuccess ["Couldn't execute " <> cmd <> " " <> unwords args]
src/HIE/Bios/Environment.hs view
@@ -1,16 +1,16 @@ {-# LANGUAGE RecordWildCards, CPP #-}-module HIE.Bios.Environment (initSession, getSystemLibDir, makeDynFlagsAbsolute, getCacheDir, addCmdOpts) where+module HIE.Bios.Environment (initSession, getRuntimeGhcLibDir, getRuntimeGhcVersion, makeDynFlagsAbsolute, makeTargetsAbsolute, getCacheDir, addCmdOpts) where  import CoreMonad (liftIO)-import GHC (DynFlags(..), GhcLink(..), HscTarget(..), GhcMonad)+import GHC (GhcMonad) import qualified GHC as G import qualified DriverPhases as G import qualified Util as G import DynFlags +import Control.Applicative import Control.Monad (void) -import System.Process (readProcess) import System.Directory import System.FilePath import System.Environment (lookupEnv)@@ -20,8 +20,7 @@ import Data.ByteString.Base16 import Data.List import Data.Char (isSpace)-import Control.Applicative ((<|>))-import Text.ParserCombinators.ReadP+import Text.ParserCombinators.ReadP hiding (optional) import HIE.Bios.Types import HIE.Bios.Ghc.Gap @@ -47,22 +46,50 @@         $ setVerbosity 0                       -- Set verbosity to zero just in case the user specified `-vx` in the options.         $ setLinkerOptions df''                 -- Set `-fno-code` to avoid generating object files, unless we have to.         )++    let targets' = makeTargetsAbsolute componentRoot targets     -- Unset the default log action to avoid output going to stdout.     unsetLogAction-    return targets+    return targets'  ---------------------------------------------------------------- --- | Obtain the directory for system libraries.-getSystemLibDir :: IO (Maybe FilePath)-getSystemLibDir = do-    res <- readProcess "ghc" ["--print-libdir"] []-    return $ case res of-        ""   -> Nothing-        dirn -> Just (init dirn)+makeTargetsAbsolute :: FilePath -> [G.Target] -> [G.Target]+makeTargetsAbsolute wdir = map (\target -> target {G.targetId = makeTargetIdAbsolute wdir (G.targetId target)}) +makeTargetIdAbsolute :: FilePath -> G.TargetId -> G.TargetId+makeTargetIdAbsolute wdir (G.TargetFile fp phase) = G.TargetFile (prependIfRelative wdir fp) phase+makeTargetIdAbsolute _ tid = tid+ ---------------------------------------------------------------- +-- | @getRuntimeGhcLibDir cradle@ will give you the ghc libDir:+-- __do not__ use 'runGhcCmd' directly.+-- This will also perform additional lookups and fallbacks to try and get a+-- reliable library directory.+-- It tries this specific order of paths:+--+-- 1. the @NIX_GHC_LIBDIR@ if it is set+-- 2. calling 'runCradleGhc' on the provided cradle+getRuntimeGhcLibDir :: Cradle a+                    -> IO (CradleLoadResult FilePath)+getRuntimeGhcLibDir cradle = do+  maybeNixLibDir <- lookupEnv "NIX_GHC_LIBDIR"+  case maybeNixLibDir of +    Just ld -> pure (CradleSuccess ld)+    Nothing -> fmap (fmap trim) $+      runGhcCmd (cradleOptsProg cradle) ["--print-libdir"]++-- | Gets the version of ghc used when compiling the cradle. It is based off of+-- 'getRuntimeGhcLibDir'. If it can't work out the verison reliably, it will+-- return a 'CradleError'+getRuntimeGhcVersion :: Cradle a+                     -> IO (CradleLoadResult String)+getRuntimeGhcVersion cradle =+  fmap (fmap trim) $ runGhcCmd (cradleOptsProg cradle) ["--numeric-version"]++----------------------------------------------------------------+ -- | What to call the cache directory in the cache folder. cacheDir :: String cacheDir = "hie-bios"@@ -171,15 +198,23 @@ -- This makes the 'DynFlags' independent of the current working directory. makeDynFlagsAbsolute :: FilePath -> DynFlags -> DynFlags makeDynFlagsAbsolute work_dir df =-  mapOverIncludePaths (prependIfNotAbsolute work_dir)+  mapOverIncludePaths (prependIfRelative work_dir)   $ df-    { importPaths = map (prependIfNotAbsolute work_dir) (importPaths df)+    { importPaths = map (prependIfRelative work_dir) (importPaths df)+    , packageDBFlags =+        let makePackageDbAbsolute (PackageDB pkgConfRef) = PackageDB+              $ case pkgConfRef of+                PkgConfFile fp -> PkgConfFile (prependIfRelative work_dir fp)+                conf -> conf+            makePackageDbAbsolute db = db+        in map makePackageDbAbsolute (packageDBFlags df)     }-  where-    prependIfNotAbsolute wdir f-      | isAbsolute f = f-      | otherwise = wdir </> f +prependIfRelative :: FilePath -> FilePath -> FilePath+prependIfRelative wdir f+  | isAbsolute f = f+  | otherwise = wdir </> f+ -- partition_args, along with some of the other code in this file, -- was copied from ghc/Main.hs -- -----------------------------------------------------------------------------@@ -277,3 +312,7 @@  anyToken :: ReadP Char anyToken = satisfy $ const True++-- Used for clipping the trailing newlines on GHC output+trim :: String -> String+trim = dropWhileEnd isSpace
src/HIE/Bios/Ghc/Api.hs view
@@ -4,57 +4,21 @@     initializeFlagsWithCradle   , initializeFlagsWithCradleWithMessage   , G.SuccessFlag(..)-  -- * Utility functions for running the GHC monad and implementing internal utilities-  , withGHC-  , withGHC'-  , withGhcT-  , getSystemLibDir   , withDynFlags   ) where  import CoreMonad (liftIO)-import Exception (ghandle, SomeException(..), ExceptionMonad(..))-import GHC (Ghc, LoadHowMuch(..), GhcMonad, GhcT)+import GHC (LoadHowMuch(..), GhcMonad) import DynFlags  import qualified GHC as G-import qualified MonadUtils as G import qualified HscMain as G import qualified GhcMake as G  import Control.Monad (void)-import System.Exit (exitSuccess) import HIE.Bios.Types-import qualified HIE.Bios.Internal.Log as Log import HIE.Bios.Environment import HIE.Bios.Flags---------------------------------------------------------------------- | Converting the 'Ghc' monad to the 'IO' monad. All exceptions are ignored and logged.-withGHC :: FilePath  -- ^ A target file displayed in an error message.-        -> Ghc a -- ^ 'Ghc' actions created by the Ghc utilities.-        -> IO a-withGHC file body = ghandle ignore $ withGHC' body-  where-    ignore :: SomeException -> IO a-    ignore e = do-        Log.logm $ file ++ ":0:0:Error:"-        Log.logm (show e)-        exitSuccess---- | Run a Ghc monad computation with an automatically discovered libdir.--- It calculates the lib dir by calling ghc with the `--print-libdir` flag.-withGHC' :: Ghc a -> IO a-withGHC' body = do-    -- TODO: Why is this not using ghc-paths?-    mlibdir <- getSystemLibDir-    G.runGhc mlibdir body--withGhcT :: (Exception.ExceptionMonad m, G.MonadIO m, Monad m) => GhcT m a -> m a-withGhcT body = do-  mlibdir <- G.liftIO $ getSystemLibDir-  G.runGhcT mlibdir body  ---------------------------------------------------------------- 
src/HIE/Bios/Ghc/Check.hs view
@@ -6,6 +6,7 @@ import GHC (DynFlags(..), GhcMonad) import Exception +import HIE.Bios.Environment import HIE.Bios.Ghc.Api import HIE.Bios.Ghc.Logger import qualified HIE.Bios.Internal.Log as Log@@ -16,8 +17,8 @@ import System.IO.Unsafe (unsafePerformIO) import qualified HIE.Bios.Ghc.Gap as Gap +import qualified DynFlags as G import qualified GHC as G-import HIE.Bios.Environment  ---------------------------------------------------------------- @@ -28,23 +29,19 @@             -> [FilePath]  -- ^ The target files.             -> IO String checkSyntax _      []    = return ""-checkSyntax cradle files = withGhcT $ do-    Log.debugm $ "Cradle: " ++ show cradle-    res <- initializeFlagsWithCradle (head files) cradle-    case res of-      CradleSuccess (ini, _) -> do-        _sf <- ini-        either id id <$> check files-      CradleFail ce -> liftIO $ throwIO ce-      CradleNone -> return "No cradle"--+checkSyntax cradle files = do+    libDirRes <- getRuntimeGhcLibDir cradle+    handleRes libDirRes $ \libDir ->+      G.runGhcT (Just libDir) $ do+        Log.debugm $ "Cradle: " ++ show cradle+        res <- initializeFlagsWithCradle (head files) cradle+        handleRes res $ \(ini, _) -> do+          _sf <- ini+          either id id <$> check files   where-    {--    sessionName = case files of-      [file] -> file-      _      -> "MultipleFiles"-      -}+    handleRes (CradleSuccess x) f = f x+    handleRes (CradleFail ce) _f = liftIO $ throwIO ce +    handleRes CradleNone _f = return "No cradle"  ---------------------------------------------------------------- @@ -53,7 +50,9 @@ check :: (GhcMonad m)       => [FilePath]  -- ^ The target files.       -> m (Either String String)-check fileNames = withLogger setAllWarningFlags $ setTargetFiles (map dup fileNames)+check fileNames = do+  libDir <- G.topDir <$> G.getDynFlags+  withLogger (setAllWarningFlags libDir) $ setTargetFiles (map dup fileNames)  dup :: a -> (a, a) dup x = (x, x)@@ -61,14 +60,13 @@ ----------------------------------------------------------------  -- | Set 'DynFlags' equivalent to "-Wall".-setAllWarningFlags :: DynFlags -> DynFlags-setAllWarningFlags df = df { warningFlags = allWarningFlags }+setAllWarningFlags :: FilePath -> DynFlags -> DynFlags+setAllWarningFlags libDir df = df { warningFlags = allWarningFlags libDir }  {-# NOINLINE allWarningFlags #-}-allWarningFlags :: Gap.WarnFlags-allWarningFlags = unsafePerformIO $ do-    mlibdir <- getSystemLibDir-    G.runGhcT mlibdir $ do+allWarningFlags :: FilePath -> Gap.WarnFlags+allWarningFlags libDir = unsafePerformIO $+    G.runGhcT (Just libDir) $ do         df <- G.getSessionDynFlags         (df', _) <- addCmdOpts ["-Wall"] df         return $ G.warningFlags df'
src/HIE/Bios/Ghc/Logger.hs view
@@ -10,7 +10,7 @@ import ErrUtils import Exception (ghandle) import FastString (unpackFS)-import GHC (DynFlags(..), SrcSpan(..), Severity(SevError), GhcMonad)+import GHC (DynFlags(..), SrcSpan(..), GhcMonad) import qualified GHC as G import HscTypes (SourceError, srcErrorMessages) import Outputable (PprStyle, SDoc)
src/HIE/Bios/Internal/Debug.hs view
@@ -1,15 +1,13 @@ {-# LANGUAGE LambdaCase #-} module HIE.Bios.Internal.Debug (debugInfo, rootInfo, configInfo, cradleInfo) where -import Control.Monad.IO.Class (liftIO) import Control.Monad import Data.Void  import qualified Data.Char as Char-import Data.Maybe (fromMaybe) -import HIE.Bios.Ghc.Api import HIE.Bios.Cradle+import HIE.Bios.Environment import HIE.Bios.Types import HIE.Bios.Flags @@ -34,20 +32,23 @@     canonFp <- canonicalizePath fp     conf <- findConfig canonFp     crdl <- findCradle' canonFp+    ghcLibDir <- getRuntimeGhcLibDir cradle+    ghcVer <- getRuntimeGhcVersion cradle     case res of       CradleSuccess (ComponentOptions gopts croot deps) -> do-        mglibdir <- liftIO getSystemLibDir         return [-            "Root directory:      " ++ rootDir-          , "Component directory: " ++ croot-          , "GHC options:         " ++ unwords (map quoteIfNeeded gopts)-          , "System libraries:    " ++ fromMaybe "" mglibdir-          , "Config Location:     " ++ conf-          , "Cradle:              " ++ crdl-          , "Dependencies:        " ++ unwords deps+            "Root directory:        " ++ rootDir+          , "Component directory:   " ++ croot+          , "GHC options:           " ++ unwords (map quoteIfNeeded gopts)+          , "GHC library directory: " ++ show ghcLibDir+          , "GHC version:           " ++ show ghcVer+          , "Config Location:       " ++ conf+          , "Cradle:                " ++ crdl+          , "Dependencies:          " ++ unwords deps           ]-      CradleFail (CradleError ext stderr) ->+      CradleFail (CradleError deps ext stderr) ->         return ["Cradle failed to load"+               , "Deps: " ++ show deps                , "Exit Code: " ++ show ext                , "Stderr: " ++ unlines stderr]       CradleNone ->
src/HIE/Bios/Types.hs view
@@ -53,10 +53,14 @@   deriving (Show, Eq, Ord, Functor)  data CradleAction a = CradleAction {-                      actionName :: ActionName a+                        actionName    :: ActionName a                       -- ^ Name of the action.-                      , runCradle :: LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions)+                      , runCradle     :: LoggingFunction -> FilePath -> IO (CradleLoadResult ComponentOptions)                       -- ^ Options to compile the given file with.+                      , runGhcCmd :: [String] -> IO (CradleLoadResult String)+                      -- ^ Executes the @ghc@ binary that is usually used to+                      -- build the cradle. E.g. for a cabal cradle this should be+                      -- equivalent to @cabal exec ghc -- args@                       }   deriving (Functor) @@ -70,10 +74,20 @@   = CradleSuccess r -- ^ The cradle succeeded and returned these options.   | CradleFail CradleError -- ^ We tried to load the cradle and it failed.   | CradleNone -- ^ No attempt was made to load the cradle.-  deriving (Functor, Show)+  deriving (Functor, Show, Eq)  -data CradleError = CradleError ExitCode [String] deriving (Show)+data CradleError = CradleError+  { cradleErrorDependencies :: [FilePath]+  -- ^ Dependencies of the cradle that failed to load.+  -- Can be watched for changes to attempt a reload of the cradle.+  , cradleErrorExitCode :: ExitCode+  -- ^ ExitCode of the cradle loading mechanism.+  , cradleErrorStderr :: [String]+  -- ^ Standard error output that can be shown to users to explain+  -- the loading error.+  }+  deriving (Show, Eq)  instance Exception CradleError where ----------------------------------------------------------------@@ -81,8 +95,8 @@ -- | Option information for GHC data ComponentOptions = ComponentOptions {     componentOptions  :: [String]  -- ^ Command line options.-  , componentRoot :: FilePath -  -- ^ Root directory of the component. All 'componentOptions' are either +  , componentRoot :: FilePath+  -- ^ Root directory of the component. All 'componentOptions' are either   -- absolute, or relative to this directory.   , componentDependencies :: [FilePath]   -- ^ Dependencies of a cradle that might change the cradle.
tests/BiosTests.hs view
@@ -1,20 +1,31 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE CPP #-} module Main where  import Test.Tasty import Test.Tasty.HUnit+import Test.Hspec.Expectations+#if __GLASGOW_HASKELL__ < 810+import Test.Tasty.ExpectedFailure+#endif+import qualified GHC as G import HIE.Bios import HIE.Bios.Ghc.Api import HIE.Bios.Ghc.Load import HIE.Bios.Cradle+import HIE.Bios.Environment import HIE.Bios.Types import Control.Monad.IO.Class-import Control.Monad ( unless, forM_, when )+import Control.Monad ( forM_, unless ) import Data.Void import System.Directory-import System.FilePath ( makeRelative, (</>) )+import System.FilePath (addTrailingPathSeparator,  makeRelative, (</>) ) import System.Info.Extra ( isWindows )+import System.IO.Temp+import System.Exit (ExitCode(ExitFailure))+import Control.Monad.Extra (unlessM)  main :: IO () main = do@@ -36,124 +47,295 @@                   (Just "./tests/projects/simple-cabal/hie.yaml")                 )         ]+      , testGroup "Symlink"+        [ testCaseSteps "Can load base module" $ \step -> do+            withTempCopy "./tests/projects/symlink-test" $ \tmpdir -> do+              crdl <- initialiseCradle isMultiCradle (addTrailingPathSeparator tmpdir) step+              step "Load module A"+              withCurrentDirectory (cradleRootDir crdl) $ do+                runCradle (cradleOptsProg crdl) (const (pure ())) "./a/A.hs"+                >>= \case+                  CradleSuccess r ->+                    componentOptions r `shouldMatchList` ["a"]+                  _ -> expectationFailure "Cradle could not be loaded"++        , testCaseSteps "Can load symlinked module" $ \step -> do+            withTempCopy "./tests/projects/symlink-test" $ \tmpdir -> do+              crdl <- initialiseCradle isMultiCradle (addTrailingPathSeparator tmpdir) step+              step "Attemp to load symlinked module A"+              withCurrentDirectory (cradleRootDir crdl) $ do+                createDirectoryLink "./a" "./b"+                unlessM (doesFileExist "./b/A.hs")+                  $ expectationFailure "Test invariant broken, this file must exist."++                runCradle (cradleOptsProg crdl) (const (pure ())) "./b/A.hs"+                >>= \case+                  CradleSuccess r ->+                    componentOptions r `shouldMatchList` ["b"]+                  _ -> expectationFailure "Cradle could not be loaded"++        , testCaseSteps "Can not load symlinked module that is ignored" $ \step -> do+            withTempCopy "./tests/projects/symlink-test" $ \tmpdir -> do+              crdl <- initialiseCradle isMultiCradle (addTrailingPathSeparator tmpdir) step+              step "Attemp to load symlinked module A"+              withCurrentDirectory (cradleRootDir crdl) $ do+                createDirectoryLink "./a" "./c"+                unlessM (doesFileExist "./c/A.hs")+                  $ expectationFailure "Test invariant broken, this file must exist."++                runCradle (cradleOptsProg crdl) (const (pure ())) "./c/A.hs"+                  >>= \case+                    CradleNone -> pure ()+                    _ -> expectationFailure "Cradle loaded symlink"+        ]       , testGroup "Loading tests"         $ linuxExlusiveTestCases         ++-           [ testCaseSteps "simple-cabal" $ testDirectory isCabalCradle "./tests/projects/simple-cabal/B.hs"-           , testCaseSteps "simple-direct" $ testDirectory isDirectCradle "./tests/projects/simple-direct/B.hs"+           [ testCaseSteps "failing-cabal" $ testDirectoryFail isCabalCradle "./tests/projects/failing-cabal" "MyLib.hs"+              (\CradleError {..} -> do+                  cradleErrorExitCode `shouldBe` ExitFailure 1+                  cradleErrorDependencies `shouldMatchList` ["failing-cabal.cabal", "cabal.project", "cabal.project.local"])+           , testCaseSteps "failing-bios" $ testDirectoryFail isBiosCradle "./tests/projects/failing-bios" "B.hs"+              (\CradleError {..} -> do+                  cradleErrorExitCode `shouldBe` ExitFailure 1+                  cradleErrorDependencies `shouldMatchList` ["hie.yaml"])+           , testCaseSteps "simple-bios-shell" $ testDirectory isBiosCradle "./tests/projects/simple-bios-shell" "B.hs"+           , testCaseSteps "simple-cabal" $ testDirectory isCabalCradle "./tests/projects/simple-cabal" "B.hs"+           , testCaseSteps "simple-direct" $ testDirectory isDirectCradle "./tests/projects/simple-direct" "B.hs"+           , testCaseSteps "nested-cabal" $ testLoadCradleDependencies isCabalCradle "./tests/projects/nested-cabal" "sub-comp/Lib.hs"+              (\deps -> deps `shouldMatchList` ["sub-comp" </> "sub-comp.cabal", "cabal.project", "cabal.project.local"]+              )+           , testCaseSteps "nested-cabal2" $ testLoadCradleDependencies isCabalCradle "./tests/projects/nested-cabal" "MyLib.hs"+              (\deps -> deps `shouldMatchList` ["nested-cabal.cabal", "cabal.project", "cabal.project.local"]+              )            , testCaseSteps "multi-direct" {- tests if both components can be loaded -}-                         $  testDirectory isMultiCradle "./tests/projects/multi-direct/A.hs"-                         >> testDirectory isMultiCradle "./tests/projects/multi-direct/B.hs"+                         $  testDirectory isMultiCradle "./tests/projects/multi-direct" "A.hs"+                         >> testDirectory isMultiCradle "./tests/projects/multi-direct" "B.hs"            , testCaseSteps "multi-cabal" {- tests if both components can be loaded -}-                         $  testDirectory isCabalCradle "./tests/projects/multi-cabal/app/Main.hs"-                         >> testDirectory isCabalCradle "./tests/projects/multi-cabal/src/Lib.hs"+                         $  testDirectory isCabalCradle "./tests/projects/multi-cabal" "app/Main.hs"+                         >> testDirectory isCabalCradle "./tests/projects/multi-cabal" "src/Lib.hs"+           , testCaseSteps "monorepo-cabal" {- issue https://github.com/mpickering/hie-bios/issues/200 -}+                         $  testDirectory isCabalCradle "./tests/projects/monorepo-cabal" "A/Main.hs"+                         >> testDirectory isCabalCradle "./tests/projects/monorepo-cabal" "B/MyLib.hs"            ]--- TODO: Remove once there's a stackage snapshot for ghc 8.10+-- TODO: Remove once stack and ghc-8.10.1 play well+-- https://github.com/bubba/hie-bios/runs/811271872?check_suite_focus=true #if __GLASGOW_HASKELL__ < 810-       ++ [ testCaseSteps "simple-stack" $ testDirectory isStackCradle "./tests/projects/simple-stack/B.hs"+       ++ [ expectFailBecause "stack repl does not fail on an invalid cabal file" $+              testCaseSteps "failing-stack" $ testDirectoryFail isStackCradle "./tests/projects/failing-stack" "src/Lib.hs"+                (\CradleError {..} -> do+                    cradleErrorExitCode `shouldBe` ExitFailure 1+                    cradleErrorDependencies `shouldMatchList` ["failing-stack.cabal", "stack.yaml", "package.yaml"])+          , testCaseSteps "simple-stack" $ testDirectory isStackCradle "./tests/projects/simple-stack" "B.hs"           , testCaseSteps "multi-stack" {- tests if both components can be loaded -}-                        $  testDirectory isStackCradle "./tests/projects/multi-stack/app/Main.hs"-                        >> testDirectory isStackCradle "./tests/projects/multi-stack/src/Lib.hs"+                        $  testDirectory isStackCradle "./tests/projects/multi-stack" "app/Main.hs"+                        >> testDirectory isStackCradle "./tests/projects/multi-stack" "src/Lib.hs"+          , expectFailBecause "stack repl set the component directory to the root directory" $+              testCaseSteps "nested-stack" $ testLoadCradleDependencies isStackCradle "./tests/projects/nested-stack" "sub-comp/Lib.hs"+                (\deps -> deps `shouldMatchList`+                  ["sub-comp" </> "sub-comp.cabal", "sub-comp" </> "package.yaml", "stack.yaml"]+                )+          , testCaseSteps "nested-stack2" $ testLoadCradleDependencies isStackCradle "./tests/projects/nested-stack" "MyLib.hs"+              (\deps -> deps `shouldMatchList` ["nested-stack.cabal", "package.yaml", "stack.yaml"]+              )           ,           -- Test for special characters in the path for parsing of the ghci-scripts.           -- Issue https://github.com/mpickering/hie-bios/issues/162           testCaseSteps "space stack"-                        $  testDirectory isStackCradle "./tests/projects/space stack/A.hs"-                        >> testDirectory isStackCradle "./tests/projects/space stack/B.hs"+                        $  testDirectory isStackCradle "./tests/projects/space stack" "A.hs"+                        >> testDirectory isStackCradle "./tests/projects/space stack" "B.hs"           ] #endif       , testGroup "Implicit cradle tests" $-        [ testCaseSteps "implicit-cabal" $ testImplicitCradle "./tests/projects/implicit-cabal/Main.hs" Cabal--- TODO: Remove once there's a stackage snapshot for ghc 8.10+        [ testCaseSteps "implicit-cabal" $+            testImplicitCradle "./tests/projects/implicit-cabal" "Main.hs" Cabal+        , testCaseSteps "implicit-cabal-no-project" $+            testImplicitCradle "./tests/projects/implicit-cabal-no-project" "Main.hs" Cabal+        , testCaseSteps "implicit-cabal-deep-project" $+            testImplicitCradle "./tests/projects/implicit-cabal-deep-project" "foo/Main.hs" Cabal+-- TODO: same here #if __GLASGOW_HASKELL__ < 810-        , testCaseSteps "implicit-stack" $ testImplicitCradle "./tests/projects/implicit-stack/Main.hs" Stack+        , testCaseSteps "implicit-stack" $+            testImplicitCradle "./tests/projects/implicit-stack" "Main.hs" Stack         , testCaseSteps "implicit-stack-multi"-            $ testImplicitCradle "./tests/projects/implicit-stack-multi/Main.hs" Stack-            >> testImplicitCradle "./tests/projects/implicit-stack-multi/other-package/Main.hs" Stack+            $ testImplicitCradle "./tests/projects/implicit-stack-multi" "Main.hs" Stack+            >> testImplicitCradle "./tests/projects/implicit-stack-multi" "other-package/Main.hs" Stack #endif         ]       ]  linuxExlusiveTestCases :: [TestTree]-linuxExlusiveTestCases = [ testCaseSteps "simple-bios" $ testDirectory isBiosCradle "./tests/projects/simple-bios/B.hs" | not isWindows ]+linuxExlusiveTestCases =+  [ testCaseSteps "simple-bios" $ testDirectory isBiosCradle "./tests/projects/simple-bios" "B.hs" | not isWindows ] -testDirectory :: (Cradle Void -> Bool) -> FilePath -> (String -> IO ()) -> IO ()-testDirectory cradlePred fp step = do-  a_fp <- canonicalizePath fp+testDirectory :: (Cradle Void -> Bool) -> FilePath -> FilePath -> (String -> IO ()) -> IO ()+testDirectory cradlePred rootDir file step =+  -- We need to copy over the directory to somewhere outside the source tree+  -- when we test, since the cabal.project/stack.yaml/hie.yaml file in the root+  -- of this repository interferes with the test cradles!+  withTempCopy rootDir $ \rootDir' -> do+    fp <- canonicalizePath (rootDir' </> file)+    crd <- initialiseCradle cradlePred fp step+    step "Get runtime GHC library directory"+    testGetGhcLibDir crd+    step "Get runtime GHC version"+    testGetGhcVersion crd+    step "Initialise Flags"+    testLoadFile crd fp step++-- | Here we are testing that the cradle's method of obtaining the ghcLibDir+-- always works.+testGetGhcLibDir :: Cradle a -> IO ()+testGetGhcLibDir crd = do+  libDirRes <- getRuntimeGhcLibDir crd+  libDirRes `shouldSatisfy` isSuccess+  where isSuccess (CradleSuccess _) = True+        isSuccess _ = False++-- | Here we are testing that the cradle's method of getting the runtime ghc+-- version is correct - which while testing, should be the version that we have+-- built the tests with. This will fail if you compiled the tests with a ghc+-- that doesn't equal the ghc on your path though :(+testGetGhcVersion :: Cradle a -> IO ()+testGetGhcVersion crd =+  getRuntimeGhcVersion crd `shouldReturn` CradleSuccess VERSION_ghc++testDirectoryFail :: (Cradle Void -> Bool) -> FilePath -> FilePath -> (CradleError -> Expectation) -> (String -> IO ()) -> IO ()+testDirectoryFail cradlePred rootDir file cradleFailPred step = do+  withTempCopy rootDir $ \rootDir' -> do+    fp <- canonicalizePath (rootDir' </> file)+    crd <- initialiseCradle cradlePred fp step+    step "Initialise Flags"+    testLoadFileCradleFail crd fp cradleFailPred step++initialiseCradle :: (Cradle Void -> Bool) -> FilePath -> (String -> IO ()) -> IO (Cradle Void)+initialiseCradle cradlePred a_fp step = do   step $ "Finding Cradle for: " ++ a_fp   mcfg <- findCradle a_fp   step $ "Loading Cradle: " ++ show mcfg   crd <- case mcfg of           Just cfg -> loadCradle cfg           Nothing -> loadImplicitCradle a_fp-  when (not $ cradlePred crd) $ error $ "Cradle is incorrect: " ++ show (actionName $ cradleOptsProg crd)-  step "Initialise Flags"-  testLoadFile crd a_fp step+  crd `shouldSatisfy` cradlePred+  pure crd  testLoadFile :: Cradle a -> FilePath -> (String -> IO ()) -> IO ()-testLoadFile crd fp step = do-  a_fp <- canonicalizePath fp-  withCurrentDirectory (cradleRootDir crd) $-    withGHC' $ do-      let relFp = makeRelative (cradleRootDir crd) a_fp-      res <- initializeFlagsWithCradleWithMessage (Just (\_ n _ _ -> step (show n))) relFp crd-      case res of-        CradleSuccess (ini, _) -> do+testLoadFile crd a_fp step = do+  libDirRes <- getRuntimeGhcLibDir crd+  handleCradleResult libDirRes $ \libDir ->+    withCurrentDirectory (cradleRootDir crd) $+      G.runGhc (Just libDir) $ do+        let relFp = makeRelative (cradleRootDir crd) a_fp+        res <- initializeFlagsWithCradleWithMessage (Just (\_ n _ _ -> step (show n))) relFp crd+        handleCradleResult res $ \(ini, _) -> do           liftIO (step "Initial module load")           sf <- ini           case sf of             -- Test resetting the targets             Succeeded -> setTargetFilesWithMessage (Just (\_ n _ _ -> step (show n))) [(a_fp, a_fp)]-            Failed -> error "Module loading failed"-        CradleNone -> error "None"-        CradleFail (CradleError _ex stde) -> error (unlines stde)+            Failed -> liftIO $ expectationFailure "Module loading failed" +testLoadFileCradleFail :: Cradle a -> FilePath -> (CradleError -> Expectation) -> (String -> IO ()) -> IO ()+testLoadFileCradleFail crd a_fp cradleErrorExpectation step = do+  -- don't spin up a ghc session, just run the opts program manually since+  -- we're not guaranteed to be able to get the ghc libdir if the cradle is+  -- failing+  withCurrentDirectory (cradleRootDir crd) $ do+    let relFp = makeRelative (cradleRootDir crd) a_fp+    res <- runCradle (cradleOptsProg crd) (step . show) relFp+    case res of+      CradleSuccess _ -> liftIO $ expectationFailure "Cradle loaded successfully"+      CradleNone -> liftIO $ expectationFailure "Unexpected none-Cradle"+      CradleFail crdlFail -> liftIO $ cradleErrorExpectation crdlFail++testLoadCradleDependencies :: (Cradle Void -> Bool) -> FilePath -> FilePath -> ([FilePath] -> Expectation) -> (String -> IO ()) -> IO ()+testLoadCradleDependencies cradlePred rootDir file dependencyPred step =+  withTempCopy rootDir $ \rootDir' -> do+    a_fp <- canonicalizePath (rootDir' </> file)+    crd <- initialiseCradle cradlePred a_fp step+    libDirRes <- getRuntimeGhcLibDir crd+    handleCradleResult libDirRes $ \libDir -> do+      step "Initialise Flags"+      withCurrentDirectory (cradleRootDir crd) $+        G.runGhc (Just libDir) $ do+          let relFp = makeRelative (cradleRootDir crd) a_fp+          res <- initializeFlagsWithCradleWithMessage (Just (\_ n _ _ -> step (show n))) relFp crd+          handleCradleResult res $ \(_, options) ->+            liftIO $ dependencyPred (componentDependencies options)++handleCradleResult :: MonadIO m => CradleLoadResult a -> (a -> m ()) -> m ()+handleCradleResult (CradleSuccess x) f = f x+handleCradleResult CradleNone _ = liftIO $ expectationFailure "Unexpected none-Cradle"+handleCradleResult (CradleFail (CradleError _deps _ex stde)) _ =+  liftIO $ expectationFailure ("Unexpected cradle fail" ++ unlines stde)+ findCradleForModule :: FilePath -> Maybe FilePath -> (String -> IO ()) -> IO () findCradleForModule fp expected' step = do   expected <- maybe (return Nothing) (fmap Just . canonicalizePath) expected'   a_fp <- canonicalizePath fp   step "Finding cradle"-  mcfg <- findCradle a_fp-  unless (mcfg == expected)-    $  error-    $  "Expected cradle: "-    ++ show expected-    ++ ", Actual: "-    ++ show mcfg+  findCradle a_fp `shouldReturn` expected -testImplicitCradle :: FilePath -> ActionName Void -> (String -> IO ()) -> IO ()-testImplicitCradle fp' expectedActionName step = do-  fp <- canonicalizePath fp'-  step "Inferring implicit cradle"-  crd <- loadImplicitCradle fp :: IO (Cradle Void)-  unless (actionName (cradleOptsProg crd) == expectedActionName)-    $ error $ "Expected cradle: " <> show expectedActionName-    <> "\n, Actual: " <> show (actionName (cradleOptsProg crd))-  step "Initialize flags"-  testLoadFile crd fp step+testImplicitCradle :: FilePath -> FilePath -> ActionName Void -> (String -> IO ()) -> IO ()+testImplicitCradle rootDir file expectedActionName step =+  withTempCopy rootDir $ \rootDir' -> do+    fp <- makeAbsolute (rootDir' </> file)+    step "Inferring implicit cradle"+    crd <- loadImplicitCradle fp :: IO (Cradle Void) +    actionName (cradleOptsProg crd) `shouldBe` expectedActionName++    expectedCradleRootDir <- makeAbsolute rootDir'+    cradleRootDir crd `shouldBe` expectedCradleRootDir++    step "Initialize flags"+    testLoadFile crd fp step++withTempCopy :: FilePath -> (FilePath -> IO a) -> IO a+withTempCopy srcDir f =+  withSystemTempDirectory "hie-bios-test" $ \newDir -> do+    copyDir srcDir newDir+    f newDir++copyDir :: FilePath -> FilePath -> IO ()+copyDir src dst = do+  contents <- listDirectory src+  forM_ contents $ \file -> do+    unless (file `elem` ignored) $ do+      let srcFp = src </> file+          dstFp = dst </> file+      isDir <- doesDirectoryExist srcFp+      if isDir+        then createDirectory dstFp >> copyDir srcFp dstFp+        else copyFile srcFp dstFp+  where ignored = ["dist", "dist-newstyle", ".stack-work"]++ writeStackYamlFiles :: IO ()-writeStackYamlFiles = do-  let yamlFile = stackYaml stackYamlResolver-  forM_ stackProjects $ \proj ->-    writeFile (proj </> "stack.yaml") yamlFile+writeStackYamlFiles =+  forM_ stackProjects $ \(proj, pkgs) ->+    writeFile (proj </> "stack.yaml") (stackYaml stackYamlResolver pkgs) -stackProjects :: [FilePath]+stackProjects :: [(FilePath, [FilePath])] stackProjects =-  [ "tests" </> "projects" </> "multi-stack"-  , "tests" </> "projects" </> "simple-stack"-  , "tests" </> "projects" </> "space stack"-  , "tests" </> "projects" </> "implicit-stack"-  , "tests" </> "projects" </> "implicit-stack-multi"+  [ ("tests" </> "projects" </> "multi-stack", ["."])+  , ("tests" </> "projects" </> "failing-stack", ["."])+  , ("tests" </> "projects" </> "simple-stack", ["."])+  , ("tests" </> "projects" </> "nested-stack", [".", "./sub-comp"])+  , ("tests" </> "projects" </> "space stack", ["."])+  , ("tests" </> "projects" </> "implicit-stack", ["."])+  , ("tests" </> "projects" </> "implicit-stack-multi", ["."])   ] -stackYaml :: String -> String-stackYaml resolver = unlines ["resolver: " ++ resolver, "packages:", "- ."]+stackYaml :: String -> [FilePath] -> String+stackYaml resolver pkgs = unlines+  $ ["resolver: " ++ resolver, "packages:"]+  ++ map ("- " ++) pkgs  stackYamlResolver :: String stackYamlResolver = #if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,10,1,0)))-  "TODO"+  "nightly-2020-06-25" #elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,8,1,0)))   "lts-15.10" #elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,6,5,0)))
tests/ParserTests.hs view
@@ -20,8 +20,11 @@     assertParser "cabal-1.yaml" (noDeps (Cabal (Just "lib:hie-bios")))     assertParser "stack-config.yaml" (noDeps (Stack Nothing))     --assertParser "bazel.yaml" (noDeps Bazel)-    assertParser "bios-1.yaml" (noDeps (Bios "program" Nothing))-    assertParser "bios-2.yaml" (noDeps (Bios "program" (Just "dep-program")))+    assertParser "bios-1.yaml" (noDeps (Bios (Program "program") Nothing))+    assertParser "bios-2.yaml" (noDeps (Bios (Program "program") (Just (Program "dep-program"))))+    assertParser "bios-3.yaml" (noDeps (Bios (Command "shellcommand") Nothing))+    assertParser "bios-4.yaml" (noDeps (Bios (Command "shellcommand") (Just (Command "dep-shellcommand"))))+    assertParser "bios-5.yaml" (noDeps (Bios (Command "shellcommand") (Just (Program "dep-program"))))     assertParser "dependencies.yaml" (Config (CradleConfig ["depFile"] (Cabal (Just "lib:hie-bios"))))     assertParser "direct.yaml" (noDeps (Direct ["list", "of", "arguments"]))     assertParser "none.yaml" (noDeps None)
+ tests/configs/bios-3.yaml view
@@ -0,0 +1,3 @@+cradle:+  bios:+    shell: "shellcommand"
+ tests/configs/bios-4.yaml view
@@ -0,0 +1,4 @@+cradle:+  bios:+    shell: "shellcommand"+    dependency-shell: "dep-shellcommand"
+ tests/configs/bios-5.yaml view
@@ -0,0 +1,4 @@+cradle:+  bios:+    shell: "shellcommand"+    dependency-program: "dep-program"
+ tests/projects/failing-bios/A.hs view
@@ -0,0 +1,1 @@+module A where
+ tests/projects/failing-bios/B.hs view
@@ -0,0 +1,3 @@+module B where++import A
+ tests/projects/failing-bios/hie.yaml view
@@ -0,0 +1,5 @@+cradle:+  bios:+    shell: "exit 1"+dependencies:+- hie.yaml
+ tests/projects/failing-cabal/MyLib.hs view
@@ -0,0 +1,4 @@+module MyLib (someFunc) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/failing-cabal/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/failing-cabal/failing-cabal.cabal view
@@ -0,0 +1,13 @@+cabal-version:       >=1.10+name:                failing-cabal+version:             0.1.0.0+license-file:        LICENSE+author:              fendor+build-type:          Simple++library+  exposed-modules:     MyLib+  build-depends:       base >=4,+                       containers < 1 && > 1+                       --         ^^^^^^^^^^ <<< Invalid constraint+  default-language:    Haskell2010
+ tests/projects/failing-cabal/hie.yaml view
@@ -0,0 +1,2 @@+cradle:+  cabal:
+ tests/projects/failing-stack/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/failing-stack/failing-stack.cabal view
@@ -0,0 +1,16 @@+cabal-version: 1.12+name:           failing-stack+version:        0.1.0.0+description:    None+build-type:     Simple++library+  exposed-modules:+      Lib+  hs-source-dirs:+      src+  build-depends:+      base >=4.7 && <5,+      containes < 1 && > 1+      --         ^^^^^^^^^^ <<< Invalid constraint+  default-language: Haskell2010
+ tests/projects/failing-stack/hie.yaml view
@@ -0,0 +1,2 @@+cradle:+  stack:
+ tests/projects/failing-stack/src/Lib.hs view
@@ -0,0 +1,6 @@+module Lib+    ( someFunc+    ) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/implicit-cabal-deep-project/Main.hs view
@@ -0,0 +1,1 @@+main = pure ()
+ tests/projects/implicit-cabal-deep-project/README view
@@ -0,0 +1,3 @@+Here we are testing that if we have a nested cabal package but a cabal.project+a directory above it, hie-bios will implicitly pick up the cabal.project and+NOT the nested cabal package
+ tests/projects/implicit-cabal-deep-project/cabal.project view
@@ -0,0 +1,2 @@+packages: .+          foo
+ tests/projects/implicit-cabal-deep-project/foo/Main.hs view
@@ -0,0 +1,1 @@+main = pure ()
+ tests/projects/implicit-cabal-deep-project/foo/foo.cabal view
@@ -0,0 +1,9 @@+cabal-version:       >=1.10+name:                foo+version:             0.1.0.0+build-type:          Simple++executable foo+  main-is:             Main.hs+  build-depends:       base >=4+  default-language:    Haskell2010
+ tests/projects/implicit-cabal-deep-project/implicit-cabal-deep-project.cabal view
@@ -0,0 +1,8 @@+cabal-version:       >=1.10+name:                implicit-cabal-deep-project+version:             0.1.0.0+build-type:          Simple+executable foo+  main-is:             Main.hs+  build-depends:       base >=4+  default-language:    Haskell2010
+ tests/projects/implicit-cabal-no-project/Main.hs view
@@ -0,0 +1,1 @@+main = pure ()
+ tests/projects/implicit-cabal-no-project/implicit-cabal-no-project.cabal view
@@ -0,0 +1,8 @@+cabal-version:       >=1.10+name:                implicit-cabal-no-project+version:             0.1.0.0+build-type:          Simple+executable foo+  main-is:             Main.hs+  build-depends:       base >=4+  default-language:    Haskell2010
+ tests/projects/monorepo-cabal/A/A.cabal view
@@ -0,0 +1,8 @@+cabal-version:       >=1.10+name:                A+version:             0.1.0.0+build-type:          Simple+executable A+  main-is:             Main.hs+  build-depends:       base >=4.8, B+  default-language:    Haskell2010
+ tests/projects/monorepo-cabal/A/Main.hs view
@@ -0,0 +1,4 @@+module Main where++main :: IO ()+main = putStrLn "Hello, Haskell!"
+ tests/projects/monorepo-cabal/A/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/monorepo-cabal/B/B.cabal view
@@ -0,0 +1,10 @@+cabal-version:       >=1.10++name:                B+version:             0.1.0.0+build-type:          Simple++library+  exposed-modules:     MyLib+  build-depends:       base >=4.8+  default-language:    Haskell2010
+ tests/projects/monorepo-cabal/B/MyLib.hs view
@@ -0,0 +1,4 @@+module MyLib (someFunc) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/monorepo-cabal/B/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/monorepo-cabal/cabal.project view
@@ -0,0 +1,1 @@+packages: ./A ./B
+ tests/projects/monorepo-cabal/hie.yaml view
@@ -0,0 +1,6 @@+cradle:+  cabal:+    - path: ./A+      component: "exe:A"+    - path: ./B+      component: "lib:B"
+ tests/projects/nested-cabal/MyLib.hs view
@@ -0,0 +1,4 @@+module MyLib (someFunc) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/nested-cabal/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/nested-cabal/cabal.project view
@@ -0,0 +1,2 @@+packages: ./sub-comp+          ./
+ tests/projects/nested-cabal/hie.yaml view
@@ -0,0 +1,6 @@+cradle:+  cabal:+    - path: ./sub-comp+      component: "lib:sub-comp"+    - path: ./+      component: "lib:nested-cabal"
+ tests/projects/nested-cabal/nested-cabal.cabal view
@@ -0,0 +1,9 @@+cabal-version:       >=1.10+name:                nested-cabal+version:             0.1.0.0+build-type:          Simple++library+  exposed-modules:     MyLib+  build-depends:       base < 5+  default-language:    Haskell2010
+ tests/projects/nested-cabal/sub-comp/Lib.hs view
@@ -0,0 +1,4 @@+module Lib (someFunc) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/nested-cabal/sub-comp/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/nested-cabal/sub-comp/sub-comp.cabal view
@@ -0,0 +1,10 @@+cabal-version:       >=1.10+name:                sub-comp+version:             0.1.0.0+build-type:          Simple++library+  exposed-modules:     Lib+  build-depends:       base < 5+  -- hs-source-dirs:+  default-language:    Haskell2010
+ tests/projects/nested-stack/MyLib.hs view
@@ -0,0 +1,4 @@+module MyLib (someFunc) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/nested-stack/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/nested-stack/hie.yaml view
@@ -0,0 +1,6 @@+cradle:+  stack:+    - path: ./sub-comp+      component: "sub-comp:lib"+    - path: ./+      component: "nested-stack:lib"
+ tests/projects/nested-stack/nested-stack.cabal view
@@ -0,0 +1,9 @@+cabal-version:       >=1.10+name:                nested-stack+version:             0.1.0.0+build-type:          Simple++library+  exposed-modules:     MyLib+  build-depends:       base < 5+  default-language:    Haskell2010
+ tests/projects/nested-stack/sub-comp/Lib.hs view
@@ -0,0 +1,4 @@+module Lib (someFunc) where++someFunc :: IO ()+someFunc = putStrLn "someFunc"
+ tests/projects/nested-stack/sub-comp/Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ tests/projects/nested-stack/sub-comp/sub-comp.cabal view
@@ -0,0 +1,10 @@+cabal-version:       >=1.10+name:                sub-comp+version:             0.1.0.0+build-type:          Simple++library+  exposed-modules:     Lib+  build-depends:       base < 5+  -- hs-source-dirs:+  default-language:    Haskell2010
+ tests/projects/simple-bios-shell/A.hs view
@@ -0,0 +1,1 @@+module A where
+ tests/projects/simple-bios-shell/B.hs view
@@ -0,0 +1,3 @@+module B where++import A
+ tests/projects/simple-bios-shell/hie.yaml view
@@ -0,0 +1,10 @@+cradle:+  bios:+    shell: |+      :; echo "-Wall" >> $HIE_BIOS_OUTPUT+      :; echo "A" >> $HIE_BIOS_OUTPUT+      :; echo "B" >> $HIE_BIOS_OUTPUT+      :; exit 0+      ECHO "-Wall" >> %HIE_BIOS_OUTPUT%+      ECHO "A" >> %HIE_BIOS_OUTPUT%+      ECHO "B" >> %HIE_BIOS_OUTPUT%
@@ -0,0 +1,4 @@+module A where++a :: Int+a = 5
@@ -0,0 +1,16 @@+cradle:+  multi:+    - path: ./a+      config:+        cradle:+          direct:+            arguments: ["a"]+    - path: ./b+      config:+        cradle:+          direct:+            arguments: ["b"]+    - path: ./c+      config:+        cradle:+          none: