[v2] NSIS installer support for Windows (#1184)

* [v2] Add support for post build hooks

Currently only supports build-level hooks

* [v2] Improve build assets handling and use single source for manifest generation

The manifest asset files are now a go template and data will be
resolved before they are included into the build output.

Breaking Change: Windows manifest file must be named
“wails.exe.manifest” and doesn’t depend on the project name
anymore.

* [v2, windows] NSIS installer generation
This commit is contained in:
stffabi
2022-03-02 19:44:31 +11:00
committed by GitHub
parent c63b1f1981
commit b02dbfaddf
27 changed files with 781 additions and 166 deletions
+33 -2
View File
@@ -93,6 +93,9 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
debug := false
command.BoolFlag("debug", "Retains debug data in the compiled application", &debug)
nsis := false
command.BoolFlag("nsis", "Generate NSIS installer for Windows", &nsis)
command.Action(func() error {
quiet := verbosity == 0
@@ -206,6 +209,9 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
return err
}
projectOptions, err := project.Load(cwd)
if err != nil {
return err
}
// Check platform
validPlatformArch := slicer.String([]string{
@@ -221,7 +227,15 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
"windows/arm64",
})
outputBinaries := map[string]string{}
// Allows cancelling the build after the first error. It would be nice if targets.Each would support funcs
// returning an error.
var targetErr error
targets.Each(func(platform string) {
if targetErr != nil {
return
}
if !validPlatformArch.Contains(platform) {
buildOptions.Logger.Println("platform '%s' is not supported - skipping. Supported platforms: %s", platform, validPlatformArch.Join(","))
@@ -293,18 +307,35 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputFilename, err := build.Build(buildOptions)
if err != nil {
logger.Println("Error: ", err.Error())
logger.Println("Error: %s", err.Error())
targetErr = err
return
}
// Subsequent iterations
buildOptions.IgnoreFrontend = true
buildOptions.CleanBuildDirectory = false
// Output stats
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.\n", outputFilename, time.Since(start).Round(time.Millisecond).String()))
outputBinaries[platform] = outputFilename
})
if targetErr != nil {
return targetErr
}
if nsis {
amd64Binary := outputBinaries["windows/amd64"]
arm64Binary := outputBinaries["windows/arm64"]
if amd64Binary == "" && arm64Binary == "" {
return fmt.Errorf("cannot build nsis installer - no windows targets")
}
if err := build.GenerateNSISInstaller(buildOptions, amd64Binary, arm64Binary); err != nil {
return err
}
}
return nil
})
}
@@ -2,8 +2,6 @@ package initialise
import (
"fmt"
"github.com/flytam/filenamify"
"github.com/leaanthony/slicer"
"io"
"os"
"os/exec"
@@ -11,6 +9,9 @@ import (
"strings"
"time"
"github.com/flytam/filenamify"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/buildassets"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise/templates"
@@ -149,7 +150,7 @@ func initProject(options *templates.Options, quiet bool) error {
}
// Install the default assets
err = buildassets.Install(options.TargetDir, options.ProjectName)
err = buildassets.Install(options.TargetDir)
if err != nil {
return err
}
@@ -1,5 +1,8 @@
# Wails bin directory
build/bin
# Wails Windows NSIS support files
build/windows/installer/wails_tools.nsh
build/windows/installer/tmp/
# IDEs
.idea
@@ -1,5 +1,8 @@
# Wails bin directory
build/bin
# Wails Windows NSIS support files
build/windows/installer/wails_tools.nsh
build/windows/installer/tmp/
# IDEs
.idea
+45
View File
@@ -46,9 +46,25 @@ type Project struct {
// The platform to target
Platform string
// RunNonNativeBuildHooks will run build hooks though they are defined for a GOOS which is not equal to the host os
RunNonNativeBuildHooks bool `json:"runNonNativeBuildHooks"`
// Post build hooks for different targets, the hooks are executed in the following order
// Key: GOOS/GOARCH - Executed at build level after a build of the specific platform and arch
// Key: GOOS/* - Executed at build level after a build of the specific platform
// Key: */* - Executed at build level after a build
// The following keys are not yet supported.
// Key: GOOS - Executed at platform level after all builds of the specific platform
// Key: * - Executed at platform level after all builds of a platform
// Key: [empty] - Executed at global level after all builds of all platforms
PostBuildHooks map[string]string `json:"postBuildHooks"`
// The application author
Author Author
// The application information
Info Info
// Fully qualified filename
filename string
@@ -60,6 +76,9 @@ type Project struct {
// Arguments that are forwared to the application in dev mode
AppArgs string `json:"appargs"`
// NSISType to be build
NSISType string `json:"nsisType"`
}
func (p *Project) Save() error {
@@ -76,6 +95,14 @@ type Author struct {
Email string `json:"email"`
}
type Info struct {
CompanyName string `json:"companyName"`
ProductName string `json:"productName"`
ProductVersion string `json:"productVersion"`
Copyright *string `json:"copyright"`
Comments *string `json:"comments"`
}
// Load the project from the current working directory
func Load(projectPath string) (*Project, error) {
@@ -117,6 +144,24 @@ func Load(projectPath string) (*Project, error) {
}
}
if result.Info.CompanyName == "" {
result.Info.CompanyName = result.Name
}
if result.Info.ProductName == "" {
result.Info.ProductName = result.Name
}
if result.Info.ProductVersion == "" {
result.Info.ProductVersion = "1.0.0"
}
if result.Info.Copyright == nil {
v := "Copyright........."
result.Info.Copyright = &v
}
if result.Info.Comments == nil {
v := "Built using Wails (https://wails.app)"
result.Info.Comments = &v
}
// Return our project data
return &result, nil
}
+3
View File
@@ -46,6 +46,9 @@ func (a *Apt) Packages() packagemap {
"docker": []*Package{
{Name: "docker.io", SystemPackage: true, Optional: true},
},
"nsis": []*Package{
{Name: "nsis", SystemPackage: true, Optional: true},
},
}
}
+25 -2
View File
@@ -1,10 +1,11 @@
package system
import (
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
"os/exec"
"strings"
"github.com/wailsapp/wails/v2/internal/system/operatingsystem"
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
)
var (
@@ -75,6 +76,28 @@ func checkUPX() *packagemanager.Dependancy {
}
}
func checkNSIS() *packagemanager.Dependancy {
// Check for nsis installer
output, err := exec.Command("makensis", "-VERSION").Output()
installed := true
version := ""
if err != nil {
installed = false
} else {
version = strings.TrimSpace(strings.Split(string(output), "\n")[0])
}
return &packagemanager.Dependancy{
Name: "nsis ",
PackageName: "N/A",
Installed: installed,
InstallCommand: "Available at https://nsis.sourceforge.io/Download",
Version: version,
Optional: true,
External: false,
}
}
func checkDocker() *packagemanager.Dependancy {
// Check for npm
+1
View File
@@ -54,5 +54,6 @@ func (i *Info) discover() error {
i.Dependencies = append(i.Dependencies, xcodeDep)
i.Dependencies = append(i.Dependencies, checkNPM())
i.Dependencies = append(i.Dependencies, checkUPX())
i.Dependencies = append(i.Dependencies, checkNSIS())
return nil
}
+7
View File
@@ -45,6 +45,13 @@ func (i *Info) discover() error {
dep.Version = locallyInstalled.Version
}
}
if dep.Name == "nsis" {
locallyInstalled := checkNSIS()
if locallyInstalled.Installed {
dep.Installed = true
dep.Version = locallyInstalled.Version
}
}
}
i.Dependencies = dependencies
}
+1
View File
@@ -21,6 +21,7 @@ func (i *Info) discover() error {
i.Dependencies = append(i.Dependencies, checkWebView2())
i.Dependencies = append(i.Dependencies, checkNPM())
i.Dependencies = append(i.Dependencies, checkUPX())
i.Dependencies = append(i.Dependencies, checkNSIS())
//i.Dependencies = append(i.Dependencies, checkDocker())
return nil
@@ -0,0 +1,21 @@
package webview2runtime
import (
_ "embed"
"os"
"path/filepath"
)
//go:embed MicrosoftEdgeWebview2Setup.exe
var setupexe []byte
// WriteInstallerToFile writes the installer file to the given file.
func WriteInstallerToFile(targetFile string) error {
return os.WriteFile(targetFile, setupexe, 0755)
}
// WriteInstaller writes the installer exe file to the given directory and returns the path to it.
func WriteInstaller(targetPath string) (string, error) {
installer := filepath.Join(targetPath, `MicrosoftEdgeWebview2Setup.exe`)
return installer, WriteInstallerToFile(installer)
}
@@ -14,9 +14,6 @@ import (
"unsafe"
)
//go:embed MicrosoftEdgeWebview2Setup.exe
var setupexe []byte
// Info contains all the information about an installation of the webview2 runtime.
type Info struct {
Location string
@@ -170,9 +167,3 @@ func OpenInstallerDownloadWebpage() error {
cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", "https://developer.microsoft.com/en-us/microsoft-edge/webview2/")
return cmd.Run()
}
// WriteInstaller writes the installer exe file to the given path
func WriteInstaller(targetPath string) (string, error) {
installer := filepath.Join(targetPath, `MicrosoftEdgeWebview2Setup.exe`)
return installer, os.WriteFile(installer, setupexe, 0755)
}
@@ -1,14 +1,14 @@
<!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>{{.Name}}</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>1.0.0</string>
<key>CFBundleGetInfoString</key><string>Built using Wails (https://wails.app)</string>
<key>CFBundleShortVersionString</key><string>1.0.0</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>Copyright.........</string>
<key>NSHumanReadableCopyright</key><string>{{.Info.Copyright}}</string>
</dict></plist>
@@ -0,0 +1,15 @@
{
"fixed": {
"file_version": "{{.Info.ProductVersion}}"
},
"info": {
"0000": {
"ProductVersion": "{{.Info.ProductVersion}}",
"CompanyName": "{{.Info.CompanyName}}",
"FileDescription": "{{.Info.ProductName}}",
"LegalCopyright": "{{.Info.Copyright}}",
"ProductName": "{{.Info.ProductName}}",
"Comments": "{{.Info.Comments}}"
}
}
}
@@ -1,15 +0,0 @@
{
"fixed": {
"file_version": "1.0.0"
},
"info": {
"0000": {
"ProductVersion": "1.0.0",
"CompanyName": "{{.Name}}",
"FileDescription": "{{.Name}}",
"LegalCopyright": "Copyright.........",
"ProductName": "{{.Name}}",
"Comments": "Built using Wails (https://wails.app)"
}
}
}
@@ -0,0 +1,101 @@
Unicode true
####
## Please note: Template replacements don't work in this file. They are provided with default defines like
## mentioned underneath.
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
## from outside of Wails for debugging and development of the installer.
##
## For development first make a wails nsis build to populate the "wails_tools.nsh":
## > wails build --target windows/amd64 --nsis
## Then you can call makensis on this file with specifying the path to your binary:
## For a AMD64 only installer:
## > makensis -DARG_WAILS_AMD64_BINARY=../../bin/app.exe
## For a ARM64 only installer:
## > makensis -DARG_WAILS_ARM64_BINARY=../../bin/app.exe
## For a installer with both architectures:
## > makensis -DARG_WAILS_AMD64_BINARY=../../bin/app-amd64.exe -DARG_WAILS_ARM64_BINARY=../../bin/app-arm64.exe
####
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
####
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
###
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
####
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
####
## Include the wails tools
####
!include "wails_tools.nsh"
# The version information for this two must consist of 4 parts
VIProductVersion "${INFO_PRODUCTVERSION}.0"
VIFileVersion "${INFO_PRODUCTVERSION}.0"
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
!include "MUI.nsh"
!define MUI_ICON "../icon.ico"
!define MUI_UNICON "../icon.ico"
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources/leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
# !insertmacro MUI_PAGE_LICENSE "resources/eula.txt" # Adds a EULA page to the installer
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.
!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
#!uninstfinalize 'signtool --file "%1"'
#!finalize 'signtool --file "%1"'
Name "${INFO_PRODUCTNAME}"
OutFile "../../bin/${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
ShowInstDetails show # This will always show the installation details.
Function .onInit
!insertmacro wails.checkArchitecture
FunctionEnd
Section
!insertmacro wails.webview2runtime
SetOutPath $INSTDIR
!insertmacro wails.files
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
!insertmacro wails.writeUninstaller
SectionEnd
Section "uninstall"
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
RMDir /r $INSTDIR
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
!insertmacro wails.deleteUninstaller
SectionEnd
@@ -0,0 +1,171 @@
# DO NOT EDIT - Generated automatically by `wails build`
!include "x64.nsh"
!include "WinVer.nsh"
!include "FileFunc.nsh"
!ifndef INFO_PROJECTNAME
!define INFO_PROJECTNAME "{{.Name}}"
!endif
!ifndef INFO_COMPANYNAME
!define INFO_COMPANYNAME "{{.Info.CompanyName}}"
!endif
!ifndef INFO_PRODUCTNAME
!define INFO_PRODUCTNAME "{{.Info.ProductName}}"
!endif
!ifndef INFO_PRODUCTVERSION
!define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
!endif
!ifndef INFO_COPYRIGHT
!define INFO_COPYRIGHT "{{.Info.Copyright}}"
!endif
!ifndef PRODUCT_EXECUTABLE
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
!endif
!ifndef UNINST_KEY_NAME
!define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
!endif
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
!ifndef REQUEST_EXECUTION_LEVEL
!define REQUEST_EXECUTION_LEVEL "admin"
!endif
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
!ifdef ARG_WAILS_AMD64_BINARY
!define SUPPORTS_AMD64
!endif
!ifdef ARG_WAILS_ARM64_BINARY
!define SUPPORTS_ARM64
!endif
!ifdef SUPPORTS_AMD64
!ifdef SUPPORTS_ARM64
!define ARCH "amd64_arm64"
!else
!define ARCH "amd64"
!endif
!else
!ifdef SUPPORTS_ARM64
!define ARCH "arm64"
!else
!error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
!endif
!endif
!macro wails.checkArchitecture
!ifndef WAILS_WIN10_REQUIRED
!define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
!endif
!ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
!define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
!endif
${If} ${AtLeastWin10}
!ifdef SUPPORTS_AMD64
${if} ${IsNativeAMD64}
Goto ok
${EndIf}
!endif
!ifdef SUPPORTS_ARM64
${if} ${IsNativeARM64}
Goto ok
${EndIf}
!endif
IfSilent silentArch notSilentArch
silentArch:
SetErrorLevel 65
Abort
notSilentArch:
MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
Quit
${else}
IfSilent silentWin notSilentWin
silentWin:
SetErrorLevel 64
Abort
notSilentWin:
MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
Quit
${EndIf}
ok:
!macroend
!macro wails.files
!ifdef SUPPORTS_AMD64
${if} ${IsNativeAMD64}
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
${EndIf}
!endif
!ifdef SUPPORTS_ARM64
${if} ${IsNativeARM64}
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
${EndIf}
!endif
!macroend
!macro wails.writeUninstaller
WriteUninstaller "$INSTDIR\uninstall.exe"
SetRegView 64
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
!macroend
!macro wails.deleteUninstaller
Delete "$INSTDIR\uninstall.exe"
SetRegView 64
DeleteRegKey HKLM "${UNINST_KEY}"
!macroend
# Install webview2 by launching the bootstrapper
# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
!macro wails.webview2runtime
!ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
!define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
!endif
SetRegView 64
# If the admin key exists and is not empty then webview2 is already installed
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
${If} $0 != ""
Goto ok
${EndIf}
${EndIf}
SetDetailsPrint both
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
SetDetailsPrint listonly
InitPluginsDir
CreateDirectory "$pluginsdir\webview2bootstrapper"
SetOutPath "$pluginsdir\webview2bootstrapper"
File "tmp/MicrosoftEdgeWebview2Setup.exe"
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
SetDetailsPrint both
ok:
!macroend
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="MyApplication" version="1.0.0.0" processorArchitecture="amd64"/>
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
+106 -34
View File
@@ -1,34 +1,32 @@
package buildassets
import (
"bytes"
"embed"
"errors"
"fmt"
iofs "io/fs"
"os"
"path"
"path/filepath"
"text/template"
"github.com/leaanthony/debme"
"github.com/leaanthony/gosod"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/project"
)
//go:embed build
var assets embed.FS
type assetData struct {
Name string
}
const (
rootFolder = "build"
)
// Install will install all default project assets
func Install(targetDir string, projectName string) error {
func Install(targetDir string) error {
templateDir := gosod.New(assets)
err := templateDir.Extract(targetDir, &assetData{Name: projectName})
if err != nil {
return err
}
// Rename the manifest file
windowsDir := filepath.Join(targetDir, "build", "windows")
manifest := filepath.Join(windowsDir, "wails.exe.manifest")
targetFile := filepath.Join(windowsDir, projectName+".exe.manifest")
err = os.Rename(manifest, targetFile)
err := templateDir.Extract(targetDir, nil)
if err != nil {
return err
}
@@ -36,32 +34,106 @@ func Install(targetDir string, projectName string) error {
return nil
}
func RegenerateManifest(target string) error {
a, err := debme.FS(assets, "build")
if err != nil {
return err
}
return a.CopyFile("windows/wails.exe.manifest", target, 0644)
// GetLocalPath returns the local path of the requested build asset file
func GetLocalPath(projectData *project.Project, file string) string {
return filepath.Clean(filepath.Join(projectData.Path, rootFolder, filepath.FromSlash(file)))
}
func RegenerateAppIcon(target string) error {
a, err := debme.FS(assets, "build")
if err != nil {
return err
// ReadFile reads the file from the project build folder.
// If the file does not exist it falls back to the embedded file and the file will be written
// to the disk for customisation.
func ReadFile(projectData *project.Project, file string) ([]byte, error) {
fs := os.DirFS(filepath.ToSlash(projectData.Path)) // os.DirFs always operates on "/" as separatator
file = path.Join(rootFolder, file)
content, err := iofs.ReadFile(fs, file)
if errors.Is(err, iofs.ErrNotExist) {
// The file does not exist, let's read it from the assets FS and write it to disk
content, err := iofs.ReadFile(assets, file)
if err != nil {
return nil, err
}
if err := writeFileSystemFile(projectData, file, content); err != nil {
return nil, fmt.Errorf("Unable to create file in build folder: %s", err)
}
return content, nil
}
return a.CopyFile("appicon.png", target, 0644)
return content, err
}
func RegeneratePlist(targetDir string, projectName string) error {
darwinAssets, err := debme.FS(assets, "build/darwin")
// ReadFileWithProjectData reads the file from the project build folder and replaces ProjectInfo if necessary.
// If the file does not exist it falls back to the embedded file and the file will be written
// to the disk for customisation. The file written is the original unresolved one.
func ReadFileWithProjectData(projectData *project.Project, file string) ([]byte, error) {
content, err := ReadFile(projectData, file)
if err != nil {
return err
}
templateDir := gosod.New(darwinAssets)
err = templateDir.Extract(targetDir, &assetData{Name: projectName})
if err != nil {
return err
return nil, err
}
content, err = resolveProjectData(content, projectData)
if err != nil {
return nil, fmt.Errorf("Unable to resolve data in %s: %w", file, err)
}
return content, nil
}
// ReadOriginalFileWithProjectDataAndSave reads the file from the embedded assets and replaces
// ProjectInfo if necessary.
// It will also write the resolved final file back to the project build folder.
func ReadOriginalFileWithProjectDataAndSave(projectData *project.Project, file string) ([]byte, error) {
file = path.Join(rootFolder, file)
content, err := iofs.ReadFile(assets, file)
if err != nil {
return nil, fmt.Errorf("Unable to read file %s: %w", file, err)
}
content, err = resolveProjectData(content, projectData)
if err != nil {
return nil, fmt.Errorf("Unable to resolve data in %s: %w", file, err)
}
if err := writeFileSystemFile(projectData, file, content); err != nil {
return nil, fmt.Errorf("Unable to create file in build folder: %w", err)
}
return content, nil
}
type assetData struct {
Name string
Info project.Info
}
func resolveProjectData(content []byte, projectData *project.Project) ([]byte, error) {
tmpl, err := template.New("").Parse(string(content))
if err != nil {
return nil, err
}
data := &assetData{
Name: projectData.Name,
Info: projectData.Info,
}
var out bytes.Buffer
if err := tmpl.Execute(&out, data); err != nil {
return nil, err
}
return out.Bytes(), nil
}
func writeFileSystemFile(projectData *project.Project, file string, content []byte) error {
path := filepath.Clean(filepath.Join(projectData.Path, filepath.FromSlash(file)))
if dir := filepath.Dir(path); !fs.DirExists(dir) {
if err := fs.MkDirs(dir, 0755); err != nil {
return fmt.Errorf("Unable to create directory: %w", err)
}
}
if err := os.WriteFile(path, content, 0644); err != nil {
return err
}
return nil
}
+58 -2
View File
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
@@ -213,8 +214,63 @@ func Build(options *Options) (string, error) {
return "", err
}
result := options.CompiledBinary
compileBinary := options.CompiledBinary
hookArgs := map[string]string{
"${platform}": options.Platform + "/" + options.Arch,
"${bin}": compileBinary,
}
return result, nil
for _, hook := range []string{options.Platform + "/" + options.Arch, options.Platform + "/*", "*/*"} {
if err := execPostBuildHook(outputLogger, options, hook, hookArgs); err != nil {
return "", err
}
}
return compileBinary, nil
}
func execPostBuildHook(outputLogger *clilogger.CLILogger, options *Options, hookIdentifier string, argReplacements map[string]string) error {
postBuildHook := options.ProjectData.PostBuildHooks[hookIdentifier]
if postBuildHook == "" {
return nil
}
if !options.ProjectData.RunNonNativeBuildHooks {
if hookIdentifier == "" {
// That's the global hook
} else {
platformOfHook := strings.Split(hookIdentifier, "/")[0]
if platformOfHook == "*" {
// Thats OK, we don't have a specific platform of the hook
} else if platformOfHook == runtime.GOOS {
// The hook is for host platform
} else {
// Skip a hook which is not native
outputLogger.Println(" - Non native build hook '%s': Skipping.", hookIdentifier)
return nil
}
}
}
outputLogger.Print(" - Executing post build hook '%s': ", hookIdentifier)
args := strings.Split(postBuildHook, " ")
for i, arg := range args {
newArg := argReplacements[arg]
if newArg == "" {
continue
}
args[i] = newArg
}
if options.Verbosity == VERBOSE {
outputLogger.Println("%s", strings.Join(args, " "))
}
_, stderr, err := shell.RunCommand(options.BuildDirectory, args[0], args[1:]...)
if err != nil {
return fmt.Errorf("%s - %s", err.Error(), stderr)
}
outputLogger.Println("Done.")
return nil
}

Some files were not shown because too many files have changed in this diff Show More