build: implement packaging steps for the Windows build

Implement a `pkg-windows` target that ultimately results in `Status.zip` being
written to `pkg/`.

Note: this commit does not introduce code signing for the Windows build since
that piece is still a work in progress.

`pkg-windows` creates a portable folder in `tmp/windows/dist` with the help of
[`windeployqt`][windeployqt], which copies the needed portions of Qt into the
folder.

Since DLL resolution is relatively inflexible, a launcher `Status.exe` is
created at the top-level of the folder; the launcher opens `bin/Status.exe`
while adding the portable folder's `bin/` to the `PATH`, allowing
`bin/Status.exe` to resolve the DLLs in that folder.

A few additional tools need to be installed (e.g. with [scoop][scoop]) and
availble in `PATH`:
* 7-zip
* dos2unix (provides unix2dos)
* findutils
* go
* rcedit
* wget

The above list builds on the tools list in PR #521, and the other requirements
and instructions in that PR's description still apply.

**Why not build an installer?**

When starting work on packaging for the Windows build, my initial plan was to
build an installer, and for that purpose I researched the [WiX Toolset][wix],
the [Qt Installer Framework][qtif], and some other options.

I found that building an installer is a bit complex. I then recalled, from
personal experience, that [Cmder][cmder]'s [Mini download][mini] is
installer-less. You simply unzip the download and place the `cmder_mini` folder
wherever you prefer. Such an approach was also recommended to me in one of the
Nim language's community chats.

In addition to being simpler, the installer-less approach also gives
installation of Status Desktop a lower profile than an installer-application
would since nothing is written to the Windows registry, added to the *Add or
remove programs* list, etc. I think that's a benefit given the privacy-security
focus of Status, but others may feel differently so please provide feedback on
this point!

[windeployqt]: https://doc.qt.io/qt-5/windows-deployment.html
[scoop]: https://scoop.sh/
[wix]: https://wixtoolset.org/
[qtif]: https://doc.qt.io/qtinstallerframework/index.html
[cmder]: https://cmder.net/
[mini]: https://github.com/cmderdev/cmder/releases/download/v1.3.15/cmder_mini.zip
This commit is contained in:
Michael Bradley, Jr 2020-07-15 17:45:56 -05:00 committed by Michael Bradley
parent 449b8c0454
commit 29e74b6b3f
5 changed files with 157 additions and 14 deletions

View File

