📜 ⬆️ ⬇️

Makefile example

Writing a makefile sometimes becomes a headache. However, if you look at it, everything falls into place, and writing a powerful 40-line makefile for an arbitrarily large project turns out quickly and elegantly.

Attention! The basic knowledge of the GNU make utility is assumed.


We have a typical abstract project with the following directory structure:
')


Suppose that for the inclusion of header files in the source code, something like #include <dir1 / file1.h> is used, that is, the project / include directory is made standard when compiled.

After assembly, it is necessary to get it like this:



That is, in the bin directory there are working (application) and debugging (application_debug) versions, in the Release and Debug subdirectories of the project / obj directory the project / src directory structure is repeated with the corresponding source files of the object files, from which the contents of the bin directory are compiled.

To achieve this effect, create a Makefile in the project directory as follows:
  1. root_include_dir: = include
  2. root_source_dir: = src
  3. source_subdirs: =. dir1 dir2
  4. compile_flags: = -Wall -MD -pipe
  5. link_flags: = -s -pipe
  6. libraries: = -ldl
  7. relative_include_dirs: = $ ( addprefix .. / .. / , $ ( root_include_dir ) )
  8. relative_source_dirs: = $ ( addprefix .. / .. / $ ( root_source_dir ) / , $ ( source_subdirs ) )
  9. objects_dirs: = $ ( addprefix $ ( root_source_dir ) / , $ ( source_subdirs ) )
  10. objects: = $ ( patsubst .. / .. /% , % , $ ( wildcard $ ( addsuffix / * .c * , $ ( relative_source_dirs ) ) ) )
  11. objects: = $ ( objects: .cpp = .o )
  12. objects: = $ ( objects: .c = .o )
  13. all: $ ( program_name )
  14. $ ( program_name ) : obj_dirs $ ( objects )
  15. g ++ -o $ @ $ ( objects ) $ ( link_flags ) $ ( libraries )
  16. obj_dirs:
  17. mkdir -p $ ( objects_dirs )
  18. VPATH: = .. / .. /
  19. % .o: % .cpp
  20. g ++ -o $ @ -c $ < $ ( compile_flags ) $ ( build_flags ) $ ( addprefix -I, $ ( relative_include_dirs ) )
  21. % .o: % .c
  22. g ++ -o $ @ -c $ < $ ( compile_flags ) $ ( build_flags ) $ ( addprefix -I, $ ( relative_include_dirs ) )
  23. .PHONY: clean
  24. clean:
  25. rm -rf bin obj
  26. include $ ( wildcard $ ( addsuffix / * .d, $ ( objects_dirs ) ) )

In its pure form, such a makefile is only useful for achieving the clean target, which will remove the bin and obj directories.
Add another script named Release to build the working version:

mkdir -p bin mkdir -p obj mkdir -p obj/Release make --directory=./obj/Release --makefile=../../Makefile build_flags="-O2 -fomit-frame-pointer" program_name=../../bin/application 


And another Debug script to build debug version:

 mkdir -p bin mkdir -p obj mkdir -p obj/Debug make --directory=./obj/Debug --makefile=../../Makefile build_flags="-O0 -g3 -D_DEBUG" program_name=../../bin/application_debug 


It is the challenge of one of them that will bring together our project in either working or debugging form. And now, about everything in order.

Suppose you need to build a debug version. Go to the project directory and call ./Debug. The first three lines create directories. The fourth line to the make utility says that the current directory should be started at project / project / obj / Debug, the path to the makefile is then passed along and two constants are set: build_flags (the compilation flags important for the debug version are listed here) this is application_debug).

Next, GNU make comes into play. Comment on each line of makefile:

1: A variable is declared with the name of the root directory of the header files.

2: A variable is declared with the name of the source directory root.

3: A variable is declared with the names of the subdirectories of the root source directory.

4: A variable is declared with common compilation flags. -MD forces the compiler to generate the same-named dependency file with the .d extension for each source. Each such file looks as a rule, where the target is the source name, and dependencies are all source codes and header files that it includes with the #include directive. The -pipe flag causes the compiler to use IPC instead of the file system, which somewhat speeds up compilation.

