Skip to content

Pr add haskell compiler type #1717

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

Merged
merged 2 commits into from
Sep 15, 2020
Merged
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
102 changes: 59 additions & 43 deletions doc/haskell-mode.texi
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ provided compared to Emacs' basic Compilation mode are:
@itemize
@item
DWIM-style auto-detection of compile command (including support for
CABAL projects)
stack and cabal projects)
@item
Support for GHC's compile messages and recognizing error, warning and
info source locations (including @option{-ferror-spans} syntax)
Expand All @@ -1106,40 +1106,56 @@ initialization to bind @code{haskell-compile} to @kbd{C-c C-c}.
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} decides what build tool to run by
consulting the value of @code{haskell-compiler-type}.

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:
@itemize

@lisp
(setq haskell-compile-cabal-build-command "stack build")
@end lisp
@item
If this is the symbol @code{cabal}, then @code{cabal} is run, using the
command defined by @code{haskell-compile-cabal-build-command}.
@code{haskell-compile} looks for a @file{.cabal} file in the current directory
or its closet ancestor.

@item
If @code{haskell-compiler-type} is the symbol @code{stack}, then @code{stack}
is run, using the command @code{haskell-compile-stack-build-command}, and
looking for the closest @file{stack.yaml} file.

@item
If @code{haskell-compiler-type} is the symbol @code{ghc}, then @code{ghc} is
run, using the command @code{haskell-compile-command}.

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.
@item
But if @code{haskell-compiler-type} is the symbol @code{auto} (the default),
then emacs tries to choose which build tool to use by looking for the closest
@file{stack.yaml} or @file{.cabal} file. (This is similar to the way
@code{haskell-interactive-mode} uses the variable
@code{haskell-process-type}.)
@end itemize

Moreover, when a negative prefix argument is supplied (e.g. @kbd{C-- C-c C-c}),
the alternative build command is used, that is,
@code{haskell-compile-cabal-build-command-alt} or
@code{haskell-compile-stack-build-command-alt}.
By default, the alternative build commands force a full rebuild.
(Note this does not affect @code{haskell-compile-command}.)

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).
As usual you can change any of these variables using @code{M-x customize-variable}.

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.

@vindex haskell-compiler-type
@vindex haskell-compile-command
@vindex haskell-compile-stack-build-command
@vindex haskell-compile-cabal-build-command
@vindex haskell-compile-stack-build-alt-command
@vindex haskell-compile-cabal-build-alt-command

@section Keybindings

@multitable 0.3 0.7
Expand Down Expand Up @@ -1275,24 +1291,27 @@ for the current GHCi session.

@cindex customizing
What kind of Haskell REPL @code{haskell-interactive-mode} will start up
depends on the value of @code{haskell-process-type}. This can be one of the
symbols @code{auto}, @code{ghci}, @code{cabal-repl}, @code{cabal-new-repl}, or
@code{stack-ghci}. If it's @code{auto}, the directory contents and available
programs will be used to make a best guess at the process type. The actual
process type will then determine which variables
@code{haskell-interactive-mode} will access to determine the program to start
and its arguments:
depends on the value of @code{haskell-process-type}. This can be one of
the symbols @code{auto}, @code{ghci}, @code{stack-ghci},
@code{cabal-repl}, or @code{cabal-new-repl}.

(@code{cabal-new-repl} is allowed but obsolete, like the cabal command
@code{new-build} that it selected. Emacs actually runs the equivalent
cabal command @code{build}.)

If it's @code{auto}, the directory contents and available programs will
be used to make a best guess at the process type. The actual process
type will then determine which variables @code{haskell-interactive-mode}
will access to determine the program to start and its arguments:

@itemize
@item
If it's @code{ghci}, @code{haskell-process-path-ghci} and
@code{haskell-process-args-ghci} will be used.
@item
If it's @code{cabal-repl}, @code{haskell-process-path-cabal} and
@code{haskell-process-args-cabal-repl}.
@item
If it's @code{cabal-new-repl}, @code{haskell-process-path-cabal} and
@code{haskell-process-args-cabal-new-repl}.
If it's @code{cabal-repl} or @code{cabal-new-repl},
@code{haskell-process-path-cabal} and
@code{haskell-process-args-cabal-repl} are used.
@item
If it's @code{stack-ghci}, @code{haskell-process-path-stack} and
@code{haskell-process-args-stack-ghci} will be used.
Expand All @@ -1310,7 +1329,6 @@ strings specifying (further) command-line arguments.
@vindex haskell-process-path-stack
@vindex haskell-process-args-ghci
@vindex haskell-process-args-cabal-repl
@vindex haskell-process-args-cabal-new-repl
@vindex haskell-process-args-stack-ghci

@section Haskell Interactive Mode Setup
Expand Down Expand Up @@ -1398,8 +1416,6 @@ Here is a list of available process types:
@item ghci
@item cabal-repl
@item cabal-new-repl
@item cabal-dev
@item cabal-ghci
@item stack-ghci
@end itemize

