Skip to content

Commit 69fd6a4

Browse files
authored
Merge pull request #242 from DWesl/cmake-build
BLD: Add CMake build using scikit-build-core
2 parents 04159d6 + 97643e7 commit 69fd6a4

30 files changed

+253
-66
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
fail-fast: false
2222
matrix:
2323
os: [ "ubuntu-latest", "macos-latest", "macos-14" ]
24-
python-version: [ "3.9", "3.10", "3.11" ]
24+
python-version: [ "3.9", "3.10", "3.11", "3.12" ]
2525

2626
steps:
2727
- name: Cancel previous runs
@@ -49,10 +49,23 @@ jobs:
4949
environment-file: build_envs/environment.yml
5050
- name: Build WRF-Python
5151
run: |
52-
cd build_scripts
53-
./gnu_omp.sh
54-
cd ..
52+
python -m pip install build
53+
python -m build .
54+
python -m pip install dist/wrf*.whl
5555
- name: Run tests
5656
run: |
5757
cd test/ci_tests
5858
python utests.py
59+
- name: Check import
60+
if: failure()
61+
run: |
62+
python -m pip show wrf-python
63+
python -m pip show --files wrf-python
64+
prefix="$(python -m pip show --files wrf-python | grep Location: | cut -f2 -d" ")"
65+
echo "Site-packages directory is ${prefix}"
66+
cd "${prefix}"
67+
installed_files="$(python -m pip show --files wrf-python | grep -v -E -e '^[-a-zA-Z]+:')"
68+
ls -l ${installed_files}
69+
file ${installed_files}
70+
python -vvv -dd -c "import wrf"
71+
ldd $(echo ${installed_files} | grep -F -v -e ".py" -e ".dist-info")

