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,49 @@ 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,
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 ( 'uv' , [ 'venv' , absolutePath , '--python' , pythonVersion , '--python-preference' , 'managed' ] , {
212203 timeout : 300000 , // 5 minutes timeout for download and installation
213- cwd : destinationDir ,
214204 } ) ;
215205
216- // Verify that Python executable was successfully installed
217- await ensurePythonExeExists ( destinationDir , pythonVersion ) ;
206+ // Verify that Python executable was successfully created
207+ await ensurePythonExeExists ( destinationDir ) ;
218208
219- log ( 'info' , `Python ${ pythonVersion } installation completed : ${ destinationDir } ` ) ;
209+ log ( 'info' , `Python ${ pythonVersion } venv created successfully : ${ destinationDir } ` ) ;
220210 return destinationDir ;
221211 } catch ( err ) {
222- throw new Error ( `UV Python installation failed: ${ err . message } ` ) ;
212+ throw new Error ( `UV venv creation failed: ${ err . message } ` ) ;
223213 }
224214}
225215
226216/**
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")
217+ * Verify that Python executable exists in the venv directory
218+ * Checks the standard venv bin/Scripts directory for Python executable
219+ * @param {string } pythonDir - Directory containing Python venv
231220 * @returns {Promise<boolean> } True if executable exists and is accessible
232221 * @throws {Error } If no Python executable found in expected locations
233222 */
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-
223+ async function ensurePythonExeExists ( pythonDir ) {
224+ // Standard venv structure: bin/ on Unix, Scripts/ on Windows
225+ const binDir = proc . IS_WINDOWS
226+ ? path . join ( pythonDir , 'Scripts' )
227+ : path . join ( pythonDir , 'bin' ) ;
243228 const executables = proc . IS_WINDOWS ? [ 'python.exe' ] : [ 'python3' , 'python' ] ;
244229
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- }
230+ for ( const exeName of executables ) {
231+ try {
232+ await fs . promises . access ( path . join ( binDir , exeName ) ) ;
233+ return true ;
234+ } catch ( err ) {
235+ // Continue trying other executables
265236 }
266237 }
267238
268- throw new Error ( 'Python executable does not exist after UV installation !' ) ;
239+ throw new Error ( 'Python executable does not exist after venv creation !' ) ;
269240}
270241
271242/**
@@ -291,39 +262,31 @@ export async function installPortablePython(destinationDir) {
291262}
292263
293264/**
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
265+ * Locate Python executable in a venv directory
266+ * Uses standard venv structure (bin/ on Unix, Scripts/ on Windows)
267+ * @param {string } pythonDir - Python venv directory to search
297268 * @returns {Promise<string> } Full path to Python executable
298- * @throws {Error } If no executable found in the directory
269+ * @throws {Error } If no executable found in the venv
299270 */
300271function getPythonExecutablePath ( pythonDir ) {
272+ // Standard venv structure
273+ const binDir = proc . IS_WINDOWS
274+ ? path . join ( pythonDir , 'Scripts' )
275+ : path . join ( pythonDir , 'bin' ) ;
301276 const executables = proc . IS_WINDOWS ? [ 'python.exe' ] : [ 'python3' , 'python' ] ;
302277
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- }
278+ for ( const exeName of executables ) {
279+ const fullPath = path . join ( binDir , exeName ) ;
280+ try {
281+ fs . accessSync ( fullPath , fs . constants . X_OK ) ;
282+ log ( 'info' , `Found Python executable: ${ fullPath } ` ) ;
283+ return fullPath ;
284+ } catch ( err ) {
285+ // Continue searching through all executables
323286 }
324287 }
325288
326- throw new Error ( `Could not find Python executable in ${ pythonDir } ` ) ;
289+ throw new Error ( `Could not find Python executable in venv ${ pythonDir } ` ) ;
327290}
328291
329292// Export utility functions for external use
0 commit comments