Skip to content

Commit 79f24e5

Browse files
committed
Simplify Python path detection to single expression
Avoid exec/eval pair that can fail in some environments. Use a single eval with lambda expression instead.
1 parent bb3ae80 commit 79f24e5

2 files changed

Lines changed: 9 additions & 48 deletions

File tree

src/py.erl

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -795,36 +795,12 @@ create_venv(Path, Opts) ->
795795
%% so we reconstruct the path from sys.prefix and version info
796796
-spec get_python_executable() -> string().
797797
get_python_executable() ->
798-
Code = <<"
799-
import sys, os
800-
# When embedded, sys.executable points to the embedding app
801-
# Use sys.prefix to find the actual Python installation
802-
_py_exe = 'python3' # default fallback
803-
if sys.platform == 'win32':
804-
path = os.path.join(sys.prefix, 'python.exe')
805-
if os.path.isfile(path):
806-
_py_exe = path
807-
else:
808-
ver = f'python{sys.version_info.major}.{sys.version_info.minor}'
809-
# Try common locations
810-
for path in [
811-
os.path.join(sys.prefix, 'bin', ver),
812-
os.path.join(sys.prefix, 'bin', 'python3'),
813-
os.path.join(sys.prefix, 'bin', 'python'),
814-
]:
815-
try:
816-
if os.path.isfile(path) and os.access(path, os.X_OK):
817-
_py_exe = path
818-
break
819-
except:
820-
pass
821-
">>,
822-
case exec(Code) of
823-
ok ->
824-
case eval(<<"_py_exe">>) of
825-
{ok, Path} when is_binary(Path) -> binary_to_list(Path);
826-
_ -> "python3"
827-
end;
798+
%% Use a single expression to find the Python executable
799+
%% Searches for pythonX.Y, python3, python in sys.prefix/bin (Unix)
800+
%% or python.exe in sys.prefix (Windows)
801+
Expr = <<"(lambda: (__import__('os').path.join(__import__('sys').prefix, 'python.exe') if __import__('sys').platform == 'win32' and __import__('os').path.isfile(__import__('os').path.join(__import__('sys').prefix, 'python.exe')) else next((p for p in [__import__('os').path.join(__import__('sys').prefix, 'bin', f'python{__import__(\"sys\").version_info.major}.{__import__(\"sys\").version_info.minor}'), __import__('os').path.join(__import__('sys').prefix, 'bin', 'python3'), __import__('os').path.join(__import__('sys').prefix, 'bin', 'python')] if __import__('os').path.isfile(p)), 'python3')))()">>,
802+
case eval(Expr) of
803+
{ok, Path} when is_binary(Path) -> binary_to_list(Path);
828804
_ -> "python3"
829805
end.
830806

test/py_venv_SUITE.erl

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,9 @@ end_per_suite(_Config) ->
6262
init_per_group(_Group, Config) ->
6363
%% Get Python executable path from the running interpreter
6464
%% Note: sys.executable returns beam.smp when embedded, so we find the actual Python
65-
Code = <<"
66-
import sys, os
67-
_python_path = 'python3' # default
68-
ver = f'python{sys.version_info.major}.{sys.version_info.minor}'
69-
for path in [
70-
os.path.join(sys.prefix, 'bin', ver),
71-
os.path.join(sys.prefix, 'bin', 'python3'),
72-
os.path.join(sys.prefix, 'bin', 'python'),
73-
]:
74-
try:
75-
if os.path.isfile(path) and os.access(path, os.X_OK):
76-
_python_path = path
77-
break
78-
except:
79-
pass
80-
">>,
81-
ok = py:exec(Code),
82-
{ok, PythonPath} = py:eval(<<"_python_path">>),
65+
%% Use a single expression to avoid any exec issues
66+
Expr = <<"(lambda: next((p for p in [__import__('os').path.join(__import__('sys').prefix, 'bin', f'python{__import__(\"sys\").version_info.major}.{__import__(\"sys\").version_info.minor}'), __import__('os').path.join(__import__('sys').prefix, 'bin', 'python3'), __import__('os').path.join(__import__('sys').prefix, 'bin', 'python')] if __import__('os').path.isfile(p)), 'python3'))()">>,
67+
{ok, PythonPath} = py:eval(Expr),
8368
[{python_path, binary_to_list(PythonPath)} | Config].
8469

8570
end_per_group(_Group, _Config) ->

0 commit comments

Comments
 (0)