-
Notifications
You must be signed in to change notification settings - Fork 344
/
haskell-compile.el
252 lines (216 loc) · 9.89 KB
/
haskell-compile.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
;;; haskell-compile.el --- Haskell/GHC compilation sub-mode -*- lexical-binding: t -*-
;; Copyright (C) 2013 Herbert Valerio Riedel
;; 2020 Marc Berkowitz <[email protected]>
;; 2020 Jacob Ilsø
;; Author: Herbert Valerio Riedel <[email protected]>
;; This file is not part of GNU Emacs.
;; 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 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Simple GHC-centric compilation sub-mode; see info node
;; `(haskell-mode)compilation' for more information
;;; Code:
(require 'compile)
(require 'haskell-cabal)
(require 'haskell-customize)
(require 'ansi-color)
(eval-when-compile (require 'subr-x))
(defgroup haskell-compile nil
"Settings for Haskell compilation mode"
:link '(custom-manual "(haskell-mode)compilation")
:group 'haskell)
(defcustom haskell-compile-cabal-build-command
"cabal build --ghc-option=-ferror-spans"
"Default build command to use for `haskell-cabal-build'.
It is used when a cabal file is detected.
For legacy compat, `%s' is replaced by the cabal package top folder."
:group 'haskell-compile
:type 'string)
(defcustom haskell-compile-cabal-build-alt-command
"cabal clean -s && cabal build --ghc-option=-ferror-spans"
"Alternative build command to use when `haskell-cabal-build'.
It is used when `haskell-cabal-build' is called with a negative prefix argument.
For legacy compat, `%s' is replaced by the cabal package top folder."
:group 'haskell-compile
:type 'string)
(defcustom haskell-compile-stack-build-command
"stack build --fast"
"Default build command to use for `haskell-stack-build'.
It is used when a stack file is detected.
For legacy compat, `%s' is replaced by the stack package top folder."
:group 'haskell-compile
:type 'string)
(defcustom haskell-compile-stack-build-alt-command
"stack clean && stack build --fast"
"Alternative build command to use when `haskell-stack-build'.
It is used when `haskell-stack-build' is called with a negative prefix argument.
For legacy compat, `%s' is replaced by the stack 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'.
It is used when no cabal or stack 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."
:group 'haskell-compile
:type 'boolean)
(defcustom haskell-compiler-type
'auto
"Controls whether to use cabal, stack, or ghc to compile.
Auto (the default) means infer from the presence of a cabal or stack spec file,
following same rules as haskell-process-type."
:type '(choice (const auto) (const ghc) (const stack) (const cabal))
:group 'haskell-compile)
(make-variable-buffer-local 'haskell-compiler-type)
(defconst haskell-compilation-error-regexp-alist
`((,(concat
"^ *\\([^\n\r\t>]*\s*> \\)?" ;; if using multi-package stack project, remove the package name that is prepended
"\\(?1:[^\t\r\n]+?\\):"
"\\(?:"
"\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?" ;; "121:1" & "12:3-5"
"\\|"
"(\\(?2:[0-9]+\\),\\(?4:[0-9]+\\))-(\\(?3:[0-9]+\\),\\(?5:[0-9]+\\))" ;; "(289,5)-(291,36)"
"\\)"
":\\(?6:\n?[ \t]+[Ww]arning:\\)?")
1 (2 . 3) (4 . 5) (6 . nil)) ;; error/warning locus
;; multiple declarations
("^ \\(?:Declared at:\\| \\) \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)$"
1 2 4 0) ;; info locus
;; failed tasty tests
(".*error, called at \\(.*\\.hs\\):\\([0-9]+\\):\\([0-9]+\\) in .*" 1 2 3 2 1)
(" +\\(.*\\.hs\\):\\([0-9]+\\):$" 1 2 nil 2 1)
;; this is the weakest pattern as it's subject to line wrapping et al.
(" at \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?[)]?$"
1 2 (4 . 5) 0)) ;; info locus
"Regexps used for matching GHC compile messages.
See `compilation-error-regexp-alist' for semantics.")
(defvar haskell-compilation-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map compilation-mode-map))
"Keymap for `haskell-compilation-mode' buffers.
This is a child of `compilation-mode-map'.")
(defun haskell-compilation-filter-hook ()
"Local `compilation-filter-hook' for `haskell-compilation-mode'."
(when haskell-compile-ghc-filter-linker-messages
(delete-matching-lines "^ *Loading package [^ \t\r\n]+ [.]+ linking [.]+ done\\.$"
(save-excursion (goto-char compilation-filter-start)
(line-beginning-position))
(point)))
(let ((inhibit-read-only t))
(ansi-color-apply-on-region compilation-filter-start (point-max))))
(define-compilation-mode haskell-compilation-mode "HsCompilation"
"Haskell/GHC specific `compilation-mode' derivative.
This mode provides support for GHC 7.[46]'s compile
messages. Specifically, also the `-ferror-spans` source location
format is supported, as well as info-locations within compile
messages pointing to additional source locations."
(setq-local compilation-error-regexp-alist
haskell-compilation-error-regexp-alist)
(add-hook 'compilation-filter-hook
'haskell-compilation-filter-hook nil t)
)
;;;###autoload
(defun haskell-compile (&optional edit-command)
"Run a compile command for the current Haskell buffer.
Obeys haskell-compiler-type to choose the appropriate build command.
If prefix argument EDIT-COMMAND is non-nil (and not a negative
prefix `-'), prompt for a custom compile command.
If EDIT-COMMAND contains the negative prefix argument `-', call
the alternative command defined in
`haskell-compile-stack-build-alt-command' /
`haskell-compile-cabal-build-alt-command'.
If there is no prefix argument, the most recent custom compile
command is used, falling back to
`haskell-compile-stack-build-command' for stack builds
`haskell-compile-cabal-build-command' for cabal builds, and
`haskell-compile-command' otherwise.
'% characters in the `-command' templates are replaced by the
base directory for build tools, or the current buffer for
`haskell-compile-command'."
(interactive "P")
(save-some-buffers (not compilation-ask-about-save)
compilation-save-buffers-predicate)
(let (htype dir)
;;test haskell-compiler-type to set htype and dir
(cond
((eq haskell-compiler-type 'cabal)
(setq htype 'cabal)
(setq dir (haskell-cabal-find-dir)))
((eq haskell-compiler-type 'stack)
(setq htype 'stack)
(setq dir (locate-dominating-file default-directory "stack.yaml")))
((eq haskell-compiler-type 'ghc)
(setq htype 'ghc))
((eq haskell-compiler-type 'auto)
(let ((r (haskell-build-type)))
(setq htype (car r))
(setq dir (cdr r))))
(t (error "Invalid haskell-compiler-type")))
;; now test htype and compile
(cond
((or (eq htype 'cabal) (eq htype 'cabal-project)) ; run cabal
(let ((command haskell-compile-cabal-build-command)
(alt-command haskell-compile-cabal-build-alt-command))
(when (eq htype 'cabal-project) ;no default target
(setq command (concat command " all")
alt-command (concat alt-command " all")))
(haskell--compile dir edit-command
'haskell--compile-cabal-last
command alt-command)))
((eq htype 'stack)
(haskell--compile dir edit-command
'haskell--compile-stack-last
haskell-compile-stack-build-command
haskell-compile-stack-build-alt-command))
((eq htype 'ghc)
(haskell--compile (buffer-file-name) edit-command
'haskell--compile-ghc-last
haskell-compile-command
haskell-compile-command)))))
;; Save commands for reuse, but only when in same context.
;; Hence save a pair (COMMAND . DIR); or nil.
(defvar haskell--compile-stack-last nil)
(defvar haskell--compile-cabal-last nil)
(defvar haskell--compile-ghc-last nil)
;; called only by (haskell-compile):
(defun haskell--compile (dir-or-file edit last-sym fallback alt)
(let* ((dir-or-file (or dir-or-file default-directory))
(local-dir-or-file (or (file-remote-p dir-or-file 'localname) dir-or-file))
(last-pair (symbol-value last-sym))
(last-command (car last-pair))
(last-dir (cdr last-pair))
(default (or (and last-dir (eq last-dir local-dir-or-file) last-command)
fallback))
(template (cond
((null edit) default)
((eq edit '-) alt)
(t (compilation-read-command default))))
(command (format template (shell-quote-argument local-dir-or-file)))
(dir (if (directory-name-p local-dir-or-file)
local-dir-or-file
default-directory))
(name (if (directory-name-p local-dir-or-file)
(file-name-base (directory-file-name local-dir-or-file))
(file-name-nondirectory local-dir-or-file))))
(unless (eq edit'-)
(set last-sym (cons template local-dir-or-file)))
(let ((default-directory dir))
(compilation-start
command
'haskell-compilation-mode
(lambda (mode) (format "*%s* <%s>" mode name))))))
(provide 'haskell-compile)
;;; haskell-compile.el ends here