Recently I reviewed a bunch of code from students. The projects are in Java. To test each one, we:
- compile Java source file into class file
- run the class file
- examine output
Over time I’ll be getting more code, thus I want a simple workflow that will automatically adapt to having more source in the same directory. I can’t just list all the source files then write out a Bash script to compile + run everything, for example. As new code comes in, I want it to be included in the overall test run.
How would you accomplish this?
GNU Make is the bomb
I used my good old friend, GNU Make! This simple tool runs programs to turn files into other files. It understands dependencies, and won’t do work unless it needs to. For example, it’ll compile C files into objects, then at the end will link all the objects into a single executable. The input language has rules like “to make X into Y, run program Z”. You say “make Y”, the system will automatically go find file X, run program Z on it, giving you the result. If the file Y already exists, it won’t re-make it, and will exit.
Make understands dependencies. For example if it knows Y depends on X, and X has been edited after Y was created, Make understands that it has to re-build Y given the updated source X.
I often use Make for holding lots of tiny 1-2 line scripts, example make backup will copy all my important files into a compressed, timestamped tarball for archival purposes. I tell Make that source code is important, but object and executable files are not — thus my archive is small but contains all my valuable work.
Simple example: use Make to build a Java class file
Create this file, name it “Makefile” in the current directory. NOTE: the second line has a tab character — don’t use spaces! Make is ornery in this regard.
The % character means “anything”, and $< means “dependent file”. The above stanza says “to make a class file from a java file, run the “javac” command on the dependent Java file”.
To create a class file from Java source, type make myfile.class (if your source is in myfile.java)
Use Make to compile and test a single program
In my case, I want to type one command and have all my programs compiled and run so I can see everyone’s output all at once. For this to happen we’ll make all the class files like above. Then, for each class file we’ll run it to get the output.
How do we do this?
We use a pseudo suffix. The java files and class files exist on disk, and can be manipulated directly. However to say “run test on file X”, the output just goes to the screen, there’s nothing stored. To resolve this I tell Make: “hey, when I ask for X.test, build the X.class file if needed, then run it with the java command”. Make is happy and runs my commands. Since the “X.test” file isn’t created, when I ask Make to do it again, it’ll just re-run the commands. When we make X.class files, they live on disk, so Make won’t rebuild them. Test files don’t exist — “.test” is a pseudo-suffix — thus Make will always run my testing commands, which is what we want.
Here’s the Makefile which allows compiling and testing of Java programs:
java $(subst .class,,$<)
$ make JenExercise2.test
Real example: use Make to test all Java programs