首頁 > 運維 > linux運維 > 主體

linux中gmake是什麼

青灯夜游
發布: 2022-11-09 19:04:41
原創
3066 人瀏覽過

在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 "onetwo"
登入後複製

#變數賦值

##在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 '$<&#39; 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 &#39;program&#39;. Note the use of the &#39;$^&#39;
# 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 &#39;$<&#39; 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
# &#39;-&#39; 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 建議

#讓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作為後端,可以非常快速

#相關推薦:《

Linux影片教學

以上是linux中gmake是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板