Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Haskell compilation #1545

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 137 additions & 64 deletions doc/haskell-mode.texi
Original file line number Diff line number Diff line change
Expand Up @@ -1075,78 +1075,151 @@ line in the right file when clicked/selected.
@node Compilation
@chapter Compilation

@findex haskell-compile
Haskell Mode comes with an intelligent Major mode for compilation named
@code{HsCompilation} defined in @code{haskell-compile.el}. This mode is
derived from @code{compilation-mode} defined in @code{compile.el}.

Haskell mode comes equipped with a specialized @dfn{Compilation mode}
tailored to GHC's compiler messages with optional support for Cabal
projects. @xref{Compilation Mode,,,emacs}, for more information about
the basic commands provided by the Compilation mode which are available
in the Haskell compilation sub-mode as well. The additional features
provided compared to Emacs' basic Compilation mode are:
First the compilation mode tries to find the project's root directory.
This is done by searching for files such as @code{cabal.sandbox.config},
@code{stack.yaml}, and @code{something.cabal} in that order.

@itemize
@item
DWIM-style auto-detection of compile command (including support for
CABAL projects)
@item
Support for GHC's compile messages and recognizing error, warning and
info source locations (including @option{-ferror-spans} syntax)
@item
Support for filtering out GHC's uninteresting @samp{Loading package...}
linker messages
@end itemize
If you have both @code{cabal.sandbox.config} and @code{stack.yaml} files
defined, in your project's root directory, @code{cabal.sandbox.config}
takes precedence over @code{stack.yaml} and similary @code{stack.yaml}
takes precedence over @code{something.cabal}.

In order to use it, invoke the @code{haskell-compile} command instead of
@code{compile} as you would for the ordinary Compilation mode. It's
recommended to bind @code{haskell-compile} to a convenient key
binding. For instance, you can add the following to your Emacs
initialization to bind @code{haskell-compile} to @kbd{C-c C-c}.
You can start the compilation by @kbd{M-x haskell-compile}. The intelligent
thing about this mode is that like it sets the project's root directory.
It will run the appropriate compilation command from the project's root
directory.

@lisp
(eval-after-load "haskell-mode"
'(define-key haskell-mode-map (kbd "C-c C-c") 'haskell-compile))
@multitable @columnfractions 0.25 0.25 0.50
@headitem Dominant file @tab Build command @tab Commandline Options

