NetCDF-Parallel on an Apple Silicon Mac

From Planets
Revision as of 20:59, 15 October 2026 by Mwolff (talk | contribs) (→‎Step 0 – Set up the environment)

Jump to: navigation, search

Building a parallel NetCDF-4 stack on macOS (Apple Silicon)

This page builds Parallel HDF5 → NetCDF-C → NetCDF-Fortran → NetCDF-C++4 from source with Open MPI and installs everything into /opt/parallel-netcdf. Every step is a block of commands to copy and paste into a terminal, followed by what you should see. This motivated by the fact the OPEN-MPI on a Mac is now typically compiled with clang instead of gcc.

The current package versions tested:

Package Version Source
HDF5 2.2.0 GitHub release (HDFGroup/hdf5)
NetCDF-C 4.10.1 downloads.unidata.ucar.edu
NetCDF-Fortran 4.6.4 downloads.unidata.ucar.edu
NetCDF-C++4 4.3.1 downloads.unidata.ucar.edu

Last verified: 2026-10-15, macOS 26 (Darwin 25.6), Apple Silicon, 18 cores, Homebrew Open MPI 5.0.11. Total time is about 10–12 minutes, plus under a minute of downloads.

Before you start

  • Use one terminal window for the whole page. Step 0 sets variables that every later step needs. If you close the terminal or open a new one, paste the Step 0 block again before continuing.
  • The commands are written for bash. macOS terminals start zsh by default, so type bash first and press Enter.
  • Paste one block at a time and compare the result with the Expected box before moving on. If something differs, stop and see Troubleshooting.

Prerequisites

Install the compilers, Open MPI and build tools with Homebrew:

brew install gcc openmpi zlib ccache wget cmake

Expected: Homebrew finishes without errors. Check that the MPI compiler wrappers are found:

which mpicc mpicxx mpif90

Expected: three paths under /opt/homebrew/bin/.

Step 0 – Set up the environment

Check for newer versions (optional)

This lists the latest release of each package on GitHub and checks whether that tarball can actually be downloaded. The download check matters for the NetCDF libraries: they are hosted by Unidata, which sometimes publishes a release later than GitHub. Nothing is changed. If you have the NetCDFlib directory, ./check_versions.sh there does the same thing and also compares against the versions set in the scripts.

