From ea376347f7d25dae3d6f0f06461b78f6c273d2b9 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:36:32 +0200 Subject: [PATCH 1/2] Use full path for uv and set windows path var --- builder/main.py | 119 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/builder/main.py b/builder/main.py index b71d6f757..d4649c209 100644 --- a/builder/main.py +++ b/builder/main.py @@ -111,6 +111,67 @@ def setup_python_paths(env): setup_python_paths(env) +def _get_executable_path(python_exe, executable_name): + """ + Get the path to an executable binary (esptool, uv, etc.) based on the Python executable path. + + Args: + python_exe (str): Path to Python executable + executable_name (str): Name of the executable to find (e.g., 'esptool', 'uv') + + Returns: + str: Path to executable or fallback to executable name + """ + if not python_exe or not os.path.isfile(python_exe): + return executable_name # Fallback to command name + + python_dir = os.path.dirname(python_exe) + + if sys.platform == "win32": + scripts_dir = os.path.join(python_dir, "Scripts") + executable_path = os.path.join(scripts_dir, f"{executable_name}.exe") + else: + # For Unix-like systems, executables are typically in the same directory as python + # or in a bin subdirectory + executable_path = os.path.join(python_dir, executable_name) + + # If not found in python directory, try bin subdirectory + if not os.path.isfile(executable_path): + bin_dir = os.path.join(python_dir, "bin") + executable_path = os.path.join(bin_dir, executable_name) + + if os.path.isfile(executable_path): + return executable_path + + return executable_name # Fallback to command name + + +def _get_esptool_executable_path(python_exe): + """ + Get the path to the esptool executable binary. + + Args: + python_exe (str): Path to Python executable + + Returns: + str: Path to esptool executable + """ + return _get_executable_path(python_exe, "esptool") + + +def _get_uv_executable_path(python_exe): + """ + Get the path to the uv executable binary. + + Args: + python_exe (str): Path to Python executable + + Returns: + str: Path to uv executable + """ + return _get_executable_path(python_exe, "uv") + + def get_packages_to_install(deps, installed_packages): """ Generator for Python packages that need to be installed. @@ -138,9 +199,12 @@ def install_python_deps(): Returns: bool: True if successful, False otherwise """ + # Get uv executable path + uv_executable = _get_uv_executable_path(env.subst("$PYTHONEXE")) + try: result = subprocess.run( - ["uv", "--version"], + [uv_executable, "--version"], capture_output=True, text=True, timeout=3 @@ -162,6 +226,17 @@ def install_python_deps(): if result.stderr: print(f"Error output: {result.stderr.strip()}") return False + + # Update uv executable path after installation + uv_executable = _get_uv_executable_path(env.subst("$PYTHONEXE")) + + # Add Scripts directory to PATH for Windows + if sys.platform == "win32": + python_dir = os.path.dirname(env.subst("$PYTHONEXE")) + scripts_dir = os.path.join(python_dir, "Scripts") + if os.path.isdir(scripts_dir): + os.environ["PATH"] = scripts_dir + os.pathsep + os.environ.get("PATH", "") + except subprocess.TimeoutExpired: print("Error: uv installation timed out") return False @@ -182,7 +257,7 @@ def _get_installed_uv_packages(): """ result = {} try: - cmd = ["uv", "pip", "list", "--format=json"] + cmd = [uv_executable, "pip", "list", "--format=json"] result_obj = subprocess.run( cmd, capture_output=True, @@ -221,7 +296,7 @@ def _get_installed_uv_packages(): packages_list = [f"{p}{python_deps[p]}" for p in packages_to_install] cmd = [ - "uv", "pip", "install", + uv_executable, "pip", "install", f"--python={env.subst('$PYTHONEXE')}", "--quiet", "--upgrade" ] + packages_list @@ -265,41 +340,15 @@ def install_esptool(env): Returns: str: Path to esptool executable, or 'esptool' as fallback """ - def _get_esptool_executable_path(python_exe): - """ - Get the path to the esptool executable binary. - - Args: - python_exe (str): Path to Python executable - - Returns: - str: Path to esptool executable - """ - if not python_exe or not os.path.isfile(python_exe): - return 'esptool' # Fallback - - python_dir = os.path.dirname(python_exe) - - if sys.platform == "win32": - scripts_dir = os.path.join(python_dir, "Scripts") - esptool_exe = os.path.join(scripts_dir, "esptool.exe") - else: - scripts_dir = os.path.join(python_dir) - esptool_exe = os.path.join(scripts_dir, "esptool") - - if os.path.isfile(esptool_exe): - return esptool_exe - - return 'esptool' - + python_exe = env.subst("$PYTHONEXE") + try: subprocess.check_call( - [env.subst("$PYTHONEXE"), "-c", "import esptool"], + [python_exe, "-c", "import esptool"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=os.environ ) - python_exe = env.subst("$PYTHONEXE") esptool_binary_path = _get_esptool_executable_path(python_exe) return esptool_binary_path except (subprocess.CalledProcessError, FileNotFoundError): @@ -307,14 +356,14 @@ def _get_esptool_executable_path(python_exe): esptool_repo_path = env.subst(platform.get_package_dir("tool-esptoolpy") or "") if esptool_repo_path and os.path.isdir(esptool_repo_path): + uv_executable = _get_uv_executable_path(python_exe) try: subprocess.check_call([ - "uv", "pip", "install", "--quiet", - f"--python={env.subst('$PYTHONEXE')}", + uv_executable, "pip", "install", "--quiet", + f"--python={python_exe}", "-e", esptool_repo_path ], env=os.environ) - python_exe = env.subst("$PYTHONEXE") esptool_binary_path = _get_esptool_executable_path(python_exe) return esptool_binary_path From 3be232eec3fcc4173c2cedc75d25a89f6ec9d147 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:48:20 +0200 Subject: [PATCH 2/2] Global Python executable path --- builder/main.py | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/builder/main.py b/builder/main.py index d4649c209..1c46018a1 100644 --- a/builder/main.py +++ b/builder/main.py @@ -51,6 +51,7 @@ platform = env.PioPlatform() projectconfig = env.GetProjectConfig() terminal_cp = locale.getpreferredencoding().lower() +PYTHON_EXE = env.subst("$PYTHONEXE") # Global Python executable path # Framework directory path FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif32") @@ -80,24 +81,20 @@ def add_to_pythonpath(path): sys.path.insert(0, normalized_path) -def setup_python_paths(env): +def setup_python_paths(): """ Setup Python paths based on the actual Python executable being used. - - Args: - env: SCons environment object """ - python_exe = env.subst('$PYTHONEXE') - if not python_exe or not os.path.isfile(python_exe): + if not PYTHON_EXE or not os.path.isfile(PYTHON_EXE): return # Get the directory containing the Python executable - python_dir = os.path.dirname(python_exe) + python_dir = os.path.dirname(PYTHON_EXE) add_to_pythonpath(python_dir) # Try to find site-packages directory using the actual Python executable result = subprocess.run( - [python_exe, "-c", "import site; print(site.getsitepackages()[0])"], + [PYTHON_EXE, "-c", "import site; print(site.getsitepackages()[0])"], capture_output=True, text=True, timeout=5 @@ -108,7 +105,7 @@ def setup_python_paths(env): add_to_pythonpath(site_packages) # Setup Python paths based on the actual Python executable -setup_python_paths(env) +setup_python_paths() def _get_executable_path(python_exe, executable_name): @@ -200,7 +197,7 @@ def install_python_deps(): bool: True if successful, False otherwise """ # Get uv executable path - uv_executable = _get_uv_executable_path(env.subst("$PYTHONEXE")) + uv_executable = _get_uv_executable_path(PYTHON_EXE) try: result = subprocess.run( @@ -216,7 +213,7 @@ def install_python_deps(): if not uv_available: try: result = subprocess.run( - [env.subst("$PYTHONEXE"), "-m", "pip", "install", "uv>=0.1.0", "-q", "-q", "-q"], + [PYTHON_EXE, "-m", "pip", "install", "uv>=0.1.0", "-q", "-q", "-q"], capture_output=True, text=True, timeout=30, # 30 second timeout @@ -228,11 +225,11 @@ def install_python_deps(): return False # Update uv executable path after installation - uv_executable = _get_uv_executable_path(env.subst("$PYTHONEXE")) + uv_executable = _get_uv_executable_path(PYTHON_EXE) # Add Scripts directory to PATH for Windows if sys.platform == "win32": - python_dir = os.path.dirname(env.subst("$PYTHONEXE")) + python_dir = os.path.dirname(PYTHON_EXE) scripts_dir = os.path.join(python_dir, "Scripts") if os.path.isdir(scripts_dir): os.environ["PATH"] = scripts_dir + os.pathsep + os.environ.get("PATH", "") @@ -297,7 +294,7 @@ def _get_installed_uv_packages(): cmd = [ uv_executable, "pip", "install", - f"--python={env.subst('$PYTHONEXE')}", + f"--python={PYTHON_EXE}", "--quiet", "--upgrade" ] + packages_list @@ -329,42 +326,37 @@ def _get_installed_uv_packages(): return True -def install_esptool(env): +def install_esptool(): """ Install esptool from package folder "tool-esptoolpy" using uv package manager. Also determines the path to the esptool executable binary. - Args: - env: SCons environment object - Returns: str: Path to esptool executable, or 'esptool' as fallback """ - python_exe = env.subst("$PYTHONEXE") - try: subprocess.check_call( - [python_exe, "-c", "import esptool"], + [PYTHON_EXE, "-c", "import esptool"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=os.environ ) - esptool_binary_path = _get_esptool_executable_path(python_exe) + esptool_binary_path = _get_esptool_executable_path(PYTHON_EXE) return esptool_binary_path except (subprocess.CalledProcessError, FileNotFoundError): pass esptool_repo_path = env.subst(platform.get_package_dir("tool-esptoolpy") or "") if esptool_repo_path and os.path.isdir(esptool_repo_path): - uv_executable = _get_uv_executable_path(python_exe) + uv_executable = _get_uv_executable_path(PYTHON_EXE) try: subprocess.check_call([ uv_executable, "pip", "install", "--quiet", - f"--python={python_exe}", + f"--python={PYTHON_EXE}", "-e", esptool_repo_path ], env=os.environ) - esptool_binary_path = _get_esptool_executable_path(python_exe) + esptool_binary_path = _get_esptool_executable_path(PYTHON_EXE) return esptool_binary_path except subprocess.CalledProcessError as e: @@ -376,7 +368,7 @@ def install_esptool(env): # Install Python dependencies and esptool install_python_deps() -esptool_binary_path = install_esptool(env) +esptool_binary_path = install_esptool() def BeforeUpload(target, source, env): @@ -923,7 +915,7 @@ def firmware_metrics(target, source, env): return try: - cmd = [env.subst("$PYTHONEXE"), "-m", "esp_idf_size", "--ng"] + cmd = [PYTHON_EXE, "-m", "esp_idf_size", "--ng"] # Parameters from platformio.ini extra_args = env.GetProjectOption("custom_esp_idf_size_args", "") @@ -1051,7 +1043,7 @@ def firmware_metrics(target, source, env): env.Replace( UPLOADER=join(FRAMEWORK_DIR, "tools", "espota.py"), UPLOADERFLAGS=["--debug", "--progress", "-i", "$UPLOAD_PORT"], - UPLOADCMD='"$PYTHONEXE" "$UPLOADER" $UPLOADERFLAGS -f $SOURCE', + UPLOADCMD=f'"{PYTHON_EXE}" "$UPLOADER" $UPLOADERFLAGS -f $SOURCE', ) if set(["uploadfs", "uploadfsota"]) & set(COMMAND_LINE_TARGETS): env.Append(UPLOADERFLAGS=["--spiffs"])