您对非递归 make 有什么经验?

发布于 2024-07-14 07:25:44 字数 1432 浏览 2 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

执着的年纪 2024-07-21 07:25:44

我工作的公司使用非递归 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 系统并非没有问题。 以下是您可能会遇到的一些最大障碍:

  • 使其可移植(尤其是 Windows)需要大量工作。
  • 虽然 GNU Make 几乎是一种可用的函数式编程语言,但它不适合大规模编程。 特别是,没有命名空间、模块或类似的东西可以帮助您将各个部分相互隔离。 这可能会导致问题,尽管没有您想象的那么严重。

相对于我们旧的递归 makefile 系统的主要优点是:

  • 。 检查整个树(2k 目录,20k 文件)大约需要两秒钟,然后确定它是最新的或开始编译。 旧的递归方法需要一分多钟才能完成任何操作。
  • 它正确处理依赖关系。 我们的旧系统依赖于子目录的构建顺序等。就像您从阅读米勒的论文中所期望的那样,将整个树视为单个实体确实是解决此问题的正确方法。
  • 经过我们付出的所有努力之后,它可以移植到我们所有支持的系统上。 它太酷了。
  • 抽象系统允许我们编写非常简洁的makefile。 仅定义库的典型子目录只有两行。 其中一行给出了库的名称,另一行列出了该库所依赖的库。

关于上面列表中的最后一项。 我们最终在构建系统中实现了一种宏扩展工具。 子目录 makefile 列出了程序、子目录、库以及 PROGRAMS、SUBDIRS、LIBS 等变量中的其他常见内容。 然后,每一个都被扩展为“真正的”GNU Make 规则。 这使我们能够避免很多命名空间问题。 例如,在我们的系统中,可以有多个同名的源文件,没有问题。

无论如何,这最终都是一项艰巨的工作。 如果您可以获得 SCons 或类似的代码来工作,我建议您首先查看它。

We use a non-recursive GNU Make system in the company I work for. It's based on Miller's paper and especially the "Implementing non-recursive make" link you gave. We've managed to refine Bergen's code into a system where there's no boiler plate at all in subdirectory makefiles. By and large, it works fine, and is much better than our previous system (a recursive thing done with GNU Automake).

We support all the "major" operating systems out there (commercially): AIX, HP-UX, Linux, OS X, Solaris, Windows, even the AS/400 mainframe. We compile the same code for all of these systems, with the platform dependent parts isolated into libraries.

There's more than two million lines of C code in our tree in about 2000 subdirectories and 20000 files. We seriously considered using SCons, but just couldn't make it work fast enough. On the slower systems, Python would use a couple of dozen seconds just parsing in the SCons files where GNU Make did the same thing in about one second. This was about three years ago, so things may have changed since then. Note that we usually keep the source code on an NFS/CIFS share and build the same code on multiple platforms. This means it's even slower for the build tool to scan the source tree for changes.

Our non-recursive GNU Make system is not without problems. Here are some of biggest hurdles you can expect to run into:

  • Making it portable, especially to Windows, is a lot of work.
  • While GNU Make is almost a usable functional programming language, it's not suitable for programming in the large. In particular, there are no namespaces, modules, or anything like that to help you isolate pieces from each other. This can cause problems, although not as much as you might think.

The major wins over our old recursive makefile system are:

  • It's fast. It takes about two seconds to check the entire tree (2k directories, 20k files) and either decide it's up to date or start compiling. The old recursive thing would take more than a minute to do nothing.
  • It handles dependencies correctly. Our old system relied on the order subdirectories were built etc. Just like you'd expect from reading Miller's paper, treating the whole tree as a single entity is really the right way to tackle this problem.
  • It's portable to all of our supported systems, after all the hard work we've poured into it. It's pretty cool.
  • The abstraction system allows us to write very concise makefiles. A typical subdirectory which defines just a library is just two lines. One line gives the name of the library and the other lists the libraries this one depends on.

Regarding the last item in the above list. We ended up implementing a sort of macro expansion facility within the build system. Subdirectory makefiles list programs, subdirectories, libraries, and other common things in variables like PROGRAMS, SUBDIRS, LIBS. Then each of these are expanded into "real" GNU Make rules. This allows us to avoid much of the namespace problems. For example, in our system it's fine to have multiple source files with the same name, no problem there.

