diff --git a/arc/job/env_run.py b/arc/job/env_run.py index 6a89589390..9b08c77aa1 100644 --- a/arc/job/env_run.py +++ b/arc/job/env_run.py @@ -196,7 +196,10 @@ def rmg_env_command(py_args: str | list[str], Resolution order, preserved from the call sites this replaced: ``MAMBA_EXE`` (exported by setup-micromamba in CI) → ``RMG_PYTHON`` from ARC's settings (needed on conda/mambaforge installs where micromamba's - ``conda`` shim is broken) → a launcher found on PATH, hunted for under a + ``conda`` shim is broken; this branch invokes the interpreter directly, + so it also re-exports ``PYTHONPATH`` from ``RMG_PATH`` after scrubbing + ARC's leaked activation vars, restoring visibility of a source-tree + RMG-Py/Arkane checkout) → a launcher found on PATH, hunted for under a login shell so that a conda initialization block in the user's profile is still honoured. @@ -240,11 +243,21 @@ def rmg_env_command(py_args: str | list[str], ]) if rmg_python and os.path.isfile(rmg_python): - return '\n'.join(preamble + [ + rmg_path = settings.get('RMG_PATH') + lines = preamble + [ f'unset {" ".join(_ARC_ENV_ACTIVATION_VARS)}', f'export PATH={shlex.quote(os.path.dirname(rmg_python))}:"$PATH"', - f'{shlex.quote(rmg_python)} {py_args}{suffix}', - ]) + ] + if rmg_path: + # Restore PYTHONPATH to RMG_PATH after scrubbing ARC's leakage above. + # rmg_python is invoked directly (no launcher, no activation hooks), so + # on a source-tree RMG-Py/Arkane checkout reachable only via + # PYTHONPATH -- rather than pip-installed into rmg_env -- unsetting + # PYTHONPATH and stopping there leaves the child unable to import + # rmgpy/arkane at all. + lines.append(f'export PYTHONPATH={shlex.quote(rmg_path)}') + lines.append(f'{shlex.quote(rmg_python)} {py_args}{suffix}') + return '\n'.join(lines) # No launcher pinned by an env var and no configured interpreter: hunt for a # launcher on PATH. This runs under ``bash -l`` so the user's profile (where diff --git a/arc/job/env_run_test.py b/arc/job/env_run_test.py index ab9863718c..48df5cbe4f 100644 --- a/arc/job/env_run_test.py +++ b/arc/job/env_run_test.py @@ -384,5 +384,42 @@ def test_py_args_list_arkane_module_invocation_three_tokens(self): self.assertIn('python -m arkane input.py', script) +class TestRmgEnvCommandPythonPath(unittest.TestCase): + """The RMG_PYTHON (direct-interpreter) branch unsets PYTHONPATH along + with the rest of ARC's leaked activation vars, then must re-export it + from RMG_PATH -- otherwise a source-tree RMG-Py/Arkane checkout that is + only reachable via PYTHONPATH (not pip-installed into rmg_env) can never + be imported by the child interpreter.""" + + def setUp(self): + # Force the RMG_PYTHON branch: no MAMBA_EXE, and RMG_PYTHON resolves + # to a real file (RMG_PATH/RMG_PYTHON come from the patched settings + # dict below; os.path.isfile still needs a real path on disk, so + # point it at this test file itself). + self.env_patch = patch.dict(os.environ, {}, clear=False) + self.env_patch.start() + self.addCleanup(self.env_patch.stop) + os.environ.pop('MAMBA_EXE', None) + self.fake_rmg_python = __file__ + + def test_pythonpath_reexported_from_rmg_path(self): + settings_overrides = {'RMG_ENV_NAME': 'rmg_env', 'RMG_PYTHON': self.fake_rmg_python, + 'RMG_PATH': '/opt/RMG-Py'} + with patch.dict('arc.job.env_run.settings', settings_overrides): + script = rmg_env_command("-c 'pass'") + self.assertIn(f'export PYTHONPATH={shlex.quote("/opt/RMG-Py")}', script) + # The re-export must come after the unset, so it is not clobbered. + unset_idx = script.index('unset ') + export_idx = script.index('export PYTHONPATH=') + self.assertLess(unset_idx, export_idx) + + def test_no_pythonpath_export_when_rmg_path_falsy(self): + settings_overrides = {'RMG_ENV_NAME': 'rmg_env', 'RMG_PYTHON': self.fake_rmg_python, + 'RMG_PATH': None} + with patch.dict('arc.job.env_run.settings', settings_overrides): + script = rmg_env_command("-c 'pass'") + self.assertNotIn('export PYTHONPATH=', script) + + if __name__ == '__main__': unittest.main()