From ad76e9b885c9b9692074cf5b8b880cb79f8a48e0 Mon Sep 17 00:00:00 2001 From: Aki Date: Sun, 25 Jul 2021 19:17:40 +0200 Subject: Initialized website as git repository --- ...st_introduction_to_building_with_makefiles.html | 239 +++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 the_gentlest_introduction_to_building_with_makefiles.html (limited to 'the_gentlest_introduction_to_building_with_makefiles.html') diff --git a/the_gentlest_introduction_to_building_with_makefiles.html b/the_gentlest_introduction_to_building_with_makefiles.html new file mode 100644 index 0000000..f4b2cac --- /dev/null +++ b/the_gentlest_introduction_to_building_with_makefiles.html @@ -0,0 +1,239 @@ + + + + + + + + + +The Gentlest Introduction to Building With Makefiles + + + +
+

The Gentlest Introduction to Building With Makefiles

+

Published on 2020-05-14 18:44:00+02:00 +

If you are here, you are most likely in need to build C or C++ program. Chances are you were not even looking for +tutorial about GNU make or Makefiles in general. Chances are that you +need to get you assignment done by yesterday, or you want to refresh your memory from back in the day you used C for +the last time. No matter your background, I'll try to walk you through the process of building your C or C++ program +using make command. +

Sadly, the tutorial will explain the stuff, so that you have an overview after reading it. It won't go too deeply. +Anyway, if you are only interested in an example to copy, there is one below. +

If you have no idea, why using build system is nice. There are plenty of reasons. I will give you two. They automate +the building process, so that you don't have to type same things all over again, and you don't need to remember your +configuration at all times. If used consistently, they try to build only parts of your project that were changed. It +can affect the building time greatly. +

Building a single file project

+

You have just finished writing your first implementation of Hello, world!, you have terminal open or some kind +of prompt up and running, and now you would like to build the program and execute it. You've probably seen it somewhere +but let me remind how to do it by hand using gcc:

+
+$ ls
+hello.c
+$ gcc hello.c -o hello
+$ ./hello
+Hello, world!
+
+

Nice! But writing gcc hello.c -o hello all the time when you want to rebuild the program sounds +bothersome even if you consider using command history. If you were to extend the program with libraries or additional +files it sounds even more tiresome. +

Let's put make to use! All you need to do is replace gcc hello.c -o part with make, +so that you have:

+
+$ ls
+hello.c
+$ make hello
+cc hello.c -o hello
+$ ls
+hello   hello.c
+$ ./hello
+Hello, world!
+
+

You probably noticed that make shamelessly prints out the command it used to build your program. How did it +know? Make is a master of default variables, implicit rules, deduction, and hiding it's secrets from curious eyes +of those who seek knowledge. Actually, no, the +documentation is available for anyone in various forms. We'll not discuss it in detail, that wouldn't be gentle, so +assume for now that make will know how to compile and link your C or C++ program. Rules that describe how +make does that are called implicit rules. We'll use and affect them extensively.

+colorful toy blocks +

Using libraries with implicit rules

+

Make and makefiles heavily rely on your environment. If you don't know what it is, for simplicity let's say +that the environment is a set of variables associated with your current shell/terminal/prompt session. Make is +so greedy that it takes all of your environmental variables and copies them as own. The implicit rules may use those +copied variables, and they do exactly that. Those variables are usually called implicit variables. +

We can take advantage of it. Let's say we are building a game with SDL2. SDL2 +requires an additional include directory, a flag, and a library in order to build with it. Firstly, we'll set selected +variables in our environment (via export VARIABLE=value), and then we'll build the program:

+
+$ ls
+hello-sdl.c
+$ export CFLAGS='-D_REENTRANT -I/usr/include/SDL2'
+$ export LDLIBS='-lSDL2'
+$ make hello-sdl
+gcc -D_REENTRANT -I/usr/include/SDL2  hello-sdl.c  -lSDL2  -o hello-sdl
+$ ./hello-sdl
+
+

The values I've used are specific to SDL2, don't mind them. What interests us in this example are the names of the +variables: CFLAGS and LDLIBS. First one, is a set of parameters that describe how our thing +should be handled during compilation. CFLAGS is for C language, for C++ programs there is +an equivalent variable called CXXFLAGS. Second variable, LDLIBS may contain a list of +libraries that the linker should link to our program. In the example above there is no clear difference between +compilation and linking, and thus both variables are copied by make to a single command. Luckily, it makes no +difference to us, especially when the outcome is satisfying. +

First Makefile

+

Obviously, we would need to repeat those exports each time we start new session. This would bring us +back to the level of repeating whole gcc call on and on. We could put them in some kind of file, couldn't +we? Luckily, make predicted that and it may read the contents of so-called makefiles. Just put a file called +Makefile in the project directory and insert the variables there:

+
+CFLAGS=-D_REENTRANT -I/usr/include/SDL2
+LDLIBS=-lSDL2
+
+

Now, if you run make, it will read the Makefile and use the variables that are defined in it:

+
+$ ls
+hello-sdl.c   Makefile
+$ make hello-sdl
+gcc -D_REENTRANT -I/usr/include/SDL2  hello-sdl.c  -lSDL2  -o hello-sdl
+
+

Less writing is always cool. How about getting rid of the hello-sdl from every call to make? +That's also possible. hello-sdl is a target. Targets are associated with +rules, any number of them, be it implicit or user-defined. If user doesn't provide a target name as an +argument in command line, make uses first target that is specified in the makefile. We can create targets by +writing rules. The syntax to do so is rather straight forward and contains: names of targets, prerequisites needed, and +a recipe which may be a single-line command or may span the eternity. Knowing all of that, we can write a very peculiar +rule. Everything will be handled by an implicit rule, and we'll only give a hint to make which thing we want it +to build by default:

