In Linux, gmake is GUN make, which is a popular and commonly used program for building C language software. It is used to build the Linux kernel and other commonly used GNU/Linux programs and software libraries. GNU Make is a program that can automate shell commands and help perform repetitive tasks; it is often used to convert files into other forms, such as compiling source code files into programs or libraries.
#The operating environment of this tutorial: linux7.3 system, Dell G3 computer.
gmake is GUN make, because on platforms other than Linux, make is generally occupied, so GUN make has to be called gmake.
GNU Make is a popular and commonly used program for building C language software. Used to build the Linux kernel and other commonly used GNU/Linux programs and software libraries.
Most embedded software developers will use GNU Make at some point in their careers, either using it to compile small libraries or to build entire projects. Although there are many, many alternatives to Make, it is still often chosen as the build system for new software due to its feature set and broad support.
This article explains the general concepts and features of GNU Make and includes advice on how to get the most out of your Make builds! This is a brief introduction to my favorite/most commonly used Make concepts and features.
What is GNU Make?
GNU Make is a program that can automate shell commands and help perform repetitive tasks. It is often used to convert files into other forms, such as compiling source code files into programs or libraries.
It does this by tracking prerequisites and executing a hierarchy of commands to generate targets.
Although the GNU Make manual is quite long, I recommend reading it as it is the best reference I have found: https://www.gnu.org/software/make/manual/html_node/index. html
When to choose Make
Make is suitable for building small C/c projects or libraries that will be included in another in the project's build system. Most build systems have a way to integrate make-based subprojects.
For larger projects, you may find more modern build systems easier to use.
I recommend using a non-Make build system in the following situations:
When the number of targets (or files) being built is (or will eventually be) hundreds. A "configure" step is required, which sets and saves variables, target definitions, and environment configuration. The project will remain in-house or private and will not need to be built by end users. You may find that debugging can be a frustrating endeavor. What you need to build is cross-platform and can be built on macOS, Linux, and Windows. In these cases, you may find that using CMake, Bazel, Meson, or other modern build systems is a more pleasant experience.
Calling Make
Running make will load a file named Makefile from the current directory and attempt to update the default target (more on this later) introduction goals).
Make will search for files named GNUmakefile, makefile and makefile in sequence
You can use the -f/--file parameter to specify a specific makefile:
$ make - f foo.mk You can specify any number of targets, listing them as positional arguments:
# Typical targets $ make clean all You can pass the Make directory with the -C argument and it will run Make just like it does First cd to that directory.
$ make -C some/sub/directory Fun fact: git can also be run with -C to achieve the same effect!
Parallel calls
Make can run jobs in parallel if the -j or -l option is provided. One guideline I've been told is to set the job limit to 1.5 times the number of processor cores:
#a machine with 4 cores: make -j make -j Interestingly , I found that using the -l "load limit" option gave slightly better CPU utilization than using the -j "job" option. Although YMMV!
There are several ways to find the current machine's CPU count programmatically. A simple method is to use the python multiprocessing.cpu_count() function to get the number of threads supported by the system (note that with hyper-threaded systems, this will consume a lot of computer resources, but may be preferable to letting the system generate unlimited work).
#Call python’s cpu_count() function in a subshell make -l (python -c "import multiprocessing;print (multiprocessing.cpu_count())")
Output During Parallel Invocations
If Make is executing commands in parallel that have a lot of output, you may see the output staggered on stdout. To deal with this problem, Make has an option - output -sync.
I recommend using --output-sync=recurse, which will print the entire output of the recipe when each goal is completed without distracting the output of other recipes.
If the recipe uses recursive Make, it will also output the output of the entire recursive Make together.
Analysis of Makefile Makefile contains rules for generating targets. Some basic components of a Makefile are as follows:
#Comments are prefixed with the '#' symbol #A variable assignment FOO = "hello there!" #A rule creating target "test", with "test.c" as a prerequisite test: test.c # The contents of a rule is called the "recipe", and is # typically composed of one or more shell commands. # It must be indented from the target name (historically with # tabs, spaces are permitted) # Using the variable "FOO" echo $(FOO) # Calling the C compiler using a predefined variable naming # the default C compiler, '$(CC)' $(CC) test.c -o test
Let’s look at each part of the above example.
Variables
Variables use the syntax $(FOO), where FOO is the variable name.
Variables contain plain strings because Make has no other data types. Appending to a variable will add a space and new content:
FOO = one FOO += two # FOO is now "one two" FOO = one FOO = $(FOO)two # FOO is now "onetwo"
Variable assignment
In GNU Make syntax , there are two ways to assign values to variables:
The expression on the right is assigned to the variable literally - this is very similar to the macro in C/c, which evaluates the expression when using the variable:
FOO = 1 BAR = $(FOO) FOO = 2 # prints BAR=2 $(info BAR=$(BAR))
Assign the result of an expression to a variable; the expression is expanded during assignment:
FOO = 1 BAR := $(FOO) FOO = 2 # prints BAR=1 $(info BAR=$(BAR))
Note: the $(info above ...) function is used to print expressions, which is very convenient when debugging makefiles!*'
Variables that are not explicitly, implicitly or automatically set will evaluate to the empty string.
Environment variables
Environment variables are carried into the Make execution environment. Take the following makefile as an example:
$(info YOLO variable = $(YOLO))
If we set the variable YOLO in the shell command when running make, we will set this value:
$ YOLO="hello there!" make YOLO variable = hello there! make: *** No targets. Stop.
Note: Make prints a "No targets" error because our makefile has no targets listed!
If you use the ?= assignment syntax, Make will only assign a value if the variable has no value:
Makefile:
#默认CC为gcc CC ? = gcc
Then we can override $(CC) in the makefile:
$ CC=clang make
Another common pattern is to allow the insertion of additional flags. In the makefile, we will append the variable instead of assigning it directly.
CFLAGS = -Wall
This allows additional flags to be passed in from the environment:
$ CFLAGS='-Werror=conversion -Werror=double-promotion' make
This is very useful The !
The most important variables
A special category of variables used is called overriding variables. Using this command line option will override values set in the environment or in the Makefile!
Makefile:
# any value set elsewhere YOLO = "not overridden" $(info $(YOLO))
Command:
# setting "YOLO" to different values in the environment + makefile + overriding # variable, yields the overriding value $ YOLO="environment set" make YOLO='overridden!!' overridden!! make: *** No targets. Stop.
Targeted Variables
These variables are only available in the recipe context. They also apply to any must-have recipe!
# set the -g value to CFLAGS # applies to the prog.o/foo.o/bar.o recipes too! prog : CFLAGS = -g prog : prog.o foo.o bar.o echo $(CFLAGS) # will print '-g'
Implicit variables
These are predefined by Make (unless overridden with any other variable type of the same name). Some common examples:
$(CC) - the C compiler (gcc) $(AR) - archive program (ar) $(CFLAGS) - flags for the C compiler Full list here: https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
Auto variables
These are special variables set by Make, in the recipe context Available in . They are useful for preventing duplicate names (Don 't Repeat Yourself).
Some common automatic variables:
# $@ : the target name, here it would be "test.txt" test.txt: echo HEYO > $@ # $^ : name of all the prerequisites all.zip: foo.txt test.txt # run the gzip command with all the prerequisites "$^", outputting to the # name of the target, "$@" gzip -c $^ > $@ See more at: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
target(target)
The target is the rule The left side of the syntax:
arget: prerequisite recipe
target almost always names the file. This is because Make uses the last modification time to track whether the target is newer or older than its prerequistite, and whether it needs to be rebuilt!
When calling Make, you can specify what you want by specifying it as a positional argument. Target to build:
# make the 'test.txt' and 'all.zip' targets make test.txt all.zip
如果您没有在命令中指定目标,Make将使用makefile中指定的第一个目标,称为“默认目标”(如果需要,也可以覆盖默认目标)。
虚假phony目标
有时候设置元目标是很有用的,比如all, clean, test等等。在这些情况下,您不希望Make检查名为all/clean等的文件。
Make提供.PHONY目标语法,将目标标记为不指向文件:
假设我们的项目构建了一个程序和一个库foo和foo.a;如果我们想要 在默认情况下,我们可以创建一个'all'规则来构建两者 .PHONY:all all : foo foo.a
如果你有多个假目标,一个好的模式可能是将每个目标都附加到定义它的.PHONY中:
# the 'all' rule that builds and tests. Note that it's listed first to make it # the default rule .PHONY: all all: build test # compile foo.c into a program 'foo' foo: foo.c $(CC) foo.c -o foo # compile foo-lib.c into a library 'foo.a' foo.a: foo-lib.c # compile the object file $(CC) foo-lib.c -c foo-lib.o # use ar to create a static library containing our object file. using the # '$@' variable here to specify the rule target 'foo.a' $(AR) rcs $@ foo-lib.o # a phony rule that builds our project; just contains a prerequisite of the # library + program .PHONY: build build: foo foo.a # a phony rule that runs our test harness. has the 'build' target as a # prerequisite! Make will make sure (pardon the pun) the build rule executes # first .PHONY: test test: build ./run-tests.sh
请注意! !. phony目标总是被认为是过期的,因此Make将总是运行这些目标的配方(因此也运行任何具有. phony先决条件的目标!)小心使用! !
隐式规则
隐含规则由Make提供。我发现使用它们会让人感到困惑,因为在幕后发生了太多的行为。你偶尔会在野外遇到它们,所以要小心。
# this will compile 'test.c' with the default $(CC), $(CFLAGS), into the program # 'test'. it will handle prerequisite tracking on test.c test: test.o Full list of implicit rules here: https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html
模式的规则
模式规则允许你编写一个通用规则,通过模式匹配应用于多个目标:
# Note the use of the '$<' automatic variable, specifying the first # prerequisite, which is the .c file %.o: %.c $(CC) -c $< -o $@
or
OBJ_FILES = foo.o bar.o # Use CC to link foo.o + bar.o into 'program'. Note the use of the '$^' # automatic variable, specifying ALL the prerequisites (all the OBJ_FILES) # should be part of the link command program: $(OBJ_FILES) $(CC) -o $@ $^
先决条件
如上所述,Make将在运行规则之前检查这些目标。它们可以是文件或其他目标。
如果任何先决条件比目标更新(修改时间),Make将运行目标规则。
在C项目中,你可能有一个将C文件转换为目标文件的规则,如果C文件发生变化,你希望目标文件重新生成:
foo.o: foo.c # use automatic variables for the input and output file names $(CC) $^ -c $@
自动的先决条件
对于C语言项目来说,一个非常重要的考虑是,如果C文件的#include头文件发生了变化,那么将触发重新编译。这是通过gcc/clang的-M编译器标志完成的,它将输出一个.d文件,然后用Make include指令导入。
.d文件将包含.c文件的必要先决条件,因此任何头文件的更改都会导致重新构建。
点击这里查看更多详情:
https://www.gnu.org/software/make/manual/html_node/Automatic-Prerequisites.html
http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
基本形式可能是:
# these are the compiler flags for emitting the dependency tracking file. Note # the usage of the '$<' automatic variable DEPFLAGS = -MMD -MP -MF $<.d test.o: test.c $(CC) $(DEPFLAGS) $< -c $@ # bring in the prerequisites by including all the .d files. prefix the line with # '-' to prevent an error if any of the files do not exist -include $(wildcard *.d)
Order-only 先决条件
这些先决条件只有在不存在的情况下才会构建;如果它们比目标更新,则不会触发目标重新构建。
典型的用法是为输出文件创建一个目录;将文件发送到目录将更新其mtime属性,但我们不希望由此触发重新构建。
OUTPUT_DIR = build # output the .o to the build directory, which we add as an order-only # prerequisite- anything right of the | pipe is considered order-only $(OUTPUT_DIR)/test.o: test.c | $(OUTPUT_DIR) $(CC) -c $^ -o $@ # rule to make the directory $(OUTPUT_DIR): mkdir -p $@
recipe
“recipe”是创建目标时要执行的shell命令列表。它们被传递到子shell中(默认为/bin/sh)。如果target在recipe运行后更新,则认为规则是成功的(但如果没有更新,则不视为错误)。
foo.txt: # a simple recipe echo HEYO > $@
如果配方中的任何一行返回非零退出代码,Make将终止并打印一条错误消息。你可以通过前缀-字符来告诉Make忽略非零退出码:
.PHONY: clean clean: # we don't care if rm fails -rm -r ./build
在recipe行前面加上@将禁止在执行之前echo该行:
clean: @# this recipe will just print 'About to clean everything!' @# prefixing the shell comment lines '#' here also prevents them from @# appearing during execution @echo About to clean everything!
Make会在运行recipe上下文中展开变量/函数表达式,但不会处理它。如果你想访问shell变量,请使用$:
USER = linus print-user: # print out the shell variable $USER echo $$USER # print out the make variable USER echo $(USER)
function
Make函数的调用语法如下:
$(function-name arguments) 其中arguments是用逗号分隔的参数列表。
For example:
FILES=$(wildcard *.c) # you can combine function calls; here we strip the suffix off of $(FILES) with # the $(basename) function, then add the .o suffix O_FILES=$(addsuffix .o,$(basename $(FILES))) # note that the GNU Make Manual suggests an alternate form for this particular # operation: O_FILES=$(FILES:.c=.o)
用户定义函数
reverse = $(2) $(1) foo = $(call reverse,a,b) # recursive wildcard (use it instead of $(shell find . -name '*.c')) # taken from https://stackoverflow.com/a/18258352 rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)) C_FILES = $(call rwildcard,.,*.c)
shell函数
你可以让Make调用一个shell表达式并捕获结果:
TODAYS_DATE=$(shell date --iso-8601)
不过,我在使用这个功能时很谨慎;它会增加对你使用的任何程序的依赖,所以如果你正在调用更奇特的程序,确保你的构建环境是受控的(例如在容器中或使用Conda)。
make的条件表达式
FOO=yolo ifeq ($(FOO),yolo) $(info foo is yolo!) else $(info foo is not yolo :( ) endif # testing if a variable is set; unset variables are empty ifneq ($(FOO),) # checking if FOO is blank $(info FOO is unset) endif # "complex conditional" ifeq ($(FOO),yolo) $(info foo is yolo) else ifeq ($(FOO), heyo) $(info foo is heyo) else $(info foo is not yolo or heyo :( ) endif
make include
sources.mk:
SOURCE_FILES :=
bar.c
foo.c \
Makefile:
include sources.mk
OBJECT_FILES = $(SOURCE_FILES:.c=.o)
%.o: %.c (CC) -c ^ -o $@
make eval
# generate rules for xml->json in some weird world FILES = $(wildcard inputfile/*.xml) # create a user-defined function that generates rules define GENERATE_RULE = $(eval # prereq rule for creating output directory $(1)_OUT_DIR = $(dir $(1))/$(1)_out $(1)_OUT_DIR: mkdir -p $@ # rule that calls a script on the input file and produces $@ target $(1)_OUT_DIR/$(1).json: $(1) | $(1)_OUT_DIR ./convert-xml-to-json.sh $(1) $@ ) # add the target to the all rule all: $(1)_OUT_DIR/$(1).json endef # produce the rules .PHONY: all all: $(foreach file,$(FILES),$(call GENERATE_RULE,$(file)))
请注意,使用Make的这个特性的方法可能会让人很困惑,添加一些有用的注释来解释意图是什么,对您未来的自己会很有用!
VPATH
VPATH是一个特殊的Make变量,它包含Make在查找先决条件和目标时应该搜索的目录列表。
它可以用来将对象文件或其他派生文件发送到./build目录中,而不是把src目录弄得乱七八糟:
# This makefile should be invoked from the temporary build directory, eg: # $ mkdir -p build && cd ./build && make -f ../Makefile # Derive the directory containing this Makefile MAKEFILE_DIR = $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) # now inform Make we should look for prerequisites from the root directory as # well as the cwd VPATH += $(MAKEFILE_DIR) SRC_FILES = $(wildcard $(MAKEFILE_DIR)/src/*.c) # Set the obj file paths to be relative to the cwd OBJ_FILES = $(subst $(MAKEFILE_DIR)/,,$(SRC_FILES:.c=.o)) # now we can continue as if Make was running from the root directory, and not a # subdirectory # $(OBJ_FILES) will be built by the pattern rule below foo.a: $(OBJ_FILES) $(AR) rcs $@ $(OBJ_FILES) # pattern rule; since we added ROOT_DIR to VPATH, Make can find prerequisites # like `src/test.c` when running from the build directory! %.o: %.c # create the directory tree for the output file echo $@ mkdir -p $(dir $@) # compile $(CC) -c $^ -o $@
touch file
# our tools are stored in tools.tar.gz, and downloaded from a server TOOLS_ARCHIVE = tools.tar.gz TOOLS_URL = https://httpbin.org/get # the rule to download the tools using wget $(TOOLS_ARCHIVE): wget $(TOOLS_URL) -O $(TOOLS_ARCHIVE) # rule to unpack them tools-unpacked.dummy: $(TOOLS_ARCHIVE) # running this command results in a directory.. but how do we know it # completed, without a file to track? tar xzvf $^ # use the touch command to record completion in a dummy file touch $@
调试makefile
对于小问题,我通常使用printf的Make等效函数,即$(info/warning/error)函数,例如当检查不工作的条件路径时:
ifeq ($(CC),clang) $(error whoops, clang not supported!) endif
要调试为什么规则在不应该运行的情况下运行(或者相反),可以使用——debug选项:https://www.gnu.org/software/make/manual/html_node/Options-Summary.html
我建议在使用此选项时将stdout重定向到文件,它会产生大量输出。
profile
For profiling a make invocation (e.g. for attempting to improve compilation times), this tool can be useful:
https://github.com/rocky/remake
Check out the tips here for compilation-related performance improvements:
https://interrupt.memfault.com/blog/improving-compilation-times-c-cpp-projects
verbose flag
# Makefile for building the 'example' binary from C sources # Verbose flag ifeq ($(V),1) Q := else Q := @ endif # The build folder, for all generated output. This should normally be included # in a .gitignore rule BUILD_FOLDER := build # Default all rule will build the 'example' target, which here is an executable .PHONY: all: $(BUILD_FOLDER)/example # List of C source files. Putting this in a separate variable, with a file on # each line, makes it easy to add files later (and makes it easier to see # additions in pull requests). Larger projects might use a wildcard to locate # source files automatically. SRC_FILES = \ src/example.c \ src/main.c # Generate a list of .o files from the .c files. Prefix them with the build # folder to output the files there OBJ_FILES = $(addprefix $(BUILD_FOLDER)/,$(SRC_FILES:.c=.o)) # Generate a list of depfiles, used to track includes. The file name is the same # as the object files with the .d extension added DEP_FILES = $(addsuffix .d,$(OBJ_FILES)) # Flags to generate the .d dependency-tracking files when we compile. It's # named the same as the target file with the .d extension DEPFLAGS = -MMD -MP -MF $@.d # Include the dependency tracking files -include $(DEP_FILES) # List of include dirs. These are put into CFLAGS. INCLUDE_DIRS = \ src/ # Prefix the include dirs with '-I' when passing them to the compiler CFLAGS += $(addprefix -I,$(INCLUDE_DIRS)) # Set some compiler flags we need. Note that we're appending to the CFLAGS # variable CFLAGS += \ -std=c11 \ -Wall \ -Werror \ -ffunction-sections -fdata-sections \ -Og \ -g3 # Our project requires some linker flags: garbage collect sections, output a # .map file LDFLAGS += \ -Wl,--gc-sections,-Map,$@.map # Set LDLIBS to specify linking with libm, the math library LDLIBS += \ -lm # The rule for compiling the SRC_FILES into OBJ_FILES $(BUILD_FOLDER)/%.o: %.c @echo Compiling $(notdir $<) @# Create the folder structure for the output file @mkdir -p $(dir $@) $(Q) $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@ # The rule for building the executable "example", using OBJ_FILES as # prerequisites. Since we're not relying on an implicit rule, we need to # explicity list CFLAGS, LDFLAGS, LDLIBS $(BUILD_FOLDER)/example: $(OBJ_FILES) @echo Linking $(notdir $@) $(Q) $(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ # Remove debug information for a smaller executable. An embedded project might # instead using [arm-none-eabi-]objcopy to convert the ELF file to a raw binary # suitable to be written to an embedded device STRIPPED_OUTPUT = $(BUILD_FOLDER)/example-stripped $(STRIPPED_OUTPUT): $(BUILD_FOLDER)/example @echo Stripping $(notdir $@) $(Q)objcopy --strip-debug $^ $@ # Since all our generated output is placed into the build folder, our clean rule # is simple. Prefix the recipe line with '-' to not error if the build folder # doesn't exist (the -f flag for rm also has this effect) .PHONY: clean clean: - rm -rf $(BUILD_FOLDER)
$ V=1 make
make Suggestions
List of suggestions to get the most out of Make:
target Usually should be a real file. When issuing sub-MAKE commands, always use (MAKE). Try to avoid using .phony targets. If the rule generates any file artifacts, consider targeting them instead of impersonating them! Try to avoid using implicit rules. For C files, make sure to use .d to automatically include tracing! Use metaprogramming with care. Use automatic variables in rules. Always try to use @ as the recipe output path so that your rules and Make's paths are exactly the same. Use comments liberally in makefiles, especially when complex behavior or subtle syntax is used. Your colleagues (and future self) will thank you. Run Make in parallel using the -j or -l option! Try to avoid using the touch command to track rule completion.
Others
You may also be using Encountered automake in open source projects (please look for the ./configure script). This is a related tool for generating makefiles and is worth a look (especially if you are writing C software that needs to be extensively ported).
There are many competitors to GNU Make today, and I encourage everyone to study them. Some examples:
CMake is very popular (the Zephyr project uses it) and worth a look. It makes out-of-tree building very easy. Bazel uses declarative syntax (vs. Make's imperative approach). Meson is a meta-builder like cmake, but uses Ninja as the backend by default and can be very fast.
Related recommendations: "Linux Video Tutorial"
The above is the detailed content of What is gmake in linux. For more information, please follow other related articles on the PHP Chinese website!