In any case, this ended up being a lot of work. If you can get SCons or similar working for your code, I'd advice you look at that first.

左耳近心 2024-07-21 07:25:44

阅读 RMCH 论文后,我的目标是为我当时正在进行的一个小项目编写一个合适的非递归 Makefile。 完成后,我意识到应该可以创建一个通用的 Makefile“框架”,它可以用来非常简单和简洁地告诉 make 您想要构建什么最终目标,它们是什么类型的目标(例如库或可执行文件) )以及应该编译哪些源文件来制作它们。

经过几次迭代后,我最终创建了这样的内容:一个包含约 150 行 GNU Make 语法的单一样板 Makefile,不需要任何修改——它适用于我想使用它的任何类型的项目,并且足够灵活,可以构建不同类型的多个目标具有足够的粒度,可以为每个源文件指定精确的编译标志(如果我愿意),并为每个可执行文件指定精确的链接器标志。 对于每个项目,我需要做的就是为其提供小型、独立的 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

像上面这样的项目 Makefile 将完全按照您的预期执行操作:构建一个名为“foo”的可执行文件,编译 foo .c(使用 CFLAGS=-std=c99)和 baz.cpp(使用 CXXFLAGS=-fstrict-aliasing)并将“./inc”和“/usr/local/include/bar”添加到 #include< /code> 搜索路径,最终链接包括“libbar”库。 它还会注意到有一个 C++ 源文件,并且知道使用 C++ 链接器而不是 C 链接器。 该框架允许我指定比这个简单示例中显示的更多内容。

样板 Makefile 执行构建指定目标所需的所有规则构建和自动依赖项生成。 所有构建生成的文件都放置在单独的输出目录层次结构中,因此它们不会与源文件混合(并且这是在不使用 VPATH 的情况下完成的,因此拥有多个同名源文件没有问题)。

我现在已经在至少两打不同的项目中(重新)使用了这个相同的 Makefile。 我最喜欢这个系统的一些优点(除了为任何新项目创建正确的 Makefile 是多么容易)是:

  • 。 它几乎可以立即判断是否有任何内容已经过时。
  • 100% 可靠的依赖关系。 并行构建神秘地中断的可能性为零,并且它总是准确构建使所有内容恢复最新所需的最低限度。
  • 我将永远需要再次重写完整的makefile :D

最后我只想提一下,由于递归make 固有的问题,我认为我不可能把它拉出来离开。 我可能注定要一遍又一遍地重写有缺陷的 makefile,徒劳地尝试创建一个实际上可以正常工作的文件。

After reading the RMCH paper, I set out with the goal of writing a proper non-recursive Makefile for a small project I was working on at the time. After I finished, I realized that it should be possible to create a generic Makefile "framework" which can be used to very simply and concisely tell make what final targets you would like to build, what kind of targets they are (e.g. libraries or executables) and what source files should be compiled to make them.

After a few iterations I eventually created just that: a single boilerplate Makefile of about 150 lines of GNU Make syntax that never needs any modification -- it just works for any kind of project I care to use it on, and is flexible enough to build multiple targets of varying types with enough granularity to specify exact compile flags for each source file (if I want) and precise linker flags for each executable. For each project, all I need to do is supply it with small, separate Makefiles that contain bits similar to this:

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

A project Makefile such as the above would do exactly what you'd expect: build an executable named "foo", compiling foo.c (with CFLAGS=-std=c99) and baz.cpp (with CXXFLAGS=-fstrict-aliasing) and adding "./inc" and "/usr/local/include/bar" to the #include search path, with final linking including the "libbar" library. It would also notice that there is a C++ source file and know to use the C++ linker instead of the C linker. The framework allows me to specify a lot more than what is shown here in this simple example.

The boilerplate Makefile does all the rule building and automatic dependency generation required to build the specified targets. All build-generated files are placed in a separate output directory hierarchy, so they're not intermingled with source files (and this is done without use of VPATH so there's no problem with having multiple source files that have the same name).

I've now (re)used this same Makefile on at least two dozen different projects that I've worked on. Some of the things I like best about this system (aside from how easy it is to create a proper Makefile for any new project) are:

  • It's fast. It can virtually instantly tell if anything is out-of-date.
  • 100% reliable dependencies. There is zero chance that parallel builds will mysteriously break, and it always builds exactly the minimum required to bring everything back up-to-date.
  • I will never need to rewrite a complete makefile again :D