+
+CFLAGS=-D_REENTRANT -I/usr/include/SDL2
+LDLIBS=-lSDL2
+hello-sdl:
+
+

Surprisingly, that's enough. hello-sdl is the name of our target. It's a first target that appears in +this makefile, therefore it will be the default one. : (colon) is a required separator between the target +and prerequisites list. We didn't add any dependencies, as the sole dependency on hello-sdl.c file is +acknowledged thanks to the implicit rule. And because we didn't write any recipe, the recipe from the implicit rule is +used. When we use it, it looks like this:

+
+$ ls
+hello-sdl.c   Makefile
+$ make
+gcc -D_REENTRANT -I/usr/include/SDL2  hello-sdl.c  -lSDL2  -o hello-sdl
+
+

Adding more files

+more files +

In a long run, it would be more useful to have more than one file in a project. Make also predicted that and +allows users to build programs from multiple sources. Amazing, isn't it? Now is the moment we finally split up +compilation from linking in a visible manner. Let's say we have a project with three files hello.c, +sum.h and sum.c, their content is respectively:

+
+#include <stdio.h>
+#include "sum.h"
+int main(int argc, char * argv[]) {
+	printf("2 + 3 = %d", sum(2, 3));
+}
+
+
+#pragma once
+int sum(int a, int b);
+
+
+int sum(int a, int b) {
+	return a + b;
+}
+
+

The structure of this project is easily seen. Hello.c depend directly on sum.h due to the include, and +it requires the sum function to be compiled and available when linking the final executable. First dependency is +so stupidly easy to write, that you might be actually surprised about it: you just need to add sum.h file to +prerequisites in the rule description. The other one, is slightly more interesting. We could just add sum.c to +prerequisites, but we will die a horrible death after a while if we do that. Technically, it's not even the thing we're +trying to accomplish, so don't do that. +

Instead, let's use .o files that are products of compilation of a single source file. We can link them +together with libraries to form an executable. We are finally clearly dividing our building process into compilation +stage, and linking stage. Let's introduce two such files: hello.o and sum.o. They are build from their +respective sources. This means we now have three files with compiled or linked code: hello, +hello.o and sum.o. Latter doesn't depend on anything, so there is no need for us to write anything about +it. hello.o depends on sum.h (again, due to already mentioned include). Despite the fact that we call the +sum() function in it, it doesn't depend on sum.o. Why? Because it is just an intermediate file. It never +executes anything. On the other hand, hello executes it, so it needs all of the intermediate .o files in +its prerequisites list. +

All in all, the Makefile will look like this:

+
+hello: hello.o sum.o
+hello.o: sum.h
+
+

When we use it:

+
+$ make
+cc -c -o hello.o hello.c
+cc -c -o sum.o sum.c
+cc hello.o sum.o -o hello
+$ ./hello
+2 + 3 = 5
+
+

Surprisingly, that's all you need to know. With that, you can build pretty much everything. With time and when you +gain some additional knowledge you may want to write your makefiles more explicitly: things like CC=gcc to +make sure that the correct compiler is used. Your own recipes for generating headers or targets that are not generating +any files, but rather install the software or clean up the directory. Targets not associated with files are called +.PHONY and sooner or later you will encounter them. Actually... +

Clean up your project directory with make

+

For some reasons, you may want to remove all built executables and intermediate files, or any other garbage files +that your workflow involves. In previous part, I've already noted that you can accomplish that using .PHONY +targets. Such cleaning target is quite common and is usually called clean. Consider the following:

+
+hello: hello.o sum.o
+hello.o: sum.h
+
+clean:
+	$(RM) *.o hello
+
+.PHONY: clean
+
+

As it's not the default target, you must invoke it by name:

+
+$ make clean
+rm -f *.o hello
+
+

The marked line is called the recipe. It describes what rule is supposed to do. Only one recipe per target is used, +make discards previous recipes for the target if new one is defined, so only the bottom-most is effective. +$(RM) is a default variable that is expected to describe the command that can be used to safely remove +files no longer needed by the project. You've probably noticed that .PHONY exists as a target. We add +clean to it's prerequisites list to let make know that clean is not expected to create +a file called clean.

+sweeping dust +

Example makefile for C++ project

+

Following makefile is used to build a simple C++ pager, program for opening and scrolling through a file in a +command line interface. Please note, that by default cc is used as a linker. It means that, if we are building +a C++ project, the standard C++ library will be missing. We can avoid it by writing own linking recipe, or by adding +-lstdc++ to libraries manually. Latter approach is used in the example.

+
+CXXFLAGS=-std=c++17 -Wall -Wextra -Werror -O2
+LDLIBS=-lstdc++ -lncurses++
+
+pager: ansi.o content.o pager.o
+pager.o: ansi.h content.h
+
+clean:
+	$(RM) pager *.o
+
+.PHONY: clean
+
+

What's next

+

Obviously, that's not everything there is to make. In my opinion, this is all you need for regular usage in +small to medium projects. From this point you can extend your knowledge. I would suggest to learn more about +variables and built-in +functions. They will help you to create +more extendible makefiles, and write less in general. In case you'll end up needing to write a proper recipe and more +complicated rule - head to sections: +writing recipes or +writing rules. +The automatic variables are +an amazingly useful tool when writing your own rules. Actually, these three things are usually, the first to be +mentioned by other tutorials about make. However, with approach presented in here, you should be able to avoid +them for quite a long time. Be wary though - don't be ignorant. You've been showed the basic usage of +make that is heavily dependent on implicit rules and hidden mechanics. You know that they exists and now it's +turn for you to go out there, use them and slowly learn how they really work. +

+ -- cgit v1.1