Skip to content

Commit 0d40934

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 0d40934

File tree

8 files changed

+202
-371
lines changed

8 files changed

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

0 commit comments

Comments
 (0)