英语法语西班牙语

Ad


OnWorks 网站图标

makepp_cookbook - 云端在线

通过 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器在 OnWorks 免费托管服务提供商中运行 makepp_cookbook

这是命令 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.oxyz2.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 文件,比如说 foob​​ar.xml 其中包含的依赖项 foob​​ar.out:


一种

C


我们决定坚持这个简单的布局,所以我们不需要解析xml。 随着
内置 &sed,这是我们对三种类型的三个简单替换所做的
行数:

%.d: %.xml
&sed 的! !$(stem).out: \\! || ! (.+) !$$1 \\! || ! !# 空的!' \
$(输入)-o $(输出)

包括 foobar.d

尝试包含此内容,首先生成“foobar.d”:

foob​​ar.out: \
一种 \
乙\
C \
# 空的

空(只是一个评论或真的是空的)行避免了不必担心尾随
反斜杠。 产生多行列表的另一种方法是:

%.d: %.xml
&sed 的! !$(stem).out: \$$((! || s! !))! || s!<.+?>!!g' \
$(输入)-o $(输出)

包括 foobar.d

这产生了一个等效的:

foob​​ar.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


免费服务器和工作站

下载 Windows 和 Linux 应用程序

Linux 命令

Ad