Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 38 additions & 11 deletions builder/penv_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,27 @@ def get_packages_to_install(deps, installed_packages):
yield package


def install_python_deps(python_exe, external_uv_executable):
def install_python_deps(python_exe, external_uv_executable, uv_cache_dir=None):
"""
Ensure uv package manager is available in penv and install required Python dependencies.

Args:
python_exe: Path to Python executable in the penv
external_uv_executable: Path to external uv executable used to create the penv (can be None)
uv_cache_dir: Optional path to uv cache directory

Returns:
bool: True if successful, False otherwise
"""
# Get the penv directory to locate uv within it
penv_dir = os.path.dirname(os.path.dirname(python_exe))
penv_uv_executable = get_executable_path(penv_dir, "uv")

# Build subprocess environment with UV_CACHE_DIR if specified
uv_env = None
if uv_cache_dir:
uv_env = dict(os.environ)
uv_env["UV_CACHE_DIR"] = str(uv_cache_dir)

# Check if uv is available in the penv
uv_in_penv_available = False
Expand All @@ -256,7 +263,8 @@ def install_python_deps(python_exe, external_uv_executable):
[external_uv_executable, "pip", "install", "uv>=0.1.0", f"--python={python_exe}", "--quiet"],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
timeout=300
timeout=300,
env=uv_env
)
except subprocess.CalledProcessError as e:
print(f"Error: uv installation failed with exit code {e.returncode}")
Expand Down Expand Up @@ -308,7 +316,8 @@ def _get_installed_uv_packages():
capture_output=True,
text=True,
encoding='utf-8',
timeout=300
timeout=300,
env=uv_env
)

if result_obj.returncode == 0:
Expand Down Expand Up @@ -356,7 +365,8 @@ def _get_installed_uv_packages():
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
timeout=300
timeout=300,
env=uv_env
)

except subprocess.CalledProcessError as e:
Expand All @@ -375,7 +385,7 @@ def _get_installed_uv_packages():
return True


def install_esptool(env, platform, python_exe, uv_executable):
def install_esptool(env, platform, python_exe, uv_executable, uv_cache_dir=None):
"""
Install esptool from package folder "tool-esptoolpy" using uv package manager.
Ensures esptool is installed from the specific tool-esptoolpy package directory.
Expand All @@ -385,6 +395,7 @@ def install_esptool(env, platform, python_exe, uv_executable):
platform: PlatformIO platform object
python_exe (str): Path to Python executable in virtual environment
uv_executable (str): Path to uv executable
uv_cache_dir: Optional path to uv cache directory

Raises:
SystemExit: If esptool installation fails or package directory not found
Expand All @@ -396,6 +407,12 @@ def install_esptool(env, platform, python_exe, uv_executable):
)
sys.exit(1)

# Build subprocess environment with UV_CACHE_DIR if specified
uv_env = None
if uv_cache_dir:
uv_env = dict(os.environ)
uv_env["UV_CACHE_DIR"] = str(uv_cache_dir)

# Check if esptool is already installed from the correct path
try:
result = subprocess.run(
Expand Down Expand Up @@ -427,7 +444,7 @@ def install_esptool(env, platform, python_exe, uv_executable):
uv_executable, "pip", "install", "--quiet", "--force-reinstall",
f"--python={python_exe}",
"-e", esptool_repo_path
], timeout=60)
], timeout=60, env=uv_env)

except subprocess.CalledProcessError as e:
sys.stderr.write(
Expand Down Expand Up @@ -468,6 +485,9 @@ def _setup_python_environment_core(env, platform, platformio_dir, should_install
tuple[str, str]: (Path to penv Python executable, Path to esptool script)
"""
penv_dir = str(Path(platformio_dir) / "penv")

# Determine uv cache directory inside .platformio/.cache
uv_cache_dir = str(Path(platformio_dir) / ".cache" / "uv")

# Create virtual environment if not present
if env is not None:
Expand Down Expand Up @@ -498,7 +518,7 @@ def _setup_python_environment_core(env, platform, platformio_dir, should_install

# Install required Python dependencies for ESP32 platform
if has_internet_connection() or github_actions:
if not install_python_deps(penv_python, used_uv_executable):
if not install_python_deps(penv_python, used_uv_executable, uv_cache_dir):
sys.stderr.write("Error: Failed to install Python dependencies into penv\n")
sys.exit(1)
else:
Expand All @@ -508,10 +528,10 @@ def _setup_python_environment_core(env, platform, platformio_dir, should_install
if should_install_esptool:
if env is not None:
# SCons version
install_esptool(env, platform, penv_python, uv_executable)
install_esptool(env, platform, penv_python, uv_executable, uv_cache_dir)
else:
# Minimal setup - install esptool from tool package
_install_esptool_from_tl_install(platform, penv_python, uv_executable)
_install_esptool_from_tl_install(platform, penv_python, uv_executable, uv_cache_dir)

# Setup certifi environment variables
_setup_certifi_env(env, penv_python)
Expand Down Expand Up @@ -582,14 +602,15 @@ def _setup_pipenv_minimal(penv_dir):
return None


def _install_esptool_from_tl_install(platform, python_exe, uv_executable):
def _install_esptool_from_tl_install(platform, python_exe, uv_executable, uv_cache_dir=None):
"""
Install esptool from tl-install provided path into penv.

Args:
platform: PlatformIO platform object
python_exe (str): Path to Python executable in virtual environment
uv_executable (str): Path to uv executable
uv_cache_dir: Optional path to uv cache directory

Raises:
SystemExit: If esptool installation fails or package directory not found
Expand All @@ -599,6 +620,12 @@ def _install_esptool_from_tl_install(platform, python_exe, uv_executable):
if not esptool_repo_path or not os.path.isdir(esptool_repo_path):
return (None, None)

# Build subprocess environment with UV_CACHE_DIR if specified
uv_env = None
if uv_cache_dir:
uv_env = dict(os.environ)
uv_env["UV_CACHE_DIR"] = str(uv_cache_dir)

# Check if esptool is already installed from the correct path
try:
result = subprocess.run(
Expand Down Expand Up @@ -630,7 +657,7 @@ def _install_esptool_from_tl_install(platform, python_exe, uv_executable):
uv_executable, "pip", "install", "--quiet", "--force-reinstall",
f"--python={python_exe}",
"-e", esptool_repo_path
], timeout=60)
], timeout=60, env=uv_env)
print(f"Installed esptool from tl-install path: {esptool_repo_path}")

except subprocess.CalledProcessError as e:
Expand Down