diff --git a/MANIFEST.in b/MANIFEST.in index 601cc08..af8bd52 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -global-include cLIBFMS/lib/libcFMS.* +graft pyfms/lib diff --git a/README.md b/README.md index fdcb2a6..2f0503e 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# fms2py +# **`fms2py`** + +## **Installation** +pyFMS requires a compiled `cFMS` library and the pyFMS repository contains +cFMS as a submodule. If `cFMS` is not installed on the user's +local system, the library can be installed with + +``` +1. git clone --recursive https://github.com/NOAA-GFDL/pyFMS.git +2. cd pyFMS +3. emacs ./compile.py (see Section compile.py) +4. python ./compile.py +``` + +The script `compile.py` will first compile and install the FMS library (which is +a submodule in cFMS) to `pyfms/lib/FMS`. Then, compile.py will compile the cFMS library +linking to FMS in `pyfms/lib/FMS`. cFMS will be installed to `pyfms/lib/cFMS`. + +Upon `import pyfms`, pyFMS will automatically load the cFMS library +in `pyfms/lib/cFMS`. If the cFMS library does not exist, or if users wish to load a +diferent instance of cFMS, the following should be set in the program before invoking +any pyFMS methods: + +``` +import pyfms + +pyfms.cfms.init(libpath=path_to_cfms/libcFMS.so) +``` + +## compile.py +To compile cFMS with the script `compile.py`, users will need to specify the following +fields: + +1. Fortran and C compilers, for example, as shown below: + +``` +FC = "mpif90" +CC = "mpicc" +``` + +2. Path to the libyaml and netCDF installations, for example, as shown below: + +``` +yaml = "/opt/libyaml/0.2.5/GNU/14.2.0/" +netcdf = "/opt/netcdf/4.9.3/GNU/14.2.0/" +``` diff --git a/cFMS b/cFMS index fc9771d..c0eb355 160000 --- a/cFMS +++ b/cFMS @@ -1 +1 @@ -Subproject commit fc9771d1d70d308e6f57f598afc6e48d35a63620 +Subproject commit c0eb3551f4200a3574460367fceed70d58de161c diff --git a/compile.py b/compile.py new file mode 100644 index 0000000..0262f60 --- /dev/null +++ b/compile.py @@ -0,0 +1,100 @@ +import os +import subprocess + + +# path to the installed yaml library +yaml = "/opt/libyaml/0.2.5/GNU/14.2.0/" + +# path to the installed netcdf library +netcdf = "/opt/netcdf/4.9.3/GNU/14.2.0/" + +# Fortran compiler +FC = "mpif90" + +# C compiler +CC = "mpicc" + +# default Fortran compiler flags +FMS_FCFLAGS = f"-I{yaml}/include -I{netcdf}/include -fPIC" + +# default C compiler flags +FMS_CFLAGS = f"-I{yaml}/include -I{netcdf}/include -fPIC" + +# library flags +FMS_LDFLAGS = f"-L{yaml}/lib -L{netcdf}/lib -lnetcdf" + +cFMS_FCFLAGS = "-fPIC" +cFMS_CFLAGS = "-fPIC" +cFMS_LDFLAGS = "" + +# current directory +currdir = os.path.dirname(__file__) + +# absolute path to cFMS submodule +cFMS = f"{currdir}/cFMS" + +# absolute path to FMS submodule +FMS = f"{cFMS}/FMS" + +# absolue path to install libraries +cFMS_install = f"{currdir}/pyfms/lib/cFMS" +FMS_install = f"{currdir}/pyfms/lib/FMS" + + +def compile_FMS(): + + """ + Install FMS to FMS_install + """ + + currdir = os.path.dirname(__file__) + os.chdir(FMS) + + subprocess.run(["autoreconf", "-iv"]) + subprocess.run( + [ + "./configure", + "--enable-portable-kinds", + "--with-yaml", + f"FC={FC}", + f"CC={CC}", + f"FCFLAGS={FMS_FCFLAGS}", + f"CFLAGS={FMS_CFLAGS}", + f"LDFLAGS={FMS_LDFLAGS}", + f"--prefix={FMS_install}", + ] + ) + subprocess.run(["make", "install"]) + + os.chdir(currdir) + + +def compile_cFMS(): + + """ + Install cFMS to cFMS_install + """ + currdir = os.path.dirname(__file__) + os.chdir(cFMS) + + subprocess.run(["autoreconf", "-iv"]) + subprocess.run( + [ + "./configure", + f"--with-fms={FMS_install}", + f"FC={FC}", + f"CC={CC}", + f"FCFLAGS={cFMS_FCFLAGS}", + f"CFLAGS={cFMS_CFLAGS}", + f"LDFLAGS={cFMS_LDFLAGS}", + f"--prefix={cFMS_install}", + ] + ) + subprocess.run(["make", "install"]) + + os.chdir(currdir) + + +# compile +compile_FMS() +compile_cFMS() diff --git a/compile_c_libs.sh b/compile_c_libs.sh deleted file mode 100755 index a292528..0000000 --- a/compile_c_libs.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -xe - -pyfms_dir=$PWD/pyfms -cfms_dir=$PWD/cFMS -install_fms=$cfms_dir/FMS/LIBFMS - -export FC=mpif90 -export CC=mpicc - -cd $cfms_dir/FMS -autoreconf -iv -export FCFLAGS="$FCFLAGS `nf-config --fflags` -fPIC" -export CFLAGS="$CFLAGS `nc-config --cflags` -fPIC" -./configure --enable-portable-kinds --with-yaml --prefix=$install_fms -make install - -cd .. - -export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$install_fms/lib" -export FCFLAGS="$FCFLAGS -I$install_fms/include" -export CFLAGS="$CFLAGS -I$install_fms/include" -export LDFLAGS="$LDFLAGS -lFMS -L$install_fms/lib" - -autoreconf -iv -./configure --with-fms=$install_fms --prefix=$pyfms_dir/cLIBFMS -make install diff --git a/pyfms/cfms.py b/pyfms/cfms.py index fd1dfb2..e849d21 100644 --- a/pyfms/cfms.py +++ b/pyfms/cfms.py @@ -4,8 +4,8 @@ import pyfms -_libpath: str = os.path.dirname(__file__) + "/cLIBFMS/lib/libcFMS.so" -_lib: type[ctypes.CDLL] = ctypes.cdll.LoadLibrary(_libpath) +_libpath = None +_lib = None def init(libpath: str = None): @@ -21,7 +21,17 @@ def init(libpath: str = None): global _libpath, _lib - if libpath is not None: + if libpath is None: + _libpath = os.path.dirname(__file__) + "/lib/cFMS/lib/libcFMS.so" + try: + _lib = ctypes.cdll.LoadLibrary(_libpath) + except OSError: + print( + f"{_libpath} does not exist. Please compile cFMS with ./compile.py\ + or provide a path to cFMS with pyfms.cfms.init(libpath=path_to_cfms" + ) + return + else: _libpath = libpath _lib = ctypes.cdll.LoadLibrary(_libpath) diff --git a/run_tests.sh b/run_tests.sh index 2745131..5139a10 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -14,8 +14,6 @@ function run_test() { eval $1 if [ $? -ne 0 ] ; then exit 1 ; fi } -run_test "python -m pytest tests/test_build.py" - test="tests/test_fms.py" create_input $test run_test "python -m pytest -m parallel $test" diff --git a/setup.py b/setup.py index bf00c81..865fba7 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,14 @@ -import subprocess -from typing import List - from setuptools import find_namespace_packages, setup -from setuptools.command.build import build - - -class CustomBuild(build): - def run(self): - with open("install.log", "w") as f: - subprocess.run(["./compile_c_libs.sh"], stdout=f) - build.run(self) -test_requirements = ["pytest", "pytest-subtests", "coverage"] +test_requirements = [ + "pytest", + "pytest-subtests", + "coverage", + "xarray", + "netCDF4", + "h5netcdf", +] develop_requirements = test_requirements + ["pre-commit"] extras_requires = { @@ -20,15 +16,7 @@ def run(self): "develop": develop_requirements, } -requirements: List[str] = [ - "dacite", - "h5netcdf", - "numpy", - "pyyaml", - "mpi4py", - "xarray", - "netcdf4", -] +requirements = ["dacite", "numpy", "pyyaml", "mpi4py"] setup( author="NOAA/GFDL", @@ -45,9 +33,8 @@ def run(self): name="pyfms", license="", packages=find_namespace_packages(include=["pyfms", "pyfms.*"]), - cmdclass={"build": CustomBuild}, include_package_data=True, - url="https://github.com/fmalatino/pyFMS.git", - version="2024.02.0", + url="https://github.com/NOAA-GFDL/pyFMS.git", + version="2024.12.0", zip_safe=False, ) diff --git a/tests/test_build.py b/tests/test_build.py deleted file mode 100644 index f2100f7..0000000 --- a/tests/test_build.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - - -def test_shared_object_exists(): - assert os.path.exists( - os.path.dirname(__file__) + "/../pyfms/cLIBFMS/lib/libcFMS.so" - ) diff --git a/tests/test_init.py b/tests/test_init.py index 17a44a0..c8fec02 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,25 +1,15 @@ -import os -import sys - import pytest import pyfms -curr_dir = os.path.dirname(os.path.abspath(__file__)) -par_dir = os.path.dirname(curr_dir) +def test_library_loaded(): + """ + Test to ensure library loaded automatically + """ -def test_write_module(): - myfile = open("module1.py", "w") - myfile.write( - """ -import pyfms -class Module1Class(): - module1_lib_id = id(pyfms.cfms.lib()) - """ - ) - myfile.close() + assert pyfms.cfms._lib is not None def test_share_same_library(): @@ -32,20 +22,6 @@ def test_share_same_library(): assert id(pyfms.cfms._lib) == id(pyfms.mpp_domains._lib) -def test_load_library_same_object(): - sys.path.append(par_dir) - - """ - Test to ensure the ctypes CDLL Library object - is instantiated only once - """ - - import module1 - - myclass = module1.Module1Class() - assert id(pyfms.cfms.lib()) == myclass.module1_lib_id - - @pytest.mark.xfail def test_library_load_fail(): @@ -55,11 +31,3 @@ def test_library_load_fail(): """ pyfms.cfms.changelib(libpath="do_not_exist") - - -def test_remove_module(): - os.remove("module1.py") - - -if __name__ == "__main__": - test_write_module()