Skip to content

Commit 020fd73

Browse files
authored
consolidate venv Python path settings (#245)
1 parent e6eca52 commit 020fd73

File tree

1 file changed

+67
-79
lines changed

1 file changed

+67
-79
lines changed

builder/main.py

Lines changed: 67 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -83,52 +83,26 @@ def setup_pipenv_in_package():
8383
env.Execute(
8484
env.VerboseAction(
8585
'"$PYTHONEXE" -m venv --clear "%s"' % penv_dir,
86-
"Creating a new virtual environment for Python dependencies",
86+
"Creating pioarduino Python virtual environment: %s" % penv_dir,
8787
)
8888
)
89-
9089
assert os.path.isfile(
9190
pip_path
9291
), "Error: Failed to create a proper virtual environment. Missing the `pip` binary!"
9392

9493
penv_python = os.path.join(penv_dir, "Scripts", "python.exe") if IS_WINDOWS else os.path.join(penv_dir, "bin", "python")
9594
env.Replace(PYTHONEXE=penv_python)
96-
print(f"PYTHONEXE updated to penv environment: {penv_python}")
9795

96+
# Setup virtual environment if needed and find path to Python exe
9897
setup_pipenv_in_package()
99-
# Update global PYTHON_EXE variable after potential pipenv setup
98+
# Set Python Scons Var to env Python
10099
PYTHON_EXE = env.subst("$PYTHONEXE")
101-
python_exe = PYTHON_EXE
102-
103-
# Ensure penv Python directory is in PATH for subprocess calls
104-
python_dir = os.path.dirname(PYTHON_EXE)
105-
current_path = os.environ.get("PATH", "")
106-
if python_dir not in current_path:
107-
os.environ["PATH"] = python_dir + os.pathsep + current_path
100+
# Remove PYTHONHOME if set
101+
os.environ.pop('PYTHONHOME', None)
108102

109-
# Verify the Python executable exists
103+
# check for python binary, exit with error when not found
110104
assert os.path.isfile(PYTHON_EXE), f"Python executable not found: {PYTHON_EXE}"
111105

112-
if os.path.isfile(python_exe):
113-
# Update sys.path to include penv site-packages
114-
if IS_WINDOWS:
115-
penv_site_packages = os.path.join(penv_dir, "Lib", "site-packages")
116-
else:
117-
# Find the actual site-packages directory in the venv
118-
penv_lib_dir = os.path.join(penv_dir, "lib")
119-
if os.path.isdir(penv_lib_dir):
120-
for python_dir in os.listdir(penv_lib_dir):
121-
if python_dir.startswith("python"):
122-
penv_site_packages = os.path.join(penv_lib_dir, python_dir, "site-packages")
123-
break
124-
else:
125-
penv_site_packages = None
126-
else:
127-
penv_site_packages = None
128-
129-
if penv_site_packages and os.path.isdir(penv_site_packages) and penv_site_packages not in sys.path:
130-
sys.path.insert(0, penv_site_packages)
131-
132106
def add_to_pythonpath(path):
133107
"""
134108
Add a path to the PYTHONPATH environment variable (cross-platform).
@@ -138,43 +112,63 @@ def add_to_pythonpath(path):
138112
"""
139113
# Normalize the path for the current OS
140114
normalized_path = os.path.normpath(path)
141-
115+
142116
# Add to PYTHONPATH environment variable
143117
if "PYTHONPATH" in os.environ:
144118
current_paths = os.environ["PYTHONPATH"].split(os.pathsep)
145119
normalized_current_paths = [os.path.normpath(p) for p in current_paths]
146120
if normalized_path not in normalized_current_paths:
147-
os.environ["PYTHONPATH"] = normalized_path + os.pathsep + os.environ.get("PYTHONPATH", "")
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)
148124
else:
149125
os.environ["PYTHONPATH"] = normalized_path
150-
126+
151127
# Also add to sys.path for immediate availability
152128
if normalized_path not in sys.path:
153129
sys.path.insert(0, normalized_path)
154130