latest() { curl -fsS --max-time 20 "https://api.github.com/repos/$1/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | sed -e 's/^v//' -e 's/^hdf5_//'; }
avail() { curl -fsIL --max-time 20 -o /dev/null "$1" && echo "yes" || echo "NOT FOUND"; }
v=$(latest HDFGroup/hdf5);         echo "HDF5            latest $v   downloadable: $(avail https://github.com/HDFGroup/hdf5/releases/download/$v/hdf5-$v.tar.gz)"
v=$(latest Unidata/netcdf-c);      echo "NetCDF-C        latest $v   downloadable: $(avail https://downloads.unidata.ucar.edu/netcdf-c/$v/netcdf-c-$v.tar.gz)"
v=$(latest Unidata/netcdf-fortran); echo "NetCDF-Fortran  latest $v   downloadable: $(avail https://downloads.unidata.ucar.edu/netcdf-fortran/$v/netcdf-fortran-$v.tar.gz)"
v=$(latest Unidata/netcdf-cxx4);   echo "NetCDF-C++      latest $v   downloadable: $(avail https://downloads.unidata.ucar.edu/netcdf-cxx/$v/netcdf-cxx4-$v.tar.gz)"

Expected (as of the "Last verified" date):
HDF5 latest 2.2.0 downloadable: yes
NetCDF-C latest 4.10.1 downloadable: yes
NetCDF-Fortran latest 4.6.4 downloadable: yes
NetCDF-C++ latest 4.3.1 downloadable: yes
If latest is empty, the GitHub query failed (it allows 60 unauthenticated requests per hour); try again later.

Compare these with the export ..._VER lines in the next block. If a newer version is shown and is downloadable, you can edit its line before pasting. Be aware that this page was only verified with the versions in the table at the top: a newer version may need different options or produce different test counts, so compare carefully against the Expected boxes.

Create the install directory (first time only)

The libraries are installed into /opt/parallel-netcdf, which needs administrator rights (sudo) to create. This is the only command on the page that uses sudo. Paste it on its own: sudo asks for your password, and anything pasted after it would be read as the password instead of being run. Type your password (nothing is shown while you type) and press Enter.

sudo mkdir -p /opt/parallel-netcdf && sudo chown -R $(whoami):admin /opt/parallel-netcdf && ls -ld /opt/parallel-netcdf

Expected: one line such as drwxr-xr-x 2 yourname admin 64 Oct 15 10:00 /opt/parallel-netcdf, showing your user name. If the directory already exists and is owned by you, you can skip this block; running it again does no harm.

Set the environment

This defines the versions, directories and compilers used by every later step. It doesn't use sudo, so it can be pasted again at any time.

ROOT is the base directory for the downloaded source archives ($ROOT/netcdf_archives) and the temporary build tree ($ROOT/netcdf_parallel_scratch). It is set to your home directory; to use another location (for example a larger disk), change the first line before pasting. The installation itself always goes to /opt/parallel-netcdf.

export ROOT=$HOME

export HDF5_VER="2.2.0"
export NETCDF_C_VER="4.10.1"
export NETCDF_F_VER="4.6.4"
export NETCDF_CXX_VER="4.3.1"

export ARCHIVE_DIR="$ROOT/netcdf_archives"
export PREFIX="/opt/parallel-netcdf"
export TMP_BUILD_DIR="$ROOT/netcdf_parallel_scratch"
export HOMEBREW_PREFIX="/opt/homebrew"
export NUM_CORES=$(sysctl -n hw.ncpu)

export CC=mpicc
export CXX=mpicxx
export FC=mpif90
export F77=mpif77

export OMPI_MCA_io=romio341

export CPPFLAGS="-I$PREFIX/include -I$HOMEBREW_PREFIX/opt/zlib/include"
export LDFLAGS="-L$PREFIX/lib -L$HOMEBREW_PREFIX/opt/zlib/lib"
export PATH="$PREFIX/bin:$PATH"

mkdir -p "$ARCHIVE_DIR" "$TMP_BUILD_DIR"
[ -w "$PREFIX" ] || echo "ERROR: $PREFIX is missing or not writable by you; run the 'Create the install directory' block above"

echo "ROOT=$ROOT  PREFIX=$PREFIX  cores=$NUM_CORES  HDF5=$HDF5_VER  C=$NETCDF_C_VER  F=$NETCDF_F_VER  CXX=$NETCDF_CXX_VER"

Expected: one line such as ROOT=/Users/yourname PREFIX=/opt/parallel-netcdf cores=18 HDF5=2.2.0 C=4.10.1 F=4.6.4 CXX=4.3.1, and no ERROR: line.

OMPI_MCA_io=romio341 is not optional: without it, Open MPI's default I/O layer can hang during large parallel writes on macOS (see Troubleshooting).

To check later that the environment is still set (for example after a break), run:

echo "PREFIX=$PREFIX  cores=$NUM_CORES  HDF5=$HDF5_VER"

If PREFIX= is empty, paste the "Set the environment" block again.

Optional – Check for newer versions

This compares the versions set in Step 0 with the latest releases on GitHub, and checks whether each latest tarball can actually be downloaded. That second check matters for the NetCDF libraries: they are hosted by Unidata, which sometimes publishes a release later than GitHub. Nothing is changed. If you have the NetCDFlib directory, ./check_versions.sh there does the same thing.

latest() { curl -fsS --max-time 20 "https://api.github.com/repos/$1/releases/latest" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | sed -e 's/^v//' -e 's/^hdf5_//'; }
avail() { curl -fsIL --max-time 20 -o /dev/null "$1" && echo "yes" || echo "NOT FOUND"; }
v=$(latest HDFGroup/hdf5);         echo "HDF5            pinned $HDF5_VER   latest $v   downloadable: $(avail https://github.com/HDFGroup/hdf5/releases/download/$v/hdf5-$v.tar.gz)"
v=$(latest Unidata/netcdf-c);      echo "NetCDF-C        pinned $NETCDF_C_VER   latest $v   downloadable: $(avail https://downloads.unidata.ucar.edu/netcdf-c/$v/netcdf-c-$v.tar.gz)"
v=$(latest Unidata/netcdf-fortran); echo "NetCDF-Fortran  pinned $NETCDF_F_VER   latest $v   downloadable: $(avail https://downloads.unidata.ucar.edu/netcdf-fortran/$v/netcdf-fortran-$v.tar.gz)"
v=$(latest Unidata/netcdf-cxx4);   echo "NetCDF-C++      pinned $NETCDF_CXX_VER   latest $v   downloadable: $(avail https://downloads.unidata.ucar.edu/netcdf-cxx/$v/netcdf-cxx4-$v.tar.gz)"

Expected (as of the "Last verified" date):
HDF5 pinned 2.2.0 latest 2.2.0 downloadable: yes
NetCDF-C pinned 4.10.1 latest 4.10.1 downloadable: yes
NetCDF-Fortran pinned 4.6.4 latest 4.6.4 downloadable: yes
NetCDF-C++ pinned 4.3.1 latest 4.3.1 downloadable: yes
If latest is empty, the GitHub query failed (it allows 60 unauthenticated requests per hour); try again later.

If a newer version is shown and is downloadable, you can change its line in Step 0, paste Step 0 again, and continue. Be aware that this page was only verified with the versions in the table at the top: a newer version may need different options or produce different test counts, so compare carefully against the Expected boxes.

Step 1 – Download the sources

These must be the official release tarballs. The "Source code" archives that GitHub generates automatically lack the configure script and will not build. The NetCDF libraries are downloaded from Unidata because their GitHub releases have no downloadable files.

cd "$ARCHIVE_DIR"
curl -fL --retry 3 -o "hdf5-${HDF5_VER}.tar.gz" "https://github.com/HDFGroup/hdf5/releases/download/${HDF5_VER}/hdf5-${HDF5_VER}.tar.gz"
curl -fL --retry 3 -o "netcdf-c-${NETCDF_C_VER}.tar.gz" "https://downloads.unidata.ucar.edu/netcdf-c/${NETCDF_C_VER}/netcdf-c-${NETCDF_C_VER}.tar.gz"
curl -fL --retry 3 -o "netcdf-fortran-${NETCDF_F_VER}.tar.gz" "https://downloads.unidata.ucar.edu/netcdf-fortran/${NETCDF_F_VER}/netcdf-fortran-${NETCDF_F_VER}.tar.gz"
curl -fL --retry 3 -o "netcdf-cxx4-${NETCDF_CXX_VER}.tar.gz" "https://downloads.unidata.ucar.edu/netcdf-cxx/${NETCDF_CXX_VER}/netcdf-cxx4-${NETCDF_CXX_VER}.tar.gz"

Check that all four files are intact:

cd "$ARCHIVE_DIR"
gzip -t "hdf5-${HDF5_VER}.tar.gz" && echo "hdf5 OK"
gzip -t "netcdf-c-${NETCDF_C_VER}.tar.gz" && echo "netcdf-c OK"
gzip -t "netcdf-fortran-${NETCDF_F_VER}.tar.gz" && echo "netcdf-fortran OK"
gzip -t "netcdf-cxx4-${NETCDF_CXX_VER}.tar.gz" && echo "netcdf-cxx4 OK"

Expected: four OK lines. A curl: (22) ... 404 error means that version is not available at that URL; check the version numbers in Step 0.

Step 2 – Parallel HDF5 (about 3 minutes)

HDF5 2.x is built with CMake.

Extract

rm -rf "$TMP_BUILD_DIR/hdf5-${HDF5_VER}"
tar -xzf "$ARCHIVE_DIR/hdf5-${HDF5_VER}.tar.gz" -C "$TMP_BUILD_DIR"
mkdir -p "$TMP_BUILD_DIR/hdf5-${HDF5_VER}/build"
cd "$TMP_BUILD_DIR/hdf5-${HDF5_VER}/build"

Configure

cmake .. \
  -DCMAKE_INSTALL_PREFIX="$PREFIX" \
  -DCMAKE_INSTALL_NAME_DIR="$PREFIX/lib" \
  -DHDF5_ENABLE_PARALLEL=ON \
  -DHDF5_BUILD_FORTRAN=ON \
  -DHDF5_ENABLE_ZLIB_SUPPORT=ON \
  -DZLIB_INCLUDE_DIR="$HOMEBREW_PREFIX/opt/zlib/include" \
  -DZLIB_LIBRARY="$HOMEBREW_PREFIX/opt/zlib/lib/libz.dylib" \
  -DBUILD_SHARED_LIBS=ON \
  -DBUILD_TESTING=ON \
  -DHDF5_TEST_TOOLS=OFF \
  -DHDF5_TEST_SWMR=OFF \
  -DHDF5_TEST_EXAMPLES=OFF \
  -DMPIEXEC_MAX_NUMPROCS=4

Expected: ends with -- Build files have been written to: …/build

Build

cd "$TMP_BUILD_DIR/hdf5-${HDF5_VER}/build"
make -j"$NUM_CORES"

Expected: ends at [100%] with no Error lines.

Test (about 1.5 minutes)

Paste this block on its own and wait for it to finish. The MPI tests read from the terminal, so commands pasted ahead of time would be swallowed and never run.

cd "$TMP_BUILD_DIR/hdf5-${HDF5_VER}/build"
ctest -j"$NUM_CORES" --output-on-failure 2>&1 | tee ctest.log | tail -5

Expected: 100% tests passed out of 826

Install

cd "$TMP_BUILD_DIR/hdf5-${HDF5_VER}/build"
make install
otool -D "$PREFIX/lib/libhdf5_hl.dylib"

Expected: the last line is /opt/parallel-netcdf/lib/libhdf5_hl.320.dylib. If it starts with @rpath/ instead, the CMAKE_INSTALL_NAME_DIR setting was lost; redo this step.

Step 3 – NetCDF-C (about 5–6 minutes)

Extract and configure

rm -rf "$TMP_BUILD_DIR/netcdf-c-${NETCDF_C_VER}"
tar -xzf "$ARCHIVE_DIR/netcdf-c-${NETCDF_C_VER}.tar.gz" -C "$TMP_BUILD_DIR"
cd "$TMP_BUILD_DIR/netcdf-c-${NETCDF_C_VER}"
export LIBS="-lhdf5_hl -lhdf5 -lz"
./configure --prefix="$PREFIX" --host=aarch64-apple-darwin --enable-netcdf-4 --enable-parallel4 --disable-dap --disable-parallel-tests

Expected: ends with a "NetCDF C Configuration Summary" that includes HDF5 Support: yes and NC-4 Parallel Support: yes.

Build

cd "$TMP_BUILD_DIR/netcdf-c-${NETCDF_C_VER}"
make -j"$NUM_CORES"

Test (about 5 minutes)

cd "$TMP_BUILD_DIR/netcdf-c-${NETCDF_C_VER}"
make check > check.log 2>&1
echo "PASS: $(grep -c '^PASS:' check.log)   FAIL: $(grep -c -E '^(FAIL|ERROR):' check.log)"

Expected: PASS: 240 FAIL: 0. A much smaller PASS count means testing stopped early at a failure; run grep -E '^(FAIL|ERROR):' check.log to see which one.

Install and verify

cd "$TMP_BUILD_DIR/netcdf-c-${NETCDF_C_VER}"
make install
unset LIBS
nc-config --has-parallel4

Expected: yes. If it prints no, NetCDF-C was built without parallel support: stop here.

Step 4 – NetCDF-Fortran (about 2 minutes)

Extract and configure

rm -rf "$TMP_BUILD_DIR/netcdf-fortran-${NETCDF_F_VER}"
tar -xzf "$ARCHIVE_DIR/netcdf-fortran-${NETCDF_F_VER}.tar.gz" -C "$TMP_BUILD_DIR"
cd "$TMP_BUILD_DIR/netcdf-fortran-${NETCDF_F_VER}"
export LIBS="-lnetcdf -lhdf5_hl -lhdf5 -lz"
./configure --prefix="$PREFIX" --host=aarch64-apple-darwin --disable-parallel-tests

Build

cd "$TMP_BUILD_DIR/netcdf-fortran-${NETCDF_F_VER}"
make -j"$NUM_CORES"

Expected: many Warning: Type mismatch … / Rank mismatch … messages from gfortran. These are normal for NetCDF-Fortran; only Error lines matter.

Test

cd "$TMP_BUILD_DIR/netcdf-fortran-${NETCDF_F_VER}"
make check > check.log 2>&1
echo "PASS: $(grep -c '^PASS:' check.log)   FAIL: $(grep -c -E '^(FAIL|ERROR):' check.log)"

Expected: PASS: 59 FAIL: 0

Install and verify

cd "$TMP_BUILD_DIR/netcdf-fortran-${NETCDF_F_VER}"
make install
unset LIBS
nf-config --has-nc4

Expected: yes

Step 5 – NetCDF-C++4 (under 1 minute)

NetCDF-C++ needs two extra commands that remove files named VERSION; the reason is explained in Troubleshooting. Don't skip them.

Extract and configure

rm -rf "$TMP_BUILD_DIR/netcdf-cxx4-${NETCDF_CXX_VER}"
tar -xzf "$ARCHIVE_DIR/netcdf-cxx4-${NETCDF_CXX_VER}.tar.gz" -C "$TMP_BUILD_DIR"
cd "$TMP_BUILD_DIR/netcdf-cxx4-${NETCDF_CXX_VER}"
find . -type f \( -name "version" -o -name "VERSION" \) -print -delete
export LIBS="-lnetcdf -lhdf5_hl -lhdf5 -lz"
CXXFLAGS="$CPPFLAGS" ./configure --prefix="$PREFIX" --host=aarch64-apple-darwin --disable-filter-testing
rm -f VERSION

Build

cd "$TMP_BUILD_DIR/netcdf-cxx4-${NETCDF_CXX_VER}"
make -j"$NUM_CORES"

Expected: no error: lines. If you see ./VERSION:1:1: error: expected unqualified-id, the rm -f VERSION line was skipped: redo this step from "Extract and configure".

Test

cd "$TMP_BUILD_DIR/netcdf-cxx4-${NETCDF_CXX_VER}"
make check > check.log 2>&1
echo "PASS: $(grep -c '^PASS:' check.log)   FAIL: $(grep -c -E '^(FAIL|ERROR):' check.log)"

Expected: PASS: 16 FAIL: 0

Install

cd "$TMP_BUILD_DIR/netcdf-cxx4-${NETCDF_CXX_VER}"
make install
unset LIBS
ls "$PREFIX/lib/libnetcdf_c++4.dylib"

Expected: /opt/parallel-netcdf/lib/libnetcdf_c++4.dylib

Step 6 – Final check

This confirms that the installed tools run without any special library path settings, and that no installed file depends on @rpath.

env -u DYLD_LIBRARY_PATH "$PREFIX/bin/h5dump" --version
env -u DYLD_LIBRARY_PATH "$PREFIX/bin/nc-config" --version
env -u DYLD_LIBRARY_PATH "$PREFIX/bin/nf-config" --has-nc4
env -u DYLD_LIBRARY_PATH "$PREFIX/bin/ncdump" 2>&1 | head -1
otool -L "$PREFIX"/lib/*.dylib "$PREFIX"/bin/* 2>/dev/null | grep -c '@rpath'

Expected: h5dump: Version 2.2.0, netCDF 4.10.1, yes, a line starting with /opt/parallel-netcdf/bin/ncdump [-c|-h], and finally 0.

The scratch build directory is no longer needed. To free the disk space:

rm -rf "$TMP_BUILD_DIR"

Shell profile

Add these lines to your shell profile (~/.bash_profile for bash, ~/.zshrc for zsh) so programs can find the tools:

export PATH="/opt/parallel-netcdf/bin:$PATH"
export OMPI_MCA_io=romio341
  • PATH is required.
  • OMPI_MCA_io is possibly required, but untested with model runs: it avoids the Open MPI hang described below. It was needed for the HDF5 parallel tests to pass.
  • DYLD_LIBRARY_PATH is not needed: the libraries record their full install paths.

Troubleshooting

curl: (22) The requested URL returned error: 404
That version isn't hosted at the URL. Unidata only keeps recent versions (for example, NetCDF-C++ 4.2.1 is gone). Check the latest versions with check_versions.sh in the NetCDFlib directory, or on the GitHub release pages.
configure: error: cannot run C compiled programs
The --host=aarch64-apple-darwin option is missing from the ./configure line. It makes configure skip a test-run of a compiled program that fails on macOS with the MPI compiler wrappers; the libraries are still built natively.
dyld: Library not loaded: @rpath/libhdf5_hl.320.dylib
HDF5 was installed without -DCMAKE_INSTALL_NAME_DIR="$PREFIX/lib". Redo Step 2, then Steps 3–5 so they link against the corrected libraries. Setting DYLD_LIBRARY_PATH is not a reliable fix: macOS removes it whenever a program is started via /bin/sh or /bin/bash (as make, job scripts and many mpirun setups do).
A parallel test or model run hangs after mca_fbtl_posix_ipwritev: error in aio_write(): Resource temporarily unavailable
OMPI_MCA_io=romio341 is not set. Open MPI's default I/O layer (OMPIO) issues more asynchronous writes than macOS allows per process, then waits forever. ROMIO avoids this. Set the variable (Step 0 and your shell profile), or run mpirun --mca io romio341 ….
NetCDF-C++
./VERSION:1:1: error: expected unqualified-id (or expected external declaration)
The standard C++ headers include a header called <version>. On macOS the file system ignores case and the build searches the source directory for headers, so the compiler picks up the package's own VERSION file instead. NetCDF-C++'s configure creates that file, which is why Step 5 deletes it after configure. Never create version/VERSION files in that tree.
NetCDF-C++
Undefined symbols … _nc_set_log_level
--disable-filter-testing is missing from the configure line. Without it, an example program is built that needs a NetCDF-C function which only exists when NetCDF-C was built with logging enabled.
make check shows far fewer passes than expected
make check stops at the first directory with a failure, so the remaining tests never run. Look at the first FAIL: line in check.log and the matching .log file next to that test.