Skip to content

Commit 79d68d7

Browse files
committed
PROOF OF CONCEPT -- comments welcome
Per discussion from https://github.com/open-mpi/ompi/wiki/Meeting-2025-03-14, we would like to make relocating an Open MPI installation easier. Specifically, if we can avoid the need to propagate various *_PREFIX environment variables / command line options down through multiple layers and subsystems (OMPI, PRRTE, PMIx), that would eliminate/simplify a bunch of our code. Text help files are something that Open MPI currently has to find in the install tree filesystem at run-time. This commit is a proposal: 1. Developers still maintain help messages in the various help_*.txt files around the code base. Maintaining descriptive, user-friendly help messages in text files (instead of manually hand-coding long strings in C) has proven to be quite useful. We do not want to lose this capability. 2. During "make" (in opal/util), those help_*.txt files are encoded into an indexed array of C strings in opal/util/show_help_content.c. 3. opal_show_help*() then can look up the appropriate help strings via filename / topic tuples, just like it used to -- these strings now just happen to be in C variables instead of text files. A single change will need to be made wherever a help_*.txt file is used (e.g., in components, but also in various base directories): update Makefile.am to remove the help_*.txt file from dist_*_DATA and it add to EXTRA_DIST. **NOTE:** This Makefile.am change has not been made across the code base yet. Once we agree on the prototype/ideas, the Makefile.am change can be implemented across the code base. This work is intended for main / v6.0.x -- not for v5.0.x. Signed-off-by: Jeff Squyres <[email protected]>
1 parent 9fbd849 commit 79d68d7

File tree

10 files changed

+192
-372
lines changed

10 files changed

+192
-372
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ opal/tools/wrappers/opalCC-wrapper-data.txt
313313
opal/tools/wrappers/opal_wrapper
314314
opal/tools/wrappers/opal.pc
315315

316-
opal/util/show_help_lex.c
316+
opal/util/show_help_content.c
317317
opal/util/keyval/keyval_lex.c
318318

319319
test/monitoring/aggregate_profile.pl

Makefile.ompi-rules

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Copyright (c) 2008-2022 Cisco Systems, Inc. All rights reserved.
33
# Copyright (c) 2008 Sun Microsystems, Inc. All rights reserved.
44
# Copyright (c) 2020 Intel, Inc. All rights reserved.
5-
# Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved.
5+
# Copyright (c) 2023-2025 Jeffrey M. Squyres. All rights reserved.
66
# $COPYRIGHT$
77
#
88
# Additional copyrights may follow
@@ -42,3 +42,5 @@ ompi__v_SPHINX_HTML_0 = @echo " GENERATE HTML docs";
4242
OMPI_V_SPHINX_MAN = $(ompi__v_SPHINX_MAN_$V)
4343
ompi__v_SPHINX_MAN_ = $(ompi__v_SPHINX_MAN_$AM_DEFAULT_VERBOSITY)
4444
ompi__v_SPHINX_MAN_0 = @echo " GENERATE man pages";
45+
46+
OMPI_HELP_GEN = $(OMPI_V_GEN) $(top_srcdir)/config/ini-to-c.py

config/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# Copyright (c) 2016 Research Organization for Information Science
1717
# and Technology (RIST). All rights reserved.
1818
# Copyright (c) 2022 Amazon.com, Inc. or its affiliates. All Rights reserved.
19+
# Copyright (c) 2025 Jeffrey M. Squyres. All Rights reserved.
1920
# $COPYRIGHT$
2021
#
2122
# Additional copyrights may follow

opal/mca/btl/tcp/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# $HEADER$
2121
#
2222

23-
dist_opaldata_DATA = help-mpi-btl-tcp.txt
23+
EXTRA_DIST = help-mpi-btl-tcp.txt
2424

2525
sources = \
2626
btl_tcp.c \

opal/util/Makefile.am

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@
2727
# $HEADER$
2828
#
2929