Finally I'd just mention that, with the problems inherent in recursive make, I don't think it would have been possible for me to pull this off. I'd probably have been doomed to rewriting flawed makefiles over and over again, trying in vain to create one that actually worked properly.

故事未完 2024-07-21 07:25:44

让我强调 Miller 论文中的一个论点:当您开始手动解决不同模块之间的依赖关系并且很难确保构建顺序时,您实际上是在重新实现构建系统首先要解决的逻辑。 构建可靠的递归 make 构建系统非常困难。现实生活中的项目有许多相互依赖的部分,其构建顺序很难弄清楚,因此,这项任务应该留给构建系统。 然而,只有拥有系统的全局知识,它才能解决这个问题。

此外,当在多个处理器/内核上同时构建时,递归 make 构建系统很容易崩溃。 虽然这些构建系统似乎可以在单个处理器上可靠地工作,但在您开始并行构建项目之前,许多缺失的依赖项都未被检测到。 我曾经使用过一个递归 make 构建系统,该系统最多可在四个处理器上运行,但在具有两个四核的机器上突然崩溃。 然后我遇到了另一个问题:这些并发问题几乎无法调试,我最终绘制了整个系统的流程图来找出问题所在。

回到你的问题,我发现很难想出为什么要使用递归 make 的充分理由。 非递归 GNU Make 构建系统的运行时性能很难被击败,相反,许多递归 make 系统都存在严重的性能问题(弱并行构建支持又是问题的一部分)。 有一篇论文,其中我评估了特定的(递归)创建构建系统并将其与 SCons 端口进行比较。 性能结果不具有代表性,因为构建系统非常不标准,但在这种特殊情况下,SCons 端口实际上更快。

底线:如果您确实想使用 Make 来控制软件构建,请选择非递归 Make,因为从长远来看,它会让您的生活变得更加轻松。 就我个人而言,出于可用性原因,我宁愿使用 SCons(或 Rake - 基本上任何使用现代脚本语言且具有隐式依赖支持的构建系统)。

Let me stress one argument of Miller's paper: When you start to manually resolve dependency relationships between different modules and have a hard time to ensure the build order, you are effectively reimplementing the logic the build system was made to solve in the first place. Constructing reliable recursive make build systems is very hard. Real life projects have many interdependent parts whose build order is non-trivial to figure out and thus, this task should be left to the build system. However, it can only resolve that problem if it has global knowledge of the system.

Furthermore, recursive make build-systems are prone to fall apart when building concurrently on multiple processors/cores. While these build systems may seem to work reliably on a single processor, many missing dependencies go undetected until you start to build your project in parallel. I've worked with a recursive make build system which worked on up to four processors, but suddenly crashed on a machine with two quad-cores. Then I was facing another problem: These concurrency issues are next to impossible to debug and I ended up drawing a flow-chart of the whole system to figure out what went wrong.

To come back to your question, I find it hard to think of good reasons why one wants to use recursive make. The runtime performance of non-recursive GNU Make build systems is hard to beat and, quite the contrary, many recursive make systems have serious performance problems (weak parallel build support is again a part of the problem). There is a paper in which I evaluated a specific (recursive) Make build system and compared it to a SCons port. The performance results are not representative because the build system was very non-standard, but in this particular case the SCons port was actually faster.

Bottom line: If you really want to use Make to control your software builds, go for non-recursive Make because it makes your life far easier in the long run. Personally, I would rather use SCons for usability reasons (or Rake - basically any build system using a modern scripting language and which has implicit dependency support).

友欢 2024-07-21 07:25:44

在我之前的工作中,我半心半意地尝试使构建系统(基于 GNU make)完全非递归,但我遇到了许多问题:

  • 工件(即构建的库和可执行文件)的源代码分散了跨多个目录,依靠 vpath 来查找它们
  • 不同目录中存在多个同名的源文件
  • 多个源文件在工件之间共享,通常使用不同的编译器标志进行编译
  • 不同的工件通常具有不同的编译器标志、优化设置等。

GNU make 简化非递归使用的一项功能是特定于目标的变量值

foo: FOO=banana
bar: FOO=orange