CMakeLists.txt

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
### setup project ###
2+
# https://numpy.org/doc/stable/f2py/buildtools/skbuild.html
3+
cmake_minimum_required(VERSION 3.18)
4+
5+
project(${SKBUILD_PROJECT_NAME}
6+
VERSION ${SKBUILD_PROJECT_VERSION}
7+
DESCRIPTION "Utilities for reading WRF output"
8+
LANGUAGES C Fortran
9+
)
10+
11+
# Safety net
12+
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
13+
message(
14+
FATAL_ERROR
15+
"In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n"
16+
)
17+
endif()
18+
19+
find_package(Python COMPONENTS Interpreter Development.Module NumPy REQUIRED)
20+
21+
# Ensure scikit-build modules
22+
if (NOT SKBUILD)
23+
# Kanged --> https://github.com/Kitware/torch_liberator/blob/master/CMakeLists.txt
24+
# If skbuild is not the driver; include its utilities in CMAKE_MODULE_PATH
25+
execute_process(
26+
COMMAND "${Python_EXECUTABLE}"
27+
-c "import os, skbuild; print(os.path.dirname(skbuild.__file__))"
28+
OUTPUT_VARIABLE SKBLD_DIR
29+
OUTPUT_STRIP_TRAILING_WHITESPACE
30+
)
31+
list(APPEND CMAKE_MODULE_PATH "${SKBLD_DIR}/resources/cmake")
32+
message(STATUS "Looking in ${SKBLD_DIR}/resources/cmake for CMake modules")
33+
endif()
34+
35+
# Grab the variables from a local Python installation
36+
# NumPy headers
37+
# F2PY headers
38+
execute_process(
39+
COMMAND "${Python_EXECUTABLE}"
40+
-c "import numpy.f2py; print(numpy.f2py.get_include())"
41+
OUTPUT_VARIABLE F2PY_INCLUDE_DIR
42+
OUTPUT_STRIP_TRAILING_WHITESPACE
43+
)
44+
45+
find_package(OpenMP COMPONENTS Fortran)
46+
set_source_files_properties(fortran/ompgen.F90 PROPERTIES Fortran_PREPROCESS ON)
47+
# TODO: Figure out the conditionals for running the C Preprocessor on Fortran files
48+
# I think the main thing to be changed is -E -cpp
49+
# Intel is -fpp -save-temps or /fpp on Windows
50+
# or call fpp instead of the fortran compiler to get it to stop after preprocessing
51+
if (${OpenMP_Fortran_FOUND})
52+
# This would probably be cleaner if I shoved it in the subdirectory
53+
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/fortran")
54+
add_executable(sizes "${CMAKE_SOURCE_DIR}/fortran/build_help/omp_sizes.f90")
55+
target_link_libraries(sizes PUBLIC OpenMP::OpenMP_Fortran)
56+
add_custom_command(
57+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/fortran/ompgen.F90"
58+
DEPENDS "${CMAKE_SOURCE_DIR}/fortran/ompgen.F90.template"
59+
${CMAKE_SOURCE_DIR}/fortran/build_help/sub_sizes.py
60+
sizes
61+
COMMAND
62+
${Python_EXECUTABLE} ${CMAKE_SOURCE_DIR}/fortran/build_help/sub_sizes.py
63+
${CMAKE_SOURCE_DIR}/fortran/ompgen.F90.template
64+
${CMAKE_CURRENT_BINARY_DIR}/fortran/ompgen.F90
65+
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
66+
)
67+
add_custom_command(
68+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/fortran/omp.f90"
69+
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/fortran/ompgen.F90"
70+
COMMAND ${CMAKE_Fortran_COMPILER} -E "${CMAKE_CURRENT_BINARY_DIR}/fortran/ompgen.F90"
71+
-o "${CMAKE_CURRENT_BINARY_DIR}/fortran/omp.f90" ${OpenMP_Fortran_FLAGS} -cpp
72+
)
73+
else()
74+
add_custom_command(
75+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/fortran/omp.f90"
76+
DEPENDS "${CMAKE_SOURCE_DIR}/fortran/ompgen.F90"
77+
COMMAND ${CMAKE_Fortran_COMPILER} -E fortran/ompgen.F90 -o fortran/omp.f90 -cpp
78+
)
79+
endif()
80+
81+
# Prepping the module
82+
set(f2py_module_name "_wrffortran")
83+
set(fortran_src_files
84+
"${CMAKE_SOURCE_DIR}/fortran/wrf_constants.f90"
85+
"${CMAKE_SOURCE_DIR}/fortran/wrf_testfunc.f90"
86+
"${CMAKE_SOURCE_DIR}/fortran/wrf_user.f90"
87+
"${CMAKE_SOURCE_DIR}/fortran/rip_cape.f90"
88+
"${CMAKE_SOURCE_DIR}/fortran/wrf_cloud_fracf.f90"
89+
"${CMAKE_SOURCE_DIR}/fortran/wrf_fctt.f90"
90+
"${CMAKE_SOURCE_DIR}/fortran/wrf_user_dbz.f90"
91+
"${CMAKE_SOURCE_DIR}/fortran/wrf_relhl.f90"
92+
"${CMAKE_SOURCE_DIR}/fortran/calc_uh.f90"
93+
"${CMAKE_SOURCE_DIR}/fortran/wrf_user_latlon_routines.f90"
94+
"${CMAKE_SOURCE_DIR}/fortran/wrf_pvo.f90"
95+
"${CMAKE_SOURCE_DIR}/fortran/eqthecalc.f90"
96+
"${CMAKE_SOURCE_DIR}/fortran/wrf_rip_phys_routines.f90"
97+
"${CMAKE_SOURCE_DIR}/fortran/wrf_pw.f90"
98+
"${CMAKE_SOURCE_DIR}/fortran/wrf_vinterp.f90"
99+
"${CMAKE_SOURCE_DIR}/fortran/wrf_wind.f90"
100+
"${CMAKE_CURRENT_BINARY_DIR}/fortran/omp.f90")
101+
set(python_src_files
102+
"${CMAKE_SOURCE_DIR}/src/wrf/__init__.py"
103+
"${CMAKE_SOURCE_DIR}/src/wrf/api.py"
104+
"${CMAKE_SOURCE_DIR}/src/wrf/cache.py"
105+
"${CMAKE_SOURCE_DIR}/src/wrf/computation.py"
106+
"${CMAKE_SOURCE_DIR}/src/wrf/config.py"
107+
"${CMAKE_SOURCE_DIR}/src/wrf/constants.py"
108+
"${CMAKE_SOURCE_DIR}/src/wrf/contrib.py"
109+
"${CMAKE_SOURCE_DIR}/src/wrf/coordpair.py"
110+
"${CMAKE_SOURCE_DIR}/src/wrf/decorators.py"
111+
"${CMAKE_SOURCE_DIR}/src/wrf/destag.py"
112+
"${CMAKE_SOURCE_DIR}/src/wrf/extension.py"
113+
"${CMAKE_SOURCE_DIR}/src/wrf/g_cape.py"
114+
"${CMAKE_SOURCE_DIR}/src/wrf/g_cloudfrac.py"
115+
"${CMAKE_SOURCE_DIR}/src/wrf/g_ctt.py"
116+
"${CMAKE_SOURCE_DIR}/src/wrf/g_dbz.py"
117+
"${CMAKE_SOURCE_DIR}/src/wrf/g_dewpoint.py"
118+
"${CMAKE_SOURCE_DIR}/src/wrf/g_geoht.py"
119+
"${CMAKE_SOURCE_DIR}/src/wrf/g_helicity.py"
120+
"${CMAKE_SOURCE_DIR}/src/wrf/g_latlon.py"
121+
"${CMAKE_SOURCE_DIR}/src/wrf/g_omega.py"
122+
"${CMAKE_SOURCE_DIR}/src/wrf/g_precip.py"
123+
"${CMAKE_SOURCE_DIR}/src/wrf/g_pressure.py"
124+
"${CMAKE_SOURCE_DIR}/src/wrf/g_pw.py"
125+
"${CMAKE_SOURCE_DIR}/src/wrf/g_rh.py"
126+
"${CMAKE_SOURCE_DIR}/src/wrf/g_slp.py"
127+
"${CMAKE_SOURCE_DIR}/src/wrf/g_temp.py"
128+
"${CMAKE_SOURCE_DIR}/src/wrf/g_terrain.py"
129+
"${CMAKE_SOURCE_DIR}/src/wrf/g_times.py"
130+
"${CMAKE_SOURCE_DIR}/src/wrf/g_uvmet.py"
131+
"${CMAKE_SOURCE_DIR}/src/wrf/g_vorticity.py"
132+
"${CMAKE_SOURCE_DIR}/src/wrf/g_wind.py"
133+
"${CMAKE_SOURCE_DIR}/src/wrf/geobnds.py"
134+
"${CMAKE_SOURCE_DIR}/src/wrf/interp.py"
135+
"${CMAKE_SOURCE_DIR}/src/wrf/interputils.py"
136+
"${CMAKE_SOURCE_DIR}/src/wrf/latlonutils.py"
137+
"${CMAKE_SOURCE_DIR}/src/wrf/metadecorators.py"
138+
"${CMAKE_SOURCE_DIR}/src/wrf/projection.py"
139+
"${CMAKE_SOURCE_DIR}/src/wrf/projutils.py"
140+
"${CMAKE_SOURCE_DIR}/src/wrf/py3compat.py"
141+
"${CMAKE_SOURCE_DIR}/src/wrf/routines.py"
142+
"${CMAKE_SOURCE_DIR}/src/wrf/specialdec.py"
143+
"${CMAKE_SOURCE_DIR}/src/wrf/units.py"
144+
"${CMAKE_SOURCE_DIR}/src/wrf/util.py"
145+
"${CMAKE_SOURCE_DIR}/src/wrf/version.py"
146+
)
147+
set(f2py_module_c "${f2py_module_name}module.c")
148+
149+
# Target for enforcing dependencies
150+
add_custom_target(genpyf
151+
DEPENDS "${fortran_src_files}"
152+
)
153+
add_custom_command(
154+
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}"
155+
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_name}-f2pywrappers.f"
156+
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_name}-f2pywrappers2.f90"
157+
COMMAND ${Python_EXECUTABLE} -m "numpy.f2py"
158+
-m "${f2py_module_name}"
159+
--lower # Important
160+
${fortran_src_files}
161+
DEPENDS "${fortran_src_files}" # Fortran source
162+
)
163+
164+
Python_add_library(${f2py_module_name} MODULE
165+
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_c}"
166+
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_name}-f2pywrappers.f"
167+
"${CMAKE_CURRENT_BINARY_DIR}/${f2py_module_name}-f2pywrappers2.f90"
168+
"${F2PY_INCLUDE_DIR}/fortranobject.c"
169+
"${fortran_src_files}")
170+
171+
target_include_directories(${f2py_module_name} PUBLIC
172+
${F2PY_INCLUDE_DIR}
173+
${Python_NumPy_INCLUDE_DIRS}
174+
${Python_INCLUDE_DIRS})
175+
set_target_properties(${f2py_module_name} PROPERTIES SUFFIX ".${Python_SOABI}${CMAKE_SHARED_MODULE_SUFFIX}")
176+
set_target_properties(${f2py_module_name} PROPERTIES PREFIX "")
177+
178+
# https://scikit-build-core.readthedocs.io/en/latest/getting_started.html
179+
target_link_libraries(${f2py_module_name} PRIVATE Python::NumPy)
180+
if (${OpenMP_Fortran_FOUND})
181+
target_link_libraries(${f2py_module_name} PUBLIC OpenMP::OpenMP_Fortran)
182+
endif()
183+
184+
# Linker fixes
185+
if (UNIX)
186+
if (APPLE)
187+
set_target_properties(${f2py_module_name} PROPERTIES
188+
LINK_FLAGS '-Wl,-dylib,-undefined,dynamic_lookup')
189+
else()
190+
set_target_properties(${f2py_module_name} PROPERTIES
191+
LINK_FLAGS '-Wl,--allow-shlib-undefined')
192+
endif()
193+
endif()
194+
195+
add_dependencies(${f2py_module_name} genpyf)
196+
197+
if (NOT SKBUILD)
198+
string(REGEX REPLACE "^/(usr/(local/)?)?" "" Python_SITEARCH_INSTALL ${Python_SITEARCH})
199+
string(REGEX REPLACE "^/(usr/(local/)?)?" "" Python_SITELIB_INSTALL ${Python_SITELIB})
200+
# string(SUBSTRING ${Python_SITEARCH} 1 -1 Python_SITEARCH_INSTALL)
201+
# string(SUBSTRING ${Python_SITELIB} 1 -1 Python_SITELIB_INSTALL)
202+
203+
install(TARGETS ${f2py_module_name} DESTINATION "${Python_SITEARCH_INSTALL}/wrf/")
204+
install(FILES ${python_src_files} DESTINATION "${Python_SITELIB_INSTALL}/wrf/")
205+
install(FILES src/wrf/data/psadilookup.dat DESTINATION "${Python_SITELIB_INSTALL}/wrf")
206+
else()
207+
# https://scikit-build-core.readthedocs.io/en/latest/cmakelists.html#install-directories
208+
install(TARGETS ${f2py_module_name} DESTINATION "${SKBUILD_PLATLIB_DIR}/wrf/")
209+
endif()

