我正在使用CMake构建一个中等规模的Fortran项目。我注意到,有时当我重构模块时,我会遇到奇怪的编译错误,这些错误在全新的构建目录中进行干净的重建后就消失了。
我已经设法在下面的最小示例中重现了这个问题。考虑一个包含两个库和一个可执行文件的项目
├── build
├── CMakeLists.txt
├── liba
│ ├── CMakeLists.txt
│ ├── first.f90
│ └── second.f90
├── libb
│ ├── CMakeLists.txt
│ └── third.f90
└── main.f90
main.f90
只是
program test
use first, only: x
use second, only: y
use third, only: z
implicit none
print *, x, y, z
end program
而根CMakeLists.txt
是
project(test)
enable_language(Fortran)
add_subdirectory(liba)
add_subdirectory(libb)
add_executable(main main.f90)
target_link_libraries(main a b)
每个模块只导出一个变量
! first.f90
module first
integer, parameter :: x = 1
end module first
! second.f90
module second
integer, parameter :: y = 2
end module second
! third.f90
module third
integer, parameter :: z = 3
end module third
这些库的描述类似于
# liba/CMakeLists.txt
add_library(a
first.f90
second.f90
)
target_include_directories(a PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
# libb/CMakeLists.txt
add_library(b
third.f90
)
target_include_directories(b PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
构建和运行后,我得到了1 2 3
,正如预期的那样。
现在的问题。如果我将second.f90
移动到libb
,并将y = 2
更改为y = 42
,我仍然将1 2 3
作为输出,即使我使用make -C build clean all
进行完全重建。这是因为liba
输出目录包含带有过时参数值y = 2
的second.mod
,并且它被用来代替libb
构建目录中的正确模块。Ninja生成器的行为是一样的。
我非常感谢任何关于如何修复我的构建脚本以避免此类错误的建议。
如果你想自己尝试一下,这里是GitHub上的例子:
$ git clone https://github.com/uranix/cmake-fortran-modules
$ cd cmake-fortran-modules
$ git checkout a90ddfc
$ mkdir build
$ cmake -B build
$ make -C build
$ ./build/main
1 2 3
$ git checkout 736b738
$ make -C build
$ ./build/main
1 2 3
$ make -C build clean all
$ ./build/main
1 2 3
$ mkdir build2
$ cmake -B build2
$ make -C build2
$ ./build2/main
1 42 3
- PS*.我听说C20也有模块,我想知道这个问题是否可以用C而不是Fortran重现。我稍后会检查并扩展这个问题
1条答案
按热度按时间bgtovc5b1#
虽然有一些复杂的问题是由模块文件(或任何生成的文件)引起的,但即使使用普通的库,这里的潜在问题也确实可以重现。
CMake已经处理了模块文件的依赖跟踪,知道何时根据时间戳重建目标,并且只有当模块文件实际发生更改时才知道。由于生成的文件的性质,很多这种情况都发生在构建时(您可以使用
make VERBOSE=1
查看很多情况)。它也只负责清理它所造成的东西;如果目标生成
second.mod
,则make clean
将仅删除second.mod
。对于所有cmake可以知道的,额外的文件可能是由一些外部工具或自定义命令生成的,所以期望它从早期的不同cmake配置中找出孤立的mod文件只是要求太多。
当您检查具有完全不同设置的不同分支时,即使您从
clean
开始,也会发生的第一件事是cmake被触发并重新构建所有依赖项等以匹配新设置,从而孤立上一次构建生成的任何文件。如果您决定将liba
重命名为libc
,您将看到遗留的孤立liba
目录。在我看来,你有几个选择;
1.在切换分支(或开始主要重构)之前,您可以确保
clean
*。你甚至可以把它作为一个本地的git钩子来添加(但是我不认为存在一个预结帐钩子)
1.您可以接受这样的主要重构需要一个干净的石板。
1.通过将
*.mod
文件的glob
和set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES list_of_mod_files)
设置组合起来,可以丰富clean
命令。仍然需要手动clean
,所以不是我最喜欢的选择。当然,您也可以手动运行rm build/lib*/*.mod
1.您可以通过为每个目标设置Fortran_MODULE_DIRECTORY来使用共享mod目录。相应修改每个
lib{a,b}/CMakeLists
假设这是一个很好的适合你的子模块。请注意,这仍然不会被检测到另一个孤立的
fourth.mod
,它可能是您完全删除的库意外遗留下来的,但主程序仍然在USE
中。