feat(windows): icon, windowed exe, no-admin installer
- PyInstaller exe built windowed (console=False) with package/testium.ico as the embedded icon (BMP entries for shell compatibility). - Suppress stray subprocess console windows in the frozen Windows build via paths.no_window_kwargs() (CREATE_NO_WINDOW); wheel/source unchanged. Applied to py_process, lua_process, bins probes, sys_app_path_win. - New per-user Inno Setup installer (package/innosetup/): no admin, version-scoped AppId/dir so versions install side-by-side, one Start Menu entry per version, .ico shipped for shortcut/uninstall icons. - DESIGN.md + release_note.txt updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
31
package/innosetup/build.ps1
Normal file
31
package/innosetup/build.ps1
Normal file
@@ -0,0 +1,31 @@
|
||||
# Build the Testium installer from testium.iss (needs Inno Setup 6 / ISCC.exe).
|
||||
# Install ISCC without admin: winget install --id JRSoftware.InnoSetup -e
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
# The PyInstaller exe must exist first.
|
||||
$exe = Join-Path $scriptDir '..\pyinstaller\dist\testium.exe'
|
||||
if (-not (Test-Path $exe)) {
|
||||
throw "PyInstaller build not found: $exe`nRun package\pyinstaller\build first."
|
||||
}
|
||||
|
||||
# Locate ISCC.exe: PATH, then the usual install dirs.
|
||||
$iscc = (Get-Command ISCC.exe -ErrorAction SilentlyContinue).Source
|
||||
if (-not $iscc) {
|
||||
foreach ($p in @(
|
||||
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
|
||||
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
|
||||
"$env:ProgramFiles\Inno Setup 6\ISCC.exe")) {
|
||||
if (Test-Path $p) { $iscc = $p; break }
|
||||
}
|
||||
}
|
||||
if (-not $iscc) {
|
||||
throw "ISCC.exe not found. Install Inno Setup 6:`n winget install --id JRSoftware.InnoSetup -e"
|
||||
}
|
||||
|
||||
Write-Host "Using ISCC: $iscc"
|
||||
& $iscc (Join-Path $scriptDir 'testium.iss')
|
||||
if ($LASTEXITCODE -ne 0) { throw "ISCC failed with exit code $LASTEXITCODE" }
|
||||
|
||||
Write-Host "`nInstaller built in: $(Join-Path $scriptDir 'dist')"
|
||||
127
package/innosetup/testium.iss
Normal file
127
package/innosetup/testium.iss
Normal file
@@ -0,0 +1,127 @@
|
||||
; Inno Setup script: wraps the PyInstaller testium.exe into a per-user installer.
|
||||
; Build with Inno Setup 6: ISCC.exe testium.iss (or ./build.ps1).
|
||||
|
||||
#define MyAppName "Testium"
|
||||
#define MyAppExeName "testium.exe"
|
||||
#define MyAppPublisher "Testium"
|
||||
#define MyAppURL "https://github.com/"
|
||||
|
||||
; Read version from src/VERSION so the installer never drifts from the build.
|
||||
#define VerFile FileOpen("..\..\src\VERSION")
|
||||
#define MyAppVersion Trim(FileRead(VerFile))
|
||||
#expr FileClose(VerFile)
|
||||
#if MyAppVersion == ""
|
||||
#error Could not read version from ..\..\src\VERSION
|
||||
#endif
|
||||
|
||||
[Setup]
|
||||
; Version-scoped AppId: each version is a distinct app, installable side-by-side.
|
||||
AppId={{B7E6F1C2-9A4D-4E3B-8F71-7C2D5A6E0B14}_{#MyAppVersion}
|
||||
AppName={#MyAppName} {#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
UninstallDisplayName={#MyAppName} {#MyAppVersion}
|
||||
WizardStyle=modern
|
||||
; Per-version install dir so versions never overwrite each other.
|
||||
DefaultDirName={autopf}\{#MyAppName}\{#MyAppVersion}
|
||||
; Shared "Testium" Start Menu folder; shortcuts below are named per version.
|
||||
DefaultGroupName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\testium.ico
|
||||
DisableProgramGroupPage=yes
|
||||
; Per-user install, no admin ever: installs under %LOCALAPPDATA%, no UAC prompt.
|
||||
PrivilegesRequired=lowest
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
OutputDir=dist
|
||||
OutputBaseFilename=testium-{#MyAppVersion}-setup
|
||||
SetupIconFile=..\testium.ico
|
||||
Compression=lzma2/max
|
||||
SolidCompression=yes
|
||||
; Tell Explorer to refresh the environment after a PATH change.
|
||||
ChangesEnvironment=yes
|
||||
|
||||
[Languages]
|
||||
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
; PATH off by default: the exe is windowed (console=False), so CLI shows no output.
|
||||
Name: "addtopath"; Description: "Ajouter Testium au PATH (usage en ligne de commande)"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "..\pyinstaller\dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
; Ship the .ico so shortcuts/uninstall reference it directly, not the embedded one.
|
||||
Source: "..\testium.ico"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[Icons]
|
||||
; Per-version names so each install shows separately in the Start Menu.
|
||||
Name: "{group}\{#MyAppName} {#MyAppVersion}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\testium.ico"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName} {#MyAppVersion}}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\{#MyAppName} {#MyAppVersion}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\testium.ico"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Code]
|
||||
const
|
||||
EnvKey = 'Environment';
|
||||
|
||||
// True if Param is not already a full segment of the per-user PATH.
|
||||
function NeedsAddPath(Param: string): Boolean;
|
||||
var
|
||||
OrigPath: string;
|
||||
begin
|
||||
if not RegQueryStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', OrigPath) then
|
||||
begin
|
||||
Result := True;
|
||||
exit;
|
||||
end;
|
||||
Result := Pos(';' + Uppercase(Param) + ';', ';' + Uppercase(OrigPath) + ';') = 0;
|
||||
end;
|
||||
|
||||
// On install: append {app} to the per-user PATH if the task is selected.
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
var
|
||||
Path: string;
|
||||
begin
|
||||
if CurStep = ssPostInstall then
|
||||
begin
|
||||
if WizardIsTaskSelected('addtopath') and NeedsAddPath(ExpandConstant('{app}')) then
|
||||
begin
|
||||
if not RegQueryStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path) then
|
||||
Path := '';
|
||||
if (Path <> '') and (Copy(Path, Length(Path), 1) <> ';') then
|
||||
Path := Path + ';';
|
||||
Path := Path + ExpandConstant('{app}');
|
||||
RegWriteStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// On uninstall: strip {app} back out of the per-user PATH.
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
var
|
||||
Path, AppDir, Segment: string;
|
||||
P: Integer;
|
||||
begin
|
||||
if CurUninstallStep = usUninstall then
|
||||
begin
|
||||
if RegQueryStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path) then
|
||||
begin
|
||||
AppDir := ExpandConstant('{app}');
|
||||
Segment := ';' + AppDir;
|
||||
P := Pos(Uppercase(Segment), Uppercase(Path));
|
||||
if P > 0 then
|
||||
Delete(Path, P, Length(Segment))
|
||||
else
|
||||
begin
|
||||
P := Pos(Uppercase(AppDir) + ';', Uppercase(Path));
|
||||
if P = 1 then
|
||||
Delete(Path, 1, Length(AppDir) + 1);
|
||||
end;
|
||||
RegWriteStringValue(HKEY_CURRENT_USER, EnvKey, 'Path', Path);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -94,11 +94,11 @@ exe = EXE(
|
||||
upx=not os.environ.get("TESTIUM_NO_UPX"),
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
ico='../testium.png'
|
||||
ico='../testium.ico'
|
||||
)
|
||||
|
||||
BIN
package/testium.ico
Normal file
BIN
package/testium.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
Reference in New Issue
Block a user