如何防止CMake使用过时的Fortran模块

kuarbcqp  于 9个月前  发布在  其他
关注(0)|答案(1)|浏览(124)

我正在使用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 = 2second.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重现。我稍后会检查并扩展这个问题
bgtovc5b

bgtovc5b1#

虽然有一些复杂的问题是由模块文件(或任何生成的文件)引起的,但即使使用普通的库,这里的潜在问题也确实可以重现。
CMake已经处理了模块文件的依赖跟踪,知道何时根据时间戳重建目标,并且只有当模块文件实际发生更改时才知道。由于生成的文件的性质,很多这种情况都发生在构建时(您可以使用make VERBOSE=1查看很多情况)。
它也只负责清理它所造成的东西;如果目标生成second.mod,则make clean将仅删除second.mod
对于所有cmake可以知道的,额外的文件可能是由一些外部工具或自定义命令生成的,所以期望它从早期的不同cmake配置中找出孤立的mod文件只是要求太多。
当您检查具有完全不同设置的不同分支时,即使您从clean开始,也会发生的第一件事是cmake被触发并重新构建所有依赖项等以匹配新设置,从而孤立上一次构建生成的任何文件。如果您决定将liba重命名为libc,您将看到遗留的孤立liba目录。
在我看来,你有几个选择;
1.在切换分支(或开始主要重构)之前,您可以确保clean *。

$ 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
$ make -C build clean  # make sure to do this before refactoring!
$ git checkout 736b738
$ make -C build
$ ./build/main         # and it will work
           1          42           3

你甚至可以把它作为一个本地的git钩子来添加(但是我不认为存在一个预结帐钩子)
1.您可以接受这样的主要重构需要一个干净的石板。
1.通过将*.mod文件的globset_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

set_target_properties(a PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/mod)
target_include_directories(a PUBLIC ${CMAKE_BINARY_DIR}/mod)
set_target_include_directories(b PROPERTIES Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/mod)
target_include_directories(b PUBLIC ${CMAKE_BINARY_DIR}/mod)

假设这是一个很好的适合你的子模块。请注意,这仍然不会被检测到另一个孤立的fourth.mod,它可能是您完全删除的库意外遗留下来的,但主程序仍然在USE中。

相关问题