Jon*_*son 62 build-process makefile
几年前,我阅读了Recursive Make Considered Harmful论文,并在我自己的构建过程中实现了这个想法.最近,我阅读了另一篇关于如何实现非递归的文章make
.所以我有一些数据点,非递归make
适用于至少几个项目.
但我很好奇别人的经历.你尝试过非递归make
吗?它让事情变得更好还是更糟?值得的时间吗?
Vil*_*ari 53
我们在我工作的公司中使用非递归GNU Make系统.它基于米勒的论文,特别是你给出的"实现非递归制作"链接.我们设法将Bergen的代码改进为一个系统,在子系统makefile中根本没有锅炉板.总的来说,它工作正常,并且比我们以前的系统要好得多(使用GNU Automake完成的递归操作).
我们支持所有"主要"操作系统(商业):AIX,HP-UX,Linux,OS X,Solaris,Windows,甚至是AS/400大型机.我们为所有这些系统编译相同的代码,平台相关的部分被隔离到库中.
我们的树中有超过200万行C代码,大约有2000个子目录和20000个文件.我们认真考虑使用SCons,但却无法让它足够快地运行.在较慢的系统上,Python只需要在SCons文件中解析几十秒,其中GNU Make在大约一秒钟内做同样的事情.这是大约三年前的事情,所以事情可能已经改变了.请注意,我们通常将源代码保留在NFS/CIFS共享上,并在多个平台上构建相同的代码.这意味着构建工具扫描源树以进行更改的速度更慢.
我们的非递归GNU Make系统并非没有问题.以下是您可能遇到的一些最大障碍:
我们旧的递归makefile系统的主要优势是:
关于上面列表中的最后一项.我们最终在构建系统中实现了一种宏扩展工具.子目录makefile列出了程序,子目录,库以及PROGRAMS,SUBDIRS,LIBS等变量中的其他常见内容.然后将其中的每一个扩展为"真正的"GNU Make规则.这使我们可以避免很多命名空间问题.例如,在我们的系统中,拥有多个具有相同名称的源文件是完美的,没有问题.
无论如何,这最终成了很多工作.如果您可以为您的代码获得SCons或类似工作,我建议您首先查看.
Dan*_*ing 30
在阅读了RMCH论文之后,我开始着手为我当时正在处理的小项目编写一个正确的非递归Makefile.在我完成之后,我意识到应该可以创建一个通用的Makefile"框架",它可以非常简单和简洁地告诉你想要构建什么样的最终目标,它们是什么样的目标(例如库或可执行文件) )以及应该编译哪些源文件来制作它们.
经过几次迭代后,我最终创建了这样一个:大约150行GNU Make语法的单个样板文件,从不需要任何修改 - 它只适用于我喜欢使用的任何类型的项目,并且足够灵活,可以构建具有足够粒度的不同类型的多个目标,以指定每个源文件的精确编译标志(如果需要)和每个可执行文件的精确链接器标志.对于每个项目,我需要做的就是为它提供小的,单独的Makefile,其中包含与此类似的位:
TARGET := foo
TGT_LDLIBS := -lbar
SOURCES := foo.c baz.cpp
SRC_CFLAGS := -std=c99
SRC_CXXFLAGS := -fstrict-aliasing
SRC_INCDIRS := inc /usr/local/include/bar
Run Code Online (Sandbox Code Playgroud)
如上所述的项目Makefile将完全符合您的期望:构建名为"foo"的可执行文件,编译foo.c(使用CFLAGS = -std = c99)和baz.cpp(使用CXXFLAGS = -fstrict-aliasing)并在#include
搜索路径中添加"./inc"和"/ usr/local/include/bar" ,最终链接包括"libbar"库.它还会注意到有一个C++源文件,并且知道使用C++链接器而不是C链接器.该框架允许我指定比这个简单示例中显示的更多的内容.
样板文件Makefile执行构建指定目标所需的所有规则构建和自动依赖关系生成.所有构建生成的文件都放在一个单独的输出目录层次结构中,因此它们不与源文件混合(这是在不使用VPATH的情况下完成的,因此使多个源文件具有相同名称没有问题).
我现在(重新)在至少二十几个我工作过的不同项目中使用了相同的Makefile.我最喜欢这个系统的一些东西(除了为任何新项目创建一个合适的 Makefile 是多么容易)是:
最后我要提一下,由于递归制作中固有的问题,我认为不可能将其拉下来.我可能注定要一遍又一遍地重写有缺陷的makefile,试图创建一个实际工作正常的makefile.
Pan*_*rat 18
让我强调米勒论文的一个论点:当你开始手动解决不同模块之间的依赖关系并且很难确保构建顺序时,你实际上是重新实现构建系统首先要解决的逻辑.构建可靠的递归make构建系统非常困难.现实生活中的项目有许多相互依赖的部分,其构建顺序非常重要,因此,这个任务应留给构建系统.但是,如果它具有系统的全局知识,它只能解决该问题.
此外,在多个处理器/内核上同时构建时,递归make构建系统容易崩溃.虽然这些构建系统似乎可以在单个处理器上可靠地工作,但是在您开始并行构建项目之前,许多缺少的依赖项都未被检测到.我使用了一个递归的make build系统,它可以处理多达四个处理器,但突然在一台带有两个四核的机器上崩溃了.然后我面临另一个问题:这些并发问题几乎无法调试,我最终绘制了整个系统的流程图,以找出问题所在.
回到你的问题,我发现很难想出为什么人们想要使用递归make.非递归GNU Make构建系统的运行时性能很难被击败,相反,许多递归make系统都存在严重的性能问题(弱并行构建支持也是问题的一部分).有一篇论文,我评估了一个特定的(递归)Make构建系统,并将其与SCons端口进行了比较.性能结果不具代表性,因为构建系统非常不标准,但在这种特殊情况下,SCons端口实际上更快.
结论:如果你真的想使用Make来控制软件构建,那就选择非递归Make,因为从长远来看,它会让你的生活变得更加容易.就个人而言,我宁愿使用SCons来实现可用性(或者Rake - 基本上任何构建系统都使用现代脚本语言并且具有隐式依赖支持).
Jes*_*erE 10
我对我以前的工作做了一个半心半意的尝试,使构建系统(基于GNU make)完全不是递归的,但我遇到了许多问题:
GNU make的一个简化非递归使用的特性是特定于目标的变量值:
foo: FOO=banana
bar: FOO=orange
Run Code Online (Sandbox Code Playgroud)
这意味着当构建目标"foo"时,$(FOO)将扩展为"banana",但是当构建目标"bar"时,$(FOO)将扩展为"orange".
这样做的一个限制是不可能具有特定于目标的VPATH定义,即无法为每个目标单独唯一地定义VPATH.在我们的例子中,这是必要的,以便找到正确的源文件.
为支持非递归而需要的GNU make的主要缺失特征是缺少名称空间.特定于目标的变量可以以有限的方式用于"模拟"命名空间,但您真正需要的是能够使用本地范围在子目录中包含Makefile.
编辑:在这种情况下,GNU make的另一个非常有用(并且通常未被充分利用)的特性是宏扩展功能(例如,参见eval函数).当您有多个具有相似规则/目标的目标但在使用常规GNU make语法无法表达的方式上有所不同时,这非常有用.
我同意所引用文章中的陈述,但我花了很长时间才找到一个好的模板来完成所有这些并且仍然易于使用.
我正在研究一个小型研究项目,我正在尝试持续集成; 在PC上自动进行单元测试,然后在(嵌入式)目标上运行系统测试.这在制作中是非常重要的,我已经寻找了一个很好的解决方案.发现make仍然是便携式多平台构建的一个很好的选择我终于在http://code.google.com/p/nonrec-make找到了一个很好的起点
这真是一种解脱.现在我的makefile是
我当然也会将它用于下一个(大)项目(假设C/C++)
我知道至少有一个大型项目(ROOT),它使用[powerpoint link]宣传 Recursive Make Considered Harmful中描述的机制.该框架超过一百万行代码并且编写得非常巧妙.
而且,当然,我使用的所有大型项目都使用递归make,编译速度很慢.::叹::
小智 7
我为一个中型C++项目开发了一个非递归make系统,该系统适用于类Unix系统(包括mac).此项目中的代码全部位于以src /目录为根的目录树中.我想编写一个非递归系统,在该系统中可以从顶级src /目录的任何子目录中键入"make all",以便编译以工作目录为根的目录树中的所有源文件,如在递归make系统中.因为我的解决方案似乎与我见过的其他解决方案略有不同,所以我想在这里描述它,看看我是否得到任何反应.
我的解决方案的主要内容如下:
1)src/tree中的每个目录都有一个名为sources.mk的文件.每个这样的文件都定义了一个makefile变量,该变量列出了以该目录为根的树中的所有源文件.此变量的名称的格式为[directory] _SRCS,其中[directory]表示从顶级src /目录到该目录的路径的规范化形式,反斜杠替换为下划线.例如,文件src/util/param/sources.mk定义了一个名为util_param_SRCS的变量,该变量包含src/util/param及其子目录中所有源文件的列表(如果有).每个sources.mk文件还定义了一个名为[directory] _OBJS的变量,该变量包含相应目标文件*.o目标的列表.在包含子目录的每个目录中,sources.mk包含每个子目录中的sources.mk文件,并连接[子目录] _SRCS变量以创建自己的[目录] _SRCS变量.
2)所有路径都在sources.mk文件中表示为绝对路径,其中src /目录由变量$(SRC_DIR)表示.例如,在文件src/util/param/sources.mk中,文件src/util/param/Componenent.cpp将列为$(SRC_DIR)/util/param/Component.cpp.$(SRC_DIR)的值未在任何sources.mk文件中设置.
3)每个目录还包含一个Makefile.每个Makefile都包含一个全局配置文件,它将变量$(SRC_DIR)的值设置为根src /目录的绝对路径.我选择使用绝对路径的符号形式,因为这似乎是在多个目录中创建多个makefile的最简单方法,它们以相同的方式解释依赖关系和目标的路径,同时仍然允许移动整个源树,如果需要的话,通过在一个文件中更改$(SRC_DIR)的值.此值由一个简单的脚本自动设置,当从git存储库下载或克隆包时,或者在移动整个源树时,指示用户运行该脚本.
4)每个目录中的makefile包含该目录的sources.mk文件.每个此类Makefile的"all"目标将该目录的[directory] _OBJS文件列为依赖项,因此需要编译该目录及其子目录中的所有源文件.
5)编译*.cpp文件的规则为每个源文件创建一个依赖文件,后缀为*.d,作为编译的副作用,如下所述:http://mad-scientist.net/make/ autodep.html.我选择使用gcc编译器生成依赖项,使用-M选项.即使使用其他编译器来编译源文件,我也使用gcc进行依赖生成,因为gcc几乎总是在类似unix的系统上可用,并且因为这有助于标准化构建系统的这一部分.可以使用不同的编译器来实际编译源文件.
6)对_OBJS和_SRCS变量中的所有文件使用绝对路径要求我编写一个脚本来编辑gcc生成的依赖文件,该文件创建具有相对路径的文件.我为此目的编写了一个python脚本,但另一个人可能已经使用了sed.生成的依赖项文件中的依赖项路径是文字绝对路径.在这种情况下这很好,因为依赖文件(与sources.mk文件不同)是在本地生成的,而不是作为包的一部分进行分发.
7)每个控制器中的Makefile包含来自同一目录的sources.mk文件,并包含一行"-include $([directory] _OBJS:.o = .d)",它试图包含每个源文件的依赖文件在目录及其子目录中,如上面给出的URL中所述.
我看到的其他允许从任何目录调用"make all"的方案之间的主要区别在于使用绝对路径来允许在从不同目录调用Make时一致地解释相同的路径.只要使用变量表示这些路径来表示顶级源目录,这不会阻止移动源树,并且比实现相同目标的一些替代方法更简单.
目前,我的该项目系统始终进行"就地"构建:通过编译每个源文件生成的目标文件与源文件放在同一目录中.通过更改编辑gcc依赖项文件的脚本来直接启用场外构建,以便用表示表达式中的构建目录的变量$(BUILD_DIR)替换src/dirctory的绝对路径.每个目标文件的规则中的目标文件目标.
到目前为止,我发现这个系统易于使用和维护.所需的makefile片段很短,并且相对容易让协作者理解.
我开发此系统的项目是使用完全独立的ANSI C++编写的,没有外部依赖性.我认为这种自制的非递归makefile系统是自包含,高度可移植代码的合理选择.我会考虑一个更强大的构建系统,如CMake或gnu autotools,但是,对于任何对外部程序或库或非标准操作系统功能具有重要依赖性的项目.