首页 > 解决方案 > make 重新编译未更改的文件

问题描述

make 重新编译源代码文件,即使它们没有改变。要重现此行为,我需要执行以下操作:

  1. make clean,这样就只剩下 *.90 个文件了
  2. 制作
  3. 保存 *.f90 文件之一,例如“touchbands.f90”
  4. make:bands.f90 被重新编译,这是正确的,因为它被改变了
  5. make:bands.f90 再次重新编译,这是不正确的,因为bands.f90 没有被触及

在这个例子中,在 4. 之后,文件bands.f90 将在每次 make 调用时重新编译。如果我另外更改任何其他源代码文件,则该行为也适用于该文件。所以过了一段时间,我最终重新编译了我所有的源代码。我可以通过调用 make clean 来部分解决这个问题,它会重置循环。但这不是永久修复。

您可以在下面看到上述五个步骤的输出:

1: find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

2: [triggered by changes in const.f90]
   mpifort -c const.f90

   [triggered by changes in system.f90 const.mod]
   mpifort -c system.f90

   [triggered by changes in io.f90 system.mod const.mod]
   mpifort -c io.f90

   [triggered by changes in parallel.f90]
   mpifort -c parallel.f90

   [triggered by changes in bands.f90 system.mod io.mod const.mod parallel.mod]
   mpifort -c bands.f90

   [triggered by changes in polmob.f90 io.mod system.mod bands.mod parallel.mod]
   mpifort -o polmob polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

3: "no output"

4: [triggered by changes in bands.f90]
   mpifort -c bands.f90

5: [triggered by changes in bands.f90]
   mpifort -c bands.f90

在第 5 步 make 实际上应该说没有要编译的东西。但是据说bands.f90发生了变化,因此需要重新编译。

这是我的Makefile:

polmob: polmob.f90 io.mod system.mod bands.mod parallel.mod
    @echo [triggered by changes in $?]
    mpifort -o polmob polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

io.mod: io.f90 system.mod const.mod
    @echo [triggered by changes in $?]
    mpifort -c io.f90

system.mod: system.f90 const.mod
    @echo [triggered by changes in $?]
    mpifort -c system.f90

bands.mod: bands.f90 system.mod io.mod const.mod parallel.mod
    @echo [triggered by changes in $?]
    mpifort -c bands.f90

const.mod: const.f90
    @echo [triggered by changes in $?]
    mpifort -c const.f90

parallel.mod: parallel.f90
    @echo [triggered by changes in $?]
    mpifort -c parallel.f90

clean:
    find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

标志 $? 告诉我,重新编译是由特定 .f90 文件中的更改触发的。所以以某种方式使涉及到旧的变化。有谁知道这种行为的原因可能是?

标签: makefilefortranrecompile

解决方案


有谁知道这种行为的原因可能是?

我找到了原因:如果我将 Makefile 中的任何地方的“mod”替换为“o”,问题就会消失。但是,我不太明白为什么。

唯一合理的解释是现有.mod文件的时间戳在重建原始源时没有更新。我认为只有在模块接口不变的情况下才会扩展——即没有添加或删除函数或子例程,或者以影响其外部接口的方式修改,同样没有添加、删除或更改类型的模块变量.

我可以看到,当文件中没有任何实际更改时,避免更新.mod文件可能被认为是可取的,特别是与存储在外部库中的模块实现结合使用时。然而,据我所知,甚至文件的存在都没有普遍的标准化.mod,更不用说创建或更新它们的环境了。您的编译器可能会提供一个选项,在编译相应的源代码时强制更新它们;如果是这样,那么将该选项添加到您的编译命令应该可以解决您的问题。

否则,您的编译器肯定会承诺的是,如果您要求它将源文件编译为目标文件,那么在成功时,它将编写一个新的目标文件。此外,虽然我不一定会猜到.mod文件也是如此,但重要的是要了解后者通常描述模块的接口,而不是它们的实现,因此您的主要规则在语义上是不正确的:

polmob: polmob.f90 io.mod system.mod bands.mod parallel.mod
    @echo [triggered by changes in $?]
    mpifort -o polmob polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

