怎么把makefile文件和c++源文件转成vs工程_通过实例学Makefile
前言老实说在我学过的所有“知识"中,shell语法和Make规则是最让我厌烦的,这俩东西更像是宏替换语言,潜规则特别多。但现实是Linux和后端编程少不了要与它们打交道,那有什么办法呢,只能学着接受它们。GNU Make是在实践中慢慢丰富起来的“语言”,一开始只是为了简单的编译问题,随着工程变复杂,后面加入的特性也越来越多,以至于这一份GNU Make Manual打印成PDF之后,竟有200页之
前言
老实说在我学过的所有“知识"中,shell语法和Make规则是最让我厌烦的,这俩东西更像是宏替换语言,潜规则特别多。但现实是Linux和后端编程少不了要与它们打交道,那有什么办法呢,只能学着接受它们。
GNU Make是在实践中慢慢丰富起来的“语言”,一开始只是为了简单的编译问题,随着工程变复杂,后面加入的特性也越来越多,以至于这一份GNU Make Manual打印成PDF之后,竟有200页之多。无怪乎很多人望而生畏。
其实在项目中使用到的特性并不会很多,如果简单使用,只要几条语句就能完成任务了;但若要支持更丰富的需求,则要懂得更多的特性。本文试图从Make Manual的一个简单例子开始,慢慢丰富它,使之最后成为一份真正实用的Makefile模板。在这个过程中,也许我们能学到一些东西,就像我自己在实践中学到的那样。
虽然Make Manual也来回翻了好几次,但我必须承认自己并不是一个写Make的专家,只能说“够用”罢了。
一个简单的例子
Make Manual里面有一个例子是这样的:
edit : main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
它的作用是编译一个叫edit的程序,这个程序是由main.c, command.c...等等这些C代码组成的。main.c编译成main.o,其他源文件也编译成相应的.o文件,最后再把这些目标文件链接成edit程序。
Makefile展示了一个很直观的过程,如果用语言描述大概是这样的:
- edit 依赖于main.o...等这些目标文件,如果目标文件比edit新,或者edit文件不存在,则会执行命令:生成edit程序。
- main.o等文件也有自己的依赖,它们分别依赖于不同的.c文件和.h文件,同样如果.c|.h文件更新,或者.o文件不存在,也会执行命令:生成.o文件。
edit称为target,是我们要生成的目标文件;edit后面的main.o...称之后prerequisites,是target依赖的条件;cc -o edit main.o...是生成目标所要执行的命令,称之为recipes,recipes须以tab键缩进。这三个东西合起来称为一个规则。
prerequisites也可以是target,比如main.o,它也有自己的prerequisites,这样的规则最终会形成一个依赖链。而make执行一个规则就是沿着它的依赖链不断往下判断,最后再从下往上执行目标更新的操作。
当我们在shell运行make的时候,make会依次查找GNUmakefile, makefile, Makefile文件,找到之后执行里面的第1个目标,上面的例子就是edit。
如果想特别指定makefile文件,要用-f选项,如:make -f MyMakefile;如果要指定特定的目标,要在make命令后面加上这个目标,如:make main.o。
使用变量
我们可以把重复的文件赋值给一个变量,这样变量就可以指代这些文件,有了变量就不用在文件增删时,到处去修改Makefile:
objects = main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
把main.o...等赋值给objects,后面就可以用$(objects)代表这些目标文件。edit目标简化了很多,可是main.o这些目标没有简化,仍然需要一个个地写它们的依赖,加一个C文件就得写一个目标,这仍然不可维护。
在继续简化之前,有必要说一下变量的展开:
foo = hello
bar = $(foo)
foo += world # foo追加值
default:
@echo $(bar)
bar引用了foo,而foo在之后又追加了一个值,echo出来的结果是hello world,这是因为用=号赋值的变量叫递归扩展变量,它会等到执行的时候才按引用递归扩展出来,所以当bar引用foo的时候,它并没有扩展出最终值。
用:=赋值的变量叫简单扩展变量,它在引用的时候会扩展成最终值,所以下面:
foo = hello
bar := $(foo) # 此时值已扩展
foo += world # foo追加值
default:
@echo $(bar)
输出的是hello,这通常更符合我们的直觉。
事实上变量展开还涉及到make的两遍处理,第一遍是读入Mailefile阶段,第二遍更新目标阶段。不过这里不想陷入这些细节了,毕竟我们是以实用为主,为了简单起见,多使用:=可能是更好的选择。
使用隐式规则
上面的Make规则可以进一步简化成这样:
objects := main.o kbd.o command.o display.o
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
.PHONY : clean
clean :
rm edit $(objects)
即把.o文件和.c的依赖规则去掉了,这里使用到了make的隐式规则:edit依赖于.o文件,.o文件依赖于.c文件。make发现没有.o规则时,它会“聪明”地查找同名的.c文件,并判断是否需要更新.o文件。如果需要更新,会自动调用$(CC) $(CPPFLAGS) $(CFLAGS) -c生成.o文件。
这再一次证明make是一个随着实践而进化的工具,有了这个隐式规则,我们确实没有必要一个个地写.o的规则了。
clean是一个"虚假"的目标,即它并不是一个文件名,但它可以作为一个目标,当我们输入make clean时就可以执行它。如果你的目录内真有一个clean的文件,make判断clean文件存在,而它又没有依赖其他的目标,下面的rm...就不会执行到。为了告诉make不要把它当成一个文件判断,这里使用了.PHONY把clean指定为虚假目标,这样make就一定会执行这个则。
隐式规则会自动使用$(CC)和$(CFLAGS)这些内置变量,只要修改这些变量就可以定制编译,为此我们把Makefile规范化一些:
SRCS := $(wildcard *.c) # 使用内置函数自动匹配源文件
OBJS := $(SRCS:.c=.o) # 将源文件转成目标文件
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS :=
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
.PHONY : clean
clean :
rm $(PROG) $(OBJS)
这里用make的内置函数wildcard,它可以根据通配符展开文件名,如此一来,我们再也不用手写文件名了。
接着使用替换引用的写法,把.c文件替换成.o,它的通用语法是$(var:a=b),注意里面不能有空格哦,不然会替换失败。和模式匹配符一起使用更加强大,如下面写法可以给文件加上目录名:
foo := a.c b.c c.c
bar := $(foo:%.c=dir/%.c)
default:
@echo $(bar)
# 输出:dir/a.c dir/b.c dir/c.c
我们定义了一些变量如PROG, CC...,以方便后面定制,规则写法现在变得比较固定,不太需要去修改它们。
生成头文件依赖
一切看起来很完美,在目录里怎么样增删改源代码,make都能正常处理。可是当我们修改了头文件,make却不会更新目标,make还没有聪明到知道源文件依赖的哪些头文件。
一个解决办法是每次增加了头文件,都执行make clean,然后再执行make。这个办法虽然好使,却会导致整个工程重新编译,这样有点浪费时间。
另一个办法是用gcc的-MM来生成目标文件依赖的源文件和头文件,在Make文件里加一个这样的规则:
depend:
@$(CC) $(CFLAGS) -MM $(SRCS)
然后每次调用make depend,会输出可直接使用的依赖关系,把这些依赖关系拷贝到Make文件里就行,最终Makefile变成这样子:
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lm
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
depend:
@$(CC) $(CFLAGS) -MM $(SRCS)
.PHONY : clean
clean :
rm $(PROG) $(OBJS)
# 这些是depend生成的
command.o: command.c defs.h command.h
display.o: display.c defs.h buffer.h
files.o: files.c defs.h buffer.h command.h
insert.o: insert.c defs.h buffer.h
kbd.o: kbd.c defs.h command.h
main.o: main.c defs.h
search.o: search.c defs.h buffer.h
utils.o: utils.c defs.h
这个办法虽好,但对于我们这些懒惰成性的程序员来说仍然不够方便,因为每次增加代码文件,都得执行一次depend,再手动粘贴到Makefile里,如果这些能自动生成就太棒了。
自动生成依赖规则
利用GNU make的remake特性可以做到这一点,先给出最终的Make规则:
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
DEPS := $(SRCS:.c=.d)
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lm
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
#############################################
# 自动生成依赖文件
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
%.d: %.c
@echo "make depend: $@"
@set -e; rm -f $@;
$(CC) $(CFLAGS) -MM $< | sed -E 's,($*).o[: ]*,1.o $@: ,g' > $@
#############################################
.PHONY : clean
clean :
rm -f $(PROG) $(OBJS) $(DEPS)
主要就是中间那一段,这一段是什么意思呢,首先有一个DEPS变量,它代表了和.o文件对应的.d文件,比如main.o就有一个对应的main.d文件,这个文件里面描述了.o文件的依赖关系。
-include $(DEPS)会尝试去包含这些文件,一开始这些文件肯定不存在,此时会使make暂停正常目标,去remake这些DEPS。 - 它找到下面的%.d: %.c模式匹配,觉得符合remake的目标,就去执行里面的命令: - 这些命令需要一点shell的知识,这里不展开说了,实在太恶心,主要意思是:使用gcc的-MM,传入c文件,输出o文件的依赖规则;然后通过管道,使用sed程序去作一些替换,把d文件也加入目标中。
以main.d为例,内容是这样的:
main.o main.d: main.c defs.h
所有d文件生成完毕,这下include成功了,也就有了o文件和依赖关系。接着make才继续执行原来的目标。
上面说明main.d文件也会依赖于main.c和defs.h,当这两个文件修改时,也会重触发%.d: %.c的执行,从而更新main.d文件。比如在main.c里面include了一个新的头文件buffer.h,main.d更新之后就变成:
main.o main.d: main.c defs.h buffer.h
细心的你一定注意到上面有一些特殊的符号:$@ $< $^,这种叫自动变量,一般和模式规则一起使用,用于代表target或prerequisites。
- $@ 代表当前target。
- $< 代表第一个prerequisites。
- $^ 代表所有prerequisites。
用%或变量来代表目标和依赖时,命令根本无法知道具体的文件名是什么,所以必然要用这些符号来代表,等make展开好了才自动替换成具体的文件名。
上面还有一句ifneq ($(MAKECMDGOALS),clean),这是make的条件判断,MAKECMDGOALS为特殊变量,代表当前要执行的目标,这里表示目标不是clean时才会include DEPS,若不这样判断,第一次执行clean会删除d文件,第二执行由于d文件不存在会先生成它们,然后再由clean删除它们。
区分不同的平台
程序支持不同的平台是常见的需求,可能需要在不同的平台里指定不同的编译条件,或者给程序加上不同的宏,以便程序作不同处理。
下面这个可以达到这个目的:
PLAT := $(shell uname) # 调用shell函数,执行shell命令,得到系统名
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
DEPS := $(SRCS:.c=.d)
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lm
####################################
# 作不同处理
ifeq ($(PLAT), Linux)
CFLAGS += -DUSE_LINUX
else
ifeq ($(PLAT), Darwin)
CFLAGS += -DUSE_MACOSX
LIBS += -ldl
endif
endif
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
%.d: %.c
@echo "make depend: $@"
@set -e; rm -f $@;
$(CC) $(CFLAGS) -MM $< | sed -E 's,($*).o[: ]*,1.o $@: ,g' > $@
.PHONY : clean
clean :
rm -f $(PROG) $(OBJS) $(DEPS)
这次用到另一个函数叫shell,它表示可以用shell命令的输出作为变量的展开结果,像在Linux下,PLAT := $(shell uname)会使PLAT得到Linux值。
有了PLAT值,就可以在下面的条件判断中,加上不同的编译标志,如在Linux上加上了DUSE_LINUX宏,在MacOS下加上了DUSE_MACOSX,并链接了dl库。
区分不同的平台的另一种方法
上面通过shell函数自动得到了目标平台,那可不可以支持make时手动指定平台呢,如果不指定才自动获取呢?当然是可以的,如下面所示:
# 默认指定为guess
PLAT := guess
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
DEPS := $(SRCS:.c=.d)
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lm
# 第一个目标,也是默认会执行的目标
default: $(PLAT)
# 自动获得平台名,然后重新调用make程序
guess:
@echo Guessing $(shell uname)
@$(MAKE) $(shell uname)
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
# 特定于平台的变量
linux Linux: CFLAGS += -DUSE_LINUX
macosx Darwin: CFLAGS += -DUSE_MACOSX
macosx Darwin: LIBS += -ldl
# 重新调用make,通过命令行指定变量
linux macosx Linux Darwin:
$(MAKE) $(PROG) CFLAGS="$(CFLAGS)" LIBS="$(LIBS)"
ifneq ($(MAKECMDGOALS),clean)
ifneq ($(MAKECMDGOALS),help)
-include $(DEPS)
endif
endif
%.d: %.c
@echo "make depend: $@"
@set -e; rm -f $@;
$(CC) $(CFLAGS) -MM $< | sed -E 's,($*).o[: ]*,1.o $@: ,g' > $@
# 输出帮助
PLATS := linux macosx Linux Darwin
help:
@echo "Do 'make PLATFORM' where PLATFORM is one of these:"
@echo " $(PLATS)"
clean :
rm -f $(PROG) $(OBJS) $(DEPS)
.PHONY : default guess clean $(PLATS)
这一份已经是比较实用的模板了,它涉及到几个知识点:
- 递归调用make:
- 如果我们简单的输出make命令,此时default目标执行,接着guess目标执行,这里自动获得平台名,并调用make并以平台名为目标,相当于执行了
make linux。 - 如果我们手动输入目标,如
make linux,则下面的具体目标被执行,调用make并以PROG为目标,但是编译条件已经有平台区别了。
- 如果我们简单的输出make命令,此时default目标执行,接着guess目标执行,这里自动获得平台名,并调用make并以平台名为目标,相当于执行了
- 特定于目标的变量,像
linux Linux: CFLAGS += -DUSE_LINUX这样的赋值,只有目标是linux Linux之一才会执行,这使我们能根据不同的平台调整不同的变量值。 - 通过命令行传递的变量会覆盖掉Makefile文件中的变量值,所以它的优先级是最高的;此外环境变量也被当作变量,但是他的优先级最低,也就是在Makefile定义相同的变量可以覆盖掉这个环境变量。
上面还提供了一个help目标,用来输出帮助。
把o文件和d文件移到不同的目录
上面的Makefile对于中小型工程已经足够了,但需求总是在慢慢变复杂。比如当程序编译完之后,代码目录里多了一堆.o文件和.d文件,总想着要把它们移到独立的目录去,这样代码目录看起来干净很多。
下面的Makefile可以达到这个目的:
PLAT := guess
# 目录
OBJDIR := objs
DEPDIR := deps
SRCS := $(wildcard *.c)
# 使用addprefix函数,给目标和依赖文件加上目录
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.c=.o))
DEPS := $(addprefix $(DEPDIR)/,$(SRCS:.c=.d))
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lm
default: $(PLAT)
guess:
@echo Guessing $(shell uname)
@$(MAKE) $(shell uname)
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
# 不再用隐式规则,显式的编译目标文件
$(OBJDIR)/%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
linux Linux: CFLAGS += -DUSE_LINUX
macosx Darwin: CFLAGS += -DUSE_MACOSX
macosx Darwin: LIBS += -ldl
linux macosx Linux Darwin:
$(MAKE) $(PROG) CFLAGS="$(CFLAGS)" LIBS="$(LIBS)"
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
# 使用order-only prerequisites生成目录
$(DEPDIR)/%.d: %.c | $(DEPDIR) $(OBJDIR)
@echo "make depend: $@"
@set -e; rm -f $@;
$(CC) $(CFLAGS) -MM $< | sed -E 's,($*).o[: ]*,$(OBJDIR)/1.o $@: ,g' > $@
$(DEPDIR):
mkdir -p $(DEPDIR)
$(OBJDIR):
mkdir -p $(OBJDIR)
PLATS := linux macosx Linux Darwin
.DEFAULT:
@echo "Do 'make PLATFORM' where PLATFORM is one of these:"
@echo " $(PLATS)"
clean :
rm -f $(PROG) $(OBJS) $(DEPS)
.PHONY : default guess clean $(PLATS)
首先使用addprefix函数,给对应的o文件和d文件加上目录前缀,比如main.c对应的o文件变成:objs/main.o。
o文件有了目录之后,不能再用隐式规则自动生成了,只能显式的写规则生成,好在有了模式规则之后,写起来并不困难:
$(OBJDIR)/%.o: %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
$(DEPDIR)/%.d: %.c | $(DEPDIR) $(OBJDIR)用到make的另一个特性:order-only prerequisites用来生成目录,在|后面的就是。它和普通依赖的区别是:当它更新时,依赖于它的目标不需要更新。而普通依赖是当依赖自己更新时,依赖于它的目标也会跟着更新。
之所以要用这个特性,是因为当目录里有文件增加删除或修改时,目录的时间戳会更改,那些在前面放入目录的d文件就会比目录“旧”。不用这个特性,会导致d文件一直重复被生成,make就会陷入死循环。可以试着把|去掉看看效果。
最终版
上面虽然可以把o和d文件移到不同目录,但在有多层目录时会失败,因为make只会生成一级目录。
我们在工程里加了这些文件:
- [subcmd]
- [subsubcmd]
- subsubcmd.c
- subsubcmd.h
- subcmd.c
- subcmd.h
这在稍微大一点的工程中是很常见的,通过不同的目录把功能划分开来便于管理。不过我个人认为一些中小型工程实在没必要去这么做,在一个平级目录里挺好的,找起来也方便。
下面这一份Makefile可以达到这种效果:
PLAT := guess
SHELL = /bin/sh
OBJDIR := objs
DEPDIR := deps
# 添加所有源代码
SRCS := $(wildcard *.c)
SRCS += $(wildcard subcmd/*.c)
SRCS += $(wildcard subcmd/subsubcmd/*.c)
OBJS := $(SRCS:%.c=$(OBJDIR)/%.o)
DEPS := $(SRCS:%.c=$(DEPDIR)/%.d)
PROG := edit
CC := gcc -std=c99
CFLAGS := -Wall -O2
LDFLAGS :=
LIBS := -lm
default: $(PLAT)
guess:
@echo Guessing $(shell uname)
@$(MAKE) $(shell uname)
$(PROG) : $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
# 如果对象目录不存在,创建该目录
$(OBJDIR)/%.o: %.c
@test -d $(@D) || mkdir -p $(@D)
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
linux Linux: CFLAGS += -DUSE_LINUX
macosx Darwin: CFLAGS += -DUSE_MACOSX
macosx Darwin: LIBS += -ldl
linux macosx Linux Darwin:
$(MAKE) $(PROG) CFLAGS="$(CFLAGS)" LIBS="$(LIBS)"
ifneq ($(MAKECMDGOALS),clean)
ifneq ($(MAKECMDGOALS),help)
-include $(DEPS)
endif
endif
# 如果依赖目录不存在,创建该目录
$(DEPDIR)/%.d: %.c
@echo "make depend: $@"
@test -d $(@D) || mkdir -p $(@D)
@set -e; rm -f $@;
$(CC) $(CFLAGS) -MM $< -MT "$*.o" | sed -E 's,($*).o[: ]*,$(OBJDIR)/1.o $@: ,g' > $@
PLATS := linux macosx Linux Darwin
help:
@echo "Do 'make PLATFORM' where PLATFORM is one of these:"
@echo " $(PLATS)"
clean :
rm -f ./$(PROG)
rm -rf ./$(OBJDIR)
rm -rf ./$(DEPDIR)
.PHONY : default guess clean $(PLATS)
SRCS 代表c源代码文件,wildcard不会递归搜索子目录,所以每个目录都要加上。
$(OBJDIR)/%.o: %.c多了一句判断并创建对应目录的命令。它又用到了新自动变量:
- $(@D) 代表目标中的路径部分
- $(@F) 代表目标中的文件名部分
$(DEPDIR)/%.d: %.c同样判断并创建对应的目录名,而命令里面有一个$*
- $* 代表模式匹配中的%部分。
其他的自动变量请参考Manual文档。
$(CC) $(CFLAGS)...这一行多了一个-MT "$*.o",这是因为对象文件在子目录里,但默认的目标依赖不会加上路径名,比如subcmd.d里是这样的:
subcmd.o deps/subcmd/subcmd.d: subcmd/subcmd.c subcmd/subcmd.h
subcmd/subsubcmd/subsubcmd.h
这会导致subcmd的依赖不成立,加上上面的标志以后,变成这样:
objs/subcmd/subcmd.o deps/subcmd/subcmd.d: subcmd/subcmd.c subcmd/subcmd.h
subcmd/subsubcmd/subsubcmd.h
这样就能正确依赖,最后make之后的目录结构是这样的:
.
├── buffer.h
├── command.c
├── command.h
├── defs.h
├── deps
│ ├── command.d
│ ├── display.d
│ ├── files.d
│ ├── insert.d
│ ├── kbd.d
│ ├── main.d
│ ├── search.d
│ ├── subcmd
│ │ ├── subcmd.d
│ │ └── subsubcmd
│ │ └── subsubcmd.d
│ └── utils.d
├── display.c
├── edit
├── files.c
├── insert.c
├── kbd.c
├── main.c
├── Makefile
├── objs
│ ├── command.o
│ ├── display.o
│ ├── files.o
│ ├── insert.o
│ ├── kbd.o
│ ├── main.o
│ ├── search.o
│ ├── subcmd
│ │ ├── subcmd.o
│ │ └── subsubcmd
│ │ └── subsubcmd.o
│ └── utils.o
├── search.c
├── subcmd
│ ├── subcmd.c
│ ├── subcmd.h
│ └── subsubcmd
│ ├── subsubcmd.c
│ └── subsubcmd.h
└── utils.c
修改某个代码文件,都能正确的只编译依赖的部分。
结束
最后的版本大概用60行规则完成了一个相对成熟的模板,大多数中小型工程其实是可以直接套用的。
Make这个工具充满了工程实用的味道,要真正熟悉它,我想大概只有三个方法
- 查看Manual手册,掌握基本的概念原理。
- 多看一些优秀的开源库,学习它们的用法,遇到不懂的特性再查看Manual手册,从而获得新知识。
- 自己多动手捣鼓捣鼓,遇到不懂的再查看Manual手册。
只有这样才能熟悉它,但是话说回来,现在这么多跨平台的构建工具,为啥还要费尽心力去学习一个老古董呢?确实也挺矛盾的:)
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)