C++ Lab 07 - Introduction to C++ Build Systems
Maintained by: mikerb@mit.edu Get PDF
1 Lab Seven Overview and Objectives
2 A First Discussion on Source Code File Management
3 Digging a Bit Deeper into the Build Process
3.1 A Simple Example to Try
3.2 How to Make Frequent Builds Efficient During Development
3.3 Exercise 1: Prepare Your Code Files for a Reorganization
4 Building a Program with Source Code Across Different Directories
5 Creating a Library Archive of C++ Utility Source Code
5.1 Exercise 2: Generation of Archive Files with a Script
5.2 Linking to Archive Files in Building an Executable
5.3 Exercise 3: Building a Set of Executables with a Script
6 Using Makefiles and GNU Make for Building Projects
6.1 Exercise 4: Replacing our Build Script with a Simple Makefile
6.2 Connecting the Makefiles - Building a Top-Level Makefile
6.3 Exercise 5: A Full Set of Makefiles with a Top-Level Makefile
7 Solutions to Exercises
7.1 Solution to Exercise 1
7.2 Solution to Exercise 2
7.3 Solution to Exercise 3
7.4 Solution to Exercise 4
7.5 Solution to Exercise 5
1 Lab Seven Overview and Objectives
We are at the point in our labs where our example programs are contained in multiple source code files, all passed as arguments to the g++ compiler on the command line. If you're thinking that building an executable this way is starting to get unwieldy, we agree. And there are many tools and techniques out there to help manage this, some very simple and some more complex and sophisticated. The sophisticated tools generalize and in some cases use the simple tools, so it's worth introducing the simple build tools and concepts here and now. Partly because the message should resonate now that you are starting to have non-trivial programs, and partly to enable the exercises in later labs to remain simple and clear. So before we dive further into more advanced C++ language concepts, we push the pause button and discuss C++ build systems.
![](/2.680/images/white.png)
In this lab the following topics are covered.
![](/2.680/images/white.png)
- The Relationship between Compiling and Linking in the Build Process
- Basic Source Code Project Management
- The Construction of Archive Libraries for Holding Code Common to Multiple Apps
- An Introduction to Makefiles to Simplify and Quicken the Build Process
- A Discussion of Cross-Platform Build Tools such as CMake
![](/2.680/images/white.png)
![](/2.680/images/white.png)
2 A First Discussion on Source Code File Management
If you have been working through the exercises in all the prior labs, chances are you have them all in one big directory, and your situation looks something like this:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ ls arrays.cpp filein.cpp rand_seglist.cpp string_split.cpp arrays_nosmall.cpp fileout.cpp rand_vertices_class.cpp string_split_v2.cpp BiteString.cpp function_hellocap.cpp rand_vertices.cpp str_search.cpp BiteString.h function_hellocmd.cpp rand_vertices_file.cpp vectors_nosmall.cpp cmd_args.cpp function_hello.cpp rand_vertices_seed.cpp Vertex.cpp factorial_cmdargs.cpp function_hellodef.cpp rand_vertices_sleep.cpp Vertex.h factorial.cpp function_main.cpp SegList.cpp VertexSimple.cpp factorial_longint.cpp function_sepdef.cpp SegList.h VertexSimple.h FileBuffer.cpp function_sepdef.h string_bite.cpp vertices.txt FileBuffer.h ParseString.cpp string_deserial.cpp filebuff_main.cpp ParseString.h string_parse.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Ideally the above "flat" structure would instead be organized more in line with the order of the labs, and the top level view would look something like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ ls lab01/ lab03/ lab05/ lab02/ lab04/ lab06/
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Perhaps the only thing stopping you from organizing it this way is that source code files like Vertex.cpp and FileBuffer.cpp are needed in multiple labs and a little red flag popped up in your head that having multiple versions of the same source code in different places is a bad idea (bravo to you if that was the case). This is indeed a bad idea, and there are ways to handle this.
![](/2.680/images/white.png)
Our goal in this lab is to allow you to organize things like the geometry source code (Vertex.h/cpp and SegList.h/cpp) into its own folder and the same with the string parsing utilities, e.g. ParseString.h/cpp. These dedicated utility folders will be called libraries and any number of apps or programs can access them. The resulting structure will look something like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ ls lab01/ lab03/ lab05/ lib_geometry/ lab02/ lab04/ lab06/ lib_strings/
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
3 Digging a Bit Deeper into the Build Process
The term build process here refers to the steps starting with source code (a set of .cpp and .h files) ending with an executable file representing your program. So far we have been building from the command line where each argument is either a source code file and another argument indicating the desired name of the executable. For example: g++ -o test main.cpp. There are a few steps going on under the hood in this process, but you could be forgiven if you held a conceptual view of this process looking like:
![](/2.680/images/white.png)
![](/2.680/project-pavlab-aro/labdocs/lab_cpp_07_build/figures/build_simple.png)
Figure 3.1: A simplistic view of the build process.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Here's another overly simplistic but more correct view that reveals an intermediate stage prior to the creation of the executable, the creation of object files:
![](/2.680/images/white.png)
![](/2.680/project-pavlab-aro/labdocs/lab_cpp_07_build/figures/build_objects.png)
Figure 3.2: A still simplistic but slightly more realistic view of the build process.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The linker stitches together pieces of code that reference other pieces of code. For example, one piece of source code can be compiled that invokes the biteString() function found in another piece of code. During the compilation, the compiler doesn't care that they live separately, but during linking the pieces are linked together into a single entitity, the executable. For now you can leave it at that, but you may want to check this out further at the below URLs:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
- Read the section "1.4 GCC Compilation Process" found here:
https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html - The whole page at this url:
- http://mylinuxbook.com/basics-of-gcc-compilation-process-explained/
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The multiple stages are done automatically when building as we have been building, but they can be split up, and that is key for achieving an efficient build. The below example illustrates simple case.
![](/2.680/images/white.png)
3.1 A Simple Example to Try [top]
As an example, the below is how we built the string_bite executable from the previous lab:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_bite BiteString.cpp string_bite.cpp $ ls BiteString.cpp string_bite.cpp string_bite*
![](/2.680/images/white.png)
![](/2.680/images/white.png)
If we wanted to, we could break this process up into first build the object files and then link them as below. First the compiling into object files. The -c command line switch tells g++ to only build the object files:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -c BiteString.cpp string_bite.cpp $ ls BiteString.cpp string_bite.cpp BiteString.o string_bite.o
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Then the linking of the object files into an executable:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_bite BiteString.o string_bite.o $ ls BiteString.cpp string_bite.cpp string_bite* BiteString.o string_bite.o
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
3.2 How to Make Frequent Builds Efficient During Development [top]
Here we begin to address the build process efficiency. In this case we're not talking about the time it takes to do a fresh build (no prior builds on this computer's recent history). Instead we're talking about efficiency between successive builds during development. The latter is something typically done many dozens or hundreds of times during the course of software development. In this scenario, the primary means for making the build process more efficient is to only re-compile what needs to be re-compiled since the previous build.
![](/2.680/images/white.png)
The situation is depicted below. An executable is comprised of many source code files. You're presently working on the code in one file. When you go to re-build the executable, this one file is the only file that has changed. The others have not. Yet if we continue to build like we have been doing, ALL source code files are re-compiled anyway:
![](/2.680/images/white.png)
![](/2.680/project-pavlab-aro/labdocs/lab_cpp_07_build/figures/build_many_one.png)
Figure 3.3: A simplistic view of a program having many source code files. In this case we highlight the situation where ONE of those files has changed since the previous build. In building the naive brute-force way, ALL source code is recompiled, even those source code files that have not changed.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
In our exercises where the number and size of source files is small, the inefficiency isn't noticed. Before long, however, it will be noticable and progressively more painful. Instead we strive for a situation like that depicted below, where only the changed file is re-compiled:
![](/2.680/images/white.png)
![](/2.680/project-pavlab-aro/labdocs/lab_cpp_07_build/figures/build_many_two.png)
Figure 3.4: A simplistic view of a program having many source code files. Only one source file has changed since the last build. This is the only file re-compiled on the following build. The build otherwise uses the unchanged object files from the previous build, shortening the overall build time.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
3.3 Exercise 1: Prepare Your Code Files for a Reorganization [top]
As a preparation for later steps, we are going to re-organize the file structure of code you have developed so far. If you have been doing all the exercises and using the name suggestions, then the steps should pretty much match up exactly to what we show below.
![](/2.680/images/white.png)
First, make a copy of what you have done so far. Make a directory called attic and move everything into there with your current file structure. After that, create the below new directories as shown:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ mkdir lab02 lab03 lab04 lab05 lab06 lib_geometry lib_strings $ ls attic/ lab03/ lab05/ lib_geometry/ lab02/ lab04/ lab06/ lib_strings/
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Each of these new directories, except for the attic, will be initially empty, and the next step is to copy the source code in the attic from each of the labs into the right directory. Here's what it should look like if you followed the naming suggestions from the exercises.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ cd attic $ cp factorial.cpp factorial_longint.cpp factorial_cmdargs.cpp str_search.cpp ../lab02 $ cp function_* ../lab03 $ cp arrays*.cpp fileout.cpp filein.cpp vectors_nosmall.cpp filebuff_main.cpp ../lab04 $ cp rand_vertices* rand_seglist* ../lab05 $ cp string_*.cpp ../lab06 $ cp FileBuffer* BiteString* ParseString* ../lib_strings $ cp Vertex* SegList* ../lib_geometry
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Afterwards, in the top level of your directory, invoking ls * should produce something like that shown in the solution for this exercise shown in Section 7.1.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
4 Building a Program with Source Code Across Different Directories
After organizing your code as per the previous exercise, the next question is how can this code be built now that all the source code files are not in the same directory. We'll use the code from the previous lab in our examples below. All this code should now be in the lab06 directory. In this lab, the string_bite executable was built from the command line with:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_bite BiteString.cpp string_bite.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
This will no longer work from within the lab06 directory since BiteString.cpp is not in that directory. It now resides in lib_strings. We could alter our build command with:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_bite ../lib_strings/BiteString.cpp string_bite.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
This would almost work, but the build will still fail because, inside string_bite.cpp there is this line of code (a pre-processor directive):
![](/2.680/images/white.png)
![](/2.680/images/white.png)
#include "BiteString.h"
![](/2.680/images/white.png)
![](/2.680/images/white.png)
It may be tempting to fix this by changing the line of code to:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
#include "../lib_string/BiteString.h"
![](/2.680/images/white.png)
![](/2.680/images/white.png)
This is almost always a bad idea. It makes the source code in bite_string.cpp brittle. If the location of the string library is ever moved or renamed, the code no longer works. A better solution is to simply tell the g++ compiler, on the command line, where it should look for any files being include with the #include directive. This is done with the -I command line argument as follows:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_bite -I../lib_strings ../lib_strings/BiteString.cpp string_bite.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Now string_bite should compile successfully as above. Naming a directory with the -I argument is referred to as setting your "include path". If your code needs to #include code from multiple places, the argument may be used multiple times on the command line. For example, the following is how string_deserial is now built with the new file structure. It needs to #include code from two different directories:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_deserial -I../lib_geometry -I../lib_strings \ ../lib_geometry/Vertex.cpp ../lib_strings/FileBuffer.cpp \ ../lib_strings/BiteString.cpp ../lib_strings/ParseString.cpp \ string_deserial.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The above build command solves the #include path problem, but it is still unwieldy. It seems like we should be able to get rid of the ../lib_geometry part of ../lib_geometry/Vertex.cpp by just specifying another path indicating where to look for source code files, in the spirit of how the -I argument works. There is indeed this option (the -L command line option), but it works with archived object files. So our next discussion is how these are made. The vast majority of non-trivial C++ objects in the world do things in this way, so it's worth discussing and start doing things this way.
![](/2.680/images/white.png)
NOTE: One final comment on the issue of the #include path. Notice that in earlier labs we have been including files like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
#include <iostream> #include <cstdio>
![](/2.680/images/white.png)
![](/2.680/images/white.png)
And we have not needed to have any -I entries in our invocations of g++, but somehow these included files have been found. In C++ a few conventional locations are automatically part of the include path. These directories are usually "system" directories (accessible to all users). On your machine they are likely found in /usr/include or in one of the subdirectories like /usr/include/c++/4.2.1/ like on my machine.
![](/2.680/images/white.png)
5 Creating a Library Archive of C++ Utility Source Code
An archive in C/C++ is a file that bundles a set of object files into a single file. This file almost always follows the naming convention of starting with lib and ending with .a, e.g., libstrings.a and libgeometry.a in our two library examples. An archive file can be build from object files using the ar command:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ ar cr libarchive.a file1.o file2.o ... fileN.o
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The ar command has several optional arguments. We use the two options cr here. You can find out what these mean by typing man ar on the command line to see the manual page for the ar command.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
5.1 Exercise 2: Generation of Archive Files with a Script [top]
In each of your two library folders lib_geometry and lib_strings, build a short script to generate an archive file in each folder. The archive names should be libgeometry.a and libstrings.a respectively. The script will have two lines, one for generating the object files, and one for generating the archive file.
![](/2.680/images/white.png)
We haven't discussed scripts in our labs so far, and there are several ways to write scripts and invoke them from the command line (bash scripts, perl scripts, python scripts to name a few). The simplest kind of script shouldn't be overlooked - a raw text file. Suppose you have a text file, named test for example, with the following few lines:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
ls mkdir one ls
![](/2.680/images/white.png)
![](/2.680/images/white.png)
You can then "execute" this script using the source command as follows:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ source test
![](/2.680/images/white.png)
![](/2.680/images/white.png)
It's as if you just typed these three commands on the command line. So, to make things easier for you, and to have a "deliverable" for this exercise, you should create a (raw text) script file in both the lib_geometry and lib_strings directory, and name the files simply "build" in both directories. Each file should have the two lines:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
g++ ... (you fill this in) ar ...
![](/2.680/images/white.png)
![](/2.680/images/white.png)
You can even add a few lines beginning with echo, another command line utility that just echoes its arguments. For example, try adding "echo Done!" to be the last line of your script.
![](/2.680/images/white.png)
You should be able to invoke your scripts from the command line with the following results, first for the geometry library:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ cd lib_geometry $ source build $ ls SegList.cpp Vertex.cpp VertexSimple.cpp build SegList.h Vertex.h VertexSimple.h libgeometry.a SegList.o Vertex.o VertexSimple.o
![](/2.680/images/white.png)
![](/2.680/images/white.png)
And then for the strings library:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ cd lib_strings $ source build $ ls BiteString.cpp FileBuffer.cpp ParseString.cpp build BiteString.h FileBuffer.h ParseString.h libstrings.a BiteString.o FileBuffer.o ParseString.o
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The solution to this exercise is in Section 7.2.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
5.2 Linking to Archive Files in Building an Executable [top]
Once the archive files have been created, linking against them is fairly easy. The -L argument to g++ names a directory to look for archive files. The -l argument names an actual archive file to link to. The first example below shows what the compile command would look like without the use of an archive:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ -o string_deserial -I../lib_geometry -I../lib_strings \ ../lib_geometry/Vertex.cpp ../lib_strings/FileBuffer.cpp \ ../lib_strings/BiteString.cpp ../lib_strings/ParseString.cpp \ string_deserial.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The below command shows the equivalent build, using the archive and the -L and -l commands.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ g++ string_deserial.cpp -o string_deserial -I../lib_geometry \ -I../lib_strings -L../lib_geometry -L../lib_strings -lstrings -lgeometry
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Notice that, for the -l option, there is no space between the switch -l and the name of the archive, as in -lstrings. Also note that an argument such as -lfoobar is seeking a library archive file named libfoobar.a. The lib and .a are thus filename conventions used for all static archive files.
![](/2.680/images/white.png)
Note also that we put the string_deserial.cpp argument first. In general, the order of arguments does not matter. In MacOS/Clang order does not matter. In some Linux distributions and versions of g++, order may matter and the above is most likely to work.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
5.3 Exercise 3: Building a Set of Executables with a Script [top]
In the previous exercise we created simple script files for building the archive files for our two libraries lib_strings and lib_geometry. We named both files build. In this exercise we will generate a build file for all executables in lab 06, for all five executables in that lab:
![](/2.680/images/white.png)
- string_split
- string_split_v2
- string_bite
- string_parse
- string_deserial
![](/2.680/images/white.png)
Your build file should have one line for each executable and use the -I include path, -L link path, and archive files already generated in the library directories.
![](/2.680/images/white.png)
You should be able to invoke your script from the command line with the following results:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ cd lab06 $ source build $ ls build string_deserial* string_parse.cpp string_split_v2* string_bite* string_deserial.cpp string_split* string_split_v2.cpp string_bite.cpp string_parse* string_split.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The solution to this exercise is in Section 7.3.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
6 Using Makefiles and GNU Make for Building Projects
So far in this lab we have successfully:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
- Moved away from a flat source code structure with all files in the same folder.
- Learned how to create utility libraries (archives) so source code common to multiple applications doesn't have to be duplicated in each application.
- Learned how to build an application with compiler directives to look for header files and archives in folders other than the current folder.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Build speed and efficiency aside, things are starting to feel more organized. We also have introduced some build efficiency too since presumably those library archives don't need to be re-built if they're not being changed and our iterative work is in the application code. The problem is, sometimes that library code is indeed being changed. When an application links to a library it is a dependency. All the non-library source code for an app are also dependencies. If any dependency has been modified between builds, that dependency needs to be re-compiled. Keeping track of what files have changed and need re-building can be daunting, and many an apparent bug has been found to be the result of failing to re-compile a dependency. So far in previous labs, we have side-stepped this problem by re-building everything, all the time (all source code and all dependencies) on each build, whether a file needs re-building or not. Simple, but not scalable unless you like working slow.
![](/2.680/images/white.png)
This is where the GNU 'make' utility comes in. The GNU 'make' utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them. This utility is very well documented on the GNU website and we ask that you read the first two sections (Sections 1 and 2, 10 short pages) of the manual now before proceeding.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
If you'd like to have this material available off line, you can get the whole manual either in raw text or PDF format using wget:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ wget https://www.gnu.org/software/make/manual/make.txt $ wget https://www.gnu.org/software/make/manual/make.pdf
![](/2.680/images/white.png)
![](/2.680/images/white.png)
From the GNU Make manual we see a Makefile is comprised of a series of rules:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
target ... : prerequisites ... recipe ...
![](/2.680/images/white.png)
![](/2.680/images/white.png)
As a simple example, we can replace each line in the build file created in Exercise 3 with a line like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
string_bite : g++ -I../lib_strings -L../lib_strings -lstrings -o string_bite string_bite.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
This works, but has the drawback that if any of the dependencies of the string_bite executable are modified, a subsequent make invocation wouldn't know do anything. So we add the dependencies to the prerequisites part of the rule:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
string_bite : ../lib_strings/libstrings.a string_bite.cpp g++ -I../lib_strings -L../lib_strings -o string_bite string_bite.cpp -lstrings
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Now if libstrings.a or string_bite.cpp have been modified more recently than the timestamp on the string_bite executable, make will re-execute this rule.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
6.1 Exercise 4: Replacing our Build Script with a Simple Makefile [top]
Replace the build script created in Exercise 3 with a Makefile containing a rule for each of the five executables. It should contain a first rule, all, that ensures that all five executables are built when the user simply types make in the lab06 directory. It should also contain a rule, clean, that removes all executables. For now, the rules corresponding to executables can be super-simple, having no prerequisites along the lines of the string_bite example above. While simple, it has serious drawbacks that we will improve on in the next exercise.
![](/2.680/images/white.png)
The Makefile should of course be in a file called Makefile. If this filename is used, make will automatically use it and the filename doesn't have to be passed in as an argument to make. FYI, if the lowercase makefile name is used instead, this also works, but the uppercase version takes precedent if both exist. Your Makefile with the seven rules discussed above should support the below command line invocations:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ make // Builds everything (the "all" rule) $ make string_split // Builds just the string_split executable $ make string_split_v2 // Builds just the string_split_v2 executable $ make string_bite // Builds just the string_bite executable $ make string_parse // Builds just the string_parse executable $ make string_deserial // Builds just the string_deserial executable $ make clean
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The first time make is run, you will likely see every line of every recipe in every rule echoed on the screen like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ make g++ -o string_split string_split.cpp g++ -o string_split_v2 string_split_v2.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_bite string_bite.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_parse string_parse.cpp g++ -I../lib_geometry -I../lib_strings -L../lib_strings -L../lib_geometry \ -lgeometry -lstrings -o string_deserial string_deserial.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
If make is invoked immediately again, all executables have already been made and you may see a message like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ make make: Nothing to be done for `all'.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The solution to this exercise is in Section 7.4.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
6.2 Connecting the Makefiles - Building a Top-Level Makefile [top]
The Makefile produced in Exercise 4 only concerns the source code for Lab 06. However, note that one of the rules contains a prerequisite that references an archive file in the lib_strings directory:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
string_bite : ../lib_strings/libstrings.a string_bite.cpp g++ -I../lib_strings -L../lib_strings -o string_bite string_bite.cpp -lstrings
![](/2.680/images/white.png)
![](/2.680/images/white.png)
This implies that the libstrings.a prerequisite will have had a chance to be built or updated prior to this Makefile execution. Presumably if you had a Makefile in each subdirectory, you could proceed in this manner:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ cd lib_strings $ make $ cd .. $ cd lib_geometry $ make $ cd .. ... $ cd lab06 $ make $ cd ..
![](/2.680/images/white.png)
![](/2.680/images/white.png)
This would work, but as you can see it would be tedious to manually do this each time source code as changed. As we mentioned before, there are lots of choices for scripting such tasks, and you can add make to that list. The notion of a "top-level" Makefile is common and described here:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
6.3 Exercise 5: A Full Set of Makefiles with a Top-Level Makefile [top]
In this exercise we will round out our build system for the full set of directories constituting our labs so far, up through Lab 06. We will need to create:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
- A top-level Makefile in the lab root directory
- A Makefile for the two libraries, lib_geometry and lib_strings
- A Makefile for the five lab directories, lab02, ... lab05.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Verify that everything works by trying the few following steps:
![](/2.680/images/white.png)
First, at the top level after typing the following you should see something like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ make make -C lib_geometry g++ -c Vertex.cpp VertexSimple.cpp SegList.cpp ar cr libgeometry.a Vertex.o VertexSimple.o SegList.o make -C lib_strings g++ -c BiteString.cpp FileBuffer.cpp ParseString.cpp ar cr libstrings.a BiteString.o FileBuffer.o ParseString.o make -C lab02 g++ -o factorial factorial.cpp g++ -o factorial_cmdargs factorial_cmdargs.cpp g++ -o factorial_longint factorial_longint.cpp g++ -o str_search str_search.cpp make -C lab03 g++ -o function_hello function_hello.cpp g++ -o function_hellocap function_hellocap.cpp g++ -o function_hellocmd function_hellocmd.cpp g++ -o function_sepdef function_sepdef.cpp function_main.cpp ...
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Next, after the initial full make, a subsequent call to make should result in the following:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ make make -C lib_geometry make[1]: `libgeometry.a' is up to date. make -C lib_strings make[1]: `libstrings.a' is up to date. make -C lab02 make[1]: Nothing to be done for `all'. make -C lab03 make[1]: Nothing to be done for `all'. make -C lab04 make[1]: Nothing to be done for `all'. make -C lab05 make[1]: Nothing to be done for `all'. make -C lab06 make[1]: Nothing to be done for `all'.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Lastly, try "touching" a file in the lib_strings directory, and seeing how make reacts. (The touch command simply updates the timestamp for a named file. In effect it tricks make into regarding the file as having just been edited). After the touch, you should see something like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ touch lib_strings/ParseString.h $ make make -C lib_geometry make[1]: `libgeometry.a' is up to date. make -C lib_strings g++ -c BiteString.cpp FileBuffer.cpp ParseString.cpp ar cr libstrings.a BiteString.o FileBuffer.o ParseString.o make -C lab02 make[1]: Nothing to be done for `all'. make -C lab03 make[1]: Nothing to be done for `all'. make -C lab04 g++ -I../lib_strings -L../lib_strings -lstrings -o filebuff filebuff_main.cpp make -C lab05 make[1]: Nothing to be done for `all'. make -C lab06 g++ -I../lib_strings -L../lib_strings -lstrings -o string_bite string_bite.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_parse string_parse.cpp g++ -I../lib_geometry -I../lib_strings -L../lib_strings -L../lib_geometry \ -lgeometry -lstrings -o string_deserial string_deserial.cpp
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Note that only the targets that had ParseString.h as a dependency were re-built. Very efficient! Pat yourself on the back!
![](/2.680/images/white.png)
You can now safely just type make in the top-level directory as you continue coding and adding to this project. In fact, if you haven't yet learned about shell aliases, this is a good time to do so, and make yourself a build alias along the lines of:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
alias mm 'cd ~/my_cpp_files/; make; cd -'
![](/2.680/images/white.png)
![](/2.680/images/white.png)
I chose "mm" here only as an example. But in this case you could type "mm" in any terminal window on your screen and your whole project would be (re-) built, with only the required steps given current edits. Very nice indeed. For more on aliases, see http://oceanai.mit.edu/ivpman/help/cmdline_aliases.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
7 Solutions to Exercises
![](/2.680/images/white.png)
7.1 Solution to Exercise 1 [top]
After the file structure re-org, in the top level of your directory, invoking ls * should produce something like:
![](/2.680/images/white.png)
![](/2.680/images/white.png)
$ ls * attic: BiteString.cpp VertexSimple.cpp function_hello.cpp rand_vertices_file.cpp BiteString.h VertexSimple.h function_hellocap.cpp rand_vertices_seed.cpp FileBuffer.cpp arrays.cpp function_hellocmd.cpp rand_vertices_sleep.cpp FileBuffer.h arrays_nosmall.cpp function_hellodef.cpp str_search.cpp ParseString.cpp factorial.cpp function_main.cpp string_bite.cpp ParseString.h factorial_cmdargs.cpp function_sepdef.cpp string_deserial.cpp SegList.cpp factorial_longint.cpp function_sepdef.h string_parse.cpp SegList.h filebuff_main.cpp rand_seglist.cpp string_split.cpp Vertex.cpp filein.cpp rand_vertices.cpp string_split_v2.cpp Vertex.h fileout.cpp rand_vertices_class.cpp vectors_nosmall.cpp lab02: factorial.cpp factorial_cmdargs.cpp factorial_longint.cpp str_search.cpp lab03: function_hello.cpp function_hellocmd.cpp function_main.cpp function_sepdef.h function_hellocap.cpp function_hellodef.cpp function_sepdef.cpp lab04: arrays.cpp filebuff_main.cpp fileout.cpp arrays_nosmall.cpp filein.cpp vectors_nosmall.cpp lab05: rand_seglist.cpp rand_vertices_class.cpp rand_vertices_seed.cpp rand_vertices.cpp rand_vertices_file.cpp rand_vertices_sleep.cpp lab06: string_bite.cpp string_parse.cpp string_split_v2.cpp string_deserial.cpp string_split.cpp lib_geometry: SegList.cpp Vertex.cpp VertexSimple.cpp SegList.h Vertex.h VertexSimple.h lib_strings: BiteString.cpp FileBuffer.cpp ParseString.cpp BiteString.h FileBuffer.h ParseString.h
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
7.2 Solution to Exercise 2 [top]
![](/2.680/images/white.png)
The lib_strings build file:
![](/2.680/images/white.png)
g++ -c FileBuffer.cpp BiteString.cpp ParseString.cpp ar cr libstrings.a FileBuffer.o BiteString.o ParseString.o echo Done!
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The lib_geometry build file:
![](/2.680/images/white.png)
g++ -c Vertex.cpp VertexSimple.cpp SegList.cpp ar cr libgeometry.a Vertex.o VertexSimple.o SegList.o echo Done!
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
7.3 Solution to Exercise 3 [top]
![](/2.680/images/white.png)
The lab06 build file:
![](/2.680/images/white.png)
g++ -o string_split string_split.cpp echo DONE compiling Exercise 01 g++ -o string_split_v2 string_split_v2.cpp echo DONE compiling Exercise 01V2 g++ -I../lib_strings -L../lib_strings -o string_bite string_bite.cpp -lstrings echo DONE compiling Exercise 02 g++ -I../lib_strings -L../lib_strings -o string_parse string_parse.cpp -lstrings echo DONE compiling Exercise 03 g++ -I../lib_geometry -I../lib_strings -L../lib_strings -L../lib_geometry \ -lgeometry -lstrings -o string_deserial string_deserial.cpp echo DONE compiling Exercise 04
![](/2.680/images/white.png)
![](/2.680/images/white.png)
The echo lines are optional. The order of the command line args is also optional in most cases.
![](/2.680/images/white.png)
![](/2.680/images/white.png)
7.4 Solution to Exercise 4 [top]
![](/2.680/images/white.png)
A simple lab06 Makefile: Compare the below MakefileSimple with the buildfile script from previous exercise. This accomplishes essentially the same thing. Notice that each MakefileSimple rule has no prerequisites (dependencies). After make has been invoked once on this file (make -f MakefileSimple), subsequent invocations will do nothing unless the executable has been deleted. It will not detect that a re-compile is necessary after a source code edit. Just like our simple build script, it is oblivious to changes in source code. This can be improved by adding the appropriate dependencies in the second Makefile example below.
![](/2.680/images/white.png)
# MakefileSimple for Lab06 (Ten Short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab06/MakefileSimple # Run: make -f MakefileSimple all: string_split string_split_v2 string_bite string_parse string_deserial string_split : g++ -o string_split string_split.cpp string_split_v2 g++ -o string_split_v2 string_split_v2.cpp string_bite : g++ -I../lib_strings -L../lib_strings -lstrings -o string_bite string_bite.cpp string_parse: g++ -I../lib_strings -L../lib_strings -lstrings -o string_parse string_parse.cpp string_deserial: g++ -I../lib_geometry -I../lib_strings -L../lib_strings -L../lib_geometry \ -lgeometry -lstrings -o string_deserial string_deserial.cpp clean: rm -f string_split string_split_v2 string_bite string_parse string_deserial *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
A Better lab06 Makefile (with Dependencies):
![](/2.680/images/white.png)
# Makefile for Lab06 (Ten Short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab06/Makefile all: string_split string_split_v2 string_bite string_parse string_deserial string_split : string_split.cpp g++ -o string_split string_split.cpp string_split_v2 : string_split_v2.cpp g++ -o string_split_v2 string_split_v2.cpp string_bite : ../lib_strings/libstrings.a string_bite.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_bite string_bite.cpp string_parse: ../lib_strings/libstrings.a string_parse.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_parse string_parse.cpp string_deserial: ../lib_strings/libstrings.a ../lib_geometry/libgeometry.a string_deserial.cpp g++ -I../lib_geometry -I../lib_strings -L../lib_strings -L../lib_geometry \ -lgeometry -lstrings -o string_deserial string_deserial.cpp clean: rm -f string_split string_split_v2 string_bite string_parse string_deserial *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
7.5 Solution to Exercise 5 [top]
![](/2.680/images/white.png)
# Top-Level Make Directory for programs through Lab 06 # wget http://oceanai.mit.edu/cpplabs/Makefile all: make -C lib_geometry make -C lib_strings make -C lab02 make -C lab03 make -C lab04 make -C lab05 make -C lab06 clean: make -C lib_geometry clean make -C lib_strings clean make -C lab02 clean make -C lab03 clean make -C lab04 clean make -C lab05 clean make -C lab06 clean
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for lib_geometry (Ten Short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lib_geometry/Makefile libgeometry.a : Vertex.cpp Vertex.h VertexSimple.cpp VertexSimple.h SegList.cpp SegList.h g++ -c Vertex.cpp VertexSimple.cpp SegList.cpp ar cr libgeometry.a Vertex.o VertexSimple.o SegList.o clean: rm -f Vertex.o VertexSimple.o SegList.o libgeometry.a *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for lib_strings (Ten Short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lib_strings/Makefile libstrings.a : BiteString.cpp BiteString.h FileBuffer.cpp FileBuffer.h ParseString.cpp ParseString.h g++ -c BiteString.cpp FileBuffer.cpp ParseString.cpp ar cr libstrings.a BiteString.o FileBuffer.o ParseString.o clean: rm -f BiteString.o FileBuffer.o ParseString.o libstrings.a *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for Lab 02 (Ten short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab02/Makefile all: factorial factorial_cmdargs factorial_longint str_search factorial: factorial.cpp g++ -o factorial factorial.cpp factorial_cmdargs: factorial_cmdargs.cpp g++ -o factorial_cmdargs factorial_cmdargs.cpp factorial_longint: factorial_longint.cpp g++ -o factorial_longint factorial_longint.cpp str_search: str_search.cpp g++ -o str_search str_search.cpp clean: rm -f factorial factorial_cmdargs factorial_longint str_search *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for Lab03 (Ten Short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab03/MakeFile all: function_hello function_hellocap function_hellocmd function_sepdef function_hello: function_hello.cpp g++ -o function_hello function_hello.cpp function_hellocap: function_hellocap.cpp g++ -o function_hellocap function_hellocap.cpp function_hellocmd: function_hellocmd.cpp g++ -o function_hellocmd function_hellocmd.cpp function_sepdef: function_sepdef.h function_sepdef.cpp function_main.cpp g++ -o function_sepdef function_sepdef.cpp function_main.cpp clean: rm -f function_hello function_hellocap function_hellocmd rm -f function_sepdef *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for Lab 04 (Ten short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab04/Makefile all: arrays arrays_nosmall filein fileout vectors_nosmall filebuff arrays: arrays.cpp g++ -o arrays arrays.cpp arrays_nosmall: arrays_nosmall.cpp g++ -o arrays_nosmall arrays_nosmall.cpp fileout: fileout.cpp g++ -o fileout fileout.cpp filein: filein.cpp g++ -o filein filein.cpp vectors_nosmall: vectors_nosmall.cpp g++ -o vectors_nosmall vectors_nosmall.cpp filebuff: filebuff_main.cpp ../lib_strings/libstrings.a g++ -I../lib_strings -L../lib_strings -lstrings -o filebuff filebuff_main.cpp clean: rm -f arrays arrays_nosmall fileout filein vectors_nosmall filebuff *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for Lab 05 (Ten short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab05/Makefile all: rand_vertices rand_vertices_file rand_vertices_class rand_seglist \ rand_vertices_sleep rand_vertices_seed rand_vertices: rand_vertices.cpp g++ -o rand_vertices rand_vertices.cpp rand_vertices_file: rand_vertices_file.cpp g++ -o rand_vertices_file rand_vertices_file.cpp rand_vertices_class: ../lib_geometry/libgeometry.a rand_vertices_class.cpp g++ -I../lib_geometry -L../lib_geometry -lgeometry -o rand_vertices_class \ rand_vertices_class.cpp rand_seglist: ../lib_geometry/libgeometry.a rand_seglist.cpp g++ -I../lib_geometry -L../lib_geometry -lgeometry -o rand_seglist rand_seglist.cpp rand_vertices_sleep: rand_vertices_sleep.cpp g++ -o rand_vertices_sleep rand_vertices_sleep.cpp rand_vertices_seed: rand_vertices_seed.cpp g++ -o rand_vertices_seed rand_vertices_seed.cpp clean: rm -f rand_vertices rand_vertices_file rand_vertices_class rand_seglist rm -f rand_vertices_sleep rand_vertices_seed *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
# Makefile for Lab 06 (Ten Short C++ Labs) # wget http://oceanai.mit.edu/cpplabs/lab06/Makefile all: string_split string_split_v2 string_bite string_parse string_deserial string_split : string_split.cpp g++ -o string_split string_split.cpp string_split_v2 : string_split_v2.cpp g++ -o string_split_v2 string_split_v2.cpp string_bite : ../lib_strings/libstrings.a string_bite.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_bite string_bite.cpp string_parse: ../lib_strings/libstrings.a string_parse.cpp g++ -I../lib_strings -L../lib_strings -lstrings -o string_parse string_parse.cpp string_deserial: ../lib_strings/libstrings.a ../lib_geometry/libgeometry.a string_deserial.cpp g++ -I../lib_geometry -I../lib_strings -L../lib_strings -L../lib_geometry \ -lgeometry -lstrings -o string_deserial string_deserial.cpp clean: rm -f string_split string_split_v2 string_bite string_parse string_deserial *~
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
![](/2.680/images/white.png)
Document Maintained by: mikerb@mit.edu
Page built from LaTeX source using texwiki, developed at MIT. Errata to issues@moos-ivp.org.
Get PDF