Skip to content

Commit ede42a8

Browse files
authored
simplify venv setup (#246)
1 parent 020fd73 commit ede42a8

File tree

1 file changed

+45
-152
lines changed

1 file changed

+45
-152
lines changed

builder/main.py

Lines changed: 45 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import json
1717
import os
1818
import re
19+
import site
1920
import semantic_version
2021
import shlex
2122
import subprocess
@@ -61,19 +62,23 @@
6162
platform = env.PioPlatform()
6263
projectconfig = env.GetProjectConfig()
6364
terminal_cp = locale.getpreferredencoding().lower()
64-
PYTHON_EXE = env.subst("$PYTHONEXE") # Global Python executable path
65-
66-
# Framework directory path
6765
FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif32")
68-
6966
platformio_dir = projectconfig.get("platformio", "core_dir")
67+
68+
# Global Python executable path, replaced later with venv python path
69+
PYTHON_EXE = env.subst("$PYTHONEXE")
7070
penv_dir = os.path.join(platformio_dir, "penv")
7171

72-
pip_path = os.path.join(
73-
penv_dir,
74-
"Scripts" if IS_WINDOWS else "bin",
75-
"pip" + (".exe" if IS_WINDOWS else ""),
76-
)
72+
73+
def get_executable_path(executable_name):
74+
"""
75+
Get the path to an executable based on the penv_dir.
76+
"""
77+
exe_suffix = ".exe" if IS_WINDOWS else ""
78+
scripts_dir = "Scripts" if IS_WINDOWS else "bin"
79+
80+
return os.path.join(penv_dir, scripts_dir, f"{executable_name}{exe_suffix}")
81+
7782

7883
def setup_pipenv_in_package():
7984
"""
@@ -87,144 +92,43 @@ def setup_pipenv_in_package():
8792
)
8893
)
8994
assert os.path.isfile(
90-
pip_path
95+
get_executable_path("pip")
9196
), "Error: Failed to create a proper virtual environment. Missing the `pip` binary!"
9297

93-
penv_python = os.path.join(penv_dir, "Scripts", "python.exe") if IS_WINDOWS else os.path.join(penv_dir, "bin", "python")
94-
env.Replace(PYTHONEXE=penv_python)
9598

96-
# Setup virtual environment if needed and find path to Python exe
99+
# Setup virtual environment if needed
97100
setup_pipenv_in_package()
101+
98102
# Set Python Scons Var to env Python
99-
PYTHON_EXE = env.subst("$PYTHONEXE")
100-
# Remove PYTHONHOME if set
101-
os.environ.pop('PYTHONHOME', None)
103+
penv_python = get_executable_path("python")
104+
env.Replace(PYTHONEXE=penv_python)
105+
PYTHON_EXE = penv_python
102106

103107
# check for python binary, exit with error when not found
104108
assert os.path.isfile(PYTHON_EXE), f"Python executable not found: {PYTHON_EXE}"
105109

106-
def add_to_pythonpath(path):
107-
"""
108-
Add a path to the PYTHONPATH environment variable (cross-platform).
109-
110-
Args:
111-
path (str): The path to add to PYTHONPATH
112-
"""
113-
# Normalize the path for the current OS
114-
normalized_path = os.path.normpath(path)
115-
116-
# Add to PYTHONPATH environment variable
117-
if "PYTHONPATH" in os.environ:
118-
current_paths = os.environ["PYTHONPATH"].split(os.pathsep)
119-
normalized_current_paths = [os.path.normpath(p) for p in current_paths]
120-
if normalized_path not in normalized_current_paths:
121-
# Rebuild PYTHONPATH with normalized paths to avoid duplicates
122-
normalized_current_paths.insert(0, normalized_path)
123-
os.environ["PYTHONPATH"] = os.pathsep.join(normalized_current_paths)
124-
else:
125-
os.environ["PYTHONPATH"] = normalized_path
126-
127-
# Also add to sys.path for immediate availability
128-
if normalized_path not in sys.path:
129-
sys.path.insert(0, normalized_path)
130-
131110

132111
def setup_python_paths():
133-
"""
134-
Setup Python paths based on the actual Python executable being used.
135-
136-
This function configures both PYTHONPATH environment variable and sys.path
137-
to include the Python executable directory and site-packages directory.
138-
"""
139-
# Get the directory containing the Python executable
140-
python_dir = os.path.dirname(PYTHON_EXE)
141-
142-
# Add Scripts directory to PATH for Windows
143-
if IS_WINDOWS:
144-
scripts_dir = os.path.join(python_dir, "Scripts")
145-
if os.path.isdir(scripts_dir):
146-
os.environ["PATH"] = scripts_dir + os.pathsep + os.environ.get("PATH", "")
147-
else:
148-
bin_dir = os.path.join(python_dir, "bin")
149-
if os.path.isdir(bin_dir):
150-
os.environ["PATH"] = bin_dir + os.pathsep + os.environ.get("PATH", "")
151-
152-
penv_site_packages = None
153-
if python_dir not in sys.path:
154-
add_to_pythonpath(python_dir)
155-
if IS_WINDOWS:
156-
penv_site_packages = os.path.join(penv_dir, "Lib", "site-packages")
157-
else:
158-
# Find the actual site-packages directory in the venv
159-
penv_lib_dir = os.path.join(penv_dir, "lib")
160-
if os.path.isdir(penv_lib_dir):
161-
for python_version_dir in os.listdir(penv_lib_dir):
162-
if python_version_dir.startswith("python"):
163-
penv_site_packages = os.path.join(penv_lib_dir, python_version_dir, "site-packages")
164-
break
165-
166-
if penv_site_packages and os.path.isdir(penv_site_packages) and penv_site_packages not in sys.path:
167-
add_to_pythonpath(penv_site_packages)
168-
169-
setup_python_paths()
170-
171-
172-
def _get_executable_path(python_exe, executable_name):
173-
"""
174-
Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path.
175-
176-
Args:
177-
python_exe (str): Path to Python executable
178-
executable_name (str): Name of the executable to find (e.g., 'esptool', 'uv')
179-
180-
Returns:
181-
str: Path to executable or fallback to executable name
182-
"""
183-
184-
python_dir = os.path.dirname(python_exe)
185-
186-
if IS_WINDOWS:
187-
executable_path = os.path.join(python_dir, f"{executable_name}.exe")
188-
else:
189-
# For Unix-like systems, executables are typically in the same directory as python
190-
# or in a bin subdirectory
191-
executable_path = os.path.join(python_dir, executable_name)
192-
193-
# If not found in python directory, try bin subdirectory
194-
if not os.path.isfile(executable_path):
195-
bin_dir = os.path.join(python_dir, "bin")
196-
executable_path = os.path.join(bin_dir, executable_name)
112+
"""Setup Python module search paths using the penv_dir."""
113+
# Add penv_dir to module search path
114+
site.addsitedir(penv_dir)
197115

198-
if os.path.isfile(executable_path):
199-
return executable_path
116+
# Add site-packages directory
117+
python_ver = f"python{sys.version_info.major}.{sys.version_info.minor}"
118+
site_packages = (
119+
os.path.join(penv_dir, "Lib", "site-packages") if IS_WINDOWS
120+
else os.path.join(penv_dir, "lib", python_ver, "site-packages")
121+
)
200122

201-
return executable_name # Fallback to command name
123+
if os.path.isdir(site_packages):
124+
site.addsitedir(site_packages)
202125

203126

204-
def _get_esptool_executable_path(python_exe):
205-
"""
206-
Get the path to the esptool executable binary.
207-
208-
Args:
209-
python_exe (str): Path to Python executable
210-
211-
Returns:
212-
str: Path to esptool executable
213-
"""
214-
return _get_executable_path(python_exe, "esptool")
127+
setup_python_paths()
215128

216-
217-
def _get_uv_executable_path(python_exe):
218-
"""
219-
Get the path to the uv executable binary.
220-
221-
Args:
222-
python_exe (str): Path to Python executable
223-
224-
Returns:
225-
str: Path to uv executable
226-
"""
227-
return _get_executable_path(python_exe, "uv")
129+
# Set executable paths from tools
130+
esptool_binary_path = get_executable_path("esptool")
131+
uv_executable = get_executable_path("uv")
228132

229133

230134
def get_packages_to_install(deps, installed_packages):
@@ -254,9 +158,6 @@ def install_python_deps():
254158
Returns:
255159
bool: True if successful, False otherwise
256160
"""
257-
# Get uv executable path
258-
uv_executable = _get_uv_executable_path(PYTHON_EXE)
259-
260161
try:
261162
result = subprocess.run(
262163
[uv_executable, "--version"],
@@ -275,15 +176,12 @@ def install_python_deps():
275176
capture_output=True,
276177
text=True,
277178
timeout=30, # 30 second timeout
278-
env=os.environ # Use modified environment with custom PYTHONPATH
179+
env=os.environ # Use current environment with venv Python
279180
)
280181
if result.returncode != 0:
281182
if result.stderr:
282183
print(f"Error output: {result.stderr.strip()}")
283184
return False
284-
285-
# Update uv executable path after installation
286-
uv_executable = _get_uv_executable_path(PYTHON_EXE)
287185

288186
except subprocess.TimeoutExpired:
289187
print("Error: uv installation timed out")
@@ -312,7 +210,7 @@ def _get_installed_uv_packages():
312210
text=True,
313211
encoding='utf-8',
314212
timeout=30, # 30 second timeout
315-
env=os.environ # Use modified environment with custom PYTHONPATH
213+
env=os.environ # Use current environment with venv Python
316214
)
317215

