Search code examples
javabashvimjavac

Clean javac output on Windows for Vim, maybe via Unix (Cygwin) tools


I am new to Java, and installed the following JDK on Windows 10:

C:\Program Files\AdoptOpenJDK\jdk-11.0.8.10-openj9

I can invoke the compiler javac from Cygwin's Bash command line (in an x-terminal), but it creates many errors that seem to go to neither stdout or stderr. I need to send them to a file that I can peruse with Vim.

Here is my invocation command

# mk.bash
# -------
javac \
-classpath "/c/Program Files/.../cplex.jar" \
TestSetup.java

The classpath argument is irrelevant here, as I only want to focus on capturing javac's output in a Vim-perusable way.

Issuing ./mk.bash >| mk.out generates an empty mk.out, as does ./mk.bash 2>&1 >| mk.out. I've used the latter pattern for decades to redirect stderr to stdout and overwrite the destination file.

I can use the script command to send the javac output to mk.out:

script mk.out
./mk.bash
exit

I can then browse the error messages using Vim. However, the contents are obfuscated by many binary characters (image & link to file below). Normally, I can clean up messy files with dos2unix, but on this output, it quits due to binary characters.

As another way to clean up the non-text content, Vim has a fileformat=dos option which can be entered using :e ++ff=dos %. The e and % says to edit the current file, while ++ff=dos says to interpret the file as dos format (ff is fileformat). All this does is clean up visual artifacts due to the different line endings in Unix and DOS. All the error messages are still interspersed with what seem like Escape characters ^[.

Is there any way to get javac to generate only plain text output or to clean up the output?

Here is an image of the non-plain-text file in Vim:

enter image description here

I doubt it is all that relevant, but I'm following this webpage to compile a simple Java app TestSetup.java that invokes a 3rd party tool: https://kunlei.github.io/cplex/cplex-java-setup.


Solution

  • Bottom line up front

    The following Bash command sends both stdout and stderr from javac to both the screen and the file mk.out:

    javac -classpath /Some/NonExistent/Jar/File.jar TestSetup.java \
    2>&1 | tee mk.out
    

    How it was found

    I found that the problem was primarily the use of Bash piping, redirection, and the script command. The latter is responsible for the ANSI codes. I could not determine this previously because I could not capture the javac error messages in a file without script. So I first solved that problem, then compared the use of script with the absence of script. Here is what I found.

    The following bash command creates a DOS format file without the ANSI codes:

    javac -classpath '/Some/NonExistent/File.jar' TestSetup.java \
    >| mk.out 2>&1
    

    Before, I had: (1) 2>&1 >| mk.out instead of (2) >| mk.out 2>&1. For option (1), I thought stderr became directed to stdout, so I simply needed to direct stdout to mk.out.

    But that's not how it works. In option (1), the specification 2>&1 directs stderr to what stdout is currently directed at, i.e., the screen. The subsequent >| mk.out then directs stdout (and only std.out) to mk.out. Unfortunately, this meant that stderr was still going to the screen. Presto, mk.out is left empty (bad).

    In option (2), stdout is first directed to mk.out. The subsequent 2>&1 then directs stderr to what stdout is directed to, i.e., mk.out. Hence, the javac error messages get sent to mk.out. It did not contain ANSI codes, which is good.

    While option (2) works, it is not ideal; the error messages are not shown on the screen. This is where I fell back to script and found that it introduced the ANSI codes:

    script \
    -c "javac -classpath '/Some/NonExistent/File.jar' TestSetup.java" \
    mk.out
    

    Since the above solves the problem of redirecting stderr, I looked for bash options that avoid script. The following sends stderr to the screen and to mk.out:

    javac -classpath /Some/NonExistent/Jar/File.jar TestSetup.java \
    2>&1 | tee mk.out
    

    According to the explanation of option (1) above, it looks like it shouldn't work, but Bash makes an exception when it is used in a pipeline. Specifically, stderr is redirected to what stdout is directed to, but only after all redirections. In a pipeline, the above means that javac's stdout is directed to tee's stdin, and only then is javac's stderr directed to the target of javac's stdout, i.e., tee's stdin.

    For reference: Initial solution (javac's Xstdout option)

    I found that my javac has a command line option -Xstdout OutputFileName.txt specifically for this purpose.

    Unfortunately, not all javac documentation that I found online shows availability of this option. In that case, the most direct solution I found was :term cat The/Output/File.Path in Vim.

    Many vim alternatives are also shown on that page, as well as here

    For future reference, if needed, 3 fulsome pages that I found to be relevant are:

    Mention is made of ansi2txt, but you need Ubuntu for that.