branch: elpa/haskell-tng-mode commit 1d53572038048f4eb24a9a61bb4695f19bc574fb Author: Tseen She <ts33n....@gmail.com> Commit: Tseen She <ts33n....@gmail.com>
bugfixes --- README.md | 14 ++++++++++---- cabal-ghcflags.sh | 48 +++++++++++++++++++++++++++++++++--------------- haskell-tng-hsinspect.el | 25 ++++++++++++++++++++----- 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0b6486b..97567e8 100644 --- a/README.md +++ b/README.md @@ -58,19 +58,25 @@ A full installation may look like the following The optional command line tool [`hsinspect`](https://gitlab.com/tseenshe/hsinspect) provides semantic information by using the `ghc` api. -For now, only one version of `ghc` is supported at a time (change `ghc-8.4.4` to your current `ghc` version): +You must install `hsinspect` for every version of `ghc` that you plan to use, e.g. ``` -cabal v2-install hsinspect --overwrite-policy=always -w ghc-8.4.4 +rm -f ~/.cabal/bin/hsinspect +for V in 8.4.4 8.6.5 ; do + cabal v2-install hsinspect -w ghc-$V && + mv ~/.cabal/bin/hsinspect ~/.cabal/bin/hsinspect-ghc-$V +done ``` -To use `hsinspect` commands, generate a `.hsinspect.env` file by running `M-x haskell-tng-hsinspect` for a project. This is only needed when the dependencies change, but the project must be compilable. +To use `hsinspect` commands, generate `.ghc.flags` / `.ghc.version` files by running `M-x haskell-tng-hsinspect` for a project. This is only needed when the dependencies change. + +`hsinspect` only works when the dependencies of the current file have been compiled (the current file doesn't need to be compilable). The `haskell-tng-contrib-company` package will automatically complete symbols that are in scope. To find out which module a symbol belongs to, use `M-x haskell-tng-fqn-at-point`. -The are some limitations to this tool in addition to known bugs. See [the gory details](https://gitlab.com/tseenshe/hsinspect) for more information. Hopefully there will be no need for `M-x haskell-tng-hsinspect` or the `.hsinspect.env` files in a future release. +The are some limitations to this tool in addition to known bugs. See [the gory details](https://gitlab.com/tseenshe/hsinspect) for more information. Hopefully there will be no need for `M-x haskell-tng-hsinspect` and the `.ghc.*` files in a future release. ## Contrib diff --git a/cabal-ghcflags.sh b/cabal-ghcflags.sh index 138bc4d..cb7e78b 100755 --- a/cabal-ghcflags.sh +++ b/cabal-ghcflags.sh @@ -9,45 +9,59 @@ # build flags. If users wish to include test phases they must add tests: True # to their cabal.project.local -# set -e -x -o pipefail +set -e -TMP="/tmp/$PWD/hack-cabal" -mkdir -p "$TMP" 2> /dev/null +TMP="/tmp/cabal-ghcflags-$USER/$PWD" +mkdir -p "$TMP" 2> /dev/null || true # to ensure the json plan is in place -cabal v2-build -v0 :all --only-dependencies +echo "Resolving dependencies" +cabal v2-build -v0 :all --dry -if [ ! -d dist-newstyle ] ; then - echo "dist-newstyle not found" +if [ ! -f dist-newstyle/cache/plan.json ] ; then + echo "dist-newstyle/cache/plan.json not found" exit 1 fi +# seems to be a bug in cabal... +mkdir dist-newstyle/tmp 2> /dev/null || true + +cabal v2-exec -v0 ghc -- --numeric-version > .ghc.version GHC=$(cabal v2-exec -v2 ghc -- --numeric-version | tail -2 | head -1 | sed 's/ .*//') -GHC_PKG=$(echo "$GHC" | rev | sed 's/chg/gkp-chg/' | rev) # ghc is called multiple times during the v2-repl startup. # The only call that we're interested in is this one. cat <<EOF > "$TMP/ghc" #!/bin/bash if [ "\$1" == "--interactive" ]; then - echo -n "\${@:2}" >> "$TMP/out" + echo -n "\${@:2}" >> "\$OUTPUT" else exec "$GHC" "\$@" fi EOF chmod 755 "$TMP/ghc" +GHC_PKG=$(echo "$GHC" | rev | sed 's/chg/gkp-chg/' | rev) cat <<EOF > "$TMP/ghc-pkg" #!/bin/bash exec "$GHC_PKG" "\$@" EOF chmod 755 "$TMP/ghc-pkg" +HSC2HS=$(echo "$GHC" | rev | sed 's/chg/sh2csh/' | rev) +cat <<EOF > "$TMP/hsc2hs" +#!/bin/bash +exec "$HSC2HS" "\$@" +EOF +chmod 755 "$TMP/hsc2hs" + +echo "Inspecting build plan" jq -c '(.["install-plan"][] | select(.["pkg-src"].type == "local") | select(.["component-name"] != null) | [ .["pkg-name"], .["component-name"], .["pkg-src"].path, .id ] )' dist-newstyle/cache/plan.json | while read LINE ; do NAME=$(echo "$LINE" | jq -r '.[0]') PART=$(echo "$LINE" | jq -r '.[1]') ROOT=$(echo "$LINE" | jq -r '.[2]') ID=$(echo "$LINE" | jq -r '.[3]') + # TODO this could be parallelised (if cabal can handle it!) if [ "$PART" == "lib" ] ; then COMPONENT="lib:$NAME" @@ -55,14 +69,21 @@ jq -c '(.["install-plan"][] | select(.["pkg-src"].type == "local") | select(.["c COMPONENT="$PART" fi - rm "$TMP/out" 2> /dev/null + echo " $NAME:$COMPONENT $ID" + + export OUTPUT="$TMP/out.$NAME:$COMPONENT" + + rm "$OUTPUT" 2> /dev/null || true cabal v2-repl -v0 -w "$TMP/ghc" "$NAME:$COMPONENT" # extract all the source directories that use these flags - for D in $(cat "$TMP/out" | tr ' ' '\n' | grep '^-i' | sed 's/^-i//' | sed '/^$/d') ; do + for D in $(cat "$OUTPUT" | tr ' ' '\n' | grep '^-i' | sed 's/^-i//' | sed '/^$/d') ; do + if [[ "$D" != /* ]] ; then + D="$ROOT/$D" + fi if [ -d "$D" ] ; then - echo "writing $D/.ghc.flags" - cat "$TMP/out" > "$D/.ghc.flags" + echo " $D/.ghc.flags" + cat "$OUTPUT" > "$D/.ghc.flags" fi done done @@ -70,6 +91,3 @@ done if [ -d "$TMP" ] ; then rm -rf "$TMP" fi - -# try our best to reset the cache to what the user expects -cabal v2-build -v0 :all --dry diff --git a/haskell-tng-hsinspect.el b/haskell-tng-hsinspect.el index 5d04250..b06f013 100644 --- a/haskell-tng-hsinspect.el +++ b/haskell-tng-hsinspect.el @@ -11,6 +11,8 @@ ;; ;;; Code: +(require 'subr-x) + (require 'haskell-tng-compile) ;;;###autoload @@ -64,7 +66,17 @@ change." (insert-file-contents (expand-file-name ".ghc.flags")) (split-string (buffer-substring-no-properties (point-min) (point-max))))) - (user-error "could not find `.ghc.flags.lib'. Run `M-x haskell-tng-hsinspect'"))) + (user-error "could not find `.ghc.flags'. Run `M-x haskell-tng-hsinspect'"))) + +(defun haskell-tng--hsinspect-ghc () + "Obtain the version of hsinspect that matches the project's compiler." + (if-let (default-directory (locate-dominating-file default-directory ".ghc.version")) + (with-temp-buffer + (insert-file-contents (expand-file-name ".ghc.version")) + (concat + "hsinspect-ghc-" + (string-trim (buffer-substring-no-properties (point-min) (point-max))))) + (user-error "could not find `.ghc.version'. Run `M-x haskell-tng-hsinspect'"))) ;; TODO invalidate cache when imports section has changed ;; TODO is there a way to tell Emacs not to render this in `C-h v'? @@ -78,16 +90,19 @@ t means the process failed.") haskell-tng--hsinspect-imports) (setq haskell-tng--hsinspect-imports t) ;; avoid races (ignore-errors (kill-buffer "*hsinspect*")) - (when-let (ghcflags (haskell-tng--hsinspect-ghcflags)) + (when-let ((ghcflags (haskell-tng--hsinspect-ghcflags)) + ;; default-directory is so that relative paths in ghcflags work + (default-directory (haskell-tng--util-locate-dominating-file + haskell-tng--compile-dominating-package))) (if (/= 0 (let ((process-environment (cons "GHC_ENVIRONMENT=-" process-environment))) (apply #'call-process - ;; TODO launching the correct hsinspect-ghc-X version ;; TODO async - "hsinspect" + (haskell-tng--hsinspect-ghc) nil "*hsinspect*" nil - (append `("imports" ,buffer-file-name "--") ghcflags)))) + ;; need to disable all warnings + (append `("imports" ,buffer-file-name "--") ghcflags '("-w"))))) (user-error "`hsinspect' failed. See the *hsinspect* buffer for more information") (setq haskell-tng--hsinspect-imports (with-current-buffer "*hsinspect*"