1. make工具的安装
pacman -Ss make
pacman -S mingw64/mingw-w64-x86_64-make
如果前面已经安装过gfortran和gdb,则无需添加环境变量,因为这些工具位于相同的目录。
如果我们通过命令提示符执行如下所示的命令:
make
可以发现命令提示符提示‘make’不是内部或外部命令,也不是可运行的程序或批处理程序。
如果我们定位到gfortran编译器所在的目录,可以发现在该目录中确实不存在make.exe,只有一个mingw32-make.exe,由于make是存在于Linux系统中的命令,因此mingw32-make可以理解为windows环境中的工具,为了使用方便,我们可以**一个该文件并将其重命名为make.exe,这样就可以在命令提示符中通过输入make
来调用该工具了。
在完成**后,我们可以输入如下命令来检查make工具是否安装成功:
make -v
看到以下内容则表示安装成功:
GNU Make 4.4
Built for Windows32
Copyright (C) 1988-2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
为了演示基于VS Code的多文件编译过程,我们构建如下所示的一个项目,假定该项目由主程序hello.f90、模块module1.f90和模块module2.f90构成。
主程序hello.f90的源代码为:
program hello
use module1, only: sub1
use module2, only: sub3
implicit none
print *, 'This is main program'
call sub1()
call sub3()
end program hello
模块module1.f90的源代码为:
module module1
use module2, only: sub2
implicit none
contains
subroutine sub1
print *, 'This is subroutine1'
call sub2()
end subroutine sub1
end module module1
模块module2.f90的源代码为:
module module2
implicit none
contains
subroutine sub2()
print *, 'This is subroutine2'
end subroutine sub2
subroutine sub3()
print *, 'This is subroutine3'
end subroutine sub3
end module module2
可以看到,主程序依赖于模块1中的子程序1和模块2中的子程序3,而模块1中的子程序1又依赖于模块2中的子程序2。我们可以直接在VS Code的终端中通过如下所示的命令编译并链接该程序:
gfortran -o hello hello.f90 module1.f90 module2.f90
通过.\hello
执行该程序,可以得到如下所示的运行结果:
This is main program
This is subroutine1
This is subroutine2
This is subroutine3
当然,我们也可以先编译各个源文件,再通过链接形成可执行程序:
gfortran -c hello.f90 module1.f90 module2.f90
gfortran -o hello hello.o module1.o module2.o
.\hello
两种方法得到的运行结果是完全一致的。
注意,这里当我们创建两个模块的源代码文件时,VS Code自动创建了两个与源代码文件同名的mod
文件,该文件包含了编译器确定该模块中包含的子程序和函数及其接口所需要的所有信息。这是因为如果我们在其他文件中调用这个模块,则在编译该文件时是需要这个模块的信息的,这个信息非常重要:它使得编译器能够检查你是否以正确的方式调用了该函数。可能是你犯了一个错误,使用了两个参数而不是一个参数调用了该函数。如果编译器对函数的接口一无所知,那么它就无法检查任何内容。
这样手动编译Fortran项目并不是一个非常好的选择,因为如果你的项目中有很多源文件,则各个源文件之间的依赖关系将会变得非常复杂;此外,如果我们更改了某个源文件中的代码,或者引入了新的源文件,除非我们能够理解它们之间的依赖关系,否则就只能重新编译整个项目,这将非常耗时。因此,我们希望有一个构建工具能够帮助我们管理源代码的编译和链接过程。这里我们可以通过make工具来完成Fortran程序的编译。关于make工具的详细介绍可以参考:https://seisman.github.io/how-to-write-makefile/introduction.html。
这里,我们仅需要知道的是make命令执行时,需要一个makefile文件,以告诉make命令需要怎么样的去编译和链接程序。makefile的基本规则为:
target ... : prerequisites ...
command
...
...
这里,target可以是一个object file(目标文件),也可以是一个执行文件;prerequisites为生成该target所依赖的文件和/或target;command为该target要执行的命令(任意的shell命令),注意command前面不能为空格,只能为制表符。
例如,对于我们构建的Fortran项目,编译该项目的makefile可以写为:
hello: hello.o module1.o module2.o
gfortran -g -o hello hello.o module1.o module2.o
hello.o: hello.f90
gfortran -g -c hello.f90
module1.o: module1.f90
gfortran -g -c module1.f90
module2.o: module2.f90
gfortran -g -c module2.f90
注意,在编译和链接时,必须通过-g
参数输出调试数据,否则在VS Code中调试时程序不会在断点处正常停止。
将makefile文件放在程序源文件所在目录,然后在VS Code终端中输入如下所示的命令:
make
make工具将会自动搜索当前目录存在的makefile并执行该文件中的命令。
可以看到,在上面的makefile中,编译生成的中间文件(.o
文件)被重复书写了两次,如果我们的项目中需要加入一个新的.o
文件,则我们需要在makefile文件中的两个地方进行相应的修改。当项目中的源文件非常多时,为了使得我们的项目更易于维护,我们可以声明一个名为objects
的变量,用来存放这些中间文件,并在makefile文件中通过$(objects)
的方式来使用这些文件:
objects= hello.o module1.o module2.o
这样,我们的makefile文件可以简写为如下所示的形式:
objects= hello.o module1.o module2.o
hello: $(objects)
gfortran -g -o hello $(objects)
hello.o: hello.f90
gfortran -g -c hello.f90
module1.o: module1.f90
gfortran -g -c module1.f90
module2.o: module2.f90
gfortran -g -c module2.f90
这里,我们可以简单地理解为通过变量objects
完成了字符串的替换。
通过这样的方式,我们可以分别为主程序和模块文件定义如下所示的变量:
MAIN= hello
MODULES= module1.f90 module2.f90
MODULES_OBJ= module1.o module2.o
FC= gfortran
这样,上述的makefile文件可以进一步简化为:
MAIN= hello
MODULES= module1.f90 module2.f90
MODULES_OBJ= module1.o module2.o
FC= gfortran
$(MAIN): $(MAIN).o $(MODULES_OBJ)
$(FC) -g -o $(MAIN) $(MAIN).o $(MODULES_OBJ)
$(MAIN).o: $(MAIN).f90 $(MODULES_OBJ)
$(FC) -g -c $(MAIN).f90
$(MODULES_OBJ): $(MODULES)
$(FC) -g -c $(MODULES)
clean:
del hello $(MODULES_OBJ)
在上面改进的makefile中,我们定义了一个变量FC
,通过修改该变量的取值,我们可以使用不同的编译器来编译我们的Fortran项目。例如,如果我们安装了Intel Visual Fortran,只需将变量FC
的取值更改为ifort
,就可以通过Intel的Fortran编译器来编译该项目。此外,makefile的最后三行定义了一个伪目标,用于在完成编译后清除中间文件,以便下次可以完整地重编译。
观察上面的makefile文件可以看到,MODULES
变量和MODULES_OBJ
变量包含的文件具有相同的前缀,因此我们可以利用make工具中的模式字符替换函数patsubst
来替换MODULES
变量中的少量字符,从而得到变量MODULES_OBJ
中包含的文件。patsubst
函数的使用规则为:
$(patsubst <pattern>,<replacement>,<text>)
patsubst
函数会检查<text>
中的单词是否符合模式<pattern>
,如果符合则以<replacement>
替换。这里,<pattern>
可以包含通配符%
,表示任意长度的字符串。基于这样的规则,我们可以将变量MODULES_OBJ
通过处理变量MODULES
获得:
MAIN= hello
MODULES= module1.f90 module2.f90
MODULES_OBJ= $(patsubst %.f90, %.o, $(MODULES))
在上面的指令中,我们仍然需要指定每一个模块文件的文件名。事实上,如果这些文件位于makefile文件所在目录,我们可以利用wildcard
函数来输出指定路径下指定文件类型的所有文件名。因此,上面的指令可以重写为:
MAIN:= hello
MODULES:= $(wildcard *.f90)
MODULES:= $(filter-out $(MAIN).f90, $(MODULES))
MODULES_OBJ:= $(patsubst %.f90, %.o, $(MODULES))
改进之后的指令与前面的指令有了很大的不同,首先,由于wildcard
函数会输出当前目录的所有后缀名为f90的文件,因此我们使用了filter-out
函数将代表主程序的文件给剔除;此外,在第三行指令中出现了递归定义,这会让make陷入无限的变量展开过程中,为了避免这样的问题,我们对于所有的变量定义均使用了:=
操作符。最终,一个改进之后的完整的makefile文件如下所示:
MAIN:= hello
MODULES:= $(wildcard *.f90)
MODULES:= $(filter-out $(MAIN).f90, $(MODULES))
MODULES_OBJ:= $(patsubst %.f90, %.o, $(MODULES))
FC= gfortran
$(MAIN): $(MAIN).o $(MODULES_OBJ)
$(FC) -g -o $(MAIN) $(MAIN).o $(MODULES_OBJ)
$(MAIN).o: $(MAIN).f90 $(MODULES_OBJ)
$(FC) -g -c $(MAIN).f90
$(MODULES_OBJ): $(MODULES)
$(FC) -g -c $(MODULES)
clean:
del hello $(MODULES_OBJ)
当前版本的makefile文件与初版的makefile文件已经有了很大区别,但改进之后的makefile文件具有更大的通用性,因为我们只需要将MAIN
变量指定为主程序文件的文件名,并将所有程序文件和makefile文件放在相同的目录,即可自动完成编译过程。
有了上面的makefile文件,我们可以在VS Code中基于make工具完成Fortran项目的自动编译。我们只需将tasks.json文件重新修改为如下所示的形式:
{
"version": "2.0.0",
"tasks": [
{
"label": "compile",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
这样,VS Code将会以调用make工具的形式完成自动编译。如果要进行程序调试,则只需将launch.json文件重新修改为如下所示的形式:
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Fortran",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/hello.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "compile"
}
]
}
如果要基于本文的方法编译其他的一些的Fortran项目,可以在本文提供模板的基础上做适当修改,主要的修改位置有两处:(1)将makefile文件中变量MAIN
的取值更改为主程序文件名;(2)将launch.json文件中“program”的可执行程序的文件名更改为变量MAIN
的取值。