这意味着在构建目标“foo”时,$(FOO) 将扩展为“banana”,但是在构建目标时“bar”,$(FOO) 将扩展为“orange”。

这样做的一个限制是不可能具有特定于目标的 VPATH 定义,即无法为每个目标单独唯一地定义 VPATH。 在我们的例子中,为了找到正确的源文件,这是必要的。

为了支持非递归性,GNU make 所缺少的主要功能是它缺少命名空间。 特定于目标的变量可以以有限的方式用于“模拟”名称空间,但您真正需要的是能够使用本地范围在子目录中包含 Makefile。

编辑:在这种情况下,GNU make 的另一个非常有用(且经常未充分使用)的功能是宏扩展设施(请参阅 eval 函数)。 当您有多个目标具有相似的规则/目标,但其方式不同且无法使用常规 GNU make 语法表达时,这非常有用。

I made a half-hearted attempt at my previous job at making the build system (based on GNU make) completely non-recursive, but I ran into a number of problems:

  • The artifacts (i.e. libraries and executables built) had their sources spread out over a number of directories, relying on vpath to find them
  • Several source files with the same name existed in different directories
  • Several source files were shared between artifacts, often compiled with different compiler flags
  • Different artifacts often had different compiler flags, optimization settings, etc.

One feature of GNU make which simplifies non-recursive use is target-specific variable values:

foo: FOO=banana
bar: FOO=orange

This means that when building target "foo", $(FOO) will expand to "banana", but when building target "bar", $(FOO) will expand to "orange".

One limitation of this is that it is not possible to have target-specific VPATH definitions, i.e. there is no way to uniquely define VPATH individually for each target. This was necessary in our case in order to find the correct source files.

The main missing feature of GNU make needed in order to support non-recursiveness is that it lacks namespaces. Target-specific variables can in a limited manner be used to "simulate" namespaces, but what you really would need is to be able to include a Makefile in a sub-directory using a local scope.

EDIT: Another very useful (and often under-used) feature of GNU make in this context is the macro-expansion facilities (see the eval function, for example). This is very useful when you have several targets which have similar rules/goals, but differ in ways which cannot be expressed using regular GNU make syntax.

送你一个梦 2024-07-21 07:25:44

我同意参考文章中的陈述,但我花了很长时间才找到一个好的模板,它可以完成所有这些工作并且仍然易于使用。

目前我正在开展一个小型研究项目,正在尝试持续集成; 自动在 PC 上进行单元测试,然后在(嵌入式)目标上运行系统测试。 这对于制作来说并不简单,我已经寻找了一个好的解决方案。 发现 make 仍然是便携式多平台构建的一个不错的选择,我终于在 http: //code.google.com/p/nonrec-make

这真是一种解脱。 现在我的 makefile

  • 修改起来非常简单(即使 make 知识有限),
  • 可以快速编译,
  • 完全检查(.h)依赖关系,毫不费力,

我当然也会将它用于下一个(大)项目(假设 C/C++)

I agree with the statements in the refered article, but it took me a long time to find a good template which does all this and is still easy to use.

Currenty I'm working on a small research project, where I'm experimenting with continuous integration; automatically unit-test on pc, and then run a system test on a (embedded) target. This is non-trivial in make, and I've searched for a good solution. Finding that make is still a good choice for portable multiplatform builds I finally found a good starting point in http://code.google.com/p/nonrec-make

This was a true relief. Now my makefiles are

  • very simple to modify (even with limited make knowledge)
  • fast to compile
  • completely checking (.h) dependencies with no effort

I will certainly also use it for the next (big) project (assuming C/C++)

痴梦一场 2024-07-21 07:25:44

我为一个中型 C++ 项目开发了一个非递归 make 系统,该系统旨在用于类 UNIX 系统(包括 Mac)。 该项目中的代码全部位于以 src/ 目录为根的目录树中。 我想编写一个非递归系统,其中可以从顶级 src/ 目录的任何子目录中键入“make all”,以便编译以工作目录为根的目录树中的所有源文件,如下所示在递归 make 系统中。 因为我的解决方案似乎与我见过的其他解决方案略有不同,所以我想在这里描述一下,看看我是否得到任何反应。

我的解决方案的主要元素如下:

