A couple of days ago I wrote about using fly-make. Even though I had only used flymake with C++ code, I tend to do most of my work related coding in Java.
Curiosity (it seems I am not the only one) led me to the Emacs wiki in order to find elisp code that will allow me to use flymake with Java. Turns out there are a couple of options, of course none of them worked (just my luck), but I will share them nonetheless:
- JdeeFlymake
- A flymake extension for Jdee that uses jikes. Since jikes seems to have died when IBM adopted Eclipse this mode is pretty useless except for code references.
- Jdee Eclipse Compiler Server
- Using Eclipse’s ‘JDT Core Batch Compiler’ this extension can run a compile server or do a-process-per-time checking. This actually came very close to working, however it hung my Emacs for very strange reasons, however it gave me enough of a start to get to a working solution.
Since none of the provided means worked, what is a good coder to do? Right! Write your own. Taking a look at the already existing modes I found that creating a ‘a-process-per-time’ check is actually quite easy and on my MacBook it is hardly noticeable. So here is the lisp code that I added to my .emacs:
(require 'flymake) (defun flymake-java-ecj-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy 'jde-ecj-create-temp-file)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) ;; Change your ecj.jar location here (list "java" (list "-jar" "/path/to/ecj.jar" "-Xemacs" "-d" "/dev/null" "-source" "1.5" "-target" "1.5" "-proceedOnError" "-sourcepath" (car jde-sourcepath) "-classpath" (jde-build-classpath jde-global-classpath) local-file)))) (defun flymake-java-ecj-cleanup () "Cleanup after `flymake-java-ecj-init' -- delete temp file and dirs." (flymake-safe-delete-file flymake-temp-source-file-name) (when flymake-temp-source-file-name (flymake-safe-delete-directory (file-name-directory flymake-temp-source-file-name)))) (defun jde-ecj-create-temp-file (file-name prefix) "Create the file FILE-NAME in a unique directory in the temp directory." (file-truename (expand-file-name (file-name-nondirectory file-name) (expand-file-name (int-to-string (random)) (flymake-get-temp-dir))))) (push '(".+\\.java$" flymake-java-ecj-init flymake-java-ecj-cleanup) flymake-allowed-file-name-masks) (push '("\\(.*?\\):\\([0-9]+\\): error: \\(.*?\\)\n" 1 2 nil 2 3 (6 compilation-error-face)) compilation-error-regexp-alist) (push '("\\(.*?\\):\\([0-9]+\\): warning: \\(.*?\\)\n" 1 2 nil 1 3 (6 compilation-warning-face)) compilation-error-regexp-alist)
As you might have noticed, you need ecj.jar. I just grabbed it from the Eclipse download site, it’s a relatively small jar of 1.5 MB. What this code gives you is the ability to enable flymake-mode in an JDEE controlled java file and see the following happen when you make a mistake or just do something dumb, like create unused variables:
Flymake itself comes with a function to display the error of a line in a nice X11 (or carbon for that matter) menu, however I am not one for the menus, so I wrote a simple function to display the error or warning in the minibuffer:
(defun credmp/flymake-display-err-minibuf () "Displays the error/warning for the current line in the minibuffer" (interactive) (let* ((line-no (flymake-current-line-no)) (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no))) (count (length line-err-info-list)) ) (while (> count 0) (when line-err-info-list (let* ((file (flymake-ler-file (nth (1- count) line-err-info-list))) (full-file (flymake-ler-full-file (nth (1- count) line-err-info-list))) (text (flymake-ler-text (nth (1- count) line-err-info-list))) (line (flymake-ler-line (nth (1- count) line-err-info-list)))) (message "[%s] %s" line text) ) ) (setq count (1- count)))))
So, you could say I am a very happy camper now, this is a useful addition to my coding tool-chain and allows for writing cleaner code, also faster since I no longer have to wait for the build scripts to run through everything.
Now, the only thing remains is documentation lookup methods for the Java API documentation.
10 Comments
I have copied the code in the blog, and saved as flymake-java.el, appended “(provide ‘flymake-java)”, modified the .emacs, to load the flymake-java.el, but, when i open a java file, add a unused variable, i can not see the effect you showed in the attached images. any other things i have to configure?
mmao,
Alright, in the Java buffer, does the modeline show that Flymake is active? If so, set the variable flymake-log-level to 3 and see what is returned from the compiler and what flymake makes of it (output is to the *Messages* buffer).
If flymake is not yet active, type M-x flymake-mode in the Java buffer
Arjen
Hey, this sounds really cool, exactly the thing I need
However, I get the error “Flymake: Configuration error has occured while running (ecj -Xemacs …..) Flymake will be switched off.
I’m using Emacs 23.0.50 (from CVS) and have set up flymake to use the ecj utility (installed with the Debian Eclipse packages):
(list “ecj” (list “-Xemacs”
“-source” “1.5″
“-target” “1.5″
“-proceedOnError”
“-sourcepath” (car jde-sourcepath)
“-classpath”
“/opt/jdk/jre/lib/rt.jar”
“/usr/share/java/ecj.jar”
local-file))))
(I’ve hard coded the bare bone CP to help debug as the error window takes 10 workspaces to display with the jde classpath)
Executing the line given in the error window manually on the command line works.
How can I debug this further? I don’t see anything in *messages*, or with –debug-init.
Torstein,
what happens when you run the command on in a console outside emacs? it should be quite easy to see the problem then…
Hi, I’ve got it working now. The problem was simply that I had to use the ecj.jar from the eclipse website and not the one supplied in the Debian package.
Thank you very much for your blog post, Emacs now rocks even more than before
Nifty. However, it breaks regular non-flymake compilation when there are warnings or errors that match the two entries that you push onto compilation-error-regexp-alist (as they refer to regexp match 6, and there is no 6th group). Can the alteration of compilation-error-regexp-alist be done in a flymake-local way, or the regexp be fixed to be compatible with regular compile?
I got this to work with ecj-3.4M4.jar and with no JDE dependencies (or JDE integration, depending on how you look at it).
It would be nice to integrate with Ant or to use a table mapping directory prefixes to classpaths, instead of using a fixed classpath, but for now I just used ant’s pathconvert in a target to grab the classpath and then set it manually for my project.
For those of you who don’t use JDE, here’s a starting point.
- I had to take out “-sourcepath” because it caused the entire hierarchy to be compiled.
- I put abs around random to avoid leading hyphens in the directory names, which might confuse some cygwin or linux tools.
- On windows, I set (setq temporary-file-directory “e:/temp”) to avoid spaces in temp file names.
- I added comments for controlling warning level and for logging output (to aid with debugging the installation).
(defconst ecj-jar-path "/path/to/ecj-3.4M4.jar")
(defvar flymake-java-version "1.5")
(defvar flymake-java-classpath ".;/path/to/elsewhere")
(defun flymake-java-ecj-init ()
(let* ((temp-file (flymake-init-create-temp-buffer-copy
'flymake-ecj-create-temp-file))
(local-file (file-relative-name
temp-file
(file-name-directory buffer-file-name))))
(list "java" (list "-jar" ecj-jar-path "-Xemacs" "-d" "none" ; "-warn:none"
"-source" flymake-java-version "-target" flymake-java-version "-proceedOnError"
"-classpath" flymake-java-classpath
; "-log" "e:/temp/foo.xml"
local-file))))
(defun flymake-java-ecj-cleanup ()
"Cleanup after `flymake-java-ecj-init' -- delete temp file and dirs."
(flymake-safe-delete-file flymake-temp-source-file-name)
(when flymake-temp-source-file-name
(flymake-safe-delete-directory (file-name-directory flymake-temp-source-file-name))))
(defun flymake-ecj-create-temp-file (file-name prefix)
"Create the file FILE-NAME in a unique directory in the temp directory."
(file-truename (expand-file-name (file-name-nondirectory file-name)
(expand-file-name (int-to-string (abs (random))) (flymake-get-temp-dir)))))
(push '(".+\\.java$" flymake-java-ecj-init flymake-java-ecj-cleanup) flymake-allowed-file-name-masks)
(push '("\\(.*?\\):\\([0-9]+\\): error: \\(.*?\\)\n” 1 2 nil 2 3 (6 compilation-error-face)) compilation-error-regexp-alist)
(push ‘(”\\(.*?\\):\\([0-9]+\\): warning: \\(.*?\\)\n” 1 2 nil 1 3 (6 compilation-warning-face)) compilation-error-regexp-alist)
(provide ‘flymake-java)
Very useful. I’m grateful!
However, I don’t know yet how to integrate your custom function credmp/flymake-display-err-minibuf so that I don’t have to execute it manually. What would I have to do to execute it automatically when I am in the same line of an error?
(car jde-sourcepath) seems to return nothing for me.
Emacs 22 with latest jdee.
Any suggestions?
Hi and thanks for this post! Got it working, and it works really well (while it works). Flymake seems to stop working after a while of coding, can’t figure out why.
Flymakes log look like this:
%screated an overlay at (5489-5515)
%screated an overlay at (5554-5616)
%sGraph.java: 0 error(s), 7 warning(s) in 0.91 second(s)
Above flymake-log shows where it still is working, but from this part and on the log will look like this:
%sstarting syntax check as more than 1 second passed since last change
%sflymake is running: nil
%sfile /Users/anton/Documents/skola/ai/lab2/src/MySearcher.java, init=flymake-java-ecj-init
%sstarting syntax check as more than 1 second passed since last change
%sflymake is running: nil
%sfile /Users/anton/Documents/skola/ai/lab2/src/MySearcher.java, init=flymake-java-ecj-init
%sstarting syntax check as more than 1 second passed since last change
%sflymake is running: nil
%sfile /Users/anton/Documents/skola/ai/lab2/src/MySearcher.java, init=flymake-java-ecj-init
%sstarting syntax check as more than 1 second passed since last change
%sflymake is running: nil
Flymake still detects changes in the source buffer but no errors or warnings are found.
I get some warnings in the log above this part, but what I can see they are nowhere near the problem, they look like this:
%sparsed ‘incorrect classpath: ./src’, no line-err-info
%sFailed to delete dir /private/var/folders/QC/QCsNFT66FvmIZvj-tQepsk+++TI/-Tmp-/-245432196/, error ignored
Restaring emacs will make flymake work again for some time, just restarting jde or flymake wont affect the problem.
Any ideas of what can be the problem?
One Trackback
[...] to the blog post at http://www.credmp.org, I’ve got Java flymake support working in Emacs. It works surprisingly well (and fast) using [...]