Skip to content

Commit 355bc87

Browse files
committed
Don't bother to exclude library aliasing when wi4mpi is active. Rework argument parsing to sanely handle unknown arguments. Recognize openmpi so.41 libraries
1 parent ddd1cd8 commit 355bc87

File tree

5 files changed

+111
-54
lines changed

5 files changed

+111
-54
lines changed

e4s_cl/cf/launchers/__init__.py

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,74 +28,117 @@ class Parser:
2828
"""
2929
Default parser.
3030
31-
Relies on a dictionnary, arguments, with an almost-exhaustive list
32-
of the launchers options to determine when the launcher stops and
33-
when the command begins.
34-
35-
All the --option=value will be caught and don't need to be present in
36-
the dictionnary.
37-
38-
A minimal launcher module can use this to simplify implementation:
39-
```
40-
from e4s_cl.cf.launchers import Parser
41-
SCRIPT_NAMES = [identifiers]
42-
ARGUMENTS = {...}
43-
PARSER = Parser(ARGUMENTS)
44-
```
31+
Uses a dictionary of known options when available, but applies robust
32+
fallbacks so unknown options don't prematurely terminate parsing.
33+
34+
Rules:
35+
- '--' ends option parsing; remainder is the program.
36+
- ':' (Open MPI app-context delimiter) is retained on the launcher side.
37+
Note: multi-app context still needs higher-level handling to inject the
38+
wrapper before each app.
39+
- Known options consume their declared nargs.
40+
- '--opt=value' is always accepted.
41+
- Unknown long options ('--opt') consume one value if the next token isn't
42+
an option; otherwise consume none.
43+
- Unknown short options consume one value if the next token isn't an option.
44+
For concatenated known 1-arg short opts (e.g., '-xFOO=bar'), the entire
45+
token is accepted as one.
4546
"""
4647

4748
def __init__(self, arguments: Dict[str, int]):
48-
self.arguments = arguments
49+
self.arguments = arguments or {}
50+
51+
@staticmethod
52+
def _is_option(tok: str) -> bool:
53+
return tok.startswith('-') and tok != '-'
54+
55+
@staticmethod
56+
def _has_inline_value(tok: str) -> bool:
57+
return tok.startswith('--') and '=' in tok
4958

5059
def parse(self, command: List[str]) -> Tuple[List[str], List[str]]:
5160
"""
5261
Separate a command line into launcher and program.
5362
"""
54-
position = 0
55-
known = True
56-
launcher = []
63+
if not command:
64+
return [], []
5765

58-
launcher.append(command[position])
66+
position = 0
67+
launcher = [command[position]]
5968
position += 1
69+
n = len(command)
6070

61-
while known and position < len(command):
71+
while position < n:
6272
flag = command[position]
6373

64-
if flag in self.arguments:
65-
to_skip = self.arguments[flag]
66-
67-
# Catch generic --flag=value
68-
elif re.match(r'^--[\-A-Za-z0-9]+=.*$', flag):
69-
to_skip = 0
70-
71-
# Catch concatenated flag and value, -p gpu => -pgpu
72-
# We know flag is not in self.arguments
73-
elif re.match(r'^-[\w\-]+', flag):
74-
# List arguments that match the start of flag and that take only one option
75-
matches = list(
76-
filter(
77-
lambda option: (flag.startswith(option) and self.
78-
arguments[option] == 1),
79-
self.arguments))
80-
81-
if (matches):
82-
to_skip = 0
83-
else:
84-
known = False
85-
break
86-
87-
else:
88-
known = False
74+
# End-of-options sentinel: keep it on the launcher side and stop
75+
if flag == '--':
76+
launcher.append(flag)
77+
position += 1
8978
break
9079

