Skip to content

Commit 8b5045c

Browse files
committed
docs: Render C/F90/F08 bindings in man pages
Use the pympistandard Python module and associated MPI-4.1 MPI Standard JSON definition file to generate bindings -- regular and embiggened -- in the MPI man pages. In the generation code, had to handle a variety of cases: * When there is no C binding * When there is no mpif.h / use mpi binding * When there is no use mpi_f08 binding * When there are embiggened C and/or F08 bindings * When a single man page includes documentation for multiple MPI APIs * When we do not have man pages for MPI APIs described in the official MPI Forum API JSON Also, this commit adds a new submodule: the pympistandard Python module from the MPI Forum repository. Some other cleanups were also necessary as part of this commit: * Add some missing man pages * Fix some random RST formatting errors in existing man pages Signed-off-by: Jeff Squyres <jeff@squyres.com>
1 parent 32bfff1 commit 8b5045c

File tree

447 files changed

+60393
-15817
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

447 files changed

+60393
-15817
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ docs/_build
516516
docs/_static
517517
docs/_static/css/custom.css
518518
docs/_templates
519+
docs/man-openmpi/man3/bindings/*.rst
519520

520521
# Common Python virtual environment and cache directory names
521522
venv

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@
99
[submodule "oac"]
1010
path = config/oac
1111
url = ../../open-mpi/oac
12+
[submodule "3rd-party/pympistandard"]
13+
path = 3rd-party/pympistandard
14+
url = ../../mpi-forum/pympistandard

.readthedocs-pre-create-environment.sh

+3
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@ PRRTE_RST_TARGET_DIR=docs/prrte-rst-content
2626

2727
cp -rp $SCHIZO_SRC_DIR $SCHIZO_TARGET_DIR
2828
cp -rp $PRRTE_RST_SRC_DIR $PRRTE_RST_TARGET_DIR
29+
30+
cd docs
31+
python3 ./generate-mpi-man3-bindings.py --srcdir . --builddir .

3rd-party/pympistandard

Submodule pympistandard added at 7bc6bb0

docs/Makefile.am

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#
22
# Copyright (c) 2022 Cisco Systems, Inc. All rights reserved.
3-
# Copyright (c) 2023-2024 Jeffrey M. Squyres. All rights reserved.
3+
# Copyright (c) 2023-2025 Jeffrey M. Squyres. All rights reserved.
44
#
55
# $COPYRIGHT$
66
#
@@ -54,12 +54,16 @@ RST_SOURCE_FILES = \
5454
$(srcdir)/license/*.rst \
5555
$(srcdir)/man-openmpi/*.rst \
5656
$(srcdir)/man-openmpi/man*/*.rst \
57+
$(srcdir)/man-openmpi/man*/bindings/*.rst \
5758
$(srcdir)/man-openshmem/*.rst \
5859
$(srcdir)/man-openshmem/man*/*.rst
5960

6061
EXTRA_DIST = \
6162
requirements.txt \
6263
no-prrte-content.rst.txt \
64+
generate-mpi-man3-bindings.py \
65+
mpi-standard-apis.json \
66+
man-openmpi/man3/bindings \
6367
html \
6468
man \
6569
$(SPHINX_CONFIG) \
@@ -843,6 +847,10 @@ OMPI_MAN3_RST = $(OMPI_MAN3:%.3=man-openmpi/man3/%.3.rst)
843847
OMPI_MAN3_BUILT = $(OMPI_MAN3:%.3=$(MAN_OUTDIR)/%.3)
844848
OMPI_MAN3_INSTALL_FROM = $(OMPI_MAN3:%.3=$(MAN_INSTALL_FROM)/%.3)
845849

