linux中gmake是什么

linux中,gmake就是GUN make,是一種流行的、常用的用于構建c語言軟件的程序,用于構建Linux內核和其他常用的gnu/Linux程序和軟件庫。GNU Make是一個可以自動運行shell命令并幫助執行重復任務的程序;它通常用于將文件轉換成其他形式,例如將源代碼文件編譯成程序或庫。

linux中gmake是什么

本教程操作環境:linux7.3系統、Dell G3電腦。

gmake就是GUN make,因為在linux外的平臺上,make一般被占用了,所以GUN make只好叫gmake了。

GNU Make是一種流行的、常用的用于構建C語言軟件的程序。用于構建Linux內核和其他常用的GNU/Linux程序和軟件庫。

大多數嵌入式軟件開發人員在職業生涯中的某個時候都會使用GNU Make,要么使用它來編譯小型庫,要么構建整個項目。盡管有很多很多的選項可以替代Make,但是由于它的特性集和廣泛的支持,它仍然通常被選擇為新軟件的構建系統。

本文解釋了GNU Make的一般概念和特性,并包括了如何最大限度地利用Make構建的建議!這是我最喜歡/最常用的Make概念和功能的簡要介紹.

什么是GNU Make?

GNU Make是一個可以自動運行shell命令并幫助執行重復任務的程序。它通常用于將文件轉換成其他形式,例如將源代碼文件編譯成程序或庫。

它通過跟蹤先決條件和執行命令層次結構來生成目標來實現這一點。

盡管GNU Make手冊很長,但我建議閱讀一下,因為它是我找到的最好的參考:https://www.gnu.org/software/make/manual/html_node/index.html

何時選擇Make

Make適用于構建小型C/ c++項目或庫,這些項目或庫將包含在另一個項目的構建系統中。大多數構建系統都有辦法集成基于make的子項目。

對于較大的項目,您會發現更現代的構建系統更易于使用。

在以下情況下,我建議使用非Make的構建系統:

當正在構建的目標(或文件)數量為(或最終將為)數百時。 需要一個“配置”步驟,它設置和保存變量、目標定義和環境配置。 該項目將保持內部或私有,將不需要由終端用戶構建。 您會發現調試是一項令人沮喪的工作。 您需要構建的是跨平臺的,可以在macos、Linux和windows上構建。 在這些情況下,您可能會發現使用CMake、Bazel、Meson或其他現代構建系統是一種更愉快的體驗。

調用Make

運行make將從當前目錄加載一個名為Makefile的文件,并嘗試更新默認目標(稍后會詳細介紹目標)。

Make將依次搜索名為GNUmakefile、makefile和makefile的文件

你可以使用-f/——file參數指定一個特定的makefile:

$ make -f foo.mk 你可以指定任意數量的目標,列出它們作為位置參數:

#典型目標 $ make clean all 你可以用-C參數傳遞Make目錄,它會運行Make,就像它首先被cd到那個目錄一樣。

$ make -C some/sub/Directory 有趣的事實:git也可以和-C一起運行,達到同樣的效果!

并行調用

如果提供-j或-l選項,Make可以并行運行作業。我被告知的一個指導原則是,將作業限制設置為處理器核心數量的1.5倍:

#a machine with 4 cores: make -j make -j 有趣的是,我發現使用-l“負載限制”選項的CPU利用率比使用-j“工作”選項略好。盡管YMMV !

有幾種方法可以通過編程方式找到當前機器的CPU計數。一個簡單的方法是使用python multiprocessing.cpu_count()函數來獲得支持的系統的線程數量(注意與超線程系統,這將消耗大量的計算機資源,但可能是更可取的讓讓產生無限的工作)。

#在子shell中調用python的cpu_count()函數 make -l (python -c “import multiprocessing;print (multiprocessing.cpu_count())”)

并行調用期間的輸出

如果Make正在并行執行的命令有大量輸出,您可能會看到在stdout上交錯輸出。為了處理這個問題,Make有一個選項——output -sync。

我建議使用——output-sync=recurse,它將在每個目標完成時打印recipe的全部輸出,而不會分散其他recipe輸出。

如果recipe使用遞歸Make,它還將一起輸出整個遞歸Make的輸出。

對Makefile的剖析 Makefile包含用于生成目標的規則。Makefile的一些基本組件如下所示:

#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

讓我們看看上面例子的每個部分。

