Make
Learning goals
You will learn the following concepts/skills:
- Build systems – Create make files for compilation and testing.
Starter code
You will create your Makefile from scratch (i.e., starting with a blank file).
About make
and the Makefile
The make
tool is used to build software projects and perform other operations
(e.g., running tests, cleaning up intermediate files, etc.). It is widely used by real-world
projects in industry and open source projects.
Instructions for how to build a project and perform other actions are given in a file called
Makefile
. That file contains instructions for how to build the executable, as well
as rules for how to build or generate any intermediate files (e.g., object files).
Structure of a Makefile
A Makefile is a text file named “Makefile” (exactly).
It may contain variables, rules, and a .PHONY
target.
# VARIABLES LHS=RHS LHS=RHS … # RULES target: prerequisites… action action … target: prerequisites… action action … # .PHONY TARGET .PHONY: target target …
Variables. The variables section (at the top) contains filenames, and other information that you will use in the commands that build and manage your project. In this homework, we have specified exactly what variables you need in the Requirements table.
Rules. A rule consists of a target, ≥0 prerequisites, and ≥0 actions.
There are two types of rules.
-
File rules
tell
make
how to generate a single file by executing ≥1 actions (bash commands). The name of a file rule is always the filename that it is supposed to generate. For example, you could use a file rule called test_mintf to generate the executable (e.g., test_mintf) for that project by compiling the code (e.g.,gcc mintf.c test_mintf.c -o test_mintf
). In more complex projects, there are often hundreds or thousands of files to be compiled or otherwise generated before the final executable is created. This is whatmake
was originally designed for. -
Phony (command) rules
let you define a sequence of ≥1 actions (bash commands) that can be run
by
make
as a convenience. Essentially, phony (command) rules are like scripts for automating and simplifying routine commands that developers need to run while working on a project. The name of a phony (command) rule should be a word (usually a verb) that describes what happens when you run those actions. For example, in HW07, you will create a phony (command) rule calledsubmit
that submits your code by calling264submit
along with all of the arguments that it requires.
Targets.
The target of a rule is just the name of the rule.
For file rules, the target will be the name of the
file to be generated (e.g., test_mintf). For phony (command) rules, the target will be
a word (usually a verb) that describes what the actions in that rule do
(e.g., submit
, test
, etc.).
While most rules (in production code) generate a single file (e.g., an executable), some rules
are essentially just commands to do something useful, and are not intended to generate
a particular file. For example, in HW07, you will
create rules to test your code, check for inadequate tests, submit your code, and run the pretester.
These are known as phony (command) rules. They will be declared in the .PHONY
target
at the bottom of the file. They are simply listed on that one line, separated by spaces.
Prerequisites. The prerequisites… are a list of targets upon which that rule depends. For example, test_count_words depends on count_words.c, count_words.h. If you were to use your new console log macros in your count_words.c, then log_macros.h would also be a prerequisite of test_count_words.
Actions. The actions in each rule consist of commands that could be entered in your shell (bash). Your bash aliases will not work here. That means simply typing gcc would not include the flags we require for the class. Therefore, you will create some variables to ensure that those flags are included whenever you compile. (These are given in the Requirements table below.)
⚠Actions must be indented with a tab character, not four spaces. If you are using Vim with the .vimrc we provided, this is taken care of for you.
The .PHONY
target
At the end of your Makefile you should have a single rule called .PHONY.
Its prerequisites should be the targets for every phony (command) rule in your make file.
It should have no actions.
Running make
make
From bash, running make
(with no arguments) will run
the first (top) rule in your make file.
make target
To run a specific rule, include its name as an argument to make
. For example,
to run the submit
rule, you would type make submit
from bash.
When make
runs a rule, it first checks its prerequisites.
First, make
decides whether to proceed with the current rule at all.
If ⓐ the current rule is a file rule,
ⓑ the file it refers to (i.e., is supposed to generate) already exists,
and
ⓒ all prerequites are names of files that exist and are older than the file the
current rule refers to, then we stop and do nothing more for the current rule.
Second, if we do need to proceed with the current rule, then
make
goes through each prerequisite one-by-one. If the prerequisite is the target of any rule
in this make file, then make
runs that target following this same process.
(It is a form of recursion.) Otherwise, if the prerequisite is not a target, then make
assumes it is the name of a file that will be needed by the actions in the current rule and simply verifies
that it exists; if not, make
quits with an error message.
Default target.
The first rule in a Makefile is the default. Simply running make
(from bash)
will build the first target. For example, if your first target in your Makefile is
test_count_words, then running make
(from bash) would be equivalent to running
make test_count_words
.
1. Set up your homework directory.
You will use your code from HW06.
Create a directory for HW07.
you@eceprog ~/
$
cd 264
you@eceprog ~/264
$
mkdir hw07
you@eceprog ~/264
$
cd hw07
you@eceprog ~/264/hw07
$
Copy your HW06 code into your HW07 directory.
you@eceprog ~/264/hw07
$
cp ../hw06/* ./
you@eceprog ~/264/hw07
$
The most important file you will create in HW07 is Makefile.
2. Create your Makefile
You will create your Makefile from scratch. However, you will be using it with your miniunit.h from HW06 and log_macros.h from HW05.
Your Makefile will have instructions for how to not only compile a program, but also run your tests, submit, run the pretester, and even run a code coverage tester to search for areas of your code that your tests may not be covering.
A Makefile must be specific to a particular project. For HW07, you will create a Makefile for the count_words project that is included in the starter. For the rest of this semester, you will simply change a few variables at the top of your Makefile to enable it to work for each future assignment.
The specifics on what your Makefile must contain are in the requirements table.
Reminder: You may hand-copy snippets from this homework description (HW07), but do not copy-paste. You won't learn as well, and it won't work anyway.)
Instructions
- Create a very simple Makefile to just compile test_count_words
-
Create a blank file called Makefile.
you@eceprog ~/264/hw07 $
vim Makefile
- Add one rule to compile test_count_words as follows.
test_count_words: count_words.c test_count_words.c count_words.h miniunit.h log_macros.h gcc -o test_count_words count_words.c test_count_words.c -g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic
-
Create a blank file called Makefile.
- Test the Makefile from bash.
- From bash:
you@eceprog ~/hw07 $
make
- From Vim:
:make
- From bash:
- Modify your Makefile to use the CC variable.
- Set the CC variable to
gcc
at the top of Makefile like this:
CC=gcc
- Replace
gcc
with$(CC)
in your test_count_words rule. - Test that everything works so far.
- Set the CC variable to
- Modify your Makefile to use the CFLAGS variable.
- Set the CFLAGS variable to
-g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic
at the top of Makefile like this:
CFLAGS=-g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic
- Replace
-g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic
with$(CFLAGS)
in your test_count_words rule. - Test that everything works so far.
- Set the CFLAGS variable to
- Modify your Makefile to set and use the other variables.
- Set the BASE_NAME variable to
count_words
at the top of Makefile like this:
BASE_NAME=count_words
- Set the SRC_C variable to
count_words.c
using$(BASE_NAME)
to minimize duplication, like this:
SRC_C=$(BASE_NAME).c
- Set the TEST_C variable to
test_count_words.c
using$(SRC_C)
to minimize duplication, like this:
TEST_C=test_$(SRC_C)
- Add all of the rest of the variables specified in the Requirements table. Wherever possible, use existing variables to minimize duplication.
- Use the variables you defined in your test_count_words rule. At this point, there should be no mention of
…count_words…
anywhere in the rule. - Test that everything works so far.
- Set the BASE_NAME variable to
- Add a phony (command) rule to submit your code
- Add a rule called
submit
that submits the assignment. Use the variables you defined. You can add more if needed. Yoursubmit
rule does not require any prerequisites. However, if you wantmake submit
to check that all required files are present, then you may optionally add$(SUBMIT_FILES)
as a prerequisite. It is your choice. - Add the following line to the bottom of your Makefile:
.PHONY: submit
- Type
make submit
in bash or:!make submit
in Vim to test your rule for the submit phony target. . You may submit to it to test your Makefile, but will not be scored and will not affect your grade.
- Add a rule called
-
Add a phony (command) rule to run your tests.
-
Add a rule called
test
. Hand-copy the following code.test: $(EXECUTABLE) ./$(EXECUTABLE) - Add
test
to your.PHONY
target. - Type
make test
in bash or:!make test
in Vim to test your rule for the test phony target.
-
Add a rule called
-
Add a phony (command) rule to run the pretester.
- Add a rule called
pretest
that runs the pretester. It should call your submit rule first (using a prerequisite) and then call264test
. Use the variables you created (includingASG_NICKNAME
) in the actions for this rule. ⚠ Do not enter “HW55”, “count_words.c”, or “test_count_words.c” directly. - Add
pretest
to your.PHONY
target. - Type
make pretest
in bash or:!make pretest
in Vim to test your rule for the pretest phony target. It is a dummy pretester that gives everyone 100%.
- Add a rule called
- Add a phony (command) rule called
clean
that deletes the executable files (test_count_words and test_count_words_gcov). The command to delete a file(s) isrm files…
. When working at the terminal, we have that command set to prompt for confirmation. To ensure thatmake
does not prompt for confirmation, you add the-f
flag. Thus, the whole command will berm -f files…
.make clean
should delete the executables and any data files created by the code coverage check (if any). To delete file(s), userm -f ▒▒▒
. The-f
tells the rm command not to prompt for confirmation or print an error if no such file exists.⚠ Be careful not to delete your .c or .h files, or your Makefile. - Add the remaining rules, as specified in the Requirements table. Some additional
hints are below. Be sure all rules except for
$(EXECUTABLE)
are listed in your.PHONY:
line.
3. Test coverage checking
Code coverage testing—including statement coverage—can help you find bugs in your code by discovering aspects of the functionality that your tests are not exercising. In this section, you will learn how to use test coverage checking, and integrate that functionality into your Makefile.
We will be using one kind of coverage, called line coverage. It runs your tests and monitors which statements in your implementation code were executed. If any statements were not executed, it typically indicates that either your tests are not exercising all of the functionality. It is also possible that you have some dead code. Either way, it should be rectified.
We will be using a tool called GCOV, which works in conjunction with GCC. To check test coverage using GCOV, use this:
# Compile a special version of executable with coverage checking gcc -o executable source files... -ftest-coverage -fprofile-arcs # Run the executable to record coverage statistics ./executable # Print a coverage summary gcov -f one source file
Detailed information, such as which lines were executed, can be found by opening the count_words.c.gcov in your editor.
Instructions
- Try running GCOV from bash. Later, you will be adding this to your Makefile but
it is important to first ensure that it works for you—and that you understand what
it does.
You could do this with the included count_words program, but to show this in context of something more familiar, we will demonstrate using and implementation of HW04.
you@eceprog ~/HW07 $
gcc -o test_mintf_gcov mintf.c test_mintf.c -fprofile-arcs -ftest-coverage
you@eceprog ~/HW07 $
./test_mintf_gcov
▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒you@eceprog ~/HW07 $
gcov -f mintf.c
Function 'mintf' Lines executed:61.29% of 31 Function 'print_integer' Lines executed:75.00% of 8 Function '_print_integer_digit' Lines executed:81.82% of 11 File 'mintf.c' Lines executed:68.00% of 50 Creating 'mintf.c.gcov'The example above is what you would see if you have not tested adequately. In this case, only 68% of the lines in count_words.c were executed at all. None of the three functions was tested thoroughly. Hopefully, the stats for your code will report 100% coverage.This metric excludes blank lines, comments, and lines containing only braces, so 100% line coverage should be attainable. - Add a rule called
coverage
to your Makefile to check code coverage. Use the variables you defined (e.g., CC, CFLAGS, etc.) wherever possible. Have the GCC command Hint: It should have two .c files as prerequisites, and three actions. Don't forget to addcoverage
to the list of phony targets (i.e.,.PHONY: submit test ▒▒▒▒▒▒▒▒▒▒▒▒▒
). - Type
make coverage
in bash or:!make coverage
in Vim to test your rule for the coverage phony target. Verify that it created five new files: test_count_words_gcov, count_words.c.gcov, count_words.gcda, count_words.gcno, test_count_words.gcda, test_count_words.gcno. - Modify the
clean
rule in your Makefile to remove the data files created by GCOV. You should use wildcards (*.c.gcov *.gcda *.gcno
). - Type
make clean
in bash or:!make clean
in Vim to test your rule for the clean phony target. Verify that the five files created by GCOV (test_count_words_gcov, count_words.c.gcov, count_words.gcda, count_words.gcno, test_count_words.gcda, test_count_words.gcno) were deleted.
Requirements
- Your submission must contain each of the following files, as specified:
file contents Makefile makefile Variable definitions (top section)name value ASG_NICKNAME HW55 BASE_NAME count_words EXECUTABLE test_count_words EXECUTABLE_GCOV test_count_words_gcov TEST_C test_count_words.c SRC_C count_words.c SRC_H count_words.h log_macros.h miniunit.h SUBMIT_FILES count_words.c test_count_words.c log_macros.h miniunit.h Makefile SHELL /bin/bash CC gcc CFLAGS -g -std=c11 -Wall -Wshadow -Wvla -Werror -pedantic CFLAGS_GCOV $(CFLAGS) -fprofile-arcs -ftest-coverage Rulestarget name action(s) $(EXECUTABLE) Compile the executable. - Reminder: Use variables in all rules, wherever possible.
test Run executable (test_count_words). submit Submit the assignment using 264submit. - Rule will look like this:
submit: ▒▒▒ 264submit ▒▒▒
- Reminder: Use variables. There should be no raw filenames in this rule.
pretest Pretest the assignment using 264test. - Rule should submit before pretesting.
- Simply include your
submit
target as a prerequisite ofpretest
(i.e.,pretest: submit
) - This rule is simple, so we'll give it to you:
pretest: submit 264test $(ASG_NICKNAME)
coverage Check the code coverage of your tests. The executable you create should be called test_count_words_gcov. clean Delete all files created by any of the above rules. - Use the variables wherever possible to avoid duplicating information.
- Information in the variables (above) should not be duplicated elsewhere. ◀◀◀◀◀◀
- Where possible, define variables in terms of existing variables.
Ex:SRC_C=$(BASE_NAME).c
-
The strings
test_
(lowercase),HW55
,count_words
,gcc
,-Wall
,log_macros.h
, andminiunit.h
should appear only once (each) in your Makefile.
- Use prerequisites to reduce duplication and ensure each action has what it needs.
- A rule should depend (directly or indirectly) on every file it uses.
- Do not repeat exactly the same compilation command in multiple places.
- Do not add pointless (unnecessary) prerequisites.
- Ex: Any rule that depends on test_count_words should not list count_words.c or test_count_words.c as prerequisites since they are implied by test_count_words and thus unnecessary.
- Ex:
make clean
should not depend on anything.
- miniunit.h should use log_macros.h to the extent possible—at least use the ANSI_▒▒▒ constants.
make submit
should result in:
264submit HW55 count_words.c test_count_words.c log_macros.h miniunit.h Makefile
log_macros.h (same as HW05)miniunit.h (same as HW06)test_count_words.c (same as HW06) - You may hand-copy any code snippets you find in this homework
description into your HW07 submission.
- ⚠ Do not use copy-paste. You learn more from hand-copying unfamiliar syntax. Expect problems if you ignore this.
- Adaptation is strongly recommended. Some snippets may not work in your file as is.
- Be sure you understand what you are copying. Correct functioning of your code is your responsibility.
- Copying from this page is not necessary. This permission is given as a convenience, since some of the syntax may be unfamiliar, and this homework is more tightly specified than most others.
-
You may use any of the following:
header functions/symbols allowed in… stdbool.h bool
,true
,false
*.c
,*.h
stdio.h printf
,stdout
*.c
,*.h
string.h strcmp
test_count_words.c
,miniunit.h
stdlib.h EXIT_SUCCESS
test_count_words.c
- miniunit.h should have
#include "log_macros.h"
so that users of miniunit.h don't need to include both. - Submissions must meet the code quality standards and the course policies on homework and academic integrity.
- Write multi-line macros with one C statement on each line. Do not try to cram many statements on a single line of code. That would not be readable.
- Indent your macros similarly to regular C code. Your code must be readable.
Submit
To submit HW07 from within your hw07 directory,
type
264submit HW07 Makefile miniunit.h log_macros.h test_count_words.c
Pre-tester ●
The pre-tester for HW07 has not yet been released. As soon as it is ready, this note will be changed.
Q&A
Shouldn't a Makefile refer to .o files?
We are using makefiles in a simplified manner that does not require awareness of .o files.What is HW55? Is that a typo?
HW55 is a dummy assignment that you will use to test the rules in your Makefile that deal with assignment submission and pretesting. It will not be scored and will not affect your grade.HW07 is the “real” assignment you are doing here.Should
No.make coverage
depend on $(EXECUTABLE)?Do I call
Callmake $(EXECUTABLE)
ormake test_count_words
?make test_count_words
(from bash).Can my Makefile refer to the ANSI color constants in log_macros.h?
No. They must be duplicated in Makefile. Themake
tool cannot read C code directly.Updates
10/3/2022 - Clarified the Structure of a Makefile section.
2/22/2022 - TEST_EXPECTED, TEST_ACTUAL, and EXECUTABLE_DBG are not required.
- expected.txt must not be included in SUBMIT_FILES.
- Screenshot was updated to reflect the correct variable names.
- Requirement about referring to variables wherever possible was clarified by
adding the corollary that
test_
(lowercase),HW55
,count_words
,gcc
,-Wall
,log_macros.h
, andminiunit.h
should appear only once each in your Makefile.
2/27/2022 - TEST_EXPECTED and expected.txt should not be referenced anywhere.
3/6/2022 - Trivial: Reordered variables in Requirements table.