30+
include $(top_srcdir)/Makefile.ompi-rules
31+
3032
SUBDIRS = \
3133
json \
3234
keyval
3335

34-
dist_opaldata_DATA = \
36+
EXTRA_DIST = \
3537
help-opal-util.txt \
36-
json/help-json.txt
37-
38-
AM_LFLAGS = -Popal_show_help_yy
39-
LEX_OUTPUT_ROOT = lex.opal_show_help_yy
38+
json/help-json.txt \
39+
convert-help-files-to-c-code.py
4040

4141
noinst_LTLIBRARIES = libopalutil.la libopalutil_core.la
4242

@@ -76,7 +76,6 @@ headers = \
7676
proc.h \
7777
qsort.h \
7878
show_help.h \
79-
show_help_lex.h \
8079
stacktrace.h \
8180
string_copy.h \
8281
sys_limits.h \
@@ -121,12 +120,17 @@ libopalutil_core_la_SOURCES = \
121120
qsort.c \
122121
sha256.c \
123122
show_help.c \
124-
show_help_lex.l \
123+
show_help_content.c \
125124
stacktrace.c \
126125
string_copy.c \
127126
sys_limits.c \
128127
uri.c
129128

129+
show_help_content.c: convert-help-files-to-c-code.py
130+
$(OMPI_V_GEN) $(abs_srcdir)/convert-help-files-to-c-code.py \
131+
--root $(abs_top_srcdir) \
132+
--out show_help_content.c
133+
130134
if OPAL_COMPILE_TIMING
131135
libopalutil_core_la_SOURCES += timings.c
132136
endif
@@ -138,18 +142,12 @@ libopalutil_core_la_DEPENDENCIES = \
138142
json/libopalutil_json.la \
139143
keyval/libopalutilkeyval.la
140144

141-
# flex prior to version 2.6.6 uses the POSIX extension fileno()
142-
# without providing the proper feature test macro, so
143-
# we add the _POSIX_C_SOURCE macro here.
144-
# See https://github.com/westes/flex/issues/263
145-
libopalutil_core_la_CFLAGS = -D_POSIX_C_SOURCE=200809L
146-
147145
# Conditionally install the header files
148146

149147
if WANT_INSTALL_HEADERS
150148
opaldir = $(opalincludedir)/$(subdir)
151149
opal_HEADERS = $(headers)
152150
endif
153151