Expand Down Expand Up @@ -2774,14 +2790,14 @@ if the @code{haskell-process-type} is @code{'auto}, the directories are searched
@code{cabal.sandbox.config} or @code{stack.yaml} or @code{*.cabal} file.
If the file is present, then appropriate process is started.

When @code{cabal.sandbox.config} is found @code{haskell-process-type} is @code{'cabal-repl}.
When @code{cabal.project} or @code{cabal.sandbox.config} is found,
@code{haskell-process-type} is @code{'cabal-repl}.
Similarly, when @code{stack.yaml} is found @code{haskell-process-type} is @code{'stack-ghci}.
Similarly, when @code{xyz.cabal} is found @code{haskell-process-type} is @code{'cabal-repl}.
When nothing is found @code{haskell-process-type} is @code{'ghci}. When more than one
file such as @code{cabal.sandbox.config} and @code{stack.yaml} are found the following
preference is followed.

@code{cabal.sandbox.config} > @code{stack.yaml} > @code{*.cabal}
preference is followed:
@code{cabal.project} > @code{stack.yaml} > @code{*.cabal}

@node Collapsing Haskell code
@chapter Collapsing Haskell code
Expand Down
5 changes: 2 additions & 3 deletions haskell-commands.el
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ You can create new session using function `haskell-session-make'."
(haskell-process-send-startup process)
(unless (memq (haskell-process-type)
;; These all set the proper CWD.
(list 'cabal-repl 'cabal-new-repl 'stack-ghci))
(list 'cabal-repl 'stack-ghci))
(haskell-process-change-dir session
process
(haskell-session-current-dir session)))
Expand Down Expand Up @@ -736,8 +736,7 @@ function `xref-find-definitions' after new table was generated."
Add <cabal-project-dir>/dist/build/autogen/ to GHCi seatch path.
This allows modules such as 'Path_...', generated by cabal, to be
loaded by GHCi."
(unless (or (eq 'cabal-repl (haskell-process-type))
(eq 'cabal-new-repl (haskell-process-type))) ;; redundant with "cabal repl"
(unless (eq 'cabal-repl (haskell-process-type))
(let*
((session (haskell-interactive-session))
(cabal-dir (haskell-session-cabal-dir session))
Expand Down
97 changes: 61 additions & 36 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
;; 2020 Marc Berkowitz <mberkowitz@github.com>

;; Author: Herbert Valerio Riedel <hvr@gnu.org>

Expand Down Expand Up @@ -28,6 +29,7 @@

(require 'compile)
(require 'haskell-cabal)
(require 'haskell-customize)
(require 'ansi-color)
(eval-when-compile (require 'subr-x))

Expand Down Expand Up @@ -67,7 +69,7 @@ For legacy compat, `%s' is replaced by the stack package top folder."

(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.
"Default build command to use for `haskell-cabal-build' when no cabal or stack file is detected.
The `%s' placeholder is replaced by the current buffer's filename."
:group 'haskell-compile
:type 'string)
Expand All @@ -78,12 +80,14 @@ The `%s' placeholder is replaced by the current buffer's filename."
:group 'haskell-compile
:type 'boolean)

(defcustom haskell-compile-ignore-cabal nil
"Ignore cabal build definitions files for this buffer when detecting the build tool."
:group 'haskell-compile
:type 'boolean)
(make-variable-buffer-local 'haskell-compile-ignore-cabal)
(put 'haskell-compile-ignore-cabal 'safe-local-variable #'booleanp)
(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
Expand Down Expand Up @@ -144,10 +148,7 @@ messages pointing to additional source locations."
;;;###autoload
(defun haskell-compile (&optional edit-command)
"Run a compile command for the current Haskell buffer.

Locates stack or cabal definitions and, if found, invokes the
default build command for that build tool. Cabal is preferred
but may be ignored with `haskell-compile-ignore-cabal'.
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.
Expand All @@ -169,35 +170,59 @@ base directory for build tools, or the current buffer for
(interactive "P")
(save-some-buffers (not compilation-ask-about-save)
compilation-save-buffers-predicate)
(let ((cabaldir (and
(not haskell-compile-ignore-cabal)
(or (haskell-cabal-find-dir)
(locate-dominating-file default-directory "cabal.project")
(locate-dominating-file default-directory "cabal.project.local")))))
(if cabaldir
(haskell--compile cabaldir edit-command
'haskell--compile-cabal-last
haskell-compile-cabal-build-command
haskell-compile-cabal-build-alt-command)
(let ((stackdir (and haskell-compile-ignore-cabal
(locate-dominating-file default-directory "stack.yaml"))))
(if stackdir
(haskell--compile stackdir edit-command
'haskell--compile-stack-last
haskell-compile-stack-build-command
haskell-compile-stack-build-alt-command)
(let ((srcfile (buffer-file-name)))
(haskell--compile srcfile edit-command
'haskell--compile-ghc-last
haskell-compile-command
haskell-compile-command)))))))

(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)
(setq dir (buffer-file-name)))
((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 dir 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* ((default (or (symbol-value last-sym) fallback))
(let* ((dir-or-file (or dir-or-file default-directory))
(last-pair (symbol-value last-sym))
(last-command (car last-pair))
(last-dir (cdr last-pair))
(default (or (and last-dir (eq last-dir dir-or-file) last-command)
fallback))
(template (cond
((null edit) default)
((eq edit '-) alt)
Expand All @@ -210,7 +235,7 @@ base directory for build tools, or the current buffer for
(file-name-base (directory-file-name dir-or-file))
(file-name-nondirectory dir-or-file))))
(unless (eq edit'-)
(set last-sym template))
(set last-sym (cons template dir-or-file)))
(let ((default-directory dir))
(compilation-start
command
Expand Down
Loading