Merge upstream

This commit is contained in:
Avi Dessauer 2020-01-18 12:43:02 -05:00
commit f34aacacce
73 changed files with 1638 additions and 516 deletions

View File

@ -0,0 +1 @@
export PATH=$HOME/.cabal/bin:/opt/cabal/$CABAL_VERSION/bin:/opt/ghc/$GHC_VERSION/bin:$HOME/.local/bin:$PATH

76
.azure/linux-cabal.yml Normal file
View File

@ -0,0 +1,76 @@
jobs:
- job: Linux_Cabal
timeoutInMinutes: 0
pool:
vmImage: ubuntu-16.04
strategy:
matrix:
ghc-8.6.5:
GHC_VERSION: "8.6.5"
ghc-8.4.4:
GHC_VERSION: "8.4.4"
variables:
CABAL_VERSION: "3.0"
steps:
- task: Cache@2
inputs:
key: '"cabal-store" | "$(Agent.OS)" | "$(CABAL_VERSION)" | "$(GHC_VERSION)" | $(Build.SourcesDirectory)/cabal.project | $(Build.SourcesDirectory)/haskell-ide-engine.cabal | $(Build.SourcesDirectory)/hie-plugin-api/hie-plugin-api.cabal'
path: .azure-cache
cacheHitVar: CACHE_RESTORED
displayName: "Download cache"
- bash: |
mkdir -p $HOME/.cabal
tar -vxzf .azure-cache/cabal-root.tar.gz -C /
mkdir -p $HOME/.ghc
tar -vxzf .azure-cache/ghc-root.tar.gz -C /
mkdir -p dist-newtyle
tar -vxzf .azure-cache/cabal-dist.tar.gz
displayName: "Unpack cache"
condition: eq(variables.CACHE_RESTORED, 'true')
- bash: |
git submodule sync
git submodule update --init
displayName: Sync submodules
- bash: |
source .azure/linux-cabal.bashrc
cabal v2-update
cabal v2-build --only-dependencies
displayName: Build dependencies
- bash: |
source .azure/linux-cabal.bashrc
cabal v2-build
displayName: Build `hie`
- bash: |
source .azure/linux-cabal.bashrc
cabal v2-install --overwrite-policy=always # `hie` binary required locally for tests
displayName: Install `hie`
- bash: |
source .azure/linux-cabal.bashrc
cabal v2-build --enable-tests --enable-benchmarks --only-dependencies
displayName: Build Test-dependencies
- bash: |
sudo apt update
sudo apt install z3
displayName: "Install Runtime Test-Dependencies: z3"
- bash: |
source .azure/linux-cabal.bashrc
# to not reinstall hie
cd $(Agent.TempDirectory)
cabal v2-install liquidhaskell-0.8.6.2 -w /opt/ghc/8.6.5/bin/ghc --overwrite-policy=always
displayName: "Install Runtime Test-Dependencies: liquidhaskell"
- bash: |
source .azure/linux-cabal.bashrc
cabal v2-build hoogle
cabal v2-exec hoogle generate
displayName: "Install Runtime Test-Dependencies: hoogle database"
- bash: |
source .azure/linux-cabal.bashrc
# TODO: Investigate why the test suite can't be run in parallel
cabal v2-test -j1
displayName: Run Test
- bash: |
mkdir -p .azure-cache
tar -vczf .azure-cache/cabal-root.tar.gz $HOME/.cabal
tar -vczf .azure-cache/ghc-root.tar.gz $HOME/.ghc
tar -vczf .azure-cache/cabal-dist.tar.gz dist-newstyle
displayName: "Pack cache"

View File

@ -3,10 +3,8 @@ jobs:
timeoutInMinutes: 0
pool:
vmImage: ubuntu-16.04
strategy:
matrix:
shake:
YAML_FILE: install/shake.yaml
variables:
YAML_FILE: install/shake.yaml
steps:
- bash: |
export STACK_ROOT="$(Build.SourcesDirectory)"/.stack-root
@ -32,9 +30,5 @@ jobs:
displayName: Run help of `install.hs`
- bash: |
source .azure/linux.bashrc
stack install.hs stack-install-cabal
displayName: Run stack-install-cabal target of `install.hs`
- bash: |
source .azure/linux.bashrc
stack install.hs build-latest
displayName: Run build-latest target of `install.hs`
stack install.hs latest
displayName: Run latest target of `install.hs`

View File

