diff --git a/.gitignore b/.gitignore index d8e3aed..6a7b3aa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,6 @@ *.nc *.nc4 *.npz -/cmake/ -/@cmake/ -/cmake@/ /env/ /@env/ /env@/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 593e61f..27ec02b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,79 +4,52 @@ # Boiler plate preamble # --------------------- -cmake_minimum_required (VERSION 3.13) +cmake_minimum_required (VERSION 3.24) cmake_policy (SET CMP0053 NEW) cmake_policy (SET CMP0054 NEW) +cmake_policy (SET CMP0132 NEW) project ( GMAOpyobs VERSION 1.1.0 - LANGUAGES Fortran CXX C) # Note - CXX is required for ESMF + LANGUAGES Fortran C +) # Enforce out of source directory builds # -------------------------------------- - if ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") - message(SEND_ERROR "In-source builds are disabled. Please - issue cmake command in separate build directory.") - endif ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") +if ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") + message(SEND_ERROR "In-source builds are disabled. Please + issue cmake command in separate build directory.") +endif ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}") -# Set the default build type to release -# ------------------------------------- -if (NOT CMAKE_BUILD_TYPE) - - message (STATUS "Setting build type to 'Release' as none was specified.") - set (CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) - - # Set the possible values of build type for cmake-gui - # --------------------------------------------------- - set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Release" "Aggressive") - endif () +list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") -# Where to find relevant cmake macros -# ----------------------------------- - if (NOT COMMAND esma) - foreach (dir cmake @cmake cmake@) - if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${dir}) - list (APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/${dir}") - set (ESMA_CMAKE_PATH "${CMAKE_CURRENT_LIST_DIR}/${dir}" - CACHE PATH "Path to ESMA_cmake code") - endif () - endforeach () - - include (esma) +# Recursively build source tree +# ----------------------------- - set (PYOBS_STANDALONE TRUE) +# mepo can now clone subrepos in three styles +set (ESMA_ENV_DIRS + env + @env + env@ +) +foreach (dir IN LISTS ESMA_ENV_DIRS) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${dir}) + add_subdirectory (${dir}) endif () - ecbuild_declare_project() +endforeach () -# Generic DFLAGS -# These should be relocated and/or eliminated. -# -------------------------------------------- - add_definitions(-Dsys${CMAKE_SYSTEM_NAME} -DESMA64) - add_definitions(${MPI_Fortran_COMPILE_FLAGS}) - include_directories(${MPI_Fortran_INCLUDE_PATH}) - -# Recursively build source tree -# ----------------------------- - esma_add_subdirectory(env) - esma_add_subdirectory(src) +add_subdirectory(src) # https://www.scivision.dev/cmake-auto-gitignore-build-dir/ # --- auto-ignore build directory - if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore) - file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*") - endif() +if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore) + file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*") +endif() # Piggyback that file into install # -------------------------------- - install( - FILES ${PROJECT_BINARY_DIR}/.gitignore - DESTINATION ${CMAKE_INSTALL_PREFIX} - ) - -# Adds ability to tar source -# -------------------------- - if (PYOBS_STANDALONE) - include(esma_cpack OPTIONAL) - endif () +install( + FILES ${PROJECT_BINARY_DIR}/.gitignore + DESTINATION ${CMAKE_INSTALL_PREFIX} +) diff --git a/cmake/UseF2Py.cmake b/cmake/UseF2Py.cmake new file mode 100644 index 0000000..fb4bac3 --- /dev/null +++ b/cmake/UseF2Py.cmake @@ -0,0 +1,131 @@ +# Copyright (c) 2024, Henry Schreiner +# Developed under NSF AWARD OAC-2209877 and by the respective contributors. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +if(CMAKE_VERSION VERSION_LESS 3.17) + message(FATAL_ERROR "CMake 3.17+ required") +endif() + +include_guard(GLOBAL) + +if(TARGET Python::NumPy) + set(_Python Python CACHE INTERNAL "" FORCE) +elseif(TARGET Python3::NumPy) + set(_Python Python3 CACHE INTERNAL "" FORCE) +else() + message(FATAL_ERROR "You must find Python or Python3 with the NumPy component before including F2PY!") +endif() + +execute_process( + COMMAND "${${_Python}_EXECUTABLE}" -c + "import numpy.f2py; print(numpy.f2py.get_include())" + OUTPUT_VARIABLE F2PY_inc_output + ERROR_VARIABLE F2PY_inc_error + RESULT_VARIABLE F2PY_inc_result + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + +if(NOT F2PY_inc_result EQUAL 0) + message(FATAL_ERROR "Can't find f2py, got ${F2PY_inc_output} ${F2PY_inc_error}") +endif() + +set(F2PY_INCLUDE_DIR "${F2PY_inc_output}" CACHE STRING "" FORCE) +set(F2PY_OBJECT_FILES "${F2PY_inc_output}/fortranobject.c;${F2PY_inc_output}/fortranobject.h" CACHE STRING "" FORCE) +mark_as_advanced(F2PY_INCLUDE_DIR F2PY_OBJECT_FILES) + +add_library(F2Py::Headers IMPORTED GLOBAL INTERFACE) +target_include_directories(F2Py::Headers INTERFACE "${F2PY_INCLUDE_DIR}") + +function(f2py_object_library NAME TYPE) + add_library(${NAME} ${TYPE} "${F2PY_INCLUDE_DIR}/fortranobject.c") + target_link_libraries(${NAME} PUBLIC ${_Python}::NumPy F2Py::Headers) + if("${TYPE}" STREQUAL "OBJECT") + set_property(TARGET ${NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + endif() +endfunction() + +function(f2py_generate_module NAME) + cmake_parse_arguments( + PARSE_ARGV 1 + F2PY + "NOLOWER;F77;F90" + "OUTPUT_DIR;OUTPUT_VARIABLE" + "F2PY_ARGS" + ) + set(ALL_FILES ${F2PY_UNPARSED_ARGUMENTS}) + + if(NOT ALL_FILES) + message(FATAL_ERROR "One or more input files must be specified") + endif() + + if(NOT F2PY_OUTPUT_DIR) + set(F2PY_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() + + if(NAME MATCHES "\\.pyf$") + set(_file_arg "${NAME}") + get_filename_component(NAME "${NAME}" NAME_WE) + else() + set(_file_arg -m ${NAME}) + endif() + + if(F2PY_F77 AND F2PY_F90) + message(FATAL_ERROR "Can't specify F77 and F90") + elseif(NOT F2PY_F77 AND NOT F2PY_F90) + set(HAS_F90_FILE FALSE) + + foreach(file IN LISTS ALL_FILES) + if("${file}" MATCHES "\\.f90$") + set(HAS_F90_FILE TRUE) + break() + endif() + endforeach() + + if(HAS_F90_FILE) + set(F2PY_F90 ON) + else() + set(F2PY_F77 ON) + endif() + endif() + + if(F2PY_F77) + set(wrapper_files ${NAME}-f2pywrappers.f) + else() + set(wrapper_files ${NAME}-f2pywrappers.f ${NAME}-f2pywrappers2.f90) + endif() + + if(F2PY_NOLOWER) + set(lower "--no-lower") + else() + set(lower "--lower") + endif() + + set(abs_all_files) + foreach(file IN LISTS ALL_FILES) + if(IS_ABSOLUTE "${file}") + list(APPEND abs_all_files "${file}") + else() + list(APPEND abs_all_files "${CMAKE_CURRENT_SOURCE_DIR}/${file}") + endif() + endforeach() + + add_custom_command( + OUTPUT ${NAME}module.c ${wrapper_files} + DEPENDS ${ALL_FILES} + VERBATIM + COMMAND + "${${_Python}_EXECUTABLE}" -m numpy.f2py + "${abs_all_files}" ${_file_arg} ${lower} ${F2PY_F2PY_ARGS} + COMMAND + "${CMAKE_COMMAND}" -E touch ${wrapper_files} + WORKING_DIRECTORY "${F2PY_OUTPUT_DIR}" + COMMENT + "F2PY making ${NAME} wrappers" + ) + + if(F2PY_OUTPUT_VARIABLE) + set(${F2PY_OUTPUT_VARIABLE} ${NAME}module.c ${wrapper_files} PARENT_SCOPE) + endif() +endfunction() diff --git a/components.yaml b/components.yaml index 862d436..0f81f74 100644 --- a/components.yaml +++ b/components.yaml @@ -7,15 +7,3 @@ env: remote: ../ESMA_env.git tag: v4.8.2 develop: main - -cmake: - local: ./cmake@ - remote: ../ESMA_cmake.git - tag: v3.28.0 - develop: develop - -ecbuild: - local: ./cmake@/ecbuild@ - remote: ../ecbuild.git - tag: geos/v1.3.0 - diff --git a/src/f2py/CMakeLists.txt b/src/f2py/CMakeLists.txt index 6d6fb18..4388890 100644 --- a/src/f2py/CMakeLists.txt +++ b/src/f2py/CMakeLists.txt @@ -2,64 +2,70 @@ # Cmake rules for creating python modules with f2py and # -# The OVERRIDE tells esma_set_this() to name the library as -# what we set here rather than the name of the directory -esma_set_this ( OVERRIDE GMAOpyobs ) - # cmake requirements # ------------------ + # NOTE: For CI purposes, f2py is hard to support. Until # a solution can be found, we add a flag to allow # the user to disable f2py in these circumstances +option(USE_F2PY "Use f2py to build python modules" ON) if (USE_F2PY) - find_package(F2PY3 REQUIRED) + # This setting is needed on macOS to ensure + # that the desired Python is found (i.e., if + # a user has installed their own Python, it + # should be favored over the system Python or + # one from Homebrew, etc.) + set (CMAKE_FIND_FRAMEWORK LAST) + + find_package( + Python + COMPONENTS Interpreter Development.Module NumPy + REQUIRED) + + include(UseF2Py) -# Libray -# ------ - esma_add_library (${this} - SRCS VegType_Mod.F90 - VegType_io.c - glint_mod.F90 - sgp4_mod.F90 - TLE_mod.F90 - csUtils.F90 + # Library: GMAOpyobs + # ------------------ + add_library (GMAOpyobs + VegType_Mod.F90 + VegType_io.c + glint_mod.F90 + sgp4_mod.F90 + TLE_mod.F90 + csUtils.F90 ) + install(TARGETS GMAOpyobs DESTINATION lib) + + # Module: binObs + # -------------- + + f2py_object_library(binObs_object OBJECT) + f2py_generate_module(binObs_ binObs_py.F OUTPUT_VARIABLE binObs_files) + python_add_library(binObs_ MODULE "${binObs_files}" WITH_SOABI) + target_link_libraries(binObs_ PRIVATE binObs_object) + target_link_libraries(binObs_ PRIVATE GMAOpyobs) + install(TARGETS binObs_ DESTINATION lib/Python) + + + # Module: IGBP + # ------------ -# Module: binObs -# -------------- - esma_add_f2py3_module(binObs_ - SOURCES binObs_py.F - DESTINATION lib/Python - INCLUDEDIRS ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_BINARY_DIR}/lib - ${include_${this}} - ) - add_dependencies(binObs_ ${this}) + f2py_object_library(IGBP_object OBJECT) + f2py_generate_module(IGBP_ IGBP_py.F90 OUTPUT_VARIABLE IGBP_files) + python_add_library(IGBP_ MODULE "${IGBP_files}" WITH_SOABI) + target_link_libraries(IGBP_ PRIVATE IGBP_object) + target_link_libraries(IGBP_ PRIVATE GMAOpyobs) + install(TARGETS IGBP_ DESTINATION lib/Python/pyobs) - # Module: IGBP - # ------------ - esma_add_f2py3_module(IGBP_ - SOURCES IGBP_py.F90 - DESTINATION lib/Python/pyobs - ONLY getsimpleveg getdetailedveg - LIBRARIES GMAOpyobs - INCLUDEDIRS ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_BINARY_DIR}/lib - ${include_${this}} - ) - add_dependencies(IGBP_ ${this}) + # Module: sgp4 + # ------------ -# Module: sgp4 -# ------------ - esma_add_f2py3_module(sgp4_ - SOURCES sgp4_py.F90 - DESTINATION lib/Python/pyobs - LIBRARIES GMAOpyobs - INCLUDEDIRS ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_BINARY_DIR}/lib - ${include_${this}} - ) - add_dependencies(sgp4_ ${this}) + f2py_object_library(sgp4_object OBJECT) + f2py_generate_module(sgp4_ sgp4_py.F90 OUTPUT_VARIABLE sgp4_files) + python_add_library(sgp4_ MODULE "${sgp4_files}" WITH_SOABI) + target_link_libraries(sgp4_ PRIVATE sgp4_object) + target_link_libraries(sgp4_ PRIVATE GMAOpyobs) + install(TARGETS sgp4_ DESTINATION lib/Python/pyobs) -endif (USE_F2PY) +endif ()