From e485423beb696e3643ae43ee36721fa84414cc30 Mon Sep 17 00:00:00 2001 From: Brad Richardson Date: Wed, 29 Apr 2020 20:03:16 -0700 Subject: Add sections to PACKAGING.md on explicit fpm.toml and tests --- PACKAGING.md | 198 +++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 145 insertions(+), 53 deletions(-) diff --git a/PACKAGING.md b/PACKAGING.md index 9c3457e..1dc580f 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -1,7 +1,7 @@ # Preparing your package for FPM This document describes how you need to organize your application or library -for it to successfully build with the Fortran Package Manager (FPM). +for it to successfully build with the Fortran Package Manager (FPM). * [What kind of package can FPM build?](#what-kind-of-package-can-fpm-build) * [Example package layouts](#example-package-layouts) @@ -10,6 +10,8 @@ for it to successfully build with the Fortran Package Manager (FPM). - [Multi-module library](#multi-module-library) - [Application and library](#application-and-library) - [Multi-level library](#multi-level-library) + - [Be more explicit](#be-more-explicit) + - [Add some tests](#add-some-tests) ## What kind of package can FPM build? @@ -29,8 +31,8 @@ You can use them to model the layout of your own package. ### Single program -Let's start with the simplest package imaginable--a single program without -dependencies or modules. +Let's start with the simplest package imaginable--a single program without +dependencies or modules. Here's what the layout of the top-level directory looks like: ``` @@ -64,21 +66,20 @@ license = "MIT" author = "Jane Programmer" maintainer = "jane@example.com" copyright = "2020 Jane Programmer" -dependencies = [] compiler = "gfortran" ``` -The preamble includes some metadata, such as `license`, `author`, and similar, +The preamble includes some metadata, such as `license`, `author`, and similar, that you may have seen in other package manager configuration files. The one option that matters here right now is: ```toml name = "hello" -``` +``` -This line specifies the name of your package, which determines the name of +This line specifies the name of your package, which determines the name of the executable file of your program. -In this example, our program executable, once built, will be called `hello`. +In this example, our program executable, once built, will be called `hello`. Let's now build this program using FPM: @@ -89,8 +90,8 @@ $ fpm build ``` On the first line, we ran `fpm build` to compile and link the application. -The latter two lines are emitted by FPM, and indicate which command was -executed at each build step (`gfortran`), and which files have been output +The latter two lines are emitted by FPM, and indicate which command was +executed at each build step (`gfortran`), and which files have been output by it: object file `main.o`, and executable `hello`. We can now run the app with `fpm run`: @@ -100,8 +101,8 @@ $ fpm run Hello, World! ``` -If your application needs to use a module internally, but you don't intent -to build it as a library to be used in other projects, you can include the +If your application needs to use a module internally, but you don't intend +to build it as a library to be used in other projects, you can include the module in your program source file as well. For example: @@ -126,21 +127,21 @@ $ fpm run # gfortran (for build/debug/app/main.o) # gfortran (for build/debug/app/hello) Hello, World! - pi = 3.14159274 + pi = 3.14159274 ``` Notice that you can run `fpm run`, and if the package hasn't been built yet, -`fpm build` will run automatically for you. +`fpm build` will run automatically for you. This is true if the source files have been updated since the last build. Thus, if you want to run your application, you can skip the `fpm build` step, and go straight to `fpm run`. -Although we have named our program `hello`, which is the same name as the +Although we have named our program `hello`, which is the same name as the package name in `fpm.toml`, you can name it anything you want as long as it's permitted by the language. In this last example, our source file defined a `math_constants` module -inside the same source file as the main program. +inside the same source file as the main program. Let's see how we can define an FPM package that makes this module available as a library. @@ -155,11 +156,11 @@ The package layout for this example looks like this: └── math_constants.f90 ``` -In this example we'll build a simple math constants library that exports +In this example we'll build a simple math constants library that exports the number pi as a parameter: ```fortran -$ cat src/math_constants.f90 +$ cat src/math_constants.f90 module math_constants real, parameter :: pi = 4 * atan(1.) end module math_constants @@ -176,9 +177,9 @@ $ fpm build ar: creating build/debug/library/math_constants.a ``` -Based on the output of `fpm build`, FPM first ran `gfortran` to emit the +Based on the output of `fpm build`, FPM first ran `gfortran` to emit the binary object (`math_constants.o`) and module (`math_constants.mod`) files. -Then it ran `ar` to create a static library archive `math_constants.a`. +Then it ran `ar` to create a static library archive `math_constants.a`. `build/debug/library` is thus both your include and library path, should you want to compile and link an exteranl program with this library. @@ -187,29 +188,29 @@ For modules in the top-level (`src`) directory, FPM requires that: * The module has the same name as the source file. * There is only one module per file. -These two requirements simplify the build process for FPM. -As Fortran compilers emit module files (`.mod`) with the same name as the -module itself (but not the source file, `.f90`), naming the module the same as +These two requirements simplify the build process for FPM. +As Fortran compilers emit module files (`.mod`) with the same name as the +module itself (but not the source file, `.f90`), naming the module the same as the source file allows FPM to: -* Uniquely and exactly map a source file (`.f90`) to its object (`.o`) and +* Uniquely and exactly map a source file (`.f90`) to its object (`.o`) and module (`.mod`) files. * Avoid conflicts with modules of the same name that could appear in dependency packages (more on this in a bit). -Since this is a library without executable programs, `fpm run` here does +Since this is a library without executable programs, `fpm run` here does nothing. In this example, our library is made of only one module. However, most real-world libraries are likely to use multiple modules. -Let's see how you can package your multi-module library. +Let's see how you can package your multi-module library. ### Multi-module library In this example, we'll use another module to define a 64-bit real kind -parameter and make it available in `math_constants` to define `pi` with +parameter and make it available in `math_constants` to define `pi` with higher precision. -To make this exercise worthwhile, we'll define another math constant, +To make this exercise worthwhile, we'll define another math constant, Euler's number. Our package layout looks like this: @@ -225,14 +226,14 @@ Our package layout looks like this: and our source file contents are: ```fortran -$ cat src/math_constants.f90 +$ cat src/math_constants.f90 module math_constants use type_kinds, only: rk real(rk), parameter :: pi = 4 * atan(1._rk) real(rk), parameter :: e = exp(1._rk) end module math_constants -$ cat src/type_kinds.f90 +$ cat src/type_kinds.f90 module type_kinds use iso_fortran_env, only: real64 integer, parameter :: rk = real64 @@ -241,8 +242,8 @@ end module type_kinds and there are no changes to our `fpm.toml` relative to previous examples. -Like before, notice that the module `type_kinds` is name exactly as the -source file that contains it. +Like before, notice that the module `type_kinds` is name exactly as the +source file that contains it. This is important. By now you know how to build the package: @@ -265,7 +266,7 @@ math_constants.a math_constants.mod math_constants.o type_kinds.mod type_kin and the static library includes all the object files: ``` -$ nm build/debug/library/math_constants.a +$ nm build/debug/library/math_constants.a math_constants.o: @@ -279,7 +280,7 @@ The takeaways from this example are that: ### Application and library -Let's now combine the two previous examples into one: +Let's now combine the two previous examples into one: We'll build the math constants library _and_ an executable program that uses it. We'll use this program as a demo, and to verify that defining higher-precision @@ -300,7 +301,7 @@ Here's the package layout for your application + library package: Our `fpm.toml` remains unchanged and our executable program source file is: ```fortran -$ cat app/main.f90 +$ cat app/main.f90 program demo use math_constants, only: e, pi print *, 'math_constants library demo' @@ -320,8 +321,8 @@ ar: creating build/debug/library/math_constants.a # gfortran (for build/debug/app/main.o) # gfortran (for build/debug/app/math_constants) math_constants library demo - pi = 3.1415926535897931 - e = 2.7182818284590451 + pi = 3.1415926535897931 + e = 2.7182818284590451 ``` The FPM build + run process works as expected, and our program correctly outputs @@ -334,8 +335,8 @@ So far we covered how FPM builds: * A multi-module library * A program and a library -However, all our modules so far have been organized in the top level source -directory. +However, all our modules so far have been organized in the top level source +directory. More complex libraries may organize their modules in subdirectories. Let's see how we can build this with FPM. @@ -357,26 +358,26 @@ two of which are defined in a subdirectory: └── type_kinds.f90 ``` -First, `fpm.toml` and `src/type_kinds.f90` remain unchanged relative to the +First, `fpm.toml` and `src/type_kinds.f90` remain unchanged relative to the previous example. The rest of the source files are: ```fortran -$ cat src/math_constants.f90 +$ cat src/math_constants.f90 module math_constants use math_constants_fundamental, only: e, pi use math_constants_derived, only: half_pi, two_pi end module math_constants -$ cat src/math_constants/fundamental.f90 +$ cat src/math_constants/fundamental.f90 module math_constants_fundamental use type_kinds, only: rk real(rk), parameter :: pi = 4 * atan(1._rk) real(rk), parameter :: e = exp(1._rk) end module math_constants_fundamental -$ cat src/math_constants/derived.f90 +$ cat src/math_constants/derived.f90 module math_constants_derived use math_constants_fundamental, only: pi use type_kinds, only: rk @@ -384,7 +385,7 @@ module math_constants_derived real(rk), parameter :: half_pi = pi / 2 end module math_constants_derived -$ cat app/main.f90 +$ cat app/main.f90 program demo use math_constants, only: e, pi, half_pi, two_pi print *, 'math_constants library demo' @@ -415,9 +416,9 @@ ar: creating build/debug/library/math_constants.a # gfortran (for build/debug/app/main.o) # gfortran (for build/debug/app/math_constants) math_constants library demo - pi = 3.1415926535897931 - 2*pi = 6.2831853071795862 - pi/2 = 1.5707963267948966 + pi = 3.1415926535897931 + 2*pi = 6.2831853071795862 + pi/2 = 1.5707963267948966 e = 2.7182818284590451 ``` @@ -428,11 +429,11 @@ Recall from an earlier example that FPM required the modules in the top-level This is why `src/math_constants.f90` defines `module math_constants`. For modules defined in subdirectories, there's an additional requirement: -module name must contain the path components of the directory that its -source file is in. -In our case, `src/math_constants/fundamental.f90` defines +module name must contain the path components of the directory that its +source file is in. +In our case, `src/math_constants/fundamental.f90` defines the `math_constants_fundamental` module. -Likewise, `src/math_constants/derived.f90` defines +Likewise, `src/math_constants/derived.f90` defines the `math_constants_derived` module. This rule applies generally to any number of nested directories and modules. @@ -441,5 +442,96 @@ For example, `src/a/b/c/d.f90` must define a module called `a_b_c_d`. Takeaways from this example are that: * You can place your module source files in any levels of subdirectories inside `src`. -* The module name must include the path components and the source file name--for example, -`src/a/b/c/d.f90` must define a module called `a_b_c_d`. +* The module name must include the path components and the source file name--for example, +`src/a/b/c/d.f90` must define a module called `a_b_c_d`. + +### Be more explicit + +So far we've let FPM use its defaults to determine the layout of our package. +It determined where our library sources would live, what the name of the +executable will be, and some other things. But we can be more explicit about it, +and make some changes to those things. + +Let's look at what the `fpm.toml` file from our last example would look like if +we specified everything. + +```toml +name = "math_constants" +version = "0.1.0" +license = "MIT" +author = "Jane Programmer" +maintainer = "jane@example.com" +copyright = "2020 Jane Programmer" +compiler = "gfortran" + +[library] +source-dir="src" + +[[executable]] +name="math_constants" +source-dir="app" +main="main.f90" +``` + +You can see that by making these explicit in the `fpm.toml` we are able to +change many of the settings that FPM used by default. We can change the folders +where our sources are stored, we can change the name of our executable, and we +can change the name of the file our program is defined in. + +### Add some tests + +FPM also provides support for unit testing. By default, FPM looks for a program +in `test/main.f90` which it will compile and execute with the command `fpm test`. +The tests are treated pretty much exactly like the executables. Let's define +one explicitly in our `fpm.toml` file. We'll make sure that our definition of +`pi` satisfies the property `sin(pi) == 0.0`. Here's the `fpm.toml` file, + +```toml +name = "math_constants" +version = "0.1.0" +license = "MIT" +author = "Jane Programmer" +maintainer = "jane@example.com" +copyright = "2020 Jane Programmer" +compiler = "gfortran" + +[library] +source-dir="src" + +[[executable]] +name="math_constants" +source-dir="app" +main="main.f90" + +[[test]] +name="runTests" +source-dir="test" +main="main.f90" +``` + +where the contents of the `main.f90` file are + +```fortran +program tests + use math_constants, only: pi + + print *, "sin(pi) = ", sin(pi) +end program tests +``` + +With this setup, we can run our tests. + +``` +$ fpm test +# gfortran (for build/debug/library/type_kinds.o build/debug/library/type_kinds.mod) +# gfortran (for build/debug/library/math_constants_fundamental.o build/debug/library/math_constants_fundamental.mod) +# gfortran (for build/debug/library/math_constants_derived.o build/debug/library/math_constants_derived.mod) +# gfortran (for build/debug/library/math_constants.o build/debug/library/math_constants.mod) +# ar (for build/debug/library/math_constants.a) +ar: creating build/debug/library/math_constants.a +# gfortran (for build/debug/app/main.o) +# gfortran (for build/debug/app/math_constants) +# gfortran (for build/debug/test/main.o) +# gfortran (for build/debug/test/runTests) + sin(pi) = 1.2246467991473532E-016 +``` -- cgit v1.2.3