11"""General utilities."""
2+
23import collections .abc
34import contextlib
45import os
6+ import platform
57import re
68import shutil
79import subprocess
810import sys
911from copy import deepcopy
1012from functools import partial
13+ from importlib .resources import files
1114from pathlib import Path
1215
16+ import haddock
1317from haddock import EmptyPath , log
1418from haddock .core .exceptions import SetupError
1519from haddock .core .typing import (
3034)
3135from haddock .gear .greetings import get_goodbye_help
3236
33-
3437check_subprocess = partial (
3538 subprocess .run ,
3639 shell = True ,
@@ -62,7 +65,7 @@ def make_list_if_string(item: Union[str, list[str]]) -> list[str]:
6265
6366
6467def transform_to_list (
65- item : Union [Iterable [AnyT ], AnyT ]
68+ item : Union [Iterable [AnyT ], AnyT ],
6669) -> Union [list [AnyT ], tuple [AnyT , ...]]:
6770 """
6871 Put `item` into a list if not a list already.
@@ -130,7 +133,7 @@ def remove_dict_keys(d: ParamMap, keys: Container[str]) -> ParamDict:
130133
131134def cpu_count () -> int :
132135 """Count number of available CPU for the process.
133-
136+
134137 User suggestion, by https://github.com/EricDeveaud
135138
136139 Note: pid 0 == current process
@@ -142,9 +145,10 @@ def cpu_count() -> int:
142145 process_ncores : int
143146 Number of cores allocated to the process pid.
144147 """
148+
145149 def _cpu_count () -> int :
146150 """Detect number of cores available.
147-
151+
148152 Returns
149153 -------
150154 ncores : int
@@ -159,7 +163,7 @@ def _cpu_count() -> int:
159163 ncores = int (_process_ncores )
160164 except AttributeError :
161165 # If unsucessful, return the number cores detected in the machine
162- # Note: this can happen on MacOS, where the os.sched_getaffinity
166+ # Note: this can happen on MacOS, where the os.sched_getaffinity
163167 # may not be defined/exist.
164168 ncores = int (os .cpu_count ())
165169 return ncores
@@ -422,3 +426,56 @@ def recursive_convert_paths_to_strings(params: ParamMapT) -> ParamMapT:
422426 params [param ] = value
423427
424428 return params
429+
430+
431+ def get_cns_executable () -> tuple [Path , Path ]:
432+ """
433+ Locate CNS executable and its Linux variant for grid execution.
434+
435+ Returns:
436+ Tuple of (cns_exec, cns_exec_linux) paths
437+
438+ Raises:
439+ SystemExit: If CNS executable cannot be found
440+ """
441+ # Try default location first
442+ cns_exec = Path (files (haddock ).joinpath ("bin/cns" )) # type: ignore
443+
444+ if not cns_exec .exists ():
445+ log .error ("CNS executable not found at %s" , cns_exec )
446+
447+ # Fall back to environment variable
448+ cns_exec_env = os .environ .get ("CNS_EXEC" )
449+ if cns_exec_env is None :
450+ log .error (
451+ "Please define the CNS binary location by setting the CNS_EXEC "
452+ "environment variable"
453+ )
454+ sys .exit (1 )
455+
456+ cns_exec = Path (cns_exec_env )
457+ if not cns_exec .exists ():
458+ log .error ("CNS executable not found at %s" , cns_exec )
459+ sys .exit (1 )
460+
461+ # Determine Linux-specific executable for grid usage
462+ system = platform .system ().lower ()
463+ machine = platform .machine ().lower ()
464+ current_arch = f"{ machine } -{ system } "
465+
466+ if current_arch == "x86_64-linux" :
467+ cns_exec_linux = cns_exec
468+ else :
469+ # Look for Linux binary variant
470+ cns_exec_linux = cns_exec .with_name (f"{ cns_exec .name } _linux" )
471+
472+ if not cns_exec_linux .exists ():
473+ log .warning (
474+ "Current architecture is %s, but grid execution requires "
475+ "an x86_64-linux binary" ,
476+ current_arch ,
477+ )
478+ log .warning ("Linux CNS binary not found at %s" , cns_exec_linux )
479+ log .warning ("GRID mode will not be available" )
480+
481+ return cns_exec , cns_exec_linux
0 commit comments