1)src/树中的每个目录都有一个名为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 文件,并连接 [subdirectory]_SRCS 变量以创建自己的 [directory]_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) 每个director中的Makefile包含来自同一目录的sources.mk文件,并包含一行“-include $([directory]_OBJS:.o=.d)”,尝试包含每个源文件的依赖文件在目录及其子目录中,如上面给出的 URL 中所述。

这个方案与我见过的允许从任何目录调用“make all”的方案之间的主要区别是使用绝对路径,以允许在从不同目录调用 Make 时一致地解释相同的路径。 只要使用变量来表示这些路径来表示顶级源目录,这就不会阻止人们移动源树,并且比实现相同目标的某些替代方法更简单。

目前,我的该项目系统始终执行“就地”构建:通过编译每个源文件生成的目标文件放置在与源文件相同的目录中。 通过更改编辑 gcc 依赖文件的脚本来启用异地构建将是很简单的,以便用变量 $(BUILD_DIR) 替换 src/ 目录的绝对路径,该变量表示表达式中的构建目录每个对象文件的规则中的对象文件目标。

到目前为止,我发现这个系统易于使用和维护。 所需的 makefile 片段很短,并且相对容易让协作者理解。

我开发这个系统的项目是用完全独立的 ANSI C++ 编写的,没有外部依赖。 我认为这种自制的非递归 makefile 系统对于独立的、高度可移植的代码来说是一个合理的选择。 然而,对于任何对外部程序或库或非标准操作系统功能具有重要依赖性的项目,我会考虑使用更强大的构建系统,例如 CMake 或 gnu autotools。

I have developed a non-recursive make system for a one medium sized C++ project, which is intended for use on unix-like systems (including macs). The code in this project is all in a directory tree rooted at a src/ directory. I wanted to write a non-recursive system in which it is possible to type "make all" from any subdirectory of the top level src/ directory in order to compile all of the source files in the directory tree rooted at the working directory, as in a recursive make system. Because my solution seems to be slightly different from others I have seen, I'd like to describe it here and see if I get any reactions.

The main elements of my solution were as follows:

1) Each directory in the src/ tree has a file named sources.mk. Each such file defines a makefile variable that lists all of the source files in the tree rooted at the directory. The name of this variable is of the form [directory]_SRCS, in which [directory] represents a canonicalized form of the path from the top level src/ directory to that directory, with backslashes replaced by underscores. For example, the file src/util/param/sources.mk defines a variable named util_param_SRCS that contains a list of all source files in src/util/param and its subdirectories, if any. Each sources.mk file also defines a variable named [directory]_OBJS that contains a list of the the corresponding object file *.o targets. In each directory that contains subdirectories, the sources.mk includes the sources.mk file from each of the subdirectories, and concatenates the [subdirectory]_SRCS variables to create its own [directory]_SRCS variable.

2) All paths are expressed in sources.mk files as absolute paths in which the src/ directory is represented by a variable $(SRC_DIR). For example, in the file src/util/param/sources.mk, the file src/util/param/Componenent.cpp would be listed as $(SRC_DIR)/util/param/Component.cpp. The value of $(SRC_DIR) is not set in any sources.mk file.

3) Each directory also contains a Makefile. Every Makefile includes a global configuration file that sets the value of the variable $(SRC_DIR) to the absolute path to the root src/ directory. I chose to use a symbolic form of absolute paths because this appeared to be the easiest way to create multiple makefiles in multiple directories that would interpret paths for dependencies and targets in the same way, while still allowing one to move the entire source tree if desired, by changing the value of $(SRC_DIR) in one file. This value is set automatically by a simple script that the user is instructed to run when the package is dowloaded or cloned from the git repository, or when the entire source tree is moved.

4) The makefile in each directory includes the sources.mk file for that directory. The "all" target for each such Makefile lists the [directory]_OBJS file for that directory as a dependency, thus requiring compilation of all of the source files in that directory and its subdirectories.

5) The rule for compiling *.cpp files create a dependency file for each source file, with a suffix *.d, as a side-effect of compilation, as described here: http://mad-scientist.net/make/autodep.html. I chose to use the gcc compiler for dependency generation, using the -M option. I use gcc for dependency generation even when using another compiler to compile the source files, because gcc is almost always available on unix-like systems, and because this helps standardize this part of the build system. A different compiler can be used to actually compile the source files.