如果任何实现发生变化,无论模块接口是否发生变化,您都需要重新构建,并且您的规则没有充分满足该要求。

在这一点上,我还观察到,当您通过该规则进行重建时,您会重建所有源代码,从而使每个源代码的构建规则变得毫无意义。如果这就是您想要的,那么更简单的解决方案是将您的 while makefile 重写为:

SOURCES = polmob.f90 io.f90 system.f90 bands.f90 parallel.f90

polmob: $(SOURCES)
    @echo [triggered by changes in $?]
    mpifort -o $@ $(SOURCES)

clean:
    find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

在那里,目标实际上是从指定的先决条件构建的,构建配方足以从这些先决条件构建目标。但是,如果任何源发生更改,它将全部重建它们(类似于您的原始 makefile 所做的)。

另一种方法是构建单个对象,然后在单独的步骤中将它们链接在一起。您原来的 makefile 似乎倾向于那个方向,但随后它用主要规则将其丢弃。如果您想采用这种方法,那么由于以下事实而变得复杂

  • 它确实是各个对象所依赖的模块接口(除了它们自己的源),因此在这些情况下,用其他对象文件表达先决条件是不正确的;
  • 和文件都是由同一个进程构建的(即它有多个输出).o.mod
  • 显然,该过程使用不同的逻辑make来确定.mod文件是否过时。

多输出问题可能是最棘手的问题。您可以在 Automake 文档中找到不同严格程度的讨论和替代解决方案(但不特定于 Automake)。

这是一种您可以解决的方法,受 Automake 文档的启发,但考虑到您在 Fortran 实现中发现的特殊性:

# All the object files contributing to the program:
OBJECTS = bands.o const.o io.o parallel.o polmob.o system.o

# Link all the objects together to form the final program
# (.mod files are typically not needed for this step)
polmob: $(OBJECTS)
    mpifort -o $@ $(OBJECTS)

# Rules for building the objects

bands.o : bands.f90 const.mod io.mod parallel.mod system.mod
    mpifort -c bands.f90 -o $@

const.o : const.f90
    mpifort -c const.f90 -o $@

io.o : io.f90 const.mod system.mod
    mpifort -c io.f90 -o $@

parallel.o : parallel.f90
    mpifort -c parallel.f90 -o $@

polmob.o : polmob.f90 bands.mod const.mod io.mod parallel.mod system.mod
    mpifort -c polmob.f90 -o $@

system.o : system.f90 const.mod
    mpifort -c system.f90 -o $@

# Rules for ensuring that .mod files are (re)created when needed, and
# that their timestamps do not fall behind those of their corresponding
# sources

bands.mod : bands.o
    @if test -f $@; then touch $@; else \
      rm -f bands.o; \
      $(MAKE) bands.o; \
    fi

const.mod : const.o
    @if test -f $@; then touch $@; else \
      rm -f const.o; \
      $(MAKE) const.o; \
    fi

io.mod : io.o
    @if test -f $@; then touch $@; else \
      rm -f io.o; \
      $(MAKE) io.o; \
    fi

parallel.mod : parallel.o
    @if test -f $@; then touch $@; else \
      rm -f parallel.o; \
      $(MAKE) parallel.o; \
    fi

system.mod : system.o
    @if test -f $@; then touch $@; else \
      rm -f system.o; \
      $(MAKE) system.o; \
    fi

####

clean:
    find . ! -name 'Makefile' -a ! -name '*.f90' -type f -exec rm -f {} +

这展示了正确的依赖关系:

  • 主程序(仅)依赖于所有对象。
  • 每个对象都依赖于它自己的源和.mod它使用的那些模块的文件。

它还解释了这样一个事实,即相同的编译过程会生成一个目标文件和(在适当的情况下)一个相应的文件,并且它会在需要满足.mod的地方更新时间戳。它可能偶尔会导致文件在不需要时被重建,因为它会阻止编译器避免更新文件时间戳的任何尝试。但这应该是一次性的,而不是每个后续构建。.modmake.mod


推荐阅读