build_envs/environment.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: wrf_python_build
33
channels:
44
- conda-forge
55
dependencies:
6-
- python>=3.9, <3.12
6+
- python>=3.9, <3.13
77
- compilers
88
- basemap
99
- cartopy
@@ -17,4 +17,5 @@ dependencies:
1717
- sphinx_rtd_theme
1818
- wrapt
1919
- xarray
20-
20+
- python-build
21+
- pip

fortran/build_help/sub_sizes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def main():
4141

4242
ompgen_temp_path = os.path.join("..", "ompgen.F90.template")
4343
ompgen_out_path = os.path.join("..", "ompgen.F90")
44+
if len(sys.argv) == 3:
45+
ompgen_temp_path = sys.argv[1]
46+
ompgen_out_path = sys.argv[2]
4447

4548
with open(ompgen_temp_path, "r") as ompgen_in:
4649
ompgen_template = Template(ompgen_in.read())

pyproject.toml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[build-system]
2-
requires = ["setuptools>=61, <70", "numpy"]
3-
build-backend = "setuptools.build_meta"
2+
requires = ["scikit-build-core", "numpy"]
3+
build-backend = "scikit_build_core.build"
44

55
[project]
66
name = "wrf-python"
@@ -12,7 +12,7 @@ maintainers = [
1212
]
1313
description = "Diagnostic and interpolation routines for WRF-ARW data."
1414
readme = "README.md"
15-
requires-python = ">=3.7, <3.12"
15+
requires-python = ">=3.9, <3.13"
1616
keywords = [
1717
"python", "wrf-python", "wrf", "forecast", "model",
1818
"weather research and forecasting", "interpolation",
@@ -29,6 +29,7 @@ classifiers = [
2929
"Programming Language :: Python :: 3.9",
3030
"Programming Language :: Python :: 3.10",
3131
"Programming Language :: Python :: 3.11",
32+
"Programming Language :: Python :: 3.12",
3233
"Topic :: Scientific/Engineering :: Atmospheric Science",
3334
"Topic :: Software Development",
3435
"Operating System :: POSIX",
@@ -41,7 +42,7 @@ dynamic = [ "version" ]
4142
dependencies = [
4243
"basemap",
4344
"numpy >=1.11, !=1.24.3",
44-
"setuptools",
45+
"setuptools>=61",
4546
"wrapt",
4647
"xarray"
4748
]
@@ -59,3 +60,19 @@ where = ["src"]
5960

6061
[tool.setuptools.dynamic]
6162
version = { attr = "wrf.version.__version__" }
63+
64+
[tool.scikit-build]
65+
cmake.verbose = true
66+
logging.level = "INFO"
67+
minimum-version = "0.8"
68+
cmake.version = ">=3.18"
69+
wheel.packages = ["src/wrf"]
70+
71+
# To avoid stripping installed libraries
72+
# Packages often want to do their own stripping
73+
# SKBUILD_INSTALL_STRIP: "false"
74+
# install.strip = false
75+
76+
[tool.scikit-build.metadata.version]
77+
provider = "scikit_build_core.metadata.regex"
78+
input = "src/wrf/version.py"

setup.py

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/wrf/__init__.py

100755100644
File mode changed.

src/wrf/constants.py

100755100644
File mode changed.

src/wrf/destag.py

100755100644
File mode changed.

src/wrf/extension.py

100755100644
File mode changed.

0 commit comments

Comments
 (0)