|
#!/usr/bin/env pwsh |
|
|
|
<# |
|
.SYNOPSIS |
|
Installs Thonny IDE, py5mode plugin, and py5 package on Windows. |
|
|
|
.DESCRIPTION |
|
This script installs Thonny IDE and the py5 coding mode plugin within a |
|
dedicated Python virtual environment. It handles finding a suitable Python |
|
installation, creating the virtual environment via its venv module, installing |
|
necessary packages via pip, and setting up a shortcut for launching Thonny. |
|
|
|
REQUIREMENTS: |
|
* Python 3.10+ installed and accessible via the system's PATH. |
|
* pip + venv (usually installed with Python). |
|
* Internet connection to download packages from PyPI. |
|
* PowerShell terminal to run this script. |
|
|
|
USAGE: |
|
.\thonny-installer.ps1 [-VenvDir <string>] [-PythonPath <string>] |
|
[-Help] [-Version] |
|
|
|
.PARAMETER VenvDir |
|
Directory to install the virtual environment. |
|
.PARAMETER PythonPath |
|
Path or name of an existing Python binary. |
|
.PARAMETER Help |
|
Show instructions. |
|
.PARAMETER Version |
|
Show script version. |
|
|
|
.OUTPUTS |
|
String. Status messages and paths to created resources. |
|
|
|
.EXAMPLE |
|
.\thonny-installer.ps1 |
|
# Installs using default prompts for Python path and venv directory. |
|
|
|
.EXAMPLE |
|
.\thonny-installer.ps1 "C:\MyVenvs\Thonny" |
|
# Installs inside specified target folder and prompts for Python path or name. |
|
|
|
.EXAMPLE |
|
.\thonny-installer.ps1 -PythonPath python3.10 |
|
# Uses python3.10 from PATH and prompts for venv target directory. |
|
|
|
.EXAMPLE |
|
.\thonny-installer.ps1 "C:\MyVenvs\Thonny" "C:\Python310\python.exe" |
|
# Installs inside specified target folder using specified Python path. |
|
|
|
.NOTES |
|
NAME: Thonny + py5mode Windows Installer |
|
AUTHOR: GoToLoop |
|
VERSION: 1.0.2 |
|
CREATED: 2025-Oct-27 |
|
UPDATED: 2026-Mar-08 |
|
LICENSE: MIT |
|
|
|
- The script searches for 'python.exe' or 'python3.exe' in the system PATH. |
|
- Prompts the user to choose a Python interpreter or type in a path for one. |
|
- The virtual environment defaults to '$HOME\Apps\Thonny'. |
|
- Creates a '.bat' launcher script and a desktop shortcut (.lnk) for Thonny. |
|
|
|
.LINK |
|
https://Discourse.Processing.org/c/28 (Forum) |
|
.LINK |
|
https://GitHub.com/py5coding/thonny-py5mode/issues (Support) |
|
.LINK |
|
https://Gist.GitHub.com/GoToLoop/246a31d437aaa8c6eadb7f7186544e0f (Download) |
|
#> |
|
|
|
[CmdletBinding()] |
|
param( |
|
[Parameter(HelpMessage = "Target folder for the Python virtual environment.")] |
|
[Alias('venv')]$VenvDir = '', |
|
|
|
[Parameter(HelpMessage = "Path or command name for the Python executable.")] |
|
[Alias('p', 'python')]$PythonPath = '', |
|
|
|
[Parameter(HelpMessage = "Display this help message and exit.")] |
|
[Alias('h', '?')][switch]$Help, |
|
|
|
[Parameter(HelpMessage = "Display script version and exit.")] |
|
[Alias('v')][switch]$Version |
|
) |
|
|
|
# --- Configuration --- |
|
$ScriptVersion = "1.0.2" |
|
$DefaultVenvDir = Join-Path $HOME "Apps" "Thonny" |
|
$MinPy = "3.10" |
|
$MinVer = [version]$MinPy |
|
$PyExe = @("python", "python.exe")[$IsWindows] |
|
$ThonnyExe = @("thonny", "thonny.exe")[$IsWindows] |
|
$ErrorActionPreference = "Stop" # Stop on errors |
|
|
|
# --- Helper Functions --- |
|
|
|
# Function to write errors and exit |
|
function ThrowError([string]$message) { |
|
Write-Host "[Error] $message" -ForegroundColor Red; exit 1 |
|
} |
|
|
|
# Function to write warnings |
|
function WriteWarning([string]$message) { |
|
Write-Host "[WARN] $message" -ForegroundColor Yellow |
|
} |
|
|
|
# Function to write success messages |
|
function WriteSuccess([string]$message) { |
|
Write-Host "[OK] $message" -ForegroundColor Green |
|
} |
|
|
|
# Function to write informational messages |
|
function WriteInfo([string]$message) { |
|
Write-Host "[INFO] $message" -ForegroundColor Cyan |
|
} |
|
|
|
# Function to execute a command, stopping on error if needed |
|
function Start-Command([string]$cmd, [string[]]$list, [string]$err) { |
|
<# |
|
.SYNOPSIS |
|
Executes a command and throws an error if it fails. |
|
|
|
.DESCRIPTION |
|
Runs an external command with specified arguments using Start-Process, |
|
waits for completion, and checks the exit code. Throws a custom error |
|
on non-zero exit codes. Provides success feedback via Write-Host. |
|
|
|
.PARAMETER cmd |
|
The executable file path or command to run. |
|
|
|
.PARAMETER list |
|
Array of arguments to pass to the command. |
|
|
|
.PARAMETER err |
|
Custom error message prefix for the thrown exception. |
|
|
|
.EXAMPLE |
|
Start-Command "python" "-m pip install thonny" "Thonny install failed" |
|
|
|
.EXAMPLE |
|
Start-Command "gh" @("repo", "clone", "account/repo") "Git clone failed" |
|
#> |
|
$process = Start-Process ` |
|
-FilePath $cmd -ArgumentList $list ` |
|
-Wait -PassThru -NoNewWindow |
|
|
|
if ($process.ExitCode -ne 0) { |
|
ThrowError "$err (Command: $cmd $list, Exit Code: $($process.ExitCode))" |
|
} |
|
|
|
Write-Host " -> '$cmd $list' completed successfully." |
|
} |
|
|
|
# Function to create a Windows shortcut (.lnk) |
|
function New-Shortcut { |
|
param( |
|
[string]$ShortcutPath, [string]$TargetFile, |
|
$Arguments = "", $WorkingDirectory = "", |
|
$IconLocation = "", $Description = "Launch Thonny IDE" |
|
) |
|
try { |
|
$Shell = New-Object -ComObject WScript.Shell |
|
$Shortcut = $Shell.CreateShortcut($ShortcutPath) |
|
|
|
$Shortcut.TargetPath = $TargetFile |
|
$Shortcut.Arguments = $Arguments |
|
$Shortcut.Description = $Description |
|
|
|
if ($WorkingDirectory) { $Shortcut.WorkingDirectory = $WorkingDirectory } |
|
|
|
# Try to find an icon associated with the target (Thonny executable) |
|
if ($IconLocation) { $Shortcut.IconLocation = $IconLocation } |
|
elseif (Test-Path $TargetFile) { $Shortcut.IconLocation = "$TargetFile,0" } |
|
|
|
$Shortcut.Save() |
|
WriteInfo " -> Created shortcut: $ShortcutPath" |
|
} catch { |
|
$msg = "Failed to create shortcut '$ShortcutPath': " |
|
return WriteWarning $msg + $_.Exception.Message |
|
} |
|
|
|
# Make shortcut accessible (optional, usually works by default) |
|
(Get-Item $ShortcutPath).IsNetwork = $false |
|
(Get-Item $ShortcutPath).Attributes = 'Normal' |
|
|
|
# Mark as trusted if possible (difficult to automate reliably cross-version) |
|
# Try using a .NET method if available, otherwise skip. |
|
try { |
|
Add-Type -AssemblyName System.Management.Automation |
|
New-Object System.IO.FileInfo($ShortcutPath) |
|
} catch { |
|
$msg = "Could not add type for shortcut trust operation: " |
|
Write-Warning $msg + $_.Exception.Message |
|
} |
|
} |
|
|
|
# --- Argument Parsing & Initial Setup --- |
|
|
|
Write-Host "`n🚀 Thonny + py5mode Windows Installer 🚀" |
|
Write-Host "==================================================" |
|
|
|
# Handle -Help and -Version flags and then exit |
|
# Display help using PowerShell's comment-based help |
|
if ($Help) { Get-Help $MyInvocation.MyCommand.Path -Full } |
|
if ($Version) { Write-Host "`nInstaller Version: $ScriptVersion`n" } |
|
if ($Help -or $Version) { exit 0 } |
|
|
|
# --- Check Network Connectivity --- |
|
Write-Host "Checking internet connection to PyPI..." |
|
if (-not (Test-Connection -ComputerName pypi.org -Count 1 -Quiet)) { |
|
WriteWarning "pypi.org seems unreachable. Package installation might fail." |
|
} else { WriteInfo " -> PyPI is reachable." } |
|
|
|
# --- Python Discovery --- |
|
$pythonExe = $null |
|
$validPythons = @{} # Hashtable to store path -> version string |
|
$pythonCandidates = @() |
|
|
|
Write-Host "`nSearching for Python installations..." |
|
|
|
# 1. Check provided path argument |
|
if (-not ([string]::IsNullOrWhiteSpace($PythonPath))) { |
|
$potentialPath = Resolve-Path -Path $PythonPath -ErrorAction SilentlyContinue |
|
|
|
if ($potentialPath) { |
|
$pythonExe = $potentialPath.Path |
|
Write-Host " -> Using provided Python path: $pythonExe" |
|
} elseif (Get-Command $PythonPath -ErrorAction SilentlyContinue) { |
|
$pythonExe = $PythonPath # Assume it's in PATH |
|
Write-Host " -> Using Python command from PATH: $pythonExe" |
|
} else { |
|
WriteWarning "Provided Python path/command '$PythonPath' not found." |
|
} |
|
} |
|
|
|
# 2. If not found yet, search common locations and PATH |
|
if (-not $pythonExe) { |
|
# Get Python executables from PATH |
|
$pathPythons = (Get-Command python, python3 -ErrorAction SilentlyContinue). |
|
Source |
|
|
|
# Add common Windows install locations |
|
if ($IsWindows) { |
|
$commonLocations = @( |
|
"$env:LocalAppData\Programs\Python\Python*", |
|
"$env:ProgramFiles\Python*", |
|
"$env:ProgramFiles(x86)\Python*" |
|
) |
|
|
|
foreach ($loc in $commonLocations) { |
|
$pathCandidates = Get-ChildItem -Path $loc -Filter $PyExe -Recurse ` |
|
-ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName |
|
|
|
$pathPythons += $pathCandidates |
|
} |
|
|
|
# Add Windows Python launcher |
|
$pyLauncher = (Get-Command py -ErrorAction SilentlyContinue).Source |
|
if ($pyLauncher) { |
|
$pathPythons += $pyLauncher |
|
} |
|
|
|
# Add pyenv-win if it exists |
|
if ($env:PYENV_HOME) { |
|
$pyenvPythons = Get-ChildItem -Path (Join-Path $env:PYENV_HOME "version" |
|
) -Filter $PyExe -Recurse -ErrorAction SilentlyContinue | |
|
Select-Object -ExpandProperty FullName |
|
|
|
$pathPythons += $pyenvPythons |
|
} |
|
|
|
# Collect uv-installed Python binaries |
|
$uvFolder = Join-Path $env:LocalAppData "uv\python" |
|
|
|
$uvPythons = Get-ChildItem -Path $uvFolder -Recurse -File -ErrorAction ` |
|
SilentlyContinue | Where-Object { $_.FullName -match |
|
"\\bin\\python[0-9\.]*\.exe$" } | |
|
Select-Object -ExpandProperty FullName |
|
|
|
$pathPythons += $uvPythons |
|
} else { |
|
# On Linux/MacOS/BSD, check typical install paths |
|
$commonLocations = @( |
|
"/usr/bin", |
|
"/usr/local/bin", |
|
"/opt/homebrew/bin", # MacOS Homebrew (Apple Silicon) |
|
"/opt/local/bin" # MacPorts |
|
) |
|
|
|
foreach ($loc in $commonLocations) { |
|
$pathCandidates = Get-ChildItem -Path $loc -Filter "python*" ` |
|
-ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName |
|
|
|
$pathPythons += $pathCandidates |
|
} |
|
|
|
# Add pyenv if it exists (PYENV_ROOT first, fallback to ~/.pyenv) |
|
$pyenv = if ($env:PYENV_ROOT) { $env:PYENV_ROOT } else { "$HOME/.pyenv" } |
|
|
|
# Collect pyenv-installed Python binaries |
|
$pyenvPythons = Get-ChildItem -Path (Join-Path $pyenv "versions" |
|
) -Recurse -File -ErrorAction SilentlyContinue | Where-Object { |
|
$_.FullName -match "/bin/python[0-9\.]*$" |
|
} | Select-Object -ExpandProperty FullName |
|
|
|
$pathPythons += $pyenvPythons |
|
|
|
# Add uv if it exists (LOCAL_SHARE first, fallback to ~/.local/share) |
|
$uv = if ($env:LOCAL_SHARE) {$env:LOCAL_SHARE} else {"$HOME/.local/share"} |
|
$uvFolder = Join-Path $uv "uv/python" |
|
|
|
# Collect uv-installed Python binaries |
|
$uvPythons = Get-ChildItem -Path $uvFolder -Recurse -File -ErrorAction ` |
|
SilentlyContinue | Where-Object { |
|
$_.FullName -match "/bin/python[0-9\.]*$" |
|
} | Select-Object -ExpandProperty FullName |
|
|
|
$pathPythons += $uvPythons |
|
} |
|
|
|
# Deduplicate and validate |
|
$pythonCandidates = $pathPythons | Sort-Object -Unique |
|
|
|
foreach ($py in $pythonCandidates) { |
|
try { |
|
$versionInfo = & "$py" -V 2>&1 |
|
if ($LASTEXITCODE -eq 0 -and $versionInfo) { |
|
# Extract version string (e.g., "Python 3.10.11") |
|
if ($versionInfo.Trim() -match 'Python\s+([\d\.]+)') { |
|
$validPythons[$py] = $matches[1] |
|
} |
|
} |
|
} catch { |
|
# Ignore errors for invalid executables |
|
} |
|
} |
|
|
|
# Select the best candidate if multiple found |
|
if ($validPythons.Count -gt 0) { |
|
Write-Host "Found potential Python interpreters:" |
|
|
|
# Sort by actual version number, filtering out versions less than 3.10 |
|
$sortedPythonPaths = $validPythons.GetEnumerator() | |
|
Where-Object { [version]$_.Value -ge $MinVer } | |
|
Sort-Object { [version]$_.Value } | |
|
Select-Object -ExpandProperty Key |
|
|
|
$i = 0 |
|
foreach ($pyPath in $sortedPythonPaths) { |
|
Write-Host " [$i] $($validPythons[$pyPath]) ($pyPath)" |
|
$i++ |
|
} |
|
|
|
# Prefer python3 if available (Unix default) |
|
$defaultPython = $sortedPythonPaths | |
|
Where-Object { $_ -match "python3(\.exe)?$" } | |
|
Sort-Object { [version]$validPythons[$_] } | |
|
Select-Object -Last 1 |
|
|
|
if (-not $defaultPython) { ... else python.exe (Windows default) |
|
$defaultPython = $sortedPythonPaths | |
|
Where-Object { $_ -match "python\.exe$" } | |
|
Sort-Object { [version]$validPythons[$_] } | |
|
Select-Object -Last 1 |
|
} |
|
|
|
if (-not $defaultPython) { ... else plain python (Unix fallback) |
|
$defaultPython = $sortedPythonPaths | |
|
Where-Object { $_ -match "python$" } | |
|
Sort-Object { [version]$validPythons[$_] } | |
|
Select-Object -Last 1 |
|
} |
|
|
|
if (-not $defaultPython) { ... else take highest version found |
|
$defaultPython = $sortedPythonPaths | Select-Object -Last 1 |
|
} |
|
|
|
$msg = "`nEnter the index number, full path, or command " |
|
$msg += "name for the Python executable`n(default: '" |
|
$selectedPython = Read-Host $msg$defaultPython"' or index 0)`n" |
|
|
|
if ([string]::IsNullOrWhiteSpace($selectedPython)) { |
|
$selectedPython = $defaultPython |
|
} |
|
|
|
if ($selectedPython -match '^\d+$') { |
|
$index = [int]$selectedPython |
|
if ($index -ge 0 -and $index -lt $sortedPythonPaths.Count) { |
|
$pythonExe = $sortedPythonPaths[$index] |
|
} else { |
|
ThrowError "Invalid Python index selected." |
|
} |
|
# If it starts with drive letter, it's likely a path |
|
} elseif ($selectedPython -match '^[a-zA-Z]:\\') { |
|
$potentialPath = Resolve-Path -Path $selectedPython ` |
|
-ErrorAction SilentlyContinue |
|
|
|
if ($potentialPath -and (Test-Path $potentialPath.Path)) { |
|
$pythonExe = $potentialPath.Path |
|
} else { |
|
ThrowError "Invalid Python path provided: $selectedPython" |
|
} |
|
} elseif (Get-Command $selectedPython -ErrorAction SilentlyContinue) { |
|
$pythonExe = $selectedPython # Command from PATH |
|
} else { |
|
ThrowError "Invalid Python selection: $selectedPython" |
|
} |
|
} else { |
|
$msg = "No suitable Python interpreter ($($MinPy)+) found.`n" |
|
ThrowError $msg"Please install Python and ensure it's in your PATH." |
|
} |
|
} |
|
|
|
# --- Final Python Validation --- |
|
if (-not $pythonExe) { |
|
ThrowError "Python executable is not set." |
|
} |
|
|
|
Write-Host "`nUsing Python: $pythonExe" |
|
try { |
|
$pyVersionCheck = & "$pythonExe" -V 2>&1 |
|
if ($LASTEXITCODE -ne 0 -or -not $pyVersionCheck) { |
|
ThrowError "Selected Python '$pythonExe' is invalid or returned an error." |
|
} |
|
Write-Host " -> Python Version Check: $($pyVersionCheck.Trim())" |
|
|
|
# Extract version number (e.g. "3.10.1") |
|
if ($pyVersionCheck -match 'Python ([\d.]+)') { |
|
$pyVersion = [version]$matches[1] |
|
|
|
if ($pyVersion -lt $MinVer) { |
|
WriteWarning "Python version $pyVersion is older than recommended $MinPy." |
|
} else { |
|
WriteSuccess "Python version $pyVersion meets min. requirement ($MinPy)." |
|
} |
|
} else { |
|
WriteWarning "Could not parse Python version from: $pyVersionCheck" |
|
} |
|
} catch { |
|
ThrowError "Failed to execute Python: $($_.Exception.Message)" |
|
} |
|
|
|
# --- Virtual Environment Directory --- |
|
if ([string]::IsNullOrWhiteSpace($VenvDir)) { |
|
$VenvDir = $DefaultVenvDir |
|
$msg = "`nEnter the virtual environment directory path " |
|
$msg += "(default: '$VenvDir')`nPress Enter to accept, or type a new path" |
|
$confirm = Read-Host $msg |
|
if (-not [string]::IsNullOrWhiteSpace($confirm)) {$VenvDir = $confirm } |
|
} |
|
|
|
# Normalize path (remove trailing slash unless it's the root) |
|
if ($VenvDir -ne "\" -and $VenvDir.EndsWith("\")) { |
|
$VenvDir = $VenvDir.Substring(0, $VenvDir.Length - 1) |
|
} |
|
|
|
# Expand ~ to the user's home directory ($HOME) |
|
# and remove any trailing slashes (both '\' and '/') |
|
$VenvDir = $VenvDir.Replace("~", $HOME).TrimEnd('\','/') |
|
|
|
# If trimming left the path empty, reset it to user's home folder |
|
# so it can be flagged as invalid later |
|
if ([string]::IsNullOrEmpty($VenvDir)) { |
|
$VenvDir = $HOME |
|
} |
|
|
|
# Get the parent directory of the virtual environment path |
|
# This will be used to detect if the chosen folder is directly under $HOME |
|
$parentDir = Split-Path -Path $VenvDir -Parent |
|
|
|
# Prevent installing directly into root or the user's home directory |
|
# If the target is root, home, or directly under home, throw an error |
|
if ($VenvDir -eq $HOME -or $parentDir -eq $HOME) { |
|
$msg = "Installation target '$VenvDir' is too close to the root or " |
|
ThrowError $msg"home directory. Choose a subfolder." |
|
} |
|
|
|
# If all checks pass, display the normalized virtual environment directory |
|
Write-Host "Virtual environment directory: $VenvDir" |
|
|
|
# Check if target exists |
|
if (Test-Path -Path $VenvDir) { |
|
$overwrite = Read-Host "⚠️ Folder '$VenvDir' already exists. Overwrite? (y/N)" |
|
if ($overwrite -notmatch '^[YySsOo]$') { |
|
Write-Host "❌ Aborting..." |
|
exit 1 |
|
} |
|
Write-Host " -> Removing existing directory..." |
|
try { |
|
Remove-Item -Path $VenvDir -Recurse -Force |
|
} catch { |
|
$msg = "Failed to remove existing directory '$VenvDir': " |
|
ThrowError $msg"$($_.Exception.Message)" |
|
} |
|
} |
|
|
|
# --- Create Virtual Environment --- |
|
try { |
|
# Ensure parent directory exists |
|
$parentVenvDir = Split-Path -Path $VenvDir -Parent |
|
if (-not (Test-Path -Path $parentVenvDir)) { |
|
New-Item -Path $parentVenvDir -ItemType Directory -Force |
|
} |
|
|
|
$venvArgs = "-m venv --copies `"$VenvDir`"" |
|
$err = "Failed to create virtual environment." |
|
|
|
Write-Host "Creating virtual environment using '$pythonExe' in '$VenvDir'..." |
|
Start-Command $pythonExe $venvArgs $err # Create venv |
|
} catch { |
|
$err = "An error occurred during virtual environment creation: " |
|
ThrowError $err"$($_.Exception.Message)" |
|
} |
|
|
|
# --- Install Packages --- |
|
$venvScriptsDir = Join-Path $VenvDir @("bin", "Scripts")[$IsWindows] |
|
$pythonExe = Join-Path $venvScriptsDir @("python", "python.exe")[$IsWindows] |
|
|
|
$pipArgs = "-m pip install -U pip" |
|
$err = "Failed to upgrade pip." |
|
Write-Host "Upgrading pip in the virtual environment..." |
|
Start-Command $pythonExe $pipArgs $err |
|
|
|
$packArgs = "-m pip install thonny thonny-py5mode[extras] pip-review" |
|
$err = "Failed to install packages." |
|
Write-Host "Installing Thonny, thonny-py5mode, and py5..." |
|
Start-Command $pythonExe $packArgs $err |
|
|
|
# --- Create Launchers --- |
|
Write-Host "Creating launchers..." |
|
|
|
# 1. Create BAT launcher script (run-thonny.bat) |
|
$batPath = Join-Path $VenvDir "run-thonny.bat" |
|
# $logFolder = Join-Path $VenvDir ".thonny" |
|
# $logFile = Join-Path $logFolder "thonny.log" |
|
$pyExeRelative = Join-Path "Scripts" $PyExe # Relative path within venv |
|
|
|
$batContent = @" |
|
@echo off |
|
setlocal |
|
|
|
REM Get the directory containing this batch script |
|
set SCRIPT_FOLDER=%~dp0 |
|
REM Remove trailing backslash if present |
|
if "%SCRIPT_FOLDER:~-1%"=="\" set SCRIPT_FOLDER=%SCRIPT_FOLDER:~0,-1% |
|
|
|
set PYTHON_EXE="%SCRIPT_FOLDER%\$pyExeRelative" |
|
set LOG_FOLDER="%SCRIPT_FOLDER%\.thonny" |
|
set LOG_FILE="%LOG_FOLDER%\thonny.log" |
|
|
|
REM Create log directory if it doesn't exist |
|
if not exist "%LOG_FOLDER%" mkdir "%LOG_FOLDER%" |
|
|
|
echo Launching Thonny from virtual environment... |
|
echo Logging output to %LOG_FILE% |
|
REM Use start /B to run in background w/o a new window, redirect stdout/stderr |
|
start "" /B "%PYTHON_EXE%" -m thonny > "%LOG_FILE%" 2>&1 |
|
|
|
endlocal |
|
"@ |
|
|
|
try { |
|
Set-Content -Path $batPath -Value $batContent -Encoding UTF8 |
|
# Make executable conceptually (Windows doesn't have chmod for this) |
|
Unblock-File -Path $batPath |
|
Write-Host " -> Created batch launcher: $batPath" |
|
} catch { |
|
$err = "Failed to create batch launcher '$batPath': " |
|
WriteWarning $err"$($_.Exception.Message)" |
|
} |
|
|
|
# --- Create PS1 launcher script (run-thonny.ps1) --- |
|
Write-Host "Creating PowerShell launcher..." |
|
|
|
$ps1Path = Join-Path $VenvDir "run-thonny.ps1" |
|
$pyExeRelative = Join-Path @("bin", "Scripts")[$IsWindows] $PyExe |
|
|
|
$ps1Content = @' |
|
#!/usr/bin/env pwsh |
|
|
|
# PowerShell launcher for Thonny |
|
|
|
$SCRIPT_FOLDER = Split-Path -Parent $MyInvocation.MyCommand.Definition |
|
|
|
if ($IsWindows) { |
|
$PYTHON_EXE = Join-Path $SCRIPT_FOLDER "Scripts\python.exe" |
|
} else { |
|
$PYTHON_EXE = Join-Path $SCRIPT_FOLDER "bin/python" |
|
} |
|
|
|
$LOG_FOLDER = Join-Path $SCRIPT_FOLDER ".thonny" |
|
$LOG_FILE = Join-Path $LOG_FOLDER "thonny.log" |
|
$ERR_FILE = Join-Path $LOG_FOLDER "thonny.err" |
|
|
|
if (-not (Test-Path $LOG_FOLDER)) { |
|
New-Item -Path $LOG_FOLDER -ItemType Directory -Force | Out-Null |
|
} |
|
|
|
Write-Host "Launching Thonny from virtual environment..." |
|
Write-Host "Logging output to $LOG_FILE" |
|
|
|
if ($IsWindows) { |
|
& $PYTHON_EXE -m thonny *>$LOG_FILE |
|
} else { |
|
Start-Process $PYTHON_EXE '-m', 'thonny' ` |
|
-RedirectStandardOutput $LOG_FILE -RedirectStandardError $ERR_FILE |
|
} |
|
'@ |
|
|
|
try { |
|
Set-Content -Path $ps1Path -Value $ps1Content -Encoding UTF8 |
|
if (-not $IsWindows) { & chmod +x $ps1Path } |
|
Write-Host " -> Created PowerShell launcher: $ps1Path" |
|
} catch { |
|
$err = "Failed to create PowerShell launcher '$ps1Path': " |
|
WriteWarning $err"$($_.Exception.Message)" |
|
} |
|
|
|
# 2. Create Desktop Shortcut |
|
$thonnyExePath = Join-Path $venvScriptsDir $ThonnyExe |
|
$desktopPath = Join-Path $HOME "Desktop" |
|
$shortcutName = "Thonny.lnk" |
|
$shortcutPath = Join-Path $desktopPath $shortcutName |
|
|
|
if (Test-Path $thonnyExePath) { |
|
Write-Host " -> Creating desktop shortcut '$shortcutPath'..." |
|
try { |
|
# Target: The Thonny executable inside the venv |
|
# Arguments: (Leave empty for now, Thonny handles its own launch) |
|
# Working Directory: Set to the venv Scripts dir so Thonny can find its |
|
# libs easily |
|
New-Shortcut -TargetFile $thonnyExePath -ShortcutFile $shortcutPath ` |
|
-WorkingDirectory $venvScriptsDir |
|
Write-Host " -> Shortcut created on Desktop." |
|
|
|
# Copy the shortcut into the venv dir as well (like the bash script did) |
|
$venvShortcutPath = Join-Path $VenvDir $shortcutName |
|
Copy-Item -Path $shortcutPath -Destination $venvShortcutPath -Force |
|
Write-Host " -> Copied shortcut to venv directory." |
|
} catch { |
|
$msg = "Failed to create Thonny desktop shortcut: " |
|
WriteWarning $msg"$($_.Exception.Message)" |
|
} |
|
} else { |
|
$msg = "Thonny executable not found at '$thonnyExePath'. " |
|
WriteWarning $msg"Cannot create shortcut." |
|
} |
|
|
|
# --- Final Output --- |
|
|
|
# List packages (optional) |
|
$pipListArgs = "-m pip list" |
|
$err = "Failed to list installed packages." |
|
Write-Host "`nInstalled Packages:" |
|
Start-Command $pythonExe $pipListArgs $err |
|
|
|
# Show package details (optional) |
|
$pipShowArgs = "-m pip show thonny thonny-py5mode py5" |
|
$err = "Failed to get package details." |
|
Write-Host "`nPackage Details:" |
|
Start-Command $pythonExe $pipShowArgs $err |
|
|
|
Write-Host "`n==================================================" |
|
WriteSuccess "Setup Complete!" |
|
Write-Host "Thonny IDE and py5 mode are installed in:" |
|
Write-Host " $VenvDir" |
|
Write-Host "" |
|
Write-Host "You can launch Thonny using:" |
|
Write-Host " 1. The '$shortcutName' shortcut on your Desktop." |
|
Write-Host " 2. Running the '$batPath' script from '$VenvDir'." |
|
Write-Host "" |
|
Write-Host "To activate the virtual environment in PowerShell for manual use:" |
|
$activateScriptPath = Join-Path $venvScriptsDir "Activate.ps1" |
|
Write-Host " & '$activateScriptPath'" |
|
Write-Host "" |
|
Write-Host "To activate the virtual environment in Command Prompt (cmd.exe):" |
|
$activateBatPath = Join-Path $venvScriptsDir "activate.bat" |
|
Write-Host " '$activateBatPath'" |
|
Write-Host "==================================================" |
|
|
|
exit 0 |