154-
maintainer-clean-local:
155-
rm -f show_help_lex.c
152+
# This file is generated
153+
CLEANFILES = show_help_content.c
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env python3
2+
#
3+
# JMS PUT COPYRIGHTS HERE
4+
5+
import os
6+
import sys
7+
import argparse
8+
9+
def find_help_files(root, verbose=False):
10+
"""Search for help-*.txt files, skipping .git and 3rd-party directories."""
11+
help_files = []
12+
skip_dirs = ['.git', '3rd-party']
13+
for root_dir, dirs, files in os.walk(root):
14+
for sd in skip_dirs:
15+
if sd in dirs:
16+
dirs.remove(sd)
17+
18+
for file in files:
19+
if file.startswith("help-") and file.endswith(".txt"):
20+
full_path = os.path.join(root_dir, file)
21+
help_files.append(full_path)
22+
if verbose:
23+
print(f"Found: {full_path}")
24+
return help_files
25+
26+
def parse_ini_files(file_paths, verbose=False):
27+
"""Parse INI-style files, returning a dictionary with filenames as keys."""
28+
data = {}
29+
for file_path in file_paths:
30+
sections = {}
31+
current_section = None
32+
with open(file_path) as file:
33+
for line in file:
34+
line = line.strip()
35+
if line.startswith('#') or not line:
36+
continue
37+
if line.startswith('[') and line.endswith(']'):
38+
current_section = line[1:-1]
39+
sections[current_section] = list()
40+
elif current_section is not None:
41+
sections[current_section].append(line)
42+
43+
data[os.path.basename(file_path)] = sections
44+
45+
if verbose:
46+
print(f"Parsed: {file_path} ({len(sections)} sections found)")
47+
48+
return data
49+
50+
def generate_c_code(parsed_data):
51+
"""Generate C code with an array of filenames and their corresponding INI sections."""
52+
c_code = f"""// THIS FILE IS GENERATED AUTOMATICALLY! EDITS WILL BE LOST!
53+
// This file generated by {sys.argv[0]}
54+
55+
"""
56+
# Can't have embedded {} in f strings; make this a separate
57+
# addition to c_code.
58+
c_code += """#include <stdio.h>
59+
#include <string.h>
60+
61+
typedef struct {
62+
const char *section;
63+
const char *content;
64+
} ini_entry;
65+
66+
typedef struct {
67+
const char *filename;
68+
ini_entry *entries;
69+
} file_entry;
70+
71+
"""
72+
73+
ini_arrays = []
74+
file_entries = []
75+
76+
for idx, (filename, sections) in enumerate(parsed_data.items()):
77+
var_name = filename.replace('-', '_').replace('.', '_')
78+
79+
ini_entries = []
80+
for section, content_list in sections.items():
81+
content = '\n'.join(content_list)
82+
c_content = content.replace('"','\\"').replace("\n", '\\n"\n"')
83+
ini_entries.append(f' {{ "{section}", "{c_content}" }}')
84+
ini_entries.append(f' {{ NULL, NULL }}')
85+
86+
ini_array_name = f"ini_entries_{idx}"
87+
ini_arrays.append(f"static ini_entry {ini_array_name}[] = {{\n" + ",\n".join(ini_entries) + "\n};\n")
88+
file_entries.append(f' {{ "{filename}", {ini_array_name} }}')
89+
file_entries.append(f' {{ NULL, NULL }}')
90+
91+
c_code += "\n".join(ini_arrays) + "\n"
92+
c_code += "static file_entry help_files[] = {\n" + ",\n".join(file_entries) + "\n};\n"
93+
94+
c_code += """
95+
96+
const char *opal_show_help_get_content(const char *filename, const char* topic)
97+
{
98+
file_entry *fe;
99+
ini_entry *ie;
100+
101+
for (int i = 0; help_files[i].filename != NULL; ++i) {
102+
fe = &(help_files[i]);
103+
if (strcmp(fe->filename, filename) == 0) {
104+
for (int j = 0; fe->entries[j].section != NULL; ++j) {
105+
ie = &(fe->entries[j]);
106+
if (strcmp(ie->section, topic) == 0) {
107+
return ie->content;
108+
}
109+
}
110+
}
111+
}
112+
113+
return NULL;
114+
}
115+
"""
116+
117+
return c_code
118+
119+
#-------------------------------
120+
121+
def main():
122+
parser = argparse.ArgumentParser(description="Generate C code from help text INI files.")
123+
parser.add_argument("--root",
124+
required=True,
125+
help="Root directory to search for help-*.txt files")
126+
parser.add_argument("--out",
127+
required=True,
128+
help="Output C file")
129+
parser.add_argument("--verbose",
130+
action="store_true",
131+
help="Enable verbose output")
132+
args = parser.parse_args()
133+
134+
if args.verbose:
135+
print(f"Searching in: {args.root}")
136+
137+
file_paths = find_help_files(args.root, args.verbose)
138+
parsed_data = parse_ini_files(file_paths, args.verbose)
139+
c_code = generate_c_code(parsed_data)
140+
141+
if os.path.exists(args.out):
142+
with open(args.out) as f:
143+
existing_content = f.read()
144+
145+
if existing_content == c_code:
146+
if args.verbose:
147+
print(f"Help string content has not changed; not re-writing {args.out}")
148+
exit(0)
149+
150+
with open(args.out, "w") as f:
151+
f.write(c_code)
152+
153+
if args.verbose:
154+
print(f"Generated C code written to {args.out}")
155+
156+
if __name__ == "__main__":
157+
main()

0 commit comments

Comments
 (0)