@ -28,59 +28,89 @@ jobs:
variables:
STACK_ROOT: /home/vsts/.stack
steps:
- task: CacheBeta@0
- task: Cache@2
inputs:
key: stack-root | $(Agent.OS) | $(Build.SourcesDirectory)/$(YAML_FILE)
key: '"stack-root" | "$(Agent.OS)" | $(Build.SourcesDirectory)/$(YAML_FILE)'
path: .azure-cache
cacheHitVar: CACHE_RESTORED
displayName: "Download cache"
- bash: |
mkdir -p $STACK_ROOT
tar -xzf .azure-cache/stack-root.tar.gz -C /
tar -vxzf .azure-cache/stack-root.tar.gz -C /
mkdir -p .stack-work
tar -xzf .azure-cache/stack-work.tar.gz
tar -vxzf .azure-cache/stack-work.tar.gz
displayName: "Unpack cache"
condition: eq(variables.CACHE_RESTORED, 'true')
- bash: |
git submodule sync
git submodule update --init
displayName: Sync submodules
- bash: |
mkdir -p ~/.local/bin
curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | \
tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
tar vxz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
displayName: Install stack
- bash: |
source .azure/linux.bashrc
source .azure/linux-stack.bashrc
stack setup --stack-yaml $(YAML_FILE)
displayName: Install GHC
- bash: |
source .azure/linux.bashrc
source .azure/linux-stack.bashrc
stack --stack-yaml $(YAML_FILE) --install-ghc build --only-dependencies
displayName: Build dependencies
- bash: |
source .azure/linux.bashrc
source .azure/linux-stack.bashrc
stack build --stack-yaml $(YAML_FILE)
displayName: Build `hie`
- bash: |
source .azure/linux.bashrc
source .azure/linux-stack.bashrc
stack install --stack-yaml $(YAML_FILE) # `hie` binary required locally for tests
mkdir .azure-deploy
stack install --stack-yaml $(YAML_FILE) --local-bin-path .azure-deploy
cd .azure-deploy
if [ $YAML_FILE != "stack.yaml" ]; then
GHC_MINOR_VERSION=${YAML_FILE:6:5}
GHC_MAJOR_VERSION=${YAML_FILE:6:3}
cp hie hie-$GHC_MINOR_VERSION
cp hie hie-$GHC_MAJOR_VERSION
else
GHC_MINOR_VERSION=nightly
fi
stack unpack hlint --stack-yaml ../$(YAML_FILE) --to "$(Agent.TempDirectory)"
mkdir -p data
cp "$(Agent.TempDirectory)"/hlint*/data/hlint.yaml data
ARTIFACT_NAME=hie-$(hie --numeric-version)-ghc-$GHC_MINOR_VERSION-linux-x86_64
tar -vczf $(Build.ArtifactStagingDirectory)/$ARTIFACT_NAME.tar.xz *
displayName: Install `hie`
- bash: |
source .azure/linux-stack.bashrc
stack build --stack-yaml $(YAML_FILE) --test --bench --only-dependencies
stack install --stack-yaml $(YAML_FILE) # `hie` binary required for tests
displayName: Build Test-dependencies
- bash: |
sudo apt update
sudo apt install z3
displayName: "Install Runtime Test-Dependencies: z3"
- bash: |
source .azure/linux.bashrc
stack install --resolver=lts-11.18 liquid-fixpoint-0.7.0.7 dotgen-0.4.2 fgl-visualize-0.1.0.1 located-base-0.1.1.1 liquidhaskell-0.8.2.4
source .azure/linux-stack.bashrc
stack install --resolver=lts-13.20 liquid-fixpoint-0.8.0.2 liquidhaskell-0.8.6.2
displayName: "Install Runtime Test-Dependencies: liquidhaskell"
# - bash: |
# source .azure/linux.bashrc
# stack test --stack-yaml $(YAML_FILE)
# displayName: Run Test
- bash: |
source .azure/linux-stack.bashrc
stack build hoogle --stack-yaml=$(YAML_FILE)
stack exec hoogle generate --stack-yaml=$(YAML_FILE)
displayName: "Install Runtime Test-Dependencies: hoogle database"
- bash: |
source .azure/linux-stack.bashrc
# cabal is also a test runtime dependency
export PATH=/opt/cabal/3.0/bin:$PATH
stack test --stack-yaml $(YAML_FILE)
displayName: Run Test
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: hie-$(Agent.OS)-$(YAML_FILE)
condition: in(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')
- bash: |
mkdir -p .azure-cache
tar -czf .azure-cache/stack-root.tar.gz $STACK_ROOT
tar -czf .azure-cache/stack-work.tar.gz .stack-work
tar -vczf .azure-cache/stack-root.tar.gz $STACK_ROOT
tar -vczf .azure-cache/stack-work.tar.gz .stack-work
displayName: "Pack cache"

View File

@ -3,10 +3,8 @@ jobs:
timeoutInMinutes: 0
pool:
vmImage: macOS-10.13
strategy:
matrix:
shake:
YAML_FILE: install/shake.yaml
variables:
YAML_FILE: install/shake.yaml
steps:
- bash: |
export STACK_ROOT="$(Build.SourcesDirectory)"/.stack-root
@ -32,9 +30,5 @@ jobs:
displayName: Run help of `install.hs`
- bash: |
source .azure/macos.bashrc
stack install.hs stack-install-cabal
displayName: Run stack-install-cabal target of `install.hs`
- bash: |
source .azure/macos.bashrc
stack install.hs build-latest
displayName: Run build-latest target of `install.hs`
stack install.hs latest
displayName: Run latest target of `install.hs`

View File

@ -24,17 +24,17 @@ jobs:
variables:
STACK_ROOT: $(Build.SourcesDirectory)/.stack
steps:
- task: CacheBeta@0
- task: Cache@2
inputs:
key: stack-root | $(Agent.OS) | $(Build.SourcesDirectory)/$(YAML_FILE)
key: '"stack-root" | "$(Agent.OS)" | $(Build.SourcesDirectory)/$(YAML_FILE)'
path: .azure-cache
cacheHitVar: CACHE_RESTORED
displayName: "Download cache"
- bash: |
mkdir -p $STACK_ROOT
tar -xzf .azure-cache/stack-root.tar.gz -C /
tar -vxzf .azure-cache/stack-root.tar.gz -C /
mkdir -p .stack-work
tar -xzf .azure-cache/stack-work.tar.gz
tar -vxzf .azure-cache/stack-work.tar.gz
displayName: "Unpack cache"
condition: eq(variables.CACHE_RESTORED, 'true')
- bash: |
@ -44,7 +44,7 @@ jobs:
- bash: |
mkdir -p ~/.local/bin
curl -skL https://get.haskellstack.org/stable/osx-x86_64.tar.gz | \
tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin;
tar vxz --strip-components=1 --include '*/stack' -C ~/.local/bin;
displayName: Install stack
- bash: |
source .azure/macos.bashrc
@ -58,26 +58,66 @@ jobs:
source .azure/macos.bashrc
stack build --stack-yaml $(YAML_FILE)
displayName: Build `hie`
- bash: |
source .azure/macos.bashrc
stack install --stack-yaml $(YAML_FILE) # `hie` binary required locally for tests
mkdir .azure-deploy
stack install --stack-yaml $(YAML_FILE) --local-bin-path .azure-deploy
cd .azure-deploy
if [ $YAML_FILE != "stack.yaml" ]; then
GHC_MINOR_VERSION=${YAML_FILE:6:5}
GHC_MAJOR_VERSION=${YAML_FILE:6:3}
cp hie hie-$GHC_MINOR_VERSION
cp hie hie-$GHC_MAJOR_VERSION
else
GHC_MINOR_VERSION=nightly
fi
stack unpack hlint --stack-yaml ../$(YAML_FILE) --to "$(Agent.TempDirectory)"
mkdir -p data
cp "$(Agent.TempDirectory)"/hlint*/data/hlint.yaml data
ARTIFACT_NAME=hie-$(hie --numeric-version)-ghc-$GHC_MINOR_VERSION-macos-x86_64
tar -vczf $(Build.ArtifactStagingDirectory)/$ARTIFACT_NAME.tar.xz *
displayName: Install `hie`
- bash: |
source .azure/macos.bashrc
stack build --stack-yaml $(YAML_FILE) --test --bench --only-dependencies
stack install --stack-yaml $(YAML_FILE) # `hie` binary required for tests
displayName: Build Test-dependencies
- bash: |
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew update
brew unlink python@2
brew install z3
displayName: "Install Runtime Test-Dependencies: z3"
- bash: |
source .azure/macos.bashrc
stack install --resolver=lts-11.18 liquid-fixpoint-0.7.0.7 dotgen-0.4.2 fgl-visualize-0.1.0.1 located-base-0.1.1.1 liquidhaskell-0.8.2.4
stack install --resolver=lts-13.20 liquid-fixpoint-0.8.0.2 liquidhaskell-0.8.6.2
displayName: "Install Runtime Test-Dependencies: liquidhaskell"
# - bash: |
# source .azure/macos.bashrc
# stack test --stack-yaml $(YAML_FILE)
# displayName: Run Test
- bash: |
source .azure/macos.bashrc
stack build hoogle --stack-yaml=$(YAML_FILE)
stack exec hoogle generate --stack-yaml=$(YAML_FILE)
displayName: "Install Runtime Test-Dependencies: hoogle database"
- bash: |
source .azure/macos.bashrc
brew install cabal-install
displayName: "Install Runtime Test-Dependencies: cabal"
- bash: |
source .azure/macos.bashrc
GHC_MAJOR_VERSION=${YAML_FILE:6:3}
if [ $GHC_MAJOR_VERSION != "8.4" ]; then
stack test --stack-yaml $(YAML_FILE)
else
#TODO Enable dispatcher-test and func-test for ghc-8.4.*
stack test --stack-yaml $(YAML_FILE) :unit-test :plugin-dispatcher-test :wrapper-test
fi
displayName: Run Test
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: hie-$(Agent.OS)-$(YAML_FILE)
condition: in(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')
- bash: |
mkdir -p .azure-cache
tar -czf .azure-cache/stack-root.tar.gz $STACK_ROOT
tar -czf .azure-cache/stack-work.tar.gz .stack-work
tar -vczf .azure-cache/stack-root.tar.gz $STACK_ROOT
tar -vczf .azure-cache/stack-work.tar.gz .stack-work
displayName: "Pack cache"

View File

@ -6,6 +6,8 @@ jobs:
variables:
YAML_FILE: install/shake.yaml
PROJECT_FILE: install/shake.project
STACK_ROOT: "C:\\sr"
# TODO: Replace stack with chocolatey for installing ghc and cabal
steps:
- bash: |
curl -sSkL http://www.stackage.org/stack/windows-x86_64 -o /usr/bin/stack.zip
@ -17,21 +19,21 @@ jobs:
displayName: Install GHC
- bash: |
source .azure/windows.bashrc
stack install cabal-install --stack-yaml $(YAML_FILE)
stack install Cabal-3.0.0.0 cabal-install-3.0.0.0 --stack-yaml $(YAML_FILE)
displayName: Install `cabal-install`
- bash: |
source .azure/windows.bashrc
cabal update
cabal v2-update
displayName: update cabal
# - bash: |
# source .azure/windows.bashrc
# stack --stack-yaml $(YAML_FILE) build --only-dependencies
# displayName: Build dependencies
- bash: |
source .azure/windows.bashrc
cabal v2-build hie-install -w $(stack path --stack-yaml $(YAML_FILE) --compiler-exe) --project-file $(PROJECT_FILE)
displayName: Build `hie-install`
- bash: |
source .azure/windows.bashrc
cabal v2-run install.hs -w $(stack path --stack-yaml $(YAML_FILE) --compiler-exe) --project-file $(PROJECT_FILE) help
GHC_PATH=$(stack path --stack-yaml $YAML_FILE --compiler-exe)
cabal v2-run install.hs -w $GHC_PATH --project-file $(PROJECT_FILE) help
displayName: Run help of `install.hs`
# - bash: |
# source .azure/windows.bashrc
# GHC_PATH=$(stack path --stack-yaml $YAML_FILE --compiler-exe)
# GHC_DIR=$(dirname $GHC_PATH)
# export PATH=$(cygpath $GHC_DIR):$PATH
# cabal v2-run install.hs -w $GHC_PATH --project-file $(PROJECT_FILE) build-latest
# displayName: Run build-latest target of `install.hs`

View File

@ -3,10 +3,9 @@ jobs:
timeoutInMinutes: 0
pool:
vmImage: windows-2019
strategy:
matrix:
shake:
YAML_FILE: install/shake.yaml
variables:
YAML_FILE: install/shake.yaml
STACK_ROOT: "C:\\sr"
steps:
- bash: |
curl -sSkL http://www.stackage.org/stack/windows-x86_64 -o /usr/bin/stack.zip
@ -18,7 +17,7 @@ jobs:
displayName: Install GHC
- bash: |
source .azure/windows.bashrc
stack --stack-yaml $(YAML_FILE) --install-ghc build --only-dependencies
stack --stack-yaml $(YAML_FILE) build --only-dependencies
displayName: Build dependencies
- bash: |
source .azure/windows.bashrc
@ -30,9 +29,6 @@ jobs:
displayName: Run help of `install.hs`
- bash: |
source .azure/windows.bashrc
stack install.hs stack-install-cabal
displayName: Run stack-install-cabal target of `install.hs`
- bash: |
source .azure/windows.bashrc
stack install.hs build-latest
displayName: Run build-latest target of `install.hs`
# Some executions fails with spurious errors installing the exe
stack install.hs latest || stack install.hs latest
displayName: Run latest target of `install.hs`

View File

@ -26,19 +26,19 @@ jobs:
STACK_ROOT: "C:\\sr"
steps:
- task: CacheBeta@0
- task: Cache@2
inputs:
key: stack-root | $(Agent.OS) | $(Build.SourcesDirectory)/$(YAML_FILE)
path: $(STACK_ROOT)
key: '"stack-root" | "$(Agent.OS)" | $(Build.SourcesDirectory)/$(YAML_FILE)'
path: .azure-cache
cacheHitVar: CACHE_RESTORED
displayName: "Cache stack-root"
- task: CacheBeta@0
inputs:
key: |
"stack-work"
$(Agent.OS)
$(Build.SourcesDirectory)/$(YAML_FILE)
path: .stack-work
displayName: "Cache stack-work"
- bash: |
mkdir -p $STACK_ROOT
tar -vxzf .azure-cache/stack-root.tar.gz -C /
mkdir -p .stack-work
tar -vxzf .azure-cache/stack-work.tar.gz
displayName: "Unpack cache"
condition: eq(variables.CACHE_RESTORED, 'true')
- bash: |
git submodule sync
git submodule update --init
@ -59,10 +59,29 @@ jobs:
source .azure/windows.bashrc
stack build --stack-yaml $(YAML_FILE)
displayName: Build `hie`
- bash: |
source .azure/windows.bashrc
stack install --stack-yaml $(YAML_FILE) # `hie` binary required locally for tests
mkdir .azure-deploy
stack install --stack-yaml $(YAML_FILE) --local-bin-path .azure-deploy
cd .azure-deploy
if [ $YAML_FILE != "stack.yaml" ]; then
GHC_MINOR_VERSION=${YAML_FILE:6:5}
GHC_MAJOR_VERSION=${YAML_FILE:6:3}
cp hie.exe hie-$GHC_MINOR_VERSION.exe
cp hie.exe hie-$GHC_MAJOR_VERSION.exe
else
GHC_MINOR_VERSION=nightly
fi
stack unpack hlint --stack-yaml ../$(YAML_FILE) --to "$(Agent.TempDirectory)"
mkdir -p data
cp "$(Agent.TempDirectory)"/hlint*/data/hlint.yaml data
ARTIFACT_NAME=hie-$(hie --numeric-version)-ghc-$GHC_MINOR_VERSION-windows-x86_64
7z a "$(Build.ArtifactStagingDirectory)/$ARTIFACT_NAME.zip" *
displayName: Install `hie`
- bash: |
source .azure/windows.bashrc
stack build --stack-yaml $(YAML_FILE) --test --bench --only-dependencies
stack install --stack-yaml $(YAML_FILE) # `hie` binary required for tests
displayName: Build Test-dependencies
- bash: |
# TODO: try to install automatically (`choco install z3` fails and pacman is not installed)
@ -72,10 +91,41 @@ jobs:
displayName: "Install Runtime Test-Dependencies: z3"
- bash: |
source .azure/windows.bashrc
stack install --resolver=lts-11.18 liquid-fixpoint-0.7.0.7 dotgen-0.4.2 fgl-visualize-0.1.0.1 located-base-0.1.1.1 liquidhaskell-0.8.2.4
liquid -v
stack install --resolver=lts-13.20 liquid-fixpoint-0.8.0.2 liquidhaskell-0.8.6.2
displayName: "Install Runtime Test-Dependencies: liquidhaskell"
# - bash: |
# source .azure/windows.bashrc
# stack test --stack-yaml $(YAML_FILE) :unit-test
# displayName: Run Test
- bash: |
source .azure/windows.bashrc
stack build hoogle --stack-yaml=$(YAML_FILE)
stack exec hoogle generate --stack-yaml=$(YAML_FILE)
displayName: "Install Runtime Test-Dependencies: hoogle database"
- bash: |
source .azure/windows.bashrc
choco install cabal
/C/ProgramData/chocolatey/bin/RefreshEnv.cmd
displayName: "Install Runtime Test-Dependencies: cabal"
- bash: |
source .azure/windows.bashrc
# TODO: Enable CabalHelper unit tests, see https://github.com/DanielG/cabal-helper/issues/91
if [ $YAML_FILE = "stack-8.6.4.yaml" ]; then
TEST_ARGS=--test-arguments="--skip=CabalHelper"
fi
# TODO: Enable rest of test suites
stack test --stack-yaml $(YAML_FILE) :unit-test :plugin-dispatcher-test $TEST_ARGS # :dispatcher-test :wrapper-test
# TODO: Enable failing functional test
if [ $YAML_FILE != "stack-8.6.4.yaml" ]; then
stack test --stack-yaml $(YAML_FILE) :func-test
else
stack test --stack-yaml $(YAML_FILE) :func-test --ta="--skip \"/Hover/hover/works\""
fi
displayName: Run Test
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: hie-$(Agent.OS)-$(YAML_FILE)
condition: in(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')
- bash: |
mkdir -p .azure-cache
tar -vczf .azure-cache/stack-root.tar.gz $(cygpath $STACK_ROOT)
tar -vczf .azure-cache/stack-work.tar.gz .stack-work
displayName: "Pack cache"

1
.gitignore vendored
View File

@ -36,6 +36,7 @@
/docs/source/__pycache__/
/docs/source/plugins/
/elisp/.cask/
/test/testdata/*.exe
/test/testdata/FuncTest.refactored.hs
/test/testdata/HaReAddRmParam.refactored.hs
/test/testdata/HaReCase.refactored.hs

View File

@ -1,3 +1,111 @@
# 1.0.0.0
- NOTE: 1.0 status does **not** mean it is now stable.
- This is a major version bump, because the internal architecture has
changed to make use of the new
[hie-bios](https://github.com/mpickering/hie-bios) from @mpickering
(and a host of others).
Some of the implications of this are
- Cabal 3.0 support.
- Support for stack scripts.
- Work on test, executable, benchmark and library components at the same time.
- You can set the build-tool (stack or cabal) explicitly.
- Various memory leaks have been fixed.
- Various performance improvements.
Also, until `hie-bios` provides a means to get a full module graph for
the project, we haved disabled HaRe (rename, caseSplitCmd, etc...).
It is better to not have a tool, than one that sort-of works, and we
did not want to hold back all the other work that has been done.
## Changes
- Fallback to direct cradle if no project context can be found
([#1551](https://github.com/haskell/haskell-ide-engine/pull/1551), by @fendor)
- Bump resolvers `lts-14.20` for GHC 8.6.5.
([#1547](https://github.com/haskell/haskell-ide-engine/pull/1547), by @alanz)
- Improve quality and information density of error message
([#1522](https://github.com/haskell/haskell-ide-engine/pull/1522), by @fendor)
- Add cmd script to install with cabal
([#1542](https://github.com/haskell/haskell-ide-engine/pull/1542), by @jneira)
- Tweak hie-wrapper and hie exe startup messages
([#1539](https://github.com/haskell/haskell-ide-engine/pull/1539), by @alanz)
- First pass at printing out debug info if started without --lsp
([#1538](https://github.com/haskell/haskell-ide-engine/pull/1538), by @alanz)
- Return hlint code actions as type 'quickfix'
([#1537](https://github.com/haskell/haskell-ide-engine/pull/1537), by @alanz)
- Readme: Remove non-existing troubleshooting entry
([#1534](https://github.com/haskell/haskell-ide-engine/pull/1534), by @andys8)
- Add sample hie.yaml files for stack and cabal
([#1533](https://github.com/haskell/haskell-ide-engine/pull/1533), by @alanz)
- Look for stack.yaml before selecting .cabal location as project root
([#1531](https://github.com/haskell/haskell-ide-engine/pull/1531), by @fendor)
- Change Maybe LspFuncs to LspFuncs inside IdeEnv
([#1523](https://github.com/haskell/haskell-ide-engine/pull/1523), by @bubba)
- Replaced cabal-helper submodule with hackage version
([#1521](https://github.com/haskell/haskell-ide-engine/pull/1521), by @alanz)
- Adapt GhcModPluginSpec after merge of #1496
([#1507](https://github.com/haskell/haskell-ide-engine/pull/1507), by @alanz)
- Find the libdir directory of ghc at run-time
([#1496](https://github.com/haskell/haskell-ide-engine/pull/1496), by @fendor)
- Restore the ghcmod plugin command routing
([#1505](https://github.com/haskell/haskell-ide-engine/pull/1505), by @alanz)
- Make errorm use errorM instead of warningM
([#1502](https://github.com/haskell/haskell-ide-engine/pull/1502), by @DavSanchez)
- Refactor plugins and commands now that JSON transport is gone
([#1492](https://github.com/haskell/haskell-ide-engine/pull/1492), by @bubba)
- Retain the --lsp option, to not break existing clients
([#1494](https://github.com/haskell/haskell-ide-engine/pull/1494), by @alanz)
updated 14 days ago
- Remove JSON transport
([#1489](https://github.com/haskell/haskell-ide-engine/pull/1489), by @bubba)
- Remove ghc-project-types
([#1487](https://github.com/haskell/haskell-ide-engine/pull/1487), by @alanz)
- Switch back to @DanielG cabal-helper
([#1488](https://github.com/haskell/haskell-ide-engine/pull/1488), by @bubba)
- Update Ubuntu dependency
([#1485](https://github.com/haskell/haskell-ide-engine/pull/1485), by @leifmetcalf)
- Some tidying up for tests
([#1483](https://github.com/haskell/haskell-ide-engine/pull/1483), by @bubba)
- Disable travis for now
([#1484](https://github.com/haskell/haskell-ide-engine/pull/1484), by @bubba)
- Implement the HIE Bios
([#1126](https://github.com/haskell/haskell-ide-engine/pull/1126), by @mpickering)
- Add mac build
([#1479](https://github.com/haskell/haskell-ide-engine/pull/1479), by @liam-ly)
- Unit test over the liquid haskell supported version
([#1449](https://github.com/haskell/haskell-ide-engine/pull/1449), by @jneira)
# 0.14.0.0
- Bump resolvers and deps `lts-14.16` for GHC 8.6.5.

View File

@ -64,7 +64,6 @@ This project aims to be __the universal interface__ to __a growing number of Has
- [Is \<package\> base-x?](#is-package-base-x)
- [Is there a hash (#) after \<package\>?](#is-there-a-hash--after-package)
- [Otherwise](#otherwise)
- [Nix: cabal-helper, No such file or directory](#nix-cabal-helper-no-such-file-or-directory)
- [Liquid Haskell](#liquid-haskell)
- [Profiling `haskell-ide-engine`.](#profiling-haskell-ide-engine)
- [Using `ghc-events-analyze`](#using-ghc-events-analyze)
@ -153,6 +152,10 @@ HIE builds from source code, so there's a couple of extra steps.
* `stack` must be in your PATH
* `git` must be in your PATH
* Stack local bin directory must be in your PATH. Get it with `stack path --local-bin`
* To make hlint suggestions work, `hie` must locate the [hlint configuration yaml file](https://raw.githubusercontent.com/ndmitchell/hlint/master/data/hlint.yaml). The file is searched in order in the following locations:
* In the directory pointed by the environment variable `$HLINT_DATADIR`.
* If hie was installed using stack or cabal, in a tool-specific internal installation directory.
* And finally in a subdirectory named `data` inside the directory where the `hie` executable is.
Tip: you can quickly check if some command is in your path by running the command.
If you receive some meaningful output instead of "command not found"-like message
@ -219,49 +222,43 @@ The install-script can be invoked via `cabal` instead of `stack` with the comman
cabal v2-run ./install.hs --project-file install/shake.project <target>
```
Running the script with cabal on windows requires a cabal version greater or equal to `3.0.0.0`.
Unfortunately, it is still required to have `stack` installed so that the install-script can locate the `local-bin` directory (on Linux `~/.local/bin`) and copy the `hie` binaries to `hie-x.y.z`, which is required for the `hie-wrapper` to function as expected. There are plans to remove this requirement and let users build hie only with one build tool or another.
For brevity, only the `stack`-based commands are presented in the following sections.
##### Install cabal using stack
Although you can use hie for stack based projects (those which have a `stack.yaml` in the project base directory) without having cabal installed, you will need it for cabal based projects (with only a `<projectName>.cabal` file or a `cabal.project` one in the project base directory).
You can install an appropriate cabal version using stack by running:
or using the existing alias script
```bash
stack ./install.hs stack-install-cabal
./cabal-hie-install <target>
```
Running the script with cabal on windows requires a cabal version greater or equal to `3.0.0.0`.
For brevity, only the `stack`-based commands are presented in the following sections.
##### Install specific GHC Version
Install hie for the latest available and supported GHC version (and hoogle docs):
```bash
stack ./install.hs build
stack ./install.hs hie
```
Install hie for a specific GHC version (and hoogle docs):
```bash
stack ./install.hs hie-8.6.5
stack ./install.hs build-data
stack ./install.hs data
```
The Haskell IDE Engine can also be built with `cabal v2-build` instead of `stack build`.
This has the advantage that you can decide how the GHC versions have been installed.
To see what GHC versions are available, the command `stack install.hs cabal-ghcs` can be used.
To see what GHC versions are available, the command `cabal-hie-install ghcs` can be used.
It will list all GHC versions that are on the path and their respective installation directory.
If you think, this list is incomplete, you can try to modify the PATH variable, such that the executables can be found.
Note, that the targets `cabal-build` and `cabal-build-data` depend on the found GHC versions.
Note, that the targets `hie` and `data` depend on the found GHC versions.
They install Haskell IDE Engine only for the found GHC versions.
An example output is:
```bash
> stack install.hs cabal-ghcs
> cabal-hie-install ghcs
******************************************************************
Found the following GHC paths:
ghc-8.4.4: /opt/bin/ghc-8.4.4
@ -273,11 +270,11 @@ ghc-8.6.2: /opt/bin/ghc-8.6.2
If your desired ghc has been found, you use it to install Haskell IDE Engine.
```bash
stack install.hs cabal-hie-8.4.4
stack install.hs cabal-build-data
cabal-hie-install hie-8.4.4
cabal-hie-install data
```
In general, targets that use `cabal` instead of `stack` are prefixed with `cabal-*` and are identical to their counterpart, except they do not install a GHC if it is missing but fail.
In general, executing targets with `cabal` instead of `stack` have the same behaviour, except they do not install a GHC if it is missing but fail.
##### Multiple versions of HIE (optional)
@ -541,6 +538,7 @@ Then issue `:CocConfig` and add the following to your Coc config file.
"languageserver": {
"haskell": {
"command": "hie-wrapper",
"args": ["--lsp"],
"rootPatterns": [
"*.cabal",
"stack.yaml",
@ -583,7 +581,7 @@ into `~/.vim/pack/XXX/start/`, where `XXX` is just a name for your "plugin suite
```vim
set rtp+=~/.vim/pack/XXX/start/LanguageClient-neovim
let g:LanguageClient_serverCommands = { 'haskell': ['hie-wrapper'] }
let g:LanguageClient_serverCommands = { 'haskell': ['hie-wrapper', '--lsp'] }
```
You'll probably want to add some mappings for common commands:
@ -726,6 +724,15 @@ This project is not started from scratch:
- Fork this repo and hack as much as you can.
- Ask @alanz or @hvr to join the project.
### Hacking on haskell-ide-engine
Haskell-ide-engine can be used on its own project. We have supplied
preset samples of `hie.yaml` files for stack and cabal, simply copy
the appropriate template to `hie.yaml` and it shoule work.
- `hie.yaml.cbl` for cabal
- `hie.yaml.stack` for stack
## Documentation
All the documentation is in [the docs folder](/docs) at the root of this project.

View File

@ -72,6 +72,8 @@ run opts = do
d <- getCurrentDirectory
logm $ "Current directory:" ++ d
logm $ "Operating system:" ++ os
args <- getArgs
logm $ "args:" ++ show args
-- Get the cabal directory from the cradle
cradle <- findLocalCradle (d </> "File.hs")
@ -99,7 +101,6 @@ run opts = do
Nothing -> logm $ "cannot find any hie exe, looked for:" ++ intercalate ", " candidates
Just e -> do
logm $ "found hie exe at:" ++ e
args <- getArgs
logm $ "args:" ++ show args
logm "launching ....\n\n\n"
callProcess e args

View File

@ -3,11 +3,17 @@
{-# LANGUAGE RankNTypes #-}
module Main where
import qualified Control.Exception as E
import Control.Monad
#if __GLASGOW_HASKELL__ < 808
import Data.Monoid ((<>))
#endif
import Data.Version (showVersion)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Yaml as Yaml
import HIE.Bios.Types
import Haskell.Ide.Engine.Cradle (findLocalCradle, cradleDisplay, getProjectGhcLibDir)
import Haskell.Ide.Engine.MonadFunctions
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.Options
@ -19,9 +25,14 @@ import Options.Applicative.Simple
import qualified Paths_haskell_ide_engine as Meta
import System.Directory
import System.Environment
import qualified System.Log.Logger as L
import HIE.Bios.Types
import System.FilePath
import System.Info
import System.IO
import qualified System.Log.Logger as L
-- ---------------------------------------------------------------------
import RunTest
-- ---------------------------------------------------------------------
-- plugins
@ -29,6 +40,9 @@ import System.IO
import Haskell.Ide.Engine.Plugin.ApplyRefact
import Haskell.Ide.Engine.Plugin.Brittany
import Haskell.Ide.Engine.Plugin.Example2
import Haskell.Ide.Engine.Plugin.Floskell
import Haskell.Ide.Engine.Plugin.Generic
import Haskell.Ide.Engine.Plugin.GhcMod
-- import Haskell.Ide.Engine.Plugin.HaRe
import Haskell.Ide.Engine.Plugin.Haddock
import Haskell.Ide.Engine.Plugin.HfaAlign
@ -36,9 +50,6 @@ import Haskell.Ide.Engine.Plugin.HsImport
import Haskell.Ide.Engine.Plugin.Liquid
import Haskell.Ide.Engine.Plugin.Package
import Haskell.Ide.Engine.Plugin.Pragmas
import Haskell.Ide.Engine.Plugin.Floskell
import Haskell.Ide.Engine.Plugin.Generic
import Haskell.Ide.Engine.Plugin.GhcMod
-- ---------------------------------------------------------------------
@ -113,23 +124,90 @@ run opts = do
maybe (pure ()) setCurrentDirectory $ projectRoot opts
progName <- getProgName
logm $ "Run entered for HIE(" ++ progName ++ ") " ++ hieVersion
logm $ "Current directory:" ++ origDir
args <- getArgs
logm $ "args:" ++ show args
let initOpts = defaultCradleOpts { cradleOptsVerbosity = verbosity }
verbosity = if optBiosVerbose opts then Verbose else Silent
when (optBiosVerbose opts) $
logm "Enabling verbose mode for hie-bios. This option currently doesn't do anything."
when (optExamplePlugin opts) $
logm "Enabling Example2 plugin, will insert constant diagnostics etc."
let plugins' = plugins (optExamplePlugin opts)
-- launch the dispatcher.
scheduler <- newScheduler plugins' initOpts
server scheduler origDir plugins' (optCaptureFile opts)
if optLsp opts
then do
-- Start up in LSP mode
logm $ "Run entered for HIE(" ++ progName ++ ") " ++ hieVersion
logm $ "Operating as a LSP server on stdio"
logm $ "Current directory:" ++ origDir
logm $ "Operating system:" ++ os
logm $ "args:" ++ show args
let initOpts = defaultCradleOpts { cradleOptsVerbosity = verbosity }
verbosity = if optBiosVerbose opts then Verbose else Silent
when (optBiosVerbose opts) $
logm "Enabling verbose mode for hie-bios. This option currently doesn't do anything."
when (optExamplePlugin opts) $
logm "Enabling Example2 plugin, will insert constant diagnostics etc."
-- launch the dispatcher.
scheduler <- newScheduler plugins' initOpts
server scheduler origDir plugins' (optCaptureFile opts)
else do
-- Provide debug info
cliOut $ "Running HIE(" ++ progName ++ ")"
cliOut $ " " ++ hieVersion
cliOut $ "To run as a LSP server on stdio, provide the '--lsp' argument"
cliOut $ "Current directory:" ++ origDir
-- args <- getArgs
cliOut $ "\nargs:" ++ show args
cliOut $ "\nLooking for project config cradle...\n"
ecradle <- getCradleInfo origDir
case ecradle of
Left e -> cliOut $ "Could not get cradle:" ++ show e
Right cradle -> do
projGhc <- getProjectGhcVersion cradle
mlibdir <- getProjectGhcLibDir cradle
cliOut "\n\n###################################################\n"
cliOut $ "Cradle: " ++ cradleDisplay cradle
cliOut $ "Project Ghc version: " ++ projGhc
cliOut $ "Libdir: " ++ show mlibdir
cliOut "Searching for Haskell source files..."
targets <- case optFiles opts of
[] -> findAllSourceFiles origDir
xs -> concat <$> mapM findAllSourceFiles xs
cliOut $ "Found " ++ show (length targets) ++ " Haskell source files.\n"
cliOut "###################################################"
cliOut "\nFound the following files:\n"
mapM_ cliOut targets
cliOut ""
unless (optDryRun opts) $ do
cliOut "\nLoad them all now. This may take a very long time.\n"
loadDiagnostics <- runServer mlibdir plugins' targets
cliOut ""
cliOut "###################################################"
cliOut "###################################################"
cliOut "\nDumping diagnostics:\n\n"
mapM_ (cliOut' . uncurry prettyPrintDiags) loadDiagnostics
cliOut "\n\nNote: loading of 'Setup.hs' is not supported."
-- ---------------------------------------------------------------------
getCradleInfo :: FilePath -> IO (Either Yaml.ParseException Cradle)
getCradleInfo currentDir = do
let dummyCradleFile = currentDir </> "File.hs"
cradleRes <- E.try (findLocalCradle dummyCradleFile)
return cradleRes
-- ---------------------------------------------------------------------
cliOut :: String -> IO ()
cliOut = putStrLn
cliOut' :: T.Text -> IO ()
cliOut' = T.putStrLn
-- ---------------------------------------------------------------------

128
app/RunTest.hs Normal file
View File

@ -0,0 +1,128 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
module RunTest
( findAllSourceFiles
, compileTarget
, runServer
, prettyPrintDiags
)
where
import GhcMonad
import qualified GHC
import Control.Monad
import qualified Control.Concurrent.STM as STM
import Data.List ( isPrefixOf )
import qualified Data.Text as T
import qualified Data.Map as Map
import Data.Default
import System.Directory ( doesDirectoryExist
, listDirectory
, canonicalizePath
, doesFileExist
)
import System.FilePath
import Language.Haskell.LSP.Core
import Language.Haskell.LSP.Types
import Haskell.Ide.Engine.PluginsIdeMonads
hiding ( withIndefiniteProgress
, withProgress
)
import Haskell.Ide.Engine.GhcModuleCache
import qualified Haskell.Ide.Engine.ModuleCache
as MC
import qualified Haskell.Ide.Engine.Ghc as Ghc
findAllSourceFiles :: FilePath -> IO [FilePath]
findAllSourceFiles fp = do
absFp <- canonicalizePath fp
isDir <- doesDirectoryExist absFp
if isDir
then findFilesRecursively
isHaskellSource
(\path -> any (\p -> p path) [isHidden, isSpecialDir])
absFp
else filterM doesFileExist [absFp]
where
isHaskellSource = (== ".hs") . takeExtension
isHidden = ("." `isPrefixOf`) . takeFileName
isSpecialDir = (== "dist-newstyle") . takeFileName
findFilesRecursively
:: (FilePath -> Bool) -> (FilePath -> Bool) -> FilePath -> IO [FilePath]
findFilesRecursively p exclude dir = do
dirContents' <- listDirectory dir
let dirContents = map (dir </>) dirContents'
files <- forM dirContents $ \fp -> do
isDirectory <- doesDirectoryExist fp
if isDirectory
then if not $ exclude fp
then findFilesRecursively p exclude fp
else return []
else if p fp then return [fp] else return []
return $ concat files
-- ---------------------------------------------------------------------
compileTarget
:: GHC.DynFlags
-> FilePath
-> IdeGhcM (IdeResult (Ghc.Diagnostics, Ghc.AdditionalErrs))
compileTarget dynFlags fp = do
let pubDiags _ _ _ = return ()
let defAction = return (mempty, mempty)
let action = Ghc.setTypecheckedModule (filePathToUri fp)
actionResult <- MC.runActionWithContext pubDiags
dynFlags
(Just fp)
defAction
action
return $ join actionResult
-- ---------------------------------------------------------------------
runServer
:: Maybe FilePath
-> IdePlugins
-> [FilePath]
-> IO [(FilePath, IdeResult (Ghc.Diagnostics, Ghc.AdditionalErrs))]
runServer mlibdir ideplugins targets = do
let initialState = IdeState emptyModuleCache Map.empty Map.empty Nothing
stateVar <- STM.newTVarIO initialState
runIdeGhcM mlibdir ideplugins dummyLspFuncs stateVar $ do
dynFlags <- getSessionDynFlags
mapM (\fp -> (fp, ) <$> compileTarget dynFlags fp) targets
-- ---------------------------------------------------------------------
prettyPrintDiags
:: FilePath -> IdeResult (Ghc.Diagnostics, Ghc.AdditionalErrs) -> T.Text
prettyPrintDiags fp diags = T.pack fp <> ": " <> case diags of
IdeResultFail IdeError { ideMessage } -> "FAILED\n\t" <> ideMessage
IdeResultOk (_diags, errs) ->
if null errs then "OK" else T.unlines (map (T.append "\t") errs)
-- ---------------------------------------------------------------------
dummyLspFuncs :: Default a => LspFuncs a
dummyLspFuncs = LspFuncs
{ clientCapabilities = def
, config = return (Just def)
, sendFunc = const (return ())
, getVirtualFileFunc = const (return Nothing)
, persistVirtualFileFunc = \uri ->
return (uriToFilePath (fromNormalizedUri uri))
, reverseFileMapFunc = return id
, publishDiagnosticsFunc = mempty
, flushDiagnosticsBySourceFunc = mempty
, getNextReqId = pure (IdInt 0)
, rootPath = Nothing
, getWorkspaceFolders = return Nothing
, withProgress = \_ _ f -> f (const (return ()))
, withIndefiniteProgress = \_ _ f -> f
}

View File

@ -1,7 +1,35 @@
# Build master, branches starting with `azure` and tags commits
trigger:
batch: false
branches:
include:
- master
- azure*
tags:
include:
- '*'
paths:
exclude:
- .circleci
- docs
- licenses
- logos
- LICENSE
- '*.md'
# Enable PR triggers that target the master branch
pr:
autoCancel: true # cancel previous builds on push
branches:
include:
- master
jobs:
- template: ./.azure/linux-stack.yml
- template: ./.azure/linux-cabal.yml
- template: ./.azure/windows-stack.yml
- template: ./.azure/macos-stack.yml
- template: ./.azure/linux-installhs-stack.yml
- template: ./.azure/windows-installhs-stack.yml
- template: ./.azure/windows-installhs-cabal.yml
- template: ./.azure/macos-installhs-stack.yml

2
cabal-hie-install Normal file
View File

@ -0,0 +1,2 @@
#!/bin/sh
cabal v2-run ./install.hs --project-file install/shake.project $@

1
cabal-hie-install.cmd Normal file
View File

@ -0,0 +1 @@
@cabal v2-run .\install.hs --project-file=install\shake.project %*

View File

@ -9,4 +9,11 @@ tests: true
package haskell-ide-engine
test-show-details: direct
-- Match the flag settings we use in stac builds
constraints:
haskell-ide-engine +pedantic
hie-plugin-api +pedantic
write-ghc-environment-files: never
index-state: 2020-01-07T22:09:46Z

View File

@ -27,18 +27,17 @@ See the project's `README` for detailed information about installing `hie`.
The build script `install.hs` defines several targets using the `shake` build system. The targets are roughly:
* `hie-*`: builds and installs the `hie` binaries. Also renames the binaries to contain the correct version-number.
* `build-latest`: builds and installs `hie` for the latest available and supported `ghc` version.
* `build-data`: builds the hoogle-db required by `hie`
* `build`: builds and installs `hie` for the latest supported `ghc` version (like `build-latest`) and the hoogle-db (like `build-data`)
* `cabal-*`: execute the same task as the original target, but with `cabal` instead of `stack`
* `latest`: builds and installs `hie` for the latest available and supported `ghc` version.
* `data`: builds the hoogle-db required by `hie`
* `hie`: builds and installs `hie` for the latest supported `ghc` version (like `latest`) and the hoogle-db (like `data`)
Each `stack-*.yaml` contains references to packages in the submodules. Calling `stack` with one of those causes the build to fail if the submodules have not been initialized already. The file `shake.yaml` solves this issue invoking the `git` binary itself to update the submodules. Moreover, it specifies the correct version of `shake` and is used for installing all run-time dependencies such as `cabal` and `hoogle` if necessary.
Each `stack-*.yaml` contains references to packages in the submodules. Calling `stack` with one of those causes the build to fail if the submodules have not been initialized already. The file `shake.yaml` solves this issue invoking the `git` binary itself to update the submodules. Moreover, it specifies the correct version of `shake` and is used for installing all run-time dependencies such as `hoogle` if necessary.
### Run-time dependencies
`hie` depends on a correct environment in order to function properly:
* `cabal-install`: This dependency is required by `hie` to handle correctly projects that are not `stack` based. You can install an appropriate version using `stack` with the `stack-install-cabal` target.
* `cabal-install`: This dependency is required by `hie` to handle correctly projects that are not `stack` based. You can install it using one of the methods listed here: https://www.haskell.org/cabal/#install-upgrade
* The `hoogle` database: `hoogle generate` needs to be called with the most-recent `hoogle` version.
### Steps to build `hie`
@ -68,12 +67,21 @@ EOF
Then `hie` can be compiled for a specific GHC version:
* For cabal prior to 3.0.0.0
```bash
export GHCP=<path-to-ghc-binary>
cabal v2-install exe:hie -w $GHCP \
--write-ghc-environment-files=never --symlink-bindir=$HOME/.local/bin \
--overwrite-policy=always --reinstall
```
* For cabal 3.0.0.0 or newer
```bash
export GHCP=<path-to-ghc-binary>
cabal v2-install exe:hie -w $GHCP \
--write-ghc-environment-files=never --installdir=$HOME/.local/bin \
--overwrite-policy=always --reinstall
```
* For windows you will need cabal 3.0.0.0 and add the argument `--install-method=copy`
The final step is to configure the `hie` client to use a custom `hie-wrapper` script that enables the runtime options for profiling. Such a script could look like this:
@ -88,17 +96,13 @@ The final step is to configure the `hie` client to use a custom `hie-wrapper` sc
The `install.hs` script performs some checks to ensure that a correct installation is possible and provide meaningful error messages for known issues.
* `stack` needs to be up-to-date. Version `1.9.3` is required
* `stack` needs to be up-to-date. Version `2.1.1` is required
* `cabal` needs to be up-to-date. Version `3.0.0.0` is required for windows systems and `2.4.1.0` for other ones.
* `ghc-8.6.3` is broken on windows. Trying to install `hie-8.6.3` on windows is not possible.
* When the build fails, an error message, that suggests to remove `.stack-work` directory, is displayed.
### Tradeoffs
#### `stack` is a build dependency
Currently, `stack` is needed even if you run the script with `cabal` to get the path where install the binaries but there are plans to remove that dependency (see #1380).
#### run `install.hs` with `stack` installs a GHC before running
Before the code in `install.hs` can be executed, `stack` installs a `GHC`, depending on the `resolver` field in `shake.yaml`. This is necessary if `install.hs` should be completely functional right after a fresh `git clone` without further configuration.

View File

@ -16,6 +16,7 @@ cabal-version: >=2.0
flag pedantic
Description: Enable -Werror
Default: False
Manual: True
library
hs-source-dirs: src
@ -47,6 +48,7 @@ library
Haskell.Ide.Engine.Types
Haskell.Ide.Engine.Version
other-modules: Paths_haskell_ide_engine
autogen-modules: Paths_haskell_ide_engine
build-depends: Cabal >= 1.22
, Diff
-- , HaRe
@ -72,7 +74,7 @@ library
, haskell-lsp == 0.19.*
, haskell-lsp-types == 0.19.*
, haskell-src-exts
, hie-plugin-api
, hie-plugin-api >= 1.0
, hoogle >= 5.0.13
, hsimport
, hslogger
@ -108,16 +110,24 @@ library
executable hie
hs-source-dirs: app
main-is: MainHie.hs
other-modules: Paths_haskell_ide_engine
other-modules: Paths_haskell_ide_engine, RunTest
autogen-modules: Paths_haskell_ide_engine
build-depends: base
, containers
, data-default
, directory
, filepath
, ghc
, hie-bios
, haskell-ide-engine
, haskell-lsp
, haskell-lsp-types
, hie-plugin-api
, hslogger
, optparse-simple
, stm
, text
, yaml
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wredundant-constraints
-with-rtsopts=-T
if flag(pedantic)
@ -129,6 +139,7 @@ executable hie-wrapper
hs-source-dirs: app
main-is: HieWrapper.hs
other-modules: Paths_haskell_ide_engine
autogen-modules: Paths_haskell_ide_engine
build-depends: base
, directory
, filepath
@ -176,6 +187,7 @@ test-suite unit-test
hs-source-dirs: test/unit
main-is: Main.hs
other-modules: ApplyRefactPluginSpec
CabalHelperSpec
CodeActionsSpec
ContextSpec
DiffSpec
@ -192,6 +204,7 @@ test-suite unit-test
build-tool-depends: cabal-helper:cabal-helper-main, hspec-discover:hspec-discover
build-depends: QuickCheck
, aeson
, cabal-helper
, ghc
, base
, bytestring

View File

@ -13,14 +13,14 @@ import Distribution.Helper (Package, projectPackages, pUnits,
unChModuleName, Ex(..), ProjLoc(..),
QueryEnv, mkQueryEnv, runQuery,
Unit, unitInfo, uiComponents,
ChEntrypoint(..))
ChEntrypoint(..), UnitInfo(..))
import Distribution.Helper.Discover (findProjects, getDefaultDistDir)
import Data.Char (toLower)
import Data.Function ((&))
import Data.List (isPrefixOf, isInfixOf, sortOn, find)
import qualified Data.List.NonEmpty as NonEmpty
import Data.List.NonEmpty (NonEmpty)
import qualified Data.Map as M
import qualified Data.Map as Map
import Data.Maybe (listToMaybe, mapMaybe, isJust)
import Data.Ord (Down(..))
import Data.String (IsString(..))
@ -45,31 +45,36 @@ import System.Process (readCreateProcessWithExitCode, shell)
findLocalCradle :: FilePath -> IO Cradle
findLocalCradle fp = do
cradleConf <- BIOS.findCradle fp
case cradleConf of
Just yaml -> BIOS.loadCradle yaml
crdl <- case cradleConf of
Just yaml -> do
debugm $ "Found \"" ++ yaml ++ "\" for \"" ++ fp ++ "\""
BIOS.loadCradle yaml
Nothing -> cabalHelperCradle fp
logm $ "Module \"" ++ fp ++ "\" is loaded by Cradle: " ++ show crdl
return crdl
-- | Check if the given cradle is a stack cradle.
-- This might be used to determine the GHC version to use on the project.
-- If it is a stack-cradle, we have to use `stack path --compiler-exe`
-- If it is a stack-cradle, we have to use @"stack path --compiler-exe"@
-- otherwise we may ask `ghc` directly what version it is.
isStackCradle :: Cradle -> Bool
isStackCradle = (`elem` ["stack", "Cabal-Helper-Stack", "Cabal-Helper-Stack-None"])
. BIOS.actionName
. BIOS.cradleOptsProg
-- | Check if the given cradle is a cabal cradle.
-- | Check if the given cradle is a cabal cradle.
-- This might be used to determine the GHC version to use on the project.
-- If it is a stack-cradle, we have to use `stack path --compiler-exe`
-- otherwise we may ask `ghc` directly what version it is.
-- If it is a stack-cradle, we have to use @"stack path --compiler-exe"@
-- otherwise we may ask @ghc@ directly what version it is.
isCabalCradle :: Cradle -> Bool
isCabalCradle =
(`elem`
["cabal"
[ "cabal"
, "Cabal-Helper-Cabal-V1"
, "Cabal-Helper-Cabal-V2"
, "Cabal-Helper-Cabal-V1-Dir"
, "Cabal-Helper-Cabal-V2-Dir"
, "Cabal-Helper-Cabal-V2-None"
, "Cabal-Helper-Cabal-None"
]
)
@ -78,10 +83,10 @@ isCabalCradle =
-- | Execute @ghc@ that is based on the given cradle.
-- Output must be a single line. If an error is raised, e.g. the command
-- failed, a @Nothing@ is returned.
-- failed, a 'Nothing' is returned.
-- The exact error is written to logs.
--
-- E.g. for a stack cradle, we use `stack ghc` and for a cabal cradle
-- E.g. for a stack cradle, we use @stack ghc@ and for a cabal cradle
-- we are taking the @ghc@ that is on the path.
execProjectGhc :: Cradle -> [String] -> IO (Maybe String)
execProjectGhc crdl args = do
@ -143,7 +148,7 @@ getProjectGhcLibDir :: Cradle -> IO (Maybe FilePath)
getProjectGhcLibDir crdl =
execProjectGhc crdl ["--print-libdir"] >>= \case
Nothing -> do
logm "Could not obtain the libdir."
errorm "Could not obtain the libdir."
return Nothing
mlibdir -> return mlibdir
@ -164,6 +169,8 @@ This guessing has no guarantees and may change at any time.
=== Example:
Assume the following project structure:
@
/
Foo/
Foo.cabal
@ -175,33 +182,39 @@ Assume the following project structure:
B.cabal
src/
Lib2.hs
@
Assume the call @findCabalHelperEntryPoint "/Foo/B/src/Lib2.hs"@.
We now want to know to which project "/Foo/B/src/Lib2.hs" belongs to
Assume the call @findCabalHelperEntryPoint "\/Foo\/B\/src\/Lib2.hs"@.
We now want to know to which project "\/Foo\/B\/src\/Lib2.hs" belongs to
and what the projects root is. If we only do a naive search to find the
first occurrence of either "B.cabal", "stack.yaml", "cabal.project"
or "Foo.cabal", we might assume that the location of "B.cabal" marks
the project's root directory of which "/Foo/B/src/Lib2.hs" is part of.
the project's root directory of which "\/Foo\/B\/src\/Lib2.hs" is part of.
However, there is also a "cabal.project" and "stack.yaml" in the parent
directory, which add the package "B" as a package.
So, the compilation of the package "B", and the file "src/Lib2.hs" in it,
directory, which add the package @B@ as a package.
So, the compilation of the package @B@, and the file "src\/Lib2.hs" in it,
does not only depend on the definitions in "B.cabal", but also
on "stack.yaml" and "cabal.project".
The project root is therefore "/Foo/".
The project root is therefore "\/Foo\/".
Only if there is no "stack.yaml" or "cabal.project" in any of the ancestor
directories, it is safe to assume that "B.cabal" marks the root of the project.
Thus:
>>> findCabalHelperEntryPoint "/Foo/B/src/Lib2.hs
Just (Ex (ProjLocStackYaml { plStackYaml = "/Foo/"}))
or
>>> findCabalHelperEntryPoint "/Foo/B/src/Lib2.hs
>>> findCabalHelperEntryPoint "/Foo/B/src/Lib2.hs"
Just (Ex (ProjLocV2File { plProjectDirV2 = "/Foo/"}))
In the given example, it is not guaranteed which project type is found,
it is only guaranteed that it will not identify the project
as a cabal v1-project.
as a cabal v1-project. Note that with cabal-helper version (1.0),
by default a *.cabal file is identified as a 'ProjLocV2Dir' project.
The same issue as before exists and we look for a 'ProjLocV2File' or
'ProjLocStackYaml' before deciding that 'ProjLocV2Dir' marks the project root.
Note that this will not return any project types for which the corresponding
build tool is not on the PATH. This is "stack" and "cabal" for stack and cabal
@ -219,29 +232,33 @@ findCabalHelperEntryPoint fp = do
let supportedProjs = filter (\x -> supported x isStackInstalled isCabalInstalled) allProjs
debugm $ "These projects have the build tools installed: " ++ show (map (\(Ex x) -> show x) supportedProjs)
case filter (\p -> isCabalNewProject p || isStackProject p) supportedProjs of
case filter (\p -> isCabalV2FileProject p || isStackProject p) supportedProjs of
(x:_) -> return $ Just x
[] -> case filter isCabalOldProject supportedProjs of
[] -> case filter isCabalProject supportedProjs of
(x:_) -> return $ Just x
[] -> return Nothing
where
supported :: (Ex ProjLoc) -> Bool -> Bool -> Bool
supported :: Ex ProjLoc -> Bool -> Bool -> Bool
supported (Ex ProjLocStackYaml {}) stackInstalled _ = stackInstalled
supported (Ex ProjLocV2Dir {}) _ cabalInstalled = cabalInstalled
supported (Ex ProjLocV2File {}) _ cabalInstalled = cabalInstalled
supported (Ex ProjLocV1Dir {}) _ cabalInstalled = cabalInstalled
supported (Ex ProjLocV1CabalFile {}) _ cabalInstalled = cabalInstalled
isStackProject (Ex ProjLocStackYaml {}) = True
isStackProject _ = False
isStackProject :: Ex ProjLoc -> Bool
isStackProject (Ex ProjLocStackYaml {}) = True
isStackProject _ = False
isCabalNewProject (Ex ProjLocV2Dir {}) = True
isCabalNewProject (Ex ProjLocV2File {}) = True
isCabalNewProject _ = False
isCabalV2FileProject :: Ex ProjLoc -> Bool
isCabalV2FileProject (Ex ProjLocV2File {}) = True
isCabalV2FileProject _ = False
isCabalOldProject (Ex ProjLocV1Dir {}) = True
isCabalOldProject (Ex ProjLocV1CabalFile {}) = True
isCabalOldProject _ = False
isCabalProject :: Ex ProjLoc -> Bool
isCabalProject (Ex ProjLocV1CabalFile {}) = True
isCabalProject (Ex ProjLocV1Dir {}) = True
isCabalProject (Ex ProjLocV2File {}) = True
isCabalProject (Ex ProjLocV2Dir {}) = True
isCabalProject _ = False
{- | Given a FilePath, find the cradle the FilePath belongs to.
@ -330,6 +347,8 @@ required to load and compile the given file.
=== Mono-Repo
Assume the project structure:
@
/
Mono/
cabal.project
@ -340,6 +359,7 @@ Assume the project structure:
B/
B.cabal
Exe.hs
@
Currently, Haskell IDE Engine needs to know on startup which GHC version is
needed to compile the project. This information is needed to show warnings to
@ -347,14 +367,16 @@ the user if the GHC version on the project does not agree with the GHC version
that was used to compile Haskell IDE Engine.
Therefore, the function 'findLocalCradle' is invoked with a dummy FilePath,
such as "/Mono/Lib.hs". Since there will be no package that contains this
such as "\/Mono\/Lib.hs". Since there will be no package that contains this
dummy FilePath, the result will be a None-cradle.
Either
>>> findLocalCradle "/Mono/Lib.hs"
Cradle { cradleRootDir = "/Mono/", CradleAction { actionName = "Cabal-Helper-Stack-None", ..} }
or:
or
>>> findLocalCradle "/Mono/Lib.hs"
Cradle { cradleRootDir = "/Mono/", CradleAction { actionName = "Cabal-Helper-Cabal-V2-None", ..} }
@ -364,8 +386,9 @@ a 'cabal' project.
If we are trying to load the executable:
>>> findLocalCradle "/Mono/B/Exe.hs"
Cradle { cradleRootDir = "/Mono/B/", CradleAction { actionName = "Cabal-Helper-Cabal-V2", ..} }
Cradle { cradleRootDir = "/Mono/", CradleAction { actionName = "Cabal-Helper-Cabal-V2", ..} }
we will detect correctly the compiler options, by first finding the appropriate
package, followed by traversing the units in the package and finding the
@ -374,6 +397,8 @@ component that exposes the executable by FilePath.
=== No explicit executable folder
Assume the project structure:
@
/
Library/
cabal.project
@ -382,18 +407,21 @@ Assume the project structure:
src
Lib.hs
Exe.hs
@
There are different dependencies for the library "Lib.hs" and the
executable "Exe.hs". If we are trying to load the executable "src/Exe.hs"
executable "Exe.hs". If we are trying to load the executable "src\/Exe.hs"
we will correctly identify the executable unit, and correctly initialise
dependencies of "exe:Library".
It will be correct even if we load the unit "lib:Library" before
the "exe:Library" because the unit "lib:Library" does not expose
a module "Exe".
a module @"Exe"@.
=== Sub package
Assume the project structure:
@
/
Repo/
cabal.project
@ -404,12 +432,13 @@ Assume the project structure:
SubRepo
SubRepo.cabal
Lib2.hs
@
When we try to load "/Repo/SubRepo/Lib2.hs", we need to identify root
of the project, which is "/Repo/" but set the root directory of the cradle
responsible to load "/Repo/SubRepo/Lib2.hs" to "/Repo/SubRepo", since
When we try to load "\/Repo\/SubRepo\/Lib2.hs", we need to identify root
of the project, which is "\/Repo\/" but set the root directory of the cradle
responsible to load "\/Repo\/SubRepo\/Lib2.hs" to "\/Repo\/SubRepo", since
the compiler options obtained from Cabal-Helper are relative to the package
source directory, which is "/Repo/SubRepo".
source directory, which is "\/Repo\/SubRepo".
-}
cabalHelperCradle :: FilePath -> IO Cradle
@ -422,17 +451,24 @@ cabalHelperCradle file = do
return
Cradle { cradleRootDir = cwd
, cradleOptsProg =
CradleAction { actionName = "Cabal-Helper-None"
, runCradle = \_ _ -> return CradleNone
CradleAction { actionName = "Direct"
, runCradle = \_ _ ->
return
$ CradleSuccess
ComponentOptions
{ componentOptions = [file, fixImportDirs cwd "-i."]
, componentDependencies = []
}
}
}
Just (Ex proj) -> do
logm $ "Cabal-Helper decided to use: " ++ show proj
-- Find the root of the project based on project type.
let root = projectRootDir proj
-- Create a suffix for the cradle name.
-- Purpose is mainly for easier debugging.
let actionNameSuffix = projectSuffix proj
logm $ "Cabal-Helper dirs: " ++ show [root, file]
debugm $ "Cabal-Helper dirs: " ++ show [root, file]
let dist_dir = getDefaultDistDir proj
env <- mkQueryEnv proj dist_dir
packages <- runQuery projectPackages env
@ -468,63 +504,67 @@ cabalHelperCradle file = do
CradleAction { actionName =
"Cabal-Helper-" ++ actionNameSuffix
, runCradle = \_ fp -> cabalHelperAction
(Ex proj)
env
realPackage
normalisedPackageLocation
fp
}
}
where
-- | Fix occurrences of "-i." to "-i<cradle-root-dir>"
-- Flags obtained from cabal-helper are relative to the package
-- source directory. This is less resilient to using absolute paths,
-- thus, we fix it here.
fixImportDirs :: FilePath -> String -> String
fixImportDirs base_dir arg =
if "-i" `isPrefixOf` arg
then let dir = drop 2 arg
-- the flag "-i" has special meaning.
in if not (null dir) && isRelative dir then ("-i" ++ base_dir </> dir)
else arg
else arg
-- | Cradle Action to query for the ComponentOptions that are needed
-- to load the given FilePath.
-- This Function is not supposed to throw any exceptions and use
-- 'CradleLoadResult' to indicate errors.
cabalHelperAction :: Ex ProjLoc -- ^ Project location, can be used
-- to present build-tool
-- agnostic error messages.
-> QueryEnv v -- ^ Query Env created by 'mkQueryEnv'
-- with the appropriate 'distdir'
-> Package v -- ^ Package this cradle is part for.
-> FilePath -- ^ Root directory of the cradle
-- this action belongs to.
-> FilePath -- ^ FilePath to load, expected to be an absolute path.
-> IO (CradleLoadResult ComponentOptions)
cabalHelperAction proj env package root fp = do
-- Get all unit infos the given FilePath may belong to
let units = pUnits package
-- make the FilePath to load relative to the root of the cradle.
let relativeFp = makeRelative root fp
debugm $ "Relative Module FilePath: " ++ relativeFp
getComponent proj env (toList units) relativeFp
>>= \case
Right comp -> do
let fs' = getFlags comp
let fs = map (fixImportDirs root) fs'
let targets = getTargets comp relativeFp
let ghcOptions = fs ++ targets
debugm $ "Flags for \"" ++ fp ++ "\": " ++ show ghcOptions
debugm $ "Component Infos: " ++ show comp
return
$ CradleSuccess
ComponentOptions { componentOptions = ghcOptions
, componentDependencies = []
}
Left err -> return
$ CradleFail
$ CradleError
(ExitFailure 2)
err
-- | Fix occurrences of "-i." to "-i<cradle-root-dir>"
-- Flags obtained from cabal-helper are relative to the package
-- source directory. This is less resilient to using absolute paths,
-- thus, we fix it here.
fixImportDirs :: FilePath -> String -> String
fixImportDirs base_dir arg =
if "-i" `isPrefixOf` arg
then let dir = drop 2 arg
-- the flag "-i" has special meaning.
in if not (null dir) && isRelative dir then ("-i" ++ base_dir </> dir)
else arg
else arg
-- | cradle Action to query for the ComponentOptions that are needed
-- to load the given FilePath.
-- This Function is not supposed to throw any exceptions and use
-- 'CradleLoadResult' to indicate errors.
cabalHelperAction :: QueryEnv v -- ^ Query Env created by 'mkQueryEnv'
-- with the appropriate 'distdir'
-> Package v -- ^ Package this cradle is part for.
-> FilePath -- ^ Root directory of the cradle
-- this action belongs to.
-> FilePath -- ^ FilePath to load, expected to be an absolute path.
-> IO (CradleLoadResult ComponentOptions)
cabalHelperAction env package root fp = do
-- Get all unit infos the given FilePath may belong to
let units = pUnits package
-- make the FilePath to load relative to the root of the cradle.
let relativeFp = makeRelative root fp
debugm $ "Relative Module FilePath: " ++ relativeFp
getComponent env (toList units) relativeFp
>>= \case
Just comp -> do
let fs' = getFlags comp
let fs = map (fixImportDirs root) fs'
let targets = getTargets comp relativeFp
let ghcOptions = fs ++ targets
debugm $ "Flags for \"" ++ fp ++ "\": " ++ show ghcOptions
debugm $ "Component Infos: " ++ show comp
return
$ CradleSuccess
ComponentOptions { componentOptions = ghcOptions
, componentDependencies = []
}
Nothing -> return
$ CradleFail
$ CradleError
(ExitFailure 2)
["Could not obtain flags for " ++ fp]
-- | Get the component the given FilePath most likely belongs to.
-- Lazily ask units whether the given FilePath is part of one of their
@ -534,25 +574,96 @@ cabalHelperCradle file = do
-- The given FilePath must be relative to the Root of the project
-- the given units belong to.
getComponent
:: QueryEnv pt -> [Unit pt] -> FilePath -> IO (Maybe ChComponentInfo)
getComponent _env [] _fp = return Nothing
getComponent env (unit : units) fp =
try (runQuery (unitInfo unit) env) >>= \case
Left (e :: IOException) -> do
warningm $ "Catching and swallowing an IOException: " ++ show e
warningm
$ "The Exception was thrown in the context of finding"
++ " a component for \""
++ fp
++ "\" in the unit: "
++ show unit
getComponent env units fp
Right ui -> do
let components = M.elems (uiComponents ui)
debugm $ "Unit Info: " ++ show ui
case find (fp `partOfComponent`) components of
Nothing -> getComponent env units fp
comp -> return comp
:: forall pt. Ex ProjLoc -> QueryEnv pt -> [Unit pt] -> FilePath -> IO (Either [String] ChComponentInfo)
getComponent proj env unitCandidates fp = getComponent' [] [] unitCandidates >>=
\case
(tried, failed, Nothing) -> return (Left $ buildErrorMsg tried failed)
(_, _, Just comp) -> return (Right comp)
where
getComponent' :: [UnitInfo] -> [(Unit pt, IOException)] -> [Unit pt] -> IO ([UnitInfo], [(Unit pt, IOException)], Maybe ChComponentInfo)
getComponent' triedUnits failedUnits [] = return (triedUnits, failedUnits, Nothing)
getComponent' triedUnits failedUnits (unit : units) =
try (runQuery (unitInfo unit) env) >>= \case
Left (e :: IOException) -> do
warningm $ "Catching and swallowing an IOException: " ++ show e
warningm
$ "The Exception was thrown in the context of finding"
++ " a component for \""
++ fp
++ "\" in the unit: "
++ show unit
getComponent' triedUnits ((unit, e):failedUnits) units
Right ui -> do
let components = Map.elems (uiComponents ui)
debugm $ "Unit Info: " ++ show ui
case find (fp `partOfComponent`) components of
Nothing -> getComponent' (ui:triedUnits) failedUnits units
comp -> return (triedUnits, failedUnits, comp)
buildErrorMsg :: [UnitInfo] -> [(Unit pt, IOException)] -> [String]
buildErrorMsg triedUnits failedUnits =
concat
[ [ "Could not obtain flags for: \"" ++ fp ++ "\"."
, ""
]
, concat
[ concat
[ [ "This module was not part of any component we are aware of."
, ""
]
, concatMap ppShowUnitInfo triedUnits
, [ ""
, ""
]
, if isStackProject proj
then stackSpecificInstructions
else cabalSpecificInstructions
]
| not (null triedUnits)
]
, concat
[
[ "We could not build all components."
, "If one of these components exposes this Module, make sure they compile."
, "You can try to invoke the commands yourself."
, "The following commands failed:"
]
++ concatMap (ppShowIOException . snd) failedUnits
| not (null failedUnits)
]
]
stackSpecificInstructions :: [String]
stackSpecificInstructions =
[ "To expose a module, refer to:"
, "https://docs.haskellstack.org/en/stable/GUIDE/"
, "If you are using `package.yaml` then you don't have to manually expose modules."
, "Maybe you didn't set the source directories for your project correctly."
]
cabalSpecificInstructions :: [String]
cabalSpecificInstructions =
[ "To expose a module, refer to:"
, "https://www.haskell.org/cabal/users-guide/developing-packages.html"
, ""
]
ppShowUnitInfo :: UnitInfo -> [String]
ppShowUnitInfo u =
u
& uiComponents
& Map.toList
& map
(\(name, info) ->
"Component: " ++ show name ++ " with source directory: " ++ show (ciSourceDirs info)
)
ppShowIOException :: IOException -> [String]
ppShowIOException e =
[ ""
, show e
]
-- | Check whether the given FilePath is part of the Component.
-- A FilePath is part of the Component if and only if:
@ -734,8 +845,8 @@ ancestors dir
where
subdir = takeDirectory dir
-- | Assuming a FilePath "src/Lib/Lib.hs" and a list of directories
-- such as ["src", "app"], returns either the given FilePath
-- | Assuming a FilePath @"src\/Lib\/Lib.hs"@ and a list of directories
-- such as @["src", "app"]@, returns either the given FilePath
-- with a matching directory stripped away.
-- If there are multiple matches, e.g. multiple directories are a prefix
-- of the given FilePath, return the first match in the list.
@ -767,4 +878,3 @@ cradleDisplay cradle = fromString result
| "multi" `isInfixOf` name = "Multi Component project"
| otherwise = "project"
name = map toLower $ BIOS.actionName (BIOS.cradleOptsProg cradle)

View File

@ -4,7 +4,6 @@
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
@ -24,6 +23,7 @@ module Haskell.Ide.Engine.ModuleCache
, cacheInfoNoClear
, runActionWithContext
, ModuleCache(..)
, PublishDiagnostics
) where
@ -32,26 +32,28 @@ import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Control
import Control.Monad.Trans.Free
import qualified Data.Aeson as Aeson
import qualified Data.ByteString.Char8 as B
import Data.Dynamic (toDyn, fromDynamic, Dynamic)
import Data.Generics (Proxy(..), TypeRep, typeRep, typeOf)
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.SortedList as SL
import qualified Data.Trie.Convenience as T
import qualified Data.Trie as T
import qualified Data.Text as Text
import Data.Typeable (Typeable)
import qualified Data.Yaml as Yaml
import System.Directory
import qualified GHC
import qualified HscMain as GHC
import qualified HIE.Bios as Bios
import qualified HIE.Bios.Ghc.Api as Bios
import qualified Data.Aeson as Aeson
import qualified Data.Trie.Convenience as T
import qualified Data.Trie as T
import qualified Data.Text as Text
import qualified Data.Yaml as Yaml
import qualified HIE.Bios as BIOS
import qualified HIE.Bios.Ghc.Api as BIOS
import qualified Data.ByteString.Char8 as B
import qualified Language.Haskell.LSP.Types as J
import qualified Language.Haskell.LSP.Diagnostics as J
import Haskell.Ide.Engine.ArtifactMap
import Haskell.Ide.Engine.Cradle (findLocalCradle, cradleDisplay)
import Haskell.Ide.Engine.TypeMap
@ -68,6 +70,9 @@ modifyCache :: (HasGhcModuleCache m) => (GhcModuleCache -> GhcModuleCache) -> m
modifyCache f = modifyModuleCache f
-- ---------------------------------------------------------------------
type PublishDiagnostics = J.NormalizedUri -> J.TextDocumentVersion -> J.DiagnosticsBySource -> IO ()
-- | Run the given action in context and initialise a session with hie-bios.
-- If a context is given, the context is used to initialise a session for GHC.
-- The project "hie-bios" is used to find a Cradle and setup a GHC session
@ -88,22 +93,23 @@ modifyCache f = modifyModuleCache f
-- though we know nothing about the file.
-- 2. Return the default value for the specific action.
runActionWithContext :: (MonadIde m, GHC.GhcMonad m, HasGhcModuleCache m, MonadBaseControl IO m)
=> GHC.DynFlags
=> PublishDiagnostics
-> GHC.DynFlags
-> Maybe FilePath -- ^ Context for the Action
-> a -- ^ Default value for none cradle
-> m a -- ^ Action to execute
-> m (IdeResult a) -- ^ Result of the action or error in
-- the context initialisation.
runActionWithContext _df Nothing _def action =
runActionWithContext _pub _df Nothing _def action =
-- Cradle with no additional flags
-- dir <- liftIO $ getCurrentDirectory
--This causes problems when loading a later package which sets the
--packageDb
-- loadCradle df (BIOS.defaultCradle dir)
-- loadCradle df (Bios.defaultCradle dir)
fmap IdeResultOk action
runActionWithContext df (Just uri) def action = do
runActionWithContext publishDiagnostics df (Just uri) def action = do
mcradle <- getCradle uri
loadCradle df mcradle def action
loadCradle publishDiagnostics df mcradle def action
-- ---------------------------------------------------------------------
@ -114,17 +120,18 @@ runActionWithContext df (Just uri) def action = do
-- to set up the Session, including downloading all dependencies of a Cradle.
loadCradle :: forall a m . (MonadIde m, HasGhcModuleCache m, GHC.GhcMonad m
, MonadBaseControl IO m)
=> GHC.DynFlags
=> PublishDiagnostics
-> GHC.DynFlags
-> LookupCradleResult
-> a
-> m a
-> m (IdeResult a)
loadCradle _ ReuseCradle _def action = do
loadCradle _ _ ReuseCradle _def action = do
-- Since we expect this message to show up often, only show in debug mode
debugm "Reusing cradle"
IdeResultOk <$> action
loadCradle _iniDynFlags (LoadCradle (CachedCradle crd env)) _def action = do
loadCradle _ _iniDynFlags (LoadCradle (CachedCradle crd env)) _def action = do
-- Reloading a cradle happens on component switch
logm $ "Switch to cradle: " ++ show crd
-- Cache the existing cradle
@ -133,7 +140,7 @@ loadCradle _iniDynFlags (LoadCradle (CachedCradle crd env)) _def action = do
setCurrentCradle crd
IdeResultOk <$> action
loadCradle iniDynFlags (NewCradle fp) def action = do
loadCradle publishDiagnostics iniDynFlags (NewCradle fp) def action = do
-- If this message shows up a lot in the logs, it is an indicator for a bug
logm $ "New cradle: " ++ fp
-- Cache the existing cradle
@ -156,34 +163,49 @@ loadCradle iniDynFlags (NewCradle fp) def action = do
where
-- | Initialise the given cradle. This might fail and return an error via `IdeResultFail`.
-- Reports its progress to the client.
initialiseCradle :: (MonadIde m, HasGhcModuleCache m, GHC.GhcMonad m, MonadBaseControl IO m)
=> BIOS.Cradle -> (Progress -> IO ()) -> m (IdeResult a)
initialiseCradle :: (MonadIde m, HasGhcModuleCache m, GHC.GhcMonad m)
=> Bios.Cradle -> (Progress -> IO ()) -> m (IdeResult a)
initialiseCradle cradle f = do
res <- BIOS.initializeFlagsWithCradleWithMessage (Just (toMessager f)) fp cradle
res <- Bios.initializeFlagsWithCradleWithMessage (Just (toMessager f)) fp cradle
case res of
BIOS.CradleNone ->
Bios.CradleNone ->
-- Note: The action is not run if we are in the none cradle, we
-- just pretend the file doesn't exist.
return $ IdeResultOk def
BIOS.CradleFail err -> do
logm $ "GhcException on cradle initialisation: " ++ show err
Bios.CradleFail (Bios.CradleError code msg) -> do
warningm $ "Fail on cradle initialisation: (" ++ show code ++ ")" ++ show msg
-- Send a detailed diagnostic to the user.
let normalizedUri = J.toNormalizedUri (filePathToUri fp)
sev = Just DsError
range = Range (Position 0 0) (Position 1 0)
msgTxt =
[ "Fail on initialisation for \"" <> Text.pack fp <> "\"."
] <> map Text.pack msg
source = Just "bios"
diag = Diagnostic range sev Nothing source (Text.unlines msgTxt) Nothing
liftIO $ publishDiagnostics normalizedUri Nothing
(Map.singleton source (SL.singleton diag))
return $ IdeResultFail $ IdeError
{ ideCode = OtherError
, ideMessage = Text.pack $ show err
, ideMessage = Text.unwords (take 2 msgTxt)
, ideInfo = Aeson.Null
}
BIOS.CradleSuccess init_session -> do
Bios.CradleSuccess init_session -> do
-- Note that init_session contains a Hook to 'f'.
-- So, it can still provide Progress Reports.
-- Therefore, invocation of 'init_session' must happen
-- while 'f' is still valid.
liftIO (GHC.newHscEnv iniDynFlags) >>= GHC.setSession
liftIO $ setCurrentDirectory (BIOS.cradleRootDir cradle)
liftIO $ setCurrentDirectory (Bios.cradleRootDir cradle)
let onGhcError = return . Left
let onSourceError srcErr = do
logm $ "Source error on cradle initialisation: " ++ show srcErr
return $ Right BIOS.Failed
return $ Right Bios.Failed
-- We continue setting the cradle in case the file has source errors
-- cause they will be reported to user by diagnostics
init_res <- gcatches
@ -202,12 +224,12 @@ loadCradle iniDynFlags (NewCradle fp) def action = do
-- it on a save whilst there are errors. Subsequent loads won't
-- be that slow, even though the cradle isn't cached because the
-- `.hi` files will be saved.
Right BIOS.Succeeded -> do
Right Bios.Succeeded -> do
setCurrentCradle cradle
logm "Cradle set succesfully"
IdeResultOk <$> action
Right BIOS.Failed -> do
Right Bios.Failed -> do
setCurrentCradle cradle
logm "Cradle did not load succesfully"
IdeResultOk <$> action
@ -217,7 +239,7 @@ loadCradle iniDynFlags (NewCradle fp) def action = do
-- that belong to this cradle.
-- If the cradle does not load any module, it is responsible for an empty
-- list of Modules.
setCurrentCradle :: (HasGhcModuleCache m, GHC.GhcMonad m) => BIOS.Cradle -> m ()
setCurrentCradle :: (HasGhcModuleCache m, GHC.GhcMonad m) => Bios.Cradle -> m ()
setCurrentCradle cradle = do
mg <- GHC.getModuleGraph
let ps = mapMaybe (GHC.ml_hs_file . GHC.ms_location) (mgModSummaries mg)
@ -230,7 +252,7 @@ setCurrentCradle cradle = do
-- for.
-- Via 'lookupCradle' it can be checked if a given FilePath is managed by
-- a any Cradle that has already been loaded.
cacheCradle :: (HasGhcModuleCache m, GHC.GhcMonad m) => ([FilePath], BIOS.Cradle) -> m ()
cacheCradle :: (HasGhcModuleCache m, GHC.GhcMonad m) => ([FilePath], Bios.Cradle) -> m ()
cacheCradle (ds, c) = do
env <- GHC.getSession
let cc = CachedCradle c env

View File

@ -1,6 +1,7 @@
name: hie-plugin-api
version: 0.14.0.0
version: 1.0.0.0
synopsis: Haskell IDE API for plugin communication
description: Please see README.md
license: BSD3
license-file: LICENSE
author: Many,TBD when we release
@ -15,6 +16,7 @@ cabal-version: >=2.0
flag pedantic
Description: Enable -Werror
Default: False
Manual: True
library
exposed-modules:
@ -57,6 +59,7 @@ library
, monad-control
, mtl
, process
, sorted-list
, stm
, syb
, text

35
hie.yaml.cbl Normal file
View File

@ -0,0 +1,35 @@
# This is a sample hie.yaml file for opening haskell-ide-engine in
# hie, using cabal as the build system.
# To use is, copy it to a file called 'hie.yaml'
cradle:
cabal:
- path: "./hie-plugin-api/Haskell"
component: "lib:hie-plugin-api"
- path: "./test/dispatcher/"
component: "haskell-ide-engine:dispatcher-test"
- path: "./test/functional/"
component: "haskell-ide-engine:func-test"
- path: "./test/unit/"
component: "haskell-ide-engine:unit-test"
- path: "./test/plugin-dispatcher/"
component: "haskell-ide-engine:plugin-dispatcher-test"
- path: "./test/wrapper/"
component: "haskell-ide-engine:wrapper-test"
- path: "./test/utils/"
component: "haskell-ide-engine:hie-test-utils"
- path: "./app/MainHie.hs"
component: "haskell-ide-engine:hie"
- path: "./app/HieWrapper.hs"
component: "haskell-ide-engine:hie-wrapper"
- path: "./"
component: "lib:haskell-ide-engine"

36
hie.yaml.stack Normal file
View File

@ -0,0 +1,36 @@
# This is a sample hie.yaml file for opening haskell-ide-engine in
# hie, using stack as the build system.
# To use is, copy it to a file called 'hie.yaml'
cradle:
stack:
- path: "./hie-plugin-api/"
component: "hie-plugin-api:lib"
- path: "./test/dispatcher/"
component: "haskell-ide-engine:test:dispatcher-test"
- path: "./test/functional/"
component: "haskell-ide-engine:test:func-test"
- path: "./test/unit/"
component: "haskell-ide-engine:test:unit-test"
- path: "./test/plugin-dispatcher/"
component: "haskell-ide-engine:test:plugin-dispatcher-test"
- path: "./test/wrapper/"
component: "haskell-ide-engine:test:wrapper-test"
# This target does not currently work (stack 2.1.3)
- path: "./test/utils/"
component: "haskell-ide-engine:lib:hie-test-utils"
- path: "./app/MainHie.hs"
component: "haskell-ide-engine:exe:hie"
- path: "./app/HieWrapper.hs"
component: "haskell-ide-engine:exe:hie-wrapper"
- path: "./"
component: "haskell-ide-engine:lib"

View File

@ -31,6 +31,8 @@ library
if flag(run-from-stack)
cpp-options: -DRUN_FROM_STACK
else
build-depends: cabal-install-parsers
flag run-from-stack
description: Inform the application that it is run from stack

View File

@ -1,3 +1,5 @@
{-# LANGUAGE CPP #-}
module Cabal where
import Development.Shake
@ -15,16 +17,26 @@ import System.Directory ( findExecutable
import Version
import Print
import Env
import Stack
import Data.Functor.Identity
#if RUN_FROM_STACK
import Control.Exception ( throwIO )
#else
import Cabal.Config
#endif
getInstallDir :: IO FilePath
#if RUN_FROM_STACK
-- we should never hit this codepath
getInstallDir = throwIO $ userError "Stack and cabal should never be mixed"
#else
getInstallDir = runIdentity . cfgInstallDir <$> readConfig
#endif
execCabal :: CmdResult r => [String] -> Action r
execCabal = execCabalWithOriginalPath
execCabal = command [] "cabal"
execCabal_ :: [String] -> Action ()
execCabal_ = execCabalWithOriginalPath
execCabalWithOriginalPath :: CmdResult r => [String] -> Action r
execCabalWithOriginalPath = withoutStackCachedBinaries . (command [] "cabal")
execCabal_ = command [] "cabal"
cabalBuildData :: Action ()
cabalBuildData = do
@ -39,19 +51,9 @@ getGhcPathOfOrThrowError versionNumber =
error (ghcVersionNotFoundFailMsg versionNumber)
Just p -> return p
cabalBuildHie :: VersionNumber -> Action ()
cabalBuildHie versionNumber = do
ghcPath <- getGhcPathOfOrThrowError versionNumber
execCabal_
[ "v2-build"
, "-w", ghcPath
, "--write-ghc-environment-files=never"
, "--max-backjumps=5000"
, "--disable-tests"]
cabalInstallHie :: VersionNumber -> Action ()
cabalInstallHie versionNumber = do
localBin <- getLocalBin
localBin <- liftIO $ getInstallDir
cabalVersion <- getCabalVersion
ghcPath <- getGhcPathOfOrThrowError versionNumber
@ -65,6 +67,7 @@ cabalInstallHie versionNumber = do
, "-w", ghcPath
, "--write-ghc-environment-files=never"
, installDirOpt, localBin
, "--max-backjumps=5000"
, "exe:hie"
, "--overwrite-policy=always"
]
@ -84,18 +87,6 @@ cabalInstallHie versionNumber = do
++ minorVerExe
++ " to " ++ localBin
installCabalWithStack :: Action ()
installCabalWithStack = do
-- try to find existing `cabal` executable with appropriate version
mbc <- withoutStackCachedBinaries (liftIO (findExecutable "cabal"))
case mbc of
Just c -> do
cabalVersion <- checkCabal
printLine $ "There is already a cabal executable in $PATH with the required minimum version: " ++ cabalVersion
-- install `cabal-install` if not already installed
Nothing -> execStackShake_ ["install", "cabal-install"]
checkCabal_ :: Action ()
checkCabal_ = checkCabal >> return ()

View File

@ -78,8 +78,7 @@ helpMessage versions@BuildableVersions {..} = do
[emptyTarget]
[ generalTargets
, defaultTargets
, stackTargets
, cabalTargets
, if isRunFromCabal then [cabalGhcsTarget] else []
, [macosIcuTarget]
]
@ -89,22 +88,6 @@ helpMessage versions@BuildableVersions {..} = do
defaultTargets = [buildTarget, buildLatestTarget, buildDataTarget]
++ map hieTarget (getDefaultBuildSystemVersions versions)
stackTargets =
[ stackTarget buildTarget
, stackTarget buildLatestTarget
, stackTarget buildDataTarget
]
++ (if isRunFromStack then [stackTarget installCabalTarget] else [])
++ map (stackTarget . hieTarget) stackVersions
cabalTargets =
[ cabalGhcsTarget
, cabalTarget buildTarget
, cabalTarget buildLatestTarget
, cabalTarget buildDataTarget
]
++ map (cabalTarget . hieTarget) cabalVersions
-- | Empty target. Purpose is to introduce a newline between the targets
emptyTarget :: (String, String)
emptyTarget = ("", "")
@ -112,29 +95,19 @@ emptyTarget = ("", "")
templateTarget :: (String, String)
templateTarget = ("<target>", "")
targetWithBuildSystem :: String -> TargetDescription -> TargetDescription
targetWithBuildSystem system (target, description) =
(system ++ "-" ++ target, description ++ "; with " ++ system)
stackTarget :: TargetDescription -> TargetDescription
stackTarget = targetWithBuildSystem "stack"
cabalTarget :: TargetDescription -> TargetDescription
cabalTarget = targetWithBuildSystem "cabal"
hieTarget :: String -> TargetDescription
hieTarget version =
("hie-" ++ version, "Builds hie for GHC version " ++ version)
buildTarget :: TargetDescription
buildTarget = ("build", "Build hie with the latest available GHC and the data files")
buildTarget = ("hie", "Build hie with the latest available GHC and the data files")
buildLatestTarget :: TargetDescription
buildLatestTarget = ("build-latest", "Build hie with the latest available GHC")
buildLatestTarget = ("latest", "Build hie with the latest available GHC")
buildDataTarget :: TargetDescription
buildDataTarget =
("build-data", "Get the required data-files for `hie` (Hoogle DB)")
("data", "Get the required data-files for `hie` (Hoogle DB)")
-- special targets
@ -146,25 +119,6 @@ helpTarget = ("help", "Show help message including all targets")
cabalGhcsTarget :: TargetDescription
cabalGhcsTarget =
( "cabal-ghcs"
( "ghcs"
, "Show all GHC versions that can be installed via `cabal-build`."
)
installCabalTarget :: TargetDescription
installCabalTarget =
( "install-cabal"
, "Install the cabal executable. It will install the required minimum version for hie (currently "
++ versionToString requiredCabalVersion
++ ") if it isn't already present in $PATH"
)
-- | Creates a message of the form "a, b, c and d", where a,b,c,d are GHC versions.
-- If there is no GHC in the list of `hieVersions`
allVersionMessage :: [String] -> String
allVersionMessage wordList = case wordList of
[] -> ""
[a] -> show a
(a : as) ->
let msg = intersperse ", " wordList
lastVersion = last msg
in concat $ init (init msg) ++ [" and ", lastVersion]

View File

@ -48,89 +48,64 @@ defaultMain = do
-- used for cabal-based targets
ghcPaths <- findInstalledGhcs
let ghcVersions = map fst ghcPaths
let cabalVersions = map fst ghcPaths
-- used for stack-based targets
hieVersions <- getHieVersions
stackVersions <- getHieVersions
let versions = BuildableVersions { stackVersions = hieVersions
, cabalVersions = ghcVersions
}
let versions = if isRunFromStack then stackVersions else cabalVersions
let latestVersion = last hieVersions
let toolsVersions = BuildableVersions stackVersions cabalVersions
putStrLn $ "run from: " ++ buildSystem
let latestVersion = last versions
shakeArgs shakeOptions { shakeFiles = "_build" } $ do
want ["short-help"]
-- general purpose targets
phony "submodules" updateSubmodules
phony "cabal" installCabalWithStack
phony "short-help" shortHelpMessage
phony "all" shortHelpMessage
phony "help" (helpMessage versions)
phony "check-stack" checkStack
phony "check-cabal" checkCabal_
phony "cabal-ghcs" $ do
let
msg =
"Found the following GHC paths: \n"
++ unlines
(map (\(version, path) -> "ghc-" ++ version ++ ": " ++ path)
ghcPaths
)
printInStars msg
-- default-targets
phony "build" $ need [buildSystem ++ "-build"]
phony "build-latest" $ need [buildSystem ++ "-build-latest"]
phony "build-data" $ need [buildSystem ++ "-build-data"]
forM_
(getDefaultBuildSystemVersions versions)
(\version ->
phony ("hie-" ++ version) $ need [buildSystem ++ "-hie-" ++ version]
)
-- stack specific targets
when isRunFromStack (phony "stack-install-cabal" (need ["cabal"]))
phony "stack-build-latest" (need ["stack-hie-" ++ last hieVersions])
phony "stack-build" (need ["build-data", "stack-build-latest"])
phony "help" (helpMessage toolsVersions)
phony "stack-build-data" $ do
phony "check" (if isRunFromStack then checkStack else checkCabal_)
phony "data" $ do
need ["submodules"]
need ["check-stack"]
stackBuildData
need ["check"]
if isRunFromStack then stackBuildData else cabalBuildData
forM_
hieVersions
(\version -> phony ("stack-hie-" ++ version) $ do
versions
(\version -> phony ("hie-" ++ version) $ do
need ["submodules"]
need ["check-stack"]
stackBuildHie version
stackInstallHie version
need ["check"]
if isRunFromStack then do
stackBuildHie version
stackInstallHie version
else
cabalInstallHie version
)
phony "latest" (need ["hie-" ++ latestVersion])
phony "hie" (need ["data", "latest"])
-- cabal specific targets
phony "cabal-build-latest" (need ["cabal-hie-" ++ last ghcVersions])
phony "cabal-build" (need ["build-data", "cabal-build-latest"])
phony "cabal-build-data" $ do
need ["submodules"]
need ["cabal"]
cabalBuildData
forM_
ghcVersions
(\version -> phony ("cabal-hie-" ++ version) $ do
need ["submodules"]
need ["cabal"]
cabalBuildHie version
cabalInstallHie version
)
when isRunFromCabal $ do
phony "ghcs" $ do
let
msg =
"Found the following GHC paths: \n"
++ unlines
(map (\(version, path) -> "ghc-" ++ version ++ ": " ++ path)
ghcPaths
)
printInStars msg
-- macos specific targets
phony "icu-macos-fix"
(need ["icu-macos-fix-install"] >> need ["icu-macos-fix-build"])
phony "icu-macos-fix-install" (command_ [] "brew" ["install", "icu4c"])
phony "icu-macos-fix-build" $ mapM_ buildIcuMacosFix hieVersions
phony "icu-macos-fix-build" $ mapM_ buildIcuMacosFix versions
buildIcuMacosFix :: VersionNumber -> Action ()

View File

@ -31,11 +31,6 @@ stackInstallHie versionNumber = do
copyFile (localBinDir </> hie)
(localBinDir </> "hie-" ++ dropExtension versionNumber <.> exe)
buildCopyCompilerTool :: VersionNumber -> Action ()
buildCopyCompilerTool versionNumber =
execStackWithGhc_ versionNumber ["build", "--copy-compiler-tool"]
-- | check `stack` has the required version
checkStack :: Action ()
checkStack = do
@ -101,30 +96,3 @@ stackBuildFailMsg =
++ "If this does not work, open an issue at \n"
++ "\thttps://github.com/haskell/haskell-ide-engine"
-- |Run actions without the stack cached binaries
withoutStackCachedBinaries :: Action a -> Action a
withoutStackCachedBinaries action = do
mbPath <- liftIO (lookupEnv "PATH")
case (mbPath, isRunFromStack) of
(Just paths, True) -> do
snapshotDir <- trimmedStdout <$> execStackShake ["path", "--snapshot-install-root"]
localInstallDir <- trimmedStdout <$> execStackShake ["path", "--local-install-root"]
let cacheBinPaths = [snapshotDir </> "bin", localInstallDir </> "bin"]
let origPaths = removePathsContaining cacheBinPaths paths
liftIO (setEnv "PATH" origPaths)
a <- action
liftIO (setEnv "PATH" paths)
return a
otherwise -> action
where removePathsContaining strs path =
joinPaths (filter (not . containsAny) (splitSearchPath path))
where containsAny p = any (`isInfixOf` p) strs
joinPaths = intercalate [searchPathSeparator]

View File

@ -6,11 +6,13 @@ import Options.Applicative.Simple
data GlobalOpts = GlobalOpts
{ optDebugOn :: Bool
, optLogFile :: Maybe String
, _optLsp :: Bool -- Kept for a while, to not break legacy clients
, optLsp :: Bool
, projectRoot :: Maybe String
, optBiosVerbose :: Bool
, optCaptureFile :: Maybe FilePath
, optExamplePlugin :: Bool
, optDryRun :: Bool
, optFiles :: [FilePath]
} deriving (Show)
globalOptsParser :: Parser GlobalOpts
@ -26,9 +28,9 @@ globalOptsParser = GlobalOpts
<> metavar "LOGFILE"
<> help "File to log to, defaults to stdout"
))
<*> flag True True
<*> flag False True
( long "lsp"
<> help "Legacy flag, no longer used, to enable LSP mode. Not required.")
<> help "Start HIE as an LSP server. Otherwise it dumps debug info to stdout")
<*> optional (strOption
( long "project-root"
<> short 'r'
@ -53,3 +55,13 @@ globalOptsParser = GlobalOpts
<*> switch
( long "example"
<> help "Enable Example2 plugin. Useful for developers only")
<*> flag False True
( long "dry-run"
<> help "Perform a dry-run of loading files. Only searches for Haskell source files to load. Does nothing if run as LSP server."
)
<*> many
( argument str
( metavar "FILES..."
<> help "Directories and Filepaths to load. Does nothing if run as LSP server.")
)

View File

@ -330,7 +330,7 @@ codeActionProvider plId docId _ context = IdeResultOk <$> hlintActions
mkHlintAction diag@(LSP.Diagnostic (LSP.Range start _) _s (Just (LSP.StringValue code)) (Just "hlint") m _) =
Just . codeAction <$> mkLspCommand plId "applyOne" title (Just args)
where
codeAction cmd = LSP.CodeAction title (Just LSP.CodeActionRefactor) (Just (LSP.List [diag])) Nothing (Just cmd)
codeAction cmd = LSP.CodeAction title (Just LSP.CodeActionQuickFix) (Just (LSP.List [diag])) Nothing (Just cmd)
title = "Apply hint:" <> head (T.lines m)
-- need 'file', 'start_pos' and hint title (to distinguish between alternative suggestions at the same location)
args = [toJSON (AOP (docId ^. LSP.uri) start code)]

View File

@ -148,11 +148,12 @@ runScheduler
-- ^ A handler to run the requests' callback in your monad of choosing.
-> Core.LspFuncs Config
-- ^ The LspFuncs provided by haskell-lsp.
-> PublishDiagnostics
-> Maybe Bios.Cradle
-- ^ Context in which the ghc thread is executed.
-- Neccessary to obtain the libdir, for example.
-> IO ()
runScheduler Scheduler {..} errorHandler callbackHandler lf mcradle = do
runScheduler Scheduler {..} errorHandler callbackHandler lf pubDiags mcradle = do
let dEnv = DispatcherEnv
{ cancelReqsTVar = requestsToCancel
, wipReqsTVar = requestsInProgress
@ -171,7 +172,7 @@ runScheduler Scheduler {..} errorHandler callbackHandler lf mcradle = do
Just crdl -> Bios.getProjectGhcLibDir crdl
let runGhcDisp = runIdeGhcM mlibdir plugins lf stateVar $
ghcDispatcher dEnv errorHandler callbackHandler ghcChanOut
ghcDispatcher dEnv errorHandler pubDiags callbackHandler ghcChanOut
runIdeDisp = runIdeM plugins lf stateVar $
ideDispatcher dEnv errorHandler callbackHandler ideChanOut
@ -325,10 +326,11 @@ ghcDispatcher
:: forall void m
. DispatcherEnv
-> ErrorHandler
-> PublishDiagnostics
-> CallbackHandler m
-> Channel.OutChan (GhcRequest m)
-> IdeGhcM void
ghcDispatcher env@DispatcherEnv { docVersionTVar } errorHandler callbackHandler pin
ghcDispatcher env@DispatcherEnv { docVersionTVar } errorHandler publishDiagnostics callbackHandler pin
= do
iniDynFlags <- getSessionDynFlags
forever $ do
@ -342,13 +344,13 @@ ghcDispatcher env@DispatcherEnv { docVersionTVar } errorHandler callbackHandler
runner :: a -> IdeGhcM a -> IdeGhcM (IdeResult a)
runner a act = case context of
Nothing -> runActionWithContext iniDynFlags Nothing a act
Nothing -> runActionWithContext publishDiagnostics iniDynFlags Nothing a act
Just uri -> case uriToFilePath uri of
Just fp -> runActionWithContext iniDynFlags (Just fp) a act
Just fp -> runActionWithContext publishDiagnostics iniDynFlags (Just fp) a act
Nothing -> do
debugm
"ghcDispatcher:Got malformed uri, running action with default context"
runActionWithContext iniDynFlags Nothing a act
runActionWithContext publishDiagnostics iniDynFlags Nothing a act
let
runWithCallback = do

View File

@ -186,7 +186,12 @@ run scheduler _origDir plugins captureFp = flip E.catches handlers $ do
Left (e :: Yaml.ParseException) -> do
logm $ "Failed to parse `hie.yaml`: " ++ show e
sf $ NotShowMessage $ fmServerShowMessageNotification J.MtError ("Couldn't parse hie.yaml: \n" <> T.pack (show e))
sf $ NotShowMessage
$ fmServerShowMessageNotification
J.MtError
( "Couldn't parse hie.yaml: \n"
<> T.pack (Yaml.prettyPrintParseException e)
)
let mcradle = case cradleRes of
Left _ -> Nothing
@ -196,23 +201,27 @@ run scheduler _origDir plugins captureFp = flip E.catches handlers $ do
-- We launch the dispatcher after that so that the default cradle is
-- recognized properly by ghc-mod
flip labelThread "scheduler" =<<
(forkIO (
Scheduler.runScheduler scheduler errorHandler callbackHandler lf mcradle
`E.catch` \(e :: E.SomeException) ->
(errorm $ "Scheduler thread exited unexpectedly: " ++ show e)
))
forkIO
( Scheduler.runScheduler scheduler errorHandler callbackHandler lf (publishDiagnostics' lf) mcradle
`E.catch`
\(e :: E.SomeException) ->
errorm $ "Scheduler thread exited unexpectedly: " ++ show e
)
flip labelThread "reactor" =<<
(forkIO (
reactorFunc
`E.catch` \(e :: E.SomeException) ->
(errorm $ "Reactor thread exited unexpectedly: " ++ show e)
))
forkIO
( reactorFunc
`E.catch`
\(e :: E.SomeException) ->
errorm $ "Reactor thread exited unexpectedly: " ++ show e
)
flip labelThread "diagnostics" =<<
(forkIO (
diagnosticsQueue tr
`E.catch` \(e :: E.SomeException) ->
(errorm $ "Diagnostic thread exited unexpectedly: " ++ show e)
))
forkIO
( diagnosticsQueue tr
`E.catch`
\(e :: E.SomeException) ->
errorm $ "Diagnostic thread exited unexpectedly: " ++ show e
)
return Nothing
@ -354,10 +363,18 @@ updatePositionMap uri changes = pluginGetFile "updatePositionMap: " uri $ \file
-- ---------------------------------------------------------------------
publishDiagnostics :: (MonadIO m, MonadReader REnv m)
=> Int -> J.NormalizedUri -> J.TextDocumentVersion -> DiagnosticsBySource -> m ()
publishDiagnostics maxToSend uri' mv diags = do
=> J.NormalizedUri -> J.TextDocumentVersion -> DiagnosticsBySource -> m ()
publishDiagnostics uri' mv diags = do
lf <- asks lspFuncs
liftIO $ Core.publishDiagnosticsFunc lf maxToSend uri' mv diags
publishDiagnostics' lf uri' mv diags
publishDiagnostics' :: MonadIO m
=> Core.LspFuncs Config -> J.NormalizedUri -> J.TextDocumentVersion -> DiagnosticsBySource -> m ()
publishDiagnostics' lf uri' mv diags = do
config <- liftIO $ fromMaybe Data.Default.def <$> Core.config lf
liftIO $ Core.publishDiagnosticsFunc lf (maxNumberOfProblems config) uri' mv diags
-- ---------------------------------------------------------------------
@ -931,18 +948,17 @@ requestDiagnosticsNormal tn file mVer = do
sendOneGhc :: J.DiagnosticSource -> (J.NormalizedUri, [Diagnostic]) -> R ()
sendOneGhc pid (fileUri,ds) = do
if any (hasSeverity J.DsError) ds
then publishDiagnostics maxToSend fileUri Nothing
then publishDiagnostics fileUri Nothing
(Map.fromList [(Just "hlint",SL.toSortedList []),(Just pid,SL.toSortedList ds)])
else sendOne pid (fileUri,ds)
sendOne pid (fileUri,ds) = do
publishDiagnostics maxToSend fileUri Nothing (Map.fromList [(Just pid,SL.toSortedList ds)])
publishDiagnostics fileUri Nothing (Map.fromList [(Just pid,SL.toSortedList ds)])
hasSeverity :: J.DiagnosticSeverity -> J.Diagnostic -> Bool
hasSeverity sev (J.Diagnostic _ (Just s) _ _ _ _) = s == sev
hasSeverity _ _ = False
sendEmpty = publishDiagnostics maxToSend (J.toNormalizedUri file) Nothing (Map.fromList [(Just "bios",SL.toSortedList [])])
maxToSend = maxNumberOfProblems clientConfig
sendEmpty = publishDiagnostics (J.toNormalizedUri file) Nothing (Map.fromList [(Just "bios",SL.toSortedList [])])
let sendHlint = hlintOn clientConfig
when sendHlint $ do

View File

@ -1,4 +1,4 @@
resolver: lts-14.16
resolver: lts-14.20
packages:
- .
- hie-plugin-api

View File

@ -81,6 +81,7 @@ startServer = do
(\lid errCode e -> logToChan logChan ("received an error", Left (lid, errCode, e)))
(\g x -> g x)
dummyLspFuncs
(\_ _ _ -> return ())
(Just crdl)
return (scheduler, logChan, dispatcher)

View File

@ -40,25 +40,18 @@ spec = describe "diagnostics providers" $ do
reduceDiag ^. LSP.source `shouldBe` Just "hlint"
diags2a <- waitForDiagnostics
-- liftIO $ show diags2a `shouldBe` ""
liftIO $ length diags2a `shouldBe` 2
-- docItem <- getDocItem file languageId
sendNotification TextDocumentDidSave (DidSaveTextDocumentParams doc)
-- diags2hlint <- waitForDiagnostics
-- -- liftIO $ show diags2hlint `shouldBe` ""
-- liftIO $ length diags2hlint `shouldBe` 3
-- diags2liquid <- waitForDiagnostics
-- liftIO $ length diags2liquid `shouldBe` 3
-- -- liftIO $ show diags2 `shouldBe` ""
diags3@(d:_) <- waitForDiagnostics
-- liftIO $ show diags3 `shouldBe` ""
diags3@(d:_) <- waitForDiagnosticsSource "eg2"
liftIO $ do
length diags3 `shouldBe` 3
length diags3 `shouldBe` 1
d ^. LSP.range `shouldBe` Range (Position 0 0) (Position 1 0)
d ^. LSP.severity `shouldBe` Nothing
d ^. LSP.code `shouldBe` Nothing
d ^. LSP.source `shouldBe` Just "eg2"
d ^. LSP.message `shouldBe` T.pack "Example plugin diagnostic, triggered byDiagnosticOnSave"
describe "typed hole errors" $

View File

@ -51,6 +51,7 @@ newPluginSpec = do
(\_ _ _ -> return ())
(\f x -> f x)
dummyLspFuncs
(\_ _ _ -> return ())
(Just crdl)
updateDocument scheduler (filePathToUri "test") 3

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1 @@
packages: ./

View File

@ -0,0 +1,17 @@
cabal-version: >=1.10
name: implicit-exe
version: 0.1.0.0
license-file: LICENSE
build-type: Simple
library
exposed-modules: Lib
hs-source-dirs: src
build-depends: base >=4.8 && <4.14
default-language: Haskell2010
executable implicit-exe
main-is: src/Exe.hs
build-depends: base >=4.8 && <4.14, implicit-exe
default-language: Haskell2010

View File

@ -0,0 +1,4 @@
import Lib (someFunc)
main = someFunc

View File

@ -0,0 +1,4 @@
module Lib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,15 @@
cabal-version: >=2.0
name: A
version: 0.1.0.0
build-type: Simple
library
exposed-modules: MyLib
build-depends: base >=4.9 && < 5
default-language: Haskell2010
executable A
main-is: Main.hs
other-modules: MyLib
build-depends: base >= 4.9 && < 5, A
default-language: Haskell2010

View File

@ -0,0 +1,8 @@
module Main where
import qualified MyLib (someFunc)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
MyLib.someFunc

View File

@ -0,0 +1,4 @@
module MyLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,15 @@
cabal-version: >=2.0
name: B
version: 0.1.0.0
build-type: Simple
library
exposed-modules: MyLib
build-depends: base >= 4.9 && < 5
default-language: Haskell2010
executable B
main-is: Main.hs
other-modules: MyLib
build-depends: base >= 4.9 && < 5, B
default-language: Haskell2010

View File

@ -0,0 +1,8 @@
module Main where
import qualified MyLib (someFunc)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
MyLib.someFunc

View File

@ -0,0 +1,4 @@
module MyLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,9 @@
cabal-version: >=2.0
name: C
version: 0.1.0.0
build-type: Simple
library
exposed-modules: MyLib
build-depends: base>= 4.9 && < 5
default-language: Haskell2010

View File

@ -0,0 +1,4 @@
module MyLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,4 @@
packages:
./A/
./B/
./C/

View File

@ -0,0 +1,4 @@
module MyLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,10 @@
cabal-version: >=1.10
name: simple-cabal-test
version: 0.1.0.0
license-file: LICENSE
build-type: Simple
library
exposed-modules: MyLib
build-depends: base >=4.12 && <4.13
default-language: Haskell2010

View File

@ -0,0 +1,4 @@
module MyLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,10 @@
cabal-version: >=1.10
name: simple-stack-test
version: 0.1.0.0
license-file: LICENSE
build-type: Simple
library
exposed-modules: MyLib
build-depends: base >=4.12 && <4.13
default-language: Haskell2010

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,8 @@
module Main where
import qualified MyLib (someFunc)
main :: IO ()
main = do
putStrLn "Hello, Haskell!"
MyLib.someFunc

View File

@ -0,0 +1,4 @@
module PluginLib (someFunc) where
someFunc :: IO ()
someFunc = putStrLn "someFunc"

View File

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View File

@ -0,0 +1,10 @@
cabal-version: >=1.10
name: plugins-api
version: 0.1.0.0
license-file: LICENSE
build-type: Simple
library
exposed-modules: PluginLib
build-depends: base >=4.12 && <4.13
default-language: Haskell2010

View File

@ -0,0 +1,6 @@
module MyLib (someFunc) where
import qualified PluginLib as L
someFunc :: IO ()
someFunc = L.someFunc

View File

@ -0,0 +1,17 @@
cabal-version: >=1.10
name: sub-package
version: 0.1.0.0
license-file: LICENSE
build-type: Simple
library
exposed-modules: MyLib
build-depends: base >=4.12 && <4.13, plugins-api
hs-source-dirs: src
default-language: Haskell2010
executable sub-package
main-is: Main.hs
build-depends: base >=4.12 && <4.13, sub-package
hs-source-dirs: app
default-language: Haskell2010

View File

@ -0,0 +1,187 @@
{-# LANGUAGE OverloadedStrings #-}
module CabalHelperSpec where
import Haskell.Ide.Engine.Cradle
import Test.Hspec
import System.FilePath
import System.Directory (getCurrentDirectory, removeFile)
import TestUtils
rootPath :: FilePath -> FilePath
rootPath cwd = cwd </> "test" </> "testdata" </> "cabal-helper"
implicitExePath :: FilePath -> FilePath
implicitExePath cwd = rootPath cwd </> "implicit-exe"
monoRepoPath :: FilePath -> FilePath
monoRepoPath cwd = rootPath cwd </> "mono-repo"
subPackagePath :: FilePath -> FilePath
subPackagePath cwd = rootPath cwd </> "sub-package"
simpleCabalPath :: FilePath -> FilePath
simpleCabalPath cwd = rootPath cwd </> "simple-cabal"
simpleStackPath :: FilePath -> FilePath
simpleStackPath cwd = rootPath cwd </> "simple-stack"
spec :: Spec
spec = beforeAll_ setupStackFiles $ do
describe "cabal-helper spec" $ do
describe "find cabal entry point spec" findCabalHelperEntryPointSpec
describe "cradle discovery" cabalHelperCradleSpec
cabalHelperCradleSpec :: Spec
cabalHelperCradleSpec = do
cwd <- runIO getCurrentDirectory
describe "dummy filepath, finds none-cradle" $ do
it "implicit exe, dummy filepath" $ do
crdl <- cabalHelperCradle (implicitExePath cwd </> "File.hs")
crdl `shouldSatisfy` isCabalCradle
it "mono repo, dummy filepath" $ do
crdl <- cabalHelperCradle (monoRepoPath cwd </> "File.hs")
crdl `shouldSatisfy` isCabalCradle
it "stack repo, dummy filepath" $ do
crdl <- cabalHelperCradle (simpleStackPath cwd </> "File.hs")
crdl `shouldSatisfy` isStackCradle
it "cabal repo, dummy filepath" $
pendingWith "Can not work because of global `cabal.project`"
-- crdl <- cabalHelperCradle (simpleCabalPath cwd </> "File.hs")
-- crdl `shouldSatisfy` isCabalCradle
it "sub package, dummy filepath" $ do
crdl <- cabalHelperCradle (subPackagePath cwd </> "File.hs")
crdl `shouldSatisfy` isStackCradle
describe "Existing projects" $ do
it "implicit exe" $ do
crdl <- cabalHelperCradle (implicitExePath cwd </> "src" </> "Exe.hs")
crdl `shouldSatisfy` isCabalCradle
it "mono repo" $ do
crdl <- cabalHelperCradle (monoRepoPath cwd </> "A" </> "Main.hs")
crdl `shouldSatisfy` isCabalCradle
it "stack repo" $ do
crdl <- cabalHelperCradle (simpleStackPath cwd </> "MyLib.hs")
crdl `shouldSatisfy` isStackCradle
it "cabal repo" $
pendingWith "Can not work because of global `cabal.project`"
-- crdl <- cabalHelperCradle (simpleCabalPath cwd </> "MyLib.hs")
-- crdl `shouldSatisfy` isCabalCradle
it "sub package" $ do
crdl <- cabalHelperCradle (subPackagePath cwd </> "plugins-api" </> "PluginLib.hs")
crdl `shouldSatisfy` isStackCradle
findCabalHelperEntryPointSpec :: Spec
findCabalHelperEntryPointSpec = do
cwd <- runIO getCurrentDirectory
describe "implicit exe" $ do
it "Find project root with dummy filepath" $ do
let dummyFile = implicitExePath cwd </> "File.hs"
cabalTest dummyFile
it "Find project root from source component" $ do
let libFile = implicitExePath cwd </> "src" </> "Lib.hs"
cabalTest libFile
it "Find project root from executable component" $ do
let mainFile = implicitExePath cwd </> "src" </> "Exe.hs"
cabalTest mainFile
describe "mono repo" $ do
it "Find project root with dummy filepath" $ do
let dummyFile = monoRepoPath cwd </> "File.hs"
cabalTest dummyFile
it "Find project root with existing executable" $ do
let mainFile = monoRepoPath cwd </> "A" </> "Main.hs"
cabalTest mainFile
describe "sub package repo" $ do
it "Find project root with dummy filepath" $ do
let dummyFile = subPackagePath cwd </> "File.hs"
stackTest dummyFile
it "Find project root with existing executable" $ do
let mainFile = subPackagePath cwd </> "plugins-api" </> "PluginLib.hs"
stackTest mainFile
describe "stack repo" $ do
it "Find project root with dummy filepath" $ do
let dummyFile = simpleStackPath cwd </> "File.hs"
stackTest dummyFile
it "Find project root with real filepath" $ do
let dummyFile = simpleStackPath cwd </> "MyLib.hs"
stackTest dummyFile
describe "simple cabal repo" $
it "Find porject root with dummy filepath" $
pendingWith "Change test-setup, we will always find `cabal.project` in root dir"
-- -------------------------------------------------------------
cabalTest :: FilePath -> IO ()
cabalTest fp = do
entryPoint <- findCabalHelperEntryPoint fp
let Just proj = entryPoint
isCabal = isCabalProject proj
shouldBe isCabal True
stackTest :: FilePath -> IO ()
stackTest fp = do
entryPoint <- findCabalHelperEntryPoint fp
let Just proj = entryPoint
isStack = isStackProject proj
shouldBe isStack True
-- -------------------------------------------------------------
setupStackFiles :: IO ()
setupStackFiles = do
resolver <- readResolver
cwd <- getCurrentDirectory
writeFile (implicitExePath cwd </> "stack.yaml") (standardStackYaml resolver)
writeFile (monoRepoPath cwd </> "stack.yaml") (monoRepoStackYaml resolver)
writeFile (subPackagePath cwd </> "stack.yaml") (subPackageStackYaml resolver)
writeFile (simpleStackPath cwd </> "stack.yaml") (standardStackYaml resolver)
cleanupStackFiles :: IO ()
cleanupStackFiles = do
cwd <- getCurrentDirectory
removeFile (implicitExePath cwd </> "stack.yaml")
removeFile (monoRepoPath cwd </> "stack.yaml")
removeFile (subPackagePath cwd </> "stack.yaml")
removeFile (simpleStackPath cwd </> "stack.yaml")
-- -------------------------------------------------------------
standardStackYaml :: String -> String
standardStackYaml resolver = unlines
[ "# WARNING: THIS FILE IS AUTOGENERATED IN test/utils/CabalHelperSpec. IT WILL BE OVERWRITTEN ON EVERY TEST RUN"
, "resolver: " ++ resolver
, "packages:"
, "- '.'"
, "extra-deps: []"
, "flags: {}"
, "extra-package-dbs: []"
]
monoRepoStackYaml :: String -> String
monoRepoStackYaml resolver = unlines
[ "# WARNING: THIS FILE IS AUTOGENERATED IN test/utils/CabalHelperSpec. IT WILL BE OVERWRITTEN ON EVERY TEST RUN"
, "resolver: " ++ resolver
, "packages:"
, "- 'A'"
, "- 'B'"
, "- 'C'"
, "extra-deps: []"
, "flags: {}"
, "extra-package-dbs: []"
]
subPackageStackYaml :: String -> String
subPackageStackYaml resolver = unlines
[ "# WARNING: THIS FILE IS AUTOGENERATED IN test/unit/CabalHelperSpec. IT WILL BE OVERWRITTEN ON EVERY TEST RUN"
, "resolver: " ++ resolver
, "packages:"
, "- '.'"
, "- 'plugins-api'"
, "extra-deps: []"
, "flags: {}"
, "extra-package-dbs: []"
]

View File

@ -10,6 +10,7 @@ module TestUtils
, runIGM
, ghcVersion, GhcVersion(..)
, logFilePath
, readResolver
, hieCommand
, hieCommandVomit
, hieCommandExamplePlugin
@ -187,7 +188,7 @@ logFilePath = "hie-" ++ stackYaml ++ ".log"
-- on PATH. Cabal seems to respond to @build-tool-depends@ specifically while
-- stack just puts all project executables on PATH.
hieCommand :: String
hieCommand = "hie --bios-verbose -d -l test-logs/" ++ logFilePath
hieCommand = "hie --lsp --bios-verbose -d -l test-logs/" ++ logFilePath
hieCommandVomit :: String
hieCommandVomit = hieCommand ++ " --vomit"

66
update-index-state.sh Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -eu
status_message() {
printf "\\033[0;32m%s\\033[0m\\n" "$1"
}
error_message() {
printf "\\033[0;31m%s\\033[0m\\n" "$1"
}
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
CACHE_LOCATION="${HOME}/.cabal/packages/hackage.haskell.org/01-index.cache"
if [ ! -f "${CACHE_LOCATION}" ] ; then
error_message "${CACHE_LOCATION} does not exist, did you run 'cabal update'?"
exit 1
fi
if [ ! -f "${SCRIPTPATH}/cabal.project" ] ; then
error_message "Could not find ${SCRIPTPATH}/cabal.project, skipping index state update."
exit 3
fi
cabal v2-update
arch=$(getconf LONG_BIT)
case "${arch}" in
32)
byte_size=4
magic_word="CABA1002"
;;
64)
byte_size=8
magic_word="00000000CABA1002"
;;
*)
error_message "Unknown architecture (long bit): ${arch}"
exit 2
;;
esac
# This is the logic to parse the binary format of 01-index.cache.
# The first word is a magic 'caba1002', the second one is the timestamp in unix epoch.
# Better than copying the cabal-install source code.
if [ "$(xxd -u -p -l${byte_size} -s 0 "${CACHE_LOCATION}")" != "${magic_word}" ] ; then
error_message "Magic word does not match!"
exit 4
fi
cache_timestamp=$(echo "ibase=16;obase=A;$(xxd -u -p -l${byte_size} -s ${byte_size} "${CACHE_LOCATION}")" | bc)
# If we got junk from the binary file, this should fail.
cache_date=$(date --utc --date "@${cache_timestamp}" "+%FT%TZ")
status_message "Updating index state in ${SCRIPTPATH}/cabal.project"
if grep -q "^index-state: .*" "${SCRIPTPATH}/cabal.project" ; then
awk '/index-state:/ {gsub(/.*/, "index-state: '${cache_date}'")}; { print }' "${SCRIPTPATH}/cabal.project" > "${SCRIPTPATH}/cabal.project.tmp"
mv "${SCRIPTPATH}/cabal.project.tmp" "${SCRIPTPATH}/cabal.project"
else
printf "index-state: %s\n" "${cache_date}" >> "${SCRIPTPATH}/cabal.project"
fi