@ -18,9 +18,13 @@ BUILD_SYSTEM_DIR := vendor/nimbus-build-system
bottles \ bottles \
bottles-dummy \ bottles-dummy \
bottles-macos \ bottles-macos \
check-pkg-target-linux \
check-pkg-target-macos \
check-pkg-target-windows \
clean \ clean \
deps \ deps \
nim_status_client \ nim_status_client \
nim_windows_launcher \
pkg \ pkg \
pkg-linux \ pkg-linux \
pkg-macos \ pkg-macos \
@ -70,13 +74,31 @@ ifeq ($(detected_OS),Darwin)
else ifeq ($(detected_OS),Windows) else ifeq ($(detected_OS),Windows)
BOTTLES_TARGET := bottles-dummy BOTTLES_TARGET := bottles-dummy
PKG_TARGET := pkg-windows PKG_TARGET := pkg-windows
QRCODEGEN_MAKE_PARAMS := CC=gcc
RUN_TARGET := run-windows RUN_TARGET := run-windows
VCINSTALLDIR ?= C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\
export VCINSTALLDIR
else else
BOTTLES_TARGET := bottles-dummy BOTTLES_TARGET := bottles-dummy
PKG_TARGET := pkg-linux PKG_TARGET := pkg-linux
RUN_TARGET := run-linux-or-macos RUN_TARGET := run-linux-or-macos
endif endif
check-pkg-target-linux:
ifneq ($(detected_OS),Linux)
$(error The pkg-linux target must be run on Linux)
endif
check-pkg-target-macos:
ifneq ($(detected_OS),Darwin)
$(error The pkg-macos target must be run on macOS)
endif
check-pkg-target-windows:
ifneq ($(detected_OS),Windows)
$(error The pkg-windows target must be run on Windows)
endif
bottles: $(BOTTLES_TARGET) bottles: $(BOTTLES_TARGET)
bottles-dummy: ; bottles-dummy: ;
@ -142,7 +164,6 @@ else
DOTHERSIDE_BUILD_CMD := cmake --build . --config Release $(HANDLE_OUTPUT) DOTHERSIDE_BUILD_CMD := cmake --build . --config Release $(HANDLE_OUTPUT)
NIM_PARAMS += -L:$(DOTHERSIDE) NIM_PARAMS += -L:$(DOTHERSIDE)
NIM_EXTRA_PARAMS := --passL:"-lsetupapi -lhid" NIM_EXTRA_PARAMS := --passL:"-lsetupapi -lhid"
QRCODEGEN_MAKE_PARAMS := CC=gcc
endif endif
# TODO: control debug/release builds with a Make var # TODO: control debug/release builds with a Make var
@ -206,6 +227,7 @@ STATUS_CLIENT_APPIMAGE ?= pkg/NimStatusClient-x86_64.AppImage
$(STATUS_CLIENT_APPIMAGE): nim_status_client $(APPIMAGE_TOOL) nim-status.desktop $(STATUS_CLIENT_APPIMAGE): nim_status_client $(APPIMAGE_TOOL) nim-status.desktop
rm -rf pkg/*.AppImage rm -rf pkg/*.AppImage
rm -rf tmp/linux/dist
mkdir -p tmp/linux/dist/usr/bin mkdir -p tmp/linux/dist/usr/bin
mkdir -p tmp/linux/dist/usr/lib mkdir -p tmp/linux/dist/usr/lib
mkdir -p tmp/linux/dist/usr/qml mkdir -p tmp/linux/dist/usr/qml
@ -285,18 +307,69 @@ ifdef MACOS_CODESIGN_IDENT
scripts/sign-macos-pkg.sh $(STATUS_CLIENT_DMG) $(MACOS_CODESIGN_IDENT) scripts/sign-macos-pkg.sh $(STATUS_CLIENT_DMG) $(MACOS_CODESIGN_IDENT)
endif endif
STATUS_CLIENT_EXE ?= pkg/Status.exe NIM_WINDOWS_PREBUILT_DLLS ?= tmp/windows/tools/pcre.dll
# not implemented yet $(NIM_WINDOWS_PREBUILT_DLLS):
# $(STATUS_CLIENT_EXE): nim_status_client echo -e "\e[92mFetching:\e[39m prebuilt DLLs from nim-lang.org"
rm -rf tmp/windows
mkdir -p tmp/windows/tools
cd tmp/windows/tools && \
wget https://nim-lang.org/download/dlls.zip && \
unzip dlls.zip
nim_windows_launcher: | deps
$(ENV_SCRIPT) nim c -d:debug --outdir:./bin --passL:"-static-libgcc -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive" src/nim_windows_launcher.nim
STATUS_CLIENT_ZIP ?= pkg/Status.zip
$(STATUS_CLIENT_ZIP): nim_status_client nim_windows_launcher $(NIM_WINDOWS_PREBUILT_DLLS)
rm -rf pkg/*.zip
rm -rf tmp/windows/dist
mkdir -p tmp/windows/dist/Status/bin
mkdir -p tmp/windows/dist/Status/resources
mkdir -p tmp/windows/dist/Status/vendor
cp windows-install.txt tmp/windows/dist/Status/INSTALL.txt
unix2dos -k tmp/windows/dist/Status/INSTALL.txt
# cp LICENSE tmp/windows/dist/Status/LICENSE.txt
# unix2dos -k tmp/windows/dist/Status/LICENSE.txt
cp status.ico tmp/windows/dist/Status/resources/
cp status.svg tmp/windows/dist/Status/resources/
cp resources.rcc tmp/windows/dist/Status/resources/
cp bin/nim_status_client.exe tmp/windows/dist/Status/bin/Status.exe
mv bin/nim_windows_launcher.exe tmp/windows/dist/Status/Status.exe
rcedit \
tmp/windows/dist/Status/bin/Status.exe \
--set-icon tmp/windows/dist/Status/resources/status.ico
rcedit \
tmp/windows/dist/Status/Status.exe \
--set-icon tmp/windows/dist/Status/resources/status.ico
cp $(DOTHERSIDE) tmp/windows/dist/Status/bin/
cp tmp/windows/tools/*.dll tmp/windows/dist/Status/bin/
cp "$(shell which libgcc_s_seh-1.dll)" tmp/windows/dist/Status/bin/
cp "$(shell which libwinpthread-1.dll)" tmp/windows/dist/Status/bin/
echo -e $(BUILD_MSG) "deployable folder"
windeployqt \
--compiler-runtime \
--qmldir ui \
--release \
tmp/windows/dist/Status/bin/DOtherSide.dll
mv tmp/windows/dist/Status/bin/vc_redist.x64.exe tmp/windows/dist/Status/vendor/
# implement code signing of applicable files in deployable folder
echo -e $(BUILD_MSG) "zip"
mkdir -p pkg
cd tmp/windows/dist/Status && \
7z a ../../../../pkg/Status.zip *
# can the final .zip file be code signed as well?
pkg: $(PKG_TARGET) pkg: $(PKG_TARGET)
pkg-linux: $(STATUS_CLIENT_APPIMAGE) pkg-linux: check-pkg-target-linux $(STATUS_CLIENT_APPIMAGE)
pkg-macos: $(STATUS_CLIENT_DMG) pkg-macos: check-pkg-target-macos $(STATUS_CLIENT_DMG)
pkg-windows: $(STATUS_CLIENT_EXE) pkg-windows: check-pkg-target-windows $(STATUS_CLIENT_ZIP)
clean: | clean-common clean: | clean-common
rm -rf bin/* node_modules pkg/* tmp/* $(STATUSGO) rm -rf bin/* node_modules pkg/* tmp/* $(STATUSGO)
@ -312,10 +385,10 @@ run-linux-or-macos:
LD_LIBRARY_PATH="$(QT5_LIBDIR)" \ LD_LIBRARY_PATH="$(QT5_LIBDIR)" \
./bin/nim_status_client ./bin/nim_status_client
run-windows: run-windows: $(NIM_WINDOWS_PREBUILT_DLLS)
echo -e "\e[92mRunning:\e[39m bin/nim_status_client.exe" echo -e "\e[92mRunning:\e[39m bin/nim_status_client.exe"
NIM_STATUS_CLIENT_DEV="$(NIM_STATUS_CLIENT_DEV)" \ NIM_STATUS_CLIENT_DEV="$(NIM_STATUS_CLIENT_DEV)" \
PATH="$(shell pwd)"/"$(shell dirname "$(DOTHERSIDE)")":"$(PATH)" \ PATH="$(shell pwd)"/"$(shell dirname "$(DOTHERSIDE)")":"$(shell pwd)"/"$(shell dirname "$(NIM_WINDOWS_PREBUILT_DLLS)")":"$(PATH)" \
./bin/nim_status_client.exe ./bin/nim_status_client.exe
endif # "variables.mk" was not included endif # "variables.mk" was not included

View File

@ -23,6 +23,7 @@ if defined(macosx):
# set the minimum supported macOS version to 10.13 # set the minimum supported macOS version to 10.13
switch("passC", "-mmacosx-version-min=10.13") switch("passC", "-mmacosx-version-min=10.13")
elif defined(windows): elif defined(windows):
--app:gui
--tlsEmulation:off --tlsEmulation:off
switch("passL", "-Wl,-as-needed") switch("passL", "-Wl,-as-needed")
else: else:

View File

@ -25,9 +25,19 @@ proc mainProc() =
initializeOpenGL() initializeOpenGL()
let app = newQApplication("Nim Status Client") let app = newQApplication("Nim Status Client")
QResource.registerResource(app.applicationDirPath & "/../resources.rcc") let resources =
if defined(windows) and getEnv("NIM_STATUS_CLIENT_DEV").string == "":
"/../resources/resources.rcc"
else:
"/../resources.rcc"
QResource.registerResource(app.applicationDirPath & resources)
app.icon(app.applicationDirPath & "/../status.svg") let statusSvg =
if defined(windows) and getEnv("NIM_STATUS_CLIENT_DEV").string == "":
"/../resources/status.svg"
else:
"/../status.svg"
app.icon(app.applicationDirPath & statusSvg)
let engine = newQQmlApplicationEngine() let engine = newQQmlApplicationEngine()
let signalController = signals.newController(app) let signalController = signals.newController(app)

View File

@ -0,0 +1,17 @@
from os import getCurrentDir, joinPath
from winlean import Handle, shellExecuteW
const NULL: Handle = 0
let cwd = getCurrentDir()
let workDir_str = joinPath(cwd, "bin")
let exePath_str = joinPath(workDir_str, "Status.exe")
let open_str = "open"
let params_str = ""
let workDir = newWideCString(workDir_str)
let exePath = newWideCString(exePath_str)
let open = newWideCString(open_str)
let params = newWideCString(params_str)
# SW_SHOW (5): activates window and displays it in its current size and position
const showCmd: int32 = 5
discard shellExecuteW(NULL, open, exePath, params, workDir, showCmd)

42
windows-install.txt Normal file
View File

@ -0,0 +1,42 @@
REQUIREMENTS
============
This application requires 64-bit Windows 7 or newer.
INSTALLING
==========
After unzipping Status.zip, the Status folder can be placed wherever you prefer
(and have permissions) on your computer.
RUNNING
=======
Double-click Status.exe in the Status folder to launch the application.
If you see an error complaining about a DLL file, you should open
vc_redist.x64.exe in the Status\vendor folder to install the Microsoft Visual
C++ Redistributable. This is usually necessary only on versions of Windows
older than Windows 10. Then retry launching the application.
You may wish to right-click Status.exe and "Send to > Desktop (create shortcut)".
The application can then be launched by double-clicking the shortcut on your
desktop.
Status.exe persists settings and encrypted data in your %LOCALAPPDATA%\Status
folder.
UPGRADING
=========
To upgrade this application download the latest Status.zip, delete this Status
folder and the older Status.zip, unzip the newer one, and then place the new
Status folder in your preferred location.
If you place the new Status folder in a different location than the old one
then you should recreate any shortcuts you created for Status.exe.
UNINSTALLING
============
Delete this Status folder and delete your %LOCALAPPDATA%\Status folder.