【问题标题】:why does Delphi decide to recompile certain files needlessly为什么 Delphi 决定不必要地重新编译某些文件
【发布时间】:2013-04-22 00:53:04
【问题描述】:

当我构建我的项目时,我希望在编译过程中看到所有单元都重新编译。当我“制作”项目时,我希望只重新编译源代码发生更改的那些单元。当我在完整构建后立即制作时,我希望看到链接发生而不是其他任何事情。

出于某种原因,Delphi 已经开始考虑重新编译某些单元。我注意到过去的主要是idIOHandler.pas - Indy 的一部分。它有时会编译其他的——总是 Indy 中的某个单元。我徒劳地在 Indy 源文件夹中搜索带有错误日期戳的文件。

(偶尔我会看到相反的问题 - 我知道我已更改的源单元没有重新编译。我将此归结为我的 PC 和保存源的服务器之间的时间差异。)

这不是什么大问题,但我很想听听解释。

【问题讨论】:

  • 需要编译的单元: 1. 已经修改的单元。 2.那些依赖于接口部分已修改的单元。
  • 为什么 Indy 单元首先出现在您项目的搜索路径中?让他们离开那里。不要把源码和编译好的文件放在同一个目录下,然后只把编译好的目录放在搜索路径上。将源码目录放在浏览路径上。
  • @Rob:这是每个开发人员应该知道但经常忘记的前十件事。

标签: delphi build compilation makefile delphi-2006


【解决方案1】:

这完全取决于 Delphi 如何决定需要重新编译的东西。

我已经看到 一个 的事情导致了这种情况(在完全非 Delphi 的环境中)。出于某种原因,这些文件被赋予了未来的日期,依赖于它们的文件总是会重新编译。

例如,假设由于某种原因 myprog.c 的日期为 2027 年。当您第一次编译它(到 myprog.o )时,结果将给出今天的日期,即 2013 年。

下一次你去 make,myprog.c 上的日期在 myprog.o 之后,因此它会重新编译。

不确定这是否是导致您的问题的原因,但可能值得研究。

您需要注意的另一件事。如果你的单元依赖于接口发生变化的另一个单元,它将被重新编译。另请参阅 this answer 以了解其他问题,但它也提供了与此问题相关的信息。

我大部分时间都倾向于进行完整构建,因为我不信任计算机 - 我对它们进行编程以便我知道它们有多么狡猾:-)

【讨论】:

  • +1 表示“文件的日期是未来”,当您测试 DST 转换问题并且必须前后移动开发(虚拟)机器的日期时,这非常讨厌;-)
  • Paxdiablo - 过时的源文件是我首先怀疑并检查的。
【解决方案2】:

不幸的是,Delphi 编译器的源代码及其依赖项是封闭源代码,所以这个问题需要推测。但是,如果您有兴趣避免重新编译 Indy 单元,您当然可以只将 DCU 文件放在库路径中,并从搜索路径(项目级别)和库路径(全局 IDE 级别)中删除 Indy 源文件配置。如果您确实跨映射的网络驱动器编译源代码,那么我认为您疯了。我建议您查看 mercurial 和这个很酷的功能,您可以在其中克隆 repos 并将所有源代码的副本完全保存在您正在构建的同一台计算机上。很酷。

无论如何,编译器...到目前为止我观察到的是:

  1. 在您(和我)看来,重新编译的内容实际上可能只是对接口部分的扫描,实际上是构建依赖树的方式。与 C(makefile)或 Java(ant 或 maven)风格的构建环境不同,至少在代码的接口部分设置编译器是找出特定模块的所有依赖项的唯一方法。我认为即使您从搜索路径中删除了 Indy 单元,您仍可能会看到编译器进度显示“编译”IdIOHandler.pas,但在这种情况下,它要做的是加载接口的编译表示来自 DCU 文件的帕斯卡单元部分,然后决定要读取哪些其他文件。有时 IDE 进度会在单元上显示“正在编译”,但最后不会修改 DCU 文件。

  2. 在进行重大更改后进行重构或修复大型项目时,我观察到以下情况:猜测下一个语法错误将在哪里破坏编译是非常困难的。湾。编译器在单元 A 中发生致命错误,然后在单元 B 中途,然后在单元 A 上,然后在单元 C 上,然后在单元 B 上,这表明编译器与您基于 C 编译器的心智模型完全不同在 makefile 上会是这样的:首先我们编译单元 A,然后我们编译单元 B,然后我们编译单元 D。我所拥有的所有证据表明,真正的过程远比这复杂得多,而且编译器团队的某个人在里面将调用文件 A 的“单程”,“扫描”其他文件内容或扫描内存中其他文件的内部表示(而不是解析物理文件内容)导致“编译”不是all or nothing 或 all on once 的事情,而是一个大的内存释放,编译器的单元评估顺序由接口构建的一些内存树驱动,然后是每个单元的实现子句。在现实世界的应用程序中预测这个大型循环图的行为超出了我的技能。

【讨论】:

  • 我的观察 #2 与 paxdiablo 对界面部分更改单位的观察相匹配。
  • 谢谢沃伦。我认为它可能是 #1,尽管这似乎需要很长时间。
【解决方案3】:

正如 Warren 所说,单元编译顺序是非常不可预测的(从人类的角度来看,我确信编译器是确定性的)并且接口和实现是单独的编译阶段(因为解析实现使用子句可以强制在继续执行之前编译其他单元),但我希望这两个部分中的每一个都可以一次编译。

偏离那个的唯一原因(我从未观察到,但从未搜索过)将是 delphi 编译器内的多个“编译”工作线程。

其他复杂因素:

  1. 循环/相互依赖,也是允许的(实现 A 使用 B,而 B 接口使用 A)类型。直接的 A B dep 通常可以正确检测到,但较大的循环可能会导致重新编译。
  2. 多个目录中的源和/或(几乎)相同的包含文件的多个副本(indy 为此而臭名昭著)。不同的包含文件可以启用稍微不同的编译器选项,在某些情况下可能(?)触发重新编译。
  3. 内联,泛型。 (即跨接口实现分离,在使用代码处理之前可能需要实现信息)
  4. .dpr 中的文件路径有一些影响。例如。选择一个特定的 以多种形式存在的文件的副本。 (您在新路径中签出,但 dpr 中的绝对路径指向旧签出)。在进行部分构建时,这在命令行构建中特别明显。 (建筑单位,而不是计划/图书馆项目)
  5. (不完全确定。我有时觉得 Delphi 可以找到在 IDE 中打开的文件,即使它们不在搜索路径中。这也可能在多个副本的情况下引发选择性行为)

最重要的两个是#1和#2,在FPC中效果比在Delphi中强。 (Delphi 中更好的循环逻辑?)。重新编译 Lazarus 时,过度重新编译是很正常的。

#3 来自理论,#4 有时出现在由批处理文件编译的较大树中。由于内联在形式上是可选的,因此应该不是问题,但如果它至少尝试超出要求,则没有记录。

【讨论】:

  • 我很确定编译器不是多线程的,但是当涉及到一系列致命错误会破坏编译的时间和顺序时,它似乎无处不在。这是让我想知道发生了什么的最重要的事情。
  • 请注意,每次(增量 F9)编译的单元顺序(和接口实现部分)可能不同。只要您不更改USES子句,“构建”构建顺序就应该相同。
猜你喜欢
  • 2016-09-05
  • 2014-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多