91-
for index in range(0, to_skip + 1):
92-
launcher.append(command[position + index])
80+
# Open MPI app-context delimiter: retain and continue parsing
81+
if flag == ':':
82+
launcher.append(flag)
83+
position += 1
84+
continue
85+
86+
# Known option: consume declared nargs
87+
if flag in self.arguments:
88+
nargs = self.arguments[flag]
89+
launcher.append(flag)
90+
# Append up to nargs following tokens safely
91+
for k in range(min(nargs, n - position - 1)):
92+
launcher.append(command[position + 1 + k])
93+
position += 1 + nargs
94+
continue
95+
96+
# Generic '--opt=value'
97+
if self._has_inline_value(flag):
98+
launcher.append(flag)
99+
position += 1
100+
continue
101+
102+
# Unknown long option: '--opt'
103+
if flag.startswith('--'):
104+
launcher.append(flag)
105+
# Heuristic: if next token is not an option, treat as value
106+
if position + 1 < n and not self._is_option(command[position + 1]):
107+
launcher.append(command[position + 1])
108+
position += 2
109+
else:
110+
position += 1
111+
continue
112+
113+
# Short options (unknown or concatenated)
114+
if flag.startswith('-') and flag != '-':
115+
# If any known 1-arg option is a prefix of this token, accept concatenated form
116+
concatenated = next(
117+
(
118+
opt for opt, nargs in self.arguments.items()
119+
if nargs == 1 and flag.startswith(opt)
120+
),
121+
None,
122+
)
123+
if concatenated:
124+
launcher.append(flag)
125+
position += 1
126+
continue
127+
128+
# Otherwise, assume unknown short option possibly with one value
129+
launcher.append(flag)
130+
if position + 1 < n and not self._is_option(command[position + 1]):
131+
launcher.append(command[position + 1])
132+
position += 2
133+
else:
134+
position += 1
135+
continue
93136

94-
position += (to_skip + 1)
137+
# First non-option token: program starts here
138+
break
95139

96140
return launcher, command[position:]
97141

98-
99142
LAUNCHERS = {}
100143

101144
for _, _module_name, _ in walk_packages(path=__path__, prefix=__name__ + '.'):

e4s_cl/cf/launchers/mpirun.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from e4s_cl.cf.launchers import Parser
44

5-
SCRIPT_NAMES = ['mpirun']
5+
SCRIPT_NAMES = ['mpirun','mpiexec']
66

77
_global = {
88
"--allow-run-as-root": 0,
@@ -42,11 +42,13 @@
4242
"--max-restarts": 1,
4343
"--max-vm-size": 1,
4444
"--mca": 2,
45+
"-mca": 2,
4546
"--merge-stderr-to-stdout": 0,
4647
"--n": 1,
4748
"--no-ready-msg": 0,
4849
"--noprefix": 0,
4950
"--np": 1,
51+
"--npernode": 1,
5052
"--omca": 2,
5153
"--ompi-server": 1,
5254
"--output-directory": 1,

e4s_cl/cli/commands/__execute.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,8 @@ def main(self, argv):
409409
for shared_object in final_libset:
410410
import_library(shared_object, container)
411411

412-
# MPICH-family aliasing (skip if Wi4MPI is active)
413-
if wi4mpi_install_dir is None:
414-
alias_guest_mpi_sonames_conservative(final_libset, container)
412+
# MPICH-family aliasing
413+
alias_guest_mpi_sonames_conservative(final_libset, container)
415414

416415
if wi4mpi_install_dir is None:
417416
# Preload the top-level libraries of the imported set to bypass

e4s_cl/cli/commands/launch.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,19 @@ def main(self, argv):
355355
launcher.extend(shlex.split(f"-x {varname}"))
356356

357357
execute_command = _format_execute(parameters)
358-
full_command = [*launcher, *wi4mpi_call, *execute_command, *program]
358+
359+
def _supports_end_of_options(tokens: List[str]) -> bool:
360+
if not tokens:
361+
return False
362+
name = Path(tokens[0]).name
363+
return name in {"mpirun", "mpiexec", "srun", "jsrun", "aprun", "ibrun"}
364+
365+
launcher_tokens = list(launcher)
366+
# Insert end-of-options delimiter if not already present
367+
if _supports_end_of_options(launcher_tokens) and (not launcher_tokens or launcher_tokens[-1] != "--"):
368+
launcher_tokens.append("--")
369+
370+
full_command = [*launcher_tokens, *wi4mpi_call, *execute_command, *program]
359371

360372
if variables.is_dry_run():
361373
print(' '.join(map(str, full_command)))

e4s_cl/scripts/e4s_cl_mpi_tester.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def bind_ompi(lib: ctypes.CDLL) -> MPIHandles:
9797
("libmpi.so.12", bind_mpich),
9898
("libmpi.so.0.0.0", bind_mpich),
9999
("libmpi.so.40", bind_ompi),
100+
("libmpi.so.41", bind_ompi),
100101
("libmpi_cray.so.12", bind_mpich),
101102
]
102103

0 commit comments

Comments
 (0)