318216
if result_obj.returncode == 0:
@@ -355,7 +253,7 @@ def _get_installed_uv_packages():
355253
capture_output=True,
356254
text=True,
357255
timeout=30, # 30 second timeout for package installation
358-
env=os.environ # Use modified environment with custom PYTHONPATH
256+
env=os.environ # Use current environment with venv Python
359257
)
360258

361259
if result.returncode != 0:
@@ -380,9 +278,6 @@ def _get_installed_uv_packages():
380278
def install_esptool():
381279
"""
382280
Install esptool from package folder "tool-esptoolpy" using uv package manager.
383-
384-
Returns:
385-
str: Path to esptool executable
386281
387282
Raises:
388283
SystemExit: If esptool installation fails
@@ -394,35 +289,33 @@ def install_esptool():
394289
stderr=subprocess.DEVNULL,
395290
env=os.environ
396291
)
397-
return _get_esptool_executable_path(PYTHON_EXE)
292+
return
398293
except (subprocess.CalledProcessError, FileNotFoundError):
399294
pass
400295

401296
esptool_repo_path = env.subst(platform.get_package_dir("tool-esptoolpy") or "")
402297
if not esptool_repo_path or not os.path.isdir(esptool_repo_path):
403298
print("Error: esptool package directory not found")
404299
sys.exit(1)
405-
406-
uv_executable = _get_uv_executable_path(PYTHON_EXE)
300+
407301
try:
408302
subprocess.check_call([
409303
uv_executable, "pip", "install", "--quiet",
410304
f"--python={PYTHON_EXE}",
411305
"-e", esptool_repo_path
412306
], env=os.environ)
413-
414-
return _get_esptool_executable_path(PYTHON_EXE)
415-
307+
308+
return
309+
416310
except subprocess.CalledProcessError as e:
417311
print(f"Error: Failed to install esptool: {e}")
418312
sys.exit(1)
419313

420314

421-
# Install Python dependencies
315+
# Install espressif32 Python dependencies
422316
install_python_deps()
423-
424317
# Install esptool after dependencies
425-
esptool_binary_path = install_esptool()
318+
install_esptool()
426319

427320

428321
def BeforeUpload(target, source, env):

0 commit comments

Comments
 (0)