66 * the root directory of this source tree.
77 */
88
9- import * as core from '../core' ;
109import * as proc from '../proc' ;
1110import { callInstallerScript } from './get-pioarduino' ;
1211import fs from 'fs' ;
@@ -173,15 +172,15 @@ async function installUV() {
173172
174173/**
175174 * Install Python using UV package manager
176- * Uses UV to download and install Python from astral-sh/python-build-standalone
177- * Automatically handles platform detection, download, verification, and extraction
178- * @param {string } destinationDir - Target installation directory
175+ * Creates a virtual environment using `uv venv` with Python 3.13
176+ * This is simpler and more reliable than installing Python separately
177+ * @param {string } destinationDir - Target installation directory (venv path)
179178 * @param {string } pythonVersion - Python version to install (default: "3.13")
180- * @returns {Promise<string> } Path to installed Python directory
181- * @throws {Error } If UV installation or Python installation fails
179+ * @returns {Promise<string> } Path to installed Python venv directory
180+ * @throws {Error } If UV installation or venv creation fails
182181 */
183182async function installPythonWithUV ( destinationDir , pythonVersion = '3.13' ) {
184- log ( 'info' , `Installing Python ${ pythonVersion } using UV` ) ;
183+ log ( 'info' , `Creating Python ${ pythonVersion } venv using UV` ) ;
185184
186185 // Ensure UV is available, install if necessary
187186 if ( ! ( await isUVAvailable ( ) ) ) {
@@ -195,77 +194,60 @@ async function installPythonWithUV(destinationDir, pythonVersion = '3.13') {
195194 // Ignore cleanup errors (directory might not exist)
196195 }
197196
198- // Create destination directory structure
199- await fs . promises . mkdir ( destinationDir , { recursive : true } ) ;
200-
201197 try {
202- // Configure environment for UV Python installation
203- const env = {
204- ...process . env ,
205- UV_PYTHON_INSTALL_DIR : destinationDir ,
206- UV_CACHE_DIR : path . join ( core . getTmpDir ( ) , 'uv-cache' ) ,
207- } ;
208-
209- // Execute UV Python installation command
210- await execFile ( 'uv' , [ 'python' , 'install' , pythonVersion ] , {
211- env,
212- timeout : 300000 , // 5 minutes timeout for download and installation
213- cwd : destinationDir ,
214- } ) ;
198+ // Create venv directly using uv venv command with absolute path
199+ const absolutePath = path . resolve ( destinationDir ) ;
200+
201+ // Use --python-preference managed to allow UV to download Python if not found on system
202+ await execFile (
203+ 'uv' ,
204+ [
205+ 'venv' ,
206+ absolutePath ,
207+ '--python' ,
208+ pythonVersion ,
209+ '--python-preference' ,
210+ 'managed' ,
211+ ] ,
212+ {
213+ timeout : 300000 , // 5 minutes timeout for download and installation
214+ } ,
215+ ) ;
215216
216- // Verify that Python executable was successfully installed
217- await ensurePythonExeExists ( destinationDir , pythonVersion ) ;
217+ // Verify that Python executable was successfully created
218+ await ensurePythonExeExists ( destinationDir ) ;
218219
219- log ( 'info' , `Python ${ pythonVersion } installation completed : ${ destinationDir } ` ) ;
220+ log ( 'info' , `Python ${ pythonVersion } venv created successfully : ${ destinationDir } ` ) ;
220221 return destinationDir ;
221222 } catch ( err ) {
222- throw new Error ( `UV Python installation failed: ${ err . message } ` ) ;
223+ throw new Error ( `UV venv creation failed: ${ err . message } ` ) ;
223224 }
224225}
225226
226227/**
227- * Verify that Python executable exists in the installed directory
228- * Searches through common installation paths where UV might place Python executables
229- * @param {string } pythonDir - Directory containing Python installation
230- * @param {string } pythonVersion - Python version for path construction (default: "3.13")
228+ * Verify that Python executable exists in the venv directory
229+ * Checks the standard venv bin/Scripts directory for Python executable
230+ * @param {string } pythonDir - Directory containing Python venv
231231 * @returns {Promise<boolean> } True if executable exists and is accessible
232232 * @throws {Error } If no Python executable found in expected locations
233233 */
234- async function ensurePythonExeExists ( pythonDir , pythonVersion = '3.13' ) {
235- // UV typically installs to subdirectories organized by version
236- const possiblePaths = [
237- pythonDir , // Direct installation in target directory
238- path . join ( pythonDir , 'python' ) ,
239- path . join ( pythonDir , `python-${ pythonVersion } ` ) ,
240- path . join ( pythonDir , pythonVersion ) ,
241- ] ;
242-
234+ async function ensurePythonExeExists ( pythonDir ) {
235+ // Standard venv structure: bin/ on Unix, Scripts/ on Windows
236+ const binDir = proc . IS_WINDOWS
237+ ? path . join ( pythonDir , 'Scripts' )
238+ : path . join ( pythonDir , 'bin' ) ;
243239 const executables = proc . IS_WINDOWS ? [ 'python.exe' ] : [ 'python3' , 'python' ] ;
244240
245- for ( const basePath of possiblePaths ) {
246- // Check for executable in root of installation path
247- for ( const exeName of executables ) {
248- try {
249- await fs . promises . access ( path . join ( basePath , exeName ) ) ;
250- return true ;
251- } catch ( err ) {
252- // Continue trying other combinations
253- }
254- }
255-
256- // Check for executable in bin subdirectory (Unix-style layout)
257- const binDir = path . join ( basePath , 'bin' ) ;
258- for ( const exeName of executables ) {
259- try {
260- await fs . promises . access ( path . join ( binDir , exeName ) ) ;
261- return true ;
262- } catch ( err ) {
263- // Continue trying other combinations
264- }
241+ for ( const exeName of executables ) {
242+ try {
243+ await fs . promises . access ( path . join ( binDir , exeName ) ) ;
244+ return true ;
245+ } catch ( err ) {
246+ // Continue trying other executables
265247 }
266248 }
267249
268- throw new Error ( 'Python executable does not exist after UV installation !' ) ;
250+ throw new Error ( 'Python executable does not exist after venv creation !' ) ;
269251}
270252
271253/**
@@ -291,39 +273,31 @@ export async function installPortablePython(destinationDir) {
291273}
292274
293275/**
294- * Locate Python executable in an installed Python directory
295- * Searches through common locations where UV might install Python executables
296- * @param {string } pythonDir - Python installation directory to search
276+ * Locate Python executable in a venv directory
277+ * Uses standard venv structure (bin/ on Unix, Scripts/ on Windows)
278+ * @param {string } pythonDir - Python venv directory to search
297279 * @returns {Promise<string> } Full path to Python executable
298- * @throws {Error } If no executable found in the directory
280+ * @throws {Error } If no executable found in the venv
299281 */
300282function getPythonExecutablePath ( pythonDir ) {
283+ // Standard venv structure
284+ const binDir = proc . IS_WINDOWS
285+ ? path . join ( pythonDir , 'Scripts' )
286+ : path . join ( pythonDir , 'bin' ) ;
301287 const executables = proc . IS_WINDOWS ? [ 'python.exe' ] : [ 'python3' , 'python' ] ;
302288
303- // Check common locations where UV might install Python
304- const searchPaths = [
305- pythonDir ,
306- path . join ( pythonDir , 'bin' ) ,
307- path . join ( pythonDir , 'python' ) ,
308- path . join ( pythonDir , 'python-3.13' ) ,
309- path . join ( pythonDir , '3.13' ) ,
310- path . join ( pythonDir , '3.13' , 'bin' ) ,
311- ] ;
312-
313- for ( const searchPath of searchPaths ) {
314- for ( const exeName of executables ) {
315- const fullPath = path . join ( searchPath , exeName ) ;
316- try {
317- fs . accessSync ( fullPath , fs . constants . X_OK ) ;
318- log ( 'info' , `Found Python executable: ${ fullPath } ` ) ;
319- return fullPath ;
320- } catch ( err ) {
321- // Continue searching through all combinations
322- }
289+ for ( const exeName of executables ) {
290+ const fullPath = path . join ( binDir , exeName ) ;
291+ try {
292+ fs . accessSync ( fullPath , fs . constants . X_OK ) ;
293+ log ( 'info' , `Found Python executable: ${ fullPath } ` ) ;
294+ return fullPath ;
295+ } catch ( err ) {
296+ // Continue searching through all executables
323297 }
324298 }
325299
326- throw new Error ( `Could not find Python executable in ${ pythonDir } ` ) ;
300+ throw new Error ( `Could not find Python executable in venv ${ pythonDir } ` ) ;
327301}
328302
329303// Export utility functions for external use
0 commit comments