├── .archive └── netcdf.cmake ├── .codespellrc ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ ├── ci_fpm.yml │ ├── ci_windows.yml │ ├── oneapi-linux.yml │ ├── oneapi_cache_exclude_linux.sh │ └── oneapi_setup_apt_repo_linux.sh ├── API.md ├── CITATION.cff ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── ci.cmake ├── cmake ├── FindHDF5.cmake ├── FindNetCDF.cmake ├── Modules │ └── CodeCoverage.cmake ├── compilers.cmake ├── config.cmake.in ├── hdf5.cmake ├── install.cmake ├── libraries.json ├── nc4fortran.cmake ├── netcdf-c.cmake ├── netcdf.cmake ├── pkgconf.cmake ├── pkgconf.pc.in └── zlib.cmake ├── codemeta.json ├── example ├── CMakeLists.txt ├── Readme.md ├── example1.f90 ├── example2.f90 └── fortran_interface.f90 ├── ford.md ├── fpm.toml ├── options.cmake ├── scripts └── CMakeLists.txt ├── src ├── CMakeLists.txt ├── attributes.f90 ├── interface.f90 ├── read.f90 ├── read_scalar.f90 ├── reader.f90 ├── reader.inc ├── utils.f90 ├── write.f90 ├── write_scalar.f90 ├── writer.f90 └── writer.inc └── test ├── CMakeLists.txt ├── test_array.f90 ├── test_attributes.f90 ├── test_deflate_props.f90 ├── test_deflate_read.f90 ├── test_deflate_write.f90 ├── test_destructor.f90 ├── test_error.f90 ├── test_exist.f90 ├── test_fill.f90 ├── test_minimal.f90 ├── test_scalar.f90 ├── test_shape.f90 ├── test_string.f90 └── test_version.f90 /.archive/netcdf.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | include(GNUInstallDirs) 3 | 4 | # need HDF5, NetCDF-C and NetCDF-Fortran 5 | # due to limitations of NetCDF-C 4.7.4 and NetCDF-Fortran 4.5.3, as per their docs, 6 | # we MUST use shared libraries or they don't archive/link properly. 7 | 8 | find_package(HDF5 COMPONENTS C Fortran) 9 | if(HDF5_FOUND) 10 | add_custom_target(HDF5) 11 | else() 12 | include(${CMAKE_CURRENT_LIST_DIR}/hdf5.cmake) 13 | endif() 14 | 15 | cmake_path(SET NetCDF_C_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include) 16 | file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}/include) 17 | 18 | if(WIN32) 19 | cmake_path(SET NetCDF_C_LIBRARIES ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdf${CMAKE_SHARED_LIBRARY_SUFFIX}) 20 | cmake_path(SET NetCDF_Fortran_LIBRARIES ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdff${CMAKE_SHARED_LIBRARY_SUFFIX}) 21 | else() 22 | cmake_path(SET NetCDF_C_LIBRARIES ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdf${CMAKE_SHARED_LIBRARY_SUFFIX}) 23 | cmake_path(SET NetCDF_Fortran_LIBRARIES ${CMAKE_INSTALL_PREFIX}//${CMAKE_INSTALL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdff${CMAKE_SHARED_LIBRARY_SUFFIX}) 24 | endif() 25 | # --- NetCDF-C 26 | 27 | set(netcdf_c_cmake_args 28 | -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} 29 | -DCMAKE_MODULE_PATH:PATH=${CMAKE_MODULE_PATH} 30 | -DCMAKE_PREFIX_PATH:PATH=${CMAKE_INSTALL_PREFIX} 31 | -DCMAKE_BUILD_TYPE:STRING=Release 32 | -DBUILD_SHARED_LIBS:BOOL=ON 33 | -DENABLE_PARALLEL4:BOOL=OFF 34 | -DENABLE_PNETCDF:BOOL=OFF 35 | -DBUILD_UTILITIES:BOOL=OFF 36 | -DENABLE_TESTS:BOOL=off 37 | -DBUILD_TESTING:BOOL=OFF 38 | -DENABLE_HDF4:BOOL=OFF 39 | -DUSE_DAP:BOOL=off 40 | -DENABLE_DAP:BOOL=OFF 41 | -DENABLE_DAP2:BOOL=OFF 42 | -DENABLE_DAP4:BOOL=OFF 43 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 44 | -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON 45 | ) 46 | # BUILD_SHARED_LIBS=on for netcdf-fortran symbol finding bug 47 | 48 | string(JSON netcdfC_url GET ${json} netcdfC url) 49 | string(JSON netcdfC_tag GET ${json} netcdfC tag) 50 | 51 | ExternalProject_Add(NETCDF_C 52 | GIT_REPOSITORY ${netcdfC_url} 53 | GIT_TAG ${netcdfC_tag} 54 | GIT_SHALLOW true 55 | CONFIGURE_HANDLED_BY_BUILD TRUE 56 | CMAKE_ARGS ${netcdf_c_cmake_args} 57 | BUILD_BYPRODUCTS ${NetCDF_C_LIBRARIES} 58 | DEPENDS HDF5::HDF5 59 | ) 60 | 61 | # --- imported target 62 | 63 | file(MAKE_DIRECTORY ${NetCDF_C_INCLUDE_DIRS}) 64 | # avoid race condition 65 | 66 | # this GLOBAL is required to be visible via other project's FetchContent 67 | add_library(NetCDF::NetCDF_C INTERFACE IMPORTED GLOBAL) 68 | target_include_directories(NetCDF::NetCDF_C INTERFACE ${NetCDF_C_INCLUDE_DIRS}) 69 | target_link_libraries(NetCDF::NetCDF_C INTERFACE ${NetCDF_C_LIBRARIES}) 70 | 71 | add_dependencies(NetCDF::NetCDF_C NETCDF_C) 72 | 73 | # -- external deps 74 | target_link_libraries(NetCDF::NetCDF_C INTERFACE HDF5::HDF5) 75 | 76 | # --- NetCDF-Fortran 77 | 78 | cmake_path(SET NetCDF_Fortran_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include) 79 | 80 | set(netcdf_fortran_cmake_args 81 | -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} 82 | -DCMAKE_MODULE_PATH:PATH=${CMAKE_MODULE_PATH} 83 | -DCMAKE_PREFIX_PATH:PATH=${CMAKE_INSTALL_PREFIX} 84 | -DnetCDF_LIBRARIES:FILEPATH=${NetCDF_C_LIBRARIES} 85 | -DnetCDF_INCLUDE_DIR:PATH=${NetCDF_C_INCLUDE_DIRS} 86 | -DCMAKE_BUILD_TYPE:STRING=Release 87 | -DBUILD_SHARED_LIBS:BOOL=ON 88 | -DENABLE_TESTS:BOOL=OFF 89 | -DBUILD_EXAMPLES:BOOL=OFF 90 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 91 | -DCMAKE_Fortran_COMPILER:FILEPATH=${CMAKE_Fortran_COMPILER} 92 | -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON 93 | ) 94 | 95 | string(JSON netcdfFortran_url GET ${json} netcdfFortran url) 96 | string(JSON netcdfFortran_tag GET ${json} netcdfFortran tag) 97 | 98 | ExternalProject_Add(NETCDF_FORTRAN 99 | GIT_REPOSITORY ${netcdfFortran_url} 100 | GIT_TAG ${netcdfFortran_tag} 101 | GIT_SHALLOW true 102 | CONFIGURE_HANDLED_BY_BUILD ON 103 | CMAKE_ARGS ${netcdf_fortran_cmake_args} 104 | BUILD_BYPRODUCTS ${NetCDF_Fortran_LIBRARIES} 105 | DEPENDS NETCDF_C 106 | ) 107 | # BUILD_SHARED_LIBS=on for netcdf-fortran symbol finding bug 108 | # netCDEF_LIBRARIES and netCDF_INCLUDE_DIR from netcdf-fortran/CMakeLists.txt 109 | 110 | # --- imported target 111 | 112 | # this GLOBAL is required to be visible via other project's FetchContent 113 | add_library(NetCDF::NetCDF_Fortran INTERFACE IMPORTED GLOBAL) 114 | target_include_directories(NetCDF::NetCDF_Fortran INTERFACE ${NetCDF_Fortran_INCLUDE_DIRS}) 115 | target_link_libraries(NetCDF::NetCDF_Fortran INTERFACE ${NetCDF_Fortran_LIBRARIES} NetCDF::NetCDF_C) 116 | 117 | add_dependencies(NetCDF::NetCDF_Fortran NETCDF_FORTRAN) 118 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = */.git,*/build,*/.mypy_cache 3 | ignore-words-list = inout 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.py] 13 | indent_size = 4 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | cmake/Modules/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | env: 4 | HOMEBREW_NO_INSTALL_CLEANUP: 1 5 | CMAKE_BUILD_PARALLEL_LEVEL: 4 6 | CTEST_PARALLEL_LEVEL: 0 7 | CTEST_NO_TESTS_ACTION: error 8 | CMAKE_INSTALL_PREFIX: ~/libs 9 | CMAKE_PREFIX_PATH: ~/libs 10 | 11 | on: 12 | push: 13 | paths: 14 | - "**/CMakeLists.txt" 15 | - "**.cmake" 16 | - "**.f90" 17 | - ".github/workflows/ci.yml" 18 | - "!scripts/**" 19 | workflow_dispatch: 20 | 21 | # avoid wasted runs 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | 28 | base: 29 | runs-on: ${{ matrix.os }} 30 | timeout-minutes: 15 31 | 32 | env: 33 | FC: gfortran-14 34 | 35 | strategy: 36 | matrix: 37 | os: [ubuntu-latest, macos-latest] 38 | shared: [true, false] 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - name: install NetCDF system libs 44 | if: runner.os == 'Linux' 45 | run: sudo apt install --no-install-recommends libnetcdff-dev 46 | 47 | - name: install NetCDF system libs 48 | if: runner.os == 'macOS' 49 | run: brew install netcdf-fortran 50 | 51 | - run: >- 52 | cmake 53 | --preset multi 54 | -DBUILD_SHARED_LIBS:BOOL=${{ matrix.shared }} 55 | 56 | - run: cmake --build --preset debug 57 | - run: ctest --preset debug 58 | 59 | - run: cmake --build --preset release 60 | - run: ctest --preset release 61 | 62 | - run: cmake --install build 63 | 64 | - name: configure examples 65 | run: >- 66 | cmake 67 | -S example 68 | -B example/build 69 | -DBUILD_SHARED_LIBS:BOOL=${{ matrix.shared }} 70 | 71 | - name: build examples 72 | run: cmake --build example/build 73 | 74 | - name: Test examples 75 | run: ctest --test-dir example/build -V 76 | 77 | build: 78 | needs: base 79 | 80 | runs-on: ${{ matrix.os }} 81 | timeout-minutes: 15 82 | 83 | env: 84 | FC: gfortran-14 85 | 86 | strategy: 87 | matrix: 88 | os: [ubuntu-latest, macos-latest] 89 | 90 | steps: 91 | - uses: actions/checkout@v4 92 | 93 | - name: install HDF5 (macOS) 94 | if: runner.os == 'macOS' 95 | run: brew install hdf5 96 | 97 | - name: install HDF5 (Linux) 98 | if: runner.os == 'Linux' 99 | run: sudo apt install --no-install-recommends libhdf5-dev 100 | 101 | - name: GCC Linux 102 | if: runner.os == 'Linux' 103 | run: | 104 | echo "CC=gcc-14" >> $GITHUB_ENV 105 | 106 | - run: cmake --workflow default 107 | 108 | - run: cmake --install build 109 | 110 | - name: configure examples 111 | run: cmake -S example -B example/build 112 | 113 | - name: build examples 114 | run: cmake --build example/build 115 | 116 | - name: Test examples 117 | run: ctest --test-dir example/build -V 118 | 119 | 120 | linux-coverage: 121 | if: github.event_name == 'release' 122 | needs: base 123 | runs-on: ubuntu-latest 124 | timeout-minutes: 10 125 | 126 | steps: 127 | - uses: actions/checkout@v4 128 | - uses: actions/setup-python@v5 129 | with: 130 | python-version: '3.x' 131 | 132 | - name: install prereqs 133 | run: | 134 | sudo apt update 135 | sudo apt install --no-install-recommends libnetcdff-dev 136 | 137 | - name: install Gcovr 138 | run: pip install gcovr 139 | 140 | - run: cmake --preset coverage 141 | - run: cmake --build --preset coverage 142 | 143 | - name: Code coverage 144 | run: cmake --build --preset run-coverage 145 | 146 | - uses: actions/upload-artifact@v4 147 | with: 148 | name: coverage-report-html 149 | path: build-coverage/coverage/ 150 | -------------------------------------------------------------------------------- /.github/workflows/ci_fpm.yml: -------------------------------------------------------------------------------- 1 | name: ci_fpm 2 | 3 | on: 4 | push: 5 | paths: 6 | - "fpm.toml" 7 | - ".github/workflows/ci_fpm.yml" 8 | 9 | 10 | jobs: 11 | 12 | linux: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 15 15 | 16 | env: 17 | FPM_FFLAGS: -I/usr/include/ -I/usr/include/hdf5/serial 18 | FPM_LDFLAGS: -L/usr/lib/x86_64-linux-gnu/ -L/usr/lib/x86_64-linux-gnu/hdf5/serial 19 | 20 | steps: 21 | 22 | - uses: fortran-lang/setup-fpm@v7 23 | with: 24 | github-token: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: install NetCDF4 27 | run: | 28 | sudo apt update 29 | sudo apt install --no-install-recommends libnetcdff-dev 30 | 31 | - uses: actions/checkout@v4 32 | 33 | - run: fpm build 34 | - run: fpm test 35 | -------------------------------------------------------------------------------- /.github/workflows/ci_windows.yml: -------------------------------------------------------------------------------- 1 | name: ci_windows 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**/CMakeLists.txt" 7 | - "**.cmake" 8 | - "**.f90" 9 | - ".github/workflows/ci_windows.yml" 10 | - "!scripts/**" 11 | 12 | env: 13 | CMAKE_BUILD_PARALLEL_LEVEL: 4 14 | CTEST_PARALLEL_LEVEL: 0 15 | CTEST_NO_TESTS_ACTION: error 16 | workflow_dispatch: 17 | 18 | # avoid wasted runs 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | 25 | msys2: 26 | timeout-minutes: 30 27 | runs-on: windows-latest 28 | 29 | steps: 30 | - uses: msys2/setup-msys2@v2 31 | id: msys2 32 | with: 33 | update: true 34 | install: >- 35 | mingw-w64-ucrt-x86_64-gcc-fortran 36 | mingw-w64-ucrt-x86_64-netcdf-fortran 37 | 38 | - name: Put MSYS2_MinGW64 on PATH 39 | run: echo "${{ steps.msys2.outputs.msys2-location }}/ucrt64/bin/" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 40 | 41 | - name: prefix path 42 | run: | 43 | echo "CMAKE_INSTALL_PREFIX=$HOME/libs" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 44 | echo "CMAKE_PREFIX_PATH=$HOME/libs" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append 45 | 46 | - uses: actions/checkout@v4 47 | 48 | # undefined reference to `ncpsharedlibfree' 49 | # - name: Debug workflow 50 | # run: cmake --workflow debug 51 | 52 | - name: config 53 | run: cmake -G "MinGW Makefiles" --preset default 54 | env: 55 | HDF5_ROOT: ${{ steps.msys2.outputs.msys2-location }}/ucrt64/ 56 | NetCDF_ROOT: ${{ steps.msys2.outputs.msys2-location }}/ucrt64/ 57 | 58 | - name: build 59 | run: cmake --build --preset default 60 | 61 | - name: test 62 | run: ctest --preset default 63 | 64 | # - run: cmake --install build 65 | 66 | # NetCDF-C packaging issue with HDF5--ignores CMAKE_MODULE_PATH and uses broken factory FindHDF5 67 | # and can't seem to override 68 | # - name: configure examples 69 | # run: cmake -S example -B example/build 70 | # - name: build Examples 71 | # run: cmake --build example/build 72 | # - name: Test Examples 73 | # run: ctest --test-dir example/build -V 74 | -------------------------------------------------------------------------------- /.github/workflows/oneapi-linux.yml: -------------------------------------------------------------------------------- 1 | name: oneapi-linux 2 | 3 | env: 4 | CC: icx 5 | FC: ifx 6 | # https://github.com/oneapi-src/oneapi-ci/blob/master/.github/workflows/build_all.yml 7 | CTEST_NO_TESTS_ACTION: error 8 | CMAKE_BUILD_PARALLEL_LEVEL: 4 9 | CTEST_PARALLEL_LEVEL: 0 10 | CMAKE_INSTALL_PREFIX: ~/libs 11 | CMAKE_PREFIX_PATH: ~/libs 12 | 13 | on: 14 | push: 15 | paths: 16 | - "**.f90" 17 | - "**.F90" 18 | - "**.cmake" 19 | - "**/CMakeLists.txt" 20 | - ".github/workflows/oneapi-linux.yml" 21 | - "!scripts/**" 22 | workflow_dispatch: 23 | 24 | # avoid wasted runs 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.ref }} 27 | cancel-in-progress: true 28 | 29 | 30 | jobs: 31 | 32 | linux: 33 | runs-on: ubuntu-latest 34 | timeout-minutes: 10 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | 39 | - name: cache install oneAPI 40 | id: cache-install 41 | uses: actions/cache@v4 42 | with: 43 | path: | 44 | /opt/intel/oneapi 45 | key: install-oneapi-compiler 46 | 47 | - name: non-cache install oneAPI 48 | if: steps.cache-install.outputs.cache-hit != 'true' 49 | timeout-minutes: 5 50 | run: | 51 | .github/workflows/oneapi_setup_apt_repo_linux.sh 52 | sudo apt install intel-oneapi-compiler-dpcpp-cpp intel-oneapi-compiler-fortran 53 | 54 | - name: Setup Intel oneAPI environment 55 | run: | 56 | source /opt/intel/oneapi/setvars.sh 57 | printenv >> $GITHUB_ENV 58 | 59 | - name: Configure nc4fortran 60 | run: cmake --workflow default 61 | 62 | - name: print config log 63 | if: ${{ failure() }} 64 | run: cat build/CMakeFiles/CMakeConfigureLog.yaml 65 | 66 | - name: install package 67 | run: cmake --install build 68 | 69 | - name: configure examples 70 | run: >- 71 | cmake 72 | -S example 73 | -B example/build 74 | 75 | # BUILD_SHARED_LIBS=false since as with any C++ / Fortran program with Intel compiler, 76 | # need to have GCC environment carefully set 77 | # so that underlying libstdc++ is compatible. 78 | 79 | - name: exclude unused files from cache 80 | if: steps.cache-install.outputs.cache-hit != 'true' 81 | run: .github/workflows/oneapi_cache_exclude_linux.sh 82 | -------------------------------------------------------------------------------- /.github/workflows/oneapi_cache_exclude_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2020 Intel Corporation 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | #shellcheck disable=SC2010 8 | LATEST_VERSION=$(ls -1 /opt/intel/oneapi/compiler/ | grep -v latest | sort | tail -1) 9 | 10 | sudo rm -rf /opt/intel/oneapi/compiler/"$LATEST_VERSION"/linux/compiler/lib/ia32_lin 11 | sudo rm -rf /opt/intel/oneapi/compiler/"$LATEST_VERSION"/linux/bin/ia32 12 | sudo rm -rf /opt/intel/oneapi/compiler/"$LATEST_VERSION"/linux/lib/emu 13 | sudo rm -rf /opt/intel/oneapi/compiler/"$LATEST_VERSION"/linux/lib/oclfpga 14 | -------------------------------------------------------------------------------- /.github/workflows/oneapi_setup_apt_repo_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2020 Intel Corporation 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB 8 | sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB 9 | echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list 10 | sudo apt-get update -o Dir::Etc::sourcelist="sources.list.d/oneAPI.list" -o APT::Get::List-Cleanup="0" 11 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # nc4fortran API 2 | 3 | This document provides a listing of nc4fortran `public` scoped user-facing procedures and methods with a summary of their parameters. 4 | 5 | All examples assume: 6 | 7 | ```fortran 8 | use nc4fortran, only: netcdf_file 9 | 10 | type(netcdf_file) :: h 11 | ``` 12 | 13 | Query NetCDF4 library version: 14 | 15 | ```fortran 16 | use nc4fortran, only : nc4version 17 | print *, nc4version() 18 | ``` 19 | 20 | ## Open NetCDF4 file reference 21 | 22 | More than one NetCDF4 file can be open in a program, by declaring unique file handle (variable) like: 23 | 24 | ```fortran 25 | type(netcdf_file) :: h1, h2, h3 26 | ``` 27 | 28 | ```fortran 29 | call h%open(filename, action, comp_lvl) 30 | !! Opens hdf5 file 31 | 32 | character(*), intent(in) :: filename 33 | character(*), intent(in), optional :: action !< r, w, rw 34 | integer, intent(in), optional :: comp_lvl !< 0: no compression. 1-9: ZLIB compression, higher is more compressior 35 | ``` 36 | 37 | ## Close NetCDF4 file reference 38 | 39 | ```fortran 40 | call h%close() 41 | !! This must be called on each open file to flush buffers to disk 42 | !! data loss can occur if program terminates before this procedure 43 | ``` 44 | 45 | To avoid memory leaks or corrupted files, always "close" files before STOPping the Fortran program. 46 | 47 | ## Flush data to disk while file is open 48 | 49 | ```fortran 50 | call h%flush() 51 | ``` 52 | 53 | ## Disk variable (dataset) inquiry 54 | 55 | To allocate variables before reading data, inquire about dataset characteristics with these procedures. 56 | 57 | ```fortran 58 | rank = h%ndim(dataset_name) 59 | 60 | character(*), intent(in) :: dataset_name 61 | ``` 62 | 63 | Get disk dataset shape (1D vector) 64 | 65 | ```fortran 66 | call h%shape(dataset_name, dims) 67 | 68 | character(*), intent(in) :: dataset_name 69 | integer(HSIZE_T), intent(out), allocatable :: dims(:) 70 | ``` 71 | 72 | Does dataset "dname" exist in this HDF5 file? 73 | 74 | ```fortran 75 | tf = h%exist(dname) 76 | 77 | character(*), intent(in) :: dname 78 | ``` 79 | 80 | Is dataset "dname" contiguous on disk? 81 | 82 | ```fortran 83 | tf = h%is_contig(dname) 84 | 85 | character(*), intent(in) :: dname 86 | ``` 87 | 88 | These are more advanced inquiries into the memory layout of the dataset, for advanced users: 89 | 90 | ```fortran 91 | call h%chunks(dname, chunk_size) 92 | 93 | character(*), intent(in) :: dname 94 | integer, intent(out) :: chunk_size(:) 95 | ``` 96 | 97 | ## file write operations 98 | 99 | ```fortran 100 | call h%write(dname,value, istart, iend, stride, chunk_size) 101 | !! write 0d..7d dataset 102 | character(*), intent(in) :: dname 103 | class(*), intent(in) :: value(:) !< array to write 104 | integer, intent(in), optional :: chunk_size(rank(value)) 105 | integer, intent(in), optional, dimension(:) :: istart, iend, stride !< array slicing 106 | ``` 107 | 108 | Write dataset attribute (e.g. units or instrument) 109 | 110 | ```fortran 111 | call h%writeattr(dname, attr, attrval) 112 | 113 | character(*), intent(in) :: dname, attr !< dataset name, attribute name 114 | class(*), intent(in) :: attrval(:) !< character, real, integer 115 | ``` 116 | 117 | ## file read operations 118 | 119 | Read data from disk to memory 120 | 121 | ```fortran 122 | call h%read(dname, value, istart, iend, stride) 123 | character(*), intent(in) :: dname 124 | class(*), intent(out) :: value(:) !< read array to this ALLOCATED variable 125 | integer, intent(in), optional, dimension(:) :: istart, iend, stride !< array slicing 126 | ``` 127 | 128 | Read dataset attribute into memory 129 | 130 | ```fortran 131 | call h%readattr(dname, attr, attrval) 132 | character(*), intent(in) :: dname, attr !< dataset name, attribute name 133 | class(*), intent(out) :: attrval(:) !< character, real, integer 134 | ``` 135 | -------------------------------------------------------------------------------- /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: nc4fortran 7 | doi: 10.5281/zenodo.3598941 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19...3.30) 2 | 3 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 4 | message(FATAL_ERROR "In-source builds are not allowed. Do like: 5 | cmake -B build") 6 | endif() 7 | 8 | get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 9 | if(NOT is_multi_config AND NOT (CMAKE_BUILD_TYPE OR DEFINED ENV{CMAKE_BUILD_TYPE})) 10 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Release default") 11 | endif() 12 | 13 | project(nc4fortran 14 | LANGUAGES C Fortran 15 | VERSION 1.7.2 16 | ) 17 | 18 | enable_testing() 19 | 20 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 21 | 22 | include(options.cmake) 23 | include(cmake/compilers.cmake) 24 | 25 | file(GENERATE OUTPUT .gitignore CONTENT "*") 26 | 27 | if(find_netcdf) 28 | find_package(NetCDF COMPONENTS C Fortran) 29 | endif() 30 | if(NOT NetCDF_FOUND AND NOT TARGET NetCDF::NetCDF_Fortran) 31 | include(cmake/netcdf.cmake) 32 | endif() 33 | 34 | # --- code coverage 35 | if(nc4fortran_COVERAGE AND nc4fortran_IS_TOP_LEVEL) 36 | include(cmake/Modules/CodeCoverage.cmake) 37 | append_coverage_compiler_flags() 38 | set(COVERAGE_EXCLUDES ${PROJECT_SOURCE_DIR}/test) 39 | endif() 40 | 41 | # --- clang-tidy 42 | if(tidy AND nc4fortran_IS_TOP_LEVEL) 43 | find_program(CLANG_TIDY_EXE NAMES "clang-tidy" REQUIRED) 44 | set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY_EXE}) 45 | endif() 46 | 47 | # --- build 48 | 49 | add_library(nc4fortran) 50 | target_include_directories(nc4fortran PUBLIC 51 | $ 52 | $ 53 | ) 54 | target_link_libraries(nc4fortran PUBLIC NetCDF::NetCDF_Fortran) 55 | set_target_properties(nc4fortran PROPERTIES 56 | Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include 57 | LABELS core 58 | VERSION ${PROJECT_VERSION} 59 | ) 60 | 61 | add_subdirectory(src) 62 | 63 | # GLOBAL needed for FetchContent use 64 | add_library(nc4fortran::nc4fortran INTERFACE IMPORTED GLOBAL) 65 | target_link_libraries(nc4fortran::nc4fortran INTERFACE nc4fortran) 66 | 67 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/nc4fortran.mod TYPE INCLUDE) 68 | 69 | install(TARGETS nc4fortran EXPORT nc4fortran-targets) 70 | 71 | # additional Find*.cmake necessary 72 | install(FILES 73 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindHDF5.cmake 74 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindNetCDF.cmake 75 | DESTINATION cmake 76 | ) 77 | 78 | 79 | if(nc4fortran_BUILD_TESTING) 80 | add_subdirectory(test) 81 | endif() 82 | 83 | include(cmake/pkgconf.cmake) 84 | include(cmake/install.cmake) 85 | -------------------------------------------------------------------------------- /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 | "name": "multi", "inherits": "default", 14 | "displayName": "Ninja Multi-Config", 15 | "generator": "Ninja Multi-Config" 16 | }, 17 | { 18 | "name": "coverage", 19 | "binaryDir": "${sourceDir}/build-coverage", 20 | "displayName": "Code Coverage", 21 | "description": "Build with code coverage enabled.", 22 | "cacheVariables": { 23 | "CMAKE_BUILD_TYPE": "Debug", 24 | "nc4fortran_COVERAGE": true 25 | } 26 | } 27 | ], 28 | "buildPresets": [ 29 | { 30 | "name": "default", 31 | "configurePreset": "default" 32 | }, 33 | { 34 | "name": "release", 35 | "configurePreset": "multi", 36 | "configuration": "Release" 37 | }, 38 | { 39 | "name": "reldebug", 40 | "configurePreset": "multi", 41 | "configuration": "RelWithDebInfo", 42 | "displayName": "Release with Debug Info" 43 | }, 44 | { 45 | "name": "debug", 46 | "configurePreset": "multi", 47 | "configuration": "Debug" 48 | }, 49 | { 50 | "name": "coverage", 51 | "configurePreset": "coverage" 52 | }, 53 | { 54 | "name": "run-coverage", 55 | "configurePreset": "coverage", 56 | "targets": "coverage" 57 | } 58 | ], 59 | "testPresets": [ 60 | { 61 | "name": "default", 62 | "configurePreset": "default", 63 | "output": { 64 | "outputOnFailure": true, 65 | "verbosity": "verbose" 66 | }, 67 | "execution": { 68 | "noTestsAction": "error", 69 | "scheduleRandom": true, 70 | "stopOnFailure": false, 71 | "timeout": 60 72 | } 73 | }, 74 | { 75 | "name": "release", "inherits": "default", 76 | "configurePreset": "multi", 77 | "configuration": "Release" 78 | }, 79 | { 80 | "name": "reldebug", "inherits": "default", 81 | "configurePreset": "multi", 82 | "configuration": "RelWithDebInfo", 83 | "displayName": "Release with Debug Info" 84 | }, 85 | { 86 | "name": "debug", "inherits": "default", 87 | "configurePreset": "multi", 88 | "configuration": "Debug" 89 | } 90 | ], 91 | "workflowPresets": [ 92 | { 93 | "name": "default", 94 | "steps": [ 95 | { 96 | "type": "configure", 97 | "name": "default" 98 | }, 99 | { 100 | "type": "build", 101 | "name": "default" 102 | }, 103 | { 104 | "type": "test", 105 | "name": "default" 106 | } 107 | ] 108 | }, 109 | { 110 | "name": "debug", 111 | "steps": [ 112 | { 113 | "type": "configure", 114 | "name": "multi" 115 | }, 116 | { 117 | "type": "build", 118 | "name": "debug" 119 | }, 120 | { 121 | "type": "test", 122 | "name": "debug" 123 | } 124 | ] 125 | }, 126 | { 127 | "name": "release", 128 | "steps": [ 129 | { 130 | "type": "configure", 131 | "name": "multi" 132 | }, 133 | { 134 | "type": "build", 135 | "name": "release" 136 | }, 137 | { 138 | "type": "test", 139 | "name": "release" 140 | } 141 | ] 142 | } 143 | ] 144 | } 145 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | # Object-oriented Fortran NetCDF4 interface 2 | 3 | [![DOI](https://zenodo.org/badge/229812591.svg)](https://zenodo.org/badge/latestdoi/229812591) 4 | [![ci](https://github.com/geospace-code/nc4fortran/actions/workflows/ci.yml/badge.svg)](https://github.com/geospace-code/nc4fortran/actions/workflows/ci.yml) 5 | [![ci_windows](https://github.com/geospace-code/nc4fortran/actions/workflows/ci_windows.yml/badge.svg)](https://github.com/geospace-code/nc4fortran/actions/workflows/ci_windows.yml) 6 | [![ci_fpm](https://github.com/geospace-code/nc4fortran/actions/workflows/ci_fpm.yml/badge.svg)](https://github.com/geospace-code/nc4fortran/actions/workflows/ci_fpm.yml) 7 | 8 | Simple, robust, thin, object-oriented NetCDF4 polymorphic read/write interface. 9 | For HDF5 see [h5fortran](https://github.com/geospace-code/h5fortran). 10 | Designed for easy use as a CMake "ExternalProject" using **static** or **shared** linking. 11 | Uses Fortran 2008 `submodule` for clean template structure. 12 | nc4fortran abstracts away the messy parts of NetCDF4 so that you can read/write various types/ranks of data with a single command. 13 | In distinction from other high-level NetCDF4 interfaces, nc4fortran works to deduplicate code, using polymorphism wherever feasible, with an extensive test suite. 14 | 15 | Polymorphic API with read/write for types int32, int64, real32, real64 with rank: 16 | 17 | * scalar (0-D) 18 | * 1-D .. 7-D 19 | 20 | Also: 21 | 22 | * read/write **character** variables. 23 | * read/write character, int, float, double attributes 24 | 25 | Datatypes are coerced as per standard Fortran rules. 26 | For example, reading a float NetCDF4 variable into an integer Fortran variable: 42.3 => 42 27 | 28 | Tested on systems with NetCDF4 including: 29 | 30 | * MacOS 31 | * Linux 32 | * Windows 33 | 34 | See [API](./API.md) for usage. 35 | 36 | ## Build 37 | 38 | Requirements: 39 | 40 | * modern Fortran compiler: examples: GCC ≥ 7 or Intel oneAPI ≥ 2021 41 | * NetCDF4 Fortran library 42 | * Mac / Homebrew: `brew install gcc netcdf` 43 | * Linux: `apt install gfortran libnetcdf-dev libnetcdff-dev` 44 | * Windows Subsystem for Linux: `apt install gfortran libnetcdf-dev libnetcdff-dev` 45 | * Windows Cygwin `libnetcdf-fortran-devel` 46 | 47 | Note that some precompiled NetCDF4 libraries include C / C++ without Fortran. 48 | 49 | Build this NetCDF OO Fortran interface. 50 | The library `libnc4fortran.a` is built, link it into your program as usual. 51 | 52 | ### CMake 53 | 54 | ```sh 55 | cmake -B build 56 | cmake --build build 57 | 58 | # optional 59 | ctest --test-dir build 60 | ``` 61 | 62 | To specify a particular NetCDF library, use 63 | 64 | ```sh 65 | cmake -DNetCDF_ROOT=/path/to/netcdff -B build 66 | ``` 67 | 68 | or set environment variable `NetCDF_ROOT=/path/to/netcdff` 69 | 70 | To not find existing netCDF (force build of netCDF from source) add option: 71 | 72 | ```sh 73 | cmake -Dfind_netcdf=off -Bbuild 74 | ``` 75 | 76 | To use nc4fortran as a CMake ExternalProject do like: 77 | 78 | ```cmake 79 | include(FetchContent) 80 | 81 | FetchContent_Declare(nc4fortran_proj 82 | GIT_REPOSITORY https://github.com/geospace-code/nc4fortran.git 83 | ) 84 | 85 | FetchContent_MakeAvailable(nc4fortran_proj) 86 | 87 | # ------------------------------------------------------ 88 | # whatever your program is 89 | add_executable(myProj main.f90) 90 | target_link_libraries(myProj nc4fortran::nc4fortran) 91 | ``` 92 | 93 | ### Fortran Package Manager (fpm) 94 | 95 | ```sh 96 | fpm build 97 | fpm test 98 | fpm install 99 | ``` 100 | 101 | ## Acknowledgements 102 | 103 | nc4fortran was funded in part by NASA NNH19ZDA001N-HDEE grant 80NSSC20K0176. 104 | -------------------------------------------------------------------------------- /ci.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | set(CTEST_PROJECT_NAME "nc4fortran") 4 | 5 | set(opts 6 | -DCMAKE_COMPILE_WARNING_AS_ERROR:BOOL=ON 7 | ) 8 | 9 | option(submit "use CDash upload" true) 10 | 11 | # --- main script 12 | 13 | set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") 14 | set(CTEST_SUBMIT_URL "https://my.cdash.org/submit.php?project=${CTEST_PROJECT_NAME}") 15 | 16 | if(NOT CTEST_MODEL) 17 | set(CTEST_MODEL "Experimental") 18 | endif() 19 | 20 | # --- other defaults 21 | set(CTEST_TEST_TIMEOUT 10) 22 | 23 | set(CTEST_USE_LAUNCHERS true) 24 | set(CTEST_OUTPUT_ON_FAILURE true) 25 | set(CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE true) 26 | 27 | set(CTEST_SOURCE_DIRECTORY ${CTEST_SCRIPT_DIRECTORY}) 28 | if(NOT DEFINED CTEST_BINARY_DIRECTORY) 29 | set(CTEST_BINARY_DIRECTORY ${CTEST_SOURCE_DIRECTORY}/build) 30 | endif() 31 | 32 | if(NOT DEFINED CTEST_SITE AND DEFINED ENV{CTEST_SITE}) 33 | set(CTEST_SITE $ENV{CTEST_SITE}) 34 | endif() 35 | 36 | find_program(GIT_EXECUTABLE NAMES git REQUIRED) 37 | 38 | # --- CTEST_BUILD_NAME is used by ctest_submit(); must be set before ctest_start() 39 | 40 | if(NOT CTEST_BUILD_NAME) 41 | execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags 42 | WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY} 43 | OUTPUT_VARIABLE git_rev OUTPUT_STRIP_TRAILING_WHITESPACE 44 | RESULT_VARIABLE ret 45 | ) 46 | if(ret EQUAL 0) 47 | set(CTEST_BUILD_NAME ${git_rev}) 48 | endif() 49 | endif() 50 | 51 | 52 | function(find_generator) 53 | 54 | if(NOT CTEST_CMAKE_GENERATOR AND DEFINED ENV{CMAKE_GENERATOR}) 55 | set(CTEST_CMAKE_GENERATOR $ENV{CMAKE_GENERATOR} PARENT_SCOPE) 56 | return() 57 | # return here as if(...) wouldn't detect it in parent_scope 58 | endif() 59 | if(CTEST_CMAKE_GENERATOR) 60 | return() 61 | endif() 62 | 63 | find_program(ninja NAMES ninja ninja-build samu) 64 | 65 | if(ninja) 66 | set(CTEST_CMAKE_GENERATOR "Ninja" PARENT_SCOPE) 67 | elseif(WIN32) 68 | set(CTEST_CMAKE_GENERATOR "MinGW Makefiles" PARENT_SCOPE) 69 | else() 70 | set(CTEST_CMAKE_GENERATOR "Unix Makefiles" PARENT_SCOPE) 71 | endif() 72 | 73 | endfunction(find_generator) 74 | 75 | find_generator() 76 | 77 | # --- CTest Dashboard 78 | 79 | set(CTEST_SUBMIT_RETRY_COUNT 2) 80 | # avoid auto-detect version control failures on some systems 81 | set(CTEST_UPDATE_TYPE git) 82 | set(CTEST_UPDATE_COMMAND git) 83 | 84 | ctest_start(${CTEST_MODEL}) 85 | 86 | if(CTEST_MODEL MATCHES "(Nightly|Continuous)") 87 | # this erases local code changes i.e. anything not "git push" already is lost forever! 88 | # we try to avoid that by guarding with a Git porcelain check 89 | execute_process(COMMAND ${GIT_EXECUTABLE} status --porcelain 90 | WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY} 91 | TIMEOUT 5 92 | OUTPUT_VARIABLE ret OUTPUT_STRIP_TRAILING_WHITESPACE 93 | COMMAND_ERROR_IS_FATAL ANY 94 | ) 95 | if(ret) 96 | message(FATAL_ERROR "CTest would have erased the non-Git Push'd changes.") 97 | else() 98 | ctest_update( 99 | RETURN_VALUE ret 100 | CAPTURE_CMAKE_ERROR err 101 | ) 102 | if(ret LESS 0 OR NOT err EQUAL 0) 103 | message(FATAL_ERROR "Update failed: return ${ret} cmake return ${err}") 104 | endif() 105 | if(ret EQUAL 0 AND CTEST_MODEL STREQUAL "Continuous") 106 | message(NOTICE "No Git-updated files -> no need to test in CTest Model ${CTEST_MODEL}. CTest stopping.") 107 | return() 108 | endif() 109 | 110 | endif() 111 | endif() 112 | 113 | # --- configure 114 | 115 | ctest_configure( 116 | OPTIONS "${opts}" 117 | RETURN_VALUE ret 118 | CAPTURE_CMAKE_ERROR err 119 | ) 120 | if(NOT (ret EQUAL 0 AND err EQUAL 0)) 121 | if(submit) 122 | ctest_submit(BUILD_ID build_id) 123 | endif() 124 | message(FATAL_ERROR "Configure ${build_id} failed: return ${ret} cmake return ${err}") 125 | endif() 126 | 127 | if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL}) 128 | set(Ncpu $ENV{CMAKE_BUILD_PARALLEL_LEVEL}) 129 | else() 130 | cmake_host_system_information(RESULT Ncpu QUERY NUMBER_OF_PHYSICAL_CORES) 131 | endif() 132 | 133 | ctest_build( 134 | PARALLEL_LEVEL ${Ncpu} 135 | RETURN_VALUE ret 136 | CAPTURE_CMAKE_ERROR err 137 | ) 138 | if(NOT (ret EQUAL 0 AND err EQUAL 0)) 139 | if(submit) 140 | ctest_submit(BUILD_ID build_id) 141 | endif() 142 | message(FATAL_ERROR "Build ${build_id} failed: return ${ret} cmake return ${err}") 143 | endif() 144 | 145 | if(DEFINED ENV{CTEST_PARALLEL_LEVEL}) 146 | set(Ntest $ENV{CTEST_PARALLEL_LEVEL}) 147 | else() 148 | set(Ntest ${Ncpu}) 149 | endif() 150 | 151 | ctest_test( 152 | SCHEDULE_RANDOM true 153 | PARALLEL_LEVEL ${Ntest} 154 | RETURN_VALUE ret 155 | CAPTURE_CMAKE_ERROR err 156 | ) 157 | 158 | if(submit) 159 | ctest_submit(BUILD_ID build_id) 160 | endif() 161 | if(NOT (ret EQUAL 0 AND err EQUAL 0)) 162 | message(FATAL_ERROR "Test ${build_id} failed: CTest code ${ret}, CMake code ${err}.") 163 | endif() 164 | 165 | message(STATUS "OK: CTest build ${build_id}") 166 | -------------------------------------------------------------------------------- /cmake/FindHDF5.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #[=======================================================================[.rst: 5 | 6 | FindHDF5 7 | --------- 8 | 9 | by Michael Hirsch www.scivision.dev 10 | 11 | Finds HDF5 library for C, CXX, Fortran. Serial or parallel HDF5. 12 | 13 | Environment variable ``HDF5MPI_ROOT`` or CMake variable HDF5MPI_ROOT can 14 | specify the location of the HDF5-MPI parallel library. 15 | 16 | 17 | Result Variables 18 | ^^^^^^^^^^^^^^^^ 19 | 20 | ``HDF5_FOUND`` 21 | HDF5 libraries were found 22 | 23 | ``HDF5_INCLUDE_DIRS`` 24 | HDF5 include directory 25 | 26 | ``HDF5_LIBRARIES`` 27 | HDF5 library files 28 | 29 | ``HDF5__COMPILER_EXECUTABLE`` 30 | wrapper compiler for HDF5 31 | 32 | ``HDF5_HAVE_PARALLEL`` 33 | HDF5 links the MPI library (thus user program must link MPI as well) 34 | 35 | Components 36 | ========== 37 | 38 | ``C`` 39 | C is normally available for all HDF5 library installs 40 | 41 | ``CXX`` 42 | C++ is an optional feature that not all HDF5 library installs are built with 43 | 44 | ``Fortran`` 45 | Fortran is an optional feature that not all HDF5 library installs are built with 46 | 47 | ``parallel`` 48 | checks that the optional MPI parallel HDF5 layer is enabled. NOTE: if HDF5_parallel_FOUND is true, 49 | the user program MUST link MPI::MPI_C and/or MPI::MPI_Fortran. 50 | 51 | ``HL`` 52 | always implied and silently accepted to keep compatibility with factory FindHDF5.cmake 53 | 54 | 55 | Targets 56 | ^^^^^^^ 57 | 58 | ``HDF5::HDF5`` 59 | HDF5 Imported Target 60 | #]=======================================================================] 61 | 62 | include(CheckSymbolExists) 63 | include(CheckSourceCompiles) 64 | 65 | 66 | function(get_flags exec outvar) 67 | 68 | execute_process(COMMAND ${exec} -show 69 | OUTPUT_STRIP_TRAILING_WHITESPACE 70 | OUTPUT_VARIABLE ret 71 | RESULT_VARIABLE code 72 | TIMEOUT 10 73 | ERROR_QUIET 74 | ) 75 | 76 | if(code EQUAL 0) 77 | set(${outvar} ${ret} PARENT_SCOPE) 78 | endif() 79 | 80 | endfunction(get_flags) 81 | 82 | 83 | function(pop_flag raw flag outvar) 84 | # this gives the argument to flags to get their paths like -I or -l or -L 85 | 86 | set(_v) 87 | string(REGEX MATCHALL "(^| )${flag} *([^\" ]+|\"[^\"]+\")" _vars "${raw}") 88 | foreach(_p IN LISTS _vars) 89 | string(REGEX REPLACE "(^| )${flag} *" "" _p "${_p}") 90 | list(APPEND _v "${_p}") 91 | endforeach() 92 | 93 | set(${outvar} ${_v} PARENT_SCOPE) 94 | 95 | endfunction(pop_flag) 96 | 97 | macro(find_mpi) 98 | # non-cache set by FindMPI are not visible outside function -- need macro just to see within that function 99 | set(mpi_comp C) 100 | if(Fortran IN_LIST HDF5_FIND_COMPONENTS) 101 | list(APPEND mpi_comp Fortran) 102 | endif() 103 | if(HDF5_FIND_REQUIRED) 104 | find_package(MPI COMPONENTS ${mpi_comp} REQUIRED) 105 | else() 106 | find_package(MPI COMPONENTS ${mpi_comp}) 107 | endif() 108 | 109 | endmacro(find_mpi) 110 | 111 | 112 | macro(detect_config) 113 | 114 | set(CMAKE_REQUIRED_INCLUDES ${HDF5_C_INCLUDE_DIR}) 115 | 116 | find_file(h5_conf 117 | NAMES H5pubconf.h H5pubconf-64.h 118 | HINTS ${HDF5_C_INCLUDE_DIR} 119 | NO_DEFAULT_PATH 120 | ) 121 | message(VERBOSE "HDF5 config: ${h5_conf}") 122 | 123 | if(NOT h5_conf) 124 | set(HDF5_C_FOUND false) 125 | return() 126 | endif() 127 | 128 | # check HDF5 features that require link of external libraries. 129 | check_symbol_exists(H5_HAVE_FILTER_SZIP ${h5_conf} hdf5_have_szip) 130 | check_symbol_exists(H5_HAVE_FILTER_DEFLATE ${h5_conf} hdf5_have_zlib) 131 | 132 | # Always check for HDF5 MPI support because HDF5 link fails if MPI is linked into HDF5. 133 | check_symbol_exists(H5_HAVE_PARALLEL ${h5_conf} HDF5_HAVE_PARALLEL) 134 | 135 | set(HDF5_parallel_FOUND false) 136 | 137 | if(HDF5_HAVE_PARALLEL) 138 | find_mpi() 139 | if(NOT MPI_FOUND) 140 | return() 141 | endif() 142 | 143 | set(HDF5_parallel_FOUND true) 144 | endif() 145 | 146 | # get version 147 | # from CMake/Modules/FindHDF5.cmake 148 | file(STRINGS ${h5_conf} _def 149 | REGEX "^[ \t]*#[ \t]*define[ \t]+H5_VERSION[ \t]+" 150 | ) 151 | message(DEBUG "HDF5 version define: ${_def}") 152 | 153 | if("${_def}" MATCHES "H5_VERSION[ \t]+\"([0-9]+\\.[0-9]+\\.[0-9]+)") 154 | set(HDF5_VERSION "${CMAKE_MATCH_1}") 155 | endif() 156 | message(DEBUG "HDF5 version match 0, 1: ${CMAKE_MATCH_0} ${CMAKE_MATCH_1}") 157 | 158 | # avoid picking up incompatible zlib over the desired zlib 159 | if(NOT ZLIB_ROOT) 160 | cmake_path(GET HDF5_C_INCLUDE_DIR PARENT_PATH ZLIB_ROOT) 161 | list(APPEND ZLIB_ROOT ${HDF5_ROOT}) 162 | endif() 163 | 164 | 165 | if(hdf5_have_zlib) 166 | 167 | if(HDF5_FIND_REQUIRED) 168 | find_package(ZLIB REQUIRED) 169 | else() 170 | find_package(ZLIB) 171 | endif() 172 | if(NOT ZLIB_FOUND) 173 | return() 174 | endif() 175 | 176 | if(hdf5_have_szip) 177 | # Szip even though not used by default. 178 | # If system HDF5 dynamically links libhdf5 with szip, our builds will fail if we don't also link szip. 179 | # however, we don't require SZIP for this case as other HDF5 libraries may statically link SZIP. 180 | 181 | find_library(SZIP_LIBRARY 182 | NAMES szip sz 183 | NAMES_PER_DIR 184 | HINTS ${SZIP_ROOT} ${ZLIB_ROOT} 185 | PATH_SUFFIXES lib lib64 186 | DOC "SZIP API" 187 | ) 188 | 189 | find_path(SZIP_INCLUDE_DIR 190 | NAMES szlib.h 191 | HINTS ${SZIP_ROOT} ${ZLIB_ROOT} 192 | PATH_SUFFIXES include 193 | DOC "SZIP header" 194 | ) 195 | 196 | if(NOT (SZIP_LIBRARY AND SZIP_INCLUDE_DIR)) 197 | message(VERBOSE "FindHDF5: SZIP not found, but HDF5 indicates it was built with SZIP. This may cause build errors.") 198 | return() 199 | endif() 200 | 201 | list(APPEND CMAKE_REQUIRED_INCLUDES ${SZIP_INCLUDE_DIR}) 202 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${SZIP_LIBRARY}) 203 | endif() 204 | 205 | list(APPEND CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIRS}) 206 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${ZLIB_LIBRARIES}) 207 | endif() 208 | 209 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) 210 | 211 | find_package(Threads) 212 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) 213 | 214 | if(UNIX) 215 | list(APPEND CMAKE_REQUIRED_LIBRARIES m) 216 | endif() 217 | 218 | endmacro(detect_config) 219 | 220 | 221 | function(find_hdf5_fortran) 222 | # NOTE: the "lib*" are for Windows Intel compiler, even for self-built HDF5. 223 | # CMake won't look for lib prefix automatically. 224 | 225 | if(parallel IN_LIST HDF5_FIND_COMPONENTS AND NOT HDF5_parallel_FOUND) 226 | # this avoids expensive Fortran find when MPI isn't linked properly 227 | return() 228 | endif() 229 | 230 | hdf5_fortran_wrap(hdf5_lib_dirs hdf5_inc_dirs) 231 | 232 | # "PATH" Env var is useful on HPC for finding HDF5 libraries 233 | 234 | if(MSVC) 235 | set(CMAKE_FIND_LIBRARY_PREFIXES lib) 236 | endif() 237 | 238 | set(_names hdf5_fortran) 239 | set(_hl_names hdf5_hl_fortran hdf5hl_fortran) 240 | set(_hl_stub_names hdf5_hl_f90cstub) 241 | set(_stub_names hdf5_f90cstub) 242 | 243 | # distro names (Ubuntu) 244 | if(HDF5_parallel_FOUND) 245 | list(APPEND _names hdf5_openmpi_fortran hdf5_mpich_fortran) 246 | list(APPEND _hl_names hdf5_openmpihl_fortran hdf5_mpichhl_fortran) 247 | else() 248 | list(APPEND _names hdf5_serial_fortran) 249 | list(APPEND _hl_names hdf5_serialhl_fortran) 250 | endif() 251 | 252 | # Debug names 253 | if(MSVC) 254 | list(APPEND _names hdf5_fortran_D) 255 | list(APPEND _hl_names hdf5_hl_fortran_D) 256 | list(APPEND _hl_stub_names hdf5_hl_f90cstub_D) 257 | list(APPEND _stub_names hdf5_f90cstub_D) 258 | else() 259 | list(APPEND _names hdf5_fortran_debug) 260 | list(APPEND _hl_names hdf5_hl_fortran_debug) 261 | list(APPEND _hl_stub_names hdf5_hl_f90cstub_debug) 262 | list(APPEND _stub_names hdf5_f90cstub_debug) 263 | endif() 264 | 265 | find_library(HDF5_Fortran_LIBRARY 266 | NAMES ${_names} 267 | HINTS ${HDF5_ROOT} ${hdf5_lib_dirs} 268 | PATH_SUFFIXES ${hdf5_lsuf} 269 | NAMES_PER_DIR 270 | DOC "HDF5 Fortran API" 271 | ) 272 | 273 | cmake_path(GET HDF5_Fortran_LIBRARY PARENT_PATH hdf5_libdir) 274 | 275 | find_library(HDF5_Fortran_HL_LIBRARY 276 | NAMES ${_hl_names} 277 | HINTS ${hdf5_libdir} 278 | NO_DEFAULT_PATH 279 | DOC "HDF5 Fortran HL high-level API" 280 | ) 281 | 282 | # not all platforms have this stub 283 | find_library(HDF5_Fortran_HL_stub 284 | NAMES ${_hl_stub_names} 285 | HINTS ${hdf5_libdir} 286 | NO_DEFAULT_PATH 287 | DOC "Fortran C HL interface, not all HDF5 implementations have/need this" 288 | ) 289 | 290 | find_library(HDF5_Fortran_stub 291 | NAMES ${_stub_names} 292 | HINTS ${hdf5_libdir} 293 | NO_DEFAULT_PATH 294 | DOC "Fortran C interface, not all HDF5 implementations have/need this" 295 | ) 296 | 297 | set(HDF5_Fortran_LIBRARIES ${HDF5_Fortran_HL_LIBRARY} ${HDF5_Fortran_LIBRARY}) 298 | if(HDF5_Fortran_HL_stub AND HDF5_Fortran_stub) 299 | list(APPEND HDF5_Fortran_LIBRARIES ${HDF5_Fortran_HL_stub} ${HDF5_Fortran_stub}) 300 | endif() 301 | 302 | if(HDF5_ROOT) 303 | find_path(HDF5_Fortran_INCLUDE_DIR 304 | NAMES hdf5.mod 305 | NO_DEFAULT_PATH 306 | HINTS ${HDF5_C_INCLUDE_DIR} ${HDF5_ROOT} 307 | DOC "HDF5 Fortran module path" 308 | ) 309 | else() 310 | if(HDF5_parallel_FOUND) 311 | # HDF5-MPI system library presents a unique challenge, as when non-MPI HDF5 is 312 | # also installed, which is typically necessary for other system libraries, the 313 | # HDF5-MPI compiler wrapper often includes that wrong non-MPI include dir first. 314 | # The most general approach seemed to be the following: 315 | # search in a for loop and do a link check. 316 | if(NOT HDF5_Fortran_INCLUDE_DIR) 317 | foreach(i IN LISTS HDF5_C_INCLUDE_DIR hdf5_inc_dirs) 318 | find_path(HDF5_Fortran_INCLUDE_DIR 319 | NAMES hdf5.mod 320 | NO_DEFAULT_PATH 321 | HINTS ${i} 322 | DOC "HDF5 Fortran module path" 323 | ) 324 | message(VERBOSE "FindHDF5: trying hdf5.mod in ${i} - got: ${HDF5_Fortran_INCLUDE_DIR}") 325 | if(HDF5_Fortran_INCLUDE_DIR) 326 | check_fortran_links() 327 | if(HDF5_Fortran_links) 328 | break() 329 | else() 330 | unset(HDF5_Fortran_INCLUDE_DIR CACHE) 331 | unset(HDF5_Fortran_links CACHE) 332 | endif() 333 | endif() 334 | endforeach() 335 | endif() 336 | 337 | if(NOT HDF5_Fortran_INCLUDE_DIR) 338 | # last resort, might give incompatible non-MPI hdf5.mod 339 | find_path(HDF5_Fortran_INCLUDE_DIR 340 | NAMES hdf5.mod 341 | HINTS ${HDF5_C_INCLUDE_DIR} ${hdf5_inc_dirs} 342 | PATHS ${hdf5_binpref} 343 | PATH_SUFFIXES ${hdf5_msuf} 344 | DOC "HDF5 Fortran module path" 345 | ) 346 | endif() 347 | else() 348 | find_path(HDF5_Fortran_INCLUDE_DIR 349 | NAMES hdf5.mod 350 | HINTS ${HDF5_C_INCLUDE_DIR} ${hdf5_inc_dirs} 351 | PATHS ${hdf5_binpref} 352 | PATH_SUFFIXES ${hdf5_msuf} 353 | DOC "HDF5 Fortran module path" 354 | ) 355 | endif() 356 | endif() 357 | 358 | if(HDF5_Fortran_LIBRARY AND HDF5_Fortran_HL_LIBRARY AND HDF5_Fortran_INCLUDE_DIR) 359 | set(HDF5_Fortran_LIBRARIES ${HDF5_Fortran_LIBRARIES} PARENT_SCOPE) 360 | set(HDF5_Fortran_FOUND true PARENT_SCOPE) 361 | set(HDF5_HL_FOUND true PARENT_SCOPE) 362 | endif() 363 | 364 | endfunction(find_hdf5_fortran) 365 | 366 | 367 | function(find_hdf5_cxx) 368 | 369 | if(parallel IN_LIST HDF5_FIND_COMPONENTS AND NOT HDF5_parallel_FOUND) 370 | # avoid expensive C++ find when MPI isn't linked properly 371 | return() 372 | endif() 373 | 374 | hdf5_cxx_wrap(hdf5_lib_dirs hdf5_inc_dirs) 375 | 376 | # "PATH" Env var is useful on HPC for finding HDF5 libraries 377 | 378 | if(MSVC) 379 | set(CMAKE_FIND_LIBRARY_PREFIXES lib) 380 | endif() 381 | 382 | set(_names hdf5_cpp) 383 | set(_hl_names hdf5_hl_cpp) 384 | 385 | # distro names (Ubuntu) 386 | if(HDF5_parallel_FOUND) 387 | list(APPEND _names hdf5_openmpi_cpp hdf5_mpich_cpp) 388 | list(APPEND _hl_names hdf5_openmpi_hl_cpp hdf5_mpich_hl_cpp) 389 | else() 390 | list(APPEND _names hdf5_serial_cpp) 391 | list(APPEND _hl_names hdf5_serial_hl_cpp) 392 | endif() 393 | 394 | # Debug names 395 | if(MSVC) 396 | list(APPEND _names hdf5_cpp_D) 397 | list(APPEND _hl_names hdf5_hl_cpp_D) 398 | else() 399 | list(APPEND _names hdf5_cpp_debug) 400 | list(APPEND _hl_names hdf5_hl_cpp_debug) 401 | endif() 402 | 403 | find_library(HDF5_CXX_LIBRARY 404 | NAMES ${_names} 405 | HINTS ${HDF5_ROOT} ${hdf5_lib_dirs} 406 | PATH_SUFFIXES ${hdf5_lsuf} 407 | NAMES_PER_DIR 408 | DOC "HDF5 C++ API" 409 | ) 410 | 411 | cmake_path(GET HDF5_CXX_LIBRARY PARENT_PATH hdf5_libdir) 412 | 413 | find_library(HDF5_CXX_HL_LIBRARY 414 | NAMES ${_hl_names} 415 | HINTS ${hdf5_libdir} 416 | NO_DEFAULT_PATH 417 | DOC "HDF5 C++ high-level API" 418 | ) 419 | 420 | find_path(HDF5_CXX_INCLUDE_DIR 421 | NAMES hdf5.h 422 | HINTS ${HDF5_C_INCLUDE_DIR} ${HDF5_ROOT} ${hdf5_inc_dirs} 423 | PATH_SUFFIXES ${hdf5_isuf} 424 | DOC "HDF5 C header" 425 | ) 426 | 427 | if(HDF5_CXX_LIBRARY AND HDF5_CXX_HL_LIBRARY AND HDF5_CXX_INCLUDE_DIR) 428 | set(HDF5_CXX_LIBRARIES ${HDF5_CXX_HL_LIBRARY} ${HDF5_CXX_LIBRARY} PARENT_SCOPE) 429 | set(HDF5_CXX_FOUND true PARENT_SCOPE) 430 | set(HDF5_HL_FOUND true PARENT_SCOPE) 431 | endif() 432 | 433 | endfunction(find_hdf5_cxx) 434 | 435 | 436 | function(find_hdf5_c) 437 | 438 | hdf5_c_wrap(hdf5_lib_dirs hdf5_inc_dirs) 439 | 440 | # "PATH" Env var is useful on HPC for finding HDF5 libraries 441 | 442 | if(MSVC) 443 | set(CMAKE_FIND_LIBRARY_PREFIXES lib) 444 | endif() 445 | 446 | set(_names hdf5) 447 | set(_hl_names hdf5_hl) 448 | 449 | # distro names (Ubuntu) 450 | if(parallel IN_LIST HDF5_FIND_COMPONENTS) 451 | list(APPEND _names hdf5_openmpi hdf5_mpich) 452 | list(APPEND _hl_names hdf5_openmpi_hl hdf5_mpich_hl) 453 | else() 454 | list(APPEND _names hdf5_serial) 455 | list(APPEND _hl_names hdf5_serial_hl) 456 | endif() 457 | 458 | # debug names 459 | if(MSVC) 460 | list(APPEND _names hdf5_D) 461 | list(APPEND _hl_names hdf5_hl_D) 462 | else() 463 | list(APPEND _names hdf5_debug) 464 | list(APPEND _hl_names hdf5_hl_debug) 465 | endif() 466 | 467 | # MUST have HDF5_ROOT in HINTS here since it was set in this script 468 | find_library(HDF5_C_LIBRARY 469 | NAMES ${_names} 470 | HINTS ${HDF5_ROOT} ${hdf5_lib_dirs} 471 | PATH_SUFFIXES ${hdf5_lsuf} 472 | NAMES_PER_DIR 473 | DOC "HDF5 C library (necessary for all languages)" 474 | ) 475 | 476 | cmake_path(GET HDF5_C_LIBRARY PARENT_PATH hdf5_libdir) 477 | 478 | find_library(HDF5_C_HL_LIBRARY 479 | NAMES ${_hl_names} 480 | HINTS ${hdf5_libdir} 481 | NO_DEFAULT_PATH 482 | DOC "HDF5 C high level interface" 483 | ) 484 | 485 | find_path(HDF5_C_INCLUDE_DIR 486 | NAMES hdf5.h 487 | HINTS ${HDF5_ROOT} ${hdf5_inc_dirs} 488 | PATH_SUFFIXES ${hdf5_isuf} 489 | DOC "HDF5 C header" 490 | ) 491 | 492 | if(HDF5_C_HL_LIBRARY AND HDF5_C_LIBRARY AND HDF5_C_INCLUDE_DIR) 493 | set(HDF5_C_LIBRARIES ${HDF5_C_HL_LIBRARY} ${HDF5_C_LIBRARY} PARENT_SCOPE) 494 | set(HDF5_C_FOUND true PARENT_SCOPE) 495 | set(HDF5_HL_FOUND true PARENT_SCOPE) 496 | endif() 497 | 498 | endfunction(find_hdf5_c) 499 | 500 | 501 | function(hdf5_fortran_wrap lib_var inc_var) 502 | 503 | set(lib_dirs) 504 | set(inc_dirs) 505 | 506 | if(HDF5_parallel_FOUND) 507 | set(wrapper_names h5pfc h5pfc.openmpi h5pfc.mpich) 508 | else() 509 | set(wrapper_names h5fc) 510 | endif() 511 | 512 | if(HDF5_ROOT) 513 | find_program(HDF5_Fortran_COMPILER_EXECUTABLE 514 | NAMES ${wrapper_names} 515 | NAMES_PER_DIR 516 | NO_DEFAULT_PATH 517 | HINTS ${HDF5_ROOT} 518 | PATH_SUFFIXES ${hdf5_binsuf} 519 | DOC "HDF5 Fortran compiler script" 520 | ) 521 | else() 522 | find_program(HDF5_Fortran_COMPILER_EXECUTABLE 523 | NAMES ${wrapper_names} 524 | NAMES_PER_DIR 525 | PATHS ${hdf5_binpref} 526 | PATH_SUFFIXES ${hdf5_binsuf} 527 | DOC "HDF5 Fortran compiler script" 528 | ) 529 | endif() 530 | 531 | if(NOT HDF5_Fortran_COMPILER_EXECUTABLE) 532 | return() 533 | endif() 534 | 535 | get_flags(${HDF5_Fortran_COMPILER_EXECUTABLE} f_raw) 536 | if(f_raw) 537 | pop_flag(${f_raw} -L lib_dirs) 538 | pop_flag(${f_raw} -I inc_dirs) 539 | if(NOT inc_dirs AND parallel IN_LIST HDF5_FIND_COMPONENTS) 540 | get_flags(${MPI_Fortran_COMPILER} f_raw) 541 | if(f_raw) 542 | pop_flag(${f_raw} -I inc_dirs) 543 | endif(f_raw) 544 | endif() 545 | endif(f_raw) 546 | 547 | if(inc_dirs) 548 | set(${inc_var} ${inc_dirs} PARENT_SCOPE) 549 | endif() 550 | 551 | if(lib_dirs) 552 | set(${lib_var} ${lib_dirs} PARENT_SCOPE) 553 | endif() 554 | 555 | endfunction(hdf5_fortran_wrap) 556 | 557 | 558 | function(hdf5_cxx_wrap lib_var inc_var) 559 | 560 | set(lib_dirs) 561 | set(inc_dirs) 562 | 563 | if(HDF5_parallel_FOUND) 564 | set(wrapper_names h5c++.openmpi h5c++.mpich) 565 | else() 566 | set(wrapper_names h5c++) 567 | endif() 568 | 569 | if(HDF5_ROOT) 570 | find_program(HDF5_CXX_COMPILER_EXECUTABLE 571 | NAMES ${wrapper_names} 572 | NAMES_PER_DIR 573 | NO_DEFAULT_PATH 574 | HINTS ${HDF5_ROOT} 575 | PATH_SUFFIXES ${hdf5_binsuf} 576 | DOC "HDF5 C++ compiler script" 577 | ) 578 | else() 579 | find_program(HDF5_CXX_COMPILER_EXECUTABLE 580 | NAMES ${wrapper_names} 581 | NAMES_PER_DIR 582 | PATHS ${hdf5_binpref} 583 | PATH_SUFFIXES ${hdf5_binsuf} 584 | DOC "HDF5 C++ compiler script" 585 | ) 586 | endif() 587 | 588 | if(NOT HDF5_CXX_COMPILER_EXECUTABLE) 589 | return() 590 | endif() 591 | 592 | get_flags(${HDF5_CXX_COMPILER_EXECUTABLE} cxx_raw) 593 | if(cxx_raw) 594 | pop_flag(${cxx_raw} -L lib_dirs) 595 | pop_flag(${cxx_raw} -I inc_dirs) 596 | endif(cxx_raw) 597 | 598 | if(inc_dirs) 599 | set(${inc_var} ${inc_dirs} PARENT_SCOPE) 600 | endif() 601 | 602 | if(lib_dirs) 603 | set(${lib_var} ${lib_dirs} PARENT_SCOPE) 604 | endif() 605 | 606 | endfunction(hdf5_cxx_wrap) 607 | 608 | 609 | function(hdf5_c_wrap lib_var inc_var) 610 | 611 | set(lib_dirs) 612 | set(inc_dirs) 613 | 614 | if(parallel IN_LIST HDF5_FIND_COMPONENTS) 615 | set(wrapper_names h5pcc h5pcc.openmpi h5pcc.mpich) 616 | else() 617 | set(wrapper_names h5cc) 618 | endif() 619 | 620 | if(HDF5_ROOT) 621 | find_program(HDF5_C_COMPILER_EXECUTABLE 622 | NAMES ${wrapper_names} 623 | NAMES_PER_DIR 624 | NO_DEFAULT_PATH 625 | HINTS ${HDF5_ROOT} 626 | PATH_SUFFIXES ${hdf5_binsuf} 627 | DOC "HDF5 C compiler script" 628 | ) 629 | else() 630 | find_program(HDF5_C_COMPILER_EXECUTABLE 631 | NAMES ${wrapper_names} 632 | NAMES_PER_DIR 633 | PATHS ${hdf5_binpref} 634 | PATH_SUFFIXES ${hdf5_binsuf} 635 | DOC "HDF5 C compiler script" 636 | ) 637 | endif() 638 | 639 | if(NOT HDF5_C_COMPILER_EXECUTABLE) 640 | return() 641 | endif() 642 | 643 | get_flags(${HDF5_C_COMPILER_EXECUTABLE} c_raw) 644 | if(c_raw) 645 | pop_flag(${c_raw} -L lib_dirs) 646 | pop_flag(${c_raw} -I inc_dirs) 647 | if(NOT inc_dirs AND parallel IN_LIST HDF5_FIND_COMPONENTS) 648 | get_flags(${MPI_C_COMPILER} c_raw) 649 | if(c_raw) 650 | pop_flag(${c_raw} -I inc_dirs) 651 | endif(c_raw) 652 | endif() 653 | endif(c_raw) 654 | 655 | 656 | if(inc_dirs) 657 | set(${inc_var} ${inc_dirs} PARENT_SCOPE) 658 | endif() 659 | 660 | if(lib_dirs) 661 | set(${lib_var} ${lib_dirs} PARENT_SCOPE) 662 | endif() 663 | 664 | 665 | endfunction(hdf5_c_wrap) 666 | 667 | 668 | function(check_c_links) 669 | 670 | list(PREPEND CMAKE_REQUIRED_LIBRARIES ${HDF5_C_LIBRARIES}) 671 | set(CMAKE_REQUIRED_INCLUDES ${HDF5_C_INCLUDE_DIR}) 672 | 673 | if(HDF5_parallel_FOUND) 674 | find_mpi() 675 | 676 | list(APPEND CMAKE_REQUIRED_INCLUDES ${MPI_C_INCLUDE_DIRS}) 677 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${MPI_C_LIBRARIES}) 678 | 679 | check_symbol_exists(H5Pset_fapl_mpio hdf5.h HAVE_H5Pset_fapl_mpio) 680 | if(NOT HAVE_H5Pset_fapl_mpio) 681 | return() 682 | endif() 683 | 684 | set(src [=[ 685 | #include "hdf5.h" 686 | #include "mpi.h" 687 | 688 | int main(void){ 689 | MPI_Init(NULL, NULL); 690 | 691 | hid_t plist_id = H5Pcreate(H5P_FILE_ACCESS); 692 | H5Pset_fapl_mpio(plist_id, MPI_COMM_WORLD, MPI_INFO_NULL); 693 | 694 | H5Pclose(plist_id); 695 | 696 | MPI_Finalize(); 697 | 698 | return 0; 699 | } 700 | ]=]) 701 | 702 | else() 703 | set(src [=[ 704 | #include "hdf5.h" 705 | 706 | int main(void){ 707 | hid_t f = H5Fcreate("junk.h5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); 708 | herr_t status = H5Fclose (f); 709 | return 0;} 710 | ]=]) 711 | endif(HDF5_parallel_FOUND) 712 | 713 | check_source_compiles(C "${src}" HDF5_C_links) 714 | 715 | endfunction(check_c_links) 716 | 717 | 718 | function(check_fortran_links) 719 | 720 | list(PREPEND CMAKE_REQUIRED_LIBRARIES ${HDF5_Fortran_LIBRARIES} ${HDF5_C_LIBRARIES}) 721 | set(CMAKE_REQUIRED_INCLUDES ${HDF5_Fortran_INCLUDE_DIR} ${HDF5_C_INCLUDE_DIR}) 722 | 723 | if(HDF5_parallel_FOUND) 724 | find_mpi() 725 | 726 | list(APPEND CMAKE_REQUIRED_INCLUDES ${MPI_Fortran_INCLUDE_DIRS}) 727 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${MPI_Fortran_LIBRARIES}) 728 | 729 | set(src "program test 730 | use hdf5 731 | use mpi 732 | implicit none 733 | integer :: ierr, mpi_id 734 | integer(HID_T) :: fapl, xfer_id 735 | call mpi_init(ierr) 736 | call h5open_f(ierr) 737 | call h5pcreate_f(H5P_FILE_ACCESS_F, fapl, ierr) 738 | call h5pset_fapl_mpio_f(fapl, MPI_COMM_WORLD, MPI_INFO_NULL, ierr) 739 | call h5pcreate_f(H5P_DATASET_XFER_F, xfer_id, ierr) 740 | call h5pset_dxpl_mpio_f(xfer_id, H5FD_MPIO_COLLECTIVE_F, ierr) 741 | call mpi_finalize(ierr) 742 | end program") 743 | else() 744 | set(src "program test_minimal 745 | use hdf5, only : h5open_f, h5close_f 746 | use h5lt, only : h5ltmake_dataset_f 747 | implicit none 748 | integer :: i 749 | call h5open_f(i) 750 | call h5close_f(i) 751 | end program") 752 | endif() 753 | 754 | check_source_compiles(Fortran ${src} HDF5_Fortran_links) 755 | 756 | endfunction(check_fortran_links) 757 | 758 | 759 | function(check_hdf5_link) 760 | 761 | # HDF5 bug #3663 for HDF5 1.14.2, ...? 762 | # https://github.com/HDFGroup/hdf5/issues/3663 763 | if(WIN32 AND CMAKE_Fortran_COMPILER_ID MATCHES "^Intel") 764 | if(HDF5_VERSION MATCHES "1.14.[2-4]") 765 | message(VERBOSE "FindHDF5: applying workaround for HDF5 bug #3663 with Intel oneAPI on Windows") 766 | list(APPEND CMAKE_REQUIRED_LIBRARIES shlwapi) 767 | endif() 768 | endif() 769 | 770 | if(NOT HDF5_C_FOUND) 771 | return() 772 | endif() 773 | 774 | if(parallel IN_LIST HDF5_FIND_COMPONENTS AND NOT HDF5_parallel_FOUND) 775 | return() 776 | endif() 777 | 778 | check_c_links() 779 | 780 | if(NOT HDF5_C_links) 781 | return() 782 | endif() 783 | 784 | if(HDF5_Fortran_FOUND) 785 | check_fortran_links() 786 | 787 | if(NOT HDF5_Fortran_links) 788 | return() 789 | endif() 790 | endif() 791 | 792 | set(HDF5_links true PARENT_SCOPE) 793 | 794 | endfunction(check_hdf5_link) 795 | 796 | # === main program 797 | 798 | set(CMAKE_REQUIRED_LIBRARIES) 799 | 800 | if(NOT HDF5MPI_ROOT AND DEFINED ENV{HDF5MPI_ROOT}) 801 | set(HDF5MPI_ROOT $ENV{HDF5MPI_ROOT}) 802 | endif() 803 | 804 | if(NOT HDF5_ROOT) 805 | if(HDF5MPI_ROOT AND parallel IN_LIST HDF5_FIND_COMPONENTS) 806 | set(HDF5_ROOT ${HDF5MPI_ROOT}) 807 | elseif(DEFINED ENV{HDF5_ROOT}) 808 | set(HDF5_ROOT $ENV{HDF5_ROOT}) 809 | endif() 810 | endif() 811 | 812 | 813 | # --- library suffixes 814 | 815 | set(hdf5_lsuf lib hdf5/lib) # need explicit "lib" for self-built HDF5 816 | if(NOT HDF5_ROOT) 817 | list(PREPEND hdf5_lsuf hdf5/openmpi hdf5/mpich) # Ubuntu 818 | list(PREPEND hdf5_lsuf openmpi/lib mpich/lib) # CentOS 819 | if(NOT parallel IN_LIST HDF5_FIND_COMPONENTS) 820 | list(PREPEND hdf5_lsuf hdf5/serial) # Ubuntu 821 | endif() 822 | endif() 823 | 824 | # --- include and modules suffixes 825 | 826 | if(BUILD_SHARED_LIBS) 827 | set(hdf5_isuf shared include) 828 | set(hdf5_msuf shared include) 829 | else() 830 | set(hdf5_isuf static include) 831 | set(hdf5_msuf static include) 832 | endif() 833 | 834 | # Ubuntu 835 | list(PREPEND hdf5_isuf hdf5/openmpi hdf5/mpich) 836 | list(PREPEND hdf5_msuf hdf5/openmpi hdf5/mpich) 837 | 838 | if(NOT parallel IN_LIST HDF5_FIND_COMPONENTS) 839 | # Ubuntu 840 | list(PREPEND hdf5_isuf hdf5/serial) 841 | list(PREPEND hdf5_msuf hdf5/serial) 842 | endif() 843 | 844 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|AMD64)") 845 | list(APPEND hdf5_isuf openmpi-x86_64 mpich-x86_64) # CentOS 846 | elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64|arm64)") 847 | list(APPEND hdf5_isuf openmpi-aarch64 mpich-aarch64) # CentOS 848 | endif() 849 | 850 | if(NOT HDF5_ROOT AND CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") 851 | # CentOS paths 852 | if(parallel IN_LIST HDF5_FIND_COMPONENTS) 853 | list(PREPEND hdf5_msuf gfortran/modules/openmpi gfortran/modules/mpich) 854 | else() 855 | list(APPEND hdf5_msuf gfortran/modules) 856 | endif() 857 | endif() 858 | 859 | # --- binary prefix / suffix 860 | set(hdf5_binpref) 861 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux") 862 | set(hdf5_binpref /usr/lib64) 863 | endif() 864 | 865 | set(hdf5_binsuf bin) 866 | if(NOT HDF5_ROOT AND parallel IN_LIST HDF5_FIND_COMPONENTS) 867 | # CentOS paths 868 | list(APPEND hdf5_binsuf openmpi/bin mpich/bin) 869 | endif() 870 | 871 | # ---- 872 | # May not help, as we'd have to foreach() a priori names, like we already do with find_library() 873 | # find_package(hdf5 CONFIG) 874 | # ---- 875 | 876 | # C is always needed 877 | find_hdf5_c() 878 | 879 | # required libraries 880 | if(HDF5_C_FOUND) 881 | detect_config() 882 | endif(HDF5_C_FOUND) 883 | 884 | if(HDF5_C_FOUND AND CXX IN_LIST HDF5_FIND_COMPONENTS) 885 | find_hdf5_cxx() 886 | endif() 887 | 888 | if(HDF5_C_FOUND AND Fortran IN_LIST HDF5_FIND_COMPONENTS) 889 | find_hdf5_fortran() 890 | endif() 891 | 892 | # --- configure time checks 893 | # these checks avoid messy, confusing errors at build time 894 | check_hdf5_link() 895 | 896 | set(CMAKE_REQUIRED_LIBRARIES) 897 | set(CMAKE_REQUIRED_INCLUDES) 898 | 899 | include(FindPackageHandleStandardArgs) 900 | find_package_handle_standard_args(HDF5 901 | REQUIRED_VARS HDF5_C_LIBRARIES HDF5_links 902 | VERSION_VAR HDF5_VERSION 903 | HANDLE_COMPONENTS 904 | ) 905 | 906 | if(HDF5_FOUND) 907 | set(HDF5_INCLUDE_DIRS ${HDF5_Fortran_INCLUDE_DIR} ${HDF5_CXX_INCLUDE_DIR} ${HDF5_C_INCLUDE_DIR}) 908 | set(HDF5_LIBRARIES ${HDF5_Fortran_LIBRARIES} ${HDF5_CXX_LIBRARIES} ${HDF5_C_LIBRARIES}) 909 | 910 | if(NOT TARGET HDF5::HDF5) 911 | add_library(HDF5::HDF5 INTERFACE IMPORTED) 912 | set_property(TARGET HDF5::HDF5 PROPERTY INTERFACE_LINK_LIBRARIES "${HDF5_LIBRARIES}") 913 | set_property(TARGET HDF5::HDF5 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${HDF5_INCLUDE_DIRS}") 914 | 915 | if(hdf5_have_szip) 916 | if(IS_DIRECTORY "${SZIP_INCLUDE_DIR}") 917 | target_include_directories(HDF5::HDF5 INTERFACE ${SZIP_INCLUDE_DIR}) 918 | else() 919 | message(STATUS "FindHDF5: SZIP_INCLUDE_DIR ${SZIP_INCLUDE_DIR} is not a directory.") 920 | endif() 921 | endif() 922 | 923 | target_link_libraries(HDF5::HDF5 INTERFACE $<$:ZLIB::ZLIB>) 924 | 925 | if(hdf5_have_szip) 926 | if(EXISTS "${SZIP_LIBRARY}") 927 | target_link_libraries(HDF5::HDF5 INTERFACE ${SZIP_LIBRARY}) 928 | else() 929 | message(STATUS "FindHDF5: SZIP_LIBRARY ${SZIP_LIBRARY} is not a file.") 930 | endif() 931 | endif() 932 | 933 | target_link_libraries(HDF5::HDF5 INTERFACE 934 | ${CMAKE_THREAD_LIBS_INIT} 935 | ${CMAKE_DL_LIBS} 936 | $<$:m> 937 | ) 938 | endif() 939 | endif(HDF5_FOUND) 940 | 941 | mark_as_advanced(HDF5_Fortran_LIBRARY HDF5_Fortran_HL_LIBRARY 942 | HDF5_C_LIBRARY HDF5_C_HL_LIBRARY 943 | HDF5_CXX_LIBRARY HDF5_CXX_HL_LIBRARY 944 | HDF5_C_INCLUDE_DIR HDF5_CXX_INCLUDE_DIR HDF5_Fortran_INCLUDE_DIR) 945 | -------------------------------------------------------------------------------- /cmake/FindNetCDF.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #[=======================================================================[.rst: 5 | FindNetCDF 6 | ---------- 7 | 8 | Find NetCDF4 library 9 | 10 | based on: https://github.com/Kitware/VTK/blob/master/CMake/FindNetCDF.cmake 11 | in general, NetCDF requires C compiler even if only using Fortran 12 | 13 | Imported targets 14 | ^^^^^^^^^^^^^^^^ 15 | 16 | This module defines the following :prop_tgt:`IMPORTED` target: 17 | 18 | ``NetCDF::NetCDF_C`` 19 | NetCDF C / C++ libraries 20 | 21 | ``NetCDF::NetCDF_Fortran`` 22 | NetCDF Fortran libraries 23 | 24 | Result Variables 25 | ^^^^^^^^^^^^^^^^ 26 | 27 | This module defines the following variables: 28 | 29 | ``NetCDF_FOUND`` 30 | NetCDF4 is found (also ``NetCDF_C_FOUND`` and ``NetCDF_Fortran_FOUND``) 31 | ``NetCDF_C_LIBRARIES`` and ``NetCDF_Fortran_LIBRARIES 32 | uncached list of libraries (using full path name) to link against 33 | ``NetCDF_C_INCLUDE_DIRS`` and ``NetCDF_Fortran_INCLUDE_DIRS`` 34 | uncached list of libraries (using full path name) to include 35 | 36 | Search details: 37 | 38 | 1. look for CMake-build config files (for C / C++ only) 39 | 2. CMake manual search optionally using pkg-config (this step always needed for Fortran, and for C if step 1 fails) 40 | 41 | #]=======================================================================] 42 | 43 | include(CheckSourceCompiles) 44 | 45 | function(netcdf_c) 46 | 47 | find_path(NetCDF_C_INCLUDE_DIR 48 | NAMES netcdf.h 49 | DOC "NetCDF C include directory" 50 | ) 51 | 52 | if(NOT NetCDF_C_INCLUDE_DIR) 53 | return() 54 | endif() 55 | 56 | find_library(NetCDF_C_LIBRARY 57 | NAMES netcdf 58 | DOC "NetCDF C library" 59 | ) 60 | 61 | if(NOT NetCDF_C_LIBRARY) 62 | return() 63 | endif() 64 | 65 | set(CMAKE_REQUIRED_FLAGS) 66 | set(CMAKE_REQUIRED_INCLUDES ${NetCDF_C_INCLUDE_DIR}) 67 | 68 | set(CMAKE_REQUIRED_LIBRARIES ${NetCDF_C_LIBRARY}) 69 | if(ZLIB_FOUND) 70 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${ZLIB_LIBRARIES}) 71 | endif() 72 | 73 | list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) 74 | 75 | if(UNIX) 76 | list(APPEND CMAKE_REQUIRED_LIBRARIES m) 77 | endif() 78 | 79 | check_source_compiles(C 80 | [=[ 81 | #include 82 | #include 83 | 84 | int main(void){ 85 | printf("%s", nc_inq_libvers()); 86 | return 0; 87 | } 88 | ]=] 89 | NetCDF_C_links 90 | ) 91 | 92 | if(NOT NetCDF_C_links) 93 | return() 94 | endif() 95 | 96 | set(NetCDF_C_FOUND true PARENT_SCOPE) 97 | set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} PARENT_SCOPE) 98 | 99 | endfunction(netcdf_c) 100 | 101 | 102 | function(netcdf_fortran) 103 | 104 | find_path(NetCDF_Fortran_INCLUDE_DIR 105 | NAMES netcdf.mod 106 | HINTS ${NetCDF_C_INCLUDE_DIR} 107 | DOC "NetCDF Fortran Include" 108 | ) 109 | 110 | if(NOT NetCDF_Fortran_INCLUDE_DIR) 111 | return() 112 | endif() 113 | 114 | if(CMAKE_VERSION VERSION_LESS 3.20) 115 | get_filename_component(NetCDF_LIBDIR ${NetCDF_C_LIBRARY} DIRECTORY) 116 | else() 117 | cmake_path(GET NetCDF_C_LIBRARY PARENT_PATH NetCDF_LIBDIR) 118 | endif() 119 | 120 | find_library(NetCDF_Fortran_LIBRARY 121 | NAMES netcdff 122 | HINTS ${NetCDF_LIBDIR} 123 | DOC "NetCDF Fortran library" 124 | ) 125 | 126 | if(NOT NetCDF_Fortran_LIBRARY) 127 | return() 128 | endif() 129 | 130 | set(CMAKE_REQUIRED_FLAGS) 131 | set(CMAKE_REQUIRED_INCLUDES ${NetCDF_Fortran_INCLUDE_DIR}) 132 | list(INSERT CMAKE_REQUIRED_LIBRARIES 0 ${NetCDF_Fortran_LIBRARY}) 133 | 134 | check_source_compiles(Fortran 135 | "program a 136 | use netcdf 137 | implicit none 138 | end program" 139 | NetCDF_Fortran_links 140 | ) 141 | 142 | if(NOT NetCDF_Fortran_links) 143 | return() 144 | endif() 145 | 146 | set(NetCDF_Fortran_FOUND true PARENT_SCOPE) 147 | 148 | endfunction(netcdf_fortran) 149 | 150 | #============================================================ 151 | # main program 152 | 153 | find_package(ZLIB) 154 | find_package(Threads) 155 | # top scope so can be reused 156 | 157 | netcdf_c() 158 | 159 | set(_ncdf_req ${NetCDF_C_LIBRARY}) 160 | 161 | if(Fortran IN_LIST NetCDF_FIND_COMPONENTS) 162 | netcdf_fortran() 163 | list(APPEND _ncdf_req ${NetCDF_Fortran_LIBRARY}) 164 | endif() 165 | 166 | set(CMAKE_REQUIRED_FLAGS) 167 | set(CMAKE_REQUIRED_INCLUDES) 168 | set(CMAKE_REQUIRED_LIBRARIES) 169 | 170 | mark_as_advanced(NetCDF_C_INCLUDE_DIR NetCDF_Fortran_INCLUDE_DIR NetCDF_C_LIBRARY NetCDF_Fortran_LIBRARY) 171 | 172 | include(FindPackageHandleStandardArgs) 173 | find_package_handle_standard_args(NetCDF 174 | REQUIRED_VARS _ncdf_req 175 | HANDLE_COMPONENTS 176 | ) 177 | 178 | if(NetCDF_FOUND) 179 | set(NetCDF_C_INCLUDE_DIRS ${NetCDF_C_INCLUDE_DIR}) 180 | set(NetCDF_C_LIBRARIES ${NetCDF_C_LIBRARY}) 181 | 182 | if(NOT TARGET NetCDF::NetCDF_C) 183 | add_library(NetCDF::NetCDF_C INTERFACE IMPORTED) 184 | set_property(TARGET NetCDF::NetCDF_C PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${NetCDF_C_INCLUDE_DIR}") 185 | set_property(TARGET NetCDF::NetCDF_C PROPERTY INTERFACE_LINK_LIBRARIES "${NetCDF_C_LIBRARY}") 186 | 187 | target_link_libraries(NetCDF::NetCDF_C INTERFACE 188 | $<$:${ZLIB_LIBRARIES}> 189 | ${CMAKE_THREAD_LIBS_INIT} 190 | ${CMAKE_DL_LIBS} 191 | $<$:m> 192 | ) 193 | endif() 194 | 195 | if(NetCDF_Fortran_FOUND) 196 | set(NetCDF_Fortran_INCLUDE_DIRS ${NetCDF_Fortran_INCLUDE_DIR}) 197 | set(NetCDF_Fortran_LIBRARIES ${NetCDF_Fortran_LIBRARY}) 198 | if(NOT TARGET NetCDF::NetCDF_Fortran) 199 | add_library(NetCDF::NetCDF_Fortran INTERFACE IMPORTED) 200 | set_property(TARGET NetCDF::NetCDF_Fortran PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${NetCDF_Fortran_INCLUDE_DIR}") 201 | set_property(TARGET NetCDF::NetCDF_Fortran PROPERTY INTERFACE_LINK_LIBRARIES "${NetCDF_Fortran_LIBRARY}") 202 | 203 | target_link_libraries(NetCDF::NetCDF_Fortran INTERFACE 204 | $<$:${ZLIB_LIBRARIES}> 205 | ${CMAKE_THREAD_LIBS_INIT} 206 | ${CMAKE_DL_LIBS} 207 | $<$:m> 208 | ) 209 | target_link_libraries(NetCDF::NetCDF_Fortran INTERFACE NetCDF::NetCDF_C) 210 | endif() 211 | endif() 212 | 213 | 214 | endif() 215 | -------------------------------------------------------------------------------- /cmake/Modules/CodeCoverage.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 - 2017, Lars Bilke 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, 5 | # are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its contributors 15 | # may be used to endorse or promote products derived from this software without 16 | # specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # CHANGES: 30 | # 31 | # 2012-01-31, Lars Bilke 32 | # - Enable Code Coverage 33 | # 34 | # 2013-09-17, Joakim Söderberg 35 | # - Added support for Clang. 36 | # - Some additional usage instructions. 37 | # 38 | # 2016-02-03, Lars Bilke 39 | # - Refactored functions to use named parameters 40 | # 41 | # 2017-06-02, Lars Bilke 42 | # - Merged with modified version from github.com/ufz/ogs 43 | # 44 | # 2019-05-06, Anatolii Kurotych 45 | # - Remove unnecessary --coverage flag 46 | # 47 | # 2019-12-13, FeRD (Frank Dana) 48 | # - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor 49 | # of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. 50 | # - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY 51 | # - All setup functions: accept BASE_DIRECTORY, EXCLUDE list 52 | # - Set lcov basedir with -b argument 53 | # - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be 54 | # overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) 55 | # - Delete output dir, .info file on 'make clean' 56 | # - Remove Python detection, since version mismatches will break gcovr 57 | # - Minor cleanup (lowercase function names, update examples...) 58 | # 59 | # 2019-12-19, FeRD (Frank Dana) 60 | # - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets 61 | # 62 | # 2020-01-19, Bob Apthorpe 63 | # - Added gfortran support 64 | # 65 | # 2020-02-17, FeRD (Frank Dana) 66 | # - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters 67 | # in EXCLUDEs, and remove manual escaping from gcovr targets 68 | # 69 | # 2021-01-19, Robin Mueller 70 | # - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run 71 | # - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional 72 | # flags to the gcovr command 73 | # 74 | # 2020-05-04, Mihchael Davis 75 | # - Add -fprofile-abs-path to make gcno files contain absolute paths 76 | # - Fix BASE_DIRECTORY not working when defined 77 | # - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines 78 | # 79 | # 2021-05-10, Martin Stump 80 | # - Check if the generator is multi-config before warning about non-Debug builds 81 | # 82 | # USAGE: 83 | # 84 | # 1. Copy this file into your cmake modules path. 85 | # 86 | # 2. Add the following line to your CMakeLists.txt (best inside an if-condition 87 | # using a CMake option() to enable it just optionally): 88 | # include(CodeCoverage) 89 | # 90 | # 3. Append necessary compiler flags: 91 | # append_coverage_compiler_flags() 92 | # 93 | # 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og 94 | # 95 | # 4. If you need to exclude additional directories from the report, specify them 96 | # using full paths in the COVERAGE_EXCLUDES variable before calling 97 | # setup_target_for_coverage_*(). 98 | # Example: 99 | # set(COVERAGE_EXCLUDES 100 | # '${PROJECT_SOURCE_DIR}/src/dir1/*' 101 | # '/path/to/my/src/dir2/*') 102 | # Or, use the EXCLUDE argument to setup_target_for_coverage_*(). 103 | # Example: 104 | # setup_target_for_coverage_lcov( 105 | # NAME coverage 106 | # EXECUTABLE testrunner 107 | # EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") 108 | # 109 | # 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set 110 | # relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) 111 | # Example: 112 | # set(COVERAGE_EXCLUDES "dir1/*") 113 | # setup_target_for_coverage_gcovr_html( 114 | # NAME coverage 115 | # EXECUTABLE testrunner 116 | # BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" 117 | # EXCLUDE "dir2/*") 118 | # 119 | # 5. Use the functions described below to create a custom make target which 120 | # runs your test executable and produces a code coverage report. 121 | # 122 | # 6. Build a Debug build: 123 | # cmake -DCMAKE_BUILD_TYPE=Debug .. 124 | # make 125 | # make my_coverage_target 126 | # 127 | 128 | include(CMakeParseArguments) 129 | 130 | option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) 131 | 132 | # Check prereqs 133 | find_program( GCOV_PATH gcov ) 134 | find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) 135 | find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) 136 | find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) 137 | find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) 138 | find_program( CPPFILT_PATH NAMES c++filt ) 139 | 140 | if(NOT GCOV_PATH) 141 | message(FATAL_ERROR "gcov not found! Aborting...") 142 | endif() # NOT GCOV_PATH 143 | 144 | get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 145 | list(GET LANGUAGES 0 LANG) 146 | 147 | if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") 148 | if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) 149 | message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") 150 | endif() 151 | elseif(NOT CMAKE_COMPILER_IS_GNUCXX) 152 | if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") 153 | # Do nothing; exit conditional without error if true 154 | elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") 155 | # Do nothing; exit conditional without error if true 156 | else() 157 | message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 158 | endif() 159 | endif() 160 | 161 | set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage" 162 | CACHE INTERNAL "") 163 | if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 164 | include(CheckCXXCompilerFlag) 165 | check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) 166 | if(HAVE_fprofile_abs_path) 167 | set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") 168 | endif() 169 | endif() 170 | 171 | set(CMAKE_Fortran_FLAGS_COVERAGE 172 | ${COVERAGE_COMPILER_FLAGS} 173 | CACHE STRING "Flags used by the Fortran compiler during coverage builds." 174 | FORCE ) 175 | set(CMAKE_CXX_FLAGS_COVERAGE 176 | ${COVERAGE_COMPILER_FLAGS} 177 | CACHE STRING "Flags used by the C++ compiler during coverage builds." 178 | FORCE ) 179 | set(CMAKE_C_FLAGS_COVERAGE 180 | ${COVERAGE_COMPILER_FLAGS} 181 | CACHE STRING "Flags used by the C compiler during coverage builds." 182 | FORCE ) 183 | set(CMAKE_EXE_LINKER_FLAGS_COVERAGE 184 | "" 185 | CACHE STRING "Flags used for linking binaries during coverage builds." 186 | FORCE ) 187 | set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 188 | "" 189 | CACHE STRING "Flags used by the shared libraries linker during coverage builds." 190 | FORCE ) 191 | mark_as_advanced( 192 | CMAKE_Fortran_FLAGS_COVERAGE 193 | CMAKE_CXX_FLAGS_COVERAGE 194 | CMAKE_C_FLAGS_COVERAGE 195 | CMAKE_EXE_LINKER_FLAGS_COVERAGE 196 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 197 | 198 | get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 199 | if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) 200 | message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") 201 | endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) 202 | 203 | if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") 204 | link_libraries(gcov) 205 | endif() 206 | 207 | # Defines a target for running and collection code coverage information 208 | # Builds dependencies, runs the given executable and outputs reports. 209 | # NOTE! The executable should always have a ZERO as exit code otherwise 210 | # the coverage generation will not complete. 211 | # 212 | # setup_target_for_coverage_lcov( 213 | # NAME testrunner_coverage # New target name 214 | # EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 215 | # DEPENDENCIES testrunner # Dependencies to build first 216 | # BASE_DIRECTORY "../" # Base directory for report 217 | # # (defaults to PROJECT_SOURCE_DIR) 218 | # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative 219 | # # to BASE_DIRECTORY, with CMake 3.4+) 220 | # NO_DEMANGLE # Don't demangle C++ symbols 221 | # # even if c++filt is found 222 | # ) 223 | function(setup_target_for_coverage_lcov) 224 | 225 | set(options NO_DEMANGLE) 226 | set(oneValueArgs BASE_DIRECTORY NAME) 227 | set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) 228 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 229 | 230 | if(NOT LCOV_PATH) 231 | message(FATAL_ERROR "lcov not found! Aborting...") 232 | endif() # NOT LCOV_PATH 233 | 234 | if(NOT GENHTML_PATH) 235 | message(FATAL_ERROR "genhtml not found! Aborting...") 236 | endif() # NOT GENHTML_PATH 237 | 238 | # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 239 | if(DEFINED Coverage_BASE_DIRECTORY) 240 | get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 241 | else() 242 | set(BASEDIR ${PROJECT_SOURCE_DIR}) 243 | endif() 244 | 245 | # Collect excludes (CMake 3.4+: Also compute absolute paths) 246 | set(LCOV_EXCLUDES "") 247 | foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) 248 | if(CMAKE_VERSION VERSION_GREATER 3.4) 249 | get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) 250 | endif() 251 | list(APPEND LCOV_EXCLUDES "${EXCLUDE}") 252 | endforeach() 253 | list(REMOVE_DUPLICATES LCOV_EXCLUDES) 254 | 255 | # Conditional arguments 256 | if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) 257 | set(GENHTML_EXTRA_ARGS "--demangle-cpp") 258 | endif() 259 | 260 | # Setting up commands which will be run to generate coverage data. 261 | # Cleanup lcov 262 | set(LCOV_CLEAN_CMD 263 | ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . 264 | -b ${BASEDIR} --zerocounters 265 | ) 266 | # Create baseline to make sure untouched files show up in the report 267 | set(LCOV_BASELINE_CMD 268 | ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b 269 | ${BASEDIR} -o ${Coverage_NAME}.base 270 | ) 271 | # Run tests 272 | set(LCOV_EXEC_TESTS_CMD 273 | ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} 274 | ) 275 | # Capturing lcov counters and generating report 276 | set(LCOV_CAPTURE_CMD 277 | ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b 278 | ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture 279 | ) 280 | # add baseline counters 281 | set(LCOV_BASELINE_COUNT_CMD 282 | ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base 283 | -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total 284 | ) 285 | # filter collected data to final coverage report 286 | set(LCOV_FILTER_CMD 287 | ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove 288 | ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info 289 | ) 290 | # Generate HTML output 291 | set(LCOV_GEN_HTML_CMD 292 | ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o 293 | ${Coverage_NAME} ${Coverage_NAME}.info 294 | ) 295 | 296 | 297 | if(CODE_COVERAGE_VERBOSE) 298 | message(STATUS "Executed command report") 299 | message(STATUS "Command to clean up lcov: ") 300 | string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") 301 | message(STATUS "${LCOV_CLEAN_CMD_SPACED}") 302 | 303 | message(STATUS "Command to create baseline: ") 304 | string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") 305 | message(STATUS "${LCOV_BASELINE_CMD_SPACED}") 306 | 307 | message(STATUS "Command to run the tests: ") 308 | string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") 309 | message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") 310 | 311 | message(STATUS "Command to capture counters and generate report: ") 312 | string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") 313 | message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") 314 | 315 | message(STATUS "Command to add baseline counters: ") 316 | string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") 317 | message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") 318 | 319 | message(STATUS "Command to filter collected data: ") 320 | string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") 321 | message(STATUS "${LCOV_FILTER_CMD_SPACED}") 322 | 323 | message(STATUS "Command to generate lcov HTML output: ") 324 | string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") 325 | message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") 326 | endif() 327 | 328 | # Setup target 329 | add_custom_target(${Coverage_NAME} 330 | COMMAND ${LCOV_CLEAN_CMD} 331 | COMMAND ${LCOV_BASELINE_CMD} 332 | COMMAND ${LCOV_EXEC_TESTS_CMD} 333 | COMMAND ${LCOV_CAPTURE_CMD} 334 | COMMAND ${LCOV_BASELINE_COUNT_CMD} 335 | COMMAND ${LCOV_FILTER_CMD} 336 | COMMAND ${LCOV_GEN_HTML_CMD} 337 | 338 | # Set output files as GENERATED (will be removed on 'make clean') 339 | BYPRODUCTS 340 | ${Coverage_NAME}.base 341 | ${Coverage_NAME}.capture 342 | ${Coverage_NAME}.total 343 | ${Coverage_NAME}.info 344 | ${Coverage_NAME}/index.html 345 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 346 | DEPENDS ${Coverage_DEPENDENCIES} 347 | VERBATIM # Protect arguments to commands 348 | COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 349 | ) 350 | 351 | # Show where to find the lcov info report 352 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 353 | COMMAND ; 354 | COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." 355 | ) 356 | 357 | # Show info where to find the report 358 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 359 | COMMAND ; 360 | COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." 361 | ) 362 | 363 | endfunction() # setup_target_for_coverage_lcov 364 | 365 | # Defines a target for running and collection code coverage information 366 | # Builds dependencies, runs the given executable and outputs reports. 367 | # NOTE! The executable should always have a ZERO as exit code otherwise 368 | # the coverage generation will not complete. 369 | # 370 | # setup_target_for_coverage_gcovr_xml( 371 | # NAME ctest_coverage # New target name 372 | # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 373 | # DEPENDENCIES executable_target # Dependencies to build first 374 | # BASE_DIRECTORY "../" # Base directory for report 375 | # # (defaults to PROJECT_SOURCE_DIR) 376 | # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative 377 | # # to BASE_DIRECTORY, with CMake 3.4+) 378 | # ) 379 | # The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the 380 | # GCVOR command. 381 | function(setup_target_for_coverage_gcovr_xml) 382 | 383 | set(options NONE) 384 | set(oneValueArgs BASE_DIRECTORY NAME) 385 | set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) 386 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 387 | 388 | if(NOT GCOVR_PATH) 389 | message(FATAL_ERROR "gcovr not found! Aborting...") 390 | endif() # NOT GCOVR_PATH 391 | 392 | # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 393 | if(DEFINED Coverage_BASE_DIRECTORY) 394 | get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 395 | else() 396 | set(BASEDIR ${PROJECT_SOURCE_DIR}) 397 | endif() 398 | 399 | # Collect excludes (CMake 3.4+: Also compute absolute paths) 400 | set(GCOVR_EXCLUDES "") 401 | foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) 402 | if(CMAKE_VERSION VERSION_GREATER 3.4) 403 | get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) 404 | endif() 405 | list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") 406 | endforeach() 407 | list(REMOVE_DUPLICATES GCOVR_EXCLUDES) 408 | 409 | # Combine excludes to several -e arguments 410 | set(GCOVR_EXCLUDE_ARGS "") 411 | foreach(EXCLUDE ${GCOVR_EXCLUDES}) 412 | list(APPEND GCOVR_EXCLUDE_ARGS "-e") 413 | list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") 414 | endforeach() 415 | 416 | # Set up commands which will be run to generate coverage data 417 | # Run tests 418 | set(GCOVR_XML_EXEC_TESTS_CMD 419 | ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} 420 | ) 421 | # Running gcovr 422 | set(GCOVR_XML_CMD 423 | ${GCOVR_PATH} --xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS} 424 | --object-directory=${PROJECT_BINARY_DIR} -o ${Coverage_NAME}.xml 425 | ) 426 | 427 | if(CODE_COVERAGE_VERBOSE) 428 | message(STATUS "Executed command report") 429 | 430 | message(STATUS "Command to run tests: ") 431 | string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") 432 | message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") 433 | 434 | message(STATUS "Command to generate gcovr XML coverage data: ") 435 | string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") 436 | message(STATUS "${GCOVR_XML_CMD_SPACED}") 437 | endif() 438 | 439 | add_custom_target(${Coverage_NAME} 440 | COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} 441 | COMMAND ${GCOVR_XML_CMD} 442 | 443 | BYPRODUCTS ${Coverage_NAME}.xml 444 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 445 | DEPENDS ${Coverage_DEPENDENCIES} 446 | VERBATIM # Protect arguments to commands 447 | COMMENT "Running gcovr to produce Cobertura code coverage report." 448 | ) 449 | 450 | # Show info where to find the report 451 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 452 | COMMAND ; 453 | COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." 454 | ) 455 | endfunction() # setup_target_for_coverage_gcovr_xml 456 | 457 | # Defines a target for running and collection code coverage information 458 | # Builds dependencies, runs the given executable and outputs reports. 459 | # NOTE! The executable should always have a ZERO as exit code otherwise 460 | # the coverage generation will not complete. 461 | # 462 | # setup_target_for_coverage_gcovr_html( 463 | # NAME ctest_coverage # New target name 464 | # EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 465 | # DEPENDENCIES executable_target # Dependencies to build first 466 | # BASE_DIRECTORY "../" # Base directory for report 467 | # # (defaults to PROJECT_SOURCE_DIR) 468 | # EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative 469 | # # to BASE_DIRECTORY, with CMake 3.4+) 470 | # ) 471 | # The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the 472 | # GCVOR command. 473 | function(setup_target_for_coverage_gcovr_html) 474 | 475 | set(options NONE) 476 | set(oneValueArgs BASE_DIRECTORY NAME) 477 | set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) 478 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 479 | 480 | if(NOT GCOVR_PATH) 481 | message(FATAL_ERROR "gcovr not found! Aborting...") 482 | endif() # NOT GCOVR_PATH 483 | 484 | # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 485 | if(DEFINED Coverage_BASE_DIRECTORY) 486 | get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 487 | else() 488 | set(BASEDIR ${PROJECT_SOURCE_DIR}) 489 | endif() 490 | 491 | # Collect excludes (CMake 3.4+: Also compute absolute paths) 492 | set(GCOVR_EXCLUDES "") 493 | foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) 494 | if(CMAKE_VERSION VERSION_GREATER 3.4) 495 | get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) 496 | endif() 497 | list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") 498 | endforeach() 499 | list(REMOVE_DUPLICATES GCOVR_EXCLUDES) 500 | 501 | # Combine excludes to several -e arguments 502 | set(GCOVR_EXCLUDE_ARGS "") 503 | foreach(EXCLUDE ${GCOVR_EXCLUDES}) 504 | list(APPEND GCOVR_EXCLUDE_ARGS "-e") 505 | list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") 506 | endforeach() 507 | 508 | # Set up commands which will be run to generate coverage data 509 | # Run tests 510 | set(GCOVR_HTML_EXEC_TESTS_CMD 511 | ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} 512 | ) 513 | # Create folder 514 | set(GCOVR_HTML_FOLDER_CMD 515 | ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} 516 | ) 517 | # Running gcovr 518 | set(GCOVR_HTML_CMD 519 | ${GCOVR_PATH} --html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} 520 | ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} 521 | -o ${Coverage_NAME}/index.html 522 | ) 523 | 524 | if(CODE_COVERAGE_VERBOSE) 525 | message(STATUS "Executed command report") 526 | 527 | message(STATUS "Command to run tests: ") 528 | string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") 529 | message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") 530 | 531 | message(STATUS "Command to create a folder: ") 532 | string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") 533 | message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") 534 | 535 | message(STATUS "Command to generate gcovr HTML coverage data: ") 536 | string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") 537 | message(STATUS "${GCOVR_HTML_CMD_SPACED}") 538 | endif() 539 | 540 | add_custom_target(${Coverage_NAME} 541 | COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} 542 | COMMAND ${GCOVR_HTML_FOLDER_CMD} 543 | COMMAND ${GCOVR_HTML_CMD} 544 | 545 | BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory 546 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 547 | DEPENDS ${Coverage_DEPENDENCIES} 548 | VERBATIM # Protect arguments to commands 549 | COMMENT "Running gcovr to produce HTML code coverage report." 550 | ) 551 | 552 | # Show info where to find the report 553 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 554 | COMMAND ; 555 | COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." 556 | ) 557 | 558 | endfunction() # setup_target_for_coverage_gcovr_html 559 | 560 | # Defines a target for running and collection code coverage information 561 | # Builds dependencies, runs the given executable and outputs reports. 562 | # NOTE! The executable should always have a ZERO as exit code otherwise 563 | # the coverage generation will not complete. 564 | # 565 | # setup_target_for_coverage_fastcov( 566 | # NAME testrunner_coverage # New target name 567 | # EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR 568 | # DEPENDENCIES testrunner # Dependencies to build first 569 | # BASE_DIRECTORY "../" # Base directory for report 570 | # # (defaults to PROJECT_SOURCE_DIR) 571 | # EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. 572 | # NO_DEMANGLE # Don't demangle C++ symbols 573 | # # even if c++filt is found 574 | # SKIP_HTML # Don't create html report 575 | # POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths 576 | # ) 577 | function(setup_target_for_coverage_fastcov) 578 | 579 | set(options NO_DEMANGLE SKIP_HTML) 580 | set(oneValueArgs BASE_DIRECTORY NAME) 581 | set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) 582 | cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 583 | 584 | if(NOT FASTCOV_PATH) 585 | message(FATAL_ERROR "fastcov not found! Aborting...") 586 | endif() 587 | 588 | if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) 589 | message(FATAL_ERROR "genhtml not found! Aborting...") 590 | endif() 591 | 592 | # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR 593 | if(Coverage_BASE_DIRECTORY) 594 | get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) 595 | else() 596 | set(BASEDIR ${PROJECT_SOURCE_DIR}) 597 | endif() 598 | 599 | # Collect excludes (Patterns, not paths, for fastcov) 600 | set(FASTCOV_EXCLUDES "") 601 | foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) 602 | list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") 603 | endforeach() 604 | list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) 605 | 606 | # Conditional arguments 607 | if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) 608 | set(GENHTML_EXTRA_ARGS "--demangle-cpp") 609 | endif() 610 | 611 | # Set up commands which will be run to generate coverage data 612 | set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) 613 | 614 | set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} 615 | --search-directory ${BASEDIR} 616 | --process-gcno 617 | --output ${Coverage_NAME}.json 618 | --exclude ${FASTCOV_EXCLUDES} 619 | --exclude ${FASTCOV_EXCLUDES} 620 | ) 621 | 622 | set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} 623 | -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info 624 | ) 625 | 626 | if(Coverage_SKIP_HTML) 627 | set(FASTCOV_HTML_CMD ";") 628 | else() 629 | set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} 630 | -o ${Coverage_NAME} ${Coverage_NAME}.info 631 | ) 632 | endif() 633 | 634 | set(FASTCOV_POST_CMD ";") 635 | if(Coverage_POST_CMD) 636 | set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) 637 | endif() 638 | 639 | if(CODE_COVERAGE_VERBOSE) 640 | message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") 641 | 642 | message(" Running tests:") 643 | string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") 644 | message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") 645 | 646 | message(" Capturing fastcov counters and generating report:") 647 | string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") 648 | message(" ${FASTCOV_CAPTURE_CMD_SPACED}") 649 | 650 | message(" Converting fastcov .json to lcov .info:") 651 | string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") 652 | message(" ${FASTCOV_CONVERT_CMD_SPACED}") 653 | 654 | if(NOT Coverage_SKIP_HTML) 655 | message(" Generating HTML report: ") 656 | string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") 657 | message(" ${FASTCOV_HTML_CMD_SPACED}") 658 | endif() 659 | if(Coverage_POST_CMD) 660 | message(" Running post command: ") 661 | string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") 662 | message(" ${FASTCOV_POST_CMD_SPACED}") 663 | endif() 664 | endif() 665 | 666 | # Setup target 667 | add_custom_target(${Coverage_NAME} 668 | 669 | # Cleanup fastcov 670 | COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} 671 | --search-directory ${BASEDIR} 672 | --zerocounters 673 | 674 | COMMAND ${FASTCOV_EXEC_TESTS_CMD} 675 | COMMAND ${FASTCOV_CAPTURE_CMD} 676 | COMMAND ${FASTCOV_CONVERT_CMD} 677 | COMMAND ${FASTCOV_HTML_CMD} 678 | COMMAND ${FASTCOV_POST_CMD} 679 | 680 | # Set output files as GENERATED (will be removed on 'make clean') 681 | BYPRODUCTS 682 | ${Coverage_NAME}.info 683 | ${Coverage_NAME}.json 684 | ${Coverage_NAME}/index.html # report directory 685 | 686 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 687 | DEPENDS ${Coverage_DEPENDENCIES} 688 | VERBATIM # Protect arguments to commands 689 | COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." 690 | ) 691 | 692 | set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") 693 | if(NOT Coverage_SKIP_HTML) 694 | string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") 695 | endif() 696 | # Show where to find the fastcov info report 697 | add_custom_command(TARGET ${Coverage_NAME} POST_BUILD 698 | COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} 699 | ) 700 | 701 | endfunction() # setup_target_for_coverage_fastcov 702 | 703 | function(append_coverage_compiler_flags) 704 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 705 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 706 | set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) 707 | message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") 708 | endfunction() # append_coverage_compiler_flags 709 | -------------------------------------------------------------------------------- /cmake/compilers.cmake: -------------------------------------------------------------------------------- 1 | # --- C compile flags 2 | if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU|^Intel") 3 | add_compile_options( 4 | "$<$,$>:-Wextra>" 5 | "$<$:-Wall>" 6 | "$<$:-Werror=implicit-function-declaration>" 7 | ) 8 | elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC") 9 | add_compile_options("$<$:/W3>") 10 | endif() 11 | 12 | if(WIN32) 13 | if(CMAKE_C_COMPILER_ID MATCHES "^Intel|MSVC") 14 | add_compile_options($<$,$>:/Od>) 15 | endif() 16 | elseif(CMAKE_C_COMPILER_ID MATCHES "^Intel") 17 | add_compile_options($<$,$>:-O0>) 18 | endif() 19 | 20 | # --- Fortran compile flags 21 | if(CMAKE_Fortran_COMPILER_ID MATCHES "^Intel") 22 | 23 | add_compile_options( 24 | "$<$:-warn>" 25 | "$<$,$>:-traceback;-check;-debug>" 26 | ) 27 | 28 | if(WIN32) 29 | add_compile_options($<$,$>:/Od>) 30 | else() 31 | add_compile_options($<$,$>:-O0>) 32 | endif() 33 | 34 | elseif(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") 35 | 36 | add_compile_options( 37 | "$<$:-Wall;-fimplicit-none;-Wno-maybe-uninitialized>" 38 | "$<$,$>:-Wextra;-fcheck=all;-Werror=array-bounds>" 39 | "$<$,$>:-fno-backtrace>" 40 | ) 41 | 42 | endif() 43 | -------------------------------------------------------------------------------- /cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) 6 | 7 | find_dependency(NetCDF COMPONENTS Fortran) 8 | 9 | include(${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake) 10 | 11 | check_required_components(@PROJECT_NAME@) 12 | -------------------------------------------------------------------------------- /cmake/hdf5.cmake: -------------------------------------------------------------------------------- 1 | # builds HDF5 library from scratch 2 | # note: the use of "lib" vs. CMAKE_*_LIBRARY_PREFIX is deliberate based on HDF5 3 | # across Intel Fortran on Windows (MSVC-like) vs. Gfortran on Windows vs. Linux. 4 | include(GNUInstallDirs) 5 | include(ExternalProject) 6 | 7 | if(hdf5_parallel) 8 | find_package(MPI REQUIRED COMPONENTS C) 9 | endif() 10 | 11 | # pass MPI hints to HDF5 12 | if(NOT MPI_ROOT AND DEFINED ENV{MPI_ROOT}) 13 | set(MPI_ROOT $ENV{MPI_ROOT}) 14 | endif() 15 | 16 | set(HDF5_LIBRARIES) 17 | foreach(_name IN ITEMS hdf5_hl_fortran hdf5_hl_f90cstub hdf5_fortran hdf5_f90cstub hdf5_hl hdf5) 18 | # need ${CMAKE_INSTALL_PREFIX}/lib as HDF5 doesn't use GNUInstallDirs 19 | if(BUILD_SHARED_LIBS) 20 | if(WIN32) 21 | list(APPEND HDF5_LIBRARIES ${CMAKE_INSTALL_FULL_BINDIR}/lib${_name}${CMAKE_SHARED_LIBRARY_SUFFIX}) 22 | else() 23 | list(APPEND HDF5_LIBRARIES ${CMAKE_INSTALL_PREFIX}/lib/lib${_name}${CMAKE_SHARED_LIBRARY_SUFFIX}) 24 | endif() 25 | else() 26 | list(APPEND HDF5_LIBRARIES ${CMAKE_INSTALL_PREFIX}/lib/lib${_name}${CMAKE_STATIC_LIBRARY_SUFFIX}) 27 | endif() 28 | endforeach() 29 | 30 | set(HDF5_INCLUDE_DIRS ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 31 | 32 | file(READ ${CMAKE_CURRENT_LIST_DIR}/libraries.json json) 33 | 34 | # --- Zlib 35 | if(TARGET ZLIB::ZLIB) 36 | add_custom_target(ZLIB) 37 | else() 38 | include(${CMAKE_CURRENT_LIST_DIR}/zlib.cmake) 39 | endif() 40 | 41 | # --- HDF5 42 | # https://forum.hdfgroup.org/t/issues-when-using-hdf5-as-a-git-submodule-and-using-cmake-with-add-subdirectory/7189/2 43 | 44 | set(hdf5_cmake_args 45 | -DHDF5_ENABLE_Z_LIB_SUPPORT:BOOL=ON 46 | -DZLIB_USE_EXTERNAL:BOOL=OFF 47 | -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} 48 | -DHDF5_GENERATE_HEADERS:BOOL=false 49 | -DHDF5_DISABLE_COMPILER_WARNINGS:BOOL=true 50 | -DBUILD_STATIC_LIBS:BOOL=$> 51 | -DBUILD_SHARED_LIBS:BOOL=${BUILD_SHARED_LIBS} 52 | -DCMAKE_BUILD_TYPE=Release 53 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 54 | -DCMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER} 55 | -DHDF5_BUILD_FORTRAN:BOOL=true 56 | -DHDF5_BUILD_CPP_LIB:BOOL=false 57 | -DBUILD_TESTING:BOOL=false 58 | -DHDF5_BUILD_EXAMPLES:BOOL=false 59 | -DHDF5_BUILD_TOOLS:BOOL=true 60 | -DHDF5_ENABLE_PARALLEL:BOOL=$ 61 | -DHDF5_BUILD_PARALLEL_TOOLS:BOOL=$ 62 | ) 63 | 64 | #-DHDF5_USE_GNU_DIRS:BOOL=ON # not yet, new for 1.14 65 | 66 | if(MPI_ROOT) 67 | list(APPEND hdf5_cmake_args -DMPI_ROOT:PATH=${MPI_ROOT}) 68 | endif() 69 | 70 | if(NOT hdf5_url) 71 | string(JSON hdf5_url GET ${json} hdf5 url) 72 | endif() 73 | 74 | # Get HDF5 version from underscore-separated version in URL 75 | 76 | if(hdf5_url MATCHES "hdf5_([0-9]+\.[0-9]+\.[0-9]+)\.") 77 | set(HDF5_VERSION "${CMAKE_MATCH_1}") 78 | elseif(hdf5_url MATCHES "hdf5-([0-9]+\_[0-9]+\_[0-9]+)") 79 | string(REPLACE "_" "." HDF5_VERSION "${CMAKE_MATCH_1}") 80 | else() 81 | message(FATAL_ERROR "Could not determine HDF5 version from URL: ${hdf5_url}") 82 | endif() 83 | 84 | message(STATUS "Building HDF5 version ${HDF5_VERSION}") 85 | 86 | ExternalProject_Add(HDF5 87 | URL ${hdf5_url} 88 | CMAKE_ARGS ${hdf5_cmake_args} 89 | BUILD_BYPRODUCTS ${HDF5_LIBRARIES} 90 | DEPENDS ZLIB 91 | CONFIGURE_HANDLED_BY_BUILD ON 92 | USES_TERMINAL_DOWNLOAD true 93 | USES_TERMINAL_UPDATE true 94 | USES_TERMINAL_PATCH true 95 | USES_TERMINAL_CONFIGURE true 96 | USES_TERMINAL_BUILD true 97 | USES_TERMINAL_INSTALL true 98 | USES_TERMINAL_TEST true 99 | ) 100 | 101 | # --- imported target 102 | 103 | file(MAKE_DIRECTORY ${HDF5_INCLUDE_DIRS}) 104 | # avoid race condition 105 | 106 | # this GLOBAL is required to be visible to parent projects 107 | add_library(HDF5::HDF5 INTERFACE IMPORTED GLOBAL) 108 | target_include_directories(HDF5::HDF5 INTERFACE "${HDF5_INCLUDE_DIRS}") 109 | target_link_libraries(HDF5::HDF5 INTERFACE "${HDF5_LIBRARIES}") 110 | 111 | add_dependencies(HDF5::HDF5 HDF5) 112 | 113 | # --- HDF5 parallel compression support 114 | # this could be improved by making it an ExternalProject post-build step instead of assumptions made here 115 | if(hdf5_parallel) 116 | if(MPI_VERSION VERSION_GREATER_EQUAL 3) 117 | message(STATUS "Building HDF5-MPI: MPI-3 available, assuming HDF5 parallel compression enabled") 118 | set(hdf5_parallel_compression ".true." CACHE STRING "configure variable for HDF5 parallel compression") 119 | else() 120 | message(STATUS "Building HDF5-MPI: MPI-3 NOT available => HDF5 parallel compression disabled") 121 | set(hdf5_parallel_compression ".false." CACHE STRING "configure variable for HDF5 parallel compression: MPI < 3") 122 | endif() 123 | endif(hdf5_parallel) 124 | 125 | # --- external deps 126 | find_package(Threads) 127 | 128 | target_link_libraries(HDF5::HDF5 INTERFACE 129 | ZLIB::ZLIB 130 | ${CMAKE_THREAD_LIBS_INIT} 131 | ${CMAKE_DL_LIBS} 132 | $<$:m> 133 | ) 134 | # libdl and libm are needed on some systems 135 | -------------------------------------------------------------------------------- /cmake/install.cmake: -------------------------------------------------------------------------------- 1 | # --- BOILERPLATE: install / packaging 2 | 3 | include(CMakePackageConfigHelpers) 4 | 5 | configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/config.cmake.in 6 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake 7 | INSTALL_DESTINATION cmake 8 | ) 9 | 10 | write_basic_package_version_file( 11 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake 12 | COMPATIBILITY SameMajorVersion 13 | ) 14 | 15 | install(EXPORT ${PROJECT_NAME}-targets 16 | NAMESPACE ${PROJECT_NAME}:: 17 | DESTINATION cmake 18 | ) 19 | 20 | install(FILES 21 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake 22 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake 23 | DESTINATION cmake 24 | ) 25 | 26 | # --- CPack 27 | 28 | set(CPACK_GENERATOR "TBZ2") 29 | set(CPACK_SOURCE_GENERATOR "TBZ2") 30 | set(CPACK_PACKAGE_VENDOR "Michael Hirsch") 31 | set(CPACK_PACKAGE_CONTACT "Michael Hirsch") 32 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libnetcdff-dev") 33 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 34 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 35 | 36 | # not .gitignore as its regex syntax is more advanced than CMake 37 | set(CPACK_SOURCE_IGNORE_FILES .git/ .github/ .vscode/ .mypy_cache/ _CPack_Packages/ 38 | ${CMAKE_BINARY_DIR}/ ${PROJECT_BINARY_DIR}/ 39 | archive/ concepts/ 40 | ) 41 | 42 | install(FILES ${CPACK_RESOURCE_FILE_README} ${CPACK_RESOURCE_FILE_LICENSE} 43 | DESTINATION share/docs/${PROJECT_NAME} 44 | ) 45 | 46 | include(CPack) 47 | -------------------------------------------------------------------------------- /cmake/libraries.json: -------------------------------------------------------------------------------- 1 | { 2 | "netcdfC": { 3 | "url": "https://github.com/Unidata/netcdf-c/archive/refs/tags/v4.9.3.tar.gz" 4 | }, 5 | "netcdfFortran": { 6 | "url": "https://github.com/Unidata/netcdf-fortran/archive/refs/tags/v4.6.2.tar.gz" 7 | }, 8 | "zlib": { 9 | "url" : "https://github.com/zlib-ng/zlib-ng/archive/refs/tags/2.2.4.tar.gz" 10 | }, 11 | "hdf5": { 12 | "url": "https://github.com/HDFGroup/hdf5/releases/download/hdf5_1.14.6/hdf5-1.14.6.tar.gz" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cmake/nc4fortran.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | 3 | find_package(NetCDF REQUIRED COMPONENTS Fortran) 4 | 5 | set(nc4fortran_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include) 6 | 7 | if(BUILD_SHARED_LIBS) 8 | if(WIN32) 9 | set(nc4fortran_LIBRARIES ${CMAKE_INSTALL_PREFIX}/bin/${CMAKE_SHARED_LIBRARY_PREFIX}nc4fortran${CMAKE_SHARED_LIBRARY_SUFFIX}) 10 | else() 11 | set(nc4fortran_LIBRARIES ${CMAKE_INSTALL_PREFIX}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}nc4fortran${CMAKE_SHARED_LIBRARY_SUFFIX}) 12 | endif() 13 | else() 14 | set(nc4fortran_LIBRARIES ${CMAKE_INSTALL_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}nc4fortran${CMAKE_STATIC_LIBRARY_SUFFIX}) 15 | endif() 16 | 17 | set(nc4fortran_cmake_args 18 | -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} 19 | -DCMAKE_PREFIX_PATH:PATH=${CMAKE_INSTALL_PREFIX} 20 | -DBUILD_SHARED_LIBS:BOOL=${BUILD_SHARED_LIBS} 21 | -DCMAKE_BUILD_TYPE=Release 22 | -DBUILD_TESTING:BOOL=false 23 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 24 | -DCMAKE_Fortran_COMPILER=${CMAKE_Fortran_COMPILER} 25 | ) 26 | 27 | ExternalProject_Add(NC4FORTRAN 28 | SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/.. 29 | CMAKE_ARGS ${nc4fortran_cmake_args} 30 | BUILD_BYPRODUCTS ${nc4fortran_LIBRARIES} 31 | CONFIGURE_HANDLED_BY_BUILD ON 32 | ) 33 | 34 | file(MAKE_DIRECTORY ${nc4fortran_INCLUDE_DIRS}) 35 | 36 | add_library(nc4fortran::nc4fortran INTERFACE IMPORTED GLOBAL) 37 | target_link_libraries(nc4fortran::nc4fortran INTERFACE ${nc4fortran_LIBRARIES} NetCDF::NetCDF_Fortran) 38 | target_include_directories(nc4fortran::nc4fortran INTERFACE ${nc4fortran_INCLUDE_DIRS}) 39 | 40 | # race condition for linking without this 41 | add_dependencies(nc4fortran::nc4fortran NC4FORTRAN) 42 | -------------------------------------------------------------------------------- /cmake/netcdf-c.cmake: -------------------------------------------------------------------------------- 1 | set(NetCDF_C_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include) 2 | 3 | set(netcdf_c_cmake_args 4 | -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} 5 | -DCMAKE_PREFIX_PATH:PATH=${CMAKE_INSTALL_PREFIX} 6 | -DCMAKE_BUILD_TYPE:STRING=Release 7 | -DBUILD_SHARED_LIBS:BOOL=ON 8 | -DNETCDF_ENABLE_PARALLEL4:BOOL=OFF 9 | -DNETCDF_ENABLE_PNETCDF:BOOL=OFF 10 | -DNETCDF_BUILD_UTILITIES:BOOL=OFF 11 | -DNETCDF_ENABLE_TESTS:BOOL=off 12 | -DBUILD_TESTING:BOOL=OFF 13 | -DNETCDF_ENABLE_HDF4:BOOL=OFF 14 | -DUSE_DAP:BOOL=off 15 | -DNETCDF_ENABLE_DAP:BOOL=OFF 16 | -DNETCDF_ENABLE_DAP2:BOOL=OFF 17 | -DNETCDF_ENABLE_DAP4:BOOL=OFF 18 | -DNETCDF_ENABLE_REMOTE_FUNCTIONALITY:BOOL=OFF 19 | -DNETCDF_ENABLE_BYTERANGE:BOOL=OFF 20 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 21 | -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON 22 | -DZLIB_LIBRARY:FILEPATH=${ZLIB_LIBRARIES} 23 | -DHDF5_ROOT:PATH=${HDF5_ROOT} 24 | ) 25 | # BUILD_SHARED_LIBS=on for netcdf-fortran symbol finding bug 26 | 27 | if(WIN32) 28 | set(NetCDF_C_LIBRARIES ${CMAKE_INSTALL_FULL_BINDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdf${CMAKE_SHARED_LIBRARY_SUFFIX}) 29 | else() 30 | set(NetCDF_C_LIBRARIES ${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdf${CMAKE_SHARED_LIBRARY_SUFFIX}) 31 | endif() 32 | 33 | set(NetCDF_C_INCLUDE_DIRS ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 34 | 35 | string(JSON netcdfC_url GET ${json} netcdfC url) 36 | 37 | 38 | ExternalProject_Add(NETCDF_C 39 | URL ${netcdfC_url} 40 | CONFIGURE_HANDLED_BY_BUILD TRUE 41 | CMAKE_ARGS ${netcdf_c_cmake_args} 42 | BUILD_BYPRODUCTS ${NetCDF_C_LIBRARIES} 43 | DEPENDS HDF5 44 | USES_TERMINAL_DOWNLOAD true 45 | USES_TERMINAL_UPDATE true 46 | USES_TERMINAL_PATCH true 47 | USES_TERMINAL_CONFIGURE true 48 | USES_TERMINAL_BUILD true 49 | USES_TERMINAL_INSTALL true 50 | USES_TERMINAL_TEST true 51 | ) 52 | 53 | # --- imported target 54 | 55 | file(MAKE_DIRECTORY ${NetCDF_C_INCLUDE_DIRS}) 56 | # avoid race condition 57 | 58 | # this GLOBAL is required to be visible via other project's FetchContent 59 | add_library(NetCDF::NetCDF_C INTERFACE IMPORTED GLOBAL) 60 | target_include_directories(NetCDF::NetCDF_C INTERFACE "${NetCDF_C_INCLUDE_DIRS}") 61 | target_link_libraries(NetCDF::NetCDF_C INTERFACE "${NetCDF_C_LIBRARIES}") 62 | 63 | add_dependencies(NetCDF::NetCDF_C NETCDF_C) 64 | -------------------------------------------------------------------------------- /cmake/netcdf.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | include(GNUInstallDirs) 3 | 4 | # need HDF5, NetCDF-C and NetCDF-Fortran 5 | # due to limitations of NetCDF-C and NetCDF-Fortran, as per their docs, 6 | # we MUST use shared libraries or they don't archive/link properly. 7 | 8 | file(READ ${CMAKE_CURRENT_LIST_DIR}/libraries.json json) 9 | 10 | if(NOT DEFINED HDF5_ROOT AND DEFINED ENV{HDF5_ROOT}) 11 | set(HDF5_ROOT $ENV{HDF5_ROOT}) 12 | endif() 13 | 14 | if(find_hdf5) 15 | find_package(HDF5 COMPONENTS C Fortran) 16 | endif() 17 | if(HDF5_FOUND) 18 | add_custom_target(HDF5) 19 | else() 20 | include(${CMAKE_CURRENT_LIST_DIR}/hdf5.cmake) 21 | endif() 22 | 23 | # --- NetCDF-C 24 | 25 | if(find_netcdf) 26 | find_package(NetCDF COMPONENTS C) 27 | endif() 28 | if(NetCDF_C_FOUND) 29 | add_custom_target(NETCDF_C) 30 | else() 31 | include(${CMAKE_CURRENT_LIST_DIR}/netcdf-c.cmake) 32 | endif() 33 | 34 | 35 | # --- NetCDF-Fortran 36 | 37 | set(netcdf_fortran_cmake_args 38 | -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} 39 | -DCMAKE_PREFIX_PATH:PATH=${CMAKE_INSTALL_PREFIX} 40 | -DnetCDF_LIBRARIES:FILEPATH=${NetCDF_C_LIBRARIES} 41 | -DnetCDF_INCLUDE_DIR:PATH=${NetCDF_C_INCLUDE_DIRS} 42 | -DCMAKE_BUILD_TYPE:STRING=Release 43 | -DBUILD_SHARED_LIBS:BOOL=ON 44 | -DNETCDF_ENABLE_TESTS:BOOL=OFF 45 | -DBUILD_EXAMPLES:BOOL=OFF 46 | -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} 47 | -DCMAKE_Fortran_COMPILER:FILEPATH=${CMAKE_Fortran_COMPILER} 48 | -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON 49 | -DHDF5_ROOT:PATH=${HDF5_ROOT} 50 | ) 51 | 52 | if(WIN32) 53 | set(NetCDF_Fortran_LIBRARIES ${CMAKE_INSTALL_FULL_BINDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdff${CMAKE_SHARED_LIBRARY_SUFFIX}) 54 | else() 55 | set(NetCDF_Fortran_LIBRARIES ${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}netcdff${CMAKE_SHARED_LIBRARY_SUFFIX}) 56 | endif() 57 | 58 | set(NetCDF_Fortran_INCLUDE_DIRS ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 59 | 60 | string(JSON netcdfFortran_url GET ${json} netcdfFortran url) 61 | 62 | ExternalProject_Add(NETCDF_FORTRAN 63 | URL ${netcdfFortran_url} 64 | CONFIGURE_HANDLED_BY_BUILD ON 65 | CMAKE_ARGS ${netcdf_fortran_cmake_args} 66 | BUILD_BYPRODUCTS ${NetCDF_Fortran_LIBRARIES} 67 | DEPENDS NETCDF_C 68 | USES_TERMINAL_DOWNLOAD true 69 | USES_TERMINAL_UPDATE true 70 | USES_TERMINAL_PATCH true 71 | USES_TERMINAL_CONFIGURE true 72 | USES_TERMINAL_BUILD true 73 | USES_TERMINAL_INSTALL true 74 | USES_TERMINAL_TEST true 75 | ) 76 | # BUILD_SHARED_LIBS=on for netcdf-fortran symbol finding bug 77 | # netCDEF_LIBRARIES and netCDF_INCLUDE_DIR from netcdf-fortran/CMakeLists.txt 78 | 79 | # --- imported target 80 | 81 | file(MAKE_DIRECTORY ${NetCDF_Fortran_INCLUDE_DIRS}) 82 | # avoid race condition 83 | 84 | # this GLOBAL is required to be visible via other project's FetchContent 85 | add_library(NetCDF::NetCDF_Fortran INTERFACE IMPORTED GLOBAL) 86 | target_include_directories(NetCDF::NetCDF_Fortran INTERFACE "${NetCDF_Fortran_INCLUDE_DIRS}") 87 | target_link_libraries(NetCDF::NetCDF_Fortran INTERFACE "${NetCDF_Fortran_LIBRARIES}") 88 | 89 | add_dependencies(NetCDF::NetCDF_Fortran NETCDF_FORTRAN) 90 | -------------------------------------------------------------------------------- /cmake/pkgconf.cmake: -------------------------------------------------------------------------------- 1 | # --- generate pkg-config .pc 2 | 3 | set(pc_requires "netcdf-fortran") 4 | 5 | set(pc_filename ${PROJECT_NAME}.pc) 6 | configure_file(${CMAKE_CURRENT_LIST_DIR}/pkgconf.pc.in ${pc_filename} @ONLY) 7 | 8 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${pc_filename} DESTINATION pkgconfig) 9 | -------------------------------------------------------------------------------- /cmake/pkgconf.pc.in: -------------------------------------------------------------------------------- 1 | prefix="@CMAKE_INSTALL_PREFIX@" 2 | exec_prefix="${prefix}" 3 | libdir="${prefix}/lib" 4 | includedir="${prefix}/include" 5 | 6 | Name: @PROJECT_NAME@ 7 | Description: @CMAKE_PROJECT_DESCRIPTION@ 8 | URL: @CMAKE_PROJECT_HOMEPAGE_URL@ 9 | Version: @PROJECT_VERSION@ 10 | Requires: @pc_requires@ 11 | Cflags: -I"${includedir}" 12 | Libs: -L"${libdir}" -l@PROJECT_NAME@ 13 | Libs.private: -L"${libdir}" -l@PROJECT_NAME@ 14 | -------------------------------------------------------------------------------- /cmake/zlib.cmake: -------------------------------------------------------------------------------- 1 | # build Zlib to ensure compatibility. 2 | # We use Zlib 2.x for speed and robustness. 3 | include(GNUInstallDirs) 4 | include(ExternalProject) 5 | 6 | set(ZLIB_INCLUDE_DIRS ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 7 | 8 | if(BUILD_SHARED_LIBS) 9 | if(WIN32) 10 | set(ZLIB_LIBRARIES ${CMAKE_INSTALL_FULL_BINDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}zlib1${CMAKE_SHARED_LIBRARY_SUFFIX}) 11 | else() 12 | set(ZLIB_LIBRARIES ${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_SHARED_LIBRARY_PREFIX}z${CMAKE_SHARED_LIBRARY_SUFFIX}) 13 | endif() 14 | else() 15 | if(MSVC) 16 | set(ZLIB_LIBRARIES ${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}zlibstatic${CMAKE_STATIC_LIBRARY_SUFFIX}) 17 | else() 18 | set(ZLIB_LIBRARIES ${CMAKE_INSTALL_FULL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}z${CMAKE_STATIC_LIBRARY_SUFFIX}) 19 | endif() 20 | endif() 21 | 22 | 23 | string(JSON zlib_url GET ${json} zlib url) 24 | 25 | set(zlib_cmake_args 26 | -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} 27 | -DBUILD_SHARED_LIBS:BOOL=${BUILD_SHARED_LIBS} 28 | -DCMAKE_BUILD_TYPE=Release 29 | -DZLIB_COMPAT:BOOL=on 30 | -DZLIB_ENABLE_TESTS:BOOL=off 31 | -DZLIBNG_ENABLE_TESTS:BOOL=off 32 | -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON 33 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 34 | ) 35 | # NetCDF 4.9/4.6 needs fPIC 36 | 37 | ExternalProject_Add(ZLIB 38 | URL ${zlib_url} 39 | CMAKE_ARGS ${zlib_cmake_args} 40 | BUILD_BYPRODUCTS ${ZLIB_LIBRARIES} 41 | CONFIGURE_HANDLED_BY_BUILD ON 42 | USES_TERMINAL_DOWNLOAD true 43 | USES_TERMINAL_UPDATE true 44 | USES_TERMINAL_PATCH true 45 | USES_TERMINAL_CONFIGURE true 46 | USES_TERMINAL_BUILD true 47 | USES_TERMINAL_INSTALL true 48 | USES_TERMINAL_TEST true 49 | ) 50 | 51 | # --- imported target 52 | 53 | file(MAKE_DIRECTORY ${ZLIB_INCLUDE_DIRS}) 54 | # avoid race condition 55 | 56 | # for visibility in parent projects 57 | add_library(ZLIB::ZLIB INTERFACE IMPORTED GLOBAL) 58 | add_dependencies(ZLIB::ZLIB ZLIB) # to avoid include directory race condition 59 | target_link_libraries(ZLIB::ZLIB INTERFACE ${ZLIB_LIBRARIES}) 60 | target_include_directories(ZLIB::ZLIB INTERFACE ${ZLIB_INCLUDE_DIRS}) 61 | -------------------------------------------------------------------------------- /codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://doi.org/10.5063/schema/codemeta-2.0", 3 | "@type": "SoftwareSourceCode", 4 | "codeRepository": "https://github.com/geospace-code/nc4fortran", 5 | "contIntegration": "https://github.com/geospace-code/nc4fortran/actions", 6 | "dateModified": "2022-05-29", 7 | "downloadUrl": "https://github.com/geospace-code/nc4fortran/releases", 8 | "issueTracker": "https://github.com/geospace-code/nc4fortran/issues", 9 | "name": "nc4fortran", 10 | "identifier": "10.5281/zenodo.3757221", 11 | "description": "Lightweight object-oriented NetCDF4 Fortran interface", 12 | "applicationCategory": "file I/O", 13 | "developmentStatus": "active", 14 | "funder": { 15 | "@type": "Organization", 16 | "name": "NASA" 17 | }, 18 | "keywords": [ 19 | "netcdf4", 20 | "object-oriented" 21 | ], 22 | "programmingLanguage": [ 23 | "Fortran" 24 | ], 25 | "author": [ 26 | { 27 | "@type": "Person", 28 | "@id": "https://orcid.org/0000-0002-1637-6526", 29 | "givenName": "Michael", 30 | "familyName": "Hirsch" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22...3.30) 2 | 3 | project(nc4fortranExample 4 | LANGUAGES C Fortran 5 | ) 6 | 7 | enable_testing() 8 | 9 | find_package(nc4fortran CONFIG REQUIRED) 10 | 11 | # --- Fortran interface for examples 12 | add_library(fortran_interface fortran_interface.f90) 13 | target_link_libraries(fortran_interface PRIVATE nc4fortran::nc4fortran) 14 | 15 | # --- example 1 16 | add_executable(example1 example1.f90) 17 | target_link_libraries(example1 nc4fortran::nc4fortran) 18 | add_test(NAME nc4fortran:Example1 COMMAND example1 19 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 20 | ) 21 | 22 | # --- example 2 23 | add_executable(example2 example2.f90) 24 | target_link_libraries(example2 nc4fortran::nc4fortran) 25 | add_test(NAME nc4fortran:Example2 COMMAND example2 26 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 27 | ) 28 | 29 | # --- NetCDF shared lib paths needed 30 | get_property(test_names DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY TESTS) 31 | get_property(incdir TARGET nc4fortran::nc4fortran PROPERTY INTERFACE_INCLUDE_DIRECTORIES) 32 | 33 | if(WIN32) 34 | set_property(TEST ${test_names} PROPERTY 35 | ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${incdir}/../bin" 36 | ) 37 | elseif(APPLE) 38 | set_property(TEST ${test_names} PROPERTY 39 | ENVIRONMENT_MODIFICATION "DYLD_LIBRARY_PATH=path_list_prepend:${incdir}/../lib" 40 | ) 41 | else() 42 | set_property(TEST ${test_names} PROPERTY 43 | ENVIRONMENT_MODIFICATION "LD_LIBRARY_PATH=path_list_prepend:${incdir}/../lib" 44 | ) 45 | endif() 46 | -------------------------------------------------------------------------------- /example/Readme.md: -------------------------------------------------------------------------------- 1 | # nc4ortran Examples 2 | 3 | From the nc4fortran/ directory, specify the nc4fortran install like: 4 | 5 | ```sh 6 | cmake -B build -DCMAKE_INSTALL_PREFIX=~/nc4fortran 7 | cmake --build build 8 | cmake --install build 9 | 10 | cmake -B Examples/build -S Examples -DCMAKE_PREFIX_PATH=~/nc4fortran 11 | ``` 12 | 13 | ## Examples 14 | 15 | Example 1 and 2 show the object-oriented interface of nc4fortran, which may offer faster performance if more than one variable is being read or written. 16 | -------------------------------------------------------------------------------- /example/example1.f90: -------------------------------------------------------------------------------- 1 | program example1 2 | 3 | use nc4fortran, only : netcdf_file 4 | implicit none (type, external) 5 | 6 | type(netcdf_file) :: h 7 | character(:), allocatable :: filename 8 | integer :: i32 9 | 10 | filename = 'nc4fortran_example1.nc' 11 | 12 | call h%open(filename, action='w') 13 | 14 | call h%write('x', 123) 15 | 16 | call h%read('x', i32) 17 | call h%close() 18 | 19 | if (i32 /= 123) error stop 'incorrect value read' 20 | 21 | print *, 'OK: example 1' 22 | 23 | end program 24 | -------------------------------------------------------------------------------- /example/example2.f90: -------------------------------------------------------------------------------- 1 | program example2 2 | 3 | use nc4fortran, only : netcdf_file 4 | implicit none (type, external) 5 | 6 | character(:), allocatable :: filename 7 | integer :: i32 8 | 9 | type(netcdf_file) :: h 10 | 11 | filename = 'nc4fortran_example2.nc' 12 | 13 | call h%open(filename, action='w') 14 | call h%write('x', 123) 15 | call h%close() 16 | 17 | call h%open(filename, action='r') 18 | call h%read('x', i32) 19 | if (i32 /= 123) error stop 'incorrect value read' 20 | 21 | print *, 'OK: example 2' 22 | 23 | end program 24 | -------------------------------------------------------------------------------- /example/fortran_interface.f90: -------------------------------------------------------------------------------- 1 | module fortran_interface 2 | !! filename(256) and var_name(64) are arbitrary sizes. 3 | !! ensure calling program buffers are equally sized 4 | use, intrinsic :: iso_c_binding, only : C_INT32_T, C_CHAR, C_NULL_CHAR 5 | use nc4fortran, only : netcdf_file 6 | 7 | implicit none (type, external) 8 | 9 | contains 10 | 11 | 12 | subroutine write_int32(filename, var_name, i32) bind(C) 13 | character(kind=C_CHAR) :: filename(256) 14 | character(kind=C_CHAR) :: var_name(64) 15 | integer(C_INT32_T), intent(in) :: i32 16 | type(netcdf_file) :: h 17 | 18 | call h%open(cstr2fstr(filename), action='w') 19 | call h%write(cstr2fstr(var_name), i32) 20 | call h%close() 21 | 22 | end subroutine write_int32 23 | 24 | 25 | subroutine read_int32(filename, var_name, i32) bind(C) 26 | character(kind=C_CHAR) :: filename(256) 27 | character(kind=C_CHAR) :: var_name(64) 28 | integer(C_INT32_T), intent(out) :: i32 29 | type(netcdf_file) :: h 30 | 31 | call h%open(cstr2fstr(filename), action='r') 32 | call h%read(cstr2fstr(var_name), i32) 33 | call h%close() 34 | 35 | end subroutine read_int32 36 | 37 | 38 | function cstr2fstr(c_str) result(f_str) 39 | 40 | character(kind=C_CHAR), intent(in) :: c_str(:) 41 | character(len=size(c_str)) :: buf 42 | character(:), allocatable :: f_str 43 | integer :: i 44 | 45 | buf = "" 46 | !! clean variable, will get extra garbled text otherwise, maybe blank but non-trimmable character 47 | 48 | do i = 1, size(c_str) 49 | if (c_str(i) == C_NULL_CHAR) exit 50 | buf(i:i) = c_str(i) 51 | enddo 52 | 53 | f_str = trim(buf) 54 | 55 | end function cstr2fstr 56 | 57 | end module fortran_interface 58 | -------------------------------------------------------------------------------- /ford.md: -------------------------------------------------------------------------------- 1 | src_dir: ./src 2 | output_dir: ./docs 3 | project: Object-oriented Fortran 2008 NetCDF4 interface 4 | project_github: https://github.com/geospace-code/nc4fortran 5 | project_website: https://geospace-code.github.io/nc4fortran 6 | summary: Object-oriented Fortran 2008 NetCDF4 interface 7 | author: Michael Hirsch, Ph.D. 8 | github: https://github.com/geospace-code 9 | license: by 10 | exclude: CMakeFortranCompilerId.F 11 | display: public 12 | protected 13 | private 14 | source: false 15 | graph: true 16 | search: true 17 | -------------------------------------------------------------------------------- /fpm.toml: -------------------------------------------------------------------------------- 1 | name = "nc4fortran" 2 | description = "Lightweight object-oriented NetCDF4 interface" 3 | categories = "io" 4 | version = "1.7.1" 5 | 6 | [build] 7 | external-modules = ["netcdf"] 8 | link = ["netcdf", "netcdff", "hdf5", "hdf5_hl", "hdf5_fortran", "hdf5hl_fortran"] 9 | 10 | [install] 11 | library = true 12 | 13 | [[test]] 14 | name = "minimal" 15 | main = "test_minimal.f90" 16 | 17 | [[test]] 18 | name = "attributes" 19 | main = "test_attributes.f90" 20 | 21 | [[test]] 22 | name = "deflate_write" 23 | main = "test_deflate_write.f90" 24 | 25 | [[test]] 26 | name = "deflate_read" 27 | main = "test_deflate_read.f90" 28 | 29 | [[test]] 30 | name = "deflate_props" 31 | main = "test_deflate_props.f90" 32 | 33 | [[test]] 34 | name = "destructor" 35 | main = "test_destructor.f90" 36 | 37 | [[test]] 38 | name = "exist" 39 | main = "test_exist.f90" 40 | 41 | [[test]] 42 | name = "scalar" 43 | main = "test_scalar.f90" 44 | 45 | [[test]] 46 | name = "shape" 47 | main = "test_shape.f90" 48 | 49 | [[test]] 50 | name = "string" 51 | main = "test_string.f90" 52 | 53 | [[test]] 54 | name = "version" 55 | main = "test_version.f90" 56 | -------------------------------------------------------------------------------- /options.cmake: -------------------------------------------------------------------------------- 1 | message(STATUS "${PROJECT_NAME} ${PROJECT_VERSION} CMake ${CMAKE_VERSION} Toolchain ${CMAKE_TOOLCHAIN_FILE}") 2 | 3 | option(nc4fortran_COVERAGE "Code coverage tests") 4 | option(tidy "Run clang-tidy on the code") 5 | 6 | option(find_hdf5 "find HDF5 libraries" ON) 7 | option(find_netcdf "find NetCDF libraries" ON) 8 | 9 | option(nc4fortran_BUILD_TESTING "Build tests" ${nc4fortran_IS_TOP_LEVEL}) 10 | 11 | set_property(DIRECTORY PROPERTY EP_UPDATE_DISCONNECTED true) 12 | 13 | # Necessary for shared library with Visual Studio / Windows oneAPI 14 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS true) 15 | 16 | if(nc4fortran_IS_TOP_LEVEL AND CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 17 | set_property(CACHE CMAKE_INSTALL_PREFIX PROPERTY VALUE "${PROJECT_BINARY_DIR}/local") 18 | endif() 19 | -------------------------------------------------------------------------------- /scripts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19...3.25) 2 | project(NetCDF_build 3 | LANGUAGES C Fortran 4 | ) 5 | 6 | # --- system checks 7 | message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") 8 | file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}) 9 | if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.29) 10 | if(NOT IS_WRITABLE ${CMAKE_INSTALL_PREFIX}) 11 | message(FATAL_ERROR "CMAKE_INSTALL_PREFIX is not writable: ${CMAKE_INSTALL_PREFIX}") 12 | endif() 13 | else() 14 | file(TOUCH ${CMAKE_INSTALL_PREFIX}/.cmake_writable "") 15 | endif() 16 | 17 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) 18 | 19 | # --- commence NetCDF build/install 20 | set_property(DIRECTORY PROPERTY EP_UPDATE_DISCONNECTED true) 21 | 22 | file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/libraries.json json) 23 | 24 | message(STATUS "Build / install NetCDF to ${CMAKE_INSTALL_PREFIX}") 25 | 26 | include(${PROJECT_SOURCE_DIR}/../cmake/netcdf.cmake) 27 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(nc4fortran PRIVATE interface.f90 utils.f90 2 | read.f90 reader.f90 read_scalar.f90 3 | write.f90 writer.f90 write_scalar.f90 4 | attributes.f90 5 | ) 6 | -------------------------------------------------------------------------------- /src/attributes.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran) attributes 2 | 3 | use netcdf, only : nf90_put_att, nf90_get_att 4 | implicit none (type, external) 5 | 6 | 7 | contains 8 | 9 | module procedure write_attribute 10 | integer :: varid, ier 11 | 12 | ier = nf90_inq_varid(self%file_id, dname, varid) 13 | 14 | if(ier == nf90_noerr) then 15 | select type(A) 16 | type is (character(*)) 17 | ier = nf90_put_att(self%file_id, varid, attrname, A) 18 | type is (real(real32)) 19 | ier = nf90_put_att(self%file_id, varid, attrname, A) 20 | type is (real(real64)) 21 | ier = nf90_put_att(self%file_id, varid, attrname, A) 22 | type is (integer(int32)) 23 | ier = nf90_put_att(self%file_id, varid, attrname, A) 24 | class default 25 | ier = NF90_EBADTYPE 26 | end select 27 | endif 28 | 29 | if (check_error(ier, dname)) error stop 'nc4fortran:attributes: failed to write' // attrname 30 | 31 | end procedure write_attribute 32 | 33 | 34 | module procedure read_attribute 35 | integer :: varid, ier 36 | 37 | ier = nf90_inq_varid(self%file_id, dname, varid) 38 | 39 | if(ier == nf90_noerr) then 40 | select type (A) 41 | type is (character(*)) 42 | ier = nf90_get_att(self%file_id, varid, attrname, A) 43 | type is (real(real32)) 44 | ier = nf90_get_att(self%file_id, varid, attrname, A) 45 | type is (real(real64)) 46 | ier = nf90_get_att(self%file_id, varid, attrname, A) 47 | type is (integer(int32)) 48 | ier = nf90_get_att(self%file_id, varid, attrname, A) 49 | class default 50 | ier = NF90_EBADTYPE 51 | end select 52 | endif 53 | 54 | if (check_error(ier, dname)) error stop 'nc4fortran:attributes: failed to read' // attrname 55 | 56 | end procedure read_attribute 57 | 58 | 59 | end submodule attributes 60 | -------------------------------------------------------------------------------- /src/interface.f90: -------------------------------------------------------------------------------- 1 | module nc4fortran 2 | !! NetCDF4 object-oriented polymorphic interface 3 | use, intrinsic :: iso_c_binding, only : c_ptr, c_loc 4 | use, intrinsic :: iso_fortran_env, only : real32, real64, int32, int64, stderr=>error_unit 5 | 6 | use netcdf, only : nf90_create, nf90_open, NF90_WRITE, NF90_CLOBBER, NF90_NETCDF4, NF90_MAX_NAME, & 7 | NF90_NOERR, NF90_EHDFERR, NF90_EBADNAME, NF90_EBADDIM, NF90_EBADTYPE, NF90_EBADGRPID, NF90_ENOTNC, NF90_ENOTVAR, & 8 | NF90_ECHAR, NF90_EEDGE, NF90_ENAMEINUSE, NF90_EBADID, NF90_EINDEFINE, NF90_NOWRITE, NF90_EDIMSIZE, & 9 | nf90_open, nf90_close, nf90_estride, nf90_inq_varid, nf90_inq_dimid, nf90_inquire_dimension, & 10 | nf90_def_dim, nf90_def_var, nf90_get_var, nf90_put_var, & 11 | nf90_inq_libvers, nf90_sync, nf90_inquire_variable 12 | 13 | implicit none (type, external) 14 | private 15 | public :: netcdf_file, NF90_MAX_NAME, NF90_NOERR, check_error, is_netcdf, nc_exist, nc4version 16 | 17 | !> main type 18 | type :: netcdf_file 19 | 20 | character(:), allocatable :: filename 21 | integer :: file_id !< location identifier 22 | 23 | integer :: comp_lvl = 0 !< compression level (1-9) 0: disable compression 24 | logical :: debug = .false. 25 | logical :: is_open = .false. 26 | !! will be auto-deleted on close 27 | 28 | contains 29 | 30 | !> methods used directly without type/rank agnosticism 31 | procedure, public :: open => nc_open 32 | procedure, public :: close => nc_close 33 | procedure, public :: create => nc_create 34 | procedure, public :: shape => get_shape 35 | procedure, public :: ndim => get_ndim 36 | procedure, public :: ndims => get_ndim 37 | procedure, public :: write_attribute 38 | procedure, public :: read_attribute 39 | procedure, public :: flush=>nc_flush 40 | procedure, public :: deflate => get_deflate 41 | procedure, public :: exist=>nc_check_exist 42 | procedure, public :: exists=>nc_check_exist 43 | procedure, public :: is_chunked 44 | procedure, public :: is_contig 45 | procedure, public :: chunks=>get_chunk 46 | 47 | !> generic procedures mapped over type / rank 48 | generic, public :: write => & 49 | nc_write_scalar, nc_write_1d, nc_write_2d, nc_write_3d, nc_write_4d, nc_write_5d, nc_write_6d, nc_write_7d 50 | 51 | generic, public :: read => nc_read_scalar, nc_read_1d, nc_read_2d, nc_read_3d, nc_read_4d, nc_read_5d, nc_read_6d, nc_read_7d 52 | 53 | procedure, private :: nc_write_scalar, nc_write_1d, nc_write_2d, nc_write_3d, nc_write_4d, nc_write_5d, nc_write_6d, nc_write_7d, & 54 | nc_read_scalar, nc_read_1d, nc_read_2d, nc_read_3d, nc_read_4d, nc_read_5d, nc_read_6d, nc_read_7d, & 55 | def_dims 56 | 57 | !> flush file to disk and close file if user forgets to do so. 58 | final :: destructor 59 | 60 | end type netcdf_file 61 | 62 | !> Submodules 63 | 64 | interface !< write.f90 65 | 66 | module subroutine nc_create(self, dset_name, dtype, dims, dim_names, chunk_size, fill_value, varid) 67 | class(netcdf_file), intent(in) :: self 68 | character(*), intent(in) :: dset_name 69 | integer, intent(in) :: dtype 70 | integer, intent(in) :: dims(:) 71 | character(*), intent(in), optional :: dim_names(:) 72 | integer, intent(in), optional :: chunk_size(:) 73 | class(*), intent(in), optional :: fill_value 74 | integer, intent(out), optional :: varid 75 | end subroutine 76 | 77 | module subroutine def_dims(self, dname, dim_names, dims, dimids) 78 | class(netcdf_file), intent(in) :: self 79 | character(*), intent(in) :: dname 80 | character(*), intent(in), optional :: dim_names(:) 81 | integer, intent(in) :: dims(:) 82 | integer, intent(out) :: dimids(size(dims)) 83 | end subroutine 84 | 85 | module subroutine nc_flush(self) 86 | class(netcdf_file), intent(in) :: self 87 | end subroutine 88 | 89 | end interface 90 | 91 | 92 | interface !< writer.f90 93 | module subroutine nc_write_scalar(self, dname, A) 94 | class(netcdf_file), intent(in) :: self 95 | character(*), intent(in) :: dname 96 | class(*), intent(in) :: A 97 | end subroutine 98 | 99 | module subroutine nc_write_1d(self, dname, A, dims, istart, iend, stride, chunk_size) 100 | class(netcdf_file), intent(in) :: self 101 | character(*), intent(in) :: dname 102 | class(*), intent(in) :: A(:) 103 | character(*), intent(in), optional :: dims(1) 104 | integer, intent(in), dimension(1), optional :: istart, iend, stride 105 | integer, intent(in), dimension(1), optional :: chunk_size 106 | end subroutine 107 | 108 | module subroutine nc_write_2d(self, dname, A, dims, istart, iend, stride, chunk_size) 109 | class(netcdf_file), intent(in) :: self 110 | character(*), intent(in) :: dname 111 | class(*), intent(in) :: A(:,:) 112 | character(*), intent(in), optional :: dims(2) 113 | integer, intent(in), dimension(2), optional :: istart, iend, stride 114 | integer, intent(in), dimension(2), optional :: chunk_size 115 | end subroutine 116 | 117 | module subroutine nc_write_3d(self, dname, A, dims, istart, iend, stride, chunk_size) 118 | class(netcdf_file), intent(in) :: self 119 | character(*), intent(in) :: dname 120 | class(*), intent(in) :: A(:,:,:) 121 | character(*), intent(in), optional :: dims(3) 122 | integer, intent(in), dimension(3), optional :: istart, iend, stride 123 | integer, intent(in), dimension(3), optional :: chunk_size 124 | end subroutine 125 | 126 | module subroutine nc_write_4d(self, dname, A, dims, istart, iend, stride, chunk_size) 127 | class(netcdf_file), intent(in) :: self 128 | character(*), intent(in) :: dname 129 | class(*), intent(in) :: A(:,:,:,:) 130 | character(*), intent(in), optional :: dims(4) 131 | integer, intent(in), dimension(4), optional :: istart, iend, stride 132 | integer, intent(in), dimension(4), optional :: chunk_size 133 | end subroutine 134 | 135 | module subroutine nc_write_5d(self, dname, A, dims, istart, iend, stride, chunk_size) 136 | class(netcdf_file), intent(in) :: self 137 | character(*), intent(in) :: dname 138 | class(*), intent(in) :: A(:,:,:,:,:) 139 | character(*), intent(in), optional :: dims(5) 140 | integer, intent(in), dimension(5), optional :: istart, iend, stride 141 | integer, intent(in), dimension(5), optional :: chunk_size 142 | end subroutine 143 | 144 | module subroutine nc_write_6d(self, dname, A, dims, istart, iend, stride, chunk_size) 145 | class(netcdf_file), intent(in) :: self 146 | character(*), intent(in) :: dname 147 | class(*), intent(in) :: A(:,:,:,:,:,:) 148 | character(*), intent(in), optional :: dims(6) 149 | integer, intent(in), dimension(6), optional :: istart, iend, stride 150 | integer, intent(in), dimension(6), optional :: chunk_size 151 | end subroutine 152 | 153 | module subroutine nc_write_7d(self, dname, A, dims, istart, iend, stride, chunk_size) 154 | class(netcdf_file), intent(in) :: self 155 | character(*), intent(in) :: dname 156 | class(*), intent(in) :: A(:,:,:,:,:,:,:) 157 | character(*), intent(in), optional :: dims(7) 158 | integer, intent(in), dimension(7), optional :: istart, iend, stride 159 | integer, intent(in), dimension(7), optional :: chunk_size 160 | end subroutine 161 | 162 | end interface 163 | 164 | 165 | interface !< read.f90 166 | 167 | module subroutine get_chunk(self, dname, chunk_size) 168 | class(netcdf_file), intent(in) :: self 169 | character(*), intent(in) :: dname 170 | integer, intent(out) :: chunk_size(:) 171 | end subroutine 172 | module integer function get_ndim(self, dname) result (drank) 173 | class(netcdf_file), intent(in) :: self 174 | character(*), intent(in) :: dname 175 | end function get_ndim 176 | 177 | module subroutine get_shape(self, dname, dims, dimnames) 178 | class(netcdf_file), intent(in) :: self 179 | character(*), intent(in) :: dname 180 | integer, intent(out), allocatable :: dims(:) 181 | character(NF90_MAX_NAME), intent(out), allocatable, optional :: dimnames(:) 182 | end subroutine 183 | 184 | module logical function get_deflate(self, dname) 185 | class(netcdf_file), intent(in) :: self 186 | character(*), intent(in) :: dname 187 | end function 188 | 189 | module logical function nc_check_exist(self, dname) result(exists) 190 | class(netcdf_file), intent(in) :: self 191 | character(*), intent(in) :: dname 192 | end function nc_check_exist 193 | 194 | module logical function nc_exist(filename, dname) 195 | character(*), intent(in) :: filename, dname 196 | end function nc_exist 197 | end interface 198 | 199 | interface !< reader.f90 200 | module subroutine nc_read_scalar(self, dname, A) 201 | class(netcdf_file), intent(in) :: self 202 | character(*), intent(in) :: dname 203 | class(*), intent(inout) :: A 204 | !! inout for character 205 | end subroutine 206 | 207 | module subroutine nc_read_1d(self, dname, A, istart, iend, stride) 208 | class(netcdf_file), intent(in) :: self 209 | character(*), intent(in) :: dname 210 | class(*), intent(inout) :: A(:) 211 | integer, intent(in), dimension(1), optional :: istart, iend, stride 212 | end subroutine 213 | 214 | module subroutine nc_read_2d(self, dname, A, istart, iend, stride) 215 | class(netcdf_file), intent(in) :: self 216 | character(*), intent(in) :: dname 217 | class(*), intent(inout) :: A(:,:) 218 | integer, intent(in), dimension(2), optional :: istart, iend, stride 219 | end subroutine 220 | 221 | module subroutine nc_read_3d(self, dname, A, istart, iend, stride) 222 | class(netcdf_file), intent(in) :: self 223 | character(*), intent(in) :: dname 224 | class(*), intent(inout) :: A(:,:,:) 225 | integer, intent(in), dimension(3), optional :: istart, iend, stride 226 | end subroutine 227 | 228 | module subroutine nc_read_4d(self, dname, A, istart, iend, stride) 229 | class(netcdf_file), intent(in) :: self 230 | character(*), intent(in) :: dname 231 | class(*), intent(inout) :: A(:,:,:,:) 232 | integer, intent(in), dimension(4), optional :: istart, iend, stride 233 | end subroutine 234 | 235 | module subroutine nc_read_5d(self, dname, A, istart, iend, stride) 236 | class(netcdf_file), intent(in) :: self 237 | character(*), intent(in) :: dname 238 | class(*), intent(inout) :: A(:,:,:,:,:) 239 | integer, intent(in), dimension(5), optional :: istart, iend, stride 240 | end subroutine 241 | 242 | module subroutine nc_read_6d(self, dname, A, istart, iend, stride) 243 | class(netcdf_file), intent(in) :: self 244 | character(*), intent(in) :: dname 245 | class(*), intent(inout) :: A(:,:,:,:,:,:) 246 | integer, intent(in), dimension(6), optional :: istart, iend, stride 247 | end subroutine 248 | 249 | module subroutine nc_read_7d(self, dname, A, istart, iend, stride) 250 | class(netcdf_file), intent(in) :: self 251 | character(*), intent(in) :: dname 252 | class(*), intent(inout) :: A(:,:,:,:,:,:,:) 253 | integer, intent(in), dimension(7), optional :: istart, iend, stride 254 | end subroutine 255 | 256 | end interface 257 | 258 | 259 | interface !< attributes.f90 260 | module subroutine write_attribute(self, dname, attrname, A) 261 | class(netcdf_file), intent(in) :: self 262 | character(*), intent(in) :: dname, attrname 263 | class(*), intent(in) :: A 264 | end subroutine 265 | 266 | module subroutine read_attribute(self, dname, attrname, A) 267 | class(netcdf_file), intent(in) :: self 268 | character(*), intent(in) :: dname, attrname 269 | class(*), intent(inout) :: A 270 | !! inout for character 271 | end subroutine 272 | 273 | end interface 274 | 275 | 276 | interface !< utils.f90 277 | 278 | module subroutine nc_open(self, filename, action, comp_lvl, debug) 279 | !! Opens NetCDF file 280 | 281 | class(netcdf_file), intent(inout) :: self 282 | character(*), intent(in) :: filename 283 | character(*), intent(in), optional :: action 284 | integer, intent(in), optional :: comp_lvl 285 | logical, intent(in), optional :: debug 286 | end subroutine 287 | 288 | module subroutine destructor(self) 289 | !! Close file and handle if user forgets to do so 290 | type(netcdf_file), intent(inout) :: self 291 | end subroutine 292 | 293 | module subroutine nc_close(self) 294 | class(netcdf_file), intent(inout) :: self 295 | end subroutine 296 | 297 | module function nc4version() 298 | !! get NetCDF4 library version 299 | character(:), allocatable :: nc4version 300 | end function 301 | 302 | module logical function is_chunked(self, dname) 303 | class(netcdf_file), intent(in) :: self 304 | character(*), intent(in) :: dname 305 | end function 306 | 307 | module logical function is_contig(self, dname) 308 | class(netcdf_file), intent(in) :: self 309 | character(*), intent(in) :: dname 310 | end function 311 | 312 | module logical function is_netcdf(filename) 313 | !! is this file NetCDF4? 314 | character(*), intent(in) :: filename 315 | end function 316 | 317 | module logical function check_error(code, dname) 318 | integer, intent(in) :: code 319 | character(*), intent(in) :: dname 320 | end function 321 | 322 | end interface 323 | 324 | 325 | end module nc4fortran 326 | -------------------------------------------------------------------------------- /src/read.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran) read 2 | 3 | use, intrinsic :: iso_c_binding, only : c_null_char 4 | 5 | implicit none (type, external) 6 | 7 | contains 8 | 9 | 10 | module procedure get_chunk 11 | 12 | logical :: contig 13 | integer :: i, varid 14 | 15 | chunk_size = -1 16 | 17 | i = nf90_inq_varid(self%file_id, dname, varid) 18 | if (i/=NF90_NOERR) error stop 'ERROR:nc4fortran:chunk: cannot find variable: ' // dname 19 | 20 | i = nf90_inquire_variable(self%file_id, varid, contiguous=contig) 21 | if (i/=NF90_NOERR) error stop 'ERROR:nc4fortran:chunk: cannot get variable properties' // dname 22 | 23 | if(contig) return 24 | i = nf90_inquire_variable(self%file_id, varid, chunksizes=chunk_size) 25 | if (i/=NF90_NOERR) error stop 'ERROR:nc4fortran:chunk: cannot get variable properties' // dname 26 | 27 | end procedure get_chunk 28 | 29 | 30 | module procedure get_deflate 31 | 32 | logical :: contig 33 | integer :: i, varid, deflate_level 34 | 35 | get_deflate = .false. 36 | 37 | i = nf90_inq_varid(self%file_id, dname, varid) 38 | if (i/=NF90_NOERR) error stop 'ERROR:nc4fortran:get_deflate: cannot find variable: ' // dname 39 | 40 | i = nf90_inquire_variable(self%file_id, varid, contiguous=contig) 41 | if (i/=NF90_NOERR) error stop 'ERROR:nc4fortran:get_deflate: cannot get variable properties' // dname 42 | 43 | if(contig) return 44 | i = nf90_inquire_variable(self%file_id, varid, deflate_level=deflate_level) 45 | if (i/=NF90_NOERR) error stop 'ERROR:nc4fortran:get_deflate: cannot get variable properties' // dname 46 | 47 | get_deflate = deflate_level /= 0 48 | 49 | end procedure get_deflate 50 | 51 | 52 | module procedure get_ndim 53 | integer :: varid, ierr 54 | 55 | if(.not.self%is_open) error stop 'ERROR:nc4fortran:read: file handle not open' 56 | 57 | drank = -1 58 | 59 | ierr = nf90_inq_varid(self%file_id, dname, varid) 60 | if(ierr/=NF90_NOERR) error stop 'ERROR:nc4fortran:get_ndim: could not get variable ID for ' // dname 61 | 62 | ierr = nf90_inquire_variable(self%file_id, varid, ndims=drank) 63 | if(ierr/=NF90_NOERR) error stop 'ERROR:nc4fortran:get_ndim: could not get rank for ' // dname 64 | 65 | end procedure get_ndim 66 | 67 | 68 | module procedure get_shape 69 | 70 | integer :: ier, varid, i, N 71 | integer, allocatable :: dimids(:) 72 | character(NF90_MAX_NAME), allocatable :: tempnames(:) 73 | 74 | N = self%ndim(dname) 75 | 76 | allocate(dimids(N), dims(N)) 77 | 78 | ier = nf90_inq_varid(self%file_id, dname, varid) 79 | if(check_error(ier, dname)) error stop 'ERROR:nc4fortran:get_shape: could not get variable ID for: ' // dname 80 | 81 | ier = nf90_inquire_variable(self%file_id, varid, dimids = dimids) 82 | if(check_error(ier, dname)) error stop 'ERROR:nc4fortran:get_shape: could not get dimension IDs for: ' // dname 83 | 84 | if (present(dimnames)) allocate(tempnames(N)) 85 | 86 | do i = 1,N 87 | if(present(dimnames)) then 88 | ier = nf90_inquire_dimension(self%file_id, dimid=dimids(i), name=tempnames(i), len=dims(i)) 89 | else 90 | ier = nf90_inquire_dimension(self%file_id, dimid=dimids(i), len=dims(i)) 91 | endif 92 | if(ier/=NF90_NOERR) error stop 'ERROR:nc4fortran:get_shape: querying dimension size' 93 | enddo 94 | 95 | if (present(dimnames)) then 96 | allocate(dimnames(N)) 97 | dimnames = tempnames 98 | endif 99 | 100 | end procedure get_shape 101 | 102 | 103 | module procedure nc_check_exist 104 | integer :: varid, ierr 105 | 106 | exists = .false. 107 | 108 | if(.not.self%is_open) error stop 'ERROR:nc4fortran:exist: file handle not open ' 109 | 110 | ierr = nf90_inq_varid(self%file_id, dname, varid) 111 | 112 | select case (ierr) 113 | case (NF90_NOERR) 114 | exists = .true. 115 | case (NF90_EBADID) 116 | write(stderr,*) 'ERROR:nc4fortran:exist: is file opened? ', self%filename 117 | case (NF90_ENOTVAR) 118 | if (self%debug) write(stderr,*) dname, ' does not exist in ', self%filename 119 | case default 120 | write(stderr,*) 'ERROR:nc4fortran:exist: unknown problem ', self%filename 121 | end select 122 | 123 | end procedure nc_check_exist 124 | 125 | 126 | module procedure nc_exist 127 | 128 | type(netcdf_file) :: h 129 | 130 | call h%open(filename, action='r') 131 | nc_exist = h%exist(dname) 132 | call h%close() 133 | 134 | end procedure nc_exist 135 | 136 | end submodule read 137 | -------------------------------------------------------------------------------- /src/read_scalar.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran:read) reader_scalar 2 | 3 | implicit none (type, external) 4 | 5 | contains 6 | 7 | module procedure nc_read_scalar 8 | integer :: varid, ier, i, dimids(1) 9 | integer :: dims 10 | character(:), allocatable :: buf 11 | 12 | ier = nf90_inq_varid(self%file_id, dname, varid) 13 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:read inquire_id ' // dname // ' in ' // self%filename 14 | 15 | select type (A) 16 | type is (character(*)) 17 | !! NetCDF4 requires knowing the exact character length as if it were an array without fill values 18 | !! HDF5 is not this strict; having a longer string variable than disk variable is OK in HDF5, but not NetCDF4 19 | ier = nf90_inquire_variable(self%file_id, varid, dimids = dimids) 20 | if(check_error(ier, dname)) error stop 'ERROR:nc4fortran:read_scalar: could not get dimension IDs for: ' // dname 21 | 22 | ier = nf90_inquire_dimension(self%file_id, dimid=dimids(1), len=dims) 23 | if(ier/=NF90_NOERR) error stop 'ERROR:nc4fortran:read_scalar: querying dimension size' 24 | 25 | allocate(character(dims) :: buf) 26 | ier = nf90_get_var(self%file_id, varid, buf) 27 | i = index(buf, c_null_char) - 1 28 | if(i == -1) i = len_trim(buf) 29 | A = buf(:i) 30 | type is (real(real64)) 31 | ier = nf90_get_var(self%file_id, varid, A) 32 | type is (real(real32)) 33 | ier = nf90_get_var(self%file_id, varid, A) 34 | type is (integer(int64)) 35 | ier = nf90_get_var(self%file_id, varid, A) 36 | type is (integer(int32)) 37 | ier = nf90_get_var(self%file_id, varid, A) 38 | class default 39 | ier = NF90_EBADTYPE 40 | end select 41 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:read_scalar: failed ' // dname // ' in ' // self%filename 42 | 43 | end procedure nc_read_scalar 44 | 45 | end submodule reader_scalar 46 | -------------------------------------------------------------------------------- /src/reader.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran:read) reader 2 | 3 | implicit none (type, external) 4 | 5 | contains 6 | 7 | module procedure nc_read_1d 8 | include "reader.inc" 9 | end procedure nc_read_1d 10 | 11 | 12 | module procedure nc_read_2d 13 | include "reader.inc" 14 | end procedure nc_read_2d 15 | 16 | 17 | module procedure nc_read_3d 18 | include "reader.inc" 19 | end procedure nc_read_3d 20 | 21 | 22 | module procedure nc_read_4d 23 | include "reader.inc" 24 | end procedure nc_read_4d 25 | 26 | 27 | module procedure nc_read_5d 28 | include "reader.inc" 29 | end procedure nc_read_5d 30 | 31 | 32 | module procedure nc_read_6d 33 | include "reader.inc" 34 | end procedure nc_read_6d 35 | 36 | 37 | module procedure nc_read_7d 38 | include "reader.inc" 39 | end procedure nc_read_7d 40 | 41 | 42 | end submodule reader 43 | -------------------------------------------------------------------------------- /src/reader.inc: -------------------------------------------------------------------------------- 1 | integer :: varid, ier, drank, i(rank(A)) 2 | 3 | if(.not.self%is_open) error stop 'ERROR:nc4fortran:reader file handle not open' 4 | 5 | ier = nf90_inq_varid(self%file_id, dname, varid) 6 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:read inquire_id ' // dname // ' in ' // self%filename 7 | 8 | ier = nf90_inquire_variable(self%file_id, varid, ndims=drank) 9 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:read inquire ' // dname // ' in ' // self%filename 10 | if(drank /= rank(A)) then 11 | write(stderr,'(a,i0,a,i0)') 'ERROR:nc4fortran:read ' // dname // ' rank ', drank, ' /= variable rank ', rank(A) 12 | error stop 13 | endif 14 | 15 | if(present(istart) .and. present(iend)) then 16 | i = iend - istart + 1 17 | else 18 | i = shape(A) 19 | endif 20 | 21 | select type (A) 22 | type is (real(real64)) 23 | ier = nf90_get_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 24 | type is (real(real32)) 25 | ier = nf90_get_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 26 | type is (integer(int64)) 27 | ier = nf90_get_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 28 | type is (integer(int32)) 29 | ier = nf90_get_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 30 | class default 31 | ier = NF90_EBADTYPE 32 | end select 33 | 34 | 35 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:read read ' // dname // ' in ' // self%filename 36 | -------------------------------------------------------------------------------- /src/utils.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran) utils 2 | 3 | use netcdf, only : NF90_STRERROR 4 | 5 | implicit none (type, external) 6 | 7 | contains 8 | 9 | 10 | module procedure nc_open 11 | 12 | character(:), allocatable :: laction 13 | integer :: ier 14 | 15 | if (self%is_open) then 16 | write(stderr,*) 'WARNING:nc4fortran:open file handle already open to: '// filename 17 | return 18 | endif 19 | 20 | self%filename = filename 21 | 22 | if (present(comp_lvl)) self%comp_lvl = comp_lvl 23 | if (present(debug)) self%debug = debug 24 | 25 | laction = 'r' 26 | if(present(action)) laction = action 27 | 28 | select case(laction) 29 | case('r') 30 | ier = nf90_open(self%filename, NF90_NOWRITE, self%file_id) 31 | case('r+') 32 | ier = nf90_open(self%filename, NF90_WRITE, self%file_id) 33 | case('rw', 'a') 34 | if(is_netcdf(filename)) then 35 | !! NF90_WRITE is necessary to be in true read/write mode 36 | ier = nf90_open(self%filename, ior(NF90_WRITE, NF90_NETCDF4), self%file_id) 37 | else 38 | ier = nf90_create(self%filename, ior(NF90_CLOBBER, NF90_NETCDF4), self%file_id) 39 | endif 40 | case('w') 41 | ier = nf90_create(self%filename, ior(NF90_CLOBBER, NF90_NETCDF4), self%file_id) 42 | case default 43 | error stop 'nc4fortran: Unsupported action -> ' // laction 44 | end select 45 | 46 | 47 | if (ier == NF90_NOERR) then 48 | self%is_open = .true. 49 | return 50 | endif 51 | 52 | error stop 'nc4fortran:ERROR: open ' // filename // ' could not be created' 53 | 54 | end procedure nc_open 55 | 56 | 57 | module procedure destructor 58 | if (.not. self%is_open) return 59 | print *, "auto-closing " // self%filename 60 | call self%close() 61 | end procedure destructor 62 | 63 | 64 | module procedure nc_close 65 | integer :: ier 66 | 67 | if(.not. self%is_open) then 68 | write(stderr,*) 'WARNING:nc4fortran:close file handle is not open' 69 | return 70 | endif 71 | 72 | ier = nf90_close(self%file_id) 73 | if (ier /= NF90_NOERR) error stop 'ERROR:close: ' // self%filename 74 | 75 | self%is_open = .false. 76 | 77 | end procedure nc_close 78 | 79 | 80 | module procedure nc4version 81 | nc4version = nf90_inq_libvers() 82 | end procedure nc4version 83 | 84 | 85 | module procedure is_contig 86 | integer :: ier, varid 87 | 88 | ier = nf90_inq_varid(self%file_id, dname, varid) 89 | if (ier/=NF90_NOERR) error stop 'nc4fortran:is_contig: cannot find variable: ' // dname 90 | 91 | ier = nf90_inquire_variable(self%file_id, varid, contiguous=is_contig) 92 | if (ier/=NF90_NOERR) error stop 'nc4fortran:is_contig: cannot get variable properties' // dname 93 | 94 | end procedure is_contig 95 | 96 | 97 | module procedure is_chunked 98 | integer :: ier, varid 99 | 100 | ier = nf90_inq_varid(self%file_id, dname, varid) 101 | if (ier/=NF90_NOERR) error stop 'nc4fortran:is_chunked: cannot find variable: ' // dname 102 | 103 | ier = nf90_inquire_variable(self%file_id, varid, contiguous=is_chunked) 104 | if (ier/=NF90_NOERR) error stop 'nc4fortran:is_chunked: cannot get variable properties' // dname 105 | 106 | is_chunked = .not.is_chunked 107 | end procedure is_chunked 108 | 109 | 110 | module procedure is_netcdf 111 | integer :: ierr, file_id 112 | 113 | inquire(file=filename, exist=is_netcdf) 114 | !! avoid warning/error messages 115 | if (.not. is_netcdf) return 116 | 117 | ierr = nf90_open(filename, NF90_NOWRITE, file_id) 118 | is_netcdf = ierr == 0 119 | 120 | ierr = nf90_close(file_id) 121 | 122 | end procedure is_netcdf 123 | 124 | 125 | module procedure check_error 126 | 127 | check_error = code /= NF90_NOERR 128 | 129 | if(check_error) write(stderr,'(/,A)') "ERROR:nc4fortran:" // NF90_STRERROR(code) 130 | 131 | end procedure check_error 132 | 133 | 134 | end submodule utils 135 | -------------------------------------------------------------------------------- /src/write.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran) write 2 | 3 | use netcdf, only : nf90_float, nf90_double, nf90_int, nf90_int64, nf90_enddef, NF90_CHAR, nf90_def_var_fill 4 | 5 | implicit none (type, external) 6 | 7 | contains 8 | 9 | 10 | module procedure nc_create 11 | 12 | integer :: var_id, dimids(size(dims)), ier 13 | 14 | call def_dims(self, dset_name, dim_names, dims=dims, dimids=dimids) 15 | 16 | if(present(chunk_size)) then 17 | ier = nf90_def_var(self%file_id, dset_name, dtype, dimids=dimids, varid=var_id, & 18 | shuffle=.true., fletcher32=.true., deflate_level=self%comp_lvl, chunksizes=chunk_size) 19 | else 20 | ier = nf90_def_var(self%file_id, dset_name, dtype, dimids=dimids, varid=var_id, & 21 | shuffle=.true., fletcher32=.true., deflate_level=self%comp_lvl) 22 | endif 23 | if (check_error(ier, dset_name)) error stop 'ERROR:nc4fortran:write def ' // dset_name // ' in ' // self%filename 24 | 25 | if(present(fill_value)) call filler(self%file_id, var_id, dtype, fill_value) 26 | 27 | ier = nf90_enddef(self%file_id) 28 | if (check_error(ier, dset_name)) error stop 'ERROR:nc4fortran:write end_def ' // dset_name // ' in ' // self%filename 29 | 30 | if(present(varid)) varid = var_id 31 | 32 | end procedure nc_create 33 | 34 | 35 | subroutine filler(file_id, var_id, dtype, fill_value) 36 | 37 | integer, intent(in) :: file_id, var_id, dtype 38 | class(*), intent(in) :: fill_value 39 | 40 | character(NF90_MAX_NAME) :: dset_name 41 | integer :: ier 42 | 43 | ier = nf90_inquire_variable(file_id, var_id, dset_name) 44 | 45 | select type(fill_value) 46 | !! type_id MUST equal the fill_value type or "transfer()" like bit pattern unexpected data will result 47 | type is (real(real32)) 48 | if(dtype == NF90_FLOAT) then 49 | ier = nf90_def_var_fill(file_id, var_id, 0, fill_value) 50 | elseif(dtype == NF90_DOUBLE) then 51 | ier = nf90_def_var_fill(file_id, var_id, 0, real(fill_value, real64)) 52 | else 53 | error stop 'ERROR:nc4fortran:write def fill datatype does not match ' // trim(dset_name) 54 | endif 55 | type is (real(real64)) 56 | if(dtype == NF90_FLOAT) then 57 | ier = nf90_def_var_fill(file_id, var_id, 0, real(fill_value, real32)) 58 | elseif(dtype == NF90_DOUBLE) then 59 | ier = nf90_def_var_fill(file_id, var_id, 0, fill_value) 60 | else 61 | error stop 'ERROR:nc4fortran:write def fill datatype does not match ' // trim(dset_name) 62 | endif 63 | type is (integer(int32)) 64 | if(dtype == NF90_INT) then 65 | ier = nf90_def_var_fill(file_id, var_id, 0, fill_value) 66 | elseif(dtype == NF90_INT64) then 67 | ier = nf90_def_var_fill(file_id, var_id, 0, int(fill_value, int32)) 68 | else 69 | error stop 'ERROR:nc4fortran:write def fill datatype does not match ' // trim(dset_name) 70 | endif 71 | type is (integer(int64)) 72 | if(dtype == NF90_INT) then 73 | ier = nf90_def_var_fill(file_id, var_id, 0, int(fill_value, int64)) 74 | elseif(dtype == NF90_INT64) then 75 | ier = nf90_def_var_fill(file_id, var_id, 0, fill_value) 76 | else 77 | error stop 'ERROR:nc4fortran:write def fill datatype does not match ' // trim(dset_name) 78 | endif 79 | type is (character(*)) 80 | error stop "ERROR:nc4fortran:write def " // trim(dset_name) // ": NetCDF4 character variables cannot have fill values" 81 | class default 82 | error stop "ERROR:nc4fortran:write def " // trim(dset_name) // ": NetCDF4 datatype not supported" 83 | end select 84 | if (check_error(ier, "")) error stop 'ERROR:nc4fortran:write fill_value ' // trim(dset_name) 85 | 86 | end subroutine filler 87 | 88 | 89 | module procedure nc_flush 90 | integer :: ier 91 | 92 | ier = nf90_sync(self%file_id) 93 | if (check_error(ier, "")) error stop 94 | end procedure nc_flush 95 | 96 | 97 | module procedure def_dims 98 | !! checks if dimension name exists. if not, create dimension 99 | integer :: i, ierr 100 | character(NF90_MAX_NAME) :: name 101 | 102 | if(.not.self%is_open) error stop 'ERROR:nc4fortran:write:def_dims: file handle not open' 103 | 104 | do i=1,size(dims) 105 | if(present(dim_names)) then 106 | ierr = nf90_inq_dimid(self%file_id, dim_names(i), dimids(i)) 107 | if(ierr==NF90_NOERR) cycle 108 | !! dimension already exists 109 | endif 110 | 111 | !> create new dimension 112 | if(present(dim_names)) then 113 | ierr = nf90_def_dim(self%file_id, dim_names(i), dims(i), dimids(i)) 114 | else 115 | write(name,'(A,A4,I1)') dname,"_dim",i 116 | ierr = nf90_def_dim(self%file_id, trim(name), dims(i), dimids(i)) 117 | endif 118 | if(self%debug) print '(a,i1,a)', "TRACE:def_dims: dimension ", i, " name: " // trim(name) 119 | if (check_error(ierr, dname)) error stop "ERROR:nc4fortran:write def_dim " // dname // " in " // self%filename 120 | end do 121 | 122 | end procedure def_dims 123 | 124 | 125 | end submodule write 126 | -------------------------------------------------------------------------------- /src/write_scalar.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran:write) writer_scalar 2 | 3 | implicit none (type, external) 4 | 5 | contains 6 | 7 | module procedure nc_write_scalar 8 | integer :: varid, ier, lenid 9 | 10 | if (self%exist(dname)) then 11 | ier = nf90_inq_varid(self%file_id, dname, varid) 12 | else 13 | select type (A) 14 | type is (real(real32)) 15 | ier = nf90_def_var(self%file_id, dname, NF90_FLOAT, varid=varid) 16 | type is (real(real64)) 17 | ier = nf90_def_var(self%file_id, dname, NF90_DOUBLE, varid=varid) 18 | type is (integer(int32)) 19 | ier = nf90_def_var(self%file_id, dname, NF90_INT, varid=varid) 20 | type is (integer(int64)) 21 | ier = nf90_def_var(self%file_id, dname, NF90_INT64, varid=varid) 22 | type is (character(*)) 23 | !! string prefill method 24 | !! https://www.unidata.ucar.edu/software/netcdf/docs-fortran/f90-variables.html#f90-reading-and-writing-character-string-values 25 | ier = nf90_def_dim(self%file_id, dname // "StrLen", len(A) + 1, lenid) 26 | if(ier == NF90_NOERR) ier = nf90_def_var(self%file_id, dname, NF90_CHAR, dimids=lenid, varid=varid) 27 | if(ier == NF90_NOERR) ier = nf90_enddef(self%file_id) !< prefill 28 | class default 29 | error stop "ERROR:nc4fortran:write: unknown type for " // dname // " in " // self%filename 30 | end select 31 | endif 32 | if (check_error(ier, dname)) error stop 'nc4fortran:write: setup write ' // dname // ' in ' // self%filename 33 | 34 | select type (A) 35 | type is (real(real32)) 36 | ier = nf90_put_var(self%file_id, varid, A) 37 | type is (real(real64)) 38 | ier = nf90_put_var(self%file_id, varid, A) 39 | type is (integer(int32)) 40 | ier = nf90_put_var(self%file_id, varid, A) 41 | type is (integer(int64)) 42 | ier = nf90_put_var(self%file_id, varid, A) 43 | type is (character(*)) 44 | ier = nf90_put_var(self%file_id, varid, A) 45 | class default 46 | ier = NF90_EBADTYPE 47 | end select 48 | 49 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:write: ' // dname // ' in ' // self%filename 50 | 51 | end procedure nc_write_scalar 52 | 53 | end submodule writer_scalar 54 | -------------------------------------------------------------------------------- /src/writer.f90: -------------------------------------------------------------------------------- 1 | submodule (nc4fortran:write) writer 2 | !! Note: for HDF5-based NetCDF4 file, nf90_enddef is implicit by nf90_put_var 3 | 4 | implicit none (type, external) 5 | 6 | contains 7 | 8 | 9 | module procedure nc_write_1d 10 | include "writer.inc" 11 | end procedure nc_write_1d 12 | 13 | module procedure nc_write_2d 14 | include "writer.inc" 15 | end procedure nc_write_2d 16 | 17 | module procedure nc_write_3d 18 | include "writer.inc" 19 | end procedure nc_write_3d 20 | 21 | module procedure nc_write_4d 22 | include "writer.inc" 23 | end procedure nc_write_4d 24 | 25 | module procedure nc_write_5d 26 | include "writer.inc" 27 | end procedure nc_write_5d 28 | 29 | module procedure nc_write_6d 30 | include "writer.inc" 31 | end procedure nc_write_6d 32 | 33 | module procedure nc_write_7d 34 | include "writer.inc" 35 | end procedure nc_write_7d 36 | 37 | 38 | end submodule writer 39 | -------------------------------------------------------------------------------- /src/writer.inc: -------------------------------------------------------------------------------- 1 | integer :: varid, ier, dtype 2 | integer, dimension(rank(A)) :: i 3 | 4 | if (self%exist(dname)) then 5 | ier = nf90_inq_varid(self%file_id, dname, varid) 6 | else 7 | select type (A) 8 | type is (real(real32)) 9 | dtype = NF90_FLOAT 10 | type is (real(real64)) 11 | dtype = NF90_DOUBLE 12 | type is (integer(int32)) 13 | dtype = NF90_INT 14 | type is (integer(int64)) 15 | dtype = NF90_INT64 16 | class default 17 | error stop "ERROR:nc4fortran:write: unknown type for " // dname // " in " // self%filename 18 | end select 19 | 20 | call nc_create(self, dname, dtype, dims=shape(A), dim_names=dims, chunk_size=chunk_size, varid=varid) 21 | endif 22 | 23 | if(present(istart) .and. present(iend)) then 24 | i = iend - istart + 1 25 | else 26 | i = shape(A) 27 | endif 28 | 29 | select type (A) 30 | type is (real(real32)) 31 | ier = nf90_put_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 32 | type is (real(real64)) 33 | ier = nf90_put_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 34 | type is (integer(int32)) 35 | ier = nf90_put_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 36 | type is (integer(int64)) 37 | ier = nf90_put_var(self%file_id, varid, A, start=istart, count=i, stride=stride) 38 | class default 39 | ier = NF90_EBADTYPE 40 | end select 41 | 42 | if (check_error(ier, dname)) error stop 'ERROR:nc4fortran:write: ' // dname // ' in ' // self%filename 43 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_directory_properties(PROPERTIES LABELS nc4fortran) 2 | 3 | # avoid nuisance test build warnings 4 | add_compile_options("$<$:-Wno-compare-reals;-Wno-maybe-uninitialized>") 5 | 6 | add_executable(test_minimal test_minimal.f90) 7 | target_link_libraries(test_minimal PRIVATE nc4fortran::nc4fortran) 8 | add_test(NAME minimal COMMAND test_minimal 9 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 10 | ) 11 | 12 | 13 | set(test_names array attributes 14 | deflate_write deflate_read deflate_props 15 | destructor error exist fill scalar shape string version) 16 | 17 | foreach(t IN LISTS test_names) 18 | add_executable(test_${t} test_${t}.f90) 19 | target_link_libraries(test_${t} PRIVATE nc4fortran::nc4fortran) 20 | 21 | if(${t} STREQUAL "version") 22 | add_test(NAME ${t} COMMAND test_${t} ${NetCDF_VERSION}) 23 | else() 24 | add_test(NAME ${t} COMMAND test_${t}) 25 | endif() 26 | 27 | endforeach() 28 | 29 | set_property(TEST ${test_names} PROPERTY LABELS unit) 30 | set_property(TEST ${test_names} PROPERTY WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 31 | 32 | set_property(TEST deflate_write PROPERTY FIXTURES_SETUP deflate_files) 33 | 34 | set_property(TEST deflate_props deflate_read PROPERTY FIXTURES_REQUIRED deflate_files) 35 | set_property(TEST deflate_props deflate_read PROPERTY REQUIRED_FILES ${CMAKE_CURRENT_BINARY_DIR}/deflate1.nc) 36 | 37 | 38 | if(nc4fortran_COVERAGE) 39 | setup_target_for_coverage_gcovr_html( 40 | NAME coverage 41 | EXECUTABLE ${CMAKE_CTEST_COMMAND} 42 | ) 43 | endif() 44 | 45 | # --- NetCDF shared lib paths needed 46 | if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.22) 47 | if(WIN32) 48 | set_property(TEST minimal ${test_names} PROPERTY 49 | ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${NetCDF_Fortran_INCLUDE_DIRS}/../bin" 50 | ) 51 | elseif(APPLE) 52 | set_property(TEST minimal ${test_names} PROPERTY 53 | ENVIRONMENT_MODIFICATION "DYLD_LIBRARY_PATH=path_list_prepend:${NetCDF_Fortran_INCLUDE_DIRS}/../lib" 54 | ) 55 | else() 56 | set_property(TEST minimal ${test_names} PROPERTY 57 | ENVIRONMENT_MODIFICATION "LD_LIBRARY_PATH=path_list_prepend:${NetCDF_Fortran_INCLUDE_DIRS}/../lib" 58 | ) 59 | endif() 60 | endif() 61 | -------------------------------------------------------------------------------- /test/test_array.f90: -------------------------------------------------------------------------------- 1 | program array_test 2 | 3 | use, intrinsic:: ieee_arithmetic, only: ieee_value, ieee_quiet_nan, ieee_is_nan 4 | use, intrinsic:: iso_fortran_env, only: int64, int32, real32, real64, stderr=>error_unit 5 | 6 | use nc4fortran, only : netcdf_file 7 | use netcdf, only : NF90_INT 8 | 9 | implicit none (type, external) 10 | 11 | real(real32) :: nan 12 | 13 | call test_basic_array('test_array.nc') 14 | print *,'PASSED: array write' 15 | call test_read_slice('test_array.nc') 16 | print *, 'PASSED: slice read' 17 | call test_write_slice('test_array.nc') 18 | print *, 'PASSED: slice write' 19 | 20 | contains 21 | 22 | subroutine test_basic_array(filename) 23 | 24 | character(*), intent(in) :: filename 25 | 26 | type(netcdf_file) :: h 27 | integer, allocatable :: dims(:) 28 | 29 | integer(int32), dimension(4) :: i1, i1t 30 | integer(int32), dimension(4,4) :: i2, i2t 31 | integer(int64), dimension(4,4) :: i2t64 32 | real(real32), allocatable :: rr2(:,:) 33 | real(real32) :: nant, r1(4), r2(4,4), B(6,6) 34 | integer :: i 35 | integer(int32) :: i2_8(8,8) 36 | 37 | nan = ieee_value(1.0, ieee_quiet_nan) 38 | 39 | do i = 1,size(i1) 40 | i1(i) = i 41 | enddo 42 | 43 | i2(1,:) = i1 44 | do i = 1,size(i2,2) 45 | i2(i,:) = i2(1,:) * i 46 | enddo 47 | 48 | r1 = i1 49 | r2 = i2 50 | 51 | call h%open(filename, action='w', comp_lvl=1) 52 | 53 | call h%write('int32-1d', i1) 54 | call h%write('int32-2d', i2, ['x', 'y']) 55 | call h%write('int64-2d', int(i2, int64)) 56 | call h%write('real32-2d', r2) 57 | call h%write('nan', nan) 58 | 59 | call h%close() 60 | 61 | !! read 62 | call h%open(filename, action='r') 63 | 64 | !> int32 65 | call h%read('int32-1d', i1t) 66 | if (.not.all(i1==i1t)) error stop 'read 1-d int32: does not match write' 67 | 68 | call h%read('int32-2d',i2t) 69 | if (.not.all(i2==i2t)) error stop 'read 2-D: int32 does not match write' 70 | 71 | call h%read('int64-2d',i2t64) 72 | if (.not.all(i2==i2t64)) error stop 'read 2-D: int64 does not match write' 73 | 74 | !> verify reading into larger array 75 | i2_8 = 0 76 | call h%read('int32-2d', i2_8(2:5,3:6)) 77 | if (.not.all(i2_8(2:5,3:6) == i2)) error stop 'read into larger array fail' 78 | 79 | !> real 80 | call h%shape('real32-2d',dims) 81 | allocate(rr2(dims(1), dims(2))) 82 | call h%read('real32-2d',rr2) 83 | if (.not.all(r2 == rr2)) error stop 'real 2-D: read does not match write' 84 | 85 | ! check read into a variable slice 86 | call h%read('real32-2d', B(2:5,3:6)) 87 | if(.not.all(B(2:5,3:6) == r2)) error stop 'real 2D: reading into variable slice' 88 | 89 | call h%read('nan',nant) 90 | if (.not.ieee_is_nan(nant)) error stop 'failed storing or reading NaN' 91 | 92 | call h%close() 93 | 94 | end subroutine test_basic_array 95 | 96 | 97 | subroutine test_read_slice(filename) 98 | 99 | character(*), intent(in) :: filename 100 | 101 | type(netcdf_file) :: h 102 | integer :: i 103 | integer(int32), dimension(4) :: i1, i1t 104 | integer(int32), dimension(4,4) :: i2, i2t 105 | 106 | do i = 1,size(i1) 107 | i1(i) = i 108 | enddo 109 | 110 | i2(1,:) = i1 111 | do i = 1,size(i2,2) 112 | i2(i,:) = i2(1,:) * i 113 | enddo 114 | 115 | call h%open(filename, 'r') 116 | 117 | i1t = 0 118 | call h%read('int32-1d', i1t(:2), istart=[2], iend=[3], stride=[1]) 119 | if (any(i1t(:2) /= [2,3])) then 120 | write(stderr, *) 'read 1D slice does not match. expected [2,3] but got ',i1t(:2) 121 | error stop 122 | endif 123 | 124 | i1t = 0 125 | call h%read('int32-1d', i1t(:2), istart=[2], iend=[3]) 126 | if (any(i1t(:2) /= [2,3])) then 127 | write(stderr, *) 'read 1D slice does not match. expected [2,3] but got ',i1t(:2) 128 | error stop 129 | endif 130 | 131 | i2t = 0 132 | call h%read('int32-2d', i2t(:2,:3), istart=[2,1], iend=[3,3], stride=[1,1]) 133 | if (any(i2t(:2,:3) /= i2(2:3,1:3))) then 134 | write(stderr, *) 'read 2D slice does not match. expected:',i2(2:3,1:3),' but got ',i2t(:2,:3) 135 | error stop 136 | endif 137 | 138 | call h%close() 139 | 140 | end subroutine test_read_slice 141 | 142 | 143 | subroutine test_write_slice(filename) 144 | 145 | character(*), intent(in) :: filename 146 | 147 | type(netcdf_file) :: h 148 | integer(int32), dimension(4) :: i1t 149 | integer(int32), dimension(4,4) :: i2t 150 | 151 | 152 | call h%open(filename, action='r+') 153 | 154 | call h%create('int32a-1d', dtype=NF90_INT, dims=[3]) 155 | call h%write('int32a-1d', [1,3], istart=[1], iend=[2]) 156 | print *, 'PASSED: create dataset and write slice 1D' 157 | 158 | call h%write('int32-1d', [35, 70], istart=[2], iend=[3], stride=[1]) 159 | 160 | call h%read('int32-1d', i1t) 161 | if (.not.all(i1t==[1,35,70,4])) then 162 | write(stderr, *) 'write 1D slice does not match. got ',i1t 163 | error stop 164 | endif 165 | print *, 'PASSED: overwrite slice 1d, stride=1' 166 | 167 | call h%write('int32-1d', [23,34,45], istart=[2], iend=[4]) 168 | call h%read('int32-1d', i1t) 169 | if (.not.all(i1t==[1,23,34,45])) then 170 | write(stderr, *) 'read 1D slice does not match.got ',i1t 171 | error stop 172 | endif 173 | print *, 'PASSED: overwrite slice 1d, no stride' 174 | 175 | 176 | call h%create('int32a-2d', dtype=NF90_INT, dims=[4,4]) 177 | print *, 'create and write slice 2d, stride=1' 178 | call h%write('int32a-2d', reshape([76,65,54,43], [2,2]), istart=[2,1], iend=[3,2]) 179 | call h%read('int32a-2d', i2t) 180 | 181 | call h%close() 182 | 183 | 184 | end subroutine test_write_slice 185 | 186 | 187 | 188 | end program 189 | -------------------------------------------------------------------------------- /test/test_attributes.f90: -------------------------------------------------------------------------------- 1 | program test_attr 2 | 3 | use, intrinsic:: iso_fortran_env, only: int64, int32, real32, real64, stderr=>error_unit 4 | use nc4fortran, only : netcdf_file 5 | 6 | implicit none (type, external) 7 | 8 | character(*), parameter :: filename = 'test_attr.h5' 9 | 10 | call test_write_attributes(filename) 11 | print *,'PASSED: write attributes' 12 | 13 | call test_read_attributes(filename) 14 | print *,'PASSED: read attributes' 15 | 16 | contains 17 | 18 | subroutine test_write_attributes(path) 19 | 20 | type(netcdf_file) :: h 21 | character(*), intent(in) :: path 22 | 23 | call h%open(path, action='w') 24 | 25 | call h%write('x', 1) 26 | 27 | call h%write_attribute('x', 'note','this is just a little number') 28 | call h%write_attribute('x', 'hello', 'hi') 29 | call h%write_attribute('x', 'life', 42) 30 | call h%write_attribute('x', 'life_float', 42._real32) 31 | call h%write_attribute('x', 'life_double', 42._real64) 32 | 33 | call h%close() 34 | 35 | end subroutine test_write_attributes 36 | 37 | 38 | subroutine test_read_attributes(path) 39 | 40 | type(netcdf_file) :: h 41 | character(*), intent(in) :: path 42 | character(1024) :: attr_str 43 | integer :: attr_int 44 | real(real32) :: attr32 45 | real(real64) :: attr64 46 | 47 | integer :: x 48 | 49 | call h%open(path, action='r') 50 | 51 | call h%read('x', x) 52 | if (x/=1) error stop 'read_attribute: unexpected value' 53 | 54 | call h%read_attribute('x', 'note', attr_str) 55 | if (attr_str /= 'this is just a little number') error stop 'read_attribute value note' 56 | 57 | call h%read_attribute('x', 'life', attr_int) 58 | if (attr_int /= 42) error stop 'read_attribute: int' 59 | 60 | call h%read_attribute('x', 'life_float', attr32) 61 | if (attr32 /= 42._real32) error stop 'read_attribute: real32' 62 | 63 | call h%read_attribute('x', 'life_double', attr64) 64 | if (attr64 /= 42._real64) error stop 'read_attribute: real64' 65 | 66 | call h%close() 67 | 68 | end subroutine test_read_attributes 69 | 70 | end program 71 | -------------------------------------------------------------------------------- /test/test_deflate_props.f90: -------------------------------------------------------------------------------- 1 | program test_deflate_props 2 | 3 | use, intrinsic :: iso_fortran_env, only : int64, stderr=>output_unit 4 | 5 | use nc4fortran, only : netcdf_file 6 | 7 | implicit none (type, external) 8 | 9 | character(*), parameter :: fn1='deflate1.nc' 10 | integer, parameter :: N(2) = [50, 1000], & 11 | MIN_COMP = 2 !< lots of CPUs, smaller arrays => poorer compression 12 | 13 | call test_read_deflate_props(fn1, N) 14 | print *,'OK: HDF5 read deflate properties' 15 | 16 | call test_get_deflate(fn1) 17 | print *, 'OK: get deflate' 18 | 19 | contains 20 | 21 | 22 | subroutine test_read_deflate_props(fn, N) 23 | 24 | character(*), intent(in) :: fn 25 | integer, intent(in) :: N(2) 26 | 27 | type(netcdf_file) :: h 28 | 29 | real :: crat 30 | integer :: fsize 31 | integer :: chunks(2) 32 | 33 | inquire(file=fn, size=fsize) 34 | 35 | crat = (N(1) * N(2) * 32 / 8) / real(fsize) 36 | print '(A,F6.2,A,f7.1)','#1 filesize (Mbytes): ',real(fsize)/1e6, ' compression ratio:',crat 37 | if(crat < MIN_COMP) error stop '2D low compression' 38 | 39 | call h%open(fn1, action='r') 40 | 41 | if(.not.h%is_chunked('A')) error stop '#1 not chunked layout: ' // fn 42 | 43 | call h%chunks('A', chunks) 44 | if(chunks(1) /= 5) then 45 | write(stderr, '(a,2I5)') "expected chunks(1) = 5 but got chunks ", chunks 46 | error stop '#1 A get_chunk mismatch' 47 | endif 48 | 49 | !if(.not.h%is_contig('small')) error stop '#1 not contig layout' 50 | call h%chunks('small', chunks) 51 | if(any(chunks(:2) /= 4)) then 52 | write(stderr, '(a,2I5)') "expected chunks(1) = 4 but got chunks ", chunks 53 | error stop 'small get_chunk mismatch' 54 | endif 55 | 56 | call h%close() 57 | 58 | end subroutine test_read_deflate_props 59 | 60 | 61 | subroutine test_get_deflate(fn) 62 | 63 | character(*), intent(in) :: fn 64 | 65 | type(netcdf_file) :: h 66 | 67 | call h%open(fn, action='r') 68 | 69 | if (.not. h%deflate("A")) error stop "test_get_deflate: expected deflate" 70 | 71 | call h%close() 72 | 73 | end subroutine test_get_deflate 74 | 75 | end program 76 | -------------------------------------------------------------------------------- /test/test_deflate_read.f90: -------------------------------------------------------------------------------- 1 | program test_deflate_read 2 | 3 | use, intrinsic:: iso_fortran_env, only: int32, int64, real32, real64, stderr=>error_unit 4 | 5 | use nc4fortran, only : netcdf_file 6 | 7 | implicit none (type, external) 8 | 9 | character(*), parameter :: fn1='deflate1.nc' 10 | integer, parameter :: N(2) = [50, 1000] 11 | 12 | 13 | call test_read_deflate(fn1, N) 14 | print *,'OK: read deflate' 15 | 16 | contains 17 | 18 | subroutine test_read_deflate(fn, N) 19 | 20 | character(*), intent(in) :: fn 21 | integer, intent(in) :: N(2) 22 | 23 | type(netcdf_file) :: h 24 | real(real32), allocatable :: A(:,:) 25 | 26 | allocate(A(N(1), N(2))) 27 | 28 | call h%open(fn, action='r') 29 | call h%read('A', A) 30 | call h%read('noMPI', A) 31 | call h%close() 32 | 33 | end subroutine test_read_deflate 34 | 35 | end program 36 | -------------------------------------------------------------------------------- /test/test_deflate_write.f90: -------------------------------------------------------------------------------- 1 | program test_deflate 2 | !! unit tests and registration tests of deflate compression write 3 | 4 | use, intrinsic:: iso_fortran_env, only: int32, real32, real64, stderr=>error_unit 5 | 6 | use nc4fortran, only: netcdf_file 7 | 8 | implicit none (type, external) 9 | 10 | character(*), parameter :: fn1='deflate1.nc', fn2='deflate2.nc' 11 | integer, parameter :: N(2) = [50, 1000], & 12 | MIN_COMP = 2 !< lots of CPUs, smaller arrays => poorer compression 13 | !! don't use too big otherwise platform/version dependent autochunk fouls up test ~ 4MB 14 | 15 | call test_write_deflate(fn1, N) 16 | print *,'OK: write deflate' 17 | 18 | call test_deflate_whole(fn2, N) 19 | print *,'OK: compress whole' 20 | 21 | contains 22 | 23 | subroutine test_write_deflate(fn, N) 24 | 25 | character(*), intent(in) :: fn 26 | integer, intent(in) :: N(2) 27 | 28 | type(netcdf_file) :: h 29 | real(real32), allocatable :: A(:,:) 30 | 31 | allocate(A(N(1), N(2))) 32 | 33 | A = 0 !< simplest data 34 | 35 | call h%open(fn, action='w', comp_lvl=1) 36 | call h%write('A', A, dims=['x','y'], chunk_size=[5, 50]) 37 | call h%close() 38 | 39 | deallocate(A) 40 | 41 | allocate(A(N(1), N(2))) 42 | A = 1 !< simplest data 43 | 44 | !! write with compression 45 | call h%open(fn, action='a', comp_lvl=1) 46 | 47 | call h%write('small', A(:4,:4)) 48 | !! not compressed because too small 49 | 50 | call h%write('noMPI', A) 51 | !! write without MPI, with compression 52 | 53 | call h%close() 54 | 55 | end subroutine test_write_deflate 56 | 57 | 58 | subroutine test_deflate_whole(fn, N) 59 | 60 | character(*), intent(in) :: fn 61 | integer, intent(in) :: N(2) 62 | 63 | type(netcdf_file) :: h 64 | real, allocatable :: A(:,:,:) 65 | integer :: chunks(3) 66 | real :: crat 67 | integer :: fsize 68 | 69 | allocate(A(N(1), N(2), 4)) 70 | 71 | call h%open(fn, action='w', comp_lvl=3) 72 | 73 | call h%write('A', A, chunk_size=[4, 20, 1]) 74 | call h%chunks('A', chunks) 75 | if(chunks(1) /= 4 .or. chunks(3) /= 1) then 76 | write(stderr, '(a,3I5)') "expected chunks: 4,*,1 but got chunks ", chunks 77 | error stop '#2 manual chunk unexpected chunk size' 78 | endif 79 | 80 | call h%write('A_autochunk', A) 81 | call h%chunks('A_autochunk', chunks) 82 | if(any(chunks < 1)) error stop '#2 auto chunk unexpected chunk size' 83 | call h%close() 84 | 85 | inquire(file=fn, size=fsize) 86 | crat = (2 * N(1) * N(2) * 4 * storage_size(A) / 8) / real(fsize) 87 | !! 2* since two datasets same size 88 | 89 | print '(A,F6.2,A,f7.1)','#2 filesize (Mbytes): ', real(fsize) / 1e6, ' compression ratio:', crat 90 | 91 | if(crat < MIN_COMP) error stop fn // ' low compression' 92 | 93 | end subroutine test_deflate_whole 94 | 95 | 96 | end program 97 | -------------------------------------------------------------------------------- /test/test_destructor.f90: -------------------------------------------------------------------------------- 1 | program test_destruct 2 | !! test netcdf_file destructor, that should auto-flush and close file 3 | !! if user forgets to %close() file 4 | 5 | use, intrinsic :: iso_fortran_env, only : stderr=>error_unit 6 | use nc4fortran, only: netcdf_file 7 | implicit none (type, external) 8 | 9 | character(*), parameter :: fn = "test_destruct.h5" 10 | 11 | call test_destructor_write(fn) 12 | print *, 'OK: destructor write' 13 | 14 | call test_destructor_read(fn) 15 | print *, 'OK: destructor read' 16 | 17 | contains 18 | 19 | 20 | subroutine test_destructor_write(fn) 21 | 22 | character(*), intent(in) :: fn 23 | 24 | type(netcdf_file) :: h 25 | 26 | call h%open(fn, action="w") 27 | 28 | call h%write('x', 42) 29 | 30 | !! deliberately omitted %close() to test destructor 31 | 32 | end subroutine test_destructor_write 33 | 34 | 35 | subroutine test_destructor_read(fn) 36 | 37 | character(*), intent(in) :: fn 38 | 39 | integer :: i 40 | type(netcdf_file) :: h 41 | 42 | call h%open(fn, action="r") 43 | 44 | call h%read("x", i) 45 | if(i/=42) error stop "destructor did not flush " // fn 46 | 47 | !! deliberately omitted %close() to test destructor 48 | 49 | end subroutine test_destructor_read 50 | 51 | end program 52 | -------------------------------------------------------------------------------- /test/test_error.f90: -------------------------------------------------------------------------------- 1 | program test_errors 2 | use, intrinsic:: iso_fortran_env, only: int64, int32, real32, real64, stderr=>error_unit 3 | use nc4fortran, only: netcdf_file 4 | 5 | implicit none (type, external) 6 | 7 | call test_wrong_type() 8 | print *, "OK: wrong type read" 9 | 10 | contains 11 | 12 | 13 | subroutine test_wrong_type() 14 | integer :: u 15 | type(netcdf_file) :: h 16 | character(*), parameter :: filename = 'bad.nc' 17 | 18 | call h%open(filename, action='w') 19 | call h%write('real32', 42.) 20 | call h%close() 21 | 22 | call h%open(filename, action='r') 23 | call h%read('real32', u) 24 | if (u /= 42) error stop 'test_wrong_type: did not coerce real to integer' 25 | call h%close() 26 | 27 | end subroutine test_wrong_type 28 | 29 | 30 | end program 31 | -------------------------------------------------------------------------------- /test/test_exist.f90: -------------------------------------------------------------------------------- 1 | program exist_tests 2 | !! test "exist" variable 3 | use, intrinsic :: iso_fortran_env, only : stderr=>error_unit 4 | use nc4fortran, only : netcdf_file, is_netcdf, nc_exist 5 | 6 | implicit none (type, external) 7 | 8 | call test_is_netcdf() 9 | print *, 'OK: is_netcdf' 10 | 11 | call test_exist('exist.h5') 12 | print *, 'OK: exist' 13 | 14 | call test_multifiles() 15 | print *, 'OK: multiple files open at once' 16 | 17 | contains 18 | 19 | subroutine test_is_netcdf() 20 | integer :: i 21 | 22 | if(is_netcdf('apidfjpj-8j9ejfpq984jfp89q39SHf.nc')) error stop 'test_exist: non-existent file declared netcdf' 23 | 24 | open(newunit=i, file='not_netcdf.nc', action='write', status='replace') 25 | write(i,*) 'I am not a NetCDF4 file.' 26 | close(i) 27 | 28 | if(is_netcdf('not.nc')) error stop 'text files are not NetCDF4' 29 | 30 | end subroutine test_is_netcdf 31 | 32 | 33 | subroutine test_exist(fn) 34 | 35 | character(*), intent(in) :: fn 36 | 37 | type(netcdf_file) :: h 38 | 39 | call h%open(fn, action='w') 40 | call h%write('x', 42) 41 | call h%close() 42 | if(.not.is_netcdf(fn)) error stop 'file does not exist' 43 | 44 | call h%open(fn, "r") 45 | if (.not. h%exist('x')) error stop 'x exists' 46 | 47 | if (h%exist('A')) error stop 'variable A should not exist in ' // h%filename 48 | 49 | call h%close() 50 | 51 | if(h%is_open) error stop 'file is closed' 52 | 53 | if (.not. nc_exist(fn, 'x')) error stop 'x exists' 54 | if (nc_exist(fn, 'A')) error stop 'A not exist' 55 | 56 | end subroutine test_exist 57 | 58 | 59 | subroutine test_multifiles() 60 | 61 | type(netcdf_file) :: f,g,h 62 | 63 | call f%open(filename='A.nc', action='w') 64 | call g%open(filename='B.nc', action='w') 65 | if (h%is_open) error stop 'is_open not isolated at constructor' 66 | call h%open(filename='C.nc', action='w') 67 | 68 | call f%flush() 69 | 70 | call f%close() 71 | if (.not.g%is_open .or. .not. h%is_open) error stop 'is_open not isolated at destructor' 72 | call g%close() 73 | call h%close() 74 | 75 | end subroutine test_multifiles 76 | 77 | end program 78 | -------------------------------------------------------------------------------- /test/test_fill.f90: -------------------------------------------------------------------------------- 1 | program test_fill 2 | 3 | use, intrinsic:: iso_fortran_env, only : real32, real64, int32, int64 4 | use, intrinsic:: ieee_arithmetic, only : ieee_value, ieee_quiet_nan, ieee_is_finite 5 | 6 | use nc4fortran, only : netcdf_file 7 | use netcdf, only : NF90_FLOAT, NF90_DOUBLE, NF90_INT, NF90_INT64 8 | 9 | implicit none (type, external) 10 | 11 | type(netcdf_file) :: nc 12 | 13 | real(real32) :: Nan32, r32 14 | real(real64) :: Nan64, r64 15 | integer(int32) :: i32 16 | integer(int64) :: i64 17 | 18 | NaN32 = ieee_value(0., ieee_quiet_nan) 19 | NaN64 = ieee_value(0._real64, ieee_quiet_nan) 20 | 21 | call nc%open("test_fill.nc", "w") 22 | 23 | call nc%create("real32", NF90_FLOAT, [1], fill_value=NaN32) 24 | call nc%create("real64>32", NF90_FLOAT, [1], fill_value=NaN64) 25 | 26 | call nc%create("real32>64", NF90_DOUBLE, [1,1], fill_value=NaN32) 27 | call nc%create("real64", NF90_DOUBLE, [1,1], fill_value=NaN64) 28 | 29 | call nc%create("int32", NF90_INT, [1], fill_value=-1) 30 | call nc%create("int64>32", NF90_INT64, [1], fill_value=int(-1, int64)) 31 | 32 | call nc%create("int64", NF90_INT64, [1], fill_value=int(-1, int64)) 33 | 34 | call nc%close() 35 | 36 | call nc%open("test_fill.nc", "r") 37 | 38 | call nc%read("real32", r32) 39 | call nc%read("real64", r64) 40 | call nc%read("int32", i32) 41 | call nc%read("int64", i64) 42 | 43 | if (ieee_is_finite(r32)) error stop "fill cast float32" 44 | if (ieee_is_finite(r64)) error stop "fill cast float64" 45 | if (i32 /= -1 .or. i64 /= -1) error stop "fill cast int" 46 | 47 | call nc%close() 48 | 49 | end program 50 | -------------------------------------------------------------------------------- /test/test_minimal.f90: -------------------------------------------------------------------------------- 1 | program test_minimal 2 | 3 | use netcdf, only : nf90_create, nf90_def_var, nf90_put_var, nf90_close, NF90_CLOBBER, NF90_NETCDF4, NF90_INT 4 | 5 | implicit none (type, external) 6 | 7 | integer :: i, file_id, varid 8 | character(*), parameter :: filename='test_minimal.nc' 9 | 10 | i = nf90_create(filename, ior(NF90_CLOBBER, NF90_NETCDF4), file_id) 11 | if (i/=0) error stop 'minimal: could not create file' 12 | print *, 'minimal: created '// filename 13 | 14 | i = nf90_def_var(file_id, 'x', NF90_INT, varid=varid) 15 | i = nf90_put_var(file_id, varid, 42) 16 | if (i/=0) error stop 'minimal: could not create variable' 17 | print *, 'minimal: created variable' 18 | 19 | i = nf90_close(file_id) 20 | if (i/=0) error stop 'minimal: could not close file' 21 | print *, 'minimal: closed '// filename 22 | 23 | ! this is a Fortran-standard way to delete files 24 | open(newunit=i, file=filename) 25 | close(i, status='delete') 26 | 27 | end program 28 | -------------------------------------------------------------------------------- /test/test_scalar.f90: -------------------------------------------------------------------------------- 1 | program test_scalar 2 | 3 | use, intrinsic:: iso_fortran_env, only: int64, int32, real32, real64, stderr=>error_unit 4 | 5 | use nc4fortran, only : netcdf_file 6 | use netcdf, only : NF90_INT, NF90_INT64 7 | 8 | implicit none (type, external) 9 | 10 | character(*), parameter :: fn = 'test_scalar.nc' 11 | 12 | call test_scalar_write(fn) 13 | print *, 'OK: scalar and vector: write and rewrite' 14 | 15 | call test_scalar_read(fN) 16 | print *, 'OK: scalar and vector: read' 17 | 18 | contains 19 | 20 | subroutine test_scalar_write(fn) 21 | 22 | character(*), intent(in) :: fn 23 | 24 | type(netcdf_file) :: h 25 | 26 | real(real32) :: r1(4) 27 | integer(int32) :: i1(4) 28 | integer(int64) :: i1_64(4) 29 | 30 | integer :: i 31 | 32 | do i = 1,size(i1) 33 | i1(i) = i 34 | enddo 35 | 36 | r1 = i1 37 | i1_64 = i1 38 | 39 | !> write 40 | call h%open(fn, action='w') 41 | !> scalar tests 42 | call h%write('scalar_int32', 42_int32) 43 | call h%write('scalar_int64', 42_int64) 44 | call h%write('scalar_real32', -1._real32) 45 | call h%write('scalar_real64', -1._real64) 46 | 47 | !> vector 48 | call h%write('1d_real', r1) 49 | call h%write('vector_scalar_real', [37.]) 50 | 51 | !> create then write 52 | call h%create('1d_int32', NF90_INT, dims=shape(i1)) 53 | call h%write('1d_int32', i1) 54 | 55 | call h%create('1d_int64', NF90_INT64, dims=shape(i1_64)) 56 | call h%write('1d_int64', i1_64) 57 | 58 | !> test rewrite 59 | call h%write('scalar_real32', 42._real32) 60 | call h%write('scalar_real64', 42._real64) 61 | call h%write('scalar_int32', 42_int32) 62 | call h%write('scalar_int64', 42_int64) 63 | call h%close() 64 | 65 | end subroutine test_scalar_write 66 | 67 | 68 | subroutine test_scalar_read(fn) 69 | 70 | character(*), intent(in) :: fn 71 | 72 | type(netcdf_file) :: h 73 | 74 | real(real32) :: rt, r1(4) 75 | integer(int32) :: i, it, i1(4) 76 | integer(int32), allocatable :: i1t(:) 77 | integer(int64) :: it_64, i1_64(4) 78 | integer(int64), allocatable :: i1t_64(:) 79 | real(real32), allocatable :: rr1_32(:) 80 | integer, allocatable :: dims(:) 81 | 82 | !> test data 83 | do i = 1,size(i1) 84 | i1(i) = i 85 | enddo 86 | 87 | r1 = i1 88 | i1_64 = i1 89 | 90 | !> read 91 | 92 | call h%open(fn, action='r') 93 | 94 | call h%read('scalar_int32', it) 95 | call h%read('scalar_int64', it_64) 96 | 97 | call h%read('scalar_real32', rt) 98 | if (.not.(rt==it .and. it==42)) then 99 | write(stderr,*) it,'/=',rt 100 | error stop 'scalar real / int: not equal 42' 101 | endif 102 | 103 | !> read vector length 1 as scalar 104 | call h%shape('vector_scalar_real', dims) 105 | if (any(dims /= [1])) error stop "vector_scalar: expected vector length 1" 106 | 107 | call h%read('vector_scalar_real', rt) 108 | if(rt /= 37) error stop 'vector_scalar: 1d length 1 => scalar' 109 | 110 | !> 1D vector read 111 | call h%shape('1d_real', dims) 112 | allocate(rr1_32(dims(1))) 113 | 114 | call h%read('1d_real', rr1_32) 115 | if (.not.all(r1 == rr1_32)) error stop 'real32 1-D: read does not match write' 116 | 117 | call h%shape('1d_int32',dims) 118 | allocate(i1t(dims(1))) 119 | call h%read('1d_int32', i1t) 120 | if (.not.all(i1==i1t)) error stop 'int32 1-D: read does not match write' 121 | 122 | allocate(i1t_64(dims(1))) 123 | call h%read('1d_int64', i1t_64) 124 | if (.not.all(i1_64==i1t_64)) error stop 'int64 1-D: read does not match write' 125 | 126 | !> check filename property 127 | if (.not. h%filename == fn) error stop h%filename // ' mismatch filename' 128 | 129 | call h%close() 130 | 131 | end subroutine test_scalar_read 132 | 133 | end program 134 | -------------------------------------------------------------------------------- /test/test_shape.f90: -------------------------------------------------------------------------------- 1 | program test_shape 2 | !! This program shows how netcdf dimension orders are distinct in different languages 3 | use nc4fortran, only: netcdf_file, is_netcdf, NF90_MAX_NAME 4 | use, intrinsic:: iso_fortran_env, only: real64, stdout=>output_unit, stderr=>error_unit 5 | 6 | implicit none (type, external) 7 | 8 | character(*), parameter :: fn = 'test_shape.nc' 9 | 10 | 11 | call test_shape_write(fn) 12 | 13 | call test_shape_read(fn) 14 | print *, "OK: test_shape" 15 | 16 | 17 | contains 18 | 19 | 20 | subroutine test_shape_write(fn) 21 | 22 | character(*), intent(in) :: fn 23 | 24 | type(netcdf_file) :: h 25 | integer :: d2(3,4), d7(2,1,3,4,7,6,5) 26 | 27 | call h%open(fn, action='w') 28 | call h%write('d2', d2) 29 | call h%write('d7', d7, dims=['x','y','z', 'p','q','r','s']) 30 | call h%close() 31 | 32 | end subroutine test_shape_write 33 | 34 | 35 | subroutine test_shape_read(fn) 36 | 37 | character(*), intent(in) :: fn 38 | 39 | type(netcdf_file) :: h 40 | character(NF90_MAX_NAME), allocatable :: dimnames(:) 41 | integer, allocatable :: dims(:) 42 | integer :: d2(3,4), d7(2,1,3,4,7,6,5) 43 | 44 | call h%open(fn, action='r') 45 | call h%shape('d2', dims) 46 | if (h%ndim('d2') /= size(dims)) error stop 'rank /= size(dims)' 47 | if (any(dims /= shape(d2))) error stop '2-D: file shape not match variable shape' 48 | 49 | call h%shape('d7', dims, dimnames) 50 | if (h%ndim('d7') /= size(dims)) error stop 'rank /= size(dims)' 51 | if (any(dims /= shape(d7))) error stop '7-D: file shape not match variable shape' 52 | 53 | call h%close() 54 | 55 | end subroutine test_shape_read 56 | 57 | end program 58 | -------------------------------------------------------------------------------- /test/test_string.f90: -------------------------------------------------------------------------------- 1 | program test_string 2 | 3 | use, intrinsic:: iso_fortran_env, only: stderr=>error_unit 4 | use, intrinsic:: iso_c_binding, only: c_null_char 5 | 6 | use nc4fortran, only : netcdf_file 7 | 8 | implicit none (type, external) 9 | 10 | character(*), parameter :: fn='test_string.nc' 11 | 12 | call test_write(fn) 13 | print *, "OK: HDF5 string write" 14 | 15 | call test_read(fn) 16 | print *,'OK: HDF5 string read' 17 | 18 | call test_overwrite(fn) 19 | print *, "OK: string overwrite" 20 | 21 | print *,'PASSED: HDF5 string write/read' 22 | 23 | contains 24 | 25 | 26 | subroutine test_write(fn) 27 | 28 | character(*), intent(in) :: fn 29 | 30 | type(netcdf_file) :: h 31 | 32 | call h%open(fn, action='w') 33 | 34 | call h%write('little', '42') 35 | call h%write('MySentence', 'this is a little sentence.') 36 | 37 | call h%close() 38 | 39 | end subroutine test_write 40 | 41 | 42 | subroutine test_read(fn) 43 | 44 | character(*), intent(in) :: fn 45 | 46 | type(netcdf_file) :: h 47 | character(2) :: A 48 | character(1024) :: val1k 49 | 50 | call h%open(fn, action='r') 51 | call h%read('little', A) 52 | 53 | if(len_trim(A) /= 2) then 54 | write(stderr,'(a,i0,a)') "test_string: read length ", len_trim(A), " /= 2" 55 | error stop 56 | endif 57 | if (A /= '42') error stop 'test_string: read/write verification failure. Value: '// A 58 | 59 | !> longer character than data 60 | call h%read('little', val1k) 61 | 62 | if (len_trim(val1k) /= 2) then 63 | write(stderr, '(a,i0,/,a)') 'expected character len_trim 2 but got len_trim() = ', len_trim(val1k), val1k 64 | error stop 65 | endif 66 | 67 | call h%close() 68 | 69 | end subroutine test_read 70 | 71 | 72 | subroutine test_overwrite(fn) 73 | 74 | character(*), intent(in) :: fn 75 | 76 | type(netcdf_file) :: h 77 | character(2) :: v 78 | 79 | call h%open(fn, action='rw') 80 | call h%write('little', '73') 81 | call h%close() 82 | 83 | call h%open(fn, action='r') 84 | call h%read('little', v) 85 | call h%close() 86 | 87 | if (v /= '73') error stop 'test_string: overwrite string failure. Value: '// v // " /= 73" 88 | 89 | end subroutine test_overwrite 90 | 91 | end program 92 | -------------------------------------------------------------------------------- /test/test_version.f90: -------------------------------------------------------------------------------- 1 | program test_version 2 | !! tests that NetCDF4 library version is available 3 | 4 | use nc4fortran, only : nc4version 5 | 6 | implicit none (type, external) 7 | 8 | character(24) :: vstr 9 | character(:), allocatable :: v 10 | character(:), allocatable :: libver, compver 11 | integer :: i 12 | 13 | v = nc4version() 14 | 15 | print '(A)', v 16 | 17 | if(command_argument_count() < 1) stop 18 | 19 | call get_command_argument(1, vstr, status=i) 20 | if (i/=0) error stop "input version string to compare" 21 | 22 | compver = get_version_mmr(vstr) 23 | libver = get_version_mmr(v) 24 | if (compver /= libver) error stop "version mismatch: " // compver // " /= " // libver 25 | 26 | contains 27 | 28 | pure function get_version_mmr(v) 29 | !! get the major.minor.release part of a version string 30 | !! cuts off further patch and arbitrary text 31 | character(:), allocatable :: get_version_mmr 32 | character(*), intent(in) :: v 33 | 34 | integer :: i, j, k, e 35 | 36 | k = index(v, '.') 37 | j = index(v(k+1:), '.') 38 | i = scan(v(k+j+1:), '.-_ ') 39 | if (i == 0) then 40 | e = len_trim(v) 41 | else 42 | e = k + j + i - 1 43 | end if 44 | 45 | get_version_mmr = v(:e) 46 | 47 | end function get_version_mmr 48 | 49 | 50 | end program 51 | --------------------------------------------------------------------------------