Since I switched to a Mac as my primary development platform I am using
aquamacs for all my my coding needs. It is a fast native app, looks good, is highly customizable and together with
distel it integrates very well with Erlang. Just one thing was giving me a headache:
Compiling from within Emacs based on an EmakefileI tried some script by Alexey Lebedeff recently
discussed on the Erlang mailing list, but at first try I did not manage to adapt the script to fit my needs. The situation got worse when I started to use non-Erlang source files such as templates and lexer / parser grammars which all get compiled to beam files and need to be reloaded when compilation was successful. After digging a bit into Emacs Lisp I came up with the following approach:
By pressing a function key (F13 in my case) emacs invokes via RPC at the Erlang application a custom compile command which in case of an Erlang source file switches to the directory of the Emakefile, runs a
make:all([load]). and switches back to the original directory of the currently edited file. If the source file is a non-Erlang, than custom code gets called to perform all the necessary steps until beam file reloading.
There are two implications which need to be considered before adapting this approach:
- An application must contain specific code which can be called by emacs and that code must be able to determine the location of the Emakefile.
- The approach can't be used for initial compilation, because emacs calls a function from an application module, which must be compiled already.
And of course you must have
distel installed. Below a code snippet showing the Emacs Lisp code, which goes into emacs.el (or Preferences.el in case of aquamacs):
(defun my-erlang-compile ()
(interactive)
(save-some-buffers (not compilation-ask-about-save) nil)
(save-excursion
(let ((thisdir default-directory))
(setq src-file-name buffer-file-name)
(with-current-buffer (get-buffer-create "*erl-output*")
(setq default-directory thisdir)
(erase-buffer)
(compilation-mode)
(toggle-read-only nil)
(setq compilation-current-error nil)
(display-buffer (current-buffer))
(erl-spawn
(erl-send-rpc (erl-target-node)
'distel
'eval_expression
(list (format "myapp_make:all(%S)." src-file-name)))
(erl-receive ()
((['rex ['ok string]]
(insert string))
(['rex ['error reason]]
(insert reason))
(other
(message "Unexpected: %S" other)))))))))
(add-hook 'erlang-mode-hook 'my-erlang-mode-hook)
(defun my-erlang-mode-hook ()
(setq inferior-erlang-machine-options '("-sname" "emacs"))
(define-key erlang-mode-map [f13]
(lambda () (interactive)
(progn
(my-erlang-compile))))
)
(add-hook 'foo-helper-mode-hook
(lambda ()
(define-key foo-helper-mode-map [f13]
(lambda () (interactive)
(progn
(my-erlang-compile))))))
With the elisp snippet from above, the corresponding Erlang application must contain a module
myapp_make which implements the function
all/1 :
all(Path) ->
case filename:extension(Path) of
".erl" ->
{ok, OldDir} = file:get_cwd(),
ok = file:set_cwd(my_find_emakefile_dir()),
make:all([load]),
ok = file:set_cwd(OldDir);
Ext ->
maybe_my_custom_stuff
end.