Make your life easier with GNU Make.
I’ve been trying to keep at least one post per month going, and I was sick with stomach flu today, so here goes:
As our CS assignments get more and more complex, it’s starting to become time consuming to build, test, and submit each assignment. I could either do this every time I want to test:
java cs241.binasm < a2p8.asm > a2p8.mips java mips.array a2p8.mips < a2p8.sample.in java mips.array a2p8.mips < a2p8.onenode.in
Or I could just run
What is make?
GNU Make is a system for constructing executables or other non-source code files from source code. It allows you to specify dependencies for files and define rules for constructing them from source. For example, the rule to build
a2p8.mips would look like this:
a2p8.mips: a2p8.asm java cs241.binasm < a2p8.asm > a2p8.mips
This says, to make
java cs241.binasm < a2p.asm > a2p8.mips. To use
make, stick your ruleset in a file called
Makefile in the same directory as your source then run
make. This command will only run if
a2p8.asm has changed since the last time it was run. These dependencies stack as well. For instance, say I need to join two files together before I compile them. I can define a ruleset like this:
linked.mips: linked.asm java cs241.binasm < linked.asm > linked.mips linked.asm: a2p7.asm print.asm cat a2p7.asm print.asm > linked.asm
This will link the files together before it tries to compile them. It’ll also make sure it’s up to date whenever you change either
print.asm. The build rule at the top of the
Makefile is called the default rule. This is the one that gets run when you just run
make at the command line. You can also pass a parameter to tell
make to construct something specific. For instance, I could run
make linked.asm here to just build the linked file without actually compiling it to machine code.
Automatic Variables in Make
One of the principles of writing good code is DRY: Don’t Repeat Yourself. Make provides various ways to support this through the use of automatic variables. Automatic variables are ones that are set for you with useful values. For instance, the following rule:
linked.asm: a2p7.asm print.asm cat a2p7.asm print.asm > linked.asm
can be reduced to this:
linked.asm: a2p7.asm print.asm cat $^ > $@
$^ is a space separated list of all the dependencies.
$@ is the target. See GNU make - Automatic Variables for the full list and description of such variables.
Writing Implicit Rules
Another unnecessary piece of code duplication exists for compiling multiple files of the same type. For instance, the following is repetitive:
a2p8.mips: a2p8.asm java cs241.binasm < $< > $@ a2p7.mips: a2p7.asm java cs241.binasm < $< > $@
Instead, I can define my own implicit rule for building all
*.mips files like so:
%.mips: %.asm java cs241.binasm < $*.asm > $*.mips
Testing Using Make
One of the things I took away from my co-op term at The Working Group was the benefits of test driven development. Untested code is broken code, and manual testing is tedious and annoying. As your code gets more and more complicated, there will be more and more edge cases to deal with. These cannot be dealt with a single test case. Normally, you would be forced to write multiple input files and pass them each individually to the executable to see if everything worked. I don’t like having to do this. So instead, I defined my own ruleset:
ASSIGNMENT = a2p7 test: linked.mips Empty.test All.test %.test: $(ASSIGNMENT).%.in @echo ----------- $* ------------------ java mips.array linked.mips < $^ %.mips: %.asm java cs241.binasm < $*.asm > $*.mips linked.asm: $(ASSIGNMENT).asm print.asm cat $^ > $@
This one merits a bit of explanation. The default task here is
test, and it has no recipe associated with it. Instead, it just makes sure all of its dependencies are up to date.
All.test here aren’t actual files. They’re simple methods of referring to another rule, in this case the
%.test implicit rule.
%.test implicit rule depends on a corresponding input file. For example,
Empty.test will rely on
a2p7.Empty.in, which will then be passed in as input to the executable. The result from running looks like this:
$ make ----------- Empty ------------------ java mips.array linked.mips < a2p7.Empty.in Enter length of array: MIPS program completed normally. $01 = 0x000001c4 $02 = 0x00000000 $03 = 0x00000000 $04 = 0x00000000 $05 = 0x00000000 $06 = 0x00000000 $07 = 0x00000000 $08 = 0x00000000 $09 = 0x00000000 $10 = 0x00000000 $11 = 0x000001c4 $12 = 0x00000000 $13 = 0x00000000 $14 = 0x00000000 $15 = 0x00000000 $16 = 0x00000000 $17 = 0x00000000 $18 = 0x00000000 $19 = 0x00000000 $20 = 0x00000000 $21 = 0x00000000 $22 = 0x00000000 $23 = 0x00000000 $24 = 0x00000000 $25 = 0x00000000 $26 = 0x00000000 $27 = 0x00000000 $28 = 0x00000000 $29 = 0x00000000 $30 = 0x01000000 $31 = 0x8123456c ----------- All ------------------ java mips.array linked.mips < a2p7.All.in Enter length of array: Enter array element 0: Enter array element 1: Enter array element 2: 123 0 -456 MIPS program completed normally. $01 = 0xfffffe38 $02 = 0x00000003 $03 = 0x00000001 $04 = 0x00000000 $05 = 0x00000000 $06 = 0x00000000 $07 = 0x00000000 $08 = 0x00000000 $09 = 0x00000000 $10 = 0x00000000 $11 = 0x000001d0 $12 = 0x00000000 $13 = 0x00000000 $14 = 0x00000000 $15 = 0x00000000 $16 = 0x00000000 $17 = 0x00000000 $18 = 0x00000000 $19 = 0x00000000 $20 = 0x00000000 $21 = 0x00000000 $22 = 0x00000000 $23 = 0x00000000 $24 = 0x00000000 $25 = 0x00000000 $26 = 0x00000000 $27 = 0x00000000 $28 = 0x00000000 $29 = 0x00000000 $30 = 0x01000000 $31 = 0x8123456c
echo line above that simple outputs the name of the test before running it so it looks nicer on the console. The
@ before the echo prevents make from displaying the command, otherwise it would look like this:
$ make cat a2p7.asm print.asm > linked.asm java cs241.binasm < linked.asm > linked.mips echo ----------- Empty ------------------ ----------- Empty ------------------
Submitting to Marmoset from the Commandline
sudo gem install marmoset
Or you can browse the code yourself here: http://github.com/jlfwong/MarmosetSubmit
Here’s how you can integrate the submission process into your
submit: $(ASSIGNMENT).asm marmoset -u jlfwong -c cs241 -a $(ASSIGNMENT) -f $(ASSIGNMENT).asm
For now, it only submits - there’s no way of checking to see if it was successful or running the release tests. I can build that if people are really interested in that, or you can fork the github repo and do it yourself.
Well, that’s all for now. Time to study up for Electrodeath.