NSIS 是流行的 Windows 程序打包程序。使用其脚本配置快速生成。该工具支持在多个平台下工作。
本文在 MacOS 下安装该工具,并打包使用 golang 的 Wails 编写的客户端程序。
安装 NSIS
该软件支持 Windows,Linux 和 MacOS。根据 Wails 官网部分 指引安装即可。
在 MacOS 系统中,本文使用 homebrew 安装。
brew install nsis
实际安装的二进制命令为 makensis
脚本编写
Wails 创建的项目自带了 nsis 的配置脚本。但是对于初次接触的人不好理解里面各种命令、指令的作用 。本人从空白到打包完成给出基础的命令。细节部分还不到位,还需要查询资料后处理。
⚠️注意:NSIS 脚本的执行是自上而下执行。所以命令的执行是有先后顺序的,必须按照以下顺序依次配置编写。
首先创建一个文件,结尾使用 .nsi 结尾,该文件就是我们的打包配置脚本了。
NSIS 的安装界面我们使用现代的 UI 设计,否则安装界面的样式就跟上个世纪的一样。在文件开头写入
# 引入现代化用户界面
!include "MUI2.nsh"
定义常用的一些变量,包含桌面快捷方式名称,主程序文件名,安装程序欢迎界面的程序名称。常量使用如下方式定义,在使用时使用 ${常量名称} 来引用。
# 常量定义
!define DESKTOP_SHORTCUT_NAME "TestProject.lnk"
!define MAIN_BIN_NAME "test_project.exe"
!define SERVER_JAR_NAME "demo-0.0.1-SNAPSHOT.jar"
!define INSTALLER_TITLE_NAME "测试程序"
定义我们的安装程序生成后放的位置,注意该位置是相对于你 nsi 脚本存放的位置。当然你也可以使用绝对路径或者仅写一个文件名,如果仅有文件名,那么打包输出的位置就是本脚本所在的位置同级。
Outfile "../../DemoInstaller.exe" # NSIS 编译打包输出位置,相对于本脚本的位置
定义安装界面的一些名字和提示语
Caption "${INSTALLER_TITLE_NAME} Installer" # 设置安装窗口标题
Name "${INSTALLER_TITLE_NAME}" # 安装界面欢迎标题中的程序名称
BrandingText "欢迎使用 ${INSTALLER_TITLE_NAME}" # 设置左下角灰色标语
配置关闭警告和安装程序的图标以及卸载程序的图标,这里的文件路径也是相对于本脚本的路径,也可以写绝对路径。图标必须是 Windows 使用的 .ico 文件,否则无效。
!define MUI_ABORTWARNING # 关闭安装程序弹窗确认
!define MUI_ICON "..\icon.ico" # 设置安装程序图标
!define MUI_UNICON "..\icon.ico" # 设置卸载程序图标
设置程序的默认安装位置。这里的 $PROGRAMFILES 就是最常见的 C:\Program XXXX 的那个,后面可以自行拼接路径。和大多数安装程序一样,安装时会自动创建安装目录,如果目录不存在的话。
# 安装目录默认路径
InstallDir "$PROGRAMFILES\MyApp"
请求管理员权限,因为安装程序可以让用户自行选择安装位置,为了防止某些文件权限问题,请求管理员权限以正常写入磁盘文件。
# 请求管理员权限
RequestExecutionLevel admin
界面设置,NSIS 新版的安装界面都是模块化的,需要什么界面就插入什么界面。这里的 license 文件路径同上,都是相对于本脚本的位置。license 文件是必须的,需要事先准备好。
# 安装器界面设置
!define MUI_PRODUCT_NAME "${APP_NAME}" # 设置产品名称
!insertmacro MUI_PAGE_WELCOME # 欢迎界面
!insertmacro MUI_PAGE_LICENSE "../../bin/license.txt" # 许可证界面,license.txt为你的许可证文件
!insertmacro MUI_PAGE_DIRECTORY # 选择安装目录界面
!insertmacro MUI_PAGE_INSTFILES # 安装进度界面
!insertmacro MUI_PAGE_FINISH # 完成界面
# 卸载界面设置
!insertmacro MUI_UNPAGE_WELCOME # 卸载欢迎界面
!insertmacro MUI_UNPAGE_CONFIRM # 卸载确认界面
!insertmacro MUI_UNPAGE_INSTFILES # 卸载进度界面
!insertmacro MUI_UNPAGE_FINISH # 卸载完成界面
# 设置语言为简体中文
!insertmacro MUI_LANGUAGE "SimpChinese"
主要部分,安装逻辑和卸载逻辑。本质上我们的程序二进制都是由编译器生成。附属的库文件什么的都是事先准备好的。NSIS 的打包动作等同于创建压缩文件。配置参考如下内容注释
# 定义安装的 Section
Section "Install"
# 创建目录 $INSTDIR 就是用户选择的安装目录。
# 注意 Windows 上的路径必须是 \ 间隔。
CreateDirectory "$INSTDIR\Resources"
CreateDirectory "$INSTDIR\Resources\jre"
# 设置文件输出路径,可以理解为切换工作目录。
# 以下两行意思是:安装时复制本机本脚本相对路径的 ../../bin/${MAIN_BIN_NAME} 文件到用户指定的安装目录
SetOutPath "$INSTDIR"
File "../../bin/${MAIN_BIN_NAME}" # 这里的路径为相对本脚本的路径
# 更换输出路径
SetOutPath "$INSTDIR\Resources"
File "../../bin/${SERVER_JAR_NAME}"
# 更换输出路径
# 复制 jre 整个文件夹,注意这里的复制是文件夹下的所有内容,并且是递归复制。
SetOutPath "$INSTDIR\Resources\jre"
File /r "../../bin/windows/jre/*.*"
# 创建桌面快捷方式,两个参数,第一个是跨界方式名称,第二个是你主程序的文件名。路径是 Windows 上的路径。
CreateShortcut "$DESKTOP\${DESKTOP_SHORTCUT_NAME}" "$INSTDIR\${MAIN_BIN_NAME}"
# 添加卸载程序,卸载程序名也是自己写的
WriteUninstaller "$INSTDIR\uninstaller.exe"
SectionEnd
# 卸载 Section
Section "Uninstall"
# 删除安装目录及其所有内容
RMDir /r "$INSTDIR"
# 删除桌面快捷方式
Delete "$DESKTOP\${DESKTOP_SHORTCUT_NAME}"
SectionEnd
开始编译
上述脚本准备好之后,执行命令。
makensis build/windows/installer/installer.nsi
注册表问题
通常来讲 Windows 上面安装的程序都可以在 控制面板 中卸载。这个功能需要在注册表中写入部分内容,且是约定的规范。本文创建的脚本,没有这块操作。所以安装完成后就是一个"绿色软件"。
Wails HTTP 访问安全问题设置
该文件出现在 MacOS 的 APP 打包上。使用 wails build 在 macOS 上打包 app 时,正常打包。但是如果前端使用了 http 方式访问服务器数据。就会被阻止。
在 wails 创建的项目中,找到 build/darwin/info.plist 以及 info.dev.plist 两个文件,在第一层 dict 标签末尾追加以下配置即可。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
完整示例
# info.dev.plist
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>