From 8ce90fd22da71ed32fcccc2c2bb87e57667241c8 Mon Sep 17 00:00:00 2001 From: Dmitry Samarkanov Date: Fri, 3 Apr 2026 08:33:28 +0000 Subject: [PATCH] Optimize executable discovery and fix MATLAB versioning --- sumatra/programs.py | 47 +++++++++++++++++++------------ test/unittests/test_parameters.py | 1 + 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/sumatra/programs.py b/sumatra/programs.py index f817f6ad..c2156ba1 100644 --- a/sumatra/programs.py +++ b/sumatra/programs.py @@ -62,6 +62,8 @@ class Executable(object): requires_script = False # does this executable require a script file required_attributes = ("executable_names", "file_extensions",) name = None + executable_names = () + file_extensions = () def __init__(self, path, version=None, options="", name=None): if path and os.path.exists(path): @@ -83,6 +85,21 @@ def __repr__(self): s += " options: %s" % self.options return s + @classmethod + def can_handle(cls, path=None, script_file=None): + """ + Return True if this class can handle the given path or script_file. + """ + if path: + prog_name = os.path.basename(path) + if prog_name in cls.executable_names: + return True + if script_file: + _, ext = os.path.splitext(script_file) + if ext in cls.file_extensions: + return True + return False + def _find_executable(self, executable_name): found = [] if sys.platform == 'win32' or sys.platform == 'win64': @@ -166,7 +183,7 @@ class MatlabExecutable(Executable): requires_script = True def _get_version(self): - returncode, output, err = run("matlab -nodesktop -nosplash -nojvm -r \"disp(['SMT_DETECT_MATLAB_VERSION=' version()]);quit\"", + returncode, output, err = run("%s -nodesktop -nosplash -nojvm -r \"disp(['SMT_DETECT_MATLAB_VERSION=' version()]);quit\"" % self.path, shell=True) return version_in_command_line_output( command_line_output=output + err, @@ -177,7 +194,7 @@ def _get_version(self): @component class RExecutable(Executable): name = "R" - executable_names = ('Rscript') + executable_names = ('Rscript',) file_extensions = ('.R', '.r') default_executable_name = "Rscript" requires_script = True @@ -223,20 +240,14 @@ def get_executable(path=None, script_file=None): script. Return an appropriate subclass of Executable """ + executable_types = get_registered_components(Executable).values() + # Find the most specific handler + for executable_type in executable_types: + if executable_type.can_handle(path, script_file): + return executable_type(path) + # Default to base Executable if path is provided but no specific handler is found if path: - prog_name = os.path.basename(path) - program = Executable(path) - for executable_type in get_registered_components(Executable).values(): - if prog_name in executable_type.executable_names: - program = executable_type(path) - elif script_file: - script_path, ext = os.path.splitext(script_file) - program = None - for executable_type in get_registered_components(Executable).values(): - if ext in executable_type.file_extensions: - program = executable_type(path) - if program is None: - raise Exception("Extension not recognized.") - else: - raise Exception('Either path or script_file must be specified') - return program + return Executable(path) + if script_file: + raise Exception("Extension not recognized.") + raise Exception('Either path or script_file must be specified') diff --git a/test/unittests/test_parameters.py b/test/unittests/test_parameters.py index 6a94cf2f..d2126caf 100644 --- a/test/unittests/test_parameters.py +++ b/test/unittests/test_parameters.py @@ -523,6 +523,7 @@ def tearDown(self): os.remove("test_file.json") if yaml_loaded: os.remove("test_file.yaml") + os.remove("test_file.yml") def test__build_parameters_simple(self): P = build_parameters("test_file.simple")