(eval-after-load "haskell-cabal"
'(define-key haskell-cabal-mode-map (kbd "C-c C-c") 'haskell-compile))
@end lisp
@item @code{cabal.sandbox.config}
@tab @code{cabal build}
@tab @code{--ghc-options=\"-ferror-spans -Wall -fforce-recomp\"}

@noindent
The following description assumes that @code{haskell-compile} has been
bound to @kbd{C-c C-c}.

@vindex haskell-compile-cabal-build-command
@vindex haskell-compile-cabal-build-command-alt
@vindex haskell-compile-command

When invoked, @code{haskell-compile} tries to guess how to compile the
Haskell program your currently visited buffer belongs to, by searching
for a @file{.cabal} file in the current of enclosing parent folders. If
a @file{.cabal} file was found, the command defined in the
@code{haskell-compile-cabal-build-command} option is used. Note that to
compile a @code{stack} based project you will need to set this variable to
@code{stack build}. As usual you can do it using @code{M-x customize-variable}
or with:
@item @code{stack.yaml}
@tab @code{stack build}
@tab @code{--ghc-options=\"-ferror-spans -Wall\"}

@lisp
(setq haskell-compile-cabal-build-command "stack build")
@end lisp
@item @code{something.cabal}
@tab @code{cabal build}

@item default
@tab @code{ghc <name-of-file-in-buffer>}
@tab @code{--make -ferror-spans -Wall -fforce-recomp}
@end multitable

@section Keybindings

@multitable @columnfractions 0.3 0.7
@headitem Key binding @tab Function
@item TAB
@tab compilation-next-error

@item RET
@tab compile-goto-error

@item C-o
@tab compilation-display-error

@item SPC
@tab scroll-up-command

@item -
@tab negative-argument

@item 0 .. 9
@tab digit-argument

@item <
@tab beginning-of-buffer

@item >
@tab end-of-buffer

@item ?
@tab describe-mode

@item g
@tab recompile

@item h
@tab describe-mode

@item q
@tab quit-window

@item DEL
@tab scroll-down-command

@item S-SPC
@tab scroll-down-command

@item <backtab>
@tab compilation-previous-error

@item <follow-link>
@tab mouse-face

@item <mouse-2>
@tab compile-goto-error

@item <remap>
@tab Prefix Command

@item M-n
@tab compilation-next-error

@item M-p
@tab compilation-previous-error

@item M-@{
@tab compilation-previous-file

@item M-@}
@tab compilation-next-file

@item C-c C-c
@tab compile-goto-error

@item C-c C-f
@tab next-error-follow-minor-mode

@item C-c C-k
@tab kill-compilation

@end multitable

@section Related Defcustoms

@multitable @columnfractions 0.3 0.7
@headitem Defcustom @tab Default Value

@item @code{haskell-process-path-ghc}
@tab @code{ghc}

@item @code{haskell-process-args-ghc}
@tab @code{--make -ferror-spans -Wall -fforce-recomp}

@item @code{haskell-process-path-cabal}
@tab @code{cabal}

@item @code{haskell-process-args-cabal-build}
@tab @code{--ghc-options=\"-ferror-spans -Wall -fforce-recomp\"}

@item @code{haskell-process-path-stack}
@tab @code{stack}

@item @code{haskell-process-args-stack-build}
@tab @code{--ghc-options=\"-ferror-spans -Wall\"}
@end multitable

@section Hooks

Moreover, when requesting to compile a @file{.cabal}-file is detected and
a negative prefix argument (e.g. @kbd{C-- C-c C-c}) was given, the
alternative @code{haskell-compile-cabal-build-command-alt} is
invoked. By default, @code{haskell-compile-cabal-build-command-alt}
contains a @samp{cabal clean -s} command in order to force a full
rebuild.

Otherwise if no @file{.cabal} could be found, a single-module
compilation is assumed and @code{haskell-compile-command} is used
(@emph{if} the currently visited buffer contains Haskell source code).

You can also inspect and modify the compile command to be invoked
temporarily by invoking @code{haskell-compile} with a prefix argument
(e.g. @kbd{C-u C-c C-c}). If later-on you want to recompile using the
same customized compile command, invoke @code{recompile} (bound to
@kbd{g}) inside the @samp{*haskell-compilation*} buffer.
None

@node Interactive Haskell
@chapter Interactive Haskell
Expand Down
74 changes: 21 additions & 53 deletions haskell-compile.el
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
;;; haskell-compile.el --- Haskell/GHC compilation sub-mode -*- lexical-binding: t -*-

;; Copyright (C) 2013 Herbert Valerio Riedel
;; Copyright (C) 2017 Vasantha Ganesh Kanniappan <[email protected]>

;; Author: Herbert Valerio Riedel <[email protected]>

Expand All @@ -27,35 +28,14 @@
;;; Code:

(require 'compile)
(require 'haskell-cabal)
(require 'haskell-customize)

;;;###autoload
(defgroup haskell-compile nil
"Settings for Haskell compilation mode"
:link '(custom-manual "(haskell-mode)compilation")
:group 'haskell)

(defcustom haskell-compile-cabal-build-command
"cd %s && cabal build --ghc-option=-ferror-spans"
"Default build command to use for `haskell-cabal-build' when a cabal file is detected.
The `%s' placeholder is replaced by the cabal package top folder."
:group 'haskell-compile
:type 'string)

(defcustom haskell-compile-cabal-build-alt-command
"cd %s && cabal clean -s && cabal build --ghc-option=-ferror-spans"
"Alternative build command to use when `haskell-cabal-build' is called with a negative prefix argument.
The `%s' placeholder is replaced by the cabal package top folder."
:group 'haskell-compile
:type 'string)

(defcustom haskell-compile-command
"ghc -Wall -ferror-spans -fforce-recomp -c %s"
"Default build command to use for `haskell-cabal-build' when no cabal file is detected.
The `%s' placeholder is replaced by the current buffer's filename."
:group 'haskell-compile
:type 'string)

(defcustom haskell-compile-ghc-filter-linker-messages
t
"Filter out unremarkable \"Loading package...\" linker messages during compilation."
Expand Down Expand Up @@ -108,46 +88,34 @@ messages pointing to additional source locations."
haskell-compilation-error-regexp-alist)

(add-hook 'compilation-filter-hook
'haskell-compilation-filter-hook nil t)
)
'haskell-compilation-filter-hook nil t))

;;;###autoload
(defun haskell-compile (&optional edit-command)
(defun haskell-compile ()
"Compile the Haskell program including the current buffer.
Tries to locate the next cabal description in current or parent
folders via `haskell-cabal-find-dir' and if found, invoke
`haskell-compile-cabal-build-command' from the cabal package root
folder. If no cabal package could be detected,
`haskell-compile-command' is used instead.

If prefix argument EDIT-COMMAND is non-nil (and not a negative
prefix `-'), `haskell-compile' prompts for custom compile
command.

If EDIT-COMMAND contains the negative prefix argument `-',
`haskell-compile' calls the alternative command defined in
`haskell-compile-cabal-build-alt-command' if a cabal package was
detected.
folders, `stack.yaml' file via `locate-dominating-file'. If they
are not found, then the haskell file in the currebt buffer is
executed.

`haskell-compile' uses `haskell-compilation-mode' which is
derived from `compilation-mode'. See Info
node `(haskell-mode)compilation' for more details."
(interactive "P")
(interactive)
(save-some-buffers (not compilation-ask-about-save)
compilation-save-buffers-predicate)
(let* ((cabdir (haskell-cabal-find-dir))
(command1 (if (eq edit-command '-)
haskell-compile-cabal-build-alt-command
haskell-compile-cabal-build-command))
(srcname (buffer-file-name))
(command (if cabdir
(format command1 cabdir)
(if (and srcname (derived-mode-p 'haskell-mode))
(format haskell-compile-command srcname)
command1))))
(when (and edit-command (not (eq edit-command '-)))
(setq command (compilation-read-command command)))

compilation-save-buffers-predicate)
(let* ((commandl (cl-ecase (haskell-process-type)
('ghci `(,haskell-process-path-ghc
,(buffer-file-name)
,haskell-process-args-ghc))
('cabal-repl `(,haskell-process-path-cabal
"build"
,haskell-process-args-cabal-build))
('stack-ghci `(,haskell-process-path-stack
"build"
,haskell-process-args-stack-build))))
(command (mapconcat #'concat commandl " ")))
(message command)
(compilation-start command 'haskell-compilation-mode)))

(provide 'haskell-compile)
Expand Down
7 changes: 7 additions & 0 deletions haskell-utils.el
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,12 @@ expression bounds."
If given DISABLED argument sets variable value to nil, otherwise to t."
(setq haskell-mode-interactive-prompt-state (not disabled)))

(defun haskell-utils-compile-error-p ()
"Return t if an error (ghci's) is found in current buffer."
(search-forward-regexp "^\\(\\(?:[A-Z]:\\)?[^ \r\n:][^\r\n:]*\\):\\([0-9()-:]+\\):?"
nil
(lambda () nil)
1))

(provide 'haskell-utils)
;;; haskell-utils.el ends here
50 changes: 50 additions & 0 deletions tests/haskell-compile-tests.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
;;; haskell-compile-tests.el --- Tests for HsCompilation Mode -*- lexical-binding: t -*-

;; Copyright © 2017 Vasantha Ganesh Kanniappan <[email protected]>

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;;; Code:

(require 'haskell-test-utils)
(require 'haskell-utils)
(require 'haskell-compile)

(ert-deftest haskell-compile-test-1 ()
(haskell-unconditional-kill-buffer "*haskell-compilation*")
(find-file-literally (concat default-directory
(file-name-as-directory "tests")
(file-name-as-directory "sample-code")
"fibonoacci.hs"))
(with-current-buffer "fibonoacci.hs"
(haskell-mode)
(haskell-compile)
(should (buffer-live-p (get-buffer "*haskell-compilation*")))))

(ert-deftest haskell-compile-test-2 ()
(haskell-unconditional-kill-buffer "*haskell-compilation*")
(find-file-literally (concat default-directory
(file-name-as-directory "tests")
(file-name-as-directory "sample-code")
"does_not_compile.hs"))
(with-current-buffer "does_not_compile.hs"
(haskell-mode)
(haskell-compile)
(with-current-buffer "*haskell-compilation*"
(goto-char (point-min))
(should (haskell-utils-compile-error-p)))))
Loading