Files
dikte/packaging/build-windows.ps1
T
yusufipek 29643f26b3 Keep the two Windows executables from being one file
Windows matches a filename without regard to its case, so Dikte.exe and
dikte.exe were one file in the installation directory: PyInstaller wrote
the console build second and that is the only one that landed. Every
launcher then opened a console window behind the tray, and closing it
killed the application.

The console one is dikte-cli.exe now, which the setup's shim names. The
build reads the PE subsystem of both afterwards, since a filesystem that
keeps the two names apart is exactly what the check would run on
otherwise.
2026-08-19 11:11:30 +03:00

110 lines
5.4 KiB
PowerShell

#!/usr/bin/env pwsh
# The Windows download: one setup program, carrying everything Dikte needs to
# record and to be started again after a sign-in.
#
# Run from anywhere; it works in build\ at the top of the checkout and leaves
# the finished .exe in dist\. x64 only, because that is what the PyQt6 wheel and
# whisper.cpp both publish for Windows; a Windows on ARM machine runs it under
# the emulation it runs everything else under.
#
# powershell -ExecutionPolicy Bypass -File packaging\build-windows.ps1
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $PSScriptRoot
$build = Join-Path $root "build"
$out = Join-Path $root "dist"
$dist = Join-Path $build "dist\dikte"
$env:PYTHONPATH = $root
$version = & python -c "import dikte; print(dikte.__version__)"
if ($LASTEXITCODE -ne 0) { throw "could not read the version out of dikte/__init__.py" }
# A pinned tag and a checksum rather than "whatever is newest": this binary goes
# out inside something people run, so what it is has to be decided here and not
# by whoever pushes to that repository next. The same release the disk image
# takes its ffmpeg from, which is gyan.dev's essentials build repackaged, and
# dshow is in it, which is the one part of ffmpeg recording here goes through.
$ffmpegTag = "b6.1.1"
$ffmpegAsset = "ffmpeg-win32-x64.gz"
$ffmpegSha = "8883A3DFFBD0A16CF4EF95206EA05283F78908DBFB118F73C83F4951DCC06D77"
if (Test-Path $build) { Remove-Item $build -Recurse -Force }
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
New-Item -ItemType Directory -Path $build, $out | Out-Null
# 1. The icon ---------------------------------------------------------------
# Drawn by Dikte itself, offscreen, which is why there is no image file in the
# repository. Before the application, because PyInstaller writes it into the
# executable rather than beside it, and the setup program uses the same file.
$icon = Join-Path $build "Dikte.ico"
$env:QT_QPA_PLATFORM = "offscreen"
& python -m dikte.trayicon --ico $icon
if ($LASTEXITCODE -ne 0) { throw "the icon would not draw" }
Remove-Item Env:\QT_QPA_PLATFORM
$env:DIKTE_ICO = $icon
# 2. The application --------------------------------------------------------
# Two executables in the one directory: Dikte.exe, which is windowed and is
# what a shortcut starts, and dikte-cli.exe, which has a console and is what
# the `dikte` command runs. Their names differ by more than case on purpose;
# Windows would otherwise keep one file for both.
& python -m PyInstaller (Join-Path $root "packaging\dikte.spec") `
--distpath (Join-Path $build "dist") --workpath (Join-Path $build "work") `
--noconfirm --clean
if ($LASTEXITCODE -ne 0) { throw "PyInstaller failed" }
# And the check that the two are really two. Windows matches a filename
# without regard to its case, so a rename that leaves them a case apart puts
# one file in the directory and whichever was written second is what both
# names find. It cost a windowed build once, silently: the machines the
# packaging is otherwise checked on all have case-sensitive filesystems, so
# this only ever shows up here. Subsystem 2 is windowed, 3 is a console, and
# it is the field the loader reads to decide which to give.
function Get-PeSubsystem($path) {
$bytes = [System.IO.File]::ReadAllBytes($path)
$header = [BitConverter]::ToInt32($bytes, 0x3C)
return [BitConverter]::ToUInt16($bytes, $header + 0x5C)
}
foreach ($pair in @(@("Dikte.exe", 2), @("dikte-cli.exe", 3))) {
$subsystem = Get-PeSubsystem (Join-Path $dist $pair[0])
if ($subsystem -ne $pair[1]) {
throw "$($pair[0]) is subsystem $subsystem, expected $($pair[1]): the two executables collided"
}
}
# 3. ffmpeg -----------------------------------------------------------------
# Recording on Windows goes through ffmpeg's DirectShow input, and Windows
# ships nothing like it, so without this the download would be an application
# that cannot record until the person who downloaded it installs one. bin\
# beside the executables, because integrate.py puts that directory in front of
# PATH at startup and everything reaching for ffmpeg goes through shutil.which.
$archive = Join-Path $build $ffmpegAsset
Invoke-WebRequest -UseBasicParsing -OutFile $archive `
"https://github.com/eugeneware/ffmpeg-static/releases/download/$ffmpegTag/$ffmpegAsset"
$got = (Get-FileHash $archive -Algorithm SHA256).Hash
if ($got -ne $ffmpegSha) { throw "ffmpeg checksum: expected $ffmpegSha, got $got" }
$bin = Join-Path $dist "bin"
New-Item -ItemType Directory -Path $bin | Out-Null
$compressed = [System.IO.File]::OpenRead($archive)
$stream = New-Object System.IO.Compression.GzipStream(
$compressed, [System.IO.Compression.CompressionMode]::Decompress)
$binary = [System.IO.File]::Create((Join-Path $bin "ffmpeg.exe"))
try { $stream.CopyTo($binary) } finally { $binary.Dispose(); $stream.Dispose(); $compressed.Dispose() }
# 4. The setup program ------------------------------------------------------
# Inno Setup comes with the GitHub runner. On a machine that has not got it:
# winget install JRSoftware.InnoSetup
$iscc = (Get-Command iscc -ErrorAction SilentlyContinue).Source
if (-not $iscc) {
$iscc = Join-Path ${env:ProgramFiles(x86)} "Inno Setup 6\ISCC.exe"
}
if (-not (Test-Path $iscc)) {
throw "no Inno Setup found. Install it with: winget install JRSoftware.InnoSetup"
}
& $iscc "/DVersion=$version" "/DSource=$dist" "/DIcon=$icon" `
(Join-Path $root "packaging\dikte.iss")
if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed" }
Write-Host "dist\Dikte-$version-x64-setup.exe"