5: A variable is declared with general layout flags. -s causes the linker to remove .symtab, .strtab sections from the resulting ELF file and a bunch of sections with names like .debug *, which significantly reduces its size. In order to better debug this key can be removed.

6: A variable is declared with the names of the libraries used in the form of layout keys.

8: A variable is declared containing relative names of directories with standard header files. Then such names are directly passed to the compiler, preceded by the -I switch. For our case, it will turn out ../../include, because we have one such directory. The addprefix function adds its first argument to all the targets that the second argument sets.

9: A variable is declared containing the relative names of all the subdirectories of the root directory of sources. As a result, we get: ../../src/. ../../src/dir1 ../../src/dir1.

10: A variable is declared containing the names of the subdirectories of the project / obj / Debug / src directory relative to the current project / obj / Debug. That is, by this we enumerate a copy of the structure of the project / src directory. As a result, we get: / src / dir1 src / dir2.

11: A variable is declared containing the names of the sources found on the basis of the same-name * .c * files (.cpp \ .c), irrespective of the current directory. We look at the stages: the result of addsuff will be ../../src/./*.c* ../../src/dir1/*.c* ../../src/dir2/*.c*. The wildcard function will expand the templates with asterisks to real file names: ../../src/./main.cpp ../../src/dir1/file1.c ../../src/dir1/file2.cpp ../../src/dir2/file3.c ../../src/dir2/file4.c. The patsubsb function removes the prefix ../../ from file names (it replaces the pattern given by the first argument with the pattern in the second argument, and% denotes any number of characters). As a result, we get: src /./ main.cpp src / dir1 / file1.c src / dir1 / file2.cpp src / dir2 / file3.c src / dir2 / file4.c.

12: In the variable with the source names of the extension .cpp is replaced with .o.

13: In the variable with the source name of the extension .c is replaced by .o.

15: The first rule to be declared is that its goal becomes the goal of the entire project. A dependency is a constant containing the name of the program (../../bin/application_debug we passed it when running make from the script).

17: Description of the key target. The dependencies are also obvious: the presence of the created sub-directories in project / obj / Debug, repeating the structure of the project / src directory and the many object files in them.

18: Describes the action on linking object files to the target.

20: The rule in which the target is the project / obj / Debug / src directory and its subdirectories.

21: Goal Achievement Action — create the appropriate directories src /., Src / dir1 and src / dir2. The -p switch of the mkdir utility ignores the error if there is already a directory created during the creation of any directory.

23: VPATH is set to ../../. This is required for the following rule templates.

25: Describes a set of rules for which goals are any goals that correspond to the pattern% .o (that is, whose names end in .o), and dependencies for this purpose are goals of the same name, which correspond to the pattern% .cpp (that is, names that end in .cpp) In this case, the same name means not only an exact match, but also if the dependency name is preceded by the contents of the variable VPATH. For example, the names src / dir1 / file2 and ../../src/dir1/file2 match, since VPATH contains ../../.

26: Call the compiler to turn the source code in C ++ into an object file.

28: Describes a set of rules for which goals are any goals that correspond to the pattern% .o (that is, whose names end in .o), and dependencies for this purpose are goals of the same name that correspond to the pattern% .c (that is, names that end in .c) Same name as line 23.

29: Call the compiler to turn the C source code into an object file.

31: Some clean target is declared abstract. Achieving an abstract goal always happens and does not depend on the existence of a file of the same name.

32: Declaring an abstract clean target.

33: The action to achieve it is to destroy the project / bin and project / obj directories with all their contents.

36: Include the contents of all dependency files (with a .d extension) in subdirectories of the current directory. The make utility does this at the beginning of the parsing makefile. However, dependency files are created only after compilation. This means that during the first build no such file will be included. But it's not scary. The purpose of including these files is to recompile the sources depending on the modified header file. For the second and subsequent builds, the make utility will include the rules described in all dependency files and, if necessary, achieve all the goals dependent on the modified header file.

Good luck!

Source: https://habr.com/ru/post/111691/


All Articles