131+
155132
def setup_python_paths():
156133
"""
157134
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.
158138
"""
159139
# Get the directory containing the Python executable
160140
python_dir = os.path.dirname(PYTHON_EXE)
161-
add_to_pythonpath(python_dir)
162-
163-
# Try to find site-packages directory using the actual Python executable
164-
result = subprocess.run(
165-
[PYTHON_EXE, "-c", "import site; print(site.getsitepackages()[0])"],
166-
capture_output=True,
167-
text=True,
168-
timeout=5
169-
)
170-
if result.returncode == 0:
171-
site_packages = result.stdout.strip()
172-
if os.path.isdir(site_packages):
173-
add_to_pythonpath(site_packages)
174141

175-
# Setup Python paths based on the actual Python executable
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+
176169
setup_python_paths()
177170

171+
178172
def _get_executable_path(python_exe, executable_name):
179173
"""
180174
Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path.
@@ -290,14 +284,7 @@ def install_python_deps():
290284

291285
# Update uv executable path after installation
292286
uv_executable = _get_uv_executable_path(PYTHON_EXE)
293-
294-
# Add Scripts directory to PATH for Windows
295-
if IS_WINDOWS:
296-
python_dir = os.path.dirname(PYTHON_EXE)
297-
scripts_dir = os.path.join(python_dir, "Scripts")
298-
if os.path.isdir(scripts_dir):
299-
os.environ["PATH"] = scripts_dir + os.pathsep + os.environ.get("PATH", "")
300-
287+
301288
except subprocess.TimeoutExpired:
302289
print("Error: uv installation timed out")
303290
return False
@@ -335,7 +322,7 @@ def _get_installed_uv_packages():
335322
for p in packages:
336323
result[p["name"]] = pepver_to_semver(p["version"])
337324
else:
338-
print(f"Warning: pip list failed with exit code {result_obj.returncode}")
325+
print(f"Warning: uv pip list failed with exit code {result_obj.returncode}")
339326
if result_obj.stderr:
340327
print(f"Error output: {result_obj.stderr.strip()}")
341328

@@ -393,10 +380,12 @@ def _get_installed_uv_packages():
393380
def install_esptool():
394381
"""
395382
Install esptool from package folder "tool-esptoolpy" using uv package manager.
396-
Also determines the path to the esptool executable binary.
397383
398384
Returns:
399-
str: Path to esptool executable, or 'esptool' as fallback
385+
str: Path to esptool executable
386+
387+
Raises:
388+
SystemExit: If esptool installation fails
400389
"""
401390
try:
402391
subprocess.check_call(
@@ -405,29 +394,28 @@ def install_esptool():
405394
stderr=subprocess.DEVNULL,
406395
env=os.environ
407396
)
408-
esptool_binary_path = _get_esptool_executable_path(PYTHON_EXE)
409-
return esptool_binary_path
397+
return _get_esptool_executable_path(PYTHON_EXE)
410398
except (subprocess.CalledProcessError, FileNotFoundError):
411399
pass
412400

413401
esptool_repo_path = env.subst(platform.get_package_dir("tool-esptoolpy") or "")
414-
if esptool_repo_path and os.path.isdir(esptool_repo_path):
415-
uv_executable = _get_uv_executable_path(PYTHON_EXE)
416-
try:
417-
subprocess.check_call([
418-
uv_executable, "pip", "install", "--quiet",
419-
f"--python={PYTHON_EXE}",
420-
"-e", esptool_repo_path
421-
], env=os.environ)
422-
423-
esptool_binary_path = _get_esptool_executable_path(PYTHON_EXE)
424-
return esptool_binary_path
425-
426-
except subprocess.CalledProcessError as e:
427-
print(f"Warning: Failed to install esptool: {e}")
428-
return 'esptool' # Fallback
429-
430-
return 'esptool' # Fallback
402+
if not esptool_repo_path or not os.path.isdir(esptool_repo_path):
403+
print("Error: esptool package directory not found")
404+
sys.exit(1)
405+
406+
uv_executable = _get_uv_executable_path(PYTHON_EXE)
407+
try:
408+
subprocess.check_call([
409+
uv_executable, "pip", "install", "--quiet",
410+
f"--python={PYTHON_EXE}",
411+
"-e", esptool_repo_path
412+
], env=os.environ)
413+
414+
return _get_esptool_executable_path(PYTHON_EXE)
415+
416+
except subprocess.CalledProcessError as e:
417+
print(f"Error: Failed to install esptool: {e}")
418+
sys.exit(1)
431419

432420

433421
# Install Python dependencies

0 commit comments

Comments
 (0)