In this lab, we are going to practice separate compilation using Makefile. We are going to design a reception bot for visitors using C++ code. You are given some source codes (.h/.cpp), which are almost complete. You will learn about the basic usage of Makefile, work with the Makefile and add a few lines in some files. Before implementation, you could read the Makefile lecture notes. The source files are available HERE.
Suppose you have a program that consists of 3 cpp files: file1.cpp
, file2.cpp
,
and file3.cpp
.
With the g++ command, one can compile the program with the following command that produces an executable
named a.out
g++ -o a.out file1.cpp file2.cpp file3.cpp
Any changes to any 3 files (e.g., just editing a line in file1.cpp
) will require the
g++ compiler to recompile all 3 cpp files to generate the updated executable.
This is inefficient. To alleviate this problem, we can make use of separate compilations.
To perform separate compilations, we use the following commands instead.
g++ -c file1.cpp g++ -c file2.cpp g++ -c file3.cpp g++ -o a.out file1.o file2.o file3.o
Each of the first 3 commands will individually compile fileN.cpp
into fileN.o
- an object file (which is not executable) containing
the compiled code for the corresponding cpp file. The last command then simply
links those object files together to generate the final executable a.out.
Now, say, if a line is edited in file1.cpp
, we just need to execute:
g++ -c file1.cpp g++ -o a.out file1.o file2.o file3.o
And an updated executable would be generated because the existing file2.o and file3.o can simply be reused.
Makefile allows us to create a set of compilation rules for your programming project. It is very useful for separate compilation. A Makefile consists of a number of rules. The rules have the following syntax:
[Name of rule (target) #1]: [List of dependent files/targets, separated by a space] [Press a tab character here][Command 1 to be executed] [Press a tab character here][Command 2 to be executed] ... [Press a tab character here][Command N to be executed] [Empty lines....] [Name of rule (target) #2]: [List of dependent files/targets, separated by a space] [Press a tab character here][Command 1 to be executed] [Press a tab character here][Command 2 to be executed] ... [Press a tab character here][Command N to be executed] [Empty lines....]
The name of a rule is also called its target. When a rule is executed, all its dependent files/targets will first be generated using the rules of the same names (e.g., a dependent file "main.exe" can be generated by the rule named "main.exe") unless the files are already there and up-to-date. Then, after all dependent files are generated, the commands under the rule will be executed.
The first rule in a Makefile will be executed by default when
you simply run the command make
(without mentioning the target/rule name), In the given Makefile, it will be the rule "all".
Note that, however, the first target is generally not necessarily called "all"; it can be anything you
want.
We usually write a rule to clean up a folder by removing all the temporary files (e.g. executable and
object files). In the given Makefile, to execute that, you will have to run the command
make
clean
.
You need these three commands in this lab.
To create an object file (e.g., main.o) from a source file (e.g., main.cpp):
g++ -std=c++11 -o Output filename -c Source file name
To link the object files together to make an executable:
g++ -std=c++11 -o Output filename Object file name 1 Object file name 2 ... Object file name N
To delete files:
On Windows:del File name patternOr, on Linux/MacOS:
rm -f File name pattern
Note: it is possible to detect which OS is being used and pick the command automatically in the Makefile, but it is slightly complicated, so just write the command that works for the OS you are using (please edit the provided Makefile accordingly yourself).
%.o: %.cpp g++ -std=c++11 -c $< -o $@The
%
symbol means any name. So the rule simply says any .o file
depends on its corresponding .cpp file. The command to be executed under this rule
uses g++ to compile the .cpp source file to its .o object file.
-c $<
: Specify the source file name %.cpp
as the first dependent file.
-o $@
: Specify the output file name %.o
as the target.Note: Due to a convention, we usually write the makefile file name "Makefile" with a capital "M". For details, read this discussion.
We use VS Code for this semester. You may refer to our VS Code tutorial page for setup and usage instructions. You may also refer to our How to add "-std=c++11" to VS Code for help with compiling.
Download the source files. Unzip/extract the zip file and open the extracted folder. See Creating a project and using the terminal for custom compilation command section of our VS Code tutorial on how to open the folder (you don't need to create a new folder because the folder is already there after the extraction). You may also find the instructions there useful in the latter part of this lab, where you will need to enter some commands in the terminal in VS Code.
There are many files to read, while most of them contain several lines of code only. You are suggested to begin with the files main.cpp, openningMessage.cpp, QA.cpp.
//main.cpp #include "openningMessage.h" int main() { sayHello(); return 0; }
//openningMessage.cpp #include <iostream> using namespace std; int sayHello() { cout << "Hello, visitors!" << endl; cout << "Welcome to HKUST 30th Anniversary!" << endl; return 0; }
//QA.cpp #include <iostream> #include <string> using namespace std; int QA() { cout << "What do you want to check (1: Today's event, 2: History of HKUST, 3: Quit)? "; string reply; while (cin >> reply) { if (reply == "1") { cout << "Today's event" << endl; } else if (reply == "2") { cout << "History of HKUST" << endl; } else if (reply == "3") { break; } else { cout << "Please enter the number 1, 2 or 3 (1: Today's event, 2: History of HKUST, 3: Quit)" << endl; } // repeat the message cout << "What do you want to check (1: Today's event, 2: History of HKUST, 3: Quit)? "; } cout << "Have a nice day in HKUST!!!" << endl; return 0; }
So, for example, main.exe
needs
different functions in openningMessage and QA c++ files. You need to deduce the
dependency and write them in the Makefile.
You should see:
CPPFLAGS = -std=c++11 all: main.exe main.exe: openningMessage.o main.o g++ -o $@ $(CPPFLAGS) openningMessage.o main.o %.o: %.cpp g++ $(CPPFLAGS) -c $< -o $@ clean: del *.o *.exe # On Windows, use: del *.o *.exe # On Linux or MacOS, use: rm -f *.o *.exe
Note: change "del *.o *.exe" to "rm -f *.o *.exe" under the "clean" rule if you are using Linux or MacOS.
Beware that the indentations are all just single tab characters as described in the previous section.
Extra: noted that we are only dealing with ".cpp" files here, how about ".h" files? You could read "make depend section" in the the Makefile lecture notes! For Windows users, since makedepend
is a Unix command, we need to download the makedepend.exe
here. Unzip the downloaded zip files and you will be able to find the makedepend.exe in UnxUtils\usr\local\wbin. You may now add the rule to the Makefile.
depend: path\to\UnxUtils\usr\local\wbin\makedepend -o.o $(SRCS)
Note: by default the win32 version prints .obj, so we need to use the -o.o
option. Besides makedepend
, you might also try the similar feature built into GCC with the -MM
flag (i.e. g++ -MM *.cpp
) without downloading anything.
The difference between g++ -MM
and makedepend
is that makedepend
change the Makefile automatically and g++ -MM
only print the dependency on screen (we need to copy those dependencies to the Makefile
manually).
In the terminal (see the tutorial on how to open the terminal in VS Code), enter the "make" commands to compile the project using the Makefile. Afterwards, you can enter the command "./main.exe" to run the generated programs.
The program "main.exe" should show:
Hello, visitors! Welcome to HKUST 30th Anniversary!
Reception bot program should allow visitors to ask some questions. Modify the Makefile to include the Q&A function.
You should update the rule of main.exe
in Makefile
to include QA object file. Also remember to include the header file of QA
in main.cpp
and add the function QA()
in the main
function of the main.cpp
.
Visitors can then ask some questions by entering number 1 or 2 and quit the program by entering number 3.
When you run
main.exe
, it shows:
Hello, visitors! Welcome to HKUST 30th Anniversary! What do you want to check (1: Today's event, 2: History of HKUST, 3: Quit)?
openningMessage.cpp
to enable the bot to introduce itself.
The bot should say "I am your reception bot." at the end of the openning messages.
g++ -std=c++11 -c openningMessage.cpp -o openningMessage.o
is run (g++ -std=c++11 -c main.cpp -o main.o
will not be executed) when you execute make
.
If succeed, you would get the result like
Hello, visitors! Welcome to HKUST 30th Anniversary! I am your reception bot. What do you want to check (1: Today's event, 2: History of HKUST, 3: Quit)?
- Makefile - main.cpp - openningMessage.cppYou can submit your codes multiple times to ZINC before the deadline. Only the last submission will be graded.