Skip to content

Commit 7a71e9e

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 45258b6 commit 7a71e9e

File tree

8 files changed

+207
-371
lines changed

8 files changed

+207
-371
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

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

0 commit comments

Comments
 (0)