變量

變量使用語法$(FOO),其中FOO是變量名。

變量包含純字符串,因為Make沒有其他數據類型。附加到一個變量將添加一個空格和新的內容:

FOO = one FOO += two # FOO is now "one two"  FOO = one FOO = $(FOO)two # FOO is now ".netwo"

變量賦值

在GNU Make語法中,變量的賦值方式有兩種:

右邊的表達式是逐字賦值給變量的——這很像C/ c++中的宏,在使用變量時對表達式求值:

FOO = 1 BAR = $(FOO) FOO = 2 # prints BAR=2 $(info BAR=$(BAR))

將一個表達式的結果賦值給一個變量;表達式在賦值時展開:

FOO = 1 BAR := $(FOO) FOO = 2 # prints BAR=1 $(info BAR=$(BAR))

注意:上面的$(info…)函數用于打印表達式,在調試makefile時非常方便!*’

未顯式、隱式或未自動設置的變量將計算為空字符串。

環境變量

環境變量被攜帶到Make執行環境中。以下面的makefile為例:

$(info YOLO variable = $(YOLO))

如果我們在運行make時在shell命令中設置了變量YOLO,我們將設置這個值:

$ YOLO="hello there!" make YOLO variable = hello there! make: *** No targets.  Stop.

注意:Make打印“No targets”錯誤,因為我們的makefile沒有列出目標!

如果你使用?=賦值語法,Make只會在變量沒有值的情況下賦值:

Makefile:

#默認CC為gcc CC ? = gcc

然后我們可以重寫makefile中的$(CC):

$ CC=clang make

另一個常見的模式是允許插入額外的標志。在makefile中,我們將追加變量而不是直接賦值給它。

CFLAGS += -Wall

這允許從環境中傳入額外的標志:

$ CFLAGS='-WError=conversion -Werror=double-promotion' make

這是非常有用的!

最重要的變量

變量使用的一個特殊類別稱為覆蓋變量。使用此命令行選項將覆蓋設置在環境中的或Makefile中的值!

Makefile:

# any value set elsewhere YOLO = "not overridden" $(info $(YOLO))

命令:

# 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.

有針對性的變量

這些變量僅在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'

隱式變量

這些都是由Make預先定義的(除非用同名的任何其他變量類型重寫)。一些常見的例子:

$(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

自動變量

這些是由Make設置的特殊變量,在recipe上下文中可用。它們對于防止重復的名字很有用(Don ‘t Repeat Yourself)。

一些常見的自動變量:

# $@ : 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

目標(目標)

目標是規則語法的左邊:

arget: prerequisite  recipe

target幾乎總是命名文件。這是因為Make使用最后修改時間來跟蹤target是否比其prerequistite更新或更早,以及是否需要重新構建它!

當調用Make時,你可以通過將其指定為位置參數來指定想要構建的target:

# 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://github.com/rocky/remake

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 建議

讓Make發揮最大作用的建議列表:

target通常應該是真實的文件。 當發出子MAKE命令時,總是使用(MAKE)。 盡量避免使用.phony目標。如果規則生成任何文件工件,請考慮將其作為目標,而不是冒名! 盡量避免使用隱含的規則。 對于C文件,確保使用.d自動包括跟蹤! 小心使用元編程。 在規則中使用自動變量。總是嘗試使用@作為菜譜輸出路徑,這樣你的規則和Make的路徑就完全相同了。 在makefile中自由地使用注釋,特別是在使用了復雜的行為或微妙的語法時。你的同事(還有未來的自己)會感謝你的。 使用-j或-l選項并行運行Make ! 盡量避免使用touch命令來跟蹤規則完成.

其他

您還可能在開放源碼項目中遇到automake(請查找./configure腳本)。這是一個生成makefile的相關工具,值得一看(特別是如果您正在編寫需要廣泛移植的C軟件)。

今天有許多GNU Make的競爭者,我鼓勵大家去研究它們。一些例子:

CMake非常流行(Zephyr項目使用了它),值得一看。它使out-of-tree 構建非常容易 Bazel使用聲明式語法(vs. Make的命令式方法) Meson是一個像cmake一樣的元構建器,但默認使用Ninja作為后端,可以非常快

相關推薦:《https://github.com/rocky/remake

以上就是

? 版權聲明
THE END
喜歡就支持一下吧
點贊12 分享