├── .editorconfig ├── .github └── workflows │ ├── ci_cmake.yml │ └── ci_meson.yml ├── CITATION.cff ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE.txt ├── README.md ├── VERSION ├── meson.build ├── src ├── CMakeLists.txt ├── basic.f90 ├── basic_sub.f90 ├── geo.f90 ├── geo_basic.f90 ├── hierarchy │ ├── CMakeLists.txt │ ├── child.f90 │ ├── grandchild.f90 │ ├── greatgrandchild.f90 │ ├── meson.build │ └── parent.f90 ├── math_const.f90 ├── meson.build ├── minimal.f90 ├── overspecified │ ├── CMakeLists.txt │ ├── child.f90 │ ├── meson.build │ └── parent.f90 ├── points.f90 ├── points_basic.f90 └── user.f90 └── test ├── CMakeLists.txt ├── hierarchy.f90 ├── meson.build ├── overspec.f90 └── repeated.f90 /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | 10 | # Matches multiple files with brace expansion notation 11 | # Set default charset 12 | [*.{js,py}] 13 | charset = utf-8 14 | 15 | [*.py] 16 | indent_style = space 17 | indent_size = 4 18 | 19 | [Makefile] 20 | indent_style = tab 21 | -------------------------------------------------------------------------------- /.github/workflows/ci_cmake.yml: -------------------------------------------------------------------------------- 1 | name: ci_cmake 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**/*.f90" 7 | - "**/CMakeLists.txt" 8 | - "**/*.cmake" 9 | - ".github/workflows/ci_cmake.yml" 10 | 11 | 12 | jobs: 13 | 14 | basic: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - run: cmake --workflow --preset default 24 | -------------------------------------------------------------------------------- /.github/workflows/ci_meson.yml: -------------------------------------------------------------------------------- 1 | name: ci_meson 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**/*.f90" 7 | - "**/meson.build" 8 | - ".github/workflows/ci_meson.yml" 9 | 10 | 11 | jobs: 12 | 13 | linux: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.x' 20 | 21 | - run: pip install meson ninja 22 | 23 | - run: meson setup build 24 | 25 | - run: meson compile -C build 26 | 27 | - run: meson test -C build -v 28 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | authors: 3 | - family-names: Hirsch 4 | given-names: Michael 5 | orcid: https://orcid.org/0000-0002-1637-6526 6 | title: fortran-submodule 7 | doi: 10.5281/zenodo.4699819 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13...3.27) 2 | 3 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 4 | message(FATAL_ERROR "cmake -B build instead of in source build") 5 | endif() 6 | 7 | file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/VERSION PROJECT_VERSION 8 | REGEX "^([0-9]+\.[0-9]+\.[0-9]+)" LIMIT_INPUT 16 LENGTH_MAXIMUM 16 LIMIT_COUNT 1) 9 | 10 | project(f2008submod 11 | LANGUAGES Fortran 12 | VERSION ${PROJECT_VERSION} 13 | HOMEPAGE_URL https://github.com/scivision/fortran-submodule 14 | DESCRIPTION "examples of Fortran 2008 submodule" 15 | ) 16 | 17 | enable_testing() 18 | 19 | set(CMAKE_Fortran_MODULE_DIRECTORY ${PROJECT_BINARY_DIR}/include) 20 | 21 | message(STATUS "CMake ${CMAKE_VERSION} Fortran module directory ${CMAKE_Fortran_MODULE_DIRECTORY}") 22 | 23 | add_subdirectory(src) 24 | add_subdirectory(test) 25 | 26 | file(GENERATE OUTPUT .gitignore CONTENT "*") 27 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | 4 | "configurePresets": [ 5 | { 6 | "name": "default", 7 | "binaryDir": "${sourceDir}/build", 8 | "cacheVariables": { 9 | "CMAKE_COMPILE_WARNING_AS_ERROR": true 10 | } 11 | } 12 | ], 13 | "buildPresets": [ 14 | { 15 | "name": "default", 16 | "configurePreset": "default" 17 | } 18 | ], 19 | "testPresets": [ 20 | { 21 | "name": "default", 22 | "configurePreset": "default", 23 | "output": { 24 | "outputOnFailure": true, 25 | "verbosity": "verbose" 26 | }, 27 | "execution": { 28 | "noTestsAction": "error", 29 | "scheduleRandom": true, 30 | "stopOnFailure": false, 31 | "timeout": 60 32 | } 33 | } 34 | ], 35 | "workflowPresets": [ 36 | { 37 | "name": "default", 38 | "steps": [ 39 | { 40 | "type": "configure", 41 | "name": "default" 42 | }, 43 | { 44 | "type": "build", 45 | "name": "default" 46 | }, 47 | { 48 | "type": "test", 49 | "name": "default" 50 | } 51 | ] 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Hirsch, Ph.D. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fortran Submodule 2 | 3 | [![DOI](https://zenodo.org/badge/163894637.svg)](https://zenodo.org/badge/latestdoi/163894637) 4 | [![Actions Status](https://github.com/scivision/fortran-submodule/workflows/ci_cmake/badge.svg)](https://github.com/scivision/fortran-submodule/actions) 5 | [![Actions Status](https://github.com/scivision/fortran-submodule/workflows/ci_meson/badge.svg)](https://github.com/scivision/fortran-submodule/actions) 6 | 7 | Examples of modern Fortran 2008 `submodule`, using Meson or CMake. 8 | Fortran submodules are a unique concept distinct from C++. 9 | Fortran submodules allow easy switching in and out of features and extremely fast recompilation of large programs perhaps 100 times or more faster. 10 | For example a program's file IO could use raw binary, HDF5, or NetCDF switched using Fortran submodule. 11 | A simulation could have different proprietary or open source modules switched in and out via Fortran submodule. 12 | 13 | In general, Fortran compilers have good submodule support. 14 | This includes gfortran, Intel ifort, nagfor, flang, PGI, IBM, Cray, etc. 15 | 16 | Meson and CMake are two build systems that support Fortran submodule. 17 | Look inside each example to understand basic use of Fortran submodule. 18 | 19 | CMake builds and tests by: 20 | 21 | ```sh 22 | cmake -B build 23 | cmake --build build 24 | ``` 25 | 26 | or use Meson: 27 | 28 | ```sh 29 | meson build 30 | 31 | meson test -C build 32 | ``` 33 | 34 | ## Notes 35 | 36 | * Steve Lionel (Intel) submodule [discussion](https://software.intel.com/en-us/blogs/2015/07/07/doctor-fortran-in-we-all-live-in-a-yellow-submodule) 37 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.3.2 2 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('Fortran Submodule', 'fortran', 2 | version : files('VERSION'), 3 | meson_version : '>=0.57.0' 4 | ) 5 | 6 | subdir('src') 7 | subdir('test') 8 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_property(DIRECTORY PROPERTY LABELS submodule) 2 | 3 | add_subdirectory(hierarchy) 4 | add_subdirectory(overspecified) 5 | 6 | add_executable(new points.f90 geo.f90) 7 | add_test(NAME PointsNew COMMAND new) 8 | 9 | add_executable(minimal minimal.f90) 10 | add_test(NAME SameFileMinimal COMMAND minimal) 11 | 12 | add_library(math_const math_const.f90) # for demoing external projects 13 | 14 | add_executable(basic basic.f90 basic_sub.f90) 15 | add_test(NAME TwoFiles COMMAND basic) 16 | 17 | add_executable(points_basic points_basic.f90 geo_basic.f90) 18 | add_test(NAME BasicPoints COMMAND points_basic) 19 | -------------------------------------------------------------------------------- /src/basic.f90: -------------------------------------------------------------------------------- 1 | module mother 2 | 3 | implicit none 4 | real, parameter :: pi = 4.*atan(1.) 5 | real :: tau 6 | 7 | interface 8 | module real elemental function pi2tau(pi) 9 | real, intent(in) :: pi 10 | end function pi2tau 11 | end interface 12 | 13 | contains 14 | 15 | end module mother 16 | 17 | 18 | program hier1 19 | use mother 20 | implicit none 21 | 22 | tau = pi2tau(pi) 23 | 24 | print *,'pi=',pi, 'tau=', tau 25 | 26 | end program 27 | -------------------------------------------------------------------------------- /src/basic_sub.f90: -------------------------------------------------------------------------------- 1 | submodule (mother) daughter 2 | 3 | implicit none 4 | 5 | contains 6 | 7 | module procedure pi2tau 8 | pi2tau = 2*pi 9 | end procedure pi2tau 10 | 11 | end submodule daughter 12 | -------------------------------------------------------------------------------- /src/geo.f90: -------------------------------------------------------------------------------- 1 | submodule (points) geo 2 | 3 | implicit none 4 | 5 | contains 6 | 7 | module procedure point_dist 8 | point_dist = hypot(a%x - b%x, a%y - b%y) 9 | end procedure point_dist 10 | 11 | end submodule geo 12 | -------------------------------------------------------------------------------- /src/geo_basic.f90: -------------------------------------------------------------------------------- 1 | submodule (points_basic) geo 2 | 3 | implicit none 4 | 5 | contains 6 | 7 | module procedure point_dist 8 | point_dist = hypot(ax - bx, ay - by) 9 | end procedure point_dist 10 | 11 | end submodule geo 12 | -------------------------------------------------------------------------------- /src/hierarchy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(obj_hier OBJECT parent.f90 grandchild.f90 child.f90 greatgrandchild.f90) 2 | target_include_directories(obj_hier INTERFACE ${PROJECT_BINARY_DIR}/include) 3 | -------------------------------------------------------------------------------- /src/hierarchy/child.f90: -------------------------------------------------------------------------------- 1 | submodule (parent_hier) child_hier 2 | 3 | implicit none 4 | real, parameter :: pi=4.*atan(1.) 5 | 6 | contains 7 | 8 | module procedure hi1 9 | 10 | print *,"I'm a child" 11 | 12 | end procedure hi1 13 | 14 | end submodule child_hier 15 | -------------------------------------------------------------------------------- /src/hierarchy/grandchild.f90: -------------------------------------------------------------------------------- 1 | submodule (parent_hier:child_hier) grandchild_hier 2 | 3 | implicit none 4 | 5 | contains 6 | 7 | module procedure hi2 8 | 9 | print *,"I'm a grandchild, pi=",pi 10 | 11 | end procedure hi2 12 | 13 | 14 | end submodule grandchild_hier 15 | -------------------------------------------------------------------------------- /src/hierarchy/greatgrandchild.f90: -------------------------------------------------------------------------------- 1 | submodule (parent_hier:child_hier) greatgrandchild_hier 2 | 3 | implicit none 4 | 5 | contains 6 | 7 | module procedure hi3 8 | 9 | print *,"I'm a greatgrandchild, tau=",2*pi 10 | 11 | end procedure hi3 12 | 13 | end submodule greatgrandchild_hier 14 | -------------------------------------------------------------------------------- /src/hierarchy/meson.build: -------------------------------------------------------------------------------- 1 | hier = library('hierarchy', 2 | sources: ['parent.f90', 'child.f90', 'greatgrandchild.f90', 'grandchild.f90'] 3 | ) 4 | -------------------------------------------------------------------------------- /src/hierarchy/parent.f90: -------------------------------------------------------------------------------- 1 | module parent_hier 2 | 3 | implicit none 4 | 5 | interface ! this can be in ancestor or parent 6 | module subroutine hi1() 7 | end subroutine hi1 8 | 9 | module subroutine hi2() 10 | end subroutine hi2 11 | 12 | module subroutine hi3() 13 | end subroutine hi3 14 | end interface 15 | 16 | end module parent_hier 17 | -------------------------------------------------------------------------------- /src/math_const.f90: -------------------------------------------------------------------------------- 1 | module constants 2 | 3 | implicit none 4 | 5 | interface 6 | module real function get_pi() result(pi) 7 | end function get_pi 8 | end interface 9 | end module constants 10 | 11 | submodule (constants) pi_getter 12 | implicit none 13 | contains 14 | 15 | module procedure get_pi 16 | pi = 4*atan(1.) 17 | end procedure get_pi 18 | 19 | end submodule pi_getter 20 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | subdir('hierarchy') 2 | subdir('overspecified') 3 | 4 | basic = executable('basic', 5 | sources: ['basic.f90', 'basic_sub.f90'] 6 | ) 7 | test('Basic Submodule 2 files', basic) 8 | 9 | points_basic = executable('points_basic', 10 | sources: ['points_basic.f90', 'geo_basic.f90'] 11 | ) 12 | test('Points Basic', points_basic) 13 | 14 | points = executable('points', 15 | sources: ['points.f90', 'geo.f90'] 16 | ) 17 | test('Points', points) 18 | 19 | minimal = executable('minimal', 'minimal.f90') 20 | test('Minimal same-file submodule', minimal) 21 | 22 | ext_lib = library('math_const', 'math_const.f90') # for subproject demo 23 | -------------------------------------------------------------------------------- /src/minimal.f90: -------------------------------------------------------------------------------- 1 | module b 2 | 3 | implicit none 4 | 5 | interface 6 | module subroutine d 7 | end subroutine d 8 | 9 | module subroutine samename 10 | end subroutine samename 11 | end interface 12 | 13 | end module b 14 | 15 | submodule (b) c 16 | implicit none 17 | contains 18 | module procedure d 19 | print *,'hi from module b, submodule d' 20 | end procedure d 21 | end submodule c 22 | 23 | submodule (b) bs 24 | implicit none 25 | contains 26 | module procedure samename 27 | print *,'hi from module b, submodule bs' 28 | end procedure samename 29 | end submodule bs 30 | 31 | program a 32 | use b 33 | implicit none 34 | 35 | call d 36 | call samename 37 | end program 38 | -------------------------------------------------------------------------------- /src/overspecified/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(overspec_obj OBJECT parent.f90 child.f90) 2 | -------------------------------------------------------------------------------- /src/overspecified/child.f90: -------------------------------------------------------------------------------- 1 | submodule (parent1) child1 2 | 3 | implicit none 4 | 5 | contains 6 | 7 | module procedure negate 8 | b = -a 9 | end procedure negate 10 | 11 | end submodule child1 12 | -------------------------------------------------------------------------------- /src/overspecified/meson.build: -------------------------------------------------------------------------------- 1 | overspec = library('overspec', 2 | sources: ['parent.f90', 'child.f90'] 3 | ) 4 | -------------------------------------------------------------------------------- /src/overspecified/parent.f90: -------------------------------------------------------------------------------- 1 | module parent1 2 | 3 | implicit none 4 | 5 | interface 6 | module pure subroutine negate(a, b) 7 | integer, intent(in) :: a 8 | integer, intent(out) :: b 9 | end subroutine negate 10 | end interface 11 | 12 | end module parent1 13 | -------------------------------------------------------------------------------- /src/points.f90: -------------------------------------------------------------------------------- 1 | module points 2 | 3 | implicit none 4 | 5 | type :: point 6 | real :: x, y 7 | end type point 8 | 9 | interface 10 | module real function point_dist(a, b) 11 | class(point), intent(in) :: a, b 12 | end function point_dist 13 | end interface 14 | 15 | end module points 16 | 17 | program tests_points 18 | use, intrinsic :: iso_fortran_env, only: stderr=>error_unit 19 | use points, only: point, point_dist 20 | implicit none 21 | 22 | type(point) :: a, b 23 | real :: dist 24 | 25 | a = point(1.,1.) 26 | b = point(3.,5.) 27 | 28 | dist = point_dist(a,b) 29 | 30 | print *,'distance', dist 31 | 32 | if (abs(dist-4.47213602) >= 1e-5) error stop 'excessive error in computation' 33 | 34 | end program 35 | -------------------------------------------------------------------------------- /src/points_basic.f90: -------------------------------------------------------------------------------- 1 | module points_basic 2 | 3 | implicit none 4 | 5 | interface 6 | module real function point_dist(ax, ay, bx, by) 7 | real, intent(in) :: ax, ay, bx, by 8 | end function point_dist 9 | end interface 10 | 11 | end module points_basic 12 | 13 | 14 | program test_points_basic 15 | use, intrinsic :: iso_fortran_env, only: stderr=>error_unit 16 | use points_basic, only: point_dist 17 | implicit none 18 | 19 | real :: dist 20 | 21 | dist = point_dist(1.,1., 3.,5.) 22 | 23 | print *,'distance', dist 24 | 25 | if (abs(dist - 4.47213602) >= 1e-5) error stop 'excessive error in computation' 26 | 27 | end program 28 | -------------------------------------------------------------------------------- /src/user.f90: -------------------------------------------------------------------------------- 1 | module points 2 | 3 | implicit none 4 | 5 | type :: point 6 | real :: x, y 7 | end type point 8 | 9 | interface 10 | module function point_dist(a, b) result(distance) 11 | class(point), intent(in) :: a, b 12 | real :: distance 13 | end function point_dist 14 | end interface 15 | end module points 16 | 17 | 18 | submodule (points) geo 19 | implicit none 20 | 21 | contains 22 | 23 | module procedure point_dist 24 | distance = hypot(a%x - b%x, a%y - b%y) 25 | end procedure point_dist 26 | end submodule geo 27 | 28 | 29 | program submod_demo 30 | use, intrinsic :: iso_fortran_env, only: stderr=>error_unit 31 | use points, only: point, point_dist 32 | implicit none 33 | 34 | type(point) :: a, b 35 | real :: dist 36 | 37 | a = point(1.,1.) 38 | b = point(3.,5.) 39 | 40 | dist = point_dist(a,b) 41 | 42 | print *,'distance', dist 43 | 44 | if (abs(dist-4.47213602) >= 1e-5) then 45 | write(stderr,*) 'excessive error in computation' 46 | stop 1 47 | endif 48 | 49 | 50 | end program 51 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(hierarchy hierarchy.f90) 2 | target_link_libraries(hierarchy PRIVATE obj_hier) 3 | add_test(NAME Hierarchal COMMAND hierarchy) 4 | 5 | add_executable(overspec overspec.f90) 6 | target_link_libraries(overspec PRIVATE overspec_obj) 7 | add_test(NAME OverSpecified COMMAND overspec) 8 | 9 | if(CMAKE_GENERATOR MATCHES "Makefiles$") 10 | message(STATUS "Please use Ninja for the 'repeated' example 11 | cmake -G Ninja") 12 | set(broken_gen 1) 13 | else() 14 | set(broken_gen 0) 15 | endif() 16 | 17 | add_executable(repeated repeated.f90) 18 | set_property(TARGET repeated PROPERTY EXCLUDE_FROM_ALL ${broken_gen}) 19 | add_test(NAME Repeated COMMAND repeated) 20 | set_property(TEST Repeated PROPERTY DISABLED ${broken_gen}) 21 | -------------------------------------------------------------------------------- /test/hierarchy.f90: -------------------------------------------------------------------------------- 1 | program hierarchy 2 | 3 | use, intrinsic:: iso_fortran_env, only : compiler_version 4 | use parent_hier, only: hi1, hi2, hi3 5 | 6 | implicit none 7 | 8 | print *,compiler_version() 9 | 10 | call hi1() 11 | 12 | call hi2() 13 | 14 | call hi3() 15 | 16 | end program 17 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | hier_exe = executable('hierarchy', 2 | sources: 'hierarchy.f90', 3 | link_with: hier 4 | ) 5 | test('Hierarchal', hier_exe) 6 | 7 | overspec_exe = executable('overspec', 8 | sources: 'overspec.f90', 9 | link_with: overspec 10 | ) 11 | test('Overspecified', overspec_exe) 12 | 13 | repeat = executable('repeated', 'repeated.f90') 14 | test('Repeated interface in submodule', repeat) 15 | -------------------------------------------------------------------------------- /test/overspec.f90: -------------------------------------------------------------------------------- 1 | program main1 2 | 3 | use parent1, only : negate 4 | 5 | implicit none 6 | 7 | integer :: b 8 | 9 | call negate(1, b) 10 | 11 | if (b /= -1) error stop 'should have gotten -1 = -1' 12 | 13 | end program 14 | -------------------------------------------------------------------------------- /test/repeated.f90: -------------------------------------------------------------------------------- 1 | !! Example that defines the procedure type in the submodule instead of the interface. 2 | !! https://gitlab.kitware.com/cmake/cmake/issues/18427 3 | 4 | module parent 5 | 6 | implicit none 7 | 8 | interface 9 | module integer function intf() 10 | end function intf 11 | 12 | module pure integer function pintf() 13 | end function pintf 14 | end interface 15 | 16 | end module parent 17 | 18 | 19 | submodule (parent) child 20 | 21 | implicit none 22 | 23 | contains 24 | 25 | module integer function intf() 26 | intf = 2 27 | end function intf 28 | 29 | module pure integer function pintf() 30 | pintf = 3 31 | end function pintf 32 | 33 | end submodule child 34 | 35 | 36 | program cmake_check 37 | use parent, only : intf, pintf 38 | implicit none 39 | 40 | if (intf() + 1 /= pintf()) error stop 'incorrect integer values' 41 | 42 | end program 43 | --------------------------------------------------------------------------------