6) The use of absolute paths for all files in the _OBJS and _SRCS variables required that I write a script to edit the dependency files generated by gcc, which creates files with relative paths. I wrote a python script for this purpose, but another person might have used sed. The paths for dependencies in the resulting dependency files are literal absolute paths. This is fine in this context because the dependency files (unlike the sources.mk files) are generated locally and rather than being distributed as part of the package.

7) The Makefile in each director includes the sources.mk file from the same directory, and contains a line "-include $([directory]_OBJS:.o=.d)" that attempts to include a dependency files for every source file in the directory and its subdirectories, as described in the URL given above.

The main difference between this an other schemes that I have seen that allow "make all" to be invoked from any directory is the use of absolute paths to allow the same paths to be interpreted consistently when Make is invoked from different directories. As long as these paths are expressed using a variable to represent the top level source directory, this does not prevent one from moving the source tree, and is simpler than some alternative methods of achieving the same goal.

Currently, my system for this project always does an "in-place" build: The object file produced by compiling each source file is placed in the same directory as the source file. It would be straightforward to enable out-of place builds by changing the script that edits the gcc dependency files so as to replace the absolute path to the src/ dirctory by a variable $(BUILD_DIR) that represents the build directory in the expression for the object file target in the rule for each object file.

Thus far, I've found this system easy to use and maintain. The required makefile fragments are short and comparatively easy for collaborators to understand.

The project for which I developed this system is written in completely self-contained ANSI C++ with no external dependencies. I think that this sort of homemade non-recursive makefile system is a reasonable option for self-contained, highly portable code. I would consider a more powerful build system such as CMake or gnu autotools, however, for any project that has nontrivial dependencies on external programs or libraries or on non-standard operating system features.

折戟 2024-07-21 07:25:44

我知道至少有一个大型项目 (ROOT),其中 使用 [powerpoint link] 递归使人认为有害中描述的机制进行广告。 该框架超过一百万行代码,并且编译得相当智能。


当然,我处理的所有使用递归 make 的大型项目的编译速度都非常慢。 ::叹::

I know of at least one large scale project (ROOT), which advertises using [powerpoint link] the mechanism described in Recursive Make Considered Harmful. The framework exceeds a million lines of code and compiles quite smartly.


And, of course, all the largish projects I work with that do use recursive make are painfully slow to compile. ::sigh::

爱你不解释 2024-07-21 07:25:44

我编写了一个不太好的非递归 make 构建系统,从那时起,我为一个名为 Pd 扩展。 它基本上有点像包含大量库的脚本语言。 现在我也在使用 Android 的非递归系统,这就是我对这个主题的思考背景。

我真的不能说太多关于两者之间的性能差异,我并没有真正关注,因为完整的构建实际上只在构建服务器上完成。 我通常研究核心语言或特定的库,所以我只对构建整个包的子集感兴趣。 递归 make 技术具有巨大的优势,可以使构建系统既独立又可以集成到更大的整体中。 这对我们来说很重要,因为我们希望对所有库使用一个构建系统,无论它们是集成的还是由外部作者编写的。

我现在正在构建 Android 内部的自定义版本,例如基于 SQLCipher 加密 sqlite 的 Android SQLite 类版本。 所以我必须编写非递归 Android.mk 文件来包装各种奇怪的构建系统,比如 sqlite 的。 我无法弄清楚如何使 Android.mk 执行任意脚本,而根据我的经验,这在传统的递归 make 系统中很容易。

I written a not very good non-recursive make build system, and since then a very clean modular recursive make build system for a project called Pd-extended. Its basically kind of like a scripting language with a bunch of libraries included. Now I'm also working Android's non-recursive system, so that's the context of my thoughts on this topic.

I can't really say much about the differences in performance between the two, I haven't really paid attention since full builds are really only ever done on the build server. I am usually working either on the core language, or a particular library, so I am only interested in building that subset of the whole package. The recursive make technique has the huge advantage of making the build system be both standalone and integrated into a larger whole. This is important to us since we want to use one build system for all libraries, whether they are integrated in or written by an external author.

I'm now working on building custom version of Android internals, for example an version of Android's SQLite classes that are based on the SQLCipher encrypted sqlite. So I have to write non-recursive Android.mk files that are wrapping all sorts of weird build systems, like sqlite's. I can't figure out how to make the Android.mk execute an arbitrary script, while this would be easy in a traditional recursive make system, from my experience.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文