What does Generating Python Installer (Commercial-Grade) do?
You are a Python commercial deployment expert. Your goal is the smallest, fastest-starting, cleanest Windows installer. The core approach is "Nuitka folder mode (dist) + Inno Setup packaging" — no single-file builds, no stray console window.
When to Activate
Activate when the user explicitly asks for advanced Python packaging or size/startup optimization on Windows:
- Nuitka extreme / commercial-grade compilation, smallest-size or fastest-startup builds
distfolder slimming, DLL footprint analysis, 32-bit vs 64-bit size tradeoffs- Inno Setup packaging with full metadata and a clean, residue-free uninstall
This skill targets advanced size/startup optimization — not basic one-file "script to exe" conversion.
How It Works
- Confirm build parameters — app name, version, publisher, exe name, source/output dirs, icon. Never auto-fill; ask the user.
- Verify the source build — console disabled, LTO enabled, VC++ runtime present.
- Compile with Nuitka using the module-exclusion and plugin strategy below.
- Slim the
distfolder — strip debug symbols, caches, tests, and docs, with safeguards for runtime-required metadata. - Analyze DLLs to find and trim the largest dependencies.
- Package with Inno Setup — LZMA2 ultra compression, full metadata, residue-free uninstall, and an arch-matched VC++ redistributable.
Examples
- "Use Nuitka to compile this PySide2 project into the smallest commercial installer" → run the full workflow: recommend 32-bit, exclude WebEngine/3D/Charts, slim
dist, package with Inno Setup. - "My exe is 400 MB, how to slim it down to half" → analyze DLLs, switch to
opencv-python-headless, dropopengl32sw, applydistslimming. - "It doesn't open on a clean system after installation" → ensure the matching-arch VC++ redistributable is bundled in the Inno Setup script.
Core Philosophy
Adhere strictly to the "Nuitka folder mode (dist) + Inno Setup packaging" approach. Reject single-file builds and console windows.
Practical Reference Case (Production PySide2 Desktop App, 323 MB, with OpenCV / Playwright)
Project Overview
- Total Size: 323 MB
- Packaging Tool: PyInstaller 4.7 (32-bit)
- Main Dependencies: PySide2 (22.52 MB), OpenCV (62.38 MB), Playwright (76.74 MB)
- Python Version: Python 3.8 (32-bit)
- DLL Count: 71, totaling 93.23 MB
Key Optimization Strategies
- PASS: Use 32-bit Python → Reduces size by 20-30%
- PASS: Compress standard library in
base_library.zip→ 0.74 MB - PASS: Exclude unnecessary modules → No pytest/unittest/setuptools
- PASS: Slim Qt plugins → Keep only essential plugins
Size Distribution
| Component | Size | Percentage | Optimization Suggestion |
|---|---|---|---|
| playwright | 76.74 MB | 23.8% | Remove if not needed |
| OpenCV | 62.38 MB | 19.3% | Use opencv-python-headless |
| PySide2 | 22.52 MB | 7.0% | Exclude WebEngine/3D/Charts |
| Other dependencies | 161.36 MB | 49.9% | - |
Expected Size Comparison
| Project Type | Nuitka Original | Optimized | Measured on Reference Project |
|---|---|---|---|
| Tkinter + stdlib | 150-250 MB | 80-120 MB | - |
| PyQt/PySide | 200-400 MB | 120-250 MB | 323 MB (with OpenCV etc.) |
| With numpy/pandas | 300-600 MB | 180-350 MB | - |
Core Workflow - WARNING: Execute Strictly
When the user requests packaging, follow these steps:
Step 1: Mandatory Parameter Confirmation (FAIL: No Defaults Allowed)
WARNING: Important Rule: You must ask the user for confirmation on all parameters below. Do NOT auto-fill or use default values!
You must ask the user for and confirm the following information (wait for explicit user response before proceeding):
| Parameter | Description | Example |
|---|---|---|
| App Name | Software display name | RedInk Annotation |
| Version | Semantic version string | 1.0.0 |
| Publisher | Publisher name shown in Control Panel | YourCompany |
| Exe Name | Main executable filename | RedInk.exe |
| Source Dir | Absolute path to the Nuitka dist folder | D:\project\dist |
| Output Dir | Where to generate the installer | D:\project\output |
| Icon Path | Absolute path to an .ico file (optional but recommended) | D:\project\icon.ico |
| URL | Optional, for Control Panel link | https://example.com |
Query Template:
"Please provide the following packaging parameters. I need your confirmation on each:
- App Name:
- Version:
- Publisher:
- Main executable filename (e.g., xxx.exe):
- Source Dir (Nuitka dist folder):
- Output Dir (where to save the installer):
- Icon Path (.ico file, optional):
- URL (optional):
Please fill in each one, or reply 'Skip' to leave empty."
Step 2: Source Build Quality and Compilation Check (Critical) Before generating code, you must issue the following critical confirmation to the user (because Inno Setup is only a packaging tool and cannot change the program's runtime properties):
"WARNING: Compilation Parameter Check:
- No Console: Please confirm your dist folder was compiled with
nuitka --windows-console-mode=disable. (Otherwise, a black console window will still appear after installation.)- High Performance: Please confirm whether you used
--lto=yes. (Otherwise, startup speed may be suboptimal.)- Runtime Libraries: Please ensure the necessary VC++ runtime libraries are included in the dist folder to prevent failures on clean systems.
If you confirm the source files are ready, reply 'Confirm'; otherwise, please recompile first."
Step 3: Code Generation After user confirmation, output the code with full metadata and uninstall icon fix.
Nuitka Extreme Optimization Compilation (Based on Reference Project Experience)
1. 32-bit vs 64-bit Selection Strategy
Why the reference project used 32-bit Python:
| Component | 64-bit Size | 32-bit Size | Savings |
|---|---|---|---|
| python3x.dll | ~4.5 MB | ~3.8 MB | 15% |
| Qt5Core.dll | ~8 MB | ~5 MB | 37% |
| numpy | ~30 MB | ~20 MB | 33% |
| Total | Baseline | -20~30% | - |
Conditions for recommending 32-bit:
- PASS: Program memory usage < 2GB
- PASS: Does not process very large files (< 2GB)
- PASS: Target users are on standard office computers
How to compile with 32-bit:
# 1. Install 32-bit Python (can coexist with 64-bit)
# Download from: https://www.python.org/downloads/windows/
# 2. Install dependencies with 32-bit Python
py -3.12-32 -m pip install -r requirements.txt
# 3. Build with 32-bit Python
py -3.12-32 -m nuitka --standalone ...your parameters
2. Module Exclusion List (Validated on Reference Project)
Safe-to-exclude list (not needed at runtime):
unittest,test,pytest,_pytest,doctest,pdb,pdbpp,
setuptools,pip,distutils,pkg_resources,
email.mime,http.server,xmlrpc,pydoc
Expected Effect: Saves 30-50 MB
3. GUI Framework Specific Optimizations
Tkinter Extreme Optimization (Recommended, Lightest)
nuitka --standalone --windows-console-mode=disable ^
--lto=yes ^
--jobs=8 ^
--enable-plugin=tk-inter ^
--enable-plugin=anti-bloat ^
--noinclude-pytest-mode=nofollow ^
--noinclude-setuptools-mode=nofollow ^
--nofollow-import-to=unittest,test,pytest,_pytest,doctest,pdb,pdbpp ^
--nofollow-import-to=setuptools,pip,distutils,pkg_resources ^
--nofollow-import-to=email.mime,http.server,xmlrpc,pydoc ^
--python-flag=no_docstrings ^
--output-dir=dist ^
--windows-icon-from-ico=icon.ico ^
--remove-output ^
main.py
Expected Size: 80-120 MB (after optimization)
PyQt5 / PySide2 Optimization
nuitka --standalone --windows-console-mode=disable ^
--lto=yes ^
--jobs=8 ^
--enable-plugin=pyqt5 ^
--enable-plugin=anti-bloat ^
--noinclude-pytest-mode=nofollow ^
--noinclude-setuptools-mode=nofollow ^
--nofollow-import-to=unittest,test,pytest,_pytest,doctest,pdb ^
--nofollow-import-to=setuptools,pip,distutils,pkg_resources ^
--nofollow-import-to=PyQt5.QtWebEngine,PyQt5.QtWebEngineWidgets ^
--nofollow-import-to=PyQt5.Qt3D,PyQt5.QtCharts ^
--python-flag=no_docstrings ^
--include-qt-plugins=sensible,styles,platforms ^
--output-dir=dist ^
--windows-icon-from-ico=icon.ico ^
--remove-output ^
main.py
Expected Size: 120-250 MB (after optimization)
4. One-Click Build Script Template
Save as build_optimized.bat (in the project root directory):
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ========================================
echo Nuitka Extreme Optimization Build (Reference Project)
echo ========================================
REM === Configuration (modify these to your actual values) ===
set APP_NAME=YourAppName
set MAIN_FILE=main.py
set ICON_FILE=icon.ico
REM === Auto-detect CPU cores ===
set CPU_CORES=%NUMBER_OF_PROCESSORS%
if not defined CPU_CORES set CPU_CORES=4
set /a BUILD_JOBS=%CPU_CORES%
REM === Module exclusion list (from reference project) ===
set EXCLUDE_MODULES=unittest,test,pytest,_pytest,doctest,pdb,pdbpp
set EXCLUDE_MODULES=%EXCLUDE_MODULES%,setuptools,pip,distutils,pkg_resources
set EXCLUDE_MODULES=%EXCLUDE_MODULES%,email.mime,http.server,xmlrpc,pydoc
echo.
echo [1/4] Cleaning old builds...
if exist dist rd /s /q dist
if exist build rd /s /q build
echo.
echo [2/4] Nuitka compiling (applying reference project optimization strategies)...
echo - CPU Cores: %CPU_CORES% (using %BUILD_JOBS% threads)
echo - Module exclusion: %EXCLUDE_MODULES%
echo.
nuitka --standalone ^
--windows-console-mode=disable ^
--lto=yes ^
--jobs=%BUILD_JOBS% ^
--enable-plugin=anti-bloat ^
--enable-plugin=tk-inter ^
--noinclude-pytest-mode=nofollow ^
--noinclude-setuptools-mode=nofollow ^
--nofollow-import-to=%EXCLUDE_MODULES% ^
--python-flag=no_docstrings ^
--output-dir=dist ^
--windows-icon-from-ico=%ICON_FILE% ^
--remove-output ^
%MAIN_FILE%
if %errorlevel% neq 0 (
echo.
echo [ERROR] Build failed!
pause
exit /b 1
)
echo.
echo [3/4] Calculating build result...
for /f %%a in ('powershell -NoProfile -Command "(Get-ChildItem -LiteralPath 'dist\%APP_NAME%.dist' -Recurse -File | Measure-Object -Property Length -Sum).Sum"') do set TOTAL_SIZE=%%a
set TOTAL_SIZE=%TOTAL_SIZE:,=%
set /a SIZE_MB=%TOTAL_SIZE% / 1048576
echo - Post-build size: %SIZE_MB% MB
echo.
echo [4/4] Applying slimming cleanup (reference project strategy)...
powershell -ExecutionPolicy Bypass -File slim_dist.ps1 -DistPath "dist\%APP_NAME%.dist"
echo.
echo ========================================
echo Build Complete!
echo ========================================
pause
5. dist Slimming Script (Reference Project Level Cleanup)
Save as slim_dist.ps1 (in the same directory as build_optimized.bat):
param(
[string]$DistPath
)
$ErrorActionPreference = "Continue"
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "dist Slimming Cleanup (Reference Project Strategy)" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
if (-not (Test-Path $DistPath)) {
Write-Host "[ERROR] Directory not found: $DistPath" -ForegroundColor Red
exit 1
}
# Calculate initial size
$InitialSize = (Get-ChildItem -Path $DistPath -Recurse -File | Measure-Object -Property Length -Sum).Sum / 1MB
Write-Host "`nInitial size: $([math]::Round($InitialSize, 2)) MB" -ForegroundColor Yellow
Write-Host "`n[Applying reference project cleanup strategy...]" -ForegroundColor Green
# 1. Delete debug symbols
Write-Host "`n[1/7] Deleting .pdb debug symbols..." -ForegroundColor Green
$pdbFiles = Get-ChildItem -Path $DistPath -Recurse -Include *.pdb -File
$pdbSize = ($pdbFiles | Measure-Object -Property Length -Sum).Sum / 1MB
if ($pdbFiles.Count -gt 0) {
$pdbFiles | Remove-Item -Force
Write-Host " Deleted $($pdbFiles.Count) files, saved $([math]::Round($pdbSize, 2)) MB"
} else {
Write-Host " No .pdb files found (already optimized)" -ForegroundColor Gray
}
# 2. Delete type hints
Write-Host "`n[2/7] Deleting .pyi type hints..." -ForegroundColor Green
$pyiFiles = Get-ChildItem -Path $DistPath -Recurse -Include *.pyi -File
$pyiSize = ($pyiFiles | Measure-Object -Property Length -Sum).Sum / 1MB
if ($pyiFiles.Count -gt 0) {
$pyiFiles | Remove-Item -Force
Write-Host " Deleted $($pyiFiles.Count) files, saved $([math]::Round($pyiSize, 2)) MB"
} else {
Write-Host " No .pyi files found (already optimized)" -ForegroundColor Gray
}
# 3. Delete __pycache__
Write-Host "`n[3/7] Deleting __pycache__ folders..." -ForegroundColor Green
$pycacheDirs = Get-ChildItem -Path $DistPath -Recurse -Directory -Filter "__pycache__"
$pycacheSize = 0
foreach ($dir in $pycacheDirs) {
$size = (Get-ChildItem -Path $dir.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
$pycacheSize += $size
Remove-Item -Path $dir.FullName -Recurse -Force
}
if ($pycacheDirs.Count -gt 0) {
Write-Host " Deleted $($pycacheDirs.Count) directories, saved $([math]::Round($pycacheSize / 1MB, 2)) MB"
} else {
Write-Host " No __pycache__ found (already optimized)" -ForegroundColor Gray
}
# 4. Delete test directories
Write-Host "`n[4/7] Deleting test/tests directories..." -ForegroundColor Green
$testDirs = Get-ChildItem -Path $DistPath -Recurse -Directory | Where-Object { $_.Name -match '^tests?$' }
$testSize = 0
foreach ($dir in $testDirs) {
$size = (Get-ChildItem -Path $dir.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
$testSize += $size
Remove-Item -Path $dir.FullName -Recurse -Force
}
if ($testDirs.Count -gt 0) {
Write-Host " Deleted $($testDirs.Count) directories, saved $([math]::Round($testSize / 1MB, 2)) MB"
} else {
Write-Host " No test directories found (already optimized)" -ForegroundColor Gray
}
# 5. Delete documentation and examples
Write-Host "`n[5/7] Deleting docs/examples directories..." -ForegroundColor Green
$docDirs = Get-ChildItem -Path $DistPath -Recurse -Directory | Where-Object { $_.Name -match '^(docs|examples|samples|demo)$' }
$docSize = 0
foreach ($dir in $docDirs) {
$size = (Get-ChildItem -Path $dir.FullName -Recurse -File | Measure-Object -Property Length -Sum).Sum
$docSize += $size
Remove-Item -Path $dir.FullName -Recurse -Force
}
if ($docDirs.Count -gt 0) {
Write-Host " Deleted $($docDirs.Count) directories, saved $([math]::Round($docSize / 1MB, 2)) MB"
} else {
Write-Host " No documentation directories found (already optimized)" -ForegroundColor Gray
}
# 6. Delete .pyc files
Write-Host "`n[6/7] Deleting .pyc bytecode files..." -ForegroundColor Green
$pycFiles = Get-ChildItem -Path $DistPath -Recurse -Include *.pyc -File
$pycSize = ($pycFiles | Measure-Object -Property Length -Sum).Sum / 1MB
if ($pycFiles.Count -gt 0) {
$pycFiles | Remove-Item -Force
Write-Host " Deleted $($pycFiles.Count) files, saved $([math]::Round($pycSize, 2)) MB"
} else {
Write-Host " No .pyc files found (already optimized)" -ForegroundColor Gray
}
# 7. Slim .dist-info metadata
Write-Host "`n[7/7] Slimming .dist-info metadata..." -ForegroundColor Green
$distInfoDirs = Get-ChildItem -Path $DistPath -Recurse -Directory -Filter "*.dist-info"
$removedCount = 0
$removedSize = 0
foreach ($infoDir in $distInfoDirs) {
# Only delete install-time bookkeeping files; keep METADATA and entry_points.txt (needed for runtime importlib.metadata)
$filesToRemove = @("RECORD", "INSTALLER", "direct_url.json")
foreach ($fileName in $filesToRemove) {
$file = Join-Path $infoDir.FullName $fileName
if (Test-Path $file) {
$size = (Get-Item $file).Length
$removedSize += $size
Remove-Item $file -Force
$removedCount++
}
}
}
if ($removedCount -gt 0) {
Write-Host " Deleted $removedCount metadata files, saved $([math]::Round($removedSize / 1MB, 2)) MB"
} else {
Write-Host " No cleanable metadata found (already optimized)" -ForegroundColor Gray
}
# Calculate final size
$FinalSize = (Get-ChildItem -Path $DistPath -Recurse -File | Measure-Object -Property Length -Sum).Sum / 1MB
$SavedSize = $InitialSize - $FinalSize
$SavedPercent = if ($InitialSize -gt 0) { ($SavedSize / $InitialSize) * 100 } else { 0 }
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "Cleanup Complete!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Initial size: $([math]::Round($InitialSize, 2)) MB" -ForegroundColor Yellow
Write-Host "Final size: $([math]::Round($FinalSize, 2)) MB" -ForegroundColor Green
Write-Host "Space saved: $([math]::Round($SavedSize, 2)) MB ($([math]::Round($SavedPercent, 1))%)" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# Ask if Inno Setup packaging should proceed
$proceed = Read-Host "`nProceed with Inno Setup packaging? (y/N)"
if ($proceed -ne 'y') {
Write-Host "Skipping Inno Setup packaging." -ForegroundColor Yellow
exit 0
}
# Launch Inno Setup packaging script
$scriptPath = Join-Path $PSScriptRoot "build_innosetup.ps1"
if (Test-Path $scriptPath) {
Write-Host "`nLaunching Inno Setup packaging script: $scriptPath" -ForegroundColor Cyan
& $scriptPath -DistPath $DistPath
} else {
Write-Host "`n[WARNING] Inno Setup script not found at: $scriptPath" -ForegroundColor Yellow
Write-Host "Please manually package with Inno Setup." -ForegroundColor Yellow
}
6. Inno Setup Script Template (with Full Metadata and Uninstall Icon Fix)
Save as build_innosetup.ps1 (in the same directory):
param(
[string]$DistPath
)
# Configuration (will be prompted if not provided)
$AppName = Read-Host "App Name"
$AppVersion = Read-Host "App Version"
$AppPublisher = Read-Host "Publisher"
$AppExeName = Read-Host "Main Executable (e.g., RedInk.exe)"
$OutputDir = Read-Host "Output Directory"
$AppURL = Read-Host "App URL (optional)"
$IconPath = Read-Host "Icon Path (optional)"
if (-not $AppURL) { $AppURL = "https://example.com" }
$AppComments = "$AppName v$AppVersion"
$AppCopyright = "Copyright (C) $((Get-Date).Year) $AppPublisher"
# Determine architecture (auto-detect from dist folder)
$arch = "x86"
if (Test-Path (Join-Path $DistPath "python3.dll")) {
$is64bit = (Get-Item (Join-Path $DistPath "python3.dll")).VersionInfo.FileDescription -match "64"
if ($is64bit) { $arch = "x64" }
}
$vcRedistArch = if ($arch -eq "x64") { "x64" } else { "x86" }
$issContent = @"
; Inno Setup Script (Generated by AI Assistant)
#define MyAppName "$AppName"
#define MyAppVersion "$AppVersion"
#define MyAppPublisher "$AppPublisher"
#define MyAppURL "$AppURL"
#define MyAppExeName "$AppExeName"
#define MyAppComments "$AppComments"
#define MyAppCopyright "$AppCopyright"
[Setup]
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
AppComments={#MyAppComments}
AppCopyright={#MyAppCopyright}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
OutputDir={#MyAppName}\$OutputDir
OutputBaseFilename={#MyAppName}_Setup_{#MyAppVersion}_{$arch}
Compression=lzma2/ultra64
InternalCompressLevel=ultra64
SolidCompression=yes
DisableProgramGroupPage=yes
DisableReadyPage=no
DisableFinishedPage=no
UninstallDisplayIcon={app}\{#MyAppExeName}
SetupIconFile=$((if ([string]::IsNullOrEmpty($IconPath)) { "" } else { $IconPath }))
PrivilegesRequired=admin
ArchitecturesInstallIn64BitMode=$((if ($arch -eq "x64") { "x64" } else { "x86" }))
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: desktopicon; Description: "Create a &desktop shortcut"; GroupDescription: "Additional icons:"; Flags: checkedonce
[Files]
Source: "$DistPath\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "vc_redist.$vcRedistArch.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall; Check: not IsMsiProductInstalled('{00000000-0000-0000-0000-000000000000}') and not VCRedistNeedsInstall
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{tmp}\vc_redist.$vcRedistArch.exe"; Parameters: "/install /quiet /norestart"; StatusMsg: "Installing Visual C++ Redistributable..."; Check: not IsMsiProductInstalled('{00000000-0000-0000-0000-000000000000}') and not VCRedistNeedsInstall
[UninstallDelete]
Type: filesandordirs; Name: "{app}"
"@
$issPath = Join-Path $PSScriptRoot "installer_$($AppName -replace ' ','_').iss"
$issContent | Out-File -FilePath $issPath -Encoding UTF8
Write-Host "`nInno Setup script generated: $issPath" -ForegroundColor Green
Write-Host "`nTo build the installer, run:" -ForegroundColor Cyan
Write-Host " iscc `"$issPath`"" -ForegroundColor White
# Prompt to build if iscc is available
$buildNow = Read-Host "`nBuild the installer now? (requires Inno Setup, y/N)"
if ($buildNow -eq 'y') {
$isccPath = Get-Command "iscc" -ErrorAction SilentlyContinue
if ($isccPath) {
Write-Host "`nBuilding installer..." -ForegroundColor Cyan
& iscc "$issPath"
Write-Host "`nInstaller build complete!" -ForegroundColor Green
} else {
Write-Host "`niscc not found. Please run manually." -ForegroundColor Yellow
}
}
Inno Setup Packaging - Key Details
The Inno Setup script handles:
- Full metadata: App name, version, publisher, URL, copyright, comments
- Clean uninstall: Removes all files and folders (no leftover residues)
- Uninstall icon fix: Uses the app exe as the uninstall icon
- Arch-matched VC++ redistributable: Automatically includes the correct runtime
- LZMA2/ultra64 compression: Maximum compression for smallest installer size
- No console window: Already handled by Nuitka's
--windows-console-mode=disable
Note: This skill is optimized for Windows. For cross-platform builds, adapt the Nuitka commands accordingly (e.g., remove
--windows-console-mode=disableon Linux/macOS). The slimming scripts are PowerShell-specific and require Windows.