850+
# Use this one file as a sentinel for building all the man page
851+
# bindings files
852+
SENTINEL_OMPI_MAN3_BINDING = man-openmpi/man3/bindings/mpi_init.rst
853+
846854
OMPI_MAN7_RST = $(OMPI_MAN7:%.7=man-openmpi/man7/%.7.rst)
847855
OMPI_MAN7_BUILT = $(OMPI_MAN7:%.7=$(MAN_OUTDIR)/%.7)
848856
OMPI_MAN7_INSTALL_FROM = $(OMPI_MAN7:%.7=$(MAN_INSTALL_FROM)/%.7)
@@ -921,7 +929,7 @@ man: $(ALL_MAN_BUILT)
921929
# Remove the copies of the built HTML and man pages to get back to a
922930
# clean git clone.
923931
maintainer-clean-local:
924-
rm -rf html man
932+
rm -rf html man man-openmpi/man3/bindings
925933

926934
# If we're doing a VPATH build, we may have "html" and "man"
927935
# directories in the build tree (e.g., if we did "make dist"). Remove
@@ -998,10 +1006,16 @@ $(builddir)/schizo-ompi-rst-content/schizo-ompi-cli.rstxt: $(srcdir)/no-prrte-co
9981006
cp -pf $(srcdir)/no-prrte-content.rst.txt "$@"
9991007
endif
10001008

1009+
$(SENTINEL_OMPI_MAN3_BINDING): generate-mpi-man3-bindings.py
1010+
$(SENTINEL_OMPI_MAN3_BINDING): mpi-standard-apis.json
1011+
$(OMPI_V_GEN) $(PYTHON3) $(srcdir)/generate-mpi-man3-bindings.py \
1012+
--srcdir $(srcdir) --builddir $(builddir)
1013+
10011014
$(ALL_MAN_BUILT): $(builddir)/prrte-rst-content
10021015
$(ALL_MAN_BUILT): $(builddir)/schizo-ompi-rst-content/schizo-ompi-cli.rstxt
10031016
$(ALL_MAN_BUILT): $(RST_SOURCE_FILES) $(IMAGE_SOURCE_FILES)
10041017
$(ALL_MAN_BUILT): $(TEXT_SOURCE_FILES) $(SPHINX_CONFIG)
1018+
$(ALL_MAN_BUILT): $(SENTINEL_OMPI_MAN3_BINDING)
10051019

10061020
# Render the RST source into both 1) full HTML docs and 2) nroff man
10071021
# pages.
@@ -1039,6 +1053,7 @@ $(ALL_MAN_BUILT): $(TEXT_SOURCE_FILES) $(SPHINX_CONFIG)
10391053
# changed. We're going to overwrite any local changes in the build
10401054
# tree, but you shouldn't be editing the build tree, anyway. So --
10411055
# good enough.
1056+
10421057
$(ALL_MAN_BUILT):
10431058
$(OMPI_V_SPHINX_COPYRST) if test "$(srcdir)" != "$(builddir)"; then \
10441059
len=`echo "$(srcdir)/" | wc -c`; \

docs/generate-mpi-man3-bindings.py

