Skip to content

Commit

Permalink
netcdf-c[xx]: CMake/Windows build (spack#34935)
Browse files Browse the repository at this point in the history
netcdf-cxx and netcdf-c now build with CMake rather than Autotools.
netcdf-c can still optionally build with Autotools (but defaults to
CMake). With some additional patches to the CMake files, netcdf-c
can use CMake to build on Windows.
  • Loading branch information
johnwparent authored Mar 21, 2023
1 parent 84ab725 commit 2e9d0e1
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ba66a6d4..217aa712 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -856,7 +856,7 @@ IF(USE_HDF5)
# This needs to be near the beginning since we
# need to know whether to add "-lz" to the symbol
# tests below.
- CHECK_C_SOURCE_COMPILES("#include <H5public.h>
+ CHECK_C_SOURCE_COMPILES("#include <H5pubconf.h>
#if !H5_HAVE_ZLIB_H
#error
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b057311..37e96a96 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1471,6 +1471,7 @@ ENDIF()

# Enable Parallel IO with netCDF-4/HDF5 files using HDF5 parallel I/O.
SET(STATUS_PARALLEL "OFF")
+set(IMPORT_MPI "")
OPTION(ENABLE_PARALLEL4 "Build netCDF-4 with parallel IO" "${HDF5_PARALLEL}")
IF(ENABLE_PARALLEL4 AND ENABLE_HDF5)
IF(NOT HDF5_PARALLEL)
@@ -1492,6 +1493,7 @@ IF(ENABLE_PARALLEL4 AND ENABLE_HDF5)
FILE(COPY "${netCDF_BINARY_DIR}/tmp/run_par_tests.sh"
DESTINATION ${netCDF_BINARY_DIR}/h5_test
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
+ set(IMPORT_MPI "include(CMakeFindDependencyMacro)\nfind_dependency(mpi COMPONENTS C)")
ENDIF()
ENDIF()

diff --git a/liblib/CMakeLists.txt b/liblib/CMakeLists.txt
index e3eddc0f..0493cb9d 100644
--- a/liblib/CMakeLists.txt
+++ b/liblib/CMakeLists.txt
@@ -50,6 +50,7 @@ ADD_LIBRARY(netcdf nc_initialize.c ${LARGS} )

IF(MPI_C_INCLUDE_PATH)
target_include_directories(netcdf PUBLIC ${MPI_C_INCLUDE_PATH})
+ target_link_libraries(netcdf PUBLIC MPI::MPI_C)
ENDIF(MPI_C_INCLUDE_PATH)

IF(MOD_NETCDF_NAME)
diff --git a/netCDFConfig.cmake.in b/netCDFConfig.cmake.in
index 9d68eec5..dae2429e 100644
--- a/netCDFConfig.cmake.in
+++ b/netCDFConfig.cmake.in
@@ -14,6 +14,8 @@ set(netCDF_LIBRARIES netCDF::netcdf)
# include target information
include("${CMAKE_CURRENT_LIST_DIR}/netCDFTargets.cmake")

+@IMPORT_MPI@
+
# Compiling Options
#
set(netCDF_C_COMPILER "@CC_VERSION@")
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 65891d82..15567c8f 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -62,6 +62,9 @@ MACRO(buildplugin TARGET TARGETLIB)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS "/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF")
# Set file name & location
set_target_properties(${TARGET} PROPERTIES COMPILE_PDB_NAME ${TARGET} COMPILE_PDB_OUTPUT_DIR ${CMAKE_BINARY_DIR})
+ IF(MPI_C_INCLUDE_PATH)
+ target_include_directories(${TARGET} PRIVATE ${MPI_C_INCLUDE_PATH})
+ ENDIF(MPI_C_INCLUDE_PATH)
ENDIF()
ENDMACRO()

157 changes: 97 additions & 60 deletions var/spack/repos/builtin/packages/netcdf-c/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

import os
import sys

from spack.build_systems.autotools import AutotoolsBuilder
from spack.build_systems.cmake import CMakeBuilder
from spack.package import *


class NetcdfC(AutotoolsPackage):
class NetcdfC(CMakePackage, AutotoolsPackage):
"""NetCDF (network Common Data Form) is a set of software libraries and
machine-independent data formats that support the creation, access, and
sharing of array-oriented scientific data. This is the C distribution."""
Expand Down Expand Up @@ -62,6 +65,9 @@ class NetcdfC(AutotoolsPackage):
when="@4.7.2",
)

patch("4.8.1-win-hdf5-with-zlib.patch", when="@4.8.1: platform=windows")

patch("netcdfc-mpi-win-support.patch", when="platform=windows")
# See https://github.com/Unidata/netcdf-c/pull/1752
patch("4.7.3-spectrum-mpi-pnetcdf-detect.patch", when="@4.7.3:4.7.4 +parallel-netcdf")

Expand Down Expand Up @@ -89,11 +95,13 @@ class NetcdfC(AutotoolsPackage):
# description='Enable CDM Remote support')

# The patch for 4.7.0 touches configure.ac. See force_autoreconf below.
depends_on("autoconf", type="build", when="@4.7.0,main")
depends_on("automake", type="build", when="@4.7.0,main")
depends_on("libtool", type="build", when="@4.7.0,main")
with when("build_system=autotools"):
depends_on("autoconf", type="build", when="@4.7.0,main")
depends_on("automake", type="build", when="@4.7.0,main")
depends_on("libtool", type="build", when="@4.7.0,main")
# CMake system can use m4, but Windows does not yet support
depends_on("m4", type="build", when=sys.platform != "win32")

depends_on("m4", type="build")
depends_on("hdf~netcdf", when="+hdf4")

# curl 7.18.0 or later is required:
Expand Down Expand Up @@ -145,13 +153,72 @@ class NetcdfC(AutotoolsPackage):

filter_compiler_wrappers("nc-config", relative_root="bin")

default_build_system = "cmake" if sys.platform == "win32" else "autotools"

build_system("cmake", "autotools", default=default_build_system)

def setup_run_environment(self, env):
if "+zstd" in self.spec:
env.append_path("HDF5_PLUGIN_PATH", self.prefix.plugins)

@property
def libs(self):
shared = "+shared" in self.spec
return find_libraries("libnetcdf", root=self.prefix, shared=shared, recursive=True)


class Setup:
def setup_dependent_build_environment(self, env, dependent_spec):
self.pkg.setup_run_environment(env)
# Some packages, e.g. ncview, refuse to build if the compiler path returned by nc-config
# differs from the path to the compiler that the package should be built with. Therefore,
# we have to shadow nc-config from self.prefix.bin, which references the real compiler,
# with a backed up version, which references Spack compiler wrapper.
if os.path.exists(self._nc_config_backup_dir):
env.prepend_path("PATH", self._nc_config_backup_dir)


class BackupStep:
@property
def _nc_config_backup_dir(self):
return join_path(self.pkg.metadata_dir, "spack-nc-config")

@run_after("install")
def backup_nc_config(self):
# We expect this to be run before filter_compiler_wrappers:
nc_config_file = self.prefix.bin.join("nc-config")
if os.path.exists(nc_config_file):
mkdirp(self._nc_config_backup_dir)
install(nc_config_file, self._nc_config_backup_dir)


class CMakeBuilder(CMakeBuilder, BackupStep, Setup):
def cmake_args(self):
base_cmake_args = [
self.define_from_variant("BUILD_SHARED_LIBS", "shared"),
self.define("BUILD_UTILITIES", True),
self.define("ENABLE_NETCDF_4", True),
self.define_from_variant("ENABLE_DAP", "dap"),
self.define("CMAKE_INSTALL_PREFIX", self.prefix),
self.define_from_variant("ENABLE_HDF4", "hdf4"),
self.define("ENABLE_PARALLEL_TESTS", False),
self.define_from_variant("ENABLE_FSYNC", "fsync"),
self.define("ENABLE_LARGE_FILE_SUPPORT", True),
]
if "+parallel-netcdf" in self.pkg.spec:
base_cmake_args.append(self.define("ENABLE_PNETCDF", True))
if self.pkg.spec.satisfies("@4.3.1:"):
base_cmake_args.append(self.define("ENABLE_DYNAMIC_LOADING", True))
return base_cmake_args


class AutotoolsBuilder(AutotoolsBuilder, BackupStep, Setup):
@property
def force_autoreconf(self):
# The patch for 4.7.0 touches configure.ac.
return self.spec.satisfies("@4.7.0")
return self.pkg.spec.satisfies("@4.7.0")

@when("@4.6.3:")
def autoreconf(self, spec, prefix):
def autoreconf(self, pkg, spec, prefix):
if not os.path.exists(self.configure_abs_path):
Executable("./bootstrap")()

Expand All @@ -169,75 +236,75 @@ def configure_args(self):
"--enable-netcdf-4",
]

if "+optimize" in self.spec:
if "+optimize" in self.pkg.spec:
cflags.append("-O2")

config_args.extend(self.enable_or_disable("fsync"))

# The flag was introduced in version 4.3.1
if self.spec.satisfies("@4.3.1:"):
if self.pkg.spec.satisfies("@4.3.1:"):
config_args.append("--enable-dynamic-loading")

config_args += self.enable_or_disable("shared")

if "+pic" in self.spec:
cflags.append(self.compiler.cc_pic_flag)
if "+pic" in self.pkg.spec:
cflags.append(self.pkg.compiler.cc_pic_flag)

config_args += self.enable_or_disable("dap")
# config_args += self.enable_or_disable('cdmremote')

# if '+dap' in self.spec or '+cdmremote' in self.spec:
if "+dap" in self.spec:
# if '+dap' in self.pkg.spec or '+cdmremote' in self.pkg.spec:
if "+dap" in self.pkg.spec:
# Make sure Netcdf links against Spack's curl, otherwise it may
# pick up system's curl, which can give link errors, e.g.:
# undefined reference to `SSL_CTX_use_certificate_chain_file
curl = self.spec["curl"]
curl = self.pkg.spec["curl"]
curl_libs = curl.libs
libs.append(curl_libs.link_flags)
ldflags.append(curl_libs.search_flags)
# TODO: figure out how to get correct flags via headers.cpp_flags
cppflags.append("-I" + curl.prefix.include)
elif self.spec.satisfies("@4.8.0:"):
elif self.pkg.spec.satisfies("@4.8.0:"):
# Prevent overlinking to a system installation of libcurl:
config_args.append("ac_cv_lib_curl_curl_easy_setopt=no")

if self.spec.satisfies("@4.4:"):
if "+mpi" in self.spec:
if self.pkg.spec.satisfies("@4.4:"):
if "+mpi" in self.pkg.spec:
config_args.append("--enable-parallel4")
else:
config_args.append("--disable-parallel4")

if self.spec.satisfies("@4.3.2:"):
if self.pkg.spec.satisfies("@4.3.2:"):
config_args += self.enable_or_disable("jna")

# Starting version 4.1.3, --with-hdf5= and other such configure options
# are removed. Variables CPPFLAGS, LDFLAGS, and LD_LIBRARY_PATH must be
# used instead.
hdf5_hl = self.spec["hdf5:hl"]
hdf5_hl = self.pkg.spec["hdf5:hl"]
cppflags.append(hdf5_hl.headers.cpp_flags)
ldflags.append(hdf5_hl.libs.search_flags)

if "+parallel-netcdf" in self.spec:
if "+parallel-netcdf" in self.pkg.spec:
config_args.append("--enable-pnetcdf")
pnetcdf = self.spec["parallel-netcdf"]
pnetcdf = self.pkg.spec["parallel-netcdf"]
cppflags.append(pnetcdf.headers.cpp_flags)
# TODO: change to pnetcdf.libs.search_flags once 'parallel-netcdf'
# package gets custom implementation of 'libs'
ldflags.append("-L" + pnetcdf.prefix.lib)
else:
config_args.append("--disable-pnetcdf")

if "+mpi" in self.spec or "+parallel-netcdf" in self.spec:
config_args.append("CC=%s" % self.spec["mpi"].mpicc)
if "+mpi" in self.pkg.spec or "+parallel-netcdf" in self.pkg.spec:
config_args.append("CC=%s" % self.pkg.spec["mpi"].mpicc)

config_args += self.enable_or_disable("hdf4")
if "+hdf4" in self.spec:
hdf4 = self.spec["hdf"]
if "+hdf4" in self.pkg.spec:
hdf4 = self.pkg.spec["hdf"]
cppflags.append(hdf4.headers.cpp_flags)
# TODO: change to hdf4.libs.search_flags once 'hdf'
# package gets custom implementation of 'libs' property.
ldflags.append("-L" + hdf4.prefix.lib)
# TODO: change to self.spec['jpeg'].libs.link_flags once the
# TODO: change to self.pkg.spec['jpeg'].libs.link_flags once the
# implementations of 'jpeg' virtual package get 'jpeg_libs'
# property.
libs.append("-ljpeg")
Expand All @@ -247,12 +314,12 @@ def configure_args(self):
if "+external-xdr" in hdf4 and hdf4["rpc"].name != "libc":
libs.append(hdf4["rpc"].libs.link_flags)

if "+zstd" in self.spec:
zstd = self.spec["zstd"]
if "+zstd" in self.pkg.spec:
zstd = self.pkg.spec["zstd"]
cppflags.append(zstd.headers.cpp_flags)
ldflags.append(zstd.libs.search_flags)
config_args.append("--with-plugin-dir={}".format(self.prefix.plugins))
elif "~zstd" in self.spec:
elif "~zstd" in self.pkg.spec:
# Prevent linking to system zstd.
# There is no explicit option to disable zstd.
config_args.append("ac_cv_lib_zstd_ZSTD_compress=no")
Expand All @@ -268,36 +335,6 @@ def configure_args(self):

return config_args

def setup_run_environment(self, env):
if "+zstd" in self.spec:
env.append_path("HDF5_PLUGIN_PATH", self.prefix.plugins)

def setup_dependent_build_environment(self, env, dependent_spec):
self.setup_run_environment(env)
# Some packages, e.g. ncview, refuse to build if the compiler path returned by nc-config
# differs from the path to the compiler that the package should be built with. Therefore,
# we have to shadow nc-config from self.prefix.bin, which references the real compiler,
# with a backed up version, which references Spack compiler wrapper.
if os.path.exists(self._nc_config_backup_dir):
env.prepend_path("PATH", self._nc_config_backup_dir)

@run_after("install")
def backup_nc_config(self):
# We expect this to be run before filter_compiler_wrappers:
nc_config_file = self.prefix.bin.join("nc-config")
if os.path.exists(nc_config_file):
mkdirp(self._nc_config_backup_dir)
install(nc_config_file, self._nc_config_backup_dir)

def check(self):
# h5_test fails when run in parallel
make("check", parallel=False)

@property
def libs(self):
shared = "+shared" in self.spec
return find_libraries("libnetcdf", root=self.prefix, shared=shared, recursive=True)

@property
def _nc_config_backup_dir(self):
return join_path(self.metadata_dir, "spack-nc-config")

0 comments on commit 2e9d0e1

Please sign in to comment.