├── .github ├── dependabot.yml └── workflows │ └── haskell.yml ├── .gitignore ├── .hlint.yaml ├── ChangeLog.md ├── LICENSE ├── README.md ├── Setup.hs ├── cabal.project ├── docs └── release.md ├── exe └── Main.hs ├── hie-bios.cabal ├── src └── HIE │ ├── Bios.hs │ └── Bios │ ├── Config.hs │ ├── Config │ └── YAML.hs │ ├── Cradle.hs │ ├── Environment.hs │ ├── Flags.hs │ ├── Ghc │ ├── Api.hs │ ├── Check.hs │ ├── Doc.hs │ ├── Gap.hs │ ├── Load.hs │ └── Logger.hs │ ├── Internal │ └── Debug.hs │ ├── Types.hs │ └── Wrappers.hs ├── tests ├── BiosTests.hs ├── ParserTests.hs ├── Utils.hs ├── configs │ ├── bazel.yaml │ ├── bios-1.yaml │ ├── bios-2.yaml │ ├── bios-3.yaml │ ├── bios-4.yaml │ ├── bios-5.yaml │ ├── cabal-1.yaml │ ├── cabal-empty-config.yaml │ ├── cabal-multi.yaml │ ├── cabal-with-both.yaml │ ├── cabal-with-project.yaml │ ├── ch-cabal.yaml │ ├── ch-stack.yaml │ ├── default.yaml │ ├── dependencies.yaml │ ├── direct.yaml │ ├── keys-not-unique-fails.yaml │ ├── multi-cabal-with-project.yaml │ ├── multi-ch.yaml │ ├── multi-stack-with-yaml.yaml │ ├── multi.yaml │ ├── nested-cabal-multi.yaml │ ├── nested-stack-multi.yaml │ ├── none.yaml │ ├── obelisk.yaml │ ├── stack-config.yaml │ ├── stack-multi.yaml │ ├── stack-with-both.yaml │ └── stack-with-yaml.yaml └── projects │ ├── cabal-with-ghc-and-project │ ├── cabal-with-ghc.cabal │ ├── cabal.project.9.2.8 │ ├── hie.yaml │ └── src │ │ └── MyLib.hs │ ├── cabal-with-ghc │ ├── cabal-with-ghc.cabal │ ├── cabal.project │ ├── hie.yaml │ └── src │ │ └── MyLib.hs │ ├── cabal-with-project │ ├── cabal-with-project.cabal │ ├── cabal.project.9.2.8 │ ├── hie.yaml │ └── src │ │ └── MyLib.hs │ ├── deps-bios-new │ ├── A.hs │ ├── B.hs │ ├── hie-bios.sh │ └── hie.yaml │ ├── failing-bios-ghc │ ├── A.hs │ ├── B.hs │ └── hie.yaml │ ├── failing-bios │ ├── A.hs │ ├── B.hs │ └── hie.yaml │ ├── failing-cabal │ ├── MyLib.hs │ ├── failing-cabal.cabal │ └── hie.yaml │ ├── failing-multi-repl-cabal-project │ ├── NotInPath.hs │ └── multi-repl-cabal-fail │ │ ├── app │ │ └── Main.hs │ │ ├── multi-repl-cabal-fail.cabal │ │ └── src │ │ ├── Fail.hs │ │ └── Lib.hs │ ├── failing-stack │ ├── failing-stack.cabal │ ├── hie.yaml │ └── src │ │ └── Lib.hs │ ├── implicit-cabal-deep-project │ ├── Main.hs │ ├── README │ ├── cabal.project │ ├── foo │ │ ├── Main.hs │ │ └── foo.cabal │ └── implicit-cabal-deep-project.cabal │ ├── implicit-cabal-no-project │ ├── Main.hs │ └── implicit-cabal-no-project.cabal │ ├── implicit-cabal │ ├── Main.hs │ ├── cabal.project │ └── implicit-cabal.cabal │ ├── implicit-stack-multi │ ├── Main.hs │ ├── implicit-stack-multi.cabal │ └── other-package │ │ ├── Main.hs │ │ └── other-package.cabal │ ├── implicit-stack │ ├── Main.hs │ └── implicit-stack.cabal │ ├── monorepo-cabal │ ├── A │ │ ├── A.cabal │ │ └── Main.hs │ ├── B │ │ ├── B.cabal │ │ └── MyLib.hs │ ├── cabal.project │ └── hie.yaml │ ├── multi-cabal-with-project │ ├── appA │ │ ├── appA.cabal │ │ └── src │ │ │ └── Lib.hs │ ├── appB │ │ ├── appB.cabal │ │ └── src │ │ │ └── Lib.hs │ ├── cabal.project.9.2.8 │ └── hie.yaml │ ├── multi-cabal │ ├── app │ │ └── Main.hs │ ├── cabal.project │ ├── hie.yaml │ ├── multi-cabal.cabal │ └── src │ │ └── Lib.hs │ ├── multi-direct │ ├── A.hs │ ├── B.hs │ └── hie.yaml │ ├── multi-stack-with-yaml │ ├── appA │ │ ├── Setup.hs │ │ ├── appA.cabal │ │ └── src │ │ │ └── Lib.hs │ ├── appB │ │ ├── Setup.hs │ │ ├── appB.cabal │ │ └── src │ │ │ └── Lib.hs │ └── hie.yaml │ ├── multi-stack │ ├── app │ │ └── Main.hs │ ├── cabal.project │ ├── hie.yaml │ ├── multi-stack.cabal │ └── src │ │ └── Lib.hs │ ├── nested-cabal │ ├── MyLib.hs │ ├── cabal.project │ ├── hie.yaml │ ├── nested-cabal.cabal │ └── sub-comp │ │ ├── Lib.hs │ │ └── sub-comp.cabal │ ├── nested-stack │ ├── MyLib.hs │ ├── hie.yaml │ ├── nested-stack.cabal │ └── sub-comp │ │ ├── Lib.hs │ │ └── sub-comp.cabal │ ├── simple-bios-ghc │ ├── A.hs │ ├── B.hs │ ├── hie-bios.sh │ └── hie.yaml │ ├── simple-bios-shell │ ├── A.hs │ ├── B.hs │ └── hie.yaml │ ├── simple-bios │ ├── A.hs │ ├── B.hs │ ├── hie-bios-deps.sh │ ├── hie-bios.sh │ └── hie.yaml │ ├── simple-cabal │ ├── .ghci │ ├── A.hs │ ├── B.hs │ ├── cabal.project │ ├── hie.yaml │ └── simple-cabal.cabal │ ├── simple-direct │ ├── A.hs │ ├── B.hs │ └── hie.yaml │ ├── simple-stack │ ├── A.hs │ ├── B.hs │ ├── cabal.project │ ├── hie.yaml │ └── simple-stack.cabal │ ├── space stack │ ├── A.hs │ ├── B.hs │ ├── hie.yaml │ └── stackproj.cabal │ ├── stack-with-yaml │ ├── Setup.hs │ ├── app │ │ └── Main.hs │ ├── hie.yaml │ ├── src │ │ └── Lib.hs │ └── stack-with-yaml.cabal │ └── symlink-test │ ├── a │ └── A.hs │ └── hie.yaml └── wrappers ├── bazel ├── cabal └── cabal.hs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # NOTE: Dependabot official configuration documentation: 4 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem 5 | 6 | # Maintain dependencies for internal GitHub Actions CI for pull requests 7 | - package-ecosystem: 'github-actions' 8 | directory: '/' 9 | schedule: 10 | interval: 'weekly' 11 | -------------------------------------------------------------------------------- /.github/workflows/haskell.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '**' 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | cabal: ['3.14.1.0', '3.10.2.0'] 19 | ghc: ['9.12.2', '9.10.1', '9.8.4', '9.6.7', '9.4.8'] 20 | os: [ubuntu-latest, macOS-latest, windows-latest] 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - id: extra-ghc 25 | uses: haskell-actions/setup@v2 26 | with: 27 | cabal-version: ${{ matrix.cabal }} 28 | ghc-version: '9.2.8' # This needs to be in-sync with 'extraGhcVersion' 29 | 30 | - uses: haskell-actions/setup@v2 31 | with: 32 | ghc-version: ${{ matrix.ghc }} 33 | cabal-version: ${{ matrix.cabal }} 34 | enable-stack: true 35 | 36 | - name: Print extra ghc version 37 | # Needs to be in sync with 'extraGhcVersion' 38 | run: ghc-9.2.8 --version 39 | 40 | - name: Print normal ghc version 41 | run: ghc --version 42 | 43 | - name: Instruct stack to use system ghc if possible 44 | run: stack config set system-ghc --global true 45 | 46 | - name: Cache Cabal 47 | uses: actions/cache@v4.2.3 48 | with: 49 | path: | 50 | ~/.cabal/packages 51 | ~/.cabal/store 52 | dist-newstyle 53 | key: ${{ runner.os }}-${{ matrix.cabal }}-${{ matrix.ghc }}-${{ hashFiles('**/*.cabal', '**/cabal.project', '**/cabal.project.freeze') }} 54 | restore-keys: ${{ runner.os }}-${{ matrix.ghc }}- 55 | 56 | - run: cabal update 57 | - name: Build 58 | run: cabal build 59 | - name: Test Parser 60 | run: cabal test parser-tests --test-show-details=direct 61 | - name: Test Bios 62 | # Run all tests in the project 63 | run: cabal test bios-tests --test-show-details=direct --test-options="--ignore-tool-deps --debug" 64 | 65 | sdist: 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: haskell-actions/setup@v2 70 | - name: Check 71 | run: cabal check 72 | - name: Generate sdist 73 | run: cabal sdist 74 | - uses: actions/upload-artifact@v4 75 | with: 76 | name: 'sdist' 77 | path: dist-newstyle/sdist/hie-bios*.tar.gz 78 | 79 | shellcheck: 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: actions/checkout@v4 83 | - uses: ludeeus/action-shellcheck@master 84 | with: 85 | ignore_paths: tests 86 | 87 | windows-wrapper: 88 | runs-on: windows-latest 89 | steps: 90 | - uses: actions/checkout@v4 91 | - uses: haskell-actions/setup@v2 92 | with: 93 | ghc-version: ${{ matrix.ghc }} 94 | - name: Compile Windows wrapper 95 | run: ghc -hide-all-packages -package base -package directory -package process -Wall -Werror wrappers/cabal.hs -o CabalWrapper 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | dist-newstyle/ 3 | .stack-work/ 4 | tests/projects/**/stack*.yaml 5 | tests/projects/**/stack*.yaml.lock 6 | cabal.project.local* 7 | .vscode/ 8 | tests/**/Setup.hs 9 | -------------------------------------------------------------------------------- /.hlint.yaml: -------------------------------------------------------------------------------- 1 | # HLint configuration file 2 | # https://github.com/ndmitchell/hlint 3 | ########################## 4 | 5 | # This file contains a template configuration file, which is typically 6 | # placed as .hlint.yaml in the root of your project 7 | 8 | 9 | # Warnings currently triggered by your code 10 | - ignore: {name: "Use newtype instead of data"} 11 | - ignore: {name: "Use camelCase"} 12 | - ignore: {name: "Redundant $"} 13 | - ignore: {name: "Redundant bracket"} 14 | - ignore: {name: "Use catMaybes"} 15 | - ignore: {name: "Use <$>"} 16 | - ignore: {name: "Eta reduce"} 17 | - ignore: {name: "Use const"} 18 | - ignore: {name: "Move brackets to avoid $"} 19 | - ignore: {name: "Parse error"} 20 | 21 | 22 | # Specify additional command line arguments 23 | # 24 | # - arguments: [--color, --cpp-simple, -XQuasiQuotes] 25 | 26 | 27 | # Control which extensions/flags/modules/functions can be used 28 | # 29 | # - extensions: 30 | # - default: false # all extension are banned by default 31 | # - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used 32 | # - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module 33 | # 34 | # - flags: 35 | # - {name: -w, within: []} # -w is allowed nowhere 36 | # 37 | # - modules: 38 | # - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' 39 | # - {name: Control.Arrow, within: []} # Certain modules are banned entirely 40 | # 41 | # - functions: 42 | # - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules 43 | 44 | 45 | # Add custom hints for this project 46 | # 47 | # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" 48 | # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} 49 | 50 | # The hints are named by the string they display in warning messages. 51 | # For example, if you see a warning starting like 52 | # 53 | # Main.hs:116:51: Warning: Redundant == 54 | # 55 | # You can refer to that hint with `{name: Redundant ==}` (see below). 56 | 57 | # Turn on hints that are off by default 58 | # 59 | # Ban "module X(module X) where", to require a real export list 60 | # - warn: {name: Use explicit module export list} 61 | # 62 | # Replace a $ b $ c with a . b $ c 63 | # - group: {name: dollar, enabled: true} 64 | # 65 | # Generalise map to fmap, ++ to <> 66 | # - group: {name: generalise, enabled: true} 67 | 68 | 69 | # Ignore some builtin hints 70 | # - ignore: {name: Use let} 71 | # - ignore: {name: Use const, within: SpecialModule} # Only within certain modules 72 | 73 | 74 | # Define some custom infix operators 75 | # - fixity: infixr 3 ~^#^~ 76 | 77 | 78 | # To generate a suitable file for HLint do: 79 | # $ hlint --default > .hlint.yaml 80 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # ChangeLog hie-bios 2 | 3 | * Rename `findFileUpwards` as `findFileUpwardsPredicate` and implement 4 | `findFileUpward` which does have better performance guarantees. 5 | 6 | ## 2025-04-25 - 0.15.0 7 | 8 | * Use consistent logging for test cases [#461](https://github.com/haskell/hie-bios/pull/461) 9 | * Use cabal path when available [#458](https://github.com/haskell/hie-bios/pull/458) 10 | * Keep track of not loaded files for cabal [#453](https://github.com/haskell/hie-bios/pull/453) 11 | * Allow GHC 9.12 [#449](https://github.com/haskell/hie-bios/pull/449) 12 | * Fix cabal check [#448](https://github.com/haskell/hie-bios/pull/448) 13 | 14 | ## 2024-05-20 - 0.14.1 15 | 16 | * Allow building with GHC 9.10.1 [#435](https://github.com/haskell/hie-bios/pull/435) 17 | 18 | ## 2024-04-22 - 0.14.0 19 | 20 | * Add Loading Style option to 'runAction' [#433](https://github.com/haskell/hie-bios/pull/433) 21 | * Cleanup CPP for GHCs < 9.2, fix most GHC warnings [#429](https://github.com/haskell/hie-bios/pull/429) 22 | * Update GHC versions in CI, drop ghcs not supported by hls [#428](https://github.com/haskell/hie-bios/pull/428) 23 | 24 | ## 2023-11-14 - 0.13.1 25 | 26 | * Add CI support for GHC 9.8.1 [#419](https://github.com/haskell/hie-bios/pull/419) 27 | * 9.8 support [#417](https://github.com/haskell/hie-bios/pull/417) 28 | * Avoid deadlocks in multi-component support [#416](https://github.com/haskell/hie-bios/pull/416) 29 | * Accept directories in 'findCradle' [#415](https://github.com/haskell/hie-bios/pull/415) 30 | * Drop old GHC version support [#414](https://github.com/haskell/hie-bios/pull/414) 31 | 32 | ## 2023-08-22 - 0.13.0 33 | 34 | * Multi Component cabal support [#409](https://github.com/haskell/hie-bios/pull/409) 35 | * Make sure cabal caches can be found [#408](https://github.com/haskell/hie-bios/pull/408) 36 | * Rename project-file to cabalProject in hie.yaml [#407](https://github.com/haskell/hie-bios/pull/407) 37 | * Update README for new project-file key [#403](https://github.com/haskell/hie-bios/pull/403) 38 | * Add more informative log messages for cradle running [#406](https://github.com/haskell/hie-bios/pull/406) 39 | * Add cabal.project support for cabal cradles [#357](https://github.com/haskell/hie-bios/pull/357) 40 | 41 | ## 2023-11-13 - 0.12.1 42 | 43 | * 9.8 support [#417](https://github.com/haskell/hie-bios/pull/417) 44 | 45 | ## 2023-03-13 - 0.12.0 46 | 47 | * 9.6 support [#392](https://github.com/haskell/hie-bios/pull/392) 48 | * Better support for multi component projects [#387](https://github.com/haskell/hie-bios/pull/387) 49 | * Remove unused dependencies from hie-bios [#381](https://github.com/haskell/hie-bios/pull/381) 50 | * Add logs over commands [#375](https://github.com/haskell/hie-bios/pull/375) 51 | 52 | ## 2022-09-13 - 0.11.0 53 | 54 | * Compatibility with aeson 1.5 [#368](https://github.com/haskell/hie-bios/pull/368) 55 | * Add GHC 9.4 support [#366](https://github.com/haskell/hie-bios/pull/366) 56 | * Actually run the bios-tests when tool-deps are ignored [#365](https://github.com/haskell/hie-bios/pull/365) 57 | * They have been accidentally disabled since 0.9.0. 58 | * Completely overhaul test-suite [#356](https://github.com/haskell/hie-bios/pull/356) 59 | 60 | ## 2022-07-26 - 0.10.0 61 | 62 | * Apply Hlint suggestions [#354](https://github.com/haskell/hie-bios/pull/354) 63 | * Cabal cradle: change error message on failure [#353](https://github.com/haskell/hie-bios/pull/353) 64 | * Refactor parsing of hie.yaml files [#329](https://github.com/haskell/hie-bios/pull/329) 65 | * Make sure we test the same versions as HLS [#346](https://github.com/haskell/hie-bios/pull/346) 66 | * Move logging from hslogger to co-log [#347](https://github.com/haskell/hie-bios/pull/347) 67 | * Demote process output to Debug severity [#348](https://github.com/haskell/hie-bios/pull/348) 68 | * Fix typos [#342](https://github.com/haskell/hie-bios/pull/342) 69 | 70 | ## 2022-03-07 - 0.9.1 71 | 72 | * Ignore .ghci files while querying project GHC [#337](https://github.com/haskell/hie-bios/pull/337) 73 | * Fixes a bug where hie-bios fails to load cabal cradles with `.ghci` files 74 | * Improve error messages if cabal invocation fails [#338](https://github.com/haskell/hie-bios/pull/338) 75 | * Allow text-2.0 [#335](https://github.com/haskell/hie-bios/pull/335) 76 | 77 | ## 2022-02-25 - 0.9.0 78 | 79 | * Use the proper GHC version given by cabal [#282](https://github.com/haskell/hie-bios/pull/282) 80 | * In particular, honour the `with-compiler` field in `cabal.project` 81 | * Drop support for GHC 8.4 [#331](https://github.com/haskell/hie-bios/pull/331) 82 | 83 | ## 2022-01-06 - 0.8.1 84 | 85 | * Add support for GHC 9.0.2 [#322](https://github.com/haskell/hie-bios/pull/322) 86 | 87 | ## 2021-11-29 - 0.8.0 88 | 89 | * Support aeson >= 2.0. [#313](https://github.com/haskell/hie-bios/pull/313) 90 | * Remove CradleOpt Type [#293](https://github.com/haskell/hie-bios/pull/293) 91 | 92 | ## 2021-08-30 - 0.7.6 93 | 94 | * Don't look for NIX_GHC_LIBDIR as it is redundant [#294](https://github.com/mpickering/hie-bios/pull/294) 95 | * Add compatibility for GHC 9.0 and 9.2 [#300](https://github.com/mpickering/hie-bios/pull/300) 96 | * Add CPP statements for IncludeSpecs [#307](https://github.com/mpickering/hie-bios/pull/307) 97 | * Refactor implicit config discovery [#291](https://github.com/mpickering/hie-bios/pull/291) 98 | * Log stderr of stack to display more informative error messages to users. [#254](https://github.com/mpickering/hie-bios/pull/254) 99 | 100 | ## 2021-03-21 - 0.7.5 101 | 102 | ### Bug Fixes 103 | 104 | * Improve out-of-the-box support for dynamically linked GHC. [#286](https://github.com/mpickering/hie-bios/pull/286), [#287](https://github.com/mpickering/hie-bios/pull/287) 105 | 106 | ## 2021-02-19 - 0.7.4 107 | 108 | ### Bug Fixes 109 | 110 | * Create the cache directory on linux if it is missing [#283](https://github.com/mpickering/hie-bios/pull/283) 111 | 112 | ## 2021-01-29 - 0.7.3 113 | 114 | * Set builddir for cabal [#264](https://github.com/mpickering/hie-bios/pull/264) 115 | * Essentially, change the build directory for cabal to the [`XDG_CACHE_HOME`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) 116 | directory (e.g. `~/.cache/hie-bios/...`). This way, user 117 | invocations of cabal will no longer trigger a `configure` step, improving 118 | the overall developer experience. 119 | * Optparse-applicative CLI [#276](https://github.com/mpickering/hie-bios/pull/276) 120 | 121 | ## 2020-12-16 - 0.7.2 122 | 123 | * Faster Bios protocol [#271](https://github.com/mpickering/hie-bios/pull/271) 124 | * Modify unreachable cabal website links [#259](https://github.com/mpickering/hie-bios/pull/259) 125 | * Only take the last line of output in getRuntimeGhcX [#256](https://github.com/mpickering/hie-bios/pull/256) 126 | 127 | ## 2020-09-01 - 0.7.1 128 | 129 | * Add explicit type for stack.yaml location [#243](https://github.com/mpickering/hie-bios/pull/243) 130 | * In particular, fixes a regression with `hie.yaml` and standalone-files for stack 131 | * Reduce noise in Extra-Source-File field [#239](https://github.com/mpickering/hie-bios/pull/239) 132 | 133 | ## 2020-08-27 - 0.7.0 134 | 135 | ### New Features 136 | 137 | * Allow specifying a stack.yaml for stack configurations [#230](https://github.com/mpickering/hie-bios/pull/230) 138 | * Pass HIE_BIOS_ARG to the dependencies program [#235](https://github.com/mpickering/hie-bios/pull/235) 139 | 140 | ### API Changes 141 | 142 | * Change Config CradleType 143 | 144 | ## 2020-08-08 - 0.6.3 145 | 146 | ### API Addition 147 | 148 | * Expose yamlConfig [#237](https://github.com/mpickering/hie-bios/pull/237) 149 | 150 | ## 2020-08-08 - 0.6.2 151 | 152 | ### New Features 153 | 154 | * Add optional ghc-path field in bios cradles [#231](https://github.com/mpickering/hie-bios/pull/231) 155 | 156 | ## 2020-07-12 - 0.6.1 157 | 158 | ### Bug Fixes 159 | 160 | * Expose 'readProcessWithCwd' [#227](https://github.com/mpickering/hie-bios/pull/227) 161 | * Fix mistakes in the ChangeLog [#228](https://github.com/mpickering/hie-bios/pull/228) 162 | 163 | ## 2020-07-12 - 0.6.0 164 | 165 | ### New Features 166 | 167 | * 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) 168 | * Add shell and dependency-shell attributes to bios cradle type [#188](https://github.com/mpickering/hie-bios/pull/188) 169 | * Store dependencies in CradleError [#186](https://github.com/mpickering/hie-bios/pull/186) 170 | 171 | ### Bug Fixes 172 | 173 | * Improve the README [#225](https://github.com/mpickering/hie-bios/pull/225) 174 | * Detect implicit cabal cradle in the absence of cabal.project [#221](https://github.com/mpickering/hie-bios/pull/221) 175 | * Dont resolve symlinks in cradle discovery [#219](https://github.com/mpickering/hie-bios/pull/219) 176 | * Make Cradle dependencies for stack and cabal more reasonable [#209](https://github.com/mpickering/hie-bios/pull/209) 177 | * This ships with a known bug: `stack` lists cradle dependencies from 178 | sub-directories incorrectly. 179 | * Fix absolute mains [#205](https://github.com/mpickering/hie-bios/pull/205) 180 | * Improve filtering of rts arguments from stack and cabal cradles [#197](https://github.com/mpickering/hie-bios/pull/197) 181 | * Make package db paths absolute [#193](https://github.com/mpickering/hie-bios/pull/193) 182 | * Add cabal.project.local to cabal cradle dependencies [#184](https://github.com/mpickering/hie-bios/pull/184) 183 | * Remove outdated reference to $HIE_BIOS_GHC[_ARGS] 184 | 185 | ## 2020-06-26 - 0.5.1 186 | 187 | * Fix printing of current directory in wrapper script [#206](https://github.com/mpickering/hie-bios/pull/206) 188 | * Export Cradle utilizes [#189](https://github.com/mpickering/hie-bios/pull/189) 189 | 190 | ## 2020-05-08 - 0.5.0 191 | 192 | * Add cabal.project.local to cabal cradle dependencies [#184](https://github.com/mpickering/hie-bios/pull/184) 193 | * Remove unused environment variables to simplify code. [#182](https://github.com/mpickering/hie-bios/pull/182) 194 | * Clean up hie-bios wrapper scripts after they are used. [#179](https://github.com/mpickering/hie-bios/pull/179) 195 | * Avoid error in windows due to temp file being locked. [#175](https://github.com/mpickering/hie-bios/pull/175) 196 | * Get building with ghc-8.10. [#173](https://github.com/mpickering/hie-bios/pull/173) 197 | * Add getCompilerOptionsWithLogger convenience function. 198 | * Add componentRoot to ComponentOptions. [#166](https://github.com/mpickering/hie-bios/pull/166) 199 | Options may be relative to the componentRoot. 200 | * Add makeDynFlagsAbsolute to fix mangling of ghc options starting with "-i". [#166](https://github.com/mpickering/hie-bios/pull/166) 201 | Breaks backwards-compatibility, because ComponentOptions now may contain 202 | filepaths relative to the component root directory. 203 | This function needs to be invoked on the parsed 'DynFlags' to normalise the filepaths. 204 | * Fix Ghci Script parses space in Filepath as Module (#162) 205 | * Correct path to .hie-bios example in readme (#159) 206 | * Relax upper bound for 'extra' (#161) 207 | 208 | ## 2020-01-29 - 0.4.0 209 | 210 | * Return CompilerOptions in initialization (#130) 211 | * Implement hook into config parser (#131) 212 | * Enable GHC 8.8.1 windows ci (#128) 213 | * Catch permission errors in cradle discovery (#127) 214 | * Add explicit cradle predicates and multi cradle depend on its cradles (#119) 215 | * Fix outdated direct cradle in README (#124) 216 | * Pass filepath to cabal v2-repl when getting flags (#123) 217 | * CPP for GHC 8.10 compatibility (#134) 218 | * Derive Ord for ComponentOptions (#133) 219 | * Lower the required version of the GHC dependency (#138) 220 | * Add tests for implicit cradles (#135) 221 | * Add Functor instance for Cradle and ActionName (#140) 222 | * Remove Show instance from public API (#146) 223 | * Add Show instance for CradleLoadResult (#145) 224 | * Typo in debug message (#144) 225 | * Add lower bound for aeson and clean-up API (#142) 226 | 227 | ## 2019-12-19 - 0.3.2 228 | 229 | * Compile windows wrapper script in a a more appropiate directory. (#109) 230 | * Fix situation in wrapper script when environmental variable wasn't set. (#109) 231 | 232 | ## 2019-12-18 - 0.3.1 233 | 234 | * Fix bug in the windows wrapper script (#108) 235 | 236 | ## 2019-12-15 - 0.3.0 237 | 238 | * Add multi cradle, cabal multi cradle and none cradle 239 | * Remove obelisk, bazel and default cradle types 240 | * bios program now expects arguments to be separated by newlines rather than 241 | spaces. (#80) 242 | * Only try to use stack cradle if `stack` is executable. 243 | * Filter out `-w -v0` from cabal output when using cabal cradle. 244 | * Initialise plugins when loading a module. 245 | * Interface file cache persists between loads -- this greatly speeds up 246 | reloading a project if the options don't change. 247 | * Reuse wrapper executable on windows if one already exists. 248 | * Make stack cradle work more like the cabal cradle 249 | - Syntax for specifying a specific component 250 | - Targets are read from the ghci script file 251 | * Cradles now use a temporary file to communicate arguments to hie-bios. 252 | bios cradles should consult the HIE_BIOS_OUTPUT envvar for the filepath to 253 | write the arguments seperated by newlines. 254 | 255 | ## 2019-09-19 - 0.2.1 256 | 257 | * Make stack cradle use the same wrappers as cabal cradle. Fixes some issues 258 | on windows. 259 | 260 | ## 2019-09-18 - 0.2.0 261 | 262 | * Compat with 8.2 and 8.8 263 | * Add support for explicitly specifying dependencies for a cradle 264 | * Separate arguments by null bytes, so arguments can contain spaces 265 | (cabal/stack wrapper) 266 | * Add --help to CLI 267 | * Fix the directories that certain processes run in 268 | 269 | ## 2019-09-07 - 0.1.1 270 | 271 | * Compat with GHC 8.4 272 | * Fix long paths issue on windows 273 | * Handle projects with .o files 274 | 275 | ## 2019-09-06 - 0.1.0 276 | 277 | * First release 278 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, IIJ Innovation Institute Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of the copyright holders nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | 3 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | This document described the release process for `hie-bios`. 4 | 5 | * [ ] Bump `version` in `hie-bios.cabal` [compliant with PVP](https://pvp.haskell.org/) 6 | * [ ] Update ChangeLog.md to include all changes. 7 | * [ ] GitHub CI must be green 8 | * [ ] Upload release candidate 9 | * `cabal sdist; cabal upload dist-newstyle/sdist/hie-bios-.tar.gz` 10 | * [ ] Upload release candidate documentation 11 | * `cabal haddock --haddock-for-hackage; cabal upload --documentation dist-newstyle/hie-bios--docs.tar.gz` 12 | * [ ] Manually verify the docs render correctly 13 | * [ ] Upload release 14 | * `cabal upload --publish dist-newstyle/sdist/hie-bios-.tar.gz; cabal upload --publish --documentation dist-newstyle/hie-bios--docs.tar.gz` 15 | * [ ] Create release tag for GitHub 16 | * [ ] `git tag -a release-` 17 | * [ ] `git push --tags` -------------------------------------------------------------------------------- /exe/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | 4 | module Main where 5 | 6 | import Control.Monad ( forM ) 7 | import qualified Colog.Core as L 8 | import Data.Version (showVersion) 9 | import Prettyprinter 10 | import Options.Applicative 11 | import System.Directory (getCurrentDirectory) 12 | import System.IO (stdout, hSetEncoding, utf8) 13 | import System.FilePath( () ) 14 | 15 | import HIE.Bios 16 | import HIE.Bios.Ghc.Check 17 | import HIE.Bios.Ghc.Gap as Gap 18 | import HIE.Bios.Internal.Debug 19 | import HIE.Bios.Types (LoadStyle(LoadFile)) 20 | import Paths_hie_bios 21 | 22 | ---------------------------------------------------------------- 23 | 24 | progVersion :: String 25 | progVersion = "hie-bios version " ++ showVersion version ++ " compiled by GHC " ++ Gap.ghcVersion ++ "\n" 26 | 27 | data Command 28 | = Check { checkTargetFiles :: [FilePath] } 29 | | Flags { flagTargetFiles :: [FilePath] } 30 | | Debug { debugComponents :: FilePath } 31 | | ConfigInfo { configFiles :: [FilePath] } 32 | | CradleInfo { cradleFiles :: [FilePath] } 33 | | Root 34 | | Version 35 | 36 | 37 | filepathParser :: Parser [FilePath] 38 | filepathParser = some (argument str ( metavar "TARGET_FILES...")) 39 | 40 | progInfo :: ParserInfo Command 41 | progInfo = info (progParser <**> helper) 42 | ( fullDesc 43 | <> progDesc "hie-bios is the way to specify how haskell-language-server and ghcide set up a GHC API session.\ 44 | \Delivers the full set of flags to pass to GHC in order to build the project." 45 | <> header progVersion 46 | <> footer "You can report issues/contribute at https://github.com/mpickering/hie-bios") 47 | 48 | progParser :: Parser Command 49 | progParser = subparser 50 | (command "check" (info (Check <$> filepathParser) (progDesc "Try to load modules into the GHC API.")) 51 | <> command "flags" (info (Flags <$> filepathParser) (progDesc "Print out the options that hie-bios thinks you will need to load a file.")) 52 | <> command "debug" (info (Debug <$> argument str ( metavar "TARGET_FILES...")) (progDesc "Print out the options that hie-bios thinks you will need to load a file.")) 53 | <> command "config" (info (ConfigInfo <$> filepathParser) (progDesc "Print out the cradle config.")) 54 | <> command "cradle" (info (CradleInfo <$> filepathParser) (progDesc ".")) 55 | <> command "root" (info (pure Root) (progDesc "Display the path towards the selected hie.yaml.")) 56 | <> command "version" (info (pure Version) (progDesc "Print version and exit.")) 57 | ) 58 | 59 | 60 | ---------------------------------------------------------------- 61 | 62 | main :: IO () 63 | main = do 64 | hSetEncoding stdout utf8 65 | cwd <- getCurrentDirectory 66 | cmd <- execParser progInfo 67 | let 68 | printLog (L.WithSeverity l sev) = "[" ++ show sev ++ "] " ++ show (pretty l) 69 | logger :: forall a . Pretty a => L.LogAction IO (L.WithSeverity a) 70 | logger = L.cmap printLog L.logStringStderr 71 | 72 | cradle <- 73 | -- find cradle does a takeDirectory on the argument, so make it into a file 74 | findCradle (cwd "File.hs") >>= \case 75 | Just yaml -> loadCradle logger yaml 76 | Nothing -> loadImplicitCradle logger (cwd "File.hs") 77 | 78 | res <- case cmd of 79 | Check targetFiles -> checkSyntax logger cradle targetFiles 80 | Debug files -> case files of 81 | [] -> debugInfo (cradleRootDir cradle) cradle 82 | fp -> debugInfo fp cradle 83 | Flags files -> case files of 84 | -- TODO force optparse to acquire one 85 | [] -> error "too few arguments" 86 | _ -> do 87 | res <- forM files $ \fp -> do 88 | res <- getCompilerOptions fp LoadFile cradle 89 | case res of 90 | CradleFail (CradleError _deps _ex err _fps) -> 91 | return $ "Failed to show flags for \"" 92 | ++ fp 93 | ++ "\": " ++ show err 94 | CradleSuccess opts -> 95 | return $ unlines ["Options: " ++ show (componentOptions opts) 96 | ,"ComponentDir: " ++ componentRoot opts 97 | ,"Dependencies: " ++ show (componentDependencies opts) ] 98 | CradleNone -> return $ "No flags/None Cradle: component " ++ fp ++ " should not be loaded" 99 | return (unlines res) 100 | ConfigInfo files -> configInfo files 101 | CradleInfo files -> cradleInfo logger files 102 | Root -> rootInfo cradle 103 | Version -> return progVersion 104 | putStr res 105 | -------------------------------------------------------------------------------- /hie-bios.cabal: -------------------------------------------------------------------------------- 1 | Cabal-Version: 2.2 2 | Name: hie-bios 3 | Version: 0.15.0 4 | Author: Matthew Pickering 5 | Maintainer: Matthew Pickering 6 | License: BSD-3-Clause 7 | License-File: LICENSE 8 | Homepage: https://github.com/haskell/hie-bios 9 | Synopsis: Set up a GHC API session 10 | Description: Set up a GHC API session and obtain flags required to compile a source file 11 | 12 | Category: Development 13 | Build-Type: Simple 14 | -- No glob syntax until GHC 8.6 because of stack 15 | extra-doc-files: ChangeLog.md 16 | Extra-Source-Files: README.md 17 | wrappers/cabal 18 | wrappers/cabal.hs 19 | tests/configs/*.yaml 20 | tests/projects/cabal-with-ghc/cabal-with-ghc.cabal 21 | tests/projects/cabal-with-ghc/cabal.project 22 | tests/projects/cabal-with-ghc/hie.yaml 23 | tests/projects/cabal-with-ghc/src/MyLib.hs 24 | tests/projects/cabal-with-ghc-and-project/cabal-with-ghc.cabal 25 | tests/projects/cabal-with-ghc-and-project/cabal.project.9.2.8 26 | tests/projects/cabal-with-ghc-and-project/hie.yaml 27 | tests/projects/cabal-with-ghc-and-project/src/MyLib.hs 28 | tests/projects/cabal-with-project/cabal-with-project.cabal 29 | tests/projects/cabal-with-project/cabal.project.9.2.8 30 | tests/projects/cabal-with-project/hie.yaml 31 | tests/projects/cabal-with-project/src/MyLib.hs 32 | tests/projects/symlink-test/a/A.hs 33 | tests/projects/symlink-test/hie.yaml 34 | tests/projects/deps-bios-new/A.hs 35 | tests/projects/deps-bios-new/B.hs 36 | tests/projects/deps-bios-new/hie-bios.sh 37 | tests/projects/deps-bios-new/hie.yaml 38 | tests/projects/multi-direct/A.hs 39 | tests/projects/multi-direct/B.hs 40 | tests/projects/multi-direct/hie.yaml 41 | tests/projects/multi-cabal/app/Main.hs 42 | tests/projects/multi-cabal/cabal.project 43 | tests/projects/multi-cabal/hie.yaml 44 | tests/projects/multi-cabal/multi-cabal.cabal 45 | tests/projects/multi-cabal/src/Lib.hs 46 | tests/projects/multi-cabal-with-project/appA/appA.cabal 47 | tests/projects/multi-cabal-with-project/appA/src/Lib.hs 48 | tests/projects/multi-cabal-with-project/appB/appB.cabal 49 | tests/projects/multi-cabal-with-project/appB/src/Lib.hs 50 | tests/projects/multi-cabal-with-project/cabal.project.9.2.8 51 | tests/projects/multi-cabal-with-project/hie.yaml 52 | tests/projects/monorepo-cabal/cabal.project 53 | tests/projects/monorepo-cabal/hie.yaml 54 | tests/projects/monorepo-cabal/A/Main.hs 55 | tests/projects/monorepo-cabal/A/A.cabal 56 | tests/projects/monorepo-cabal/B/MyLib.hs 57 | tests/projects/monorepo-cabal/B/B.cabal 58 | tests/projects/multi-stack/app/Main.hs 59 | tests/projects/multi-stack/cabal.project 60 | tests/projects/multi-stack/hie.yaml 61 | tests/projects/multi-stack/multi-stack.cabal 62 | tests/projects/multi-stack/src/Lib.hs 63 | tests/projects/failing-bios/A.hs 64 | tests/projects/failing-bios/B.hs 65 | tests/projects/failing-bios/hie.yaml 66 | tests/projects/failing-bios-ghc/A.hs 67 | tests/projects/failing-bios-ghc/B.hs 68 | tests/projects/failing-bios-ghc/hie.yaml 69 | tests/projects/failing-cabal/failing-cabal.cabal 70 | tests/projects/failing-cabal/hie.yaml 71 | tests/projects/failing-cabal/MyLib.hs 72 | tests/projects/failing-stack/failing-stack.cabal 73 | tests/projects/failing-stack/hie.yaml 74 | tests/projects/failing-stack/src/Lib.hs 75 | tests/projects/nested-cabal/nested-cabal.cabal 76 | tests/projects/nested-cabal/cabal.project 77 | tests/projects/nested-cabal/hie.yaml 78 | tests/projects/nested-cabal/MyLib.hs 79 | tests/projects/nested-cabal/sub-comp/sub-comp.cabal 80 | tests/projects/nested-cabal/sub-comp/Lib.hs 81 | tests/projects/nested-stack/nested-stack.cabal 82 | tests/projects/nested-stack/hie.yaml 83 | tests/projects/nested-stack/MyLib.hs 84 | tests/projects/nested-stack/sub-comp/sub-comp.cabal 85 | tests/projects/nested-stack/sub-comp/Lib.hs 86 | tests/projects/simple-bios/A.hs 87 | tests/projects/simple-bios/B.hs 88 | tests/projects/simple-bios/hie-bios.sh 89 | tests/projects/simple-bios/hie-bios-deps.sh 90 | tests/projects/simple-bios/hie.yaml 91 | tests/projects/simple-bios-ghc/A.hs 92 | tests/projects/simple-bios-ghc/B.hs 93 | tests/projects/simple-bios-ghc/hie-bios.sh 94 | tests/projects/simple-bios-ghc/hie.yaml 95 | tests/projects/simple-bios-shell/A.hs 96 | tests/projects/simple-bios-shell/B.hs 97 | tests/projects/simple-bios-shell/hie.yaml 98 | tests/projects/simple-cabal/A.hs 99 | tests/projects/simple-cabal/B.hs 100 | tests/projects/simple-cabal/cabal.project 101 | tests/projects/simple-cabal/hie.yaml 102 | tests/projects/simple-cabal/simple-cabal.cabal 103 | tests/projects/simple-direct/A.hs 104 | tests/projects/simple-direct/B.hs 105 | tests/projects/simple-direct/hie.yaml 106 | tests/projects/simple-stack/A.hs 107 | tests/projects/simple-stack/B.hs 108 | tests/projects/simple-stack/cabal.project 109 | tests/projects/simple-stack/hie.yaml 110 | tests/projects/simple-stack/simple-stack.cabal 111 | "tests/projects/space stack/A.hs" 112 | "tests/projects/space stack/B.hs" 113 | "tests/projects/space stack/hie.yaml" 114 | "tests/projects/space stack/stackproj.cabal" 115 | tests/projects/implicit-cabal/cabal.project 116 | tests/projects/implicit-cabal/implicit-cabal.cabal 117 | tests/projects/implicit-cabal/Main.hs 118 | tests/projects/implicit-cabal-no-project/implicit-cabal-no-project.cabal 119 | tests/projects/implicit-cabal-no-project/Main.hs 120 | tests/projects/implicit-cabal-deep-project/README 121 | tests/projects/implicit-cabal-deep-project/implicit-cabal-deep-project.cabal 122 | tests/projects/implicit-cabal-deep-project/Main.hs 123 | tests/projects/implicit-cabal-deep-project/cabal.project 124 | tests/projects/implicit-cabal-deep-project/foo/foo.cabal 125 | tests/projects/implicit-cabal-deep-project/foo/Main.hs 126 | tests/projects/implicit-stack/implicit-stack.cabal 127 | tests/projects/implicit-stack/Main.hs 128 | tests/projects/implicit-stack-multi/implicit-stack-multi.cabal 129 | tests/projects/implicit-stack-multi/Main.hs 130 | tests/projects/implicit-stack-multi/other-package/other-package.cabal 131 | tests/projects/implicit-stack-multi/other-package/Main.hs 132 | tests/projects/multi-stack-with-yaml/appA/appA.cabal 133 | tests/projects/multi-stack-with-yaml/appA/src/Lib.hs 134 | tests/projects/multi-stack-with-yaml/appB/appB.cabal 135 | tests/projects/multi-stack-with-yaml/appB/src/Lib.hs 136 | tests/projects/multi-stack-with-yaml/hie.yaml 137 | tests/projects/stack-with-yaml/app/Main.hs 138 | tests/projects/stack-with-yaml/hie.yaml 139 | tests/projects/stack-with-yaml/stack-with-yaml.cabal 140 | tests/projects/stack-with-yaml/src/Lib.hs 141 | tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/app/Main.hs 142 | tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/multi-repl-cabal-fail.cabal 143 | tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/src/Fail.hs 144 | tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/src/Lib.hs 145 | tests/projects/failing-multi-repl-cabal-project/NotInPath.hs 146 | 147 | tested-with: GHC ==9.4.8 || ==9.6.7 || ==9.8.4 || ==9.10.1 || ==9.12.2 148 | 149 | Library 150 | Default-Language: Haskell2010 151 | GHC-Options: -Wall 152 | HS-Source-Dirs: src 153 | Exposed-Modules: HIE.Bios 154 | HIE.Bios.Config 155 | HIE.Bios.Config.YAML 156 | HIE.Bios.Cradle 157 | HIE.Bios.Environment 158 | HIE.Bios.Internal.Debug 159 | HIE.Bios.Flags 160 | HIE.Bios.Types 161 | HIE.Bios.Ghc.Api 162 | HIE.Bios.Ghc.Check 163 | HIE.Bios.Ghc.Doc 164 | HIE.Bios.Ghc.Gap 165 | HIE.Bios.Ghc.Load 166 | HIE.Bios.Ghc.Logger 167 | HIE.Bios.Wrappers 168 | Other-Modules: Paths_hie_bios 169 | autogen-modules: Paths_hie_bios 170 | Build-Depends: 171 | base >= 4.16 && < 5, 172 | aeson >= 1.4.4 && < 2.3, 173 | base16-bytestring >= 0.1.1 && < 1.1, 174 | bytestring >= 0.10.8 && < 0.13, 175 | co-log-core ^>= 0.3.0, 176 | deepseq >= 1.4.3 && < 1.6, 177 | exceptions ^>= 0.10, 178 | cryptohash-sha1 >= 0.11.100 && < 0.12, 179 | directory >= 1.3.0 && < 1.4, 180 | filepath >= 1.4.1 && < 1.6, 181 | time >= 1.8.0 && < 1.15, 182 | extra >= 1.6.14 && < 1.9, 183 | prettyprinter ^>= 1.6 || ^>= 1.7.0, 184 | ghc >= 9.2.1 && < 9.13, 185 | transformers >= 0.5.2 && < 0.7, 186 | temporary >= 1.2 && < 1.4, 187 | template-haskell >= 2.18 && <2.24, 188 | text >= 1.2.3 && < 2.2, 189 | unix-compat >= 0.5.1 && < 0.8, 190 | unordered-containers >= 0.2.9 && < 0.3, 191 | yaml >= 0.10.0 && < 0.12, 192 | file-embed >= 0.0.11 && < 1, 193 | conduit >= 1.3 && < 2, 194 | conduit-extra >= 1.3 && < 2 195 | 196 | 197 | Executable hie-bios 198 | Default-Language: Haskell2010 199 | Main-Is: Main.hs 200 | Other-Modules: Paths_hie_bios 201 | autogen-modules: Paths_hie_bios 202 | GHC-Options: -Wall 203 | HS-Source-Dirs: exe 204 | Build-Depends: base >= 4.16 && < 5 205 | , co-log-core 206 | , directory 207 | , filepath 208 | , hie-bios 209 | , optparse-applicative >= 0.18.1 && < 0.19 210 | , prettyprinter 211 | 212 | test-suite parser-tests 213 | type: exitcode-stdio-1.0 214 | default-language: Haskell2010 215 | build-depends: 216 | base, 217 | aeson, 218 | filepath, 219 | hie-bios, 220 | tasty, 221 | tasty-hunit, 222 | yaml 223 | 224 | hs-source-dirs: tests/ 225 | ghc-options: -threaded -Wall 226 | main-is: ParserTests.hs 227 | 228 | test-suite bios-tests 229 | type: exitcode-stdio-1.0 230 | default-language: Haskell2010 231 | build-depends: 232 | base, 233 | co-log-core, 234 | extra, 235 | transformers, 236 | tasty, 237 | tasty-hunit, 238 | tasty-expected-failure, 239 | hie-bios, 240 | filepath, 241 | directory, 242 | prettyprinter, 243 | temporary, 244 | text, 245 | ghc 246 | 247 | hs-source-dirs: tests/ 248 | ghc-options: -threaded -Wall 249 | main-is: BiosTests.hs 250 | other-modules: Utils 251 | 252 | Source-Repository head 253 | Type: git 254 | Location: https://github.com/haskell/hie-bios.git 255 | -------------------------------------------------------------------------------- /src/HIE/Bios.hs: -------------------------------------------------------------------------------- 1 | {- | The HIE Bios 2 | 3 | Provides an abstraction over the GHC Api to initialise a GHC session and 4 | loading modules in a project. 5 | 6 | Defines the `hie.yaml` file specification. This is used to explicitly configure 7 | how a project should be built by GHC. 8 | 9 | -} 10 | module HIE.Bios ( 11 | -- * Find and load a Cradle 12 | Cradle(..) 13 | , CradleLoadResult(..) 14 | , CradleError(..) 15 | , findCradle 16 | , loadCradle 17 | , loadImplicitCradle 18 | , defaultCradle 19 | -- * Compiler Options 20 | , ComponentOptions(..) 21 | , getCompilerOptions 22 | -- * Initialising a GHC session from a Cradle 23 | , initSession 24 | -- * Loading targets into a GHC session 25 | , loadFile 26 | 27 | ) where 28 | 29 | import HIE.Bios.Cradle 30 | import HIE.Bios.Types 31 | import HIE.Bios.Flags 32 | import HIE.Bios.Environment 33 | import HIE.Bios.Ghc.Load 34 | -------------------------------------------------------------------------------- /src/HIE/Bios/Config.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NamedFieldPuns #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE DeriveFunctor #-} 4 | {-# LANGUAGE PatternSynonyms #-} 5 | -- | Logic and datatypes for parsing @hie.yaml@ files. 6 | module HIE.Bios.Config( 7 | readConfig, 8 | Config(..), 9 | CradleConfig(..), 10 | CabalType, 11 | pattern CabalType, 12 | cabalComponent, 13 | cabalProjectFile, 14 | StackType, 15 | pattern StackType, 16 | stackComponent, 17 | stackYaml, 18 | CradleTree(..), 19 | Callable(..) 20 | ) where 21 | 22 | import Control.Exception 23 | import Data.Maybe (mapMaybe, fromMaybe) 24 | import Data.Monoid (Last(..)) 25 | import Data.Aeson (JSONPath) 26 | import Data.Yaml 27 | import Data.Yaml.Internal (Warning(..)) 28 | 29 | import HIE.Bios.Config.YAML (CradleConfigYAML) 30 | import qualified HIE.Bios.Config.YAML as YAML 31 | 32 | 33 | -- | Configuration that can be used to load a 'Cradle'. 34 | -- A configuration has roughly the following form: 35 | -- 36 | -- @ 37 | -- cradle: 38 | -- cabal: 39 | -- component: "lib:hie-bios" 40 | -- @ 41 | newtype Config a = Config { cradle :: CradleConfig a } 42 | deriving (Show, Eq, Functor) 43 | 44 | data CradleConfig a = 45 | CradleConfig 46 | { cradleDependencies :: [FilePath] 47 | -- ^ Dependencies of a cradle. 48 | -- Dependencies are expected to be relative to the root directory. 49 | -- The given files are not required to exist. 50 | , cradleTree :: CradleTree a 51 | -- ^ Type of the cradle to use. Actions to obtain 52 | -- compiler flags from are dependant on this field. 53 | } 54 | deriving (Show, Eq, Functor) 55 | 56 | data Callable = Program FilePath | Command String 57 | deriving (Show, Eq) 58 | 59 | -- | A cabal yaml configuration consists of component configuration and project configuration. 60 | -- 61 | -- The former specifies how we can find the compilation flags for any filepath 62 | -- in the project. 63 | -- There might be an explicit mapping from source directories to components, 64 | -- or we let cabal figure it out on its own. 65 | -- 66 | -- Project configuration is the 'cabal.project' file, we is by default named 67 | -- 'cabal.project'. We allow to override that name to have an HLS specific 68 | -- project configuration file. 69 | data CabalType 70 | = CabalType_ { _cabalComponent :: !(Last String), _cabalProjectFile :: !(Last FilePath) } 71 | deriving (Eq) 72 | 73 | instance Semigroup CabalType where 74 | CabalType_ cr cpr <> CabalType_ cl cpl = CabalType_ (cr <> cl) (cpr <> cpl) 75 | 76 | instance Monoid CabalType where 77 | mempty = CabalType_ mempty mempty 78 | 79 | pattern CabalType :: Maybe String -> Maybe FilePath -> CabalType 80 | pattern CabalType { cabalComponent, cabalProjectFile } = CabalType_ (Last cabalComponent) (Last cabalProjectFile) 81 | {-# COMPLETE CabalType #-} 82 | 83 | instance Show CabalType where 84 | show = show . Cabal 85 | 86 | data StackType 87 | = StackType_ { _stackComponent :: !(Last String) , _stackYaml :: !(Last String) } 88 | deriving (Eq) 89 | 90 | instance Semigroup StackType where 91 | StackType_ cr yr <> StackType_ cl yl = StackType_ (cr <> cl) (yr <> yl) 92 | 93 | instance Monoid StackType where 94 | mempty = StackType_ mempty mempty 95 | 96 | pattern StackType :: Maybe String -> Maybe FilePath -> StackType 97 | pattern StackType { stackComponent, stackYaml } = StackType_ (Last stackComponent) (Last stackYaml) 98 | {-# COMPLETE StackType #-} 99 | 100 | instance Show StackType where 101 | show = show . Stack 102 | 103 | data CradleTree a 104 | = Cabal { cabalType :: !CabalType } 105 | | CabalMulti { defaultCabal :: !CabalType, subCabalComponents :: [ (FilePath, CabalType) ] } 106 | | Stack { stackType :: !StackType } 107 | | StackMulti { defaultStack :: !StackType, subStackComponents :: [ (FilePath, StackType) ] } 108 | -- Bazel and Obelisk used to be supported but bit-rotted and no users have complained. 109 | -- They can be added back if a user 110 | -- | Bazel 111 | -- | Obelisk 112 | | Bios 113 | { call :: Callable 114 | -- ^ Path to program or shell command that retrieves options to compile a file 115 | , depsCall :: Maybe Callable 116 | -- ^ Optional path to program or shell command to obtain cradle dependencies. 117 | -- Each cradle dependency is to be expected to be on a separate line 118 | -- and relative to the root dir of the cradle. 119 | , ghcPath :: Maybe FilePath 120 | -- ^ Optional path to the ghc binary 121 | } 122 | | Direct { arguments :: [String] } 123 | | None 124 | | Multi [ (FilePath, CradleConfig a) ] 125 | | Other { otherConfig :: a, originalYamlValue :: Value } 126 | deriving (Eq, Functor) 127 | 128 | instance Show (CradleTree a) where 129 | show (Cabal comp) = "Cabal {component = " ++ show (cabalComponent comp) ++ "}" 130 | show (CabalMulti d a) = "CabalMulti {defaultCabal = " ++ show d ++ ", subCabalComponents = " ++ show a ++ "}" 131 | show (Stack comp) = "Stack {component = " ++ show (stackComponent comp) ++ ", stackYaml = " ++ show (stackYaml comp) ++ "}" 132 | show (StackMulti d a) = "StackMulti {defaultStack = " ++ show d ++ ", subStackComponents = " ++ show a ++ "}" 133 | show Bios { call, depsCall } = "Bios {call = " ++ show call ++ ", depsCall = " ++ show depsCall ++ "}" 134 | show (Direct args) = "Direct {arguments = " ++ show args ++ "}" 135 | show None = "None" 136 | show (Multi a) = "Multi " ++ show a 137 | show (Other _ val) = "Other {originalYamlValue = " ++ show val ++ "}" 138 | 139 | readConfig :: FromJSON a => FilePath -> IO (Config a) 140 | readConfig fp = do 141 | result <- decodeFileWithWarnings fp 142 | fmap fromYAMLConfig $ either throwIO failOnAnyDuplicate result 143 | where 144 | failOnAnyDuplicate :: ([Warning], CradleConfigYAML a) -> IO (CradleConfigYAML a) 145 | failOnAnyDuplicate (warnings, config) = do 146 | _ <- case mapMaybe failOnDuplicate warnings of 147 | dups@(_:_) -> throwIO $ InvalidYaml $ Just $ YamlException 148 | $ "Duplicate keys are not allowed, found: " ++ show dups 149 | _ -> return () 150 | return config 151 | -- future proofing in case more warnings are added 152 | failOnDuplicate :: Warning -> Maybe JSONPath 153 | failOnDuplicate (DuplicateKey a) = Just a 154 | 155 | fromYAMLConfig :: CradleConfigYAML a -> Config a 156 | fromYAMLConfig cradleYAML = Config $ CradleConfig (fromMaybe [] $ YAML.dependencies cradleYAML) 157 | (toCradleTree $ YAML.cradle cradleYAML) 158 | 159 | toCradleTree :: YAML.CradleComponent a -> CradleTree a 160 | toCradleTree (YAML.Multi cpts) = 161 | Multi $ (\(YAML.MultiSubComponent fp' cfg) -> (fp', cradle $ fromYAMLConfig cfg)) <$> cpts 162 | toCradleTree (YAML.Stack (YAML.StackConfig yaml cpts)) = 163 | case cpts of 164 | YAML.NoComponent -> Stack $ StackType Nothing yaml 165 | (YAML.SingleComponent c) -> Stack $ StackType (Just c) yaml 166 | (YAML.ManyComponents cs) -> StackMulti (StackType Nothing yaml) 167 | ((\(YAML.StackComponent fp' c cYAML) -> 168 | (fp', StackType (Just c) cYAML)) <$> cs) 169 | toCradleTree (YAML.Cabal (YAML.CabalConfig prjFile cpts)) = 170 | case cpts of 171 | YAML.NoComponent -> Cabal $ CabalType Nothing prjFile 172 | (YAML.SingleComponent c) -> Cabal $ CabalType (Just c) prjFile 173 | (YAML.ManyComponents cs) -> CabalMulti (CabalType Nothing prjFile) 174 | ((\(YAML.CabalComponent fp' c cPrjFile) -> 175 | (fp', CabalType (Just c) cPrjFile)) <$> cs) 176 | toCradleTree (YAML.Direct cfg) = Direct (YAML.arguments cfg) 177 | toCradleTree (YAML.Bios cfg) = Bios (toCallable $ YAML.callable cfg) 178 | (toCallable <$> YAML.depsCallable cfg) 179 | (YAML.ghcPath cfg) 180 | toCradleTree (YAML.None _) = None 181 | toCradleTree (YAML.Other cfg) = Other (YAML.otherConfig cfg) 182 | (YAML.originalYamlValue cfg) 183 | 184 | toCallable :: YAML.Callable -> Callable 185 | toCallable (YAML.Program p) = Program p 186 | toCallable (YAML.Shell c) = Command c 187 | -------------------------------------------------------------------------------- /src/HIE/Bios/Config/YAML.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE DuplicateRecordFields #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | {-# LANGUAGE CPP #-} 6 | 7 | -- | Datatypes for parsing @hie.yaml@ files 8 | module HIE.Bios.Config.YAML 9 | ( CradleConfigYAML(..) 10 | , CradleComponent(..) 11 | , MultiSubComponent(..) 12 | , CabalConfig(..) 13 | , CabalComponent(..) 14 | , StackConfig(..) 15 | , StackComponent(..) 16 | , DirectConfig(..) 17 | , BiosConfig(..) 18 | , NoneConfig(..) 19 | , OtherConfig(..) 20 | , OneOrManyComponents(..) 21 | , Callable(..) 22 | ) where 23 | 24 | import Control.Applicative ((<|>)) 25 | import Data.Aeson 26 | #if MIN_VERSION_aeson(2,0,0) 27 | import Data.Aeson.KeyMap (keys) 28 | #else 29 | import qualified Data.HashMap.Strict as Map 30 | import qualified Data.Text as T 31 | #endif 32 | import Data.Aeson.Types (Parser, typeMismatch) 33 | import qualified Data.Char as C (toLower) 34 | import Data.List ((\\)) 35 | import GHC.Generics (Generic) 36 | 37 | #if !MIN_VERSION_aeson(2,0,0) 38 | -- | Backwards compatible type-def for Key 39 | -- This used to be just a Text, but since aeson >= 2 40 | -- this is an opaque datatype. 41 | type Key = T.Text 42 | -- | Backwards compatible type-def for KeyMap 43 | -- This used to be just a HashMap, but since aeson >= 2 44 | -- this is an opaque datatype. 45 | type KeyMap v = Map.HashMap T.Text v 46 | 47 | keys :: KeyMap v -> [Key] 48 | keys = Map.keys 49 | #endif 50 | 51 | checkObjectKeys :: [Key] -> Object -> Parser () 52 | checkObjectKeys allowedKeys obj = 53 | let extraKeys = keys obj \\ allowedKeys 54 | in case extraKeys of 55 | [] -> pure () 56 | _ -> fail $ mconcat [ "Unexpected keys " 57 | , show extraKeys 58 | , ", keys allowed: " 59 | , show allowedKeys 60 | ] 61 | 62 | data CradleConfigYAML a 63 | = CradleConfigYAML { cradle :: CradleComponent a 64 | , dependencies :: Maybe [FilePath] 65 | } deriving (Generic, FromJSON) 66 | 67 | data CradleComponent a 68 | = Multi [MultiSubComponent a] 69 | | Cabal CabalConfig 70 | | Stack StackConfig 71 | | Direct DirectConfig 72 | | Bios BiosConfig 73 | | None NoneConfig 74 | | Other (OtherConfig a) 75 | deriving (Generic) 76 | 77 | instance FromJSON a => FromJSON (CradleComponent a) where 78 | parseJSON = let opts = defaultOptions { constructorTagModifier = fmap C.toLower 79 | , sumEncoding = ObjectWithSingleField 80 | } 81 | in genericParseJSON opts 82 | 83 | data NoneConfig = NoneConfig 84 | 85 | data OtherConfig a 86 | = OtherConfig { otherConfig :: a 87 | , originalYamlValue :: Value 88 | } 89 | 90 | instance FromJSON a => FromJSON (OtherConfig a) where 91 | parseJSON v = OtherConfig 92 | <$> parseJSON v 93 | <*> pure v 94 | 95 | instance FromJSON NoneConfig where 96 | parseJSON Null = pure NoneConfig 97 | parseJSON v = typeMismatch "NoneConfig" v 98 | 99 | data MultiSubComponent a 100 | = MultiSubComponent { path :: FilePath 101 | , config :: CradleConfigYAML a 102 | } deriving (Generic, FromJSON) 103 | 104 | data CabalConfig 105 | = CabalConfig { cabalProject :: Maybe FilePath 106 | , cabalComponents :: OneOrManyComponents CabalComponent 107 | } 108 | 109 | instance FromJSON CabalConfig where 110 | parseJSON v@(Array _) = CabalConfig Nothing . ManyComponents <$> parseJSON v 111 | parseJSON v@(Object obj) = (checkObjectKeys ["cabalProject", "component", "components"] obj) 112 | *> (CabalConfig 113 | <$> obj .:? "cabalProject" 114 | <*> parseJSON v) 115 | parseJSON Null = pure $ CabalConfig Nothing NoComponent 116 | parseJSON v = typeMismatch "CabalConfig" v 117 | 118 | data CabalComponent 119 | = CabalComponent { cabalPath :: FilePath 120 | , cabalComponent :: String 121 | , cabalComponentProject :: Maybe FilePath 122 | } 123 | 124 | instance FromJSON CabalComponent where 125 | parseJSON = 126 | let parseCabalComponent obj = checkObjectKeys ["path", "component", "cabalProject"] obj 127 | *> (CabalComponent 128 | <$> obj .: "path" 129 | <*> obj .: "component" 130 | <*> obj .:? "cabalProject" 131 | ) 132 | in withObject "CabalComponent" parseCabalComponent 133 | 134 | data StackConfig 135 | = StackConfig { stackYaml :: Maybe FilePath 136 | , stackComponents :: OneOrManyComponents StackComponent 137 | } 138 | 139 | data StackComponent 140 | = StackComponent { stackPath :: FilePath 141 | , stackComponent :: String 142 | , stackComponentYAML :: Maybe FilePath 143 | } 144 | 145 | instance FromJSON StackConfig where 146 | parseJSON v@(Array _) = StackConfig Nothing . ManyComponents <$> parseJSON v 147 | parseJSON v@(Object obj) = (checkObjectKeys ["component", "components", "stackYaml"] obj) 148 | *> (StackConfig 149 | <$> obj .:? "stackYaml" 150 | <*> parseJSON v 151 | ) 152 | parseJSON Null = pure $ StackConfig Nothing NoComponent 153 | parseJSON v = typeMismatch "StackConfig" v 154 | 155 | instance FromJSON StackComponent where 156 | parseJSON = 157 | let parseStackComponent obj = (checkObjectKeys ["path", "component", "stackYaml"] obj) 158 | *> (StackComponent 159 | <$> obj .: "path" 160 | <*> obj .: "component" 161 | <*> obj .:? "stackYaml") 162 | in withObject "StackComponent" parseStackComponent 163 | 164 | data OneOrManyComponents component 165 | = SingleComponent String 166 | | ManyComponents [component] 167 | | NoComponent 168 | 169 | instance FromJSON component => FromJSON (OneOrManyComponents component) where 170 | parseJSON = 171 | let parseComponents o = (parseSingleComponent o <|> parseSubComponents o <|> pure NoComponent) 172 | parseSingleComponent o = SingleComponent <$> o .: "component" 173 | parseSubComponents o = ManyComponents <$> o .: "components" 174 | in withObject "Components" parseComponents 175 | 176 | data DirectConfig 177 | = DirectConfig { arguments :: [String] } 178 | deriving (Generic, FromJSON) 179 | 180 | data BiosConfig = 181 | BiosConfig { callable :: Callable 182 | , depsCallable :: Maybe Callable 183 | , ghcPath :: Maybe FilePath 184 | } 185 | 186 | instance FromJSON BiosConfig where 187 | parseJSON = withObject "BiosConfig" parseBiosConfig 188 | 189 | data Callable 190 | = Program FilePath 191 | | Shell String 192 | 193 | parseBiosConfig :: Object -> Parser BiosConfig 194 | parseBiosConfig obj = 195 | let parseCallable o = (Program <$> o .: "program") <|> (Shell <$> o .: "shell") 196 | parseDepsCallable o = (Just . Program <$> o .: "dependency-program") 197 | <|> (Just . Shell <$> o .: "dependency-shell") 198 | <|> (pure Nothing) 199 | parse o = BiosConfig <$> parseCallable o 200 | <*> parseDepsCallable o 201 | <*> (o .:? "with-ghc") 202 | check = checkObjectKeys ["program", "shell", "dependency-program", "dependency-shell", "with-ghc"] 203 | in check obj *> parse obj 204 | -------------------------------------------------------------------------------- /src/HIE/Bios/Environment.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RecordWildCards, CPP #-} 2 | module HIE.Bios.Environment (initSession, getRuntimeGhcLibDir, getRuntimeGhcVersion, makeDynFlagsAbsolute, makeTargetsAbsolute, getCacheDir, addCmdOpts) where 3 | 4 | import GHC (GhcMonad) 5 | import qualified GHC as G 6 | 7 | import Control.Applicative 8 | import Control.Monad (void) 9 | import Control.Monad.IO.Class 10 | 11 | import System.Directory 12 | import System.FilePath 13 | import System.Environment (lookupEnv) 14 | 15 | import qualified Crypto.Hash.SHA1 as H 16 | import qualified Data.ByteString.Char8 as B 17 | import Data.ByteString.Base16 18 | import Data.List 19 | import Data.Char (isSpace) 20 | import Text.ParserCombinators.ReadP hiding (optional) 21 | 22 | import HIE.Bios.Types 23 | import qualified HIE.Bios.Ghc.Gap as Gap 24 | 25 | -- | Start a GHC session and set some sensible options for tooling to use. 26 | -- Creates a folder in the cache directory to cache interface files to make 27 | -- reloading faster. 28 | initSession :: (GhcMonad m) 29 | => ComponentOptions 30 | -> m [G.Target] 31 | initSession ComponentOptions {..} = do 32 | df <- G.getSessionDynFlags 33 | -- Create a unique folder per set of different GHC options, assuming that each different set of 34 | -- GHC options will create incompatible interface files. 35 | let opts_hash = B.unpack $ encode $ H.finalize $ H.updates H.init (map B.pack componentOptions) 36 | cache_dir <- liftIO $ getCacheDir opts_hash 37 | -- Add the user specified options to a fresh GHC session. 38 | (df', targets) <- addCmdOpts componentOptions df 39 | let df'' = makeDynFlagsAbsolute componentRoot df' 40 | void $ G.setSessionDynFlags 41 | (disableOptimisation -- Compile with -O0 as we are not going to produce object files. 42 | $ setIgnoreInterfacePragmas -- Ignore any non-essential information in interface files such as unfoldings changing. 43 | $ writeInterfaceFiles (Just cache_dir) -- Write interface files to the cache 44 | $ setVerbosity 0 -- Set verbosity to zero just in case the user specified `-vx` in the options. 45 | $ Gap.setWayDynamicIfHostIsDynamic -- Add dynamic way if GHC is built with dynamic linking 46 | $ setLinkerOptions df'' -- Set `-fno-code` to avoid generating object files, unless we have to. 47 | ) 48 | 49 | let targets' = makeTargetsAbsolute componentRoot targets 50 | -- Unset the default log action to avoid output going to stdout. 51 | Gap.unsetLogAction 52 | return targets' 53 | 54 | ---------------------------------------------------------------- 55 | 56 | makeTargetsAbsolute :: FilePath -> [G.Target] -> [G.Target] 57 | makeTargetsAbsolute wdir = map (\target -> target {G.targetId = makeTargetIdAbsolute wdir (G.targetId target)}) 58 | 59 | makeTargetIdAbsolute :: FilePath -> G.TargetId -> G.TargetId 60 | makeTargetIdAbsolute wdir (G.TargetFile fp phase) = G.TargetFile (wdir fp) phase 61 | makeTargetIdAbsolute _ tid = tid 62 | 63 | ---------------------------------------------------------------- 64 | 65 | -- | @getRuntimeGhcLibDir cradle@ will give you the ghc libDir: 66 | -- __do not__ use 'runGhcCmd' directly. 67 | -- 68 | -- 69 | -- Obtains libdir by calling 'runCradleGhc' on the provided cradle. 70 | getRuntimeGhcLibDir :: Cradle a 71 | -> IO (CradleLoadResult FilePath) 72 | getRuntimeGhcLibDir cradle = fmap (fmap trim) $ 73 | runGhcCmd (cradleOptsProg cradle) ["--print-libdir"] 74 | 75 | -- | Gets the version of ghc used when compiling the cradle. It is based off of 76 | -- 'getRuntimeGhcLibDir'. If it can't work out the verison reliably, it will 77 | -- return a 'CradleError' 78 | getRuntimeGhcVersion :: Cradle a 79 | -> IO (CradleLoadResult String) 80 | getRuntimeGhcVersion cradle = 81 | fmap (fmap trim) $ runGhcCmd (cradleOptsProg cradle) ["--numeric-version"] 82 | 83 | ---------------------------------------------------------------- 84 | 85 | -- | What to call the cache directory in the cache folder. 86 | cacheDir :: String 87 | cacheDir = "hie-bios" 88 | 89 | {- | 90 | Back in the day we used to clear the cache at the start of each session, 91 | however, it's not really necessary as 92 | 1. There is one cache dir for any change in options. 93 | 2. Interface files are resistent to bad option changes anyway. 94 | 95 | > clearInterfaceCache :: FilePath -> IO () 96 | > clearInterfaceCache fp = do 97 | > cd <- getCacheDir fp 98 | > res <- doesPathExist cd 99 | > when res (removeDirectoryRecursive cd) 100 | -} 101 | 102 | -- | Prepends the cache directory used by the library to the supplied file path. 103 | -- It tries to use the path under the environment variable `$HIE_BIOS_CACHE_DIR` 104 | -- and falls back to the standard `$XDG_CACHE_HOME/hie-bios` if the former is not set 105 | getCacheDir :: FilePath -> IO FilePath 106 | getCacheDir fp = do 107 | mbEnvCacheDirectory <- lookupEnv "HIE_BIOS_CACHE_DIR" 108 | cacheBaseDir <- maybe (getXdgDirectory XdgCache cacheDir) return 109 | mbEnvCacheDirectory 110 | return (cacheBaseDir fp) 111 | 112 | ---------------------------------------------------------------- 113 | 114 | -- we don't want to generate object code so we compile to bytecode 115 | -- (HscInterpreted) which implies LinkInMemory 116 | -- HscInterpreted 117 | setLinkerOptions :: G.DynFlags -> G.DynFlags 118 | setLinkerOptions df = Gap.setNoCode $ df { 119 | G.ghcLink = G.LinkInMemory 120 | , G.ghcMode = G.CompManager 121 | } 122 | 123 | setIgnoreInterfacePragmas :: G.DynFlags -> G.DynFlags 124 | setIgnoreInterfacePragmas df = Gap.gopt_set df G.Opt_IgnoreInterfacePragmas 125 | 126 | setVerbosity :: Int -> G.DynFlags -> G.DynFlags 127 | setVerbosity n df = df { G.verbosity = n } 128 | 129 | writeInterfaceFiles :: Maybe FilePath -> G.DynFlags -> G.DynFlags 130 | writeInterfaceFiles Nothing df = df 131 | writeInterfaceFiles (Just hi_dir) df = setHiDir hi_dir (Gap.gopt_set df G.Opt_WriteInterface) 132 | 133 | setHiDir :: FilePath -> G.DynFlags -> G.DynFlags 134 | setHiDir f d = d { G.hiDir = Just f} 135 | 136 | 137 | -- | Interpret and set the specific command line options. 138 | -- A lot of this code is just copied from ghc/Main.hs 139 | -- It would be good to move this code into a library module so we can just use it 140 | -- rather than copy it. 141 | addCmdOpts :: (GhcMonad m) 142 | => [String] -> G.DynFlags -> m (G.DynFlags, [G.Target]) 143 | addCmdOpts cmdOpts df1 = do 144 | logger <- Gap.getLogger <$> G.getSession 145 | (df2, leftovers', _warns) <- Gap.parseDynamicFlags logger df1 (map G.noLoc cmdOpts) 146 | -- parse targets from ghci-scripts. Only extract targets that have been ":add"'ed. 147 | additionalTargets <- concat <$> mapM (liftIO . getTargetsFromGhciScript) (G.ghciScripts df2) 148 | 149 | -- leftovers contains all Targets from the command line 150 | let leftovers = map G.unLoc leftovers' ++ additionalTargets 151 | 152 | let (df3, srcs, _objs) = Gap.parseTargetFiles df2 leftovers 153 | ts <- mapM (\(f, phase) -> Gap.guessTarget f (Just $ Gap.homeUnitId_ df3) phase) srcs 154 | return (df3, ts) 155 | 156 | -- | Make filepaths in the given 'DynFlags' absolute. 157 | -- This makes the 'DynFlags' independent of the current working directory. 158 | makeDynFlagsAbsolute :: FilePath -> G.DynFlags -> G.DynFlags 159 | makeDynFlagsAbsolute root df = 160 | Gap.mapOverIncludePaths makeAbs 161 | $ df 162 | { G.importPaths = map makeAbs (G.importPaths df) 163 | , G.packageDBFlags = 164 | map (Gap.overPkgDbRef makeAbs) (G.packageDBFlags df) 165 | } 166 | where 167 | makeAbs = 168 | #if __GLASGOW_HASKELL__ >= 903 169 | case G.workingDirectory df of 170 | Just fp -> ((root fp) ) 171 | Nothing -> 172 | #endif 173 | (root ) 174 | 175 | -- -------------------------------------------------------- 176 | 177 | disableOptimisation :: G.DynFlags -> G.DynFlags 178 | disableOptimisation df = Gap.updOptLevel 0 df 179 | 180 | -- -------------------------------------------------------- 181 | 182 | -- | Read a ghci script and extract all targets to load form it. 183 | -- The ghci script is expected to have the following format: 184 | -- @ 185 | -- :add Foo Bar Main.hs 186 | -- @ 187 | -- 188 | -- We strip away ":add" and parse the Targets. 189 | getTargetsFromGhciScript :: FilePath -> IO [String] 190 | getTargetsFromGhciScript script = do 191 | contents <- lines <$> readFile script 192 | let parseGhciLine = concatMap fst . filter (null . snd) . readP_to_S parser 193 | return $ concatMap parseGhciLine contents 194 | 195 | -- |This parser aims to parse targets and double-quoted filepaths that are separated by spaces 196 | -- and prefixed with the literal ":add" 197 | -- 198 | -- >>> filter (null . snd) $ readP_to_S parser ":add Lib Lib2" 199 | -- [(["Lib","Lib2"],"")] 200 | -- 201 | -- >>> filter (null . snd) $ readP_to_S parser ":add Lib Lib2 \"Test Example.hs\"" 202 | -- [(["Lib","Lib2","Test Example.hs"],"")] 203 | -- 204 | -- >>> filter (null . snd) $ readP_to_S parser ":add Lib Lib2 \"Test Exa\\\"mple.hs\"" 205 | -- [(["Lib","Lib2","Test Exa\"mple.hs"],"")] 206 | parser :: ReadP [String] 207 | parser = do 208 | _ <- string ":add" <* space1 209 | scriptword `sepBy` space1 210 | 211 | space1 :: ReadP [Char] 212 | space1 = many1 (char ' ') 213 | 214 | scriptword :: ReadP String 215 | scriptword = quoted <++ value 216 | 217 | -- | A balanced double-quoted string 218 | quoted :: ReadP String 219 | quoted = do 220 | _ <- char '"' 221 | manyTill (escaped '"' <|> anyToken) $ char '"' 222 | 223 | escaped :: Char -> ReadP Char 224 | escaped c = c <$ string ("\\" <> [c]) 225 | 226 | value :: ReadP String 227 | value = many1 (satisfy (not . isSpace)) 228 | 229 | anyToken :: ReadP Char 230 | anyToken = satisfy $ const True 231 | 232 | -- Used for clipping the trailing newlines on GHC output 233 | -- Also only take the last line of output 234 | -- (Stack's ghc output has a lot of preceding noise from 7zip etc) 235 | trim :: String -> String 236 | trim s = case lines s of 237 | [] -> s 238 | ls -> dropWhileEnd isSpace $ last ls 239 | -------------------------------------------------------------------------------- /src/HIE/Bios/Flags.hs: -------------------------------------------------------------------------------- 1 | module HIE.Bios.Flags (getCompilerOptions) where 2 | 3 | import HIE.Bios.Types 4 | 5 | import Colog.Core (WithSeverity (..), Severity (..), (<&)) 6 | 7 | -- | Initialize the 'DynFlags' relating to the compilation of a single 8 | -- file or GHC session according to the provided 'Cradle'. 9 | getCompilerOptions 10 | :: FilePath -- ^ The file we are loading it because of 11 | -> LoadStyle -- ^ previous files we might want to include in the build 12 | -> Cradle a 13 | -> IO (CradleLoadResult ComponentOptions) 14 | getCompilerOptions fp loadStyle cradle = do 15 | (cradleLogger cradle) <& LogProcessOutput "invoking build tool to determine build flags (this may take some time depending on the cache)" `WithSeverity` Info 16 | runCradle (cradleOptsProg cradle) fp loadStyle 17 | -------------------------------------------------------------------------------- /src/HIE/Bios/Ghc/Api.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables, CPP #-} 2 | -- | These functions are for conveniently implementing the simple CLI 3 | module HIE.Bios.Ghc.Api ( 4 | initializeFlagsWithCradle 5 | , initializeFlagsWithCradleWithMessage 6 | , G.SuccessFlag(..) 7 | , withDynFlags 8 | -- For test purposes 9 | , initSessionWithMessage 10 | ) where 11 | 12 | import GHC (LoadHowMuch(..), DynFlags, GhcMonad) 13 | import qualified GHC as G 14 | import qualified GHC.Driver.Main as G 15 | import qualified HIE.Bios.Ghc.Gap as Gap 16 | import Control.Monad (void) 17 | import Control.Monad.IO.Class 18 | import HIE.Bios.Types 19 | import HIE.Bios.Environment 20 | import HIE.Bios.Flags 21 | 22 | ---------------------------------------------------------------- 23 | 24 | -- | Initialize a GHC session by loading a given file into a given cradle. 25 | initializeFlagsWithCradle :: 26 | GhcMonad m 27 | => FilePath -- ^ The file we are loading the 'Cradle' because of 28 | -> Cradle a -- ^ The cradle we want to load 29 | -> m (CradleLoadResult (m G.SuccessFlag, ComponentOptions)) 30 | initializeFlagsWithCradle = initializeFlagsWithCradleWithMessage (Just Gap.batchMsg) 31 | 32 | -- | The same as 'initializeFlagsWithCradle' but with an additional argument to control 33 | -- how the loading progress messages are displayed to the user. In @haskell-ide-engine@ 34 | -- the module loading progress is displayed in the UI by using a progress notification. 35 | initializeFlagsWithCradleWithMessage :: 36 | GhcMonad m 37 | => Maybe G.Messager 38 | -> FilePath -- ^ The file we are loading the 'Cradle' because of 39 | -> Cradle a -- ^ The cradle we want to load 40 | -> m (CradleLoadResult (m G.SuccessFlag, ComponentOptions)) -- ^ Whether we actually loaded the cradle or not. 41 | initializeFlagsWithCradleWithMessage msg fp cradle = 42 | fmap (initSessionWithMessage msg) <$> liftIO (getCompilerOptions fp LoadFile cradle) 43 | 44 | -- | Actually perform the initialisation of the session. Initialising the session corresponds to 45 | -- parsing the command line flags, setting the targets for the session and then attempting to load 46 | -- all the targets. 47 | initSessionWithMessage :: (GhcMonad m) 48 | => Maybe G.Messager 49 | -> ComponentOptions 50 | -> (m G.SuccessFlag, ComponentOptions) 51 | initSessionWithMessage msg compOpts = (do 52 | targets <- initSession compOpts 53 | G.setTargets targets 54 | -- Get the module graph using the function `getModuleGraph` 55 | mod_graph <- G.depanal [] True 56 | Gap.load' Nothing LoadAllTargets msg mod_graph, compOpts) 57 | 58 | ---------------------------------------------------------------- 59 | 60 | withDynFlags :: 61 | (GhcMonad m) 62 | => (DynFlags -> DynFlags) -> m a -> m a 63 | withDynFlags setFlag body = Gap.bracket setup teardown (\_ -> body) 64 | where 65 | setup = do 66 | dflag <- G.getSessionDynFlags 67 | void $ G.setSessionDynFlags (setFlag dflag) 68 | return dflag 69 | teardown = void . G.setSessionDynFlags 70 | 71 | ---------------------------------------------------------------- 72 | -------------------------------------------------------------------------------- /src/HIE/Bios/Ghc/Check.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE OverloadedStrings #-} 4 | {-# LANGUAGE ExistentialQuantification #-} 5 | module HIE.Bios.Ghc.Check ( 6 | checkSyntax 7 | , check 8 | ) where 9 | 10 | import GHC (GhcMonad) 11 | import qualified GHC as G 12 | 13 | 14 | import Control.Exception 15 | import Control.Monad.IO.Class 16 | import Colog.Core (LogAction (..), WithSeverity (..), Severity (..), (<&), cmap) 17 | import Prettyprinter 18 | 19 | import HIE.Bios.Ghc.Api 20 | import HIE.Bios.Ghc.Logger 21 | import HIE.Bios.Types hiding (Log (..)) 22 | import qualified HIE.Bios.Types as T 23 | import qualified HIE.Bios.Ghc.Load as Load 24 | import HIE.Bios.Environment 25 | 26 | 27 | data Log = 28 | LoadLog Load.Log 29 | | LogAny T.Log 30 | | forall a . Show a => LogCradle (Cradle a) 31 | 32 | instance Pretty Log where 33 | pretty (LoadLog l) = pretty l 34 | pretty (LogAny l) = pretty l 35 | pretty (LogCradle c) = "Cradle:" <+> viaShow c 36 | 37 | ---------------------------------------------------------------- 38 | 39 | -- | Checking syntax of a target file using GHC. 40 | -- Warnings and errors are returned. 41 | checkSyntax :: Show a 42 | => LogAction IO (WithSeverity Log) 43 | -> Cradle a 44 | -> [FilePath] -- ^ The target files. 45 | -> IO String 46 | checkSyntax _ _ [] = return "" 47 | checkSyntax checkLogger cradle files@(file:_) = do 48 | libDirRes <- getRuntimeGhcLibDir cradle 49 | handleRes libDirRes $ \libDir -> 50 | G.runGhcT (Just libDir) $ do 51 | liftIO $ checkLogger <& LogCradle cradle `WithSeverity` Info 52 | res <- initializeFlagsWithCradle file cradle 53 | handleRes res $ \(ini, _) -> do 54 | _sf <- ini 55 | either id id <$> check checkLogger files 56 | where 57 | handleRes (CradleSuccess x) f = f x 58 | handleRes (CradleFail ce) _f = liftIO $ throwIO ce 59 | handleRes CradleNone _f = return "None cradle" 60 | 61 | ---------------------------------------------------------------- 62 | 63 | -- | Checking syntax of a target file using GHC. 64 | -- Warnings and errors are returned. 65 | check :: (GhcMonad m) 66 | => LogAction IO (WithSeverity Log) 67 | -> [FilePath] -- ^ The target files. 68 | -> m (Either String String) 69 | check logger fileNames = do 70 | withLogger id $ Load.setTargetFiles (cmap (fmap LoadLog) logger) (map dup fileNames) 71 | 72 | dup :: a -> (a, a) 73 | dup x = (x, x) 74 | 75 | -------------------------------------------------------------------------------- /src/HIE/Bios/Ghc/Doc.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | -- | Pretty printer utilities 3 | module HIE.Bios.Ghc.Doc where 4 | 5 | 6 | import GHC (DynFlags 7 | #if __GLASGOW_HASKELL__ < 905 8 | , getPrintUnqual 9 | #endif 10 | , pprCols, GhcMonad) 11 | #if __GLASGOW_HASKELL__ >= 905 12 | import GHC.Utils.Outputable 13 | #endif 14 | 15 | import GHC.Driver.Session (initSDocContext) 16 | import GHC.Utils.Ppr (Mode(..), Doc, Style(..), renderStyle, style) 17 | 18 | import HIE.Bios.Ghc.Gap (makeUserStyle, pageMode, oneLineMode) 19 | 20 | #if __GLASGOW_HASKELL__ < 905 21 | import GHC.Utils.Outputable (PprStyle, SDoc, runSDoc, neverQualify, ) 22 | #endif 23 | 24 | #if __GLASGOW_HASKELL__ >= 905 25 | getPrintUnqual :: Monad m => m NamePprCtx 26 | getPrintUnqual = pure neverQualify 27 | #endif 28 | 29 | showPage :: DynFlags -> PprStyle -> SDoc -> String 30 | showPage dflag stl sdoc = showDocWith dflag pageMode $ runSDoc sdoc scontext 31 | where 32 | scontext = initSDocContext dflag stl 33 | 34 | showOneLine :: DynFlags -> PprStyle -> SDoc -> String 35 | showOneLine dflag stl sdoc = showDocWith dflag oneLineMode $ runSDoc sdoc scontext 36 | where 37 | scontext = initSDocContext dflag stl 38 | 39 | getStyle :: (GhcMonad m) => DynFlags -> m PprStyle 40 | getStyle dflags = makeUserStyle dflags <$> getPrintUnqual 41 | 42 | styleUnqualified :: DynFlags -> PprStyle 43 | styleUnqualified dflags = makeUserStyle dflags neverQualify 44 | 45 | showDocWith :: DynFlags -> Mode -> Doc -> String 46 | showDocWith dflags md = renderStyle mstyle 47 | where 48 | mstyle = style { mode = md, lineLength = pprCols dflags } 49 | -------------------------------------------------------------------------------- /src/HIE/Bios/Ghc/Gap.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances, CPP, PatternSynonyms #-} 2 | -- | All the CPP for GHC version compability should live in this module. 3 | module HIE.Bios.Ghc.Gap ( 4 | ghcVersion 5 | -- * Warnings, Doc Compat 6 | , makeUserStyle 7 | , PprStyle 8 | -- * Argument parsing 9 | , HIE.Bios.Ghc.Gap.parseTargetFiles 10 | -- * Ghc Monad 11 | , G.modifySession 12 | , G.reflectGhc 13 | , G.Session(..) 14 | -- * Hsc Monad 15 | , getHscEnv 16 | -- * Driver compat 17 | , batchMsg 18 | -- * HscEnv Compat 19 | , set_hsc_dflags 20 | , overPkgDbRef 21 | , HIE.Bios.Ghc.Gap.guessTarget 22 | , setNoCode 23 | , getModSummaries 24 | , mapOverIncludePaths 25 | , HIE.Bios.Ghc.Gap.getLogger 26 | -- * AST compat 27 | , pattern HIE.Bios.Ghc.Gap.RealSrcSpan 28 | -- * Exceptions 29 | , catch 30 | , bracket 31 | , handle 32 | -- * Doc Gap functions 33 | , pageMode 34 | , oneLineMode 35 | -- * DynFlags compat 36 | , initializePluginsForModSummary 37 | , setFrontEndHooks 38 | , updOptLevel 39 | , setWayDynamicIfHostIsDynamic 40 | , HIE.Bios.Ghc.Gap.gopt_set 41 | , HIE.Bios.Ghc.Gap.parseDynamicFlags 42 | -- * Platform constants 43 | , hostIsDynamic 44 | -- * misc 45 | , getTyThing 46 | , fixInfo 47 | , Tc.FrontendResult(..) 48 | , Hsc 49 | , mapMG 50 | , mgModSummaries 51 | , unsetLogAction 52 | , load' 53 | , homeUnitId_ 54 | , getDynFlags 55 | ) where 56 | 57 | import Control.Monad.IO.Class 58 | import qualified Control.Monad.Catch as E 59 | 60 | import GHC 61 | import qualified GHC as G 62 | 63 | ---------------------------------------------------------------- 64 | ---------------------------------------------------------------- 65 | 66 | import GHC.Driver.Env as G 67 | import GHC.Driver.Session as G 68 | import GHC.Driver.Hooks 69 | import GHC.Driver.Main 70 | import GHC.Driver.Monad as G 71 | import qualified GHC.Driver.Plugins as Plugins 72 | import GHC.Platform.Ways (Way(WayDyn)) 73 | import qualified GHC.Platform.Ways as Platform 74 | import qualified GHC.Runtime.Loader as DynamicLoading (initializePlugins) 75 | import qualified GHC.Tc.Types as Tc 76 | import GHC.Utils.Logger 77 | import GHC.Utils.Outputable 78 | import qualified GHC.Utils.Ppr as Ppr 79 | import qualified GHC.Driver.Make as G 80 | 81 | #if __GLASGOW_HASKELL__ > 903 82 | import GHC.Unit.Types (UnitId) 83 | #endif 84 | #if __GLASGOW_HASKELL__ < 904 85 | import qualified GHC.Driver.Main as G 86 | #endif 87 | #if __GLASGOW_HASKELL__ >= 907 88 | import GHC.Types.Error (mkUnknownDiagnostic, Messages) 89 | import GHC.Driver.Errors.Types (DriverMessage) 90 | #endif 91 | #if __GLASGOW_HASKELL__ < 907 92 | import GHC.Driver.CmdLine as CmdLine 93 | #endif 94 | 95 | ghcVersion :: String 96 | ghcVersion = VERSION_ghc 97 | 98 | #if __GLASGOW_HASKELL__ >= 907 99 | load' :: GhcMonad m => Maybe G.ModIfaceCache -> LoadHowMuch -> Maybe Messager -> ModuleGraph -> m SuccessFlag 100 | load' mhmi_cache how_much = G.load' mhmi_cache how_much mkUnknownDiagnostic 101 | #elif __GLASGOW_HASKELL__ >= 904 102 | load' :: GhcMonad m => Maybe G.ModIfaceCache -> LoadHowMuch -> Maybe Messager -> ModuleGraph -> m SuccessFlag 103 | load' = G.load' 104 | #else 105 | load' :: GhcMonad m => a -> LoadHowMuch -> Maybe G.Messager -> ModuleGraph -> m SuccessFlag 106 | load' _ a b c = G.load' a b c 107 | #endif 108 | 109 | bracket :: E.MonadMask m => m a -> (a -> m c) -> (a -> m b) -> m b 110 | bracket = 111 | E.bracket 112 | 113 | handle :: (E.MonadCatch m, E.Exception e) => (e -> m a) -> m a -> m a 114 | handle = E.handle 115 | 116 | catch :: (E.MonadCatch m, E.Exception e) => m a -> (e -> m a) -> m a 117 | catch = 118 | E.catch 119 | 120 | ---------------------------------------------------------------- 121 | 122 | pattern RealSrcSpan :: G.RealSrcSpan -> G.SrcSpan 123 | pattern RealSrcSpan t <- G.RealSrcSpan t _ 124 | 125 | ---------------------------------------------------------------- 126 | 127 | setNoCode :: DynFlags -> DynFlags 128 | #if __GLASGOW_HASKELL__ >= 905 129 | setNoCode d = d { G.backend = G.noBackend } 130 | #else 131 | setNoCode d = d { G.backend = G.NoBackend } 132 | #endif 133 | 134 | ---------------------------------------------------------------- 135 | 136 | set_hsc_dflags :: DynFlags -> HscEnv -> HscEnv 137 | set_hsc_dflags dflags hsc_env = hsc_env { G.hsc_dflags = dflags } 138 | 139 | overPkgDbRef :: (FilePath -> FilePath) -> G.PackageDBFlag -> G.PackageDBFlag 140 | overPkgDbRef f (G.PackageDB pkgConfRef) = G.PackageDB $ case pkgConfRef of 141 | G.PkgDbPath fp -> G.PkgDbPath (f fp) 142 | conf -> conf 143 | overPkgDbRef _f db = db 144 | 145 | ---------------------------------------------------------------- 146 | 147 | #if __GLASGOW_HASKELL__ >= 903 148 | guessTarget :: GhcMonad m => String -> Maybe UnitId -> Maybe G.Phase -> m G.Target 149 | guessTarget a b c = G.guessTarget a b c 150 | #else 151 | guessTarget :: GhcMonad m => String -> a -> Maybe G.Phase -> m G.Target 152 | guessTarget a _ b = G.guessTarget a b 153 | #endif 154 | 155 | ---------------------------------------------------------------- 156 | 157 | #if __GLASGOW_HASKELL__ >= 905 158 | makeUserStyle :: DynFlags -> NamePprCtx -> PprStyle 159 | #else 160 | makeUserStyle :: DynFlags -> PrintUnqualified -> PprStyle 161 | #endif 162 | makeUserStyle _dflags style = mkUserStyle style AllTheWay 163 | 164 | ---------------------------------------------------------------- 165 | 166 | getModSummaries :: ModuleGraph -> [ModSummary] 167 | getModSummaries = mgModSummaries 168 | 169 | getTyThing :: (a, b, c, d, e) -> a 170 | getTyThing (t,_,_,_,_) = t 171 | 172 | fixInfo :: (a, b, c, d, e) -> (a, b, c, d) 173 | fixInfo (t,f,cs,fs,_) = (t,f,cs,fs) 174 | 175 | ---------------------------------------------------------------- 176 | 177 | mapOverIncludePaths :: (FilePath -> FilePath) -> DynFlags -> DynFlags 178 | mapOverIncludePaths f df = df 179 | { includePaths = 180 | G.IncludeSpecs 181 | (map f $ G.includePathsQuote (includePaths df)) 182 | (map f $ G.includePathsGlobal (includePaths df)) 183 | #if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0) 184 | (map f $ G.includePathsQuoteImplicit (includePaths df)) 185 | #endif 186 | } 187 | 188 | ---------------------------------------------------------------- 189 | 190 | unsetLogAction :: GhcMonad m => m () 191 | unsetLogAction = do 192 | hsc_env <- getSession 193 | logger <- liftIO $ initLogger 194 | let env = hsc_env { hsc_logger = pushLogHook (const noopLogger) logger } 195 | setSession env 196 | 197 | noopLogger :: LogAction 198 | #if __GLASGOW_HASKELL__ >= 903 199 | noopLogger = (\_wr _s _ss _m -> return ()) 200 | #else 201 | noopLogger = (\_df _wr _s _ss _m -> return ()) 202 | #endif 203 | 204 | -- -------------------------------------------------------- 205 | -- Doc Compat functions 206 | -- -------------------------------------------------------- 207 | 208 | pageMode :: Ppr.Mode 209 | pageMode = 210 | Ppr.PageMode True 211 | 212 | oneLineMode :: Ppr.Mode 213 | oneLineMode = Ppr.OneLineMode 214 | 215 | -- -------------------------------------------------------- 216 | -- DynFlags Compat functions 217 | -- -------------------------------------------------------- 218 | 219 | numLoadedPlugins :: HscEnv -> Int 220 | #if __GLASGOW_HASKELL__ >= 903 221 | numLoadedPlugins = length . Plugins.pluginsWithArgs . hsc_plugins 222 | #else 223 | numLoadedPlugins = length . Plugins.plugins 224 | #endif 225 | 226 | initializePluginsForModSummary :: HscEnv -> ModSummary -> IO (Int, [G.ModuleName], ModSummary) 227 | initializePluginsForModSummary hsc_env' mod_summary = do 228 | hsc_env <- DynamicLoading.initializePlugins hsc_env' 229 | pure ( numLoadedPlugins hsc_env 230 | , pluginModNames $ hsc_dflags hsc_env 231 | , mod_summary 232 | ) 233 | 234 | setFrontEndHooks :: Maybe (ModSummary -> G.Hsc Tc.FrontendResult) -> HscEnv -> HscEnv 235 | setFrontEndHooks frontendHook env = 236 | env 237 | { hsc_hooks = hooks 238 | { hscFrontendHook = frontendHook 239 | } 240 | } 241 | where 242 | hooks = hsc_hooks env 243 | 244 | getLogger :: HscEnv -> Logger 245 | getLogger = 246 | hsc_logger 247 | 248 | gopt_set :: DynFlags -> G.GeneralFlag -> DynFlags 249 | gopt_set = G.gopt_set 250 | 251 | setWayDynamicIfHostIsDynamic :: DynFlags -> DynFlags 252 | setWayDynamicIfHostIsDynamic = 253 | if hostIsDynamic 254 | then 255 | updateWays . addWay' WayDyn 256 | else 257 | id 258 | 259 | updateWays :: DynFlags -> DynFlags 260 | updateWays = id 261 | 262 | -- Copied from GHC, do we need that? 263 | addWay' :: Way -> DynFlags -> DynFlags 264 | addWay' w dflags0 = 265 | let platform = targetPlatform dflags0 266 | dflags1 = dflags0 { targetWays_ = Platform.addWay w (targetWays_ dflags0) } 267 | dflags2 = foldr setGeneralFlag' dflags1 268 | (Platform.wayGeneralFlags platform w) 269 | dflags3 = foldr unSetGeneralFlag' dflags2 270 | (Platform.wayUnsetGeneralFlags platform w) 271 | in dflags3 272 | 273 | parseDynamicFlags :: MonadIO m 274 | => Logger 275 | -> DynFlags 276 | -> [G.Located String] 277 | -> m (DynFlags, [G.Located String] 278 | #if __GLASGOW_HASKELL__ >= 907 279 | , Messages DriverMessage) 280 | #else 281 | , [CmdLine.Warn]) 282 | #endif 283 | parseDynamicFlags = G.parseDynamicFlags 284 | 285 | 286 | parseTargetFiles :: DynFlags -> [String] -> (DynFlags, [(String, Maybe G.Phase)], [String]) 287 | parseTargetFiles = G.parseTargetFiles 288 | 289 | -- -------------------------------------------------------- 290 | -- Platform constants 291 | -- -------------------------------------------------------- 292 | 293 | hostIsDynamic :: Bool 294 | hostIsDynamic = Platform.hostIsDynamic 295 | -------------------------------------------------------------------------------- /src/HIE/Bios/Ghc/Load.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | {-# LANGUAGE CPP #-} 4 | -- | Convenience functions for loading a file into a GHC API session 5 | module HIE.Bios.Ghc.Load where 6 | 7 | 8 | import Colog.Core (LogAction (..), WithSeverity (..), Severity (..), (<&)) 9 | import Control.Monad (forM, void) 10 | import Control.Monad.IO.Class 11 | 12 | import Data.List 13 | 14 | import Prettyprinter 15 | import Data.IORef 16 | 17 | import GHC 18 | import qualified GHC as G 19 | 20 | import qualified GHC.Driver.Main as G 21 | 22 | import qualified HIE.Bios.Ghc.Gap as Gap 23 | #if __GLASGOW_HASKELL__ > 903 24 | import GHC.Fingerprint 25 | #endif 26 | #if __GLASGOW_HASKELL__ < 903 27 | import Data.Time.Clock 28 | #endif 29 | 30 | data Log = 31 | LogLoaded FilePath FilePath 32 | | LogTypechecked [TypecheckedModule] 33 | | LogInitPlugins Int [ModuleName] 34 | | LogSetTargets [(FilePath, FilePath)] 35 | | LogModGraph ModuleGraph 36 | 37 | instance Pretty Log where 38 | pretty (LogLoaded fp1 fp2) = "Loaded" <+> viaShow fp1 <+> "-" <+> viaShow fp2 39 | pretty (LogTypechecked tcs) = "Typechecked modules for:" <+> (cat $ map (viaShow . get_fp) tcs) 40 | pretty (LogInitPlugins n ns) = "Loaded" <+> viaShow n <+> "plugins, specified" <+> viaShow (length ns) 41 | pretty (LogSetTargets ts) = "Set targets:" <+> viaShow ts 42 | pretty (LogModGraph mod_graph) = "ModGraph:" <+> viaShow (map ms_location $ Gap.mgModSummaries mod_graph) 43 | 44 | get_fp :: TypecheckedModule -> Maybe FilePath 45 | get_fp = ml_hs_file . ms_location . pm_mod_summary . tm_parsed_module 46 | 47 | -- | Load a target into the GHC session. 48 | -- 49 | -- The target is represented as a tuple. The tuple consists of the 50 | -- original filename and another file that contains the actual 51 | -- source code to compile. 52 | -- 53 | -- The optional messager can be used to log diagnostics, warnings or errors 54 | -- that occurred during loading the target. 55 | -- 56 | -- If the loading succeeds, the typechecked module is returned 57 | -- together with all the typechecked modules that had to be loaded 58 | -- in order to typecheck the given target. 59 | loadFileWithMessage :: GhcMonad m 60 | => LogAction IO (WithSeverity Log) 61 | -> Maybe G.Messager -- ^ Optional messager hook 62 | -- to log messages produced by GHC. 63 | -> (FilePath, FilePath) -- ^ Target file to load. 64 | -> m (Maybe TypecheckedModule, [TypecheckedModule]) 65 | -- ^ Typechecked module and modules that had to 66 | -- be loaded for the target. 67 | loadFileWithMessage logger msg file = do 68 | -- STEP 1: Load the file into the session, using collectASTs to also retrieve 69 | -- typechecked and parsed modules. 70 | (_, tcs) <- collectASTs logger $ (setTargetFilesWithMessage logger msg [file]) 71 | liftIO $ logger <& LogLoaded (fst file) (snd file) `WithSeverity` Debug 72 | liftIO $ logger <& LogTypechecked tcs `WithSeverity` Debug 73 | -- Find the specific module in the list of returned typechecked modules if it exists. 74 | let findMod [] = Nothing 75 | findMod (x:xs) = case get_fp x of 76 | Just fp -> if fp `isSuffixOf` (snd file) then Just x else findMod xs 77 | Nothing -> findMod xs 78 | return (findMod tcs, tcs) 79 | 80 | -- | Load a target into the GHC session with the default messager 81 | -- which outputs updates in the same format as normal GHC. 82 | -- 83 | -- The target is represented as a tuple. The tuple consists of the 84 | -- original filename and another file that contains the actual 85 | -- source code to compile. 86 | -- 87 | -- If the message should configured, use 'loadFileWithMessage'. 88 | -- 89 | -- If the loading succeeds, the typechecked module is returned 90 | -- together with all the typechecked modules that had to be loaded 91 | -- in order to typecheck the given target. 92 | loadFile :: (GhcMonad m) 93 | => LogAction IO (WithSeverity Log) 94 | -> (FilePath, FilePath) -- ^ Target file to load. 95 | -> m (Maybe TypecheckedModule, [TypecheckedModule]) 96 | -- ^ Typechecked module and modules that had to 97 | -- be loaded for the target. 98 | loadFile logger = loadFileWithMessage logger (Just G.batchMsg) 99 | 100 | 101 | -- | Set the files as targets and load them. This will reset GHC's targets so only the modules you 102 | -- set as targets and its dependencies will be loaded or reloaded. 103 | -- Produced diagnostics will be printed similar to the normal output of GHC. 104 | -- To configure this, use 'setTargetFilesWithMessage'. 105 | setTargetFiles 106 | :: GhcMonad m 107 | => LogAction IO (WithSeverity Log) 108 | -> [(FilePath, FilePath)] 109 | -> m () 110 | setTargetFiles logger = setTargetFilesWithMessage logger (Just G.batchMsg) 111 | 112 | msTargetIs :: ModSummary -> Target -> Bool 113 | msTargetIs ms t = case targetId t of 114 | TargetModule m -> moduleName (ms_mod ms) == m 115 | TargetFile f _ -> ml_hs_file (ms_location ms) == Just f 116 | 117 | -- | We bump the times for any ModSummary's that are Targets, to 118 | -- fool the recompilation checker so that we can get the typechecked modules 119 | updateTime :: MonadIO m => [Target] -> ModuleGraph -> m ModuleGraph 120 | updateTime ts graph = liftIO $ do 121 | #if __GLASGOW_HASKELL__ < 903 122 | cur_time <- getCurrentTime 123 | #endif 124 | let go ms 125 | | any (msTargetIs ms) ts = 126 | #if __GLASGOW_HASKELL__ >= 903 127 | ms {ms_hs_hash = fingerprint0} 128 | #else 129 | ms {ms_hs_date = cur_time} 130 | #endif 131 | | otherwise = ms 132 | pure $ Gap.mapMG go graph 133 | 134 | -- | Set the files as targets and load them. This will reset GHC's targets so only the modules you 135 | -- set as targets and its dependencies will be loaded or reloaded. 136 | setTargetFilesWithMessage 137 | :: (GhcMonad m) 138 | => LogAction IO (WithSeverity Log) 139 | -> Maybe G.Messager 140 | -> [(FilePath, FilePath)] 141 | -> m () 142 | setTargetFilesWithMessage logger msg files = do 143 | targets <- forM files guessTargetMapped 144 | liftIO $ logger <& LogSetTargets files `WithSeverity` Debug 145 | G.setTargets targets 146 | mod_graph <- updateTime targets =<< depanal [] False 147 | liftIO $ logger <& LogModGraph mod_graph `WithSeverity` Debug 148 | void $ Gap.load' Nothing LoadAllTargets msg mod_graph 149 | 150 | -- | Add a hook to record the contents of any 'TypecheckedModule's which are produced 151 | -- during compilation. 152 | collectASTs 153 | :: (GhcMonad m) 154 | => LogAction IO (WithSeverity Log) 155 | -> m a 156 | -> m (a, [TypecheckedModule]) 157 | collectASTs logger action = do 158 | ref1 <- liftIO $ newIORef [] 159 | -- Modify session is much faster than `setSessionDynFlags`. 160 | Gap.modifySession $ Gap.setFrontEndHooks (Just (astHook logger ref1)) 161 | res <- action 162 | tcs <- liftIO $ readIORef ref1 163 | -- Unset the hook so that we don't retain the reference to the IORef so it can be GCed. 164 | -- This stops the typechecked modules being retained in some cases. 165 | liftIO $ writeIORef ref1 [] 166 | Gap.modifySession $ Gap.setFrontEndHooks Nothing 167 | 168 | return (res, tcs) 169 | 170 | -- | This hook overwrites the default frontend action of GHC. 171 | astHook 172 | :: LogAction IO (WithSeverity Log) 173 | -> IORef [TypecheckedModule] 174 | -> ModSummary 175 | -> Gap.Hsc Gap.FrontendResult 176 | astHook logger tc_ref ms = ghcInHsc $ do 177 | p <- G.parseModule =<< initializePluginsGhc logger ms 178 | tcm <- G.typecheckModule p 179 | let tcg_env = fst (tm_internals_ tcm) 180 | liftIO $ modifyIORef tc_ref (tcm :) 181 | return $ Gap.FrontendTypecheck tcg_env 182 | 183 | initializePluginsGhc 184 | :: GhcMonad m 185 | => LogAction IO (WithSeverity Log) 186 | -> ModSummary 187 | -> m ModSummary 188 | initializePluginsGhc logger ms = do 189 | hsc_env <- getSession 190 | (pluginsLoaded, pluginNames, newMs) <- liftIO $ Gap.initializePluginsForModSummary hsc_env ms 191 | liftIO $ logger <& LogInitPlugins pluginsLoaded pluginNames `WithSeverity` Debug 192 | return newMs 193 | 194 | ghcInHsc :: Ghc a -> Gap.Hsc a 195 | ghcInHsc gm = do 196 | hsc_session <- Gap.getHscEnv 197 | session <- liftIO $ newIORef hsc_session 198 | liftIO $ Gap.reflectGhc gm (Gap.Session session) 199 | 200 | -- | A variant of 'guessTarget' which after guessing the target for a filepath, overwrites the 201 | -- target file to be a temporary file. 202 | guessTargetMapped :: (GhcMonad m) => (FilePath, FilePath) -> m Target 203 | guessTargetMapped (orig_file_name, mapped_file_name) = do 204 | df <- Gap.getDynFlags 205 | t <- Gap.guessTarget orig_file_name (Just $ Gap.homeUnitId_ df) Nothing 206 | return (setTargetFilename mapped_file_name t) 207 | 208 | setTargetFilename :: FilePath -> Target -> Target 209 | setTargetFilename fn t = 210 | t { targetId = case targetId t of 211 | TargetFile _ p -> TargetFile fn p 212 | tid -> tid } 213 | -------------------------------------------------------------------------------- /src/HIE/Bios/Ghc/Logger.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE BangPatterns, CPP, TypeApplications #-} 2 | 3 | module HIE.Bios.Ghc.Logger ( 4 | withLogger 5 | ) where 6 | 7 | import GHC (DynFlags(..), SrcSpan(..), GhcMonad, getSessionDynFlags) 8 | import qualified GHC as G 9 | import Control.Monad.IO.Class 10 | 11 | import GHC.Data.Bag 12 | import GHC.Data.FastString (unpackFS) 13 | import GHC.Driver.Session (dopt, DumpFlag(Opt_D_dump_splices)) 14 | import GHC.Types.SourceError 15 | import GHC.Utils.Error 16 | import GHC.Utils.Logger 17 | 18 | import Data.IORef (IORef, newIORef, readIORef, writeIORef, modifyIORef) 19 | import Data.Maybe (fromMaybe) 20 | 21 | import System.FilePath (normalise) 22 | 23 | import HIE.Bios.Ghc.Doc (showPage, getStyle) 24 | import HIE.Bios.Ghc.Api (withDynFlags) 25 | import qualified HIE.Bios.Ghc.Gap as Gap 26 | 27 | #if __GLASGOW_HASKELL__ >= 903 28 | import GHC.Types.Error 29 | import GHC.Driver.Errors.Types 30 | #endif 31 | 32 | ---------------------------------------------------------------- 33 | 34 | type Builder = [String] -> [String] 35 | 36 | newtype LogRef = LogRef (IORef Builder) 37 | 38 | newLogRef :: IO LogRef 39 | newLogRef = LogRef <$> newIORef id 40 | 41 | readAndClearLogRef :: LogRef -> IO String 42 | readAndClearLogRef (LogRef ref) = do 43 | b <- readIORef ref 44 | writeIORef ref id 45 | return $! unlines (b []) 46 | 47 | appendLogRef :: DynFlags -> Gap.PprStyle -> LogRef -> LogAction 48 | appendLogRef df style (LogRef ref) _ 49 | #if __GLASGOW_HASKELL__ < 903 50 | _ sev 51 | #elif __GLASGOW_HASKELL__ < 905 52 | (MCDiagnostic sev _) 53 | #else 54 | (MCDiagnostic sev _ _) 55 | #endif 56 | src msg = do 57 | let !l = ppMsg src sev df style msg 58 | modifyIORef ref (\b -> b . (l:)) 59 | 60 | ---------------------------------------------------------------- 61 | 62 | -- | Set the session flag (e.g. "-Wall" or "-w:") then 63 | -- executes a body. Log messages are returned as 'String'. 64 | -- Right is success and Left is failure. 65 | withLogger :: 66 | (GhcMonad m) 67 | => (DynFlags -> DynFlags) -> m () -> m (Either String String) 68 | withLogger setDF body = Gap.handle sourceError $ do 69 | logref <- liftIO newLogRef 70 | dflags <- getSessionDynFlags 71 | style <- getStyle dflags 72 | G.pushLogHookM (const $ appendLogRef dflags style logref) 73 | let setLogger _ df = df 74 | r <- withDynFlags (setLogger logref . setDF) $ do 75 | body 76 | liftIO $ Right <$> readAndClearLogRef logref 77 | G.popLogHookM 78 | pure r 79 | 80 | 81 | 82 | ---------------------------------------------------------------- 83 | 84 | -- | Converting 'SourceError' to 'String'. 85 | sourceError :: 86 | (GhcMonad m) 87 | => SourceError -> m (Either String String) 88 | sourceError err = do 89 | dflag <- getSessionDynFlags 90 | style <- getStyle dflag 91 | #if __GLASGOW_HASKELL__ >= 903 92 | let ret = unlines . errBagToStrList dflag style . getMessages . srcErrorMessages $ err 93 | #else 94 | let ret = unlines . errBagToStrList dflag style . srcErrorMessages $ err 95 | #endif 96 | return (Left ret) 97 | 98 | #if __GLASGOW_HASKELL__ >= 903 99 | errBagToStrList :: DynFlags -> Gap.PprStyle -> Bag (MsgEnvelope GhcMessage) -> [String] 100 | errBagToStrList dflag style = map (ppErrMsg dflag style) . reverse . bagToList 101 | 102 | 103 | ppErrMsg :: DynFlags -> Gap.PprStyle -> MsgEnvelope GhcMessage -> String 104 | ppErrMsg dflag style err = ppMsg spn SevError dflag style msg -- ++ ext 105 | where 106 | spn = errMsgSpan err 107 | #if __GLASGOW_HASKELL__ >= 905 108 | msg = pprLocMsgEnvelope (defaultDiagnosticOpts @GhcMessage) err 109 | #else 110 | msg = pprLocMsgEnvelope err 111 | #endif 112 | -- fixme 113 | #else 114 | errBagToStrList :: DynFlags -> Gap.PprStyle -> Bag (MsgEnvelope DecoratedSDoc) -> [String] 115 | errBagToStrList dflag style = map (ppErrMsg dflag style) . reverse . bagToList 116 | 117 | 118 | ppErrMsg :: DynFlags -> Gap.PprStyle -> MsgEnvelope DecoratedSDoc -> String 119 | ppErrMsg dflag style err = ppMsg spn SevError dflag style msg -- ++ ext 120 | where 121 | spn = errMsgSpan err 122 | msg = pprLocMsgEnvelope err 123 | -- fixme 124 | #endif 125 | 126 | ppMsg :: SrcSpan -> G.Severity-> DynFlags -> Gap.PprStyle -> SDoc -> String 127 | ppMsg spn sev dflag style msg = prefix ++ cts 128 | where 129 | cts = showPage dflag style msg 130 | defaultPrefix 131 | | isDumpSplices dflag = "" 132 | | otherwise = checkErrorPrefix 133 | prefix = fromMaybe defaultPrefix $ do 134 | (line,col,_,_) <- getSrcSpan spn 135 | file <- normalise <$> getSrcFile spn 136 | let severityCaption = showSeverityCaption sev 137 | return $ file ++ ":" ++ show line ++ ":" ++ show col ++ ":" ++ severityCaption 138 | 139 | checkErrorPrefix :: String 140 | checkErrorPrefix = "Dummy:0:0:Error:" 141 | 142 | showSeverityCaption :: G.Severity -> String 143 | showSeverityCaption G.SevWarning = "Warning: " 144 | showSeverityCaption _ = "" 145 | 146 | getSrcFile :: SrcSpan -> Maybe String 147 | getSrcFile (Gap.RealSrcSpan spn) = Just . unpackFS . G.srcSpanFile $ spn 148 | getSrcFile _ = Nothing 149 | 150 | isDumpSplices :: DynFlags -> Bool 151 | isDumpSplices dflag = dopt Opt_D_dump_splices dflag 152 | 153 | getSrcSpan :: SrcSpan -> Maybe (Int,Int,Int,Int) 154 | getSrcSpan (Gap.RealSrcSpan spn) = 155 | Just ( G.srcSpanStartLine spn 156 | , G.srcSpanStartCol spn 157 | , G.srcSpanEndLine spn 158 | , G.srcSpanEndCol spn) 159 | getSrcSpan _ = Nothing 160 | -------------------------------------------------------------------------------- /src/HIE/Bios/Internal/Debug.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | module HIE.Bios.Internal.Debug (debugInfo, rootInfo, configInfo, cradleInfo) where 3 | 4 | import Control.Monad 5 | import Colog.Core (LogAction (..), WithSeverity (..)) 6 | import Data.Void 7 | 8 | import qualified Data.Char as Char 9 | 10 | import HIE.Bios.Cradle 11 | import HIE.Bios.Environment 12 | import HIE.Bios.Types 13 | import HIE.Bios.Flags 14 | 15 | import System.Directory 16 | 17 | ---------------------------------------------------------------- 18 | 19 | -- | Obtain debug information for a 'Cradle'. 20 | -- 21 | -- Tries to load the 'Cradle' and dump any information associated with it. 22 | -- If loading succeeds, contains information such as the root directory of 23 | -- the cradle, the compiler options to compile a module in this 'Cradle', 24 | -- the file dependencies and so on. 25 | -- 26 | -- Otherwise, shows the error message and exit-code. 27 | debugInfo :: Show a 28 | => FilePath 29 | -> Cradle a 30 | -> IO String 31 | debugInfo fp cradle = unlines <$> do 32 | let logger = cradleLogger cradle 33 | res <- getCompilerOptions fp LoadFile cradle 34 | canonFp <- canonicalizePath fp 35 | conf <- findConfig canonFp 36 | crdl <- findCradle' logger canonFp 37 | ghcLibDir <- getRuntimeGhcLibDir cradle 38 | ghcVer <- getRuntimeGhcVersion cradle 39 | case res of 40 | CradleSuccess (ComponentOptions gopts croot deps) -> do 41 | return [ 42 | "Root directory: " ++ rootDir 43 | , "Component directory: " ++ croot 44 | , "GHC options: " ++ unwords (map quoteIfNeeded gopts) 45 | , "GHC library directory: " ++ show ghcLibDir 46 | , "GHC version: " ++ show ghcVer 47 | , "Config Location: " ++ conf 48 | , "Cradle: " ++ crdl 49 | , "Dependencies: " ++ unwords deps 50 | ] 51 | CradleFail (CradleError deps ext stderr extraFiles) -> 52 | return ["Cradle failed to load" 53 | , "Deps: " ++ show deps 54 | , "Exit Code: " ++ show ext 55 | , "Stderr: " ++ unlines stderr 56 | , "Failed: " ++ unlines extraFiles 57 | ] 58 | CradleNone -> 59 | return ["No cradle"] 60 | where 61 | rootDir = cradleRootDir cradle 62 | quoteIfNeeded option 63 | | any Char.isSpace option = "\"" ++ option ++ "\"" 64 | | otherwise = option 65 | 66 | ---------------------------------------------------------------- 67 | 68 | -- | Get the root directory of the given Cradle. 69 | rootInfo :: Cradle a 70 | -> IO String 71 | rootInfo cradle = return $ cradleRootDir cradle 72 | 73 | ---------------------------------------------------------------- 74 | 75 | configInfo :: [FilePath] -> IO String 76 | configInfo [] = return "No files given" 77 | configInfo args = 78 | fmap unlines $ forM args $ \fp -> do 79 | fp' <- canonicalizePath fp 80 | (("Config for \"" ++ fp' ++ "\": ") ++) <$> findConfig fp' 81 | 82 | findConfig :: FilePath -> IO String 83 | findConfig fp = findCradle fp >>= \case 84 | Just yaml -> return yaml 85 | _ -> return "No explicit config found" 86 | 87 | ---------------------------------------------------------------- 88 | 89 | cradleInfo :: LogAction IO (WithSeverity Log) -> [FilePath] -> IO String 90 | cradleInfo _ [] = return "No files given" 91 | cradleInfo l args = 92 | fmap unlines $ forM args $ \fp -> do 93 | fp' <- canonicalizePath fp 94 | (("Cradle for \"" ++ fp' ++ "\": ") ++) <$> findCradle' l fp' 95 | 96 | findCradle' :: LogAction IO (WithSeverity Log) -> FilePath -> IO String 97 | findCradle' l fp = 98 | findCradle fp >>= \case 99 | Just yaml -> do 100 | crdl <- loadCradle l yaml 101 | return $ show crdl 102 | Nothing -> do 103 | crdl <- loadImplicitCradle l fp :: IO (Cradle Void) 104 | return $ show crdl 105 | -------------------------------------------------------------------------------- /src/HIE/Bios/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE DeriveTraversable #-} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | module HIE.Bios.Types where 7 | 8 | import System.Exit 9 | import qualified Colog.Core as L 10 | import Control.Exception ( Exception ) 11 | import Control.Monad.IO.Class 12 | import Control.Monad.Trans.Class 13 | #if MIN_VERSION_base(4,9,0) 14 | import qualified Control.Monad.Fail as Fail 15 | #endif 16 | import Data.Maybe (fromMaybe) 17 | import qualified Data.Text as T 18 | import Prettyprinter 19 | import System.Process.Extra (CreateProcess (env, cmdspec), CmdSpec (..)) 20 | 21 | ---------------------------------------------------------------- 22 | -- Environment variables used by hie-bios. 23 | -- 24 | -- If you need more, add a constant here. 25 | ---------------------------------------------------------------- 26 | 27 | -- | Environment variable containing the filepath to which 28 | -- cradle actions write their results to. 29 | -- If the filepath does not exist, cradle actions must create them. 30 | hie_bios_output :: String 31 | hie_bios_output = "HIE_BIOS_OUTPUT" 32 | 33 | -- | Environment variable pointing to the GHC location used by 34 | -- cabal's and stack's GHC wrapper. 35 | -- 36 | -- If not set, will default to sensible defaults. 37 | hie_bios_ghc :: String 38 | hie_bios_ghc = "HIE_BIOS_GHC" 39 | 40 | -- | Environment variable with extra arguments passed to the GHC location 41 | -- in cabal's and stack's GHC wrapper. 42 | -- 43 | -- If not set, assume no extra arguments. 44 | hie_bios_ghc_args :: String 45 | hie_bios_ghc_args = "HIE_BIOS_GHC_ARGS" 46 | 47 | -- | Environment variable pointing to the source file location that caused 48 | -- the cradle action to be executed. 49 | hie_bios_arg :: String 50 | hie_bios_arg = "HIE_BIOS_ARG" 51 | 52 | -- | Environment variable pointing to the multiple source files location that 53 | -- caused the cradle action to be executed. Differs from hie_bios_arg for 54 | -- backwards compatibility. 55 | hie_bios_multi_arg :: String 56 | hie_bios_multi_arg = "HIE_BIOS_MULTI_ARG" 57 | 58 | -- | Environment variable pointing to a filepath to which dependencies 59 | -- of a cradle can be written to by the cradle action. 60 | hie_bios_deps :: String 61 | hie_bios_deps = "HIE_BIOS_DEPS" 62 | 63 | ---------------------------------------------------------------- 64 | 65 | -- | The environment of a single 'Cradle'. 66 | -- A 'Cradle' is a unit for the respective build-system. 67 | -- 68 | -- It contains the root directory of the 'Cradle', the name of 69 | -- the 'Cradle' (for debugging purposes), and knows how to set up 70 | -- a GHC session that is able to compile files that are part of this 'Cradle'. 71 | -- 72 | -- A 'Cradle' may be a single unit in the \"cabal-install\" context, or 73 | -- the whole package, comparable to how \"stack\" works. 74 | data Cradle a = Cradle { 75 | -- | The project root directory. 76 | cradleRootDir :: FilePath 77 | -- | The action which needs to be executed to get the correct 78 | -- command line arguments. 79 | , cradleOptsProg :: CradleAction a 80 | , cradleLogger :: L.LogAction IO (L.WithSeverity Log) 81 | } deriving (Functor) 82 | 83 | instance Show a => Show (Cradle a) where 84 | show (Cradle root prog _) 85 | = "Cradle{ cradleRootDir = " ++ show root 86 | ++", cradleOptsProg = " ++ show prog 87 | ++"}" 88 | 89 | data ActionName a 90 | = Stack 91 | | Cabal 92 | | Bios 93 | | Default 94 | | Multi 95 | | Direct 96 | | None 97 | | Other a 98 | deriving (Show, Eq, Ord, Functor) 99 | 100 | data Log 101 | = LogAny !T.Text 102 | | LogProcessOutput String 103 | | LogCreateProcessRun CreateProcess 104 | | LogProcessRun FilePath [FilePath] 105 | | LogRequestedCradleLoadStyle !T.Text !LoadStyle 106 | | LogComputedCradleLoadStyle !T.Text !LoadStyle 107 | | LogLoadWithContextUnsupported !T.Text !(Maybe T.Text) 108 | | LogCabalLoad !FilePath !(Maybe String) ![FilePath] ![FilePath] 109 | | LogCabalPath !T.Text 110 | deriving (Show) 111 | 112 | instance Pretty Log where 113 | pretty (LogAny s) = pretty s 114 | pretty (LogProcessOutput s) = pretty s 115 | pretty (LogProcessRun fp args) = pretty fp <+> pretty (unwords args) 116 | pretty (LogCreateProcessRun cp) = 117 | vcat $ 118 | [ case cmdspec cp of 119 | ShellCommand sh -> pretty sh 120 | RawCommand cmd args -> pretty cmd <+> pretty (unwords args) 121 | ] 122 | <> 123 | if null envText 124 | then [] 125 | else 126 | [ indent 2 $ "Environment Variables" 127 | , indent 2 $ vcat envText 128 | ] 129 | where 130 | envText = map (indent 2 . pretty) $ prettyProcessEnv cp 131 | pretty (LogRequestedCradleLoadStyle crdlName ls) = 132 | "Requested to load" <+> pretty crdlName <+> "cradle" <+> case ls of 133 | LoadFile -> "using single file mode" 134 | LoadWithContext fps -> "using all files (multi-components):" <> line <> indent 4 (pretty fps) 135 | pretty (LogComputedCradleLoadStyle crdlName ls) = 136 | "Load" <+> pretty crdlName <+> "cradle" <+> case ls of 137 | LoadFile -> "using single file" 138 | LoadWithContext _ -> "using all files (multi-components)" 139 | 140 | pretty (LogLoadWithContextUnsupported crdlName mReason) = 141 | pretty crdlName <+> "cradle doesn't support loading using all files (multi-components)" <> 142 | case mReason of 143 | Nothing -> "." 144 | Just reason -> ", because:" <+> pretty reason <> "." 145 | <+> "Falling back loading to single file mode." 146 | pretty (LogCabalLoad file prefixes projectFile crs) = 147 | "Cabal Loading file" <+> pretty file 148 | <> line <> indent 4 "from project: " <+> pretty projectFile 149 | <> line <> indent 4 "with prefixes:" <+> pretty prefixes 150 | <> line <> indent 4 "with actual loading files:" <+> pretty crs 151 | pretty (LogCabalPath err) = 152 | "Could not parse json output of 'cabal path': " 153 | <> line <> indent 4 (pretty err) 154 | 155 | -- | The 'LoadStyle' instructs a cradle on how to load a given file target. 156 | data LoadStyle 157 | = LoadFile 158 | -- ^ Instruct the cradle to load the given file target. 159 | -- 160 | -- What this entails depends on the cradle. For example, the 'cabal' cradle 161 | -- will configure the whole component the file target belongs to, and produce 162 | -- component options to load the component, which is the minimal unit of code in cabal repl. 163 | -- A 'default' cradle, on the other hand, will only load the given filepath. 164 | | LoadWithContext [FilePath] 165 | -- ^ Give a cradle additional context for loading a file target. 166 | -- 167 | -- The context instructs the cradle to load the file target, while also loading 168 | -- the given filepaths. 169 | -- This is useful for cradles that support loading multiple code units at once, 170 | -- e.g. cabal cradles can use the 'multi-repl' feature to set up a multiple home unit 171 | -- session in GHC. 172 | deriving (Show, Eq, Ord) 173 | 174 | data CradleAction a = CradleAction { 175 | actionName :: ActionName a 176 | -- ^ Name of the action. 177 | , runCradle :: FilePath -> LoadStyle -> IO (CradleLoadResult ComponentOptions) 178 | -- ^ Options to compile the given file with. 179 | , runGhcCmd :: [String] -> IO (CradleLoadResult String) 180 | -- ^ Executes the @ghc@ binary that is usually used to 181 | -- build the cradle. E.g. for a cabal cradle this should be 182 | -- equivalent to @cabal exec ghc -- args@ 183 | } 184 | deriving (Functor) 185 | 186 | instance Show a => Show (CradleAction a) where 187 | show CradleAction { actionName = name } = "CradleAction: " ++ show name 188 | 189 | -- | Result of an attempt to set up a GHC session for a 'Cradle'. 190 | -- This is the go-to error handling mechanism. When possible, this 191 | -- should be preferred over throwing exceptions. 192 | data CradleLoadResult r 193 | = CradleSuccess r -- ^ The cradle succeeded and returned these options. 194 | | CradleFail CradleError -- ^ We tried to load the cradle and it failed. 195 | | CradleNone -- ^ No attempt was made to load the cradle. 196 | deriving (Functor, Foldable, Traversable, Show, Eq) 197 | 198 | cradleLoadResult :: c -> (CradleError -> c) -> (r -> c) -> CradleLoadResult r -> c 199 | cradleLoadResult c _ _ CradleNone = c 200 | cradleLoadResult _ f _ (CradleFail e) = f e 201 | cradleLoadResult _ _ f (CradleSuccess r) = f r 202 | 203 | instance Applicative CradleLoadResult where 204 | pure = CradleSuccess 205 | CradleSuccess a <*> CradleSuccess b = CradleSuccess (a b) 206 | CradleFail err <*> _ = CradleFail err 207 | _ <*> CradleFail err = CradleFail err 208 | _ <*> _ = CradleNone 209 | 210 | instance Monad CradleLoadResult where 211 | return = pure 212 | CradleSuccess r >>= k = k r 213 | CradleFail err >>= _ = CradleFail err 214 | CradleNone >>= _ = CradleNone 215 | 216 | newtype CradleLoadResultT m a = CradleLoadResultT { runCradleResultT :: m (CradleLoadResult a) } 217 | 218 | instance Functor f => Functor (CradleLoadResultT f) where 219 | {-# INLINE fmap #-} 220 | fmap f = CradleLoadResultT . (fmap . fmap) f . runCradleResultT 221 | 222 | instance (Monad m, Applicative m) => Applicative (CradleLoadResultT m) where 223 | {-# INLINE pure #-} 224 | pure = CradleLoadResultT . pure . CradleSuccess 225 | {-# INLINE (<*>) #-} 226 | x <*> f = CradleLoadResultT $ do 227 | a <- runCradleResultT x 228 | case a of 229 | CradleSuccess a' -> do 230 | b <- runCradleResultT f 231 | case b of 232 | CradleSuccess b' -> pure (CradleSuccess (a' b')) 233 | CradleFail err -> pure $ CradleFail err 234 | CradleNone -> pure CradleNone 235 | CradleFail err -> pure $ CradleFail err 236 | CradleNone -> pure CradleNone 237 | 238 | instance Monad m => Monad (CradleLoadResultT m) where 239 | {-# INLINE return #-} 240 | return = pure 241 | {-# INLINE (>>=) #-} 242 | x >>= f = CradleLoadResultT $ do 243 | val <- runCradleResultT x 244 | case val of 245 | CradleSuccess r -> runCradleResultT . f $ r 246 | CradleFail err -> return $ CradleFail err 247 | CradleNone -> return $ CradleNone 248 | #if !(MIN_VERSION_base(4,13,0)) 249 | fail = CradleLoadResultT . fail 250 | {-# INLINE fail #-} 251 | #endif 252 | #if MIN_VERSION_base(4,9,0) 253 | instance Fail.MonadFail m => Fail.MonadFail (CradleLoadResultT m) where 254 | fail = CradleLoadResultT . Fail.fail 255 | {-# INLINE fail #-} 256 | #endif 257 | 258 | instance MonadTrans CradleLoadResultT where 259 | lift = CradleLoadResultT . fmap CradleSuccess 260 | {-# INLINE lift #-} 261 | 262 | instance (MonadIO m) => MonadIO (CradleLoadResultT m) where 263 | liftIO = lift . liftIO 264 | {-# INLINE liftIO #-} 265 | 266 | modCradleError :: Monad m => CradleLoadResultT m a -> (CradleError -> m CradleError) -> CradleLoadResultT m a 267 | modCradleError action f = CradleLoadResultT $ do 268 | a <- runCradleResultT action 269 | case a of 270 | CradleFail err -> CradleFail <$> f err 271 | _ -> pure a 272 | 273 | throwCE :: Monad m => CradleError -> CradleLoadResultT m a 274 | throwCE = CradleLoadResultT . return . CradleFail 275 | 276 | data CradleError = CradleError 277 | { cradleErrorDependencies :: [FilePath] 278 | -- ^ Dependencies of the cradle that failed to load. 279 | -- Can be watched for changes to attempt a reload of the cradle. 280 | , cradleErrorExitCode :: ExitCode 281 | -- ^ ExitCode of the cradle loading mechanism. 282 | , cradleErrorStderr :: [String] 283 | -- ^ Standard error output that can be shown to users to explain 284 | -- the loading error. 285 | , cradleErrorLoadingFiles :: [FilePath] 286 | -- ^ files that were attempted to be loaded by the cradle. 287 | -- This can be useful if we are loading multiple files at once, 288 | -- e.g. in a cabal cradle with the multi-repl feature. 289 | } 290 | deriving (Show, Eq) 291 | 292 | instance Exception CradleError where 293 | ---------------------------------------------------------------- 294 | 295 | -- | Option information for GHC 296 | data ComponentOptions = ComponentOptions { 297 | componentOptions :: [String] -- ^ Command line options. 298 | , componentRoot :: FilePath 299 | -- ^ Root directory of the component. All 'componentOptions' are either 300 | -- absolute, or relative to this directory. 301 | , componentDependencies :: [FilePath] 302 | -- ^ Dependencies of a cradle that might change the cradle. 303 | -- Contains both files specified in hie.yaml as well as 304 | -- specified by the build-tool if there is any. 305 | -- FilePaths are expected to be relative to the `cradleRootDir` 306 | -- to which this CradleAction belongs to. 307 | -- Files returned by this action might not actually exist. 308 | -- This is useful, because, sometimes, adding specific files 309 | -- changes the options that a Cradle may return, thus, needs reload 310 | -- as soon as these files are created. 311 | } deriving (Eq, Ord, Show) 312 | 313 | 314 | -- ------------------------------------------------ 315 | 316 | -- | Prettify 'CmdSpec', so we can show the command to a user 317 | prettyCmdSpec :: CmdSpec -> String 318 | prettyCmdSpec (ShellCommand s) = s 319 | prettyCmdSpec (RawCommand cmd args) = cmd ++ " " ++ unwords args 320 | 321 | -- | Pretty print hie-bios's relevant environment variables. 322 | prettyProcessEnv :: CreateProcess -> [String] 323 | prettyProcessEnv p = 324 | [ key <> ": " <> value 325 | | (key, value) <- fromMaybe [] (env p) 326 | , key `elem` [ hie_bios_output 327 | , hie_bios_ghc 328 | , hie_bios_ghc_args 329 | , hie_bios_arg 330 | , hie_bios_deps 331 | ] 332 | ] 333 | -------------------------------------------------------------------------------- /src/HIE/Bios/Wrappers.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE QuasiQuotes #-} 3 | {-# LANGUAGE CPP #-} 4 | module HIE.Bios.Wrappers (cabalWrapper, cabalWrapperHs) where 5 | 6 | import Data.FileEmbed 7 | #if __GLASGOW_HASKELL__ >= 903 8 | hiding (makeRelativeToProject) 9 | #endif 10 | import Language.Haskell.TH.Syntax 11 | 12 | cabalWrapper :: String 13 | cabalWrapper = $(makeRelativeToProject "wrappers/cabal" >>= embedStringFile) 14 | 15 | cabalWrapperHs :: String 16 | cabalWrapperHs = $(makeRelativeToProject "wrappers/cabal.hs" >>= embedStringFile) 17 | 18 | -------------------------------------------------------------------------------- /tests/BiosTests.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | {-# LANGUAGE RecordWildCards #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE CPP #-} 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE TypeApplications #-} 7 | module Main where 8 | 9 | import Utils 10 | 11 | import Test.Tasty 12 | import Test.Tasty.HUnit 13 | import Test.Tasty.ExpectedFailure 14 | import qualified Test.Tasty.Options as Tasty 15 | import qualified Test.Tasty.Ingredients as Tasty 16 | import HIE.Bios 17 | import HIE.Bios.Cradle 18 | import Control.Monad ( forM_ ) 19 | import Data.List ( sort, isPrefixOf ) 20 | import Data.Typeable 21 | import System.Directory 22 | import System.FilePath ((), makeRelative) 23 | import System.Exit (ExitCode(ExitSuccess, ExitFailure)) 24 | import Control.Monad.Extra (unlessM) 25 | import qualified HIE.Bios.Ghc.Gap as Gap 26 | import Control.Monad.IO.Class 27 | 28 | argDynamic :: [String] 29 | argDynamic = ["-dynamic" | Gap.hostIsDynamic] 30 | 31 | -- | This ghc version is assumed to be tested by CI to validate 32 | -- the "with-compiler" field is honoured by hie-bios. 33 | -- 34 | -- If you change this version, make sure to also update 'cabal.project' 35 | -- in 'tests\/projects\/cabal-with-ghc'. 36 | extraGhcVersion :: String 37 | extraGhcVersion = "9.2.8" 38 | 39 | extraGhc :: String 40 | extraGhc = "ghc-" ++ extraGhcVersion 41 | 42 | -- | To get all logs, run the tests via: 43 | -- 44 | -- @ 45 | -- cabal run test:bios-tests --test-options="--debug" 46 | -- @ 47 | -- 48 | -- or 49 | -- 50 | -- @ 51 | -- TASTY_DEBUG=TRUE cabal test test:bios-tests 52 | -- @ 53 | -- 54 | -- to avoid recompilation. 55 | main :: IO () 56 | main = do 57 | writeStackYamlFiles 58 | stackDep <- checkToolIsAvailable "stack" 59 | cabalDep <- checkToolIsAvailable "cabal" 60 | extraGhcDep <- checkToolIsAvailable extraGhc 61 | 62 | defaultMainWithIngredients (ignoreToolTests:verboseLogging:defaultIngredients) $ 63 | testGroup "Bios-tests" 64 | [ testGroup "Find cradle" findCradleTests 65 | , testGroup "Symlink" symbolicLinkTests 66 | , testGroup "Loading tests" 67 | [ testGroup "bios" biosTestCases 68 | , testGroup "direct" directTestCases 69 | , testGroupWithDependency cabalDep (cabalTestCases extraGhcDep) 70 | , ignoreOnUnsupportedGhc $ testGroupWithDependency stackDep stackTestCases 71 | ] 72 | ] 73 | 74 | symbolicLinkTests :: [TestTree] 75 | symbolicLinkTests = 76 | [ biosTestCase "Can load base module" $ runTestEnv "./symlink-test" $ do 77 | initCradle "doesNotExist.hs" 78 | assertCradle isMultiCradle 79 | step "Attempt to load symlinked module A" 80 | inCradleRootDir $ do 81 | loadComponentOptions "./a/A.hs" 82 | assertComponentOptions $ \opts -> 83 | componentOptions opts `shouldMatchList` ["a"] <> argDynamic 84 | 85 | , biosTestCase "Can load symlinked module" $ runTestEnv "./symlink-test" $ do 86 | initCradle "doesNotExist.hs" 87 | assertCradle isMultiCradle 88 | step "Attempt to load symlinked module A" 89 | inCradleRootDir $ do 90 | liftIO $ createDirectoryLink "./a" "./b" 91 | liftIO $ unlessM (doesFileExist "./b/A.hs") $ 92 | assertFailure "Test invariant broken, this file must exist." 93 | loadComponentOptions "./b/A.hs" 94 | assertComponentOptions $ \opts -> 95 | componentOptions opts `shouldMatchList` ["b"] <> argDynamic 96 | , biosTestCase "Can not load symlinked module that is ignored" $ runTestEnv "./symlink-test" $ do 97 | initCradle "doesNotExist.hs" 98 | assertCradle isMultiCradle 99 | step "Attempt to load symlinked module A" 100 | inCradleRootDir $ do 101 | liftIO $ createDirectoryLink "./a" "./c" 102 | liftIO $ unlessM (doesFileExist "./c/A.hs") $ 103 | assertFailure "Test invariant broken, this file must exist." 104 | loadComponentOptions "./c/A.hs" 105 | assertLoadNone 106 | ] 107 | 108 | biosTestCases :: [TestTree] 109 | biosTestCases = 110 | [ biosTestCase "failing-bios" $ runTestEnv "./failing-bios" $ do 111 | initCradle "B.hs" 112 | assertCradle isBiosCradle 113 | loadComponentOptions "B.hs" 114 | assertCradleError $ \CradleError {..} -> do 115 | cradleErrorExitCode @?= ExitFailure 1 116 | cradleErrorDependencies `shouldMatchList` ["hie.yaml"] 117 | , biosTestCase "failing-bios-ghc" $ runTestEnv "./failing-bios-ghc" $ do 118 | initCradle "B.hs" 119 | assertCradle isBiosCradle 120 | loadRuntimeGhcVersion 121 | ghcVersionLR <- askGhcVersionResult 122 | assertCradleLoadError ghcVersionLR >>= \CradleError {..} -> liftIO $ do 123 | cradleErrorExitCode @?= ExitSuccess 124 | cradleErrorDependencies `shouldMatchList` [] 125 | length cradleErrorStderr @?= 1 126 | "Couldn't execute myGhc" `isPrefixOf` head cradleErrorStderr @? "Error message should contain basic information" 127 | , biosTestCase "simple-bios-shell" $ runTestEnv "./simple-bios-shell" $ do 128 | testDirectoryM isBiosCradle "B.hs" 129 | , biosTestCase "simple-bios-shell-deps" $ runTestEnv "./simple-bios-shell" $ do 130 | biosCradleDeps "B.hs" ["hie.yaml"] 131 | ] <> concat [linuxTestCases | False] -- TODO(fendor), enable again 132 | where 133 | biosCradleDeps :: FilePath -> [FilePath] -> TestM () 134 | biosCradleDeps fp deps = do 135 | initCradle fp 136 | assertCradle isBiosCradle 137 | loadComponentOptions fp 138 | assertComponentOptions $ \opts -> do 139 | deps @?= componentDependencies opts 140 | 141 | linuxTestCases = 142 | [ biosTestCase "simple-bios" $ runTestEnv "./simple-bios" $ 143 | testDirectoryM isBiosCradle "B.hs" 144 | , biosTestCase "simple-bios-ghc" $ runTestEnv "./simple-bios-ghc" $ 145 | testDirectoryM isBiosCradle "B.hs" 146 | , biosTestCase "simple-bios-deps" $ runTestEnv "./simple-bios" $ do 147 | biosCradleDeps "B.hs" ["hie-bios.sh", "hie.yaml"] 148 | , biosTestCase "simple-bios-deps-new" $ runTestEnv "./deps-bios-new" $ do 149 | biosCradleDeps "B.hs" ["hie-bios.sh", "hie.yaml"] 150 | ] 151 | 152 | cabalTestCases :: ToolDependency -> [TestTree] 153 | cabalTestCases extraGhcDep = 154 | [ 155 | biosTestCase "failing-cabal" $ runTestEnv "./failing-cabal" $ do 156 | cabalAttemptLoad "MyLib.hs" 157 | assertCradleError (\CradleError {..} -> do 158 | cradleErrorExitCode @?= ExitFailure 1 159 | cradleErrorDependencies `shouldMatchList` ["failing-cabal.cabal", "cabal.project", "cabal.project.local"]) 160 | , biosTestCase "failing-cabal-multi-repl-with-shrink-error-files" $ runTestEnv "./failing-multi-repl-cabal-project" $ do 161 | cabalAttemptLoadFiles "multi-repl-cabal-fail/app/Main.hs" ["multi-repl-cabal-fail/src/Lib.hs", "multi-repl-cabal-fail/src/Fail.hs", "NotInPath.hs"] 162 | root <- askRoot 163 | multiSupported <- isCabalMultipleCompSupported' 164 | if multiSupported 165 | then 166 | assertCradleError (\CradleError {..} -> do 167 | cradleErrorExitCode @?= ExitFailure 1 168 | cradleErrorDependencies `shouldMatchList` ["cabal.project","cabal.project.local","multi-repl-cabal-fail.cabal"] 169 | -- NotInPath.hs does not match the cradle for `app/Main.hs`, so it should not be tried. 170 | (makeRelative root <$> cradleErrorLoadingFiles) `shouldMatchList` ["multi-repl-cabal-fail/app/Main.hs","multi-repl-cabal-fail/src/Fail.hs","multi-repl-cabal-fail/src/Lib.hs"]) 171 | else assertLoadSuccess >>= \ComponentOptions {} -> do 172 | return () 173 | , biosTestCase "simple-cabal" $ runTestEnv "./simple-cabal" $ do 174 | testDirectoryM isCabalCradle "B.hs" 175 | , biosTestCase "nested-cabal" $ runTestEnv "./nested-cabal" $ do 176 | cabalAttemptLoad "sub-comp/Lib.hs" 177 | assertComponentOptions $ \opts -> do 178 | componentDependencies opts `shouldMatchList` 179 | [ "sub-comp" "sub-comp.cabal" 180 | , "cabal.project" 181 | , "cabal.project.local" 182 | ] 183 | , biosTestCase "nested-cabal2" $ runTestEnv "./nested-cabal" $ do 184 | cabalAttemptLoad "MyLib.hs" 185 | assertComponentOptions $ \opts -> do 186 | componentDependencies opts `shouldMatchList` 187 | [ "nested-cabal.cabal" 188 | , "cabal.project" 189 | , "cabal.project.local" 190 | ] 191 | , biosTestCase "multi-cabal" $ runTestEnv "./multi-cabal" $ do 192 | {- tests if both components can be loaded -} 193 | testDirectoryM isCabalCradle "app/Main.hs" 194 | testDirectoryM isCabalCradle "src/Lib.hs" 195 | , {- issue https://github.com/mpickering/hie-bios/issues/200 -} 196 | biosTestCase "monorepo-cabal" $ runTestEnv "./monorepo-cabal" $ do 197 | testDirectoryM isCabalCradle "A/Main.hs" 198 | testDirectoryM isCabalCradle "B/MyLib.hs" 199 | , testGroup "Implicit cradle tests" $ 200 | [ biosTestCase "implicit-cabal" $ runTestEnv "./implicit-cabal" $ do 201 | testImplicitDirectoryM isCabalCradle "Main.hs" 202 | , biosTestCase "implicit-cabal-no-project" $ runTestEnv "./implicit-cabal-no-project" $ do 203 | testImplicitDirectoryM isCabalCradle "Main.hs" 204 | , biosTestCase "implicit-cabal-deep-project" $ runTestEnv "./implicit-cabal-deep-project" $ do 205 | testImplicitDirectoryM isCabalCradle "foo/Main.hs" 206 | ] 207 | , testGroupWithDependency extraGhcDep 208 | [ biosTestCase "Appropriate ghc and libdir" $ runTestEnvLocal "./cabal-with-ghc" $ do 209 | initCradle "src/MyLib.hs" 210 | assertCradle isCabalCradle 211 | loadRuntimeGhcLibDir 212 | assertLibDirVersionIs extraGhcVersion 213 | loadRuntimeGhcVersion 214 | assertGhcVersionIs extraGhcVersion 215 | ] 216 | , testGroup "Cabal cabalProject" 217 | [ biosTestCase "cabal-with-project, options propagated" $ runTestEnv "cabal-with-project" $ do 218 | opts <- cabalLoadOptions "src/MyLib.hs" 219 | liftIO $ do 220 | "-O2" `elem` componentOptions opts 221 | @? "Options must contain '-O2'" 222 | , biosTestCase "cabal-with-project, load" $ runTestEnv "cabal-with-project" $ do 223 | testDirectoryM isCabalCradle "src/MyLib.hs" 224 | , biosTestCase "multi-cabal-with-project, options propagated" $ runTestEnv "multi-cabal-with-project" $ do 225 | optsAppA <- cabalLoadOptions "appA/src/Lib.hs" 226 | liftIO $ do 227 | "-O2" `elem` componentOptions optsAppA 228 | @? "Options must contain '-O2'" 229 | optsAppB <- cabalLoadOptions "appB/src/Lib.hs" 230 | liftIO $ do 231 | "-O2" `notElem` componentOptions optsAppB 232 | @? "Options must not contain '-O2'" 233 | , biosTestCase "multi-cabal-with-project, load" $ runTestEnv "multi-cabal-with-project" $ do 234 | testDirectoryM isCabalCradle "appB/src/Lib.hs" 235 | testDirectoryM isCabalCradle "appB/src/Lib.hs" 236 | , testGroupWithDependency extraGhcDep 237 | [ biosTestCase "Honours extra ghc setting" $ runTestEnv "cabal-with-ghc-and-project" $ do 238 | initCradle "src/MyLib.hs" 239 | assertCradle isCabalCradle 240 | loadRuntimeGhcLibDir 241 | assertLibDirVersionIs extraGhcVersion 242 | loadRuntimeGhcVersion 243 | assertGhcVersionIs extraGhcVersion 244 | ] 245 | ] 246 | ] 247 | where 248 | cabalAttemptLoad :: FilePath -> TestM () 249 | cabalAttemptLoad fp = do 250 | initCradle fp 251 | assertCradle isCabalCradle 252 | loadComponentOptions fp 253 | 254 | cabalAttemptLoadFiles :: FilePath -> [FilePath] -> TestM () 255 | cabalAttemptLoadFiles fp fps = do 256 | initCradle fp 257 | assertCradle isCabalCradle 258 | loadComponentOptionsMultiStyle fp fps 259 | 260 | cabalLoadOptions :: FilePath -> TestM ComponentOptions 261 | cabalLoadOptions fp = do 262 | initCradle fp 263 | assertCradle isCabalCradle 264 | loadComponentOptions fp 265 | assertLoadSuccess 266 | 267 | stackTestCases :: [TestTree] 268 | stackTestCases = 269 | [ expectFailBecause "stack repl does not fail on an invalid cabal file" $ 270 | biosTestCase "failing-stack" $ runTestEnv "./failing-stack" $ do 271 | stackAttemptLoad "src/Lib.hs" 272 | assertCradleError $ \CradleError {..} -> do 273 | cradleErrorExitCode @?= ExitFailure 1 274 | cradleErrorDependencies `shouldMatchList` ["failing-stack.cabal", "stack.yaml", "package.yaml"] 275 | , biosTestCase "simple-stack" $ runTestEnv "./simple-stack" $ do 276 | testDirectoryM isStackCradle "B.hs" 277 | , biosTestCase "multi-stack" $ runTestEnv "./multi-stack" $ do {- tests if both components can be loaded -} 278 | testDirectoryM isStackCradle "app/Main.hs" 279 | testDirectoryM isStackCradle "src/Lib.hs" 280 | , biosTestCase "nested-stack" $ runTestEnv "./nested-stack" $ do 281 | stackAttemptLoad "sub-comp/Lib.hs" 282 | assertComponentOptions $ \opts -> 283 | componentDependencies opts `shouldMatchList` ["sub-comp" "sub-comp.cabal", "sub-comp" "package.yaml", "stack.yaml"] 284 | , biosTestCase "nested-stack2" $ runTestEnv "./nested-stack" $ do 285 | stackAttemptLoad "MyLib.hs" 286 | assertComponentOptions $ \opts -> 287 | componentDependencies opts `shouldMatchList` ["nested-stack.cabal", "package.yaml", "stack.yaml"] 288 | , biosTestCase "stack-with-yaml" $ runTestEnv "./stack-with-yaml" $ do 289 | {- tests if both components can be loaded -} 290 | testDirectoryM isStackCradle "app/Main.hs" 291 | testDirectoryM isStackCradle "src/Lib.hs" 292 | , biosTestCase "multi-stack-with-yaml" $ runTestEnv "./multi-stack-with-yaml" $ do 293 | {- tests if both components can be loaded -} 294 | testDirectoryM isStackCradle "appA/src/Lib.hs" 295 | testDirectoryM isStackCradle "appB/src/Lib.hs" 296 | , 297 | -- Test for special characters in the path for parsing of the ghci-scripts. 298 | -- Issue https://github.com/mpickering/hie-bios/issues/162 299 | biosTestCase "space stack" $ runTestEnv "./space stack" $ do 300 | testDirectoryM isStackCradle "A.hs" 301 | testDirectoryM isStackCradle "B.hs" 302 | , testGroup "Implicit cradle tests" 303 | [ biosTestCase "implicit-stack" $ runTestEnv "./implicit-stack" $ 304 | testImplicitDirectoryM isStackCradle "Main.hs" 305 | , biosTestCase "implicit-stack-multi" $ runTestEnv "./implicit-stack-multi" $ do 306 | testImplicitDirectoryM isStackCradle "Main.hs" 307 | testImplicitDirectoryM isStackCradle "other-package/Main.hs" 308 | ] 309 | ] 310 | where 311 | stackAttemptLoad :: FilePath -> TestM () 312 | stackAttemptLoad fp = do 313 | initCradle fp 314 | assertCradle isStackCradle 315 | loadComponentOptions fp 316 | 317 | directTestCases :: [TestTree] 318 | directTestCases = 319 | [ biosTestCase "simple-direct" $ runTestEnv "./simple-direct" $ do 320 | testDirectoryM isDirectCradle "B.hs" 321 | , biosTestCase "multi-direct" $ runTestEnv "./multi-direct" $ do 322 | {- tests if both components can be loaded -} 323 | testDirectoryM isMultiCradle "A.hs" 324 | testDirectoryM isMultiCradle "B.hs" 325 | ] 326 | 327 | findCradleTests :: [TestTree] 328 | findCradleTests = 329 | [ cradleFileTest "Simple Existing File" "./simple-cabal" "B.hs" (Just "hie.yaml") 330 | -- Checks if we can find a hie.yaml even when the given filepath 331 | -- is unknown. This functionality is required by Haskell IDE Engine. 332 | , cradleFileTest "Existing File" "cabal-with-ghc" "src/MyLib.hs" (Just "hie.yaml") 333 | , cradleFileTest "Non-existing file" "cabal-with-ghc" "src/MyLib2.hs" (Just "hie.yaml") 334 | , cradleFileTest "Non-existing file 2" "cabal-with-ghc" "MyLib2.hs" (Just "hie.yaml") 335 | , cradleFileTest "Directory 1" "cabal-with-ghc" "src/" (Just "hie.yaml") 336 | , cradleFileTest "Directory 2" "simple-cabal" "" (Just "hie.yaml") 337 | -- Unknown directory in a project, ought to work as well. 338 | , cradleFileTest "Directory 3" "simple-cabal" "src/" (Just "hie.yaml") 339 | , cradleFileTest "Directory does not exist" "doesnotexist" "A.hs" Nothing 340 | ] 341 | where 342 | cradleFileTest :: String -> FilePath -> FilePath -> Maybe FilePath -> TestTree 343 | cradleFileTest testName dir fpTarget result = biosTestCase testName $ do 344 | runTestEnv dir $ do 345 | findCradleForModuleM fpTarget result 346 | 347 | -- ------------------------------------------------------------------ 348 | -- Unit-test Helper functions 349 | -- ------------------------------------------------------------------ 350 | 351 | shouldMatchList :: (Show a, Ord a) => [a] -> [a] -> Assertion 352 | shouldMatchList xs ys = sort xs @?= sort ys 353 | 354 | infix 1 `shouldMatchList` 355 | 356 | biosTestCase :: TestName -> (Bool -> Assertion) -> TestTree 357 | biosTestCase name assertion = askOption @VerboseLogging (\case 358 | VerboseLogging verbose -> testCase name (assertion verbose) 359 | ) 360 | 361 | 362 | -- ------------------------------------------------------------------ 363 | -- Stack related helper functions 364 | -- ------------------------------------------------------------------ 365 | 366 | writeStackYamlFiles :: IO () 367 | writeStackYamlFiles = 368 | forM_ stackProjects $ \(proj, syaml, pkgs) -> 369 | writeFile (proj syaml) (stackYaml stackYamlResolver pkgs) 370 | 371 | stackProjects :: [(FilePath, FilePath, [FilePath])] 372 | stackProjects = 373 | [ ("tests" "projects" "multi-stack", "stack.yaml", ["."]) 374 | , ("tests" "projects" "failing-stack", "stack.yaml", ["."]) 375 | , ("tests" "projects" "simple-stack", "stack.yaml", ["."]) 376 | , ("tests" "projects" "nested-stack", "stack.yaml", [".", "./sub-comp"]) 377 | , ("tests" "projects" "space stack", "stack.yaml", ["."]) 378 | , ("tests" "projects" "implicit-stack", "stack.yaml", ["."]) 379 | , ("tests" "projects" "implicit-stack-multi", "stack.yaml", ["."]) 380 | , ("tests" "projects" "implicit-stack-multi", "stack.yaml", ["."]) 381 | , ("tests" "projects" "multi-stack-with-yaml", "stack-alt.yaml", ["appA", "appB"]) 382 | , ("tests" "projects" "stack-with-yaml", "stack-alt.yaml", ["."]) 383 | ] 384 | 385 | stackYaml :: String -> [FilePath] -> String 386 | stackYaml resolver pkgs = unlines 387 | $ ["resolver: " ++ resolver, "packages:"] 388 | ++ map ("- " ++) pkgs 389 | 390 | stackYamlResolver :: String 391 | stackYamlResolver = 392 | #if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(9,10,0,0))) 393 | "nightly-2025-04-24" -- GHC 9.10.1 394 | #elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(9,8,0,0))) 395 | "lts-23.19" -- GHC 9.8.4 396 | #elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(9,6,0,0))) 397 | "lts-22.43" -- GHC 9.6.6 398 | #elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(9,4,0,0))) 399 | "lts-21.25" -- GHC 9.4.8 400 | #elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(9,2,0,0))) 401 | "lts-20.26" -- GHC 9.2.8 402 | #endif 403 | 404 | -- ------------------------------------------------------------------ 405 | -- Most tests have some run-time tool dependencies. 406 | -- We only want to run tests if these tools are available. 407 | -- ------------------------------------------------------------------ 408 | 409 | data ToolDependency = ToolDependency 410 | { toolName :: String 411 | , toolExists :: Bool 412 | } 413 | 414 | checkToolIsAvailable :: String -> IO ToolDependency 415 | checkToolIsAvailable f = do 416 | exists <- maybe False (const True) <$> findExecutable f 417 | pure ToolDependency 418 | { toolName = f 419 | , toolExists = exists 420 | } 421 | 422 | testGroupWithDependency :: ToolDependency -> [TestTree] -> TestTree 423 | testGroupWithDependency td tc = askOption @IgnoreToolDeps (\case 424 | IgnoreToolDeps ignoreToolDep 425 | | ignoreToolDep || toolExists td -> tg 426 | | otherwise -> itg 427 | ) 428 | where 429 | tg = testGroup (toolName td) tc 430 | 431 | itg = 432 | ignoreTestBecause 433 | ("These tests require that the following" ++ 434 | " tool can be found on the path: " ++ toolName td) 435 | tg 436 | 437 | -- ------------------------------------------------------------------ 438 | -- Run test-suite ignoring run-time tool dependencies. 439 | -- Can be used to force CI to run the whole test-suite. 440 | -- Makes sure that the full test-suite is being run on a properly configured 441 | -- environment. 442 | -- ------------------------------------------------------------------ 443 | 444 | -- | This option, when set to 'True', specifies that we should run in the 445 | -- «list tests» mode 446 | newtype IgnoreToolDeps = IgnoreToolDeps Bool 447 | deriving (Eq, Ord, Typeable) 448 | 449 | instance Tasty.IsOption IgnoreToolDeps where 450 | defaultValue = IgnoreToolDeps False 451 | parseValue = fmap IgnoreToolDeps . Tasty.safeReadBool 452 | optionName = pure "ignore-tool-deps" 453 | optionHelp = pure "Run tests whether their tool dependencies exist or not" 454 | optionCLParser = Tasty.flagCLParser Nothing (IgnoreToolDeps True) 455 | 456 | -- | The ingredient that provides the "ignore missing run-time dependencies" functionality 457 | ignoreToolTests :: Tasty.Ingredient 458 | ignoreToolTests = Tasty.TestManager [Tasty.Option (Proxy :: Proxy IgnoreToolDeps)] $ 459 | \_opts _tree -> Nothing 460 | 461 | newtype VerboseLogging = VerboseLogging Bool 462 | 463 | instance Tasty.IsOption VerboseLogging where 464 | defaultValue = VerboseLogging False 465 | parseValue = fmap VerboseLogging . Tasty.safeReadBool 466 | optionName = pure "debug" 467 | optionHelp = pure "Run the tests with verbose logging" 468 | optionCLParser = Tasty.flagCLParser Nothing (VerboseLogging True) 469 | 470 | -- | The ingredient that provides the "ignore missing run-time dependencies" functionality 471 | verboseLogging :: Tasty.Ingredient 472 | verboseLogging = Tasty.TestManager [Tasty.Option (Proxy :: Proxy VerboseLogging)] $ 473 | \_opts _tree -> Nothing 474 | 475 | -- ------------------------------------------------------------------ 476 | -- Ignore test group if built with GHC 9.6.7 and GHC 9.12.2 477 | -- ------------------------------------------------------------------ 478 | 479 | ignoreOnUnsupportedGhc :: TestTree -> TestTree 480 | ignoreOnUnsupportedGhc tt = 481 | #if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(9,6,7,0) && !MIN_VERSION_GLASGOW_HASKELL(9,6,8,0)) || (MIN_VERSION_GLASGOW_HASKELL(9,12,2,0) && !MIN_VERSION_GLASGOW_HASKELL(9,12,3,0))) 482 | ignoreTestBecause "Not supported on 9.6.7 and 9.12.2" 483 | #endif 484 | tt 485 | 486 | -------------------------------------------------------------------------------- /tests/ParserTests.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE CPP #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | module Main where 5 | 6 | import Test.Tasty 7 | import Test.Tasty.HUnit 8 | import HIE.Bios.Config 9 | #if MIN_VERSION_aeson(2,0,0) 10 | import Data.Aeson.Key ( Key ) 11 | import qualified Data.Aeson.KeyMap as Map 12 | #else 13 | import qualified Data.HashMap.Strict as Map 14 | import qualified Data.Text as T 15 | #endif 16 | import Data.Void 17 | import Data.Yaml 18 | import System.FilePath 19 | import Control.Applicative ( (<|>) ) 20 | import Control.Exception 21 | import Data.Typeable 22 | 23 | configDir :: FilePath 24 | configDir = "tests/configs" 25 | 26 | main :: IO () 27 | main = defaultMain $ 28 | testCase "Parser Tests" $ do 29 | assertParser "cabal-1.yaml" (noDeps (Cabal $ CabalType (Just "lib:hie-bios") Nothing)) 30 | assertParser "stack-config.yaml" (noDeps (Stack $ StackType Nothing Nothing)) 31 | --assertParser "bazel.yaml" (noDeps Bazel) 32 | assertParser "bios-1.yaml" (noDeps (Bios (Program "program") Nothing Nothing)) 33 | assertParser "bios-2.yaml" (noDeps (Bios (Program "program") (Just (Program "dep-program")) Nothing)) 34 | assertParser "bios-3.yaml" (noDeps (Bios (Command "shellcommand") Nothing Nothing)) 35 | assertParser "bios-4.yaml" (noDeps (Bios (Command "shellcommand") (Just (Command "dep-shellcommand")) Nothing)) 36 | assertParser "bios-5.yaml" (noDeps (Bios (Command "shellcommand") (Just (Program "dep-program")) Nothing)) 37 | assertParser "dependencies.yaml" (Config (CradleConfig ["depFile"] (Cabal $ CabalType (Just "lib:hie-bios") Nothing))) 38 | assertParser "direct.yaml" (noDeps (Direct ["list", "of", "arguments"])) 39 | assertParser "none.yaml" (noDeps None) 40 | --assertParser "obelisk.yaml" (noDeps Obelisk) 41 | assertParser "multi.yaml" (noDeps (Multi [("./src", CradleConfig [] (Cabal $ CabalType (Just "lib:hie-bios") Nothing)) 42 | ,("./test", CradleConfig [] (Cabal $ CabalType (Just "test") Nothing))])) 43 | 44 | assertParser "cabal-multi.yaml" (noDeps (CabalMulti (CabalType Nothing Nothing) 45 | [("./src", CabalType (Just "lib:hie-bios") Nothing) 46 | ,("./", CabalType (Just "lib:hie-bios") Nothing)])) 47 | 48 | assertParser "stack-multi.yaml" (noDeps (StackMulti (StackType Nothing Nothing) 49 | [("./src", StackType (Just "lib:hie-bios") Nothing) 50 | ,("./", StackType (Just"lib:hie-bios") Nothing)])) 51 | 52 | assertParser "nested-cabal-multi.yaml" (noDeps (Multi [("./test/testdata", CradleConfig [] None) 53 | ,("./", CradleConfig [] ( 54 | CabalMulti (CabalType Nothing Nothing) 55 | [("./src", CabalType (Just "lib:hie-bios") Nothing) 56 | ,("./tests", CabalType (Just "parser-tests") Nothing)]))])) 57 | 58 | assertParser "nested-stack-multi.yaml" (noDeps (Multi [("./test/testdata", CradleConfig [] None) 59 | ,("./", CradleConfig [] ( 60 | StackMulti (StackType Nothing Nothing) 61 | [("./src", StackType (Just "lib:hie-bios") Nothing) 62 | ,("./tests", StackType (Just "parser-tests") Nothing)]))])) 63 | -- Assertions for cabal.project files 64 | assertParser "cabal-with-project.yaml" 65 | (noDeps (Cabal $ CabalType Nothing (Just "cabal.project.9.2.8"))) 66 | assertParser "cabal-with-both.yaml" 67 | (noDeps (Cabal $ CabalType (Just "hie-bios:hie") (Just "cabal.project.9.2.8"))) 68 | assertParser "multi-cabal-with-project.yaml" 69 | (noDeps (CabalMulti (CabalType Nothing (Just "cabal.project.9.2.8")) 70 | [("./src", CabalType (Just "lib:hie-bios") Nothing) 71 | ,("./vendor", CabalType (Just "parser-tests") Nothing)])) 72 | -- Assertions for stack.yaml files 73 | assertParser "stack-with-yaml.yaml" 74 | (noDeps (Stack $ StackType Nothing (Just "stack-8.8.3.yaml"))) 75 | assertParser "stack-with-both.yaml" 76 | (noDeps (Stack $ StackType (Just "hie-bios:hie") (Just "stack-8.8.3.yaml"))) 77 | assertParser "multi-stack-with-yaml.yaml" 78 | (noDeps (StackMulti (StackType Nothing (Just "stack-8.8.3.yaml")) 79 | [("./src", StackType (Just "lib:hie-bios") Nothing) 80 | ,("./vendor", StackType (Just "parser-tests") Nothing)])) 81 | 82 | assertCustomParser "ch-cabal.yaml" 83 | (noDeps (Other CabalHelperCabal $ simpleCabalHelperYaml "cabal")) 84 | assertCustomParser "ch-stack.yaml" 85 | (noDeps (Other CabalHelperStack $ simpleCabalHelperYaml "stack")) 86 | assertCustomParser "multi-ch.yaml" 87 | (noDeps (Multi 88 | [ ("./src", CradleConfig [] (Other CabalHelperStack $ simpleCabalHelperYaml "stack")) 89 | , ("./input", CradleConfig [] (Other CabalHelperCabal $ simpleCabalHelperYaml "cabal")) 90 | , ("./test", CradleConfig [] (Cabal $ CabalType (Just "test") Nothing)) 91 | , (".", CradleConfig [] None) 92 | ])) 93 | assertParserFails "keys-not-unique-fails.yaml" invalidYamlException 94 | assertParser "cabal-empty-config.yaml" (noDeps (Cabal $ CabalType Nothing Nothing)) 95 | 96 | assertParser :: FilePath -> Config Void -> Assertion 97 | assertParser fp cc = do 98 | conf <- readConfig (configDir fp) 99 | (conf == cc) @? (unlines [("Parser Failed: " ++ fp) 100 | , "Expected: " ++ show cc 101 | , "Actual: " ++ show conf ]) 102 | 103 | invalidYamlException :: Selector ParseException 104 | invalidYamlException (InvalidYaml (Just _)) = True 105 | invalidYamlException _ = False 106 | 107 | assertParserFails :: Exception e => FilePath -> Selector e -> Assertion 108 | assertParserFails fp es = (readConfig (configDir fp) :: IO (Config Void)) `shouldThrow` es 109 | 110 | assertCustomParser :: FilePath -> Config CabalHelper -> Assertion 111 | assertCustomParser fp cc = do 112 | conf <- readConfig (configDir fp) 113 | (conf == cc) @? (unlines [("Parser Failed: " ++ fp) 114 | , "Expected: " ++ show cc 115 | , "Actual: " ++ show conf ]) 116 | 117 | noDeps :: CradleTree a -> Config a 118 | noDeps c = Config (CradleConfig [] c) 119 | 120 | shouldThrow :: (HasCallStack, Exception e) => IO a -> Selector e -> Assertion 121 | shouldThrow act select = do 122 | r <- try act 123 | case r of 124 | Left (exc :: e) 125 | | select exc -> pure () 126 | Left exc -> assertFailure $ "Exception Type is not expected: " ++ exceptionType ++ " (" ++ show exc ++ ")" 127 | Right _ -> assertFailure $ "did not get expected exception: " ++ exceptionType 128 | where 129 | -- a string repsentation of the expected exception's type 130 | exceptionType = (show . typeOf . instanceOf) select 131 | where 132 | instanceOf :: Selector a -> a 133 | instanceOf _ = error "ParserTests.shouldThrow: broken Typeable instance" 134 | 135 | -- ------------------------------------------------------------------ 136 | 137 | data CabalHelper 138 | = CabalHelperCabal 139 | | CabalHelperStack 140 | deriving (Show, Eq) 141 | 142 | instance FromJSON CabalHelper where 143 | parseJSON (Object o) 144 | | Just obj <- Map.lookup "cabal-helper" o = chCabal obj <|> chStack obj 145 | where 146 | chCabal (Object val) 147 | | Just _val <- Map.lookup "cabal" val = return CabalHelperCabal 148 | chCabal _ = fail "CH: not a cabal cradle." 149 | 150 | chStack (Object val) 151 | | Just _val <- Map.lookup "stack" val = return CabalHelperStack 152 | chStack _ = fail "CH: not a stack cradle." 153 | 154 | parseJSON _ = fail "Not a valid cabal-helper specification" 155 | 156 | simpleCabalHelperYaml :: Key -> Value 157 | simpleCabalHelperYaml tool = 158 | object 159 | [ ( "cabal-helper", object 160 | [ (tool, Null) 161 | ] 162 | ) 163 | ] 164 | 165 | type Selector a = a -> Bool 166 | 167 | -- ------------------------------------------------------------------ 168 | -- Helper functions to support aeson < 2 169 | -- ------------------------------------------------------------------ 170 | 171 | #if !MIN_VERSION_aeson(2,0,0) 172 | type Key = T.Text 173 | #endif 174 | -------------------------------------------------------------------------------- /tests/Utils.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE LambdaCase #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | 6 | module Utils ( 7 | -- * Test Environment 8 | TestM, 9 | TestEnv, 10 | TestConfig (..), 11 | defConfig, 12 | 13 | -- * Run Tests 14 | runTestEnv, 15 | runTestEnv', 16 | runTestEnvLocal, 17 | 18 | -- * Low-level test-env modification helpers 19 | setCradle, 20 | unsetCradle, 21 | setLoadResult, 22 | unsetLoadResult, 23 | setLibDirResult, 24 | setGhcVersionResult, 25 | 26 | -- * Ask for test environment 27 | askRoot, 28 | askStep, 29 | askLogger, 30 | askCradle, 31 | askLoadResult, 32 | askOrLoadLibDir, 33 | askLibDir, 34 | askLibDirResult, 35 | askGhcVersion, 36 | askGhcVersionResult, 37 | 38 | -- * Test setup helpers 39 | step, 40 | normFile, 41 | relFile, 42 | findCradleLoc, 43 | initCradle, 44 | initImplicitCradle, 45 | loadComponentOptions, 46 | loadComponentOptionsMultiStyle, 47 | loadRuntimeGhcLibDir, 48 | loadRuntimeGhcVersion, 49 | inCradleRootDir, 50 | loadFileGhc, 51 | isCabalMultipleCompSupported', 52 | 53 | -- * Assertion helpers 54 | assertCradle, 55 | assertLibDirVersion, 56 | assertGhcVersion, 57 | assertLibDirVersionIs, 58 | assertGhcVersionIs, 59 | assertComponentOptions, 60 | assertCradleError, 61 | assertLoadSuccess, 62 | assertLoadFailure, 63 | assertLoadNone, 64 | assertCradleLoadSuccess, 65 | assertCradleLoadError, 66 | 67 | -- * High-level test helpers 68 | testDirectoryM, 69 | testImplicitDirectoryM, 70 | findCradleForModuleM, 71 | ) where 72 | 73 | import qualified Colog.Core as L 74 | import Control.Monad 75 | import Control.Monad.IO.Class 76 | import Control.Monad.Trans.State 77 | import Data.List 78 | import Data.Void 79 | import qualified GHC as G 80 | import HIE.Bios.Cradle 81 | import HIE.Bios.Environment 82 | import HIE.Bios.Flags 83 | import HIE.Bios.Ghc.Api 84 | import qualified HIE.Bios.Ghc.Gap as G 85 | import HIE.Bios.Ghc.Load 86 | import HIE.Bios.Types as HIE 87 | import Prettyprinter 88 | import System.Directory 89 | import System.FilePath 90 | import System.IO.Temp 91 | import Test.Tasty.HUnit 92 | import Colog.Core 93 | import qualified Data.Text as Text 94 | import Data.Function ((&)) 95 | 96 | -- --------------------------------------------------------------------------- 97 | -- Test configuration and information 98 | -- --------------------------------------------------------------------------- 99 | 100 | type TestM a = StateT (TestEnv Void) IO a 101 | 102 | data TestConfig = TestConfig 103 | { useTemporaryDirectory :: Bool 104 | , testProjectRoots :: FilePath 105 | , testVerbose :: Bool 106 | } 107 | deriving (Eq, Show, Ord) 108 | 109 | data TestEnv ext = TestEnv 110 | { testCradleType :: Maybe (Cradle ext) 111 | , testLoadResult :: Maybe (CradleLoadResult ComponentOptions) 112 | , testLibDirResult :: Maybe (CradleLoadResult FilePath) 113 | , testGhcVersionResult :: Maybe (CradleLoadResult String) 114 | , testRootDir :: FilePath 115 | , testLogger :: L.LogAction IO (L.WithSeverity HIE.Log) 116 | } 117 | 118 | defConfig :: TestConfig 119 | defConfig = TestConfig True "./tests/projects" False 120 | 121 | runTestEnv :: FilePath -> TestM a -> Bool -> IO a 122 | runTestEnv fp act verbose = runTestEnv' defConfig{testVerbose=verbose} fp act 123 | 124 | runTestEnvLocal :: FilePath -> TestM a -> Bool -> IO a 125 | runTestEnvLocal fp act verbose = runTestEnv' defConfig{useTemporaryDirectory = False,testVerbose=verbose} fp act 126 | 127 | runTestEnv' :: TestConfig -> FilePath -> TestM a -> IO a 128 | runTestEnv' config root act = do 129 | -- We need to copy over the directory to somewhere outside the source tree 130 | -- when we test, since the cabal.project/stack.yaml/hie.yaml file in the root 131 | -- of this repository interferes with the test cradles! 132 | let wrapper = 133 | if useTemporaryDirectory config 134 | then withTempCopy 135 | else \r cont -> cont r 136 | mkEnv root' = 137 | TestEnv 138 | { testCradleType = Nothing 139 | , testLoadResult = Nothing 140 | , testLibDirResult = Nothing 141 | , testGhcVersionResult = Nothing 142 | , testRootDir = root' 143 | , testLogger = init_logger 144 | } 145 | realRoot = testProjectRoots config root 146 | wrapper realRoot $ \root' -> flip evalStateT (mkEnv root') $ do 147 | step $ "Run test in: " <> root' 148 | act 149 | where 150 | init_logger = L.logStringStderr 151 | & L.cmap printLog 152 | & L.filterBySeverity (if testVerbose config then Debug else Error) getSeverity 153 | printLog (L.WithSeverity l sev) = "[" ++ show sev ++ "] " ++ show (pretty l) 154 | 155 | -- --------------------------------------------------------------------------- 156 | -- Modification helpers 157 | -- --------------------------------------------------------------------------- 158 | 159 | setCradle :: Cradle Void -> TestM () 160 | setCradle crd = modify' (\env -> env{testCradleType = Just crd}) 161 | 162 | unsetCradle :: TestM () 163 | unsetCradle = modify' (\env -> env{testCradleType = Nothing}) 164 | 165 | setLoadResult :: CradleLoadResult ComponentOptions -> TestM () 166 | setLoadResult clr = modify' (\env -> env{testLoadResult = Just clr}) 167 | 168 | unsetLoadResult :: TestM () 169 | unsetLoadResult = modify' (\env -> env{testLoadResult = Nothing}) 170 | 171 | setLibDirResult :: CradleLoadResult FilePath -> TestM () 172 | setLibDirResult libdir = modify' (\env -> env{testLibDirResult = Just libdir}) 173 | 174 | setGhcVersionResult :: CradleLoadResult String -> TestM () 175 | setGhcVersionResult ghcVersion = modify' (\env -> env{testGhcVersionResult = Just ghcVersion}) 176 | 177 | -- --------------------------------------------------------------------------- 178 | -- Access the Test Environment 179 | -- --------------------------------------------------------------------------- 180 | 181 | askRoot :: TestM FilePath 182 | askRoot = gets testRootDir 183 | 184 | askStep :: TestM (String -> IO ()) 185 | askStep = do 186 | logger <- gets testLogger 187 | pure $ \s -> 188 | logger <& LogAny (Text.pack s) `WithSeverity` Info 189 | 190 | askCradle :: TestM (Cradle Void) 191 | askCradle = 192 | gets testCradleType >>= \case 193 | Just crd -> pure crd 194 | Nothing -> 195 | liftIO $ 196 | assertFailure 197 | "No Cradle set, use 'initCradle' or 'initImplicitCradle' before asking for it" 198 | 199 | askLoadResult :: TestM (CradleLoadResult ComponentOptions) 200 | askLoadResult = 201 | gets testLoadResult >>= \case 202 | Just crd -> pure crd 203 | Nothing -> 204 | liftIO $ 205 | assertFailure 206 | "No CradleLoadResult set, use 'loadComponent' before asking for it" 207 | 208 | askOrLoadLibDir :: TestM FilePath 209 | askOrLoadLibDir = 210 | gets testLibDirResult >>= \case 211 | Just lrLibDir -> 212 | assertCradleLoadSuccess lrLibDir 213 | Nothing -> do 214 | loadRuntimeGhcLibDir 215 | askLibDir 216 | 217 | askLibDir :: TestM FilePath 218 | askLibDir = do 219 | assertCradleLoadSuccess =<< askLibDirResult 220 | 221 | askLibDirResult :: TestM (CradleLoadResult FilePath) 222 | askLibDirResult = 223 | gets testLibDirResult >>= \case 224 | Just lrLibDir -> pure lrLibDir 225 | Nothing -> 226 | liftIO $ 227 | assertFailure 228 | "No Lib Dir set, use 'loadRuntimeGhcLibDir' before asking for it" 229 | 230 | askGhcVersion :: TestM String 231 | askGhcVersion = do 232 | assertCradleLoadSuccess =<< askGhcVersionResult 233 | 234 | askGhcVersionResult :: TestM (CradleLoadResult String) 235 | askGhcVersionResult = 236 | gets testGhcVersionResult >>= \case 237 | Just lrGhcVersion -> pure lrGhcVersion 238 | Nothing -> 239 | liftIO $ 240 | assertFailure 241 | "No GHC version set, use 'loadRuntimeGhcVersion' before asking for it" 242 | 243 | askLogger :: TestM (L.LogAction IO (L.WithSeverity HIE.Log)) 244 | askLogger = gets testLogger 245 | 246 | -- --------------------------------------------------------------------------- 247 | -- Test setup helpers 248 | -- --------------------------------------------------------------------------- 249 | 250 | step :: String -> TestM () 251 | step msg = do 252 | s <- askStep 253 | liftIO $ s msg 254 | 255 | normFile :: FilePath -> TestM FilePath 256 | normFile fp = ( fp) <$> gets testRootDir 257 | 258 | relFile :: FilePath -> TestM FilePath 259 | relFile fp = (`makeRelative` fp) <$> gets testRootDir 260 | 261 | findCradleLoc :: FilePath -> TestM (Maybe FilePath) 262 | findCradleLoc fp = do 263 | a_fp <- normFile fp 264 | liftIO $ findCradle a_fp 265 | 266 | initCradle :: FilePath -> TestM () 267 | initCradle fp = do 268 | a_fp <- normFile fp 269 | step $ "Finding Cradle for: " <> fp 270 | mcfg <- findCradleLoc a_fp 271 | relMcfg <- traverse relFile mcfg 272 | step $ "Loading Cradle: " <> show relMcfg 273 | logger <- askLogger 274 | crd <- case mcfg of 275 | Just cfg -> liftIO $ loadCradle logger cfg 276 | Nothing -> liftIO $ loadImplicitCradle logger a_fp 277 | setCradle crd 278 | 279 | initImplicitCradle :: FilePath -> TestM () 280 | initImplicitCradle fp = do 281 | a_fp <- normFile fp 282 | step $ "Loading implicit Cradle for: " <> fp 283 | logger <- askLogger 284 | crd <- liftIO $ loadImplicitCradle logger a_fp 285 | setCradle crd 286 | 287 | loadComponentOptions :: FilePath -> TestM () 288 | loadComponentOptions fp = do 289 | a_fp <- normFile fp 290 | crd <- askCradle 291 | step $ "Initialise flags for: " <> fp 292 | clr <- liftIO $ getCompilerOptions a_fp LoadFile crd 293 | setLoadResult clr 294 | 295 | loadComponentOptionsMultiStyle :: FilePath -> [FilePath] -> TestM () 296 | loadComponentOptionsMultiStyle fp fps = do 297 | a_fp <- normFile fp 298 | a_fps <- mapM normFile fps 299 | crd <- askCradle 300 | step $ "Initialise flags for: " <> fp <> " and " <> show fps 301 | clr <- liftIO $ getCompilerOptions a_fp (LoadWithContext a_fps) crd 302 | setLoadResult clr 303 | 304 | loadRuntimeGhcLibDir :: TestM () 305 | loadRuntimeGhcLibDir = do 306 | crd <- askCradle 307 | step "Load run-time ghc libdir" 308 | libdirRes <- liftIO $ getRuntimeGhcLibDir crd 309 | setLibDirResult libdirRes 310 | 311 | loadRuntimeGhcVersion :: TestM () 312 | loadRuntimeGhcVersion = do 313 | crd <- askCradle 314 | step "Load run-time ghc version" 315 | ghcVersionRes <- liftIO $ getRuntimeGhcVersion crd 316 | setGhcVersionResult ghcVersionRes 317 | 318 | isCabalMultipleCompSupported' :: TestM Bool 319 | isCabalMultipleCompSupported' = do 320 | cr <- askCradle 321 | root <- askRoot 322 | versions <- liftIO $ makeVersions (cradleLogger cr) root ((runGhcCmd . cradleOptsProg) cr) 323 | liftIO $ isCabalMultipleCompSupported versions 324 | 325 | inCradleRootDir :: TestM a -> TestM a 326 | inCradleRootDir act = do 327 | crd <- askCradle 328 | prev <- liftIO getCurrentDirectory 329 | liftIO $ setCurrentDirectory (cradleRootDir crd) 330 | a <- act 331 | liftIO $ setCurrentDirectory prev 332 | pure a 333 | 334 | loadFileGhc :: FilePath -> TestM () 335 | loadFileGhc fp = do 336 | libdir <- askOrLoadLibDir 337 | a_fp <- normFile fp 338 | stepF <- askStep 339 | step "Cradle load" 340 | loadComponentOptions fp 341 | opts <- assertLoadSuccess 342 | liftIO $ 343 | G.runGhc (Just libdir) $ do 344 | let (ini, _) = initSessionWithMessage (Just G.batchMsg) opts 345 | sf <- ini 346 | case sf of 347 | -- Test resetting the targets 348 | Succeeded -> do 349 | liftIO $ stepF "Set target files" 350 | setTargetFiles mempty [(a_fp, a_fp)] 351 | Failed -> liftIO $ assertFailure "Module loading failed" 352 | 353 | -- --------------------------------------------------------------------------- 354 | -- Assertion helpers for hie-bios 355 | -- --------------------------------------------------------------------------- 356 | 357 | assertCradle :: (Cradle Void -> Bool) -> TestM () 358 | assertCradle cradlePred = do 359 | crd <- askCradle 360 | liftIO $ cradlePred crd @? "Must be the correct kind of cradle, got " ++ show (actionName $ cradleOptsProg crd) 361 | 362 | assertLibDirVersion :: TestM () 363 | assertLibDirVersion = assertLibDirVersionIs VERSION_ghc 364 | 365 | assertGhcVersion :: TestM () 366 | assertGhcVersion = assertGhcVersionIs VERSION_ghc 367 | 368 | assertLibDirVersionIs :: String -> TestM () 369 | assertLibDirVersionIs ghcVersion = do 370 | step $ "Verify runtime GHC library directory is: " <> ghcVersion 371 | libdir <- askLibDir 372 | liftIO $ 373 | ghcVersion `isInfixOf` libdir @? "Expected \"" <> ghcVersion 374 | <> "\" to be infix of: " 375 | <> libdir 376 | 377 | assertGhcVersionIs :: String -> TestM () 378 | assertGhcVersionIs expectedVersion = do 379 | step $ "Verify runtime GHC version is: " <> expectedVersion 380 | ghcVersion <- askGhcVersion 381 | liftIO $ ghcVersion @?= expectedVersion 382 | 383 | assertComponentOptions :: (ComponentOptions -> Assertion) -> TestM () 384 | assertComponentOptions cont = do 385 | opts <- assertLoadSuccess 386 | liftIO $ cont opts 387 | 388 | assertCradleError :: (CradleError -> Assertion) -> TestM () 389 | assertCradleError cont = do 390 | err <- assertLoadFailure 391 | liftIO $ cont err 392 | 393 | assertLoadSuccess :: TestM ComponentOptions 394 | assertLoadSuccess = do 395 | askLoadResult >>= \case 396 | CradleSuccess opts -> pure opts 397 | other -> liftIO $ assertFailure $ "Expected CradleSuccess but got: " <> show other 398 | 399 | assertLoadFailure :: TestM CradleError 400 | assertLoadFailure = do 401 | askLoadResult >>= \case 402 | CradleFail err -> pure err 403 | other -> liftIO $ assertFailure $ "Expected CradleFail but got: " <> show other 404 | 405 | assertLoadNone :: TestM () 406 | assertLoadNone = do 407 | askLoadResult >>= \case 408 | CradleNone -> pure () 409 | other -> liftIO $ assertFailure $ "Expected CradleNone but got: " <> show other 410 | 411 | assertCradleLoadSuccess :: CradleLoadResult a -> TestM a 412 | assertCradleLoadSuccess = \case 413 | (CradleSuccess x) -> pure x 414 | CradleNone -> liftIO $ assertFailure "Unexpected none-Cradle" 415 | (CradleFail (CradleError _deps _ex stde _err_loading_files)) -> 416 | liftIO $ assertFailure ("Unexpected cradle fail" <> unlines stde) 417 | 418 | assertCradleLoadError :: CradleLoadResult a -> TestM CradleError 419 | assertCradleLoadError = \case 420 | (CradleSuccess _) -> liftIO $ assertFailure "Unexpected CradleSuccess" 421 | CradleNone -> liftIO $ assertFailure "Unexpected none-Cradle" 422 | (CradleFail err) -> pure err 423 | 424 | -- --------------------------------------------------------------------------- 425 | -- High-level, re-usable assertions 426 | -- --------------------------------------------------------------------------- 427 | 428 | testDirectoryM :: (Cradle Void -> Bool) -> FilePath -> TestM () 429 | testDirectoryM cradlePred file = do 430 | initCradle file 431 | assertCradle cradlePred 432 | loadRuntimeGhcLibDir 433 | assertLibDirVersion 434 | loadRuntimeGhcVersion 435 | assertGhcVersion 436 | loadFileGhc file 437 | 438 | testImplicitDirectoryM :: (Cradle Void -> Bool) -> FilePath -> TestM () 439 | testImplicitDirectoryM cradlePred file = do 440 | initImplicitCradle file 441 | assertCradle cradlePred 442 | loadRuntimeGhcLibDir 443 | assertLibDirVersion 444 | loadRuntimeGhcVersion 445 | assertGhcVersion 446 | loadFileGhc file 447 | 448 | findCradleForModuleM :: FilePath -> Maybe FilePath -> TestM () 449 | findCradleForModuleM fp expected' = do 450 | rootFp <- askRoot 451 | let expected = fmap (rootFp ) expected' 452 | crd <- findCradleLoc fp 453 | liftIO $ crd @?= expected 454 | 455 | -- --------------------------------------------------------------------------- 456 | -- Copy Directory system utilities 457 | -- --------------------------------------------------------------------------- 458 | 459 | withTempCopy :: FilePath -> (FilePath -> IO a) -> IO a 460 | withTempCopy srcDir f = 461 | withSystemTempDirectory "hie-bios-test" $ \newDir -> do 462 | exists <- doesDirectoryExist srcDir 463 | when exists $ do 464 | copyDir srcDir newDir 465 | f newDir 466 | 467 | copyDir :: FilePath -> FilePath -> IO () 468 | copyDir src dst = do 469 | contents <- listDirectory src 470 | forM_ contents $ \file -> do 471 | unless (file `elem` ignored) $ do 472 | let srcFp = src file 473 | dstFp = dst file 474 | isDir <- doesDirectoryExist srcFp 475 | if isDir 476 | then createDirectory dstFp >> copyDir srcFp dstFp 477 | else copyFile srcFp dstFp 478 | where 479 | ignored = ["dist", "dist-newstyle", ".stack-work"] 480 | -------------------------------------------------------------------------------- /tests/configs/bazel.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | { bazel: } 3 | -------------------------------------------------------------------------------- /tests/configs/bios-1.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | program: "program" 4 | -------------------------------------------------------------------------------- /tests/configs/bios-2.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | program: "program" 4 | dependency-program: "dep-program" 5 | -------------------------------------------------------------------------------- /tests/configs/bios-3.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | shell: "shellcommand" 4 | -------------------------------------------------------------------------------- /tests/configs/bios-4.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | shell: "shellcommand" 4 | dependency-shell: "dep-shellcommand" 5 | -------------------------------------------------------------------------------- /tests/configs/bios-5.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | shell: "shellcommand" 4 | dependency-program: "dep-program" 5 | -------------------------------------------------------------------------------- /tests/configs/cabal-1.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | { cabal: {component: "lib:hie-bios"} } 3 | -------------------------------------------------------------------------------- /tests/configs/cabal-empty-config.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | -------------------------------------------------------------------------------- /tests/configs/cabal-multi.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: "./src" 4 | component: "lib:hie-bios" 5 | - path: "./" 6 | component: "lib:hie-bios" 7 | -------------------------------------------------------------------------------- /tests/configs/cabal-with-both.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | cabalProject: "cabal.project.9.2.8" 4 | component: "hie-bios:hie" 5 | -------------------------------------------------------------------------------- /tests/configs/cabal-with-project.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | cabalProject: "cabal.project.9.2.8" 4 | -------------------------------------------------------------------------------- /tests/configs/ch-cabal.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | other: 3 | cabal-helper: 4 | cabal: -------------------------------------------------------------------------------- /tests/configs/ch-stack.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | other: 3 | cabal-helper: 4 | stack: -------------------------------------------------------------------------------- /tests/configs/default.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | default: 3 | -------------------------------------------------------------------------------- /tests/configs/dependencies.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | { cabal: {component: "lib:hie-bios"} } 3 | 4 | dependencies: 5 | - "depFile" 6 | -------------------------------------------------------------------------------- /tests/configs/direct.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | direct: 3 | arguments: ["list", "of", "arguments"] 4 | -------------------------------------------------------------------------------- /tests/configs/keys-not-unique-fails.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | components: 4 | - path: "./src" 5 | component: "lib:hie-bios" 6 | - path: "./" 7 | component: "lib:hie-bios" 8 | components: 9 | - path: "./src" 10 | component: "exe:hie-bios" 11 | - path: "./" 12 | component: "exe:hie-bios" 13 | -------------------------------------------------------------------------------- /tests/configs/multi-cabal-with-project.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | cabalProject: "cabal.project.9.2.8" 4 | components: 5 | - path: "./src" 6 | component: "lib:hie-bios" 7 | - path: "./vendor" 8 | component: "parser-tests" 9 | -------------------------------------------------------------------------------- /tests/configs/multi-ch.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "./src" 4 | config: { cradle: {other: {cabal-helper: {stack: } } } } 5 | - path: "./input" 6 | config: { cradle: {other: {cabal-helper: {cabal: } } } } 7 | - path: "./test" 8 | config: { cradle: {cabal: {component: "test"}} } 9 | - path: "." 10 | config: { cradle: { none: } } 11 | -------------------------------------------------------------------------------- /tests/configs/multi-stack-with-yaml.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | stackYaml: "stack-8.8.3.yaml" 4 | components: 5 | - path: "./src" 6 | component: "lib:hie-bios" 7 | - path: "./vendor" 8 | component: "parser-tests" 9 | -------------------------------------------------------------------------------- /tests/configs/multi.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "./src" 4 | config: { cradle: {cabal: {component: "lib:hie-bios"}} } 5 | - path: "./test" 6 | config: { cradle: {cabal: {component: "test"}} } 7 | -------------------------------------------------------------------------------- /tests/configs/nested-cabal-multi.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "./test/testdata" 4 | config: { cradle: { none: } } 5 | - path: "./" 6 | config: { cradle: { cabal: 7 | [ { path: "./src", component: "lib:hie-bios" } 8 | , { path: "./tests", component: "parser-tests" } ] } } 9 | -------------------------------------------------------------------------------- /tests/configs/nested-stack-multi.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "./test/testdata" 4 | config: { cradle: { none: } } 5 | - path: "./" 6 | config: { cradle: { stack: 7 | [ { path: "./src", component: "lib:hie-bios" } 8 | , { path: "./tests", component: "parser-tests" } ] } } 9 | -------------------------------------------------------------------------------- /tests/configs/none.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | none: 3 | -------------------------------------------------------------------------------- /tests/configs/obelisk.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | { obelisk: } 3 | -------------------------------------------------------------------------------- /tests/configs/stack-config.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | { stack: {}} 3 | -------------------------------------------------------------------------------- /tests/configs/stack-multi.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | - path: "./src" 4 | component: "lib:hie-bios" 5 | - path: "./" 6 | component: "lib:hie-bios" 7 | -------------------------------------------------------------------------------- /tests/configs/stack-with-both.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | stackYaml: "stack-8.8.3.yaml" 4 | component: "hie-bios:hie" 5 | -------------------------------------------------------------------------------- /tests/configs/stack-with-yaml.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | stackYaml: "stack-8.8.3.yaml" 4 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc-and-project/cabal-with-ghc.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: cabal-with-ghc 3 | version: 0.1.0.0 4 | 5 | library 6 | exposed-modules: MyLib 7 | build-depends: base 8 | hs-source-dirs: src 9 | default-language: Haskell2010 10 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc-and-project/cabal.project.9.2.8: -------------------------------------------------------------------------------- 1 | packages: . 2 | 3 | -- It is intended that this ghc version is different to at least one ghc version 4 | -- that is tested in CI. 5 | -- 6 | -- Project validates that hie-bios honours this field. 7 | -- 8 | -- If you change this, make sure to also change the 'extraGhc' constant in 9 | -- 'tests/BiosTests.hs'. 10 | -- 11 | -- Additionally, CI needs a proper setup to execute this test-case. 12 | with-compiler: ghc-9.2.8 13 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc-and-project/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | cabalProject: cabal.project.9.2.8 4 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc-and-project/src/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc/cabal-with-ghc.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: cabal-with-ghc 3 | version: 0.1.0.0 4 | 5 | library 6 | exposed-modules: MyLib 7 | build-depends: base 8 | hs-source-dirs: src 9 | default-language: Haskell2010 10 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | 3 | -- It is intended that this ghc version is different to at least one ghc version 4 | -- that is tested in CI. 5 | -- 6 | -- Project validates that hie-bios honours this field. 7 | -- 8 | -- If you change this, make sure to also change the 'extraGhc' constant in 9 | -- 'tests/BiosTests.hs'. 10 | -- 11 | -- Additionally, CI needs a proper setup to execute this test-case. 12 | with-compiler: ghc-9.2.8 13 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-ghc/src/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-project/cabal-with-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: cabal-with-project 3 | version: 0.1.0.0 4 | 5 | library 6 | exposed-modules: MyLib 7 | build-depends: base 8 | hs-source-dirs: src 9 | default-language: Haskell2010 10 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-project/cabal.project.9.2.8: -------------------------------------------------------------------------------- 1 | -- Test file. 2 | packages: ./ 3 | 4 | package cabal-with-project 5 | ghc-options: -O2 6 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-project/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | cabalProject: cabal.project.9.2.8 4 | -------------------------------------------------------------------------------- /tests/projects/cabal-with-project/src/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/deps-bios-new/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/deps-bios-new/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/deps-bios-new/hie-bios.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "-Wall" >> $HIE_BIOS_OUTPUT 4 | echo "A" >> $HIE_BIOS_OUTPUT 5 | echo "B" >> $HIE_BIOS_OUTPUT 6 | echo "hie-bios.sh" >> $HIE_BIOS_DEPS 7 | echo "hie.yaml" >> $HIE_BIOS_DEPS 8 | -------------------------------------------------------------------------------- /tests/projects/deps-bios-new/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | program: ./hie-bios.sh 4 | -------------------------------------------------------------------------------- /tests/projects/failing-bios-ghc/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/failing-bios-ghc/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/failing-bios-ghc/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | shell: 4 | echo "-Wall" >> %HIE_BIOS_OUTPUT% 5 | echo "A" >> %HIE_BIOS_OUTPUT% 6 | echo "B" >> %HIE_BIOS_OUTPUT% 7 | with-ghc: myGhc 8 | -------------------------------------------------------------------------------- /tests/projects/failing-bios/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/failing-bios/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/failing-bios/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | shell: "exit 1" 4 | dependencies: 5 | - hie.yaml 6 | -------------------------------------------------------------------------------- /tests/projects/failing-cabal/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/failing-cabal/failing-cabal.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: failing-cabal 3 | version: 0.1.0.0 4 | license-file: LICENSE 5 | author: fendor 6 | build-type: Simple 7 | 8 | library 9 | exposed-modules: MyLib 10 | build-depends: base >=4, 11 | containers < 1 && > 1 12 | -- ^^^^^^^^^^ <<< Invalid constraint 13 | default-language: Haskell2010 14 | -------------------------------------------------------------------------------- /tests/projects/failing-cabal/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | -------------------------------------------------------------------------------- /tests/projects/failing-multi-repl-cabal-project/NotInPath.hs: -------------------------------------------------------------------------------- 1 | module NotInPath where 2 | 3 | import System.FilePath (()) 4 | 5 | foo = "test" "me" 6 | -------------------------------------------------------------------------------- /tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/app/Main.hs: -------------------------------------------------------------------------------- 1 | 2 | import System.Directory (getCurrentDirectory) 3 | 4 | main = return () 5 | -------------------------------------------------------------------------------- /tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/multi-repl-cabal-fail.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: multi-cabal 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | 14 | 15 | executable multi-cabal 16 | main-is: app/Main.hs 17 | -- other-modules: 18 | -- other-extensions: 19 | build-depends: base >=4.10 && < 5, directory 20 | -- hs-source-dirs: 21 | default-language: Haskell2010 22 | -------------------------------------------------------------------------------- /tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/src/Fail.hs: -------------------------------------------------------------------------------- 1 | module Fail where 2 | 3 | import System.FilePath (()) 4 | 5 | foo = "test" "me" 6 | -------------------------------------------------------------------------------- /tests/projects/failing-multi-repl-cabal-project/multi-repl-cabal-fail/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | 3 | import System.FilePath (()) 4 | 5 | foo = "test" "me" 6 | -------------------------------------------------------------------------------- /tests/projects/failing-stack/failing-stack.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 1.12 2 | name: failing-stack 3 | version: 0.1.0.0 4 | description: None 5 | build-type: Simple 6 | 7 | library 8 | exposed-modules: 9 | Lib 10 | hs-source-dirs: 11 | src 12 | build-depends: 13 | base >=4.7 && <5, 14 | containes < 1 && > 1 15 | -- ^^^^^^^^^^ <<< Invalid constraint 16 | default-language: Haskell2010 17 | -------------------------------------------------------------------------------- /tests/projects/failing-stack/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | -------------------------------------------------------------------------------- /tests/projects/failing-stack/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib 2 | ( someFunc 3 | ) where 4 | 5 | someFunc :: IO () 6 | someFunc = putStrLn "someFunc" 7 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-deep-project/Main.hs: -------------------------------------------------------------------------------- 1 | main = pure () 2 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-deep-project/README: -------------------------------------------------------------------------------- 1 | Here we are testing that if we have a nested cabal package but a cabal.project 2 | a directory above it, hie-bios will implicitly pick up the cabal.project and 3 | NOT the nested cabal package 4 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-deep-project/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | foo 3 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-deep-project/foo/Main.hs: -------------------------------------------------------------------------------- 1 | main = pure () 2 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-deep-project/foo/foo.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: foo 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | executable foo 7 | main-is: Main.hs 8 | build-depends: base >=4 9 | default-language: Haskell2010 10 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-deep-project/implicit-cabal-deep-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: implicit-cabal-deep-project 3 | version: 0.1.0.0 4 | build-type: Simple 5 | executable foo 6 | main-is: Main.hs 7 | build-depends: base >=4 8 | default-language: Haskell2010 9 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-no-project/Main.hs: -------------------------------------------------------------------------------- 1 | main = pure () 2 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal-no-project/implicit-cabal-no-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: implicit-cabal-no-project 3 | version: 0.1.0.0 4 | build-type: Simple 5 | executable foo 6 | main-is: Main.hs 7 | build-depends: base >=4 8 | default-language: Haskell2010 9 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /tests/projects/implicit-cabal/implicit-cabal.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | -- Initial package description 'implicit-cabal.cabal' generated by 'cabal 3 | -- init'. For further documentation, see 4 | -- http://haskell.org/cabal/users-guide/ 5 | 6 | name: implicit-cabal 7 | version: 0.1.0.0 8 | -- synopsis: 9 | -- description: 10 | -- bug-reports: 11 | -- license: 12 | license-file: LICENSE 13 | author: Luke Lau 14 | maintainer: luke_lau@icloud.com 15 | -- copyright: 16 | -- category: 17 | build-type: Simple 18 | 19 | executable implicit-cabal 20 | main-is: Main.hs 21 | -- other-modules: 22 | -- other-extensions: 23 | build-depends: base == 4.* 24 | -- hs-source-dirs: 25 | default-language: Haskell2010 26 | -------------------------------------------------------------------------------- /tests/projects/implicit-stack-multi/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /tests/projects/implicit-stack-multi/implicit-stack-multi.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | -- Initial package description 'implicit-stack-multi.cabal' generated by 3 | -- 'cabal init'. For further documentation, see 4 | -- http://haskell.org/cabal/users-guide/ 5 | 6 | name: implicit-stack-multi 7 | version: 0.1.0.0 8 | -- synopsis: 9 | -- description: 10 | -- bug-reports: 11 | -- license: 12 | license-file: LICENSE 13 | author: Luke Lau 14 | maintainer: luke_lau@icloud.com 15 | -- copyright: 16 | -- category: 17 | build-type: Simple 18 | 19 | executable implicit-stack-multi 20 | main-is: Main.hs 21 | -- other-modules: 22 | -- other-extensions: 23 | build-depends: base == 4.* 24 | -- hs-source-dirs: 25 | default-language: Haskell2010 26 | -------------------------------------------------------------------------------- /tests/projects/implicit-stack-multi/other-package/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /tests/projects/implicit-stack-multi/other-package/other-package.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | -- Initial package description 'other-package.cabal' generated by 'cabal 3 | -- init'. For further documentation, see 4 | -- http://haskell.org/cabal/users-guide/ 5 | 6 | name: other-package 7 | version: 0.1.0.0 8 | -- synopsis: 9 | -- description: 10 | -- bug-reports: 11 | -- license: 12 | license-file: LICENSE 13 | author: Luke Lau 14 | maintainer: luke_lau@icloud.com 15 | -- copyright: 16 | -- category: 17 | build-type: Simple 18 | 19 | executable other-package 20 | main-is: Main.hs 21 | -- other-modules: 22 | -- other-extensions: 23 | build-depends: base == 4.* 24 | -- hs-source-dirs: 25 | default-language: Haskell2010 26 | -------------------------------------------------------------------------------- /tests/projects/implicit-stack/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /tests/projects/implicit-stack/implicit-stack.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | -- Initial package description 'implicit-stack.cabal' generated by 'cabal 3 | -- init'. For further documentation, see 4 | -- http://haskell.org/cabal/users-guide/ 5 | 6 | name: implicit-stack 7 | version: 0.1.0.0 8 | -- synopsis: 9 | -- description: 10 | -- bug-reports: 11 | -- license: 12 | license-file: LICENSE 13 | author: Luke Lau 14 | maintainer: luke_lau@icloud.com 15 | -- copyright: 16 | -- category: 17 | build-type: Simple 18 | 19 | executable implicit-stack 20 | main-is: Main.hs 21 | -- other-modules: 22 | -- other-extensions: 23 | build-depends: base == 4.* 24 | -- hs-source-dirs: 25 | default-language: Haskell2010 26 | -------------------------------------------------------------------------------- /tests/projects/monorepo-cabal/A/A.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: A 3 | version: 0.1.0.0 4 | build-type: Simple 5 | executable A 6 | main-is: Main.hs 7 | build-depends: base >=4.8, B 8 | default-language: Haskell2010 9 | -------------------------------------------------------------------------------- /tests/projects/monorepo-cabal/A/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /tests/projects/monorepo-cabal/B/B.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | 3 | name: B 4 | version: 0.1.0.0 5 | build-type: Simple 6 | 7 | library 8 | exposed-modules: MyLib 9 | build-depends: base >=4.8 10 | default-language: Haskell2010 11 | -------------------------------------------------------------------------------- /tests/projects/monorepo-cabal/B/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/monorepo-cabal/cabal.project: -------------------------------------------------------------------------------- 1 | packages: ./A ./B -------------------------------------------------------------------------------- /tests/projects/monorepo-cabal/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: ./A 4 | component: "exe:A" 5 | - path: ./B 6 | component: "lib:B" -------------------------------------------------------------------------------- /tests/projects/multi-cabal-with-project/appA/appA.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.0 2 | name: appA 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | build-depends: base >=4.10 && < 5, filepath 9 | hs-source-dirs: src 10 | default-language: Haskell2010 11 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal-with-project/appA/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal-with-project/appB/appB.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.0 2 | name: appB 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal-with-project/appB/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal-with-project/cabal.project.9.2.8: -------------------------------------------------------------------------------- 1 | packages: ./appA ./appB 2 | 3 | -- Only appA gets the config 4 | package appA 5 | ghc-options: -O2 6 | 7 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal-with-project/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "appA" 4 | config: 5 | cradle: 6 | cabal: 7 | - path: appA/src 8 | component: appA:lib 9 | cabalProject: cabal.project.9.2.8 10 | - path: "appB" 11 | config: 12 | cradle: 13 | cabal: 14 | - path: appB/src 15 | component: appB:lib 16 | cabalProject: cabal.project.9.2.8 17 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal/app/Main.hs: -------------------------------------------------------------------------------- 1 | 2 | import System.Directory (getCurrentDirectory) 3 | 4 | main = return () -------------------------------------------------------------------------------- /tests/projects/multi-cabal/cabal.project: -------------------------------------------------------------------------------- 1 | packages: ./ 2 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: ./src 4 | component: "lib:multi-cabal" 5 | 6 | - path: ./app 7 | component: "exe:multi-cabal" -------------------------------------------------------------------------------- /tests/projects/multi-cabal/multi-cabal.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: multi-cabal 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | ghc-options: +RTS -A1m -N -RTS -Wall 14 | 15 | 16 | 17 | executable multi-cabal 18 | main-is: app/Main.hs 19 | -- other-modules: 20 | -- other-extensions: 21 | build-depends: base >=4.10 && < 5, directory 22 | -- hs-source-dirs: 23 | default-language: Haskell2010 24 | -------------------------------------------------------------------------------- /tests/projects/multi-cabal/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | 3 | import System.FilePath (()) 4 | 5 | foo = "test" "me" 6 | -------------------------------------------------------------------------------- /tests/projects/multi-direct/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/multi-direct/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/multi-direct/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "A.hs" 4 | config: 5 | cradle: 6 | direct: 7 | arguments: ["-Walaaal", "A", "B"] 8 | - path: "B.hs" 9 | config: 10 | cradle: 11 | direct: 12 | arguments: ["-Walaaal", "A", "B"] 13 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/appA/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/appA/appA.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: appA 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/appA/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/appB/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/appB/appB.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: appB 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/appB/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | -------------------------------------------------------------------------------- /tests/projects/multi-stack-with-yaml/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: "appA" 4 | config: 5 | cradle: 6 | stack: 7 | - path: appA/src 8 | component: appA:lib 9 | stackYaml: stack-alt.yaml 10 | - path: "appB" 11 | config: 12 | cradle: 13 | stack: 14 | - path: appB/src 15 | component: appB:lib 16 | stackYaml: stack-alt.yaml 17 | -------------------------------------------------------------------------------- /tests/projects/multi-stack/app/Main.hs: -------------------------------------------------------------------------------- 1 | 2 | import System.Directory (getCurrentDirectory) 3 | 4 | main = return () -------------------------------------------------------------------------------- /tests/projects/multi-stack/cabal.project: -------------------------------------------------------------------------------- 1 | packages: ./ 2 | -------------------------------------------------------------------------------- /tests/projects/multi-stack/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | - path: ./src 4 | component: "multi-stack:lib" 5 | 6 | - path: ./app 7 | component: "multi-stack:exe:multi-stack" -------------------------------------------------------------------------------- /tests/projects/multi-stack/multi-stack.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: multi-stack 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | 14 | 15 | 16 | executable multi-stack 17 | main-is: app/Main.hs 18 | -- other-modules: 19 | -- other-extensions: 20 | build-depends: base >=4.10 && < 5, directory 21 | -- hs-source-dirs: 22 | default-language: Haskell2010 23 | ghc-options: +RTS -A1m -N -RTS -Wall 24 | -------------------------------------------------------------------------------- /tests/projects/multi-stack/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | 3 | import System.FilePath (()) 4 | 5 | foo = "test" "me" 6 | -------------------------------------------------------------------------------- /tests/projects/nested-cabal/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/nested-cabal/cabal.project: -------------------------------------------------------------------------------- 1 | packages: ./sub-comp 2 | ./ -------------------------------------------------------------------------------- /tests/projects/nested-cabal/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: ./sub-comp 4 | component: "lib:sub-comp" 5 | - path: ./ 6 | component: "lib:nested-cabal" -------------------------------------------------------------------------------- /tests/projects/nested-cabal/nested-cabal.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: nested-cabal 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: MyLib 8 | build-depends: base < 5 9 | default-language: Haskell2010 10 | -------------------------------------------------------------------------------- /tests/projects/nested-cabal/sub-comp/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/nested-cabal/sub-comp/sub-comp.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: sub-comp 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | build-depends: base < 5 9 | -- hs-source-dirs: 10 | default-language: Haskell2010 11 | -------------------------------------------------------------------------------- /tests/projects/nested-stack/MyLib.hs: -------------------------------------------------------------------------------- 1 | module MyLib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/nested-stack/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | - path: ./sub-comp 4 | component: "sub-comp:lib" 5 | - path: ./ 6 | component: "nested-stack:lib" -------------------------------------------------------------------------------- /tests/projects/nested-stack/nested-stack.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: nested-stack 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: MyLib 8 | build-depends: base < 5 9 | default-language: Haskell2010 10 | -------------------------------------------------------------------------------- /tests/projects/nested-stack/sub-comp/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib (someFunc) where 2 | 3 | someFunc :: IO () 4 | someFunc = putStrLn "someFunc" 5 | -------------------------------------------------------------------------------- /tests/projects/nested-stack/sub-comp/sub-comp.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: sub-comp 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | build-depends: base < 5 9 | -- hs-source-dirs: 10 | default-language: Haskell2010 11 | -------------------------------------------------------------------------------- /tests/projects/simple-bios-ghc/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/simple-bios-ghc/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/simple-bios-ghc/hie-bios.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "-Wall" >> $HIE_BIOS_OUTPUT 4 | echo "A" >> $HIE_BIOS_OUTPUT 5 | echo "B" >> $HIE_BIOS_OUTPUT 6 | -------------------------------------------------------------------------------- /tests/projects/simple-bios-ghc/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: {program: ./hie-bios.sh, with-ghc: ghc } 3 | -------------------------------------------------------------------------------- /tests/projects/simple-bios-shell/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/simple-bios-shell/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/simple-bios-shell/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | shell: | 4 | :; echo "-Wall" >> $HIE_BIOS_OUTPUT 5 | :; echo "A" >> $HIE_BIOS_OUTPUT 6 | :; echo "B" >> $HIE_BIOS_OUTPUT 7 | :; exit 0 8 | ECHO "-Wall" >> %HIE_BIOS_OUTPUT% 9 | ECHO "A" >> %HIE_BIOS_OUTPUT% 10 | ECHO "B" >> %HIE_BIOS_OUTPUT% 11 | dependency-shell: | 12 | echo -n # > nul && ECHO hie.yaml>> %HIE_BIOS_OUTPUT% 13 | :; echo "hie.yaml" >> $HIE_BIOS_OUTPUT 14 | :; exit 0 15 | -------------------------------------------------------------------------------- /tests/projects/simple-bios/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/simple-bios/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/simple-bios/hie-bios-deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "hie-bios.sh" >> $HIE_BIOS_OUTPUT 4 | -------------------------------------------------------------------------------- /tests/projects/simple-bios/hie-bios.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "-Wall" >> $HIE_BIOS_OUTPUT 4 | echo "A" >> $HIE_BIOS_OUTPUT 5 | echo "B" >> $HIE_BIOS_OUTPUT 6 | -------------------------------------------------------------------------------- /tests/projects/simple-bios/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | bios: 3 | program: ./hie-bios.sh 4 | dependency-program: ./hie-bios-deps.sh 5 | 6 | dependencies: 7 | - hie.yaml -------------------------------------------------------------------------------- /tests/projects/simple-cabal/.ghci: -------------------------------------------------------------------------------- 1 | -- This file is intentional 2 | -- 3 | -- Issue: https://github.com/haskell/hie-bios/issues/336 4 | -- 5 | :set +t 6 | -------------------------------------------------------------------------------- /tests/projects/simple-cabal/A.hs: -------------------------------------------------------------------------------- 1 | module A where 2 | -------------------------------------------------------------------------------- /tests/projects/simple-cabal/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/simple-cabal/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /tests/projects/simple-cabal/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: {component: "lib:simple-cabal"} 3 | -------------------------------------------------------------------------------- /tests/projects/simple-cabal/simple-cabal.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: simple-cabal 3 | version: 0.1.0.0 4 | -- synopsis: 5 | -- description: 6 | -- bug-reports: 7 | -- license: 8 | license-file: LICENSE 9 | author: Matthew Pickering 10 | maintainer: matthewtpickering@gmail.com 11 | -- copyright: 12 | -- category: 13 | build-type: Simple 14 | 15 | library 16 | exposed-modules: A B 17 | -- other-modules: 18 | -- other-extensions: 19 | build-depends: base >=4.10 && < 5 20 | ghc-options: +RTS -A1m -N -RTS -Wall 21 | -- hs-source-dirs: 22 | default-language: Haskell2010 23 | -------------------------------------------------------------------------------- /tests/projects/simple-direct/A.hs: -------------------------------------------------------------------------------- 1 | module A where -------------------------------------------------------------------------------- /tests/projects/simple-direct/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/simple-direct/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | direct: {arguments: ["-Walaaal", "A", "B"] } 3 | -------------------------------------------------------------------------------- /tests/projects/simple-stack/A.hs: -------------------------------------------------------------------------------- 1 | module A where 2 | -------------------------------------------------------------------------------- /tests/projects/simple-stack/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/simple-stack/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /tests/projects/simple-stack/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | component: "simple-stack:lib" 4 | -------------------------------------------------------------------------------- /tests/projects/simple-stack/simple-stack.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: simple-stack 3 | version: 0.1.0.0 4 | -- synopsis: 5 | -- description: 6 | -- bug-reports: 7 | -- license: 8 | license-file: LICENSE 9 | author: Matthew Pickering 10 | maintainer: matthewtpickering@gmail.com 11 | -- copyright: 12 | -- category: 13 | build-type: Simple 14 | 15 | library 16 | exposed-modules: A B 17 | -- other-modules: 18 | -- other-extensions: 19 | build-depends: base >=4.10 && < 5 20 | ghc-options: -Wall +RTS -A1m -N -RTS 21 | -- hs-source-dirs: 22 | default-language: Haskell2010 23 | -------------------------------------------------------------------------------- /tests/projects/space stack/A.hs: -------------------------------------------------------------------------------- 1 | module A where 2 | 3 | main :: IO () 4 | main = return () -------------------------------------------------------------------------------- /tests/projects/space stack/B.hs: -------------------------------------------------------------------------------- 1 | module B where 2 | 3 | import A 4 | -------------------------------------------------------------------------------- /tests/projects/space stack/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | component: "stackproj:exe:exe" 4 | -------------------------------------------------------------------------------- /tests/projects/space stack/stackproj.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: stackproj 3 | version: 0.1.0.0 4 | author: Matthew Pickering 5 | maintainer: matthewtpickering@gmail.com 6 | build-type: Simple 7 | 8 | executable exe 9 | other-modules: B 10 | main-is: A.hs 11 | -- other-extensions: 12 | build-depends: base >=4.10 && < 5 13 | ghc-options: -Wall +RTS -A1m -N -RTS 14 | -- hs-source-dirs: 15 | default-language: Haskell2010 16 | -------------------------------------------------------------------------------- /tests/projects/stack-with-yaml/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /tests/projects/stack-with-yaml/app/Main.hs: -------------------------------------------------------------------------------- 1 | 2 | import System.Directory (getCurrentDirectory) 3 | 4 | main = return () 5 | -------------------------------------------------------------------------------- /tests/projects/stack-with-yaml/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | stackYaml: "stack-alt.yaml" 4 | components: 5 | - path: ./src 6 | component: "stack-with-yaml:lib" 7 | 8 | - path: ./app 9 | component: "stack-with-yaml:exe:stack-with-yaml" 10 | -------------------------------------------------------------------------------- /tests/projects/stack-with-yaml/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib where 2 | 3 | import System.FilePath (()) 4 | 5 | foo = "test" "me" 6 | -------------------------------------------------------------------------------- /tests/projects/stack-with-yaml/stack-with-yaml.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=2.0 2 | name: stack-with-yaml 3 | version: 0.1.0.0 4 | build-type: Simple 5 | 6 | library 7 | exposed-modules: Lib 8 | -- other-modules: 9 | -- other-extensions: 10 | build-depends: base >=4.10 && < 5, filepath 11 | hs-source-dirs: src 12 | default-language: Haskell2010 13 | 14 | 15 | 16 | executable stack-with-yaml 17 | main-is: app/Main.hs 18 | -- other-modules: 19 | -- other-extensions: 20 | build-depends: base >=4.10 && < 5, directory 21 | -- hs-source-dirs: 22 | default-language: Haskell2010 23 | ghc-options: +RTS -A1m -N -RTS -Wall 24 | -------------------------------------------------------------------------------- /tests/projects/symlink-test/a/A.hs: -------------------------------------------------------------------------------- 1 | module A where 2 | 3 | a :: Int 4 | a = 5 -------------------------------------------------------------------------------- /tests/projects/symlink-test/hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | multi: 3 | - path: ./a 4 | config: 5 | cradle: 6 | direct: 7 | arguments: ["a"] 8 | - path: ./b 9 | config: 10 | cradle: 11 | direct: 12 | arguments: ["b"] 13 | - path: ./c 14 | config: 15 | cradle: 16 | none: -------------------------------------------------------------------------------- /wrappers/bazel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | fullname=$(bazel query "$1") 3 | attr=$(bazel query "kind(haskell_*, attr('srcs', $fullname, ${fullname//:*/}:*))") 4 | bazel build "$attr@repl" --experimental_show_artifacts 2>&1 | sed -ne '/>>>/ s/^>>>\(.*\)$/\1/ p' | xargs tail -1 5 | 6 | -------------------------------------------------------------------------------- /wrappers/cabal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function out(){ 4 | echo "$1" >> "$HIE_BIOS_OUTPUT" 5 | } 6 | 7 | if [ "$1" == "--interactive" ]; then 8 | out "$(pwd)" 9 | for arg in "$@"; do 10 | out "$arg" 11 | done 12 | else 13 | "${HIE_BIOS_GHC:-ghc}" "${HIE_BIOS_GHC_ARGS}" "$@" 14 | fi 15 | -------------------------------------------------------------------------------- /wrappers/cabal.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Data.Maybe (fromMaybe) 4 | import System.Directory (getCurrentDirectory) 5 | import System.Environment (getArgs, getEnv, lookupEnv) 6 | import System.Exit (exitWith) 7 | import System.Process (spawnProcess, waitForProcess) 8 | import System.IO (openFile, hClose, hPutStrLn, IOMode(..)) 9 | 10 | main :: IO () 11 | main = do 12 | args <- getArgs 13 | case args of 14 | "--interactive":_ -> do 15 | output_file <- getEnv "HIE_BIOS_OUTPUT" 16 | h <- openFile output_file AppendMode 17 | getCurrentDirectory >>= hPutStrLn h 18 | mapM_ (hPutStrLn h) args 19 | hClose h 20 | _ -> do 21 | ghc_path <- fromMaybe "ghc" <$> lookupEnv "HIE_BIOS_GHC" 22 | ph <- spawnProcess ghc_path (args) 23 | code <- waitForProcess ph 24 | exitWith code 25 | --------------------------------------------------------------------------------