+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved.
4+
#
5+
# $COPYRIGHT$
6+
#
7+
# Additional copyrights may follow
8+
#
9+
# $HEADER$
10+
#
11+
12+
# Script to create RST files containing the C, F90, and F80 bindings
13+
# that will be included in each of the MPI API man pages. We generate
14+
# the bindings using the official MPI Forum python library to read the
15+
# API JSON that was generated when building the MPI Forum standard
16+
# LaTeX document and then emit the bindings.
17+
#
18+
# Using this method, we can emit both "regular" and "embiggened"
19+
# versions of each API (if an "embiggened" version exists).
20+
21+
import re
22+
import os
23+
import sys
24+
import textwrap
25+
import argparse
26+
27+
from pathlib import Path
28+
from collections import defaultdict
29+
from pprint import pprint
30+
31+
#----------------
32+
33+
def setup_cli():
34+
parser = argparse.ArgumentParser(description="Generate C/F90/F08 bindings for RST")
35+
36+
parser.add_argument('--srcdir',
37+
required=True,
38+
help='Build source dir')
39+
parser.add_argument('--builddir',
40+
required=True,
41+
help='Build build dir')
42+
43+
args = parser.parse_args()
44+
return args
45+
46+
#----------------
47+
48+
def generate(func_name_arg, output_dir, directives):
49+
global std
50+
51+
# Sanity check
52+
func_name = func_name_arg.lower()
53+
if not func_name in std.PROCEDURES:
54+
print(f"ERROR: Don't know {func_name}")
55+
return
56+
57+
# Do not generate this file if the binding for this function is
58+
# going to be included in another file
59+
for key, data in directives.items():
60+
if key == func_name:
61+
continue
62+
63+
# If this function is in the list of MPI bindings included in
64+
# another page, then we don't need to generate this page.
65+
if func_name in data:
66+
return
67+
68+
# Also do not generate if we have no man page for this MPI API
69+
# function.
70+
if func_name not in directives:
71+
return
72+
73+
#----------------
74+
75+
# If we get here, we're going to generate the bindings for one or
76+
# more API functions.
77+
func_names_data = []
78+
for name in directives[func_name_arg]:
79+
func_names_data.append(std.PROCEDURES[name])
80+
data = std.PROCEDURES[func_name_arg]
81+
if data not in func_names_data:
82+
func_names_data.insert(0, data)
83+
84+
# Make an array of strings to emit into the output RST file.
85+
blank = ''
86+
out = []
87+
88+
out.append('SYNTAX')
89+
out.append('------')
90+
out.append(blank)
91+
92+
have_binding = False
93+
94+
# C bindings
95+
emitted_header = False
96+
for data in func_names_data:
97+
binding = str(data.express.iso_c)
98+
if binding and len(binding) > 0 and binding != 'None':
99+
have_binding = True
100+
if not emitted_header:
101+
out.append('C Syntax')
102+
out.append('^^^^^^^^')
103+
out.append(blank)
104+
out.append('.. code-block:: c')
105+
out.append(blank)
106+
emitted_header = True
107+
108+
# Note that the binding string is technically in LaTeX.
109+
# One thing we'll need to adjust is to convert "\ldots" to
110+
# "..." (e.g., for MPI_Pcontrol).
111+
binding = binding.replace(r'\ldots', '...')
112+
113+
line = textwrap.fill(binding, width=72,
114+
initial_indent=' ',
115+
subsequent_indent = ' ')
116+
out.append(line)
117+
out.append(blank)
118+
119+
if data.has_embiggenment():
120+
binding = str(data.express.embiggen.iso_c)
121+
binding = binding.replace(r'\ldots', '...')
122+
line = textwrap.fill(binding, width=72,
123+
initial_indent=' ',
124+
subsequent_indent = ' ')
125+
out.append(line)
126+
out.append(blank)
127+
128+
# F90 bindings
129+
# Note: the f90 bindings were not embiggened
130+
emitted_header = False
131+
for data in func_names_data:
132+
binding = str(data.express.f90)
133+
if binding and len(binding) > 0 and binding != 'None':
134+
have_binding = True
135+
if not emitted_header:
136+
out.append('Fortran Syntax')
137+
out.append('^^^^^^^^^^^^^^')
138+
out.append(blank)
139+
out.append('.. code-block:: fortran')
140+
out.append(blank)
141+
out.append(' USE MPI')
142+
out.append(" ! or the older form: INCLUDE 'mpif.h'")
143+
emitted_header = True
144+
145+
lines = binding.split('\n')
146+
for line in lines:
147+
out.append(f' {line}')
148+
out.append(blank)
149+
150+
# F08 bindings
151+
emitted_header = False
152+
for data in func_names_data:
153+
binding = str(data.express.f08)
154+
if binding and len(binding) > 0 and binding != 'None':
155+
have_binding = True
156+
if not emitted_header:
157+
out.append('Fortran 2008 Syntax')
158+
out.append('^^^^^^^^^^^^^^^^^^^')
159+
out.append(blank)
160+
out.append('.. code-block:: fortran')
161+
out.append(blank)
162+
out.append(' USE mpi_f08')
163+
emitted_header = True
164+
165+
lines = binding.split('\n')
166+
for line in lines:
167+
out.append(f' {line}')
168+
out.append(blank)
169+
170+
if data.has_embiggenment():
171+
binding = str(data.express.embiggen.f08)
172+
lines = binding.split('\n')
173+
for line in lines:
174+
out.append(f' {line}')
175+
out.append(blank)
176+
177+
# Sanity check
178+
if not have_binding:
179+
print(f"NO BINDINGS for {func_name_arg}")
180+
return
181+
182+
# Write the output file -- but only if it has changed
183+
old_content = None
184+
new_content = '\n'.join(out)
185+
output_file = os.path.join(output_dir, f'{func_name}.rst')
186+
if os.path.exists(output_file):
187+
with open(output_file) as fp:
188+
old_content = fp.read()
189+
190+
if old_content != new_content:
191+
with open(output_file, 'w') as fp:
192+
fp.write('\n'.join(out))
193+
194+
# This script is run during "make". So emit a make-style
195+
# message.
196+
print(f' GENERATE RST {func_name_arg} binding')
197+
198+
#----------------
199+
200+
# Some existing .3.rst man pages actually contain the docs for
201+
# multiple MPI_* API functions. Read the .3.rst files and look for
202+
# directives that mean "this file contains documentation for all these
203+
# MPI API functions".
204+
def read_rst_man_pages(src_dir):
205+
directives = {}
206+
prog = re.compile(r'^MPI_.*\.3\.rst$')
207+
208+
man3_dir = Path(os.path.join(src_dir, 'man-openmpi', 'man3')).resolve()
209+
for file in os.listdir(man3_dir):
210+
# Only want MPI man pages
211+
if not prog.match(file):
212+
continue
213+
214+
with open(os.path.join(man3_dir, file)) as fp:
215+
lines = fp.readlines()
216+
217+
file_api_name = file.replace('.3.rst', '').lower()
218+
219+
# Make an initial/empty list for every MPI API man page that
220+
# we find
221+
directives[file_api_name] = list()
222+
223+
prefix = '.. mpi-bindings:'
224+
for line in lines:
225+
line = line.strip()
226+
if not line.startswith(prefix):
227+
continue
228+
229+
bindings = line[len(prefix):].split(',')
230+
for binding in bindings:
231+
binding = binding.strip()
232+
directives[file_api_name].append(binding.lower())
233+
234+
return directives
235+
236+
#----------------
237+
238+
def main():
239+
args = setup_cli()
240+
241+
src_dir = os.path.abspath(args.srcdir)
242+
build_dir = os.path.abspath(args.builddir)
243+
244+
# Read existing srcdir/man-openmpi/man3/MPI_*.3.rst files and look
245+
# for directives to guide this generation process.
246+
directives = read_rst_man_pages(src_dir)
247+
248+
# A bit of a hack to load the pympistandard module, which is in
249+
# the Open MPI '3rd-party" tree.
250+
pympistandard_dir = Path(os.path.join(src_dir, '..', '3rd-party',
251+
'pympistandard', 'src')).resolve()
252+
253+
sys.path.insert(0, str(pympistandard_dir))
254+
global std
255+
import pympistandard as std
256+
257+
# This is the JSON file with all the MPI standard APIs. This is
258+
# not currently officially distributed by the MPI Forum, so it was
259+
# obtained by checking out the relevant branch from
260+
# https://github.com/mpi-forum/mpi-standard/ and doing a build.
261+
# This will create a file named apis.json. Copy that here to this
262+
# tree.
263+
mpi_standard_json = os.path.abspath(os.path.join(src_dir,
264+
'mpi-standard-apis.json'))
265+
std.use_api_version(1, given_path=mpi_standard_json)
266+
267+
# We need to write all of these into the build tree. See
268+
# docs/Makefile.am for a fuller explaination: all RST files are
269+
# copied to the build tree, and we build there.
270+
output_dir = os.path.join(build_dir, 'man-openmpi', 'man3', 'bindings')
271+
if not os.path.exists(output_dir):
272+
os.makedirs(output_dir)
273+
274+
# Now we finally generate the files. Iterate over all the MPI
275+
# procedures and generate a binding file for each one of them.
276+
for func_name in std.PROCEDURES:
277+
generate(func_name, output_dir, directives)
278+
279+
if __name__ == "__main__":
280+
main()

0 commit comments

Comments
 (0)