The tsunami simulator source code is now made of two source files: the main program (tsunami.f 90), which defines the top-level simulation loop, and the finite difference module (mod_diff.f 90). In all our work so far, we’ve always compiled our Fortran programs from single-source files. How do you compile a program that’s defined across multiple files? Easy! Compile and link all of them at once in a single line, or compile each file individually and link them all at the end. Let’s try the former first, as shown in the following listing.
Listing 4.5 Compiling and linking multiple source files in a single step
gfortran tsunami.f90 mod_diff.f90 -o tsunami ❶
tsunami.f90:11:6: ❷
use mod_diff, only: diff ❸
1
Fatal Error: Can't open module file ‘mod_diff.mod’ for reading at (1): No such file or directory
compilation terminated. ❹
❶ Command to compile and link both source files at once
❷ Compiler output showing file name, source line, and column where the error occurred
❸ Part of the source code where the error occurred
Ouch! This doesn’t seem to work, and the compiler suggests that the mod_diff.mod file couldn’t be found. We’ll get to the .mod file in a bit, but for now, let’s try something else. Perhaps we need to compile the module first before we compile the program. We can test this by flipping the order of the source files in the compile command:
gfortran mod_diff.f90 tsunami.f90 -o tsunami
Great, this compiles without error. Note that the compiler will emit only warning and error messages; if you see no output in the terminal, that indicates success.
So, what happened here? In the first attempt, I listed the main program source file (tsunami.f 90) first, and the module source file (mod_diff.f 90) second. The compiler took that and went from left to right. However, since the module source file came after the program file, the compiler couldn’t find the compiled module file that was needed by the main program. This means we have a dependency here–the main program depending on the module–which dictates the compilation order.
To confirm that it works as expected, run the program and compare the output with that of the previous tsunami version from the end of chapter 3. The output should be exactly the same between the two programs, because the only thing we did here is move the function to a module, and import it in the main program. The program only changes in terms of source code organization and is otherwise semantically equivalent. See listing 3.20 for how to exactly compare the output of two programs.
Recall that we have two ways we can compile Fortran programs that are defined across multiple source files. We just compiled the new tsunami simulator by listing all source files on a single line, and in a specific order. As you can imagine, this would be quite challenging if we had many source files to compile. That’s where the alternative approach of compiling files individually, and linking them all at the end, is a more sane approach. To compile files individually, use the -c compiler option, where “c” stands for compile only–do not link:
gfortran -c mod_diff.f90 ❶
gfortran -c tsunami.f90 ❷
gfortran *.o -o tsunami ❸
❶ Compiles the module file and outputs mod_diff.o and mod_diff.mod
❷ Compiles the main program file and outputs tsunami.o
❸ Links all object files into the executable tsunami
Like before, here the module file needs to be compiled before the source file that uses it. For any nontrivial project, the dependency tree and the order of compilation are typically handled by a build system, such as Make or CMake. Example Makefiles for the tsunami simulator are provided in the GitHub repository, and I encourage you to take a look at how they work. In addition to the compiled object file (.o), building a module also outputs a compiled module file (.mod). The compiler uses these files for optimization. They’re not portable across compilers, and sometimes even across compiler versions.
Exercise 2: Define the set_gaussian subroutine in a module
Like we did with the diff function and mod_diff module, let’s now define the set_gaussian subroutine in its own module. As this subroutine sets the initial conditions for the quantity that our simulator predicts, it’s appropriate to call this module mod_initial and place it in a file called mod_initial.f90. You’ve got all the ingredients for the solution. If implemented correctly, you’ll be able to import the set_ gaussian subroutine from mod_initial into the main program and use it there like we did before.
You can find the solution to this exercise in the “Answer key” section near the end of this chapter.