Setting up flymake to use ecj

Background

I prefer emacs when I write code, but I also wanted to try to see what flymake could do. Compilation in the background and direct feedback on bad syntax can sometimes be nice.

I started to look at the existing versions, most of them were either using jde or jikes or both. I do not like jde, it does too much and since jikes is no good for java/6 wich is all I code nowdays I started working on my own version of flymake/ecj.

One of the first things that I noticed was that the compilation process used in standard flymake is:

  1. create temporary files
  2. start compilation process
  3. wait for compilation process to die

For a java compiler that is no good, due to the jvm having a startup cost of 1-5 seconds depending on hardware. This meant hacking flymake to run one java process in the background and send command to it instead of starting new all the time.

I selected ecj, the eclipse compiler for a few reasons, easier api and more warnings are the two big reasons.

Setting it up

Start by getting my hacked version of flymake.el and store it someplace with your other elisp packages.

Get the compiler java compiler binary and unpack it. It includes the tiny source code as well so if you want to modify it you can easily do so.

Add the needed lines to your .emacs to start using flymake:

(add-to-list 'load-path "~/pkg/flymake/cvs")
(require 'flymake)
;(setq flymake-log-level 2) ; set this when debugging flymake
(setq flymake-compiler-jar "/home/robo/src/java/ecj-flymake/jars/compiler.jar")
I also prefer to change a few simple things:
(global-set-key [f4] 'flymake-display-err-menu-for-current-line)
(global-set-key [f3] 'flymake-goto-next-error)

(custom-set-faces
 '(flymake-errline ((((class color)) (:underline "Red"))))
 '(flymake-warnline ((((class color)) (:underline "Orange")))))
If you work on larger projects it can also be nice to turn off recompilation on newline:
(setq flymake-start-syntax-check-on-newline nil)
You can also increase the time for recompiles after changes:
(setq flymake-no-changes-timeout 10)
The projects I work on are usually laid out a bit differently, so to get flymake to handle the different directory layouts we need to tell flymake where the different sources, classes and external libs can be found. You can also change the flags to ecj here if you want.
(defun compile-server-dir-init (base-dir src-dir lib-dir)
  (let* ((temp-file (flymake-init-create-temp-buffer-copy 
		     'java-ecj-create-temp-file))
	 (jar-files (files-in-below-directory-safe base-dir lib-dir))
	 (class-path (mapconcat 'identity jar-files ":"))
	 (command (format (concat "-Xemacs -1.6 -proceedOnError -proc:none " 
				  "-warn:-serial -warn:+uselessTypeCheck,unnecessaryElse -warn:+over-ann "
				  "-sourcepath %s %s %s\n")
			  (get-source-dir base-dir src-dir) 
			  (get-class-path-string class-path) 
			  temp-file)))
    (list command)))

(defun compile-server-rabbit-init ()
  (compile-server-dir-init "/home/robo/src/java/RabbIT3/" "src" "external_libs"))

(push '("/home/robo/src/java/RabbIT3/src.+\\.java$" 
	compile-server-rabbit-init 
	compile-server-flymake-cleanup)
      flymake-allowed-file-name-masks)
Now we also tell emacs to start flymake when entering java mode:
(push '("\\(.*?\\):\\([0-9]+\\): error: \\(.*?\\)\n" 1 2 nil 2 3 
	(6 compilation-error-face)) compilation-error-regexp-alist)

(add-hook 'java-mode-hook 'flymake-mode-on)
You can find my .emacs here for more examples.