这是命令 makepp_cookbook 可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器
程序:
您的姓名
makepp_cookbook -- 针对各种情况设置 makefile 的最佳方式
商品描述
我发现几乎没有人读过 make 工具的手册,因为坦率地说
没有人真正对制作过程本身感兴趣——我们只对结果感兴趣。
所以这本食谱被放在一起,希望人们能够得到他们需要的东西
无需翻阅手册即可快速从示例中获取。 这显示了如何输入
问题,而安装说明和绊脚石将在
经常问的问题。
建筑物 库
Do 您 真 需要 a 图书馆?
我见过许多由大量模块组成的大型程序,每个模块
它位于自己的目录中。 通常,每个目录都放入自己的库中,
然后最终的程序链接到所有的库。
在很多情况下,我认为不是使用库,而是有更好的方法。 图书馆
如果每个模块不能或不会在任何其他模块中重用,就不是真正正确的解决方案
程序,因为那样你就会得到库的所有缺点,而没有
好处。 库在以下情况下很有用:
1.当你有一堆子程序必须与几个不同的链接时
程序,并且没有程序实际使用 100% 的子程序——每个程序使用一个
不同的子集。 在这种情况下,使用静态库(a
.a 文件或存档文件)。
2. 当你有一个模块应该链接到几个不同的程序中,并且你
想要动态加载它,所以每个程序不必有一个单独的副本
图书馆。 动态库可以节省可执行文件空间,有时还可以增强
系统性能,因为所有的库只加载一个副本
使用它的不同程序。
3.当你的链接时间过长时,使用共享库进行大块
该程序可以显着加快链接速度。
使用静态库有一个主要缺点:在某些系统(例如 Linux)上,命令
在其中链接库至关重要。 链接器处理库
按照其命令行中指定的顺序。 它从中获取它认为需要的一切
每个图书馆,然后移动到下一个图书馆。 如果某个后续库引用了一个
尚未从以前的库中合并的符号,链接器不会
知道回去从以前的图书馆拿它。 因此,可能有必要
在链接器命令行上多次列出库。 (我参与了一个项目
我们不得不重复整个图书馆列表三遍。 这个项目是什么
我更喜欢下面建议的替代方法,即增量链接。)
使用动态库有几个缺点。 首先,你的程序可以稍微
如果库尚未被其他程序使用,则启动速度较慢,因为
必须找到并加载它。 其次,获得所有动态可能是一个真正的麻烦
安装在正确位置的库; 你不能只复制程序可执行文件,
您还必须确保复制其所有库。 第三,在某些系统上,它
很难调试共享库中的代码,因为调试器不支持
他们很好。
如果您的模块永远不会在任何其他程序中使用,那么几乎没有理由使用
一个库:你会得到使用库的所有缺点,但没有任何优点。
我更喜欢的技术是在可用的情况下使用增量链接。
以下是在 Linux 上执行此操作的方法:
my_module.o : $(filter_out my_module.o, $(通配符 *.o))
ld -r -o $(输出) $(输入)
这将做的是创建另一个 .o 文件名为 我的模块.o,这将包括
所有的 .o 此子目录中的文件。 链接器将解析尽可能多的
尽可能地引用,并将剩下的引用留在一个
链接的后续阶段。 在顶层,当你最终构建你的程序时,
而不是链接 libmy_module.a or libmy_module.so,您只需链接
我的模块.o. 当你链接 .o 文件中,您没有顺序依赖问题
链接器命令行。
让 制造商 数字 输出 这 图书馆 模块 ,那恭喜你, 打印车票
即使你有一个真正的库,其中一个给定的程序只需要其中的几个文件
(而不是每个模块),makepp 可能能够找出哪些模块是
需要从库中获取,并且只包含构建中的那些。 这可以节省编译
如果您正在开发库和程序,那么时间,因为您不费心
编译您正在处理的特定程序不需要的库模块。
如果您的库严格遵守所有函数或类声明的约定
一份文件 xyz 完全在编译为的源文件中实现 xyz.o (即,你
不要将实现分成 xyz1.o 和 xyz2.o),那么你可以使用
"$(infer_objects)" 函数告诉 makepp 从
图书馆。 这对于具有甚至数十个包含文件的库来说非常有效。
基本上,“$(infer_objects)”检查列表 .h 包含的文件,并看起来
对应的 .o 文件。 如果你正在快速开发一个库和一个程序
在一起,这可以节省编译时间,因为您从不费心编译
程序不使用的库。
这是我使用它的方式的示例:
my_program: $(infer_objects *.o, $(LIB1)/*.o $(LIB2)/*.o)
$(CXX) $(输入) -o $(输出) $(SYSTEM_LIBRARIES)
"$(infer_objects)" 函数返回它的第一个参数(在执行通配符之后
展开),并在其第二个参数中查看文件列表,对于
名称与任何名称相同的文件 .h 任何文件在其第一个包含的文件
争论。 如果找到任何此类文件,则将它们添加到列表中。
建筑物 a 静止 图书馆
如果您确定您确实需要一个库并且增量链接不可用或
不是你想做的,有几种方法可以做到。 首先,这是一个例子
其中明确列出了所有文件:
LIBRARY_FILES = abcde
libmine.a: $(LIBRARY_FILES).o
&rm -f $(输出)
$(AR) cr $(输出) $(输入)
ranlib $(output) # 可能没有必要,具体取决于您的操作系统。
&rm 是 makepp 的内置“rm”命令。 如果您习惯于编写 makefile,您可能会
对这个命令有点惊讶; 你可能已经习惯了这样的事情:
libmine.a: $(LIBRARY_FILES).o
$(AR) ru $@ $? # 不建议!!!!!!!
ranlib $(输出)
哪里$? (也称为“$(changed_inputs)”)是一个自动变量,表示任何文件
自上次构建库以来发生了变化,$@ 大致相同
作为“$(输出)”。
不推荐这种方法有以下几个原因:
· 假设您从当前目录中删除了一个源文件。 它仍然在
库,因为您没有从头开始重建库。 结果,任何事
与此库的链接将过时 .o 文件,这可能会搞砸你的
建立。 (当我试图删除死代码时,我曾经对此感到非常困惑
来自一个项目:我一直在删除文件但它仍然链接,所以我认为代码是
死的。 但是,当其他人从头开始重建该项目时,它没有链接任何
更多的! 问题是旧的 .o 文件仍在存档中。)
此外,取决于您对“ar”的选择和“ar”的实现(例如,如果您
使用“q”选项而不是“r”),你可以得到多个版本的
同 .o 里面的 .a 文件。 如果不同的版本定义了不同的全局变量,
链接器可能会尝试同时拉入它们。 这恐怕是件坏事。
这就是为什么我们首先删除库文件,然后从头开始创建它。 这会
比仅仅更新库中的模块花费的时间略长,但不会更长; 在
现代计算机所消耗的时间 ar 程序相比之下微不足道
C 编译器在典型构建中占用的内容,因此不值得担心
关于。
· makepp 试图保证正确构建的方法之一是
如果构建给定目标的命令行已更改,则自动重建。 但
使用 $? 变量会引起问题,因为每次更新库时,
构建命令是不同的。 (您可以使用
":build_check ignore_action"; 有关详细信息,请参阅 makepp_build_check。)
· 更新存档而不是重建存档将使 makepp 无法
将文件正确放入构建缓存(有关详细信息,请参阅 makepp_build_cache)。
有时您可能会发现列出所有文件有点麻烦,特别是如果
项目正在快速发展,文件列表也在不断变化。 它
使用通配符构建库可能更容易,如下所示:
libmine.a: $(only_targets *.o)
&rm $(输出)
$(AR) cr $(输出) $(输入)
这让所有的 .o 当前目录下的文件到库中。 通配符
匹配任何 .o 存在或可以构建的文件,因此即使文件不存在它也能工作
还存在。
“only_targets”函数用于排除 .o 没有对应的文件
源文件了。 假设你有一个名为 xyz 你曾经把你的
图书馆。 这意味着有一个 xyz.o 文件躺在身边。 现在你删除 xyz
因为它已经过时了,但你忘记删除了 xyz.o. 没有“only_targets”
功能, xyz.o 仍会被列入名单 .o 库中包含的文件。
建筑物 a 动态 图书馆
构建动态库的过程完全依赖于系统。 我会高度
推荐使用 libtool 构建动态库(见
<http://www.gnu.org/software/libtool/>),所以你不必弄清楚如何做
您的平台,以便您的 makefile 将继续工作,即使您切换到
不同的操作系统。 有关详细信息,请参阅 libtool 文档。 这是一个示例 Makefile:
LIBTOOL := 库工具
libflick.la : $(only_targets *.lo)
$(LIBTOOL) --mode=link $(CC) $(输入) -o $(输出)
%.lo:%.c
$(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(INCLUDES) -c $(输入) -o $(输出)
建筑物 on 几个 不同 机 or 网络
makefile 最烦人的问题之一是它们几乎从不工作
切换到不同的机器或不同的网络。 如果您的 makefile 必须处理
地球上所有可能的机器,那么您可能需要某种配置
脚本。 但是如果你只需要在几台不同的机器上工作,有几种方法
你可以解决这个问题:
使用 a 不同 包括 文件 in 所有 此 环境中
在每个 makefile 的开头,您可以包含如下一行:
包括 system_defs.mk
该文件 系统定义文件 每个人通常会位于不同的地方
环境。 如果您希望所有机器上的构建目录都相同,则将
系统定义文件 在构建目录上方的目录中,或者提供一个包含路径
使用“-I”命令行选项makepp。
这通常有点痛苦,但如果有大量
差异。
使用 if 声明
这是最丑陋的方法,但通常会奏效。
IFSYS i386
抄送:= gcc
否则 ifsys sun4u
抄送:=抄送
否则 ifsys hpux11
抄送 = c89
ENDIF
如果您需要做的只是找到一些程序或库,或者在不同的目录中包含文件
地方,可能有更好的方法(见下文)。
查找程序, 第一个可用, 查找文件
这些功能可以搜索系统中的各种不同目录以找到
适当的文件。 这当然不如配置脚本强大,但我发现它
有用。 例如,我执行以下操作:
CXX ;= $(find_program g++ c++ pg++ cxx CC aCC)
# 选择第一个在 PATH 中可用的 C++ 编译器。
#(顺便说一句,如果你根本不定义 CXX,这个
# 是它的定义方式。)
TCL_INCLUDE ;= -I$(dir_noslash $(findfile tcl.h, \
/usr/local/stow/tcl-8.4.5-nothread/包括\
/usr/include/tcl8.4 /usr/include/tcl \
/net/na1/tcl8.4a3/include /net/na1/tcl8.4a3/include))
# $(findfile ) 在每个指定的文件中查找 tcl.h
# 目录并返回完整路径。 这就是
# 通过剥离转换为编译选项
# 文件名(离开目录)并以 -I 为前缀。
%.o:%.cpp
$(CXX) $(CXXFLAGS) $(TCL_INCLUDE) $(输入) -o $(输出)
TCL_LIB ;= $((第一个可用
/usr/local/stow/tcl-8.4.5-nothread/lib/libtcl8.4.so
/usr/lib/libtcl8.4.so /usr/lib/libtcl.so
/net/na1/tcl8.4a3/lib/libtcl8.4.a
/net/na1/tcl8.4a3/lib/libtcl8.4.sl))
# 找到Tcl库在哪里。 这是明确的
# 在链接命令中列出:
我的程序:*.o
$(CXX) $(CXXFLAGS) $(输入) -o $(输出) $(TCL_LIB)
采取 优点 of Perl 的 配置 信息
如果您需要一些额外的信息,上述技术可能还不够
您的系统,例如 long double 是否存在,或字节顺序是什么。 然而,
perl 已经计算了这些东西,所以你可以使用它的答案。
Perl 的自动配置脚本通过以下方式提供其所有配置信息
%Config 哈希。 在 makepp 中没有直接访问 Perl 哈希的语法,但是您可以
进入 Perl 并设置可从 makepp 直接访问的标量变量:
perl_开始
# 从配置哈希中提取值。
使用配置;
$CC = $Config{'cc'}; # perl 使用的 C 编译器;
$byteorder_flags = "-DBYTEORDER=$Config{'byteorder'}";
$longdouble_defined = $Config{'d_longdbl'} eq 'define';
$CFLAGS_for_shared_libs = $Config{'cccdlflags'};
$LDFLAGS_for_shared_libs = $Config{'ccdlflags'};
perl_结束
此外,一旦您完成了“使用配置”,您就可以使用“$(perl)”语句,例如
这个:
SHARED_LIB_EXTENSION := $(perl $Config{'dlext'})
键入“perldoc Config”以查看通过 %Config 散列可用的信息。
Perl 的配置是获取有关整数类型、字节等信息的好地方
订单,以及其他通常需要单独的配置脚本来定位的东西。 一些
它与文件系统中存在的事物相关的信息可能不是
有效的。 例如, $Config{'cc'} 指的是构建 perl 的 C 编译器,
这可能与您要使用的 C 编译器不同。 事实上,它甚至可能不存在
在您的系统上,因为您可能通过二进制包安装了 Perl。
Tips 运用 通配符
匹配 所有 档 除 a 一定 子集
Makepp的通配符目前没有办法匹配所有文件 除 一定
设置,但您可以使用功能组合来完成。
例如,假设您对库中的每个模块都有一个测试程序,但您没有
想要在库中包含测试程序。 如果所有的测试程序都以
test,那么你可以像这样排除它们:
libproduct.a: $(filter_out test*, $(通配符 *.o))
"$(filter )" 和 "$(filter_out )" 函数是一组非常强大的过滤器
各种集合交差运算。 例如,
SUBDIRS ;= $(filter_out *test* *$(ARCH)*, $(shell find .-type d -print))
# 返回所有没有的子目录
# "test" 或 $(ARCH) 在其中。
$(过滤器 $(patsubst test_dir/test_%.o, %.o, $(wildcard test_dir/*.o)), \
$(通配符*.o))
# 返回当前 .o 文件的列表
# 对应的目录
# test_*.o 文件在 test_dir 子目录中。
$(filter_out $(patsubst man/man3/%.3, %.o, $(通配符 man/man3/*.3)), \
$(通配符*.o))
# 返回当前 .o 文件的列表
# 没有手册页的目录
# 在 man/man3 子目录中使用相同的文件名。
运用 此 "$(only_targets )" 功能 至 消除 陈旧 .o 档
假设您正在使用如下构建命令构建程序或库:
程序:*.o
$(CC) $(输入) -o $(输出)
假设您现在删除了一个源文件。 如果忘记删除相应的 .o 文件,
即使无法再构建它,它仍将被链接。 在里面
未来,makepp 可能会自动识别这种情况并将其排除在外
通配符列表,但目前,您必须告诉它手动排除它:
程序:$(only_targets *.o)
$(CC) $(输入) -o $(输出)
Makepp 不知道任何构建陈旧的方法 .o 文件不再是因为它的源文件是
消失了,因此“$(only_targets)”函数会将其从依赖项列表中排除。
Tips 多 目录
编写 makepp 的主要原因之一是为了简化多个
目录。 Makepp 能够组合来自多个 makefile 的构建命令,因此它可以
正确处理一个 makefile 中的规则,该规则依赖于由
不同的生成文件。
什么是 至 do in 地方 of 递归 使
Makepp 支持递归 make 以实现向后兼容性,但强烈推荐
你 不能 用它。 如果你不知道它是什么,很好。
有关为什么您不想这样做的详细信息,请参阅 makepp 中的“更好的分层构建系统”
使用递归生成,或者在网上搜索“递归生成被认为有害”。
不是在每个 makefile 中进行递归 make 来创建“all”目标,而是
通常更容易让 makepp 确定实际需要构建哪些目标。
此外,如果你把你所有的 .o 和库文件在同一目录中
makefiles,然后 makepp 会自动找出哪些 makefiles 也是需要的——
唯一需要的是让你的顶级制作列出所需的文件
用于最后的连接步骤。 请参阅下面的示例。
一个 生成文件 每 目录: 含蓄 装载
处理多个目录最常见的方法是在每个目录中放置一个makefile
它描述了如何在该目录中或从该目录构建所有内容。 如果你把 .o 在文件
与源文件相同的目录,然后隐式加载(参见“隐式加载”
makepp_build_algorithm) 会自动找到所有的 makefile。 如果你把你的 .o
不同目录中的文件(例如,在依赖于体系结构的子目录中),然后您
可能必须使用“load_makefile”语句加载所有相关的makefile。
这是使用隐式加载的目录层次结构的示例顶级生成文件
构建一个由许多共享库组成的程序(但请参阅“你真的需要一个
库?”在 makepp_cookbook 中,因为从一堆共享库中制作程序
不一定是个好主意):
# 顶级生成文件:
program : main.o **/*.la # 链接所有子目录中的共享库。
$(LIBTOOL) --mode=link $(CC) $(CFLAGS) $(输入) -o $(输出) $(LIBS)
这几乎是您在顶级 makefile 中所需的全部内容。 在每个子目录中,您
可能会做这样的事情:
# 每个子目录下的Makefile:
include standard_defs.mk # 搜索 ., .., ../ ..等,直到它
# 找到指定的包含文件。
# 在这里覆盖一些变量定义
SPECIAL_FLAGS := -do_something_ different
如果构建目标的命令,每个 makefile 可能几乎相同
非常相似。
最后,您将以下内容放入 标准定义.mk 文件(可能应该
位于顶级目录中):
# 所有目录的公共变量设置和构建规则。
CFLAGS := -g -O2
INCLUDE_DIR := $(find_upwards 包括)
# 搜索 ., .., ../ ..等文件或
# 目录称为包含,所以如果你把
# 你所有的包含文件都在那里,这将
# 找到他们。
包括 := -I$(INCLUDE_DIR)
%.lo:%.c
$(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(INCLUDES) -c $(输入) -o $(输出)
lib$(relative_to ., ..).la: $(only_targets *.lo)
$(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $(输出) $(输入)
# $(relative_to ., ..) 返回当前的名称
# 相对于上层的子目录
# 子目录。 所以如果这个makefile是xyz/Makefile,
# 此规则将构建 xyz/libxyz.la。
# 将公共包含文件发布到顶级包含目录:
$(INCLUDE_DIR)/public_%.h:public_%.h
:build_check 符号
&ln -fr $(输入) $(输出)
一个 生成文件 每 目录: 明确的 装载
如果你想把你所有的 .o 文件到依赖于体系结构的子目录,然后
上面的例子应该修改成这样:
# 顶级生成文件:
MAKEFILES := $(wildcard **/Makeppfile) # 所有子目录的列表
# 从中获取生成文件。
load_makefile $(MAKEFILES) # 全部加载。
include standard_defs.mk # 获取 main.o 的编译命令。
程序:$(ARCH)/main.o */**/$(ARCH)/*.la
$(LIBTOOL) --mode=link $(CC) $(CFLAGS) $(输入) -o $(输出) $(LIBS)
# */**/$(ARCH) 排除子目录
# $(ARCH),我们不想构建的地方
# 共享库。
每个 makefile 都与以前完全相同:
# 每个子目录下的Makefile:
包括standard_defs.mk
# ... 变量覆盖在这里
以及最后, 标准定义.mk 将包含如下内容:
# 所有目录的公共变量设置和构建规则。
ARCH ;= $(shell uname -s)-$(shell uname -m)-$(shell uname -r)
# 有时人们只使用 $(shell uname -m),但是
# 这对于 FreeBSD 和 Linux 来说是一样的
# 一个 x86。 -r 在 Linux 上不是很有用,
# 但对其他操作系统很重要:二进制文件
# SunOS 5.8 通常不会在 SunOS 5.7 上运行。
&mkdir -p $(ARCH) # 确保输出目录存在。
CFLAGS := -g -O2
INCLUDE_DIR := $(find_upwards 包括)
# 搜索 ., .., ../ ..等文件或
# 目录称为包含,所以如果你把
# 你所有的包含文件都在那里,这将
# 找到他们。
包括 := -I$(INCLUDE_DIR)
$(ARCH)/%.lo : %.c
$(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(INCLUDES) -c $(输入) -o $(输出)
$(拱门)/ lib目录$(relative_to ., ..).la: $(only_targets *.lo)
$(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $(输出) $(输入)
# $(relative_to ., ..) 返回当前的名称
# 相对于上层的子目录
# 子目录。 所以如果这个makefile是xyz/Makefile,
# 此规则将构建 xyz/$(ARCH)/libxyz.la。
# 将公共包含文件复制到顶级包含目录中:
$(INCLUDE_DIR)/public_%.h:public_%.h
&cp $(输入) $(输出)
自动 制造 此 生成文件
如果你的 makefile 都非常相似(如上例所示),你可以告诉 Makepp
如果它们不存在,则自动构建它们。 只需将以下内容添加到您的顶级
生成文件:
子目录:= $(过滤器输出不需要的目录1 不需要的目录2,$(通配符*/**))
$(foreach)/Makeppfile:: foreach $(SUBDIRS)
&echo "include standard_defs.mk" -o $(output)
&echo "_include additional_defs.mk" -o >>$(output)
# 如果文件 additional_defs.mk 存在,则
# 它将被包含,但如果它不存在,
# _include 语句将被忽略。
现在生成文件本身将被自动构建。
一个 生成文件 仅由 at 此 最佳 水平
如果你所有的 makefile 都是相同的,你可能会问:为什么我要在每个地方都有一个 makefile
等级? 为什么不把这些都放到顶级的 makefile 中呢?
是的,这是可以做到的。 主要缺点是变得更难指定
每个子目录的不同构建选项。 第二个缺点是你的
makefile 可能会变得有点难以阅读。
这是一个这样做的例子:
# 目录层次结构的顶级生成文件。 构建程序
# 以一组共享库为例。 (见上面的警告
# 为什么你可能想要使用增量链接或其他一些
# 方法而不是共享库。)
makepp_percent_subdirs := 1 # 允许 % 匹配多个目录。
SUBDIRS := $(filter_out *CVS* other-unwanted_dirs $(通配符 **))
CFLAGS := -g -O2
包括:= -Iincludes
%.lo:%.c
$(LIBTOOL) --mode=compile $(CC) $(INCLUDES) $(CFLAGS) -c $(输入) -o $(输出)
$(foreach)/ lib目录$(notdir $(foreach)).la: $(foreach)/*.lo : foreach $(SUBDIRS)
$(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $(输出) $(输入)
# 生成所有库的规则。
程序:main.o **/*.la
$(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $(输出) $(输入)
包括/$(notdir $(foreach)) : $(foreach) : foreach **/public_*.h
&cp $(输入) $(输出)
# 公开复制的示例规则
# 可访问的 .h 文件到正确的位置。
A 清洁 目标
传统的 makefile 包含一个干净的目标,它允许删除所有
建成。 不应该使用 makepp 执行此操作的三个原因:
1. Makepp 竭尽全力确保正确构建。 所以绝望的“我不
知道出了什么问题”,让您想从头开始已成为过去。
2. 人们有时会试图通过同时做两件相互矛盾的事情来节省时间:
“把一切都弄干净”。 这可能会混淆 makepp 的智能通配符系统,因为它会
在做任何事情之前,首先要了解事实。 然后是干净的动作,它确实
不要告诉 makepp 它做了什么(实际上它不能,因为它取消了某些东西——
与构建工具的用途相反)。 然后是“所有”,但最新的文件,
哪里有,都莫名其妙的消失了。
3. 有“makeppclean”命令,它做同样的事情,而且效率更高。
尽管如此,我们保留了这个历史部分,因为它确实告诉了你一些关于
makepp 的工作方式:一个名为“clean”的虚假目标只是一组命令的名称
删除由 make 过程产生的所有文件。 通常一个干净的目标看起来
这样的事情:
$(虚假清洁):
&rm -fm $(通配符 *.o .makepp_log)
# -m 和 .makepp_log 清除所有 makepp 的垃圾。
除了明确列出要删除的文件之外,您还可以告诉 makepp
删除它知道如何构建的所有内容,如下所示:
$(虚假清洁):
&rm -fm .makepp_log $(only_targets *)
这样做的好处是,如果您的任何源文件可以从其他文件构建,
它们也会被删除; 另一方面,陈旧 .o 文件(以前的文件
可构建但其源文件已被删除)将不会被删除。
如果您的构建涉及多个不同目录中的 makefile,则您的 top-
级别生成文件可以在不同的位置引用“干净”目标(或任何其他虚假目标)
生成文件:
# 顶层makefile
子目录:=子1子2
# 在这里建立规则
# 构建后清理:
$(虚假清洁):$(SUBDIRS)/清洁
&rm -fm .makepp_log $(only_targets *)
或者,您可以仅将“干净”目标放在顶级 makefile 中,并使用它
处理所有目录,如下所示:
$(虚假清洁):
&rm -fm $(only_targets **/*)
运用 Qt的 莫克 预处理器
此示例显示了使用诺基亚 Qt GUI 库的实用程序的生成文件(请参阅
<http://qt.nokia.com>)。 唯一有点不寻常的是你
必须在大多数包含小部件定义的“.h”文件上运行一个名为“moc”的预处理器,
但您不想在任何不使用“Q_OBJECT”宏的“.h”文件上运行“moc”。
自动 确定 这 档 需要 莫克 档
当然,您可以只列出需要在其上运行“moc”的所有“.h”文件。
但是,如果您正在快速开发新的小部件,则可能会给您带来麻烦
不断更新 makefile 中的列表。 您可以绕过列出 moc 的需要
模块明确地与这样的事情:
MOC := $(QTDIR)/bin/moc
模块 := 你的程序中碰巧有的任何模块
MOC_MODULES := $(patsubst %.h, moc_%, $(&grep -l /Q_OBJECT/ *.h))
# 扫描 Q_OBJECT 宏的所有 .h 文件。
my_program: $(MODULES).o $(MOC_MODULES).o
$(CXX) $(输入) -o $(输出)
moc_%.cxx: %.h # 从 .h 文件制作 moc 文件。
$(MOC) $(输入) -o $(输出)
%.o: %.cxx
$(CXX) $(CXXFLAGS) -c $(输入) -o $(输出)
这种方法会扫描您的每个 .h 每次运行 makepp 时的文件,寻找
“Q_OBJECT”宏。 这听起来很贵,但可能根本用不了多久。 (这 .h
无论如何,编译过程都必须从磁盘加载文件,因此它们将
被缓存。)
的#include 此 .moc 文件
另一种方法是“#include”小部件中“moc”预处理器的输出
实施文件。 这意味着你必须记住写“#include”,但它有
优点是需要编译的模块更少,因此编译速度更快。
(对于大多数 C++ 编译,大部分时间都花在读取头文件上,而
预处理器的输出需要包含与小部件几乎一样多的文件
无论如何。)例如:
// 我的_widget.h
类 MyWidget :公共 QWidget {
Q_OBJECT
// ...
}
// my_widget.cpp
#include "my_widget.h"
#include "my_widget.moc" // my_widget.moc 是来自
// moc 预处理器。
// 其他实现的东西在这里。
MyWidget::MyWidget(QWidget * parent, const char * name) :
QWidget(父母,姓名)
{
// ...
}
现在你需要在你的 makefile 中有一个规则来制作所有的“.moc”文件,像这样:
MOC := $(QTDIR)/bin/moc
# 生成 .moc 文件的规则:
%.moc: %.h
$(MOC) $(输入) -o $(输出)
Makepp 足够聪明,它意识到如果没有,它需要制作“my_widget.moc”
已经存在,或者它已经过时。
第二种方法是我通常使用的方法,因为它可以加快编译速度。
替换 弃用 使 成语
制定目标
有时人们在他们的 makefile 中有规则取决于他们正在构建的目标,
使用特殊变量“MAKECMDGOALS”。 例如,人们有时会看到诸如
这个:
ifneq ($(过滤生产, $(MAKECMDGOALS)),)
CFLAGS := -O2
其他
CFLAGS := -g
ENDIF
这将与 makepp 一起工作。 但是,我建议不要将“MAKECMDGOALS”用于此类
案例(GNU 制作手册也是如此)。 你最好把你的优化和
调试编译 .o 不同目录中的文件,或给它们不同的前缀或
后缀,或使用存储库,将它们分开。
可能您真正想要引用“MAKECMDGOALS”的唯一时间是
加载 makefile 需要很长时间,而您的“干净”目标不需要它
(但你不需要一个干净的目标)。 例如,
ifneq ($(MAKECMDGOALS),干净)
load_makefile $(通配符**/Makeppfile)
其他
no_implicit_load 。 # 防止自动加载任何其他 makefile。
ENDIF
$(虚假清洁):
&rm -f $(通配符**/*.o)
递归 使 至 建立 in 不同 目录
请参阅 makepp_cookbook 中的“多个目录的提示”。
递归 使 至 更改 折扣值 of a 变量
一些 makefile 使用不同的变量值重新调用自己,例如,调试
目标在以下 makefile 片段中
.PHONY:所有调试
优化:
$(MAKE) 程序 CFLAGS=-O2
调试:
$(MAKE) 程序 CFLAGS=-g
节目:奥博
$(CC) $(CFLAGS) $^ -o $@
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
如果用户键入“make debug”,它会在启用调试的默认模式下构建程序
而不是优化。
更好的方法是构建两个不同的程序,使用两组不同的
目标文件,像这样:
CFLAGS := -O2
调试标志 := -g
模块:= ab
程序:$(MODULES).o
$(CC) $(CFLAGS) $(输入) -o $(输出)
调试/程序:调试/$(MODULES).o
$(CC) $(DEBUG_FLAGS) $(输入) -o $(输出)
%.o : %.c
$(CC) $(CFLAGS) -c $(输入) -o $(输出)
调试/%.o : %.c
$(CC) $(DEBUG_FLAGS) -c $(输入) -o $(输出)
$(虚假调试):调试/程序
这样做的好处是 (a) 当你
从调试切换到优化再返回; (二)
使用存储库可以更简洁地编写上述内容。 下列
makefile 完全等效:
存储库调试=。 # 使调试子目录看起来像
# 当前子目录。
load_makefile 调试 CFLAGS=-g
# 在调试子目录中调用时覆盖 CFLAGS
CFLAGS := -O2 # 在此子目录中调用时 CFLAGS 的值
节目:奥博
$(CC) $(CFLAGS) $^ -o $@
%.o : %.c
$(CC) $(CFLAGS) -c $< -o $@
$(虚假调试):调试/程序
# 如果用户输入“makepp debug”,则构建
# 调试/程序而不是程序。
其他 秘诀
创新中心 do I 建立 一种 部分 不同 只是 一次?
Makepp 使这很难做到,因为结果与规则不一致。
但是在某些情况下您可能需要这样做,例如只编译一个模块
大量调试信息。 您可以通过首先构建
单独依赖,然后将其从链接阶段排除:
makepp DEBUG=3 buggy.o # 使用其他选项构建它。
makepp --dont-build=buggy.o buggy # 使用它,尽管“错误”的构建选项。
创新中心 do I 使 肯定 my 产量 目录 存在?
您可以指定一个规则来构建输出目录,然后确保每个文件
进入输出目录取决于它。 但通常更容易做类似的事情
这个:
#经典方式
虚拟 := $(shell 测试 -d $(OUTPUT_DIRECTORY) || mkdir -p $(OUTPUT_DIRECTORY))
# 这通常比让所有文件依赖更容易
# $(OUTPUT_DIRECTORY) 并制定规则。
# 注意你必须使用 := 而不是 = 来强制它
# 立即执行。
# 另一种方法:使用 Perl 代码,OUTPUT_DIRECTORY 局部变量
perl_开始
-d $OUTPUT_DIRECTORY 或 mkdir $OUTPUT_DIRECTORY;
perl_结束
# 现代方式,对现有目录不做任何事情
&mkdir -p $(OUTPUT_DIRECTORY)
这些语句之一应该靠近你的 makefile 的顶部,所以它们被执行
在任何可能需要目录之前。
创新中心 do I 力 a 命令 至 执行 on 每周 建造?
最简单的方法是根本不使用规则机制,而只是执行它,例如
这个:
虚拟 := $(shell 日期 > last_build_timestamp)
或者把它放在一个 perl 块中,像这样:
perl_开始
system("要执行的命令");
perl_结束
这种方法的缺点是即使不相关的目标也会被执行
正在运行。
第二种方法是将文件声明为虚假目标,即使它是真实文件。
这将强制 makepp 每次都重新执行命令来构建它,但前提是它
出现在某个规则的依赖列表中。
创新中心 do I 缩短 此 显示 建立 命令?
编译命令通常有很多选项,以至于显示在
屏幕不可读。 您可以通过抑制显示来更改显示的内容
整个命令,然后显式打印出命令的有趣部分。 它是
使用“$(filter_out)”可以轻松地仅打印出命令的相关部分,例如
这个:
ALL_CFLAGS = $(CFLAGS) $(INCLUDES) $(ADDL_CXX_FLAGS) $(DEBUG_FLAGS)
%.o : %.c
@&echo $(notdir $(CC)) ... \
$(filter_out -I* $(ADDL_CXX_FLAGS), $(ALL_CFLAGS)) \
-c $(输入)
@$(CC) $(ALL_CFLAGS) -c $(输入) -o $(输出)
(命令前面的“@”禁止打印命令。)
这将允许您查看大多数有趣的选项,但不会显示所有
包括目录(其中通常有很多!)。 如果您感兴趣的部分
in 在您的命令中是连续的,您还可以使用“打印”功能(它添加了一个
换行,所以你不想要其中的几个):
目标:
@... $(打印有趣的部分)...
创新中心 do I 兑换 a 文件 成 依赖?
对于一些晦涩的文件格式,实现扫描仪是不值得的。 在一个项目中
我们有 xml 文件,比如说 foobar.xml 其中包含的依赖项 foobar.out:
一种
乙
C
我们决定坚持这个简单的布局,所以我们不需要解析xml。 随着
内置 &sed,这是我们对三种类型的三个简单替换所做的
行数:
%.d: %.xml
&sed 的! !$(stem).out: \\! || ! (.+) !$$1 \\! || ! !# 空的!' \
$(输入)-o $(输出)
包括 foobar.d
尝试包含此内容,首先生成“foobar.d”:
foobar.out: \
一种 \
乙\
C \
# 空的
空(只是一个评论或真的是空的)行避免了不必担心尾随
反斜杠。 产生多行列表的另一种方法是:
%.d: %.xml
&sed 的! !$(stem).out: \$$((! || s! !))! || s!<.+?>!!g' \
$(输入)-o $(输出)
包括 foobar.d
这产生了一个等效的:
foobar.out: $((
a
b
c
))
如果您有更复杂的重写要做,请在 makefile 中或在
您包含的模块。 例如,取消定义 $_ 将跳过输入行:
子过滤器{
返回 undef $_ 如果 /
我的 $stem = f_stem;
! !$stem.out: \$((! || s! !))! || s!<.+?>!!g;
}
%.d: %.xml
&sed 的! !$(stem).out: \$$((! || s! !))! || s!<.+?>!!g' \
$(输入)-o $(输出)
包括 foobar.d
使用 onworks.net 服务在线使用 makepp_cookbook