diff --git a/v2/cmd/wails/internal/commands/build/build.go b/v2/cmd/wails/internal/commands/build/build.go
index 2a6988122..1ea93bcb9 100644
--- a/v2/cmd/wails/internal/commands/build/build.go
+++ b/v2/cmd/wails/internal/commands/build/build.go
@@ -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
})
}
diff --git a/v2/cmd/wails/internal/commands/initialise/initialise.go b/v2/cmd/wails/internal/commands/initialise/initialise.go
index f87e6b7a3..f572061f7 100644
--- a/v2/cmd/wails/internal/commands/initialise/initialise.go
+++ b/v2/cmd/wails/internal/commands/initialise/initialise.go
@@ -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
}
diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/gitignore.txt b/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/gitignore.txt
index da44b7c53..b92a6f8bf 100644
--- a/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/gitignore.txt
+++ b/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/gitignore.txt
@@ -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
diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/gitignore.txt b/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/gitignore.txt
index da44b7c53..b92a6f8bf 100644
--- a/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/gitignore.txt
+++ b/v2/cmd/wails/internal/commands/initialise/templates/templates/vanilla/gitignore.txt
@@ -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
diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go
index 61f5ea612..2326a1b7e 100644
--- a/v2/internal/project/project.go
+++ b/v2/internal/project/project.go
@@ -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
}
diff --git a/v2/internal/system/packagemanager/apt.go b/v2/internal/system/packagemanager/apt.go
index 986ce85d6..7fe9f444b 100644
--- a/v2/internal/system/packagemanager/apt.go
+++ b/v2/internal/system/packagemanager/apt.go
@@ -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},
+ },
}
}
diff --git a/v2/internal/system/system.go b/v2/internal/system/system.go
index 9d7650622..341f75942 100644
--- a/v2/internal/system/system.go
+++ b/v2/internal/system/system.go
@@ -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
diff --git a/v2/internal/system/system_darwin.go b/v2/internal/system/system_darwin.go
index 671c6bd76..b683f4fd5 100644
--- a/v2/internal/system/system_darwin.go
+++ b/v2/internal/system/system_darwin.go
@@ -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
}
diff --git a/v2/internal/system/system_linux.go b/v2/internal/system/system_linux.go
index b9d8b1fc9..a591d7411 100644
--- a/v2/internal/system/system_linux.go
+++ b/v2/internal/system/system_linux.go
@@ -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
}
diff --git a/v2/internal/system/system_windows.go b/v2/internal/system/system_windows.go
index b0982828a..ce8c90028 100644
--- a/v2/internal/system/system_windows.go
+++ b/v2/internal/system/system_windows.go
@@ -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
diff --git a/v2/internal/webview2runtime/webview2installer.go b/v2/internal/webview2runtime/webview2installer.go
new file mode 100644
index 000000000..a2a2922dc
--- /dev/null
+++ b/v2/internal/webview2runtime/webview2installer.go
@@ -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)
+}
diff --git a/v2/internal/webview2runtime/webview2runtime.go b/v2/internal/webview2runtime/webview2runtime.go
index d3d7474c5..c5f6c0d53 100644
--- a/v2/internal/webview2runtime/webview2runtime.go
+++ b/v2/internal/webview2runtime/webview2runtime.go
@@ -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)
-}
diff --git a/v2/pkg/buildassets/build/darwin/Info.tmpl.plist b/v2/pkg/buildassets/build/darwin/Info.plist
similarity index 58%
rename from v2/pkg/buildassets/build/darwin/Info.tmpl.plist
rename to v2/pkg/buildassets/build/darwin/Info.plist
index 7180c58de..303145e39 100644
--- a/v2/pkg/buildassets/build/darwin/Info.tmpl.plist
+++ b/v2/pkg/buildassets/build/darwin/Info.plist
@@ -1,14 +1,14 @@
CFBundlePackageTypeAPPL
- CFBundleName{{.Name}}
+ CFBundleName{{.Info.ProductName}}
CFBundleExecutable{{.Name}}
CFBundleIdentifiercom.wails.{{.Name}}
- CFBundleVersion1.0.0
- CFBundleGetInfoStringBuilt using Wails (https://wails.app)
- CFBundleShortVersionString1.0.0
+ CFBundleVersion{{.Info.ProductVersion}}
+ CFBundleGetInfoString{{.Info.Comments}}
+ CFBundleShortVersionString{{.Info.ProductVersion}}
CFBundleIconFileiconfile
LSMinimumSystemVersion10.13.0
NSHighResolutionCapabletrue
- NSHumanReadableCopyrightCopyright.........
+ NSHumanReadableCopyright{{.Info.Copyright}}
\ No newline at end of file
diff --git a/v2/pkg/buildassets/build/windows/info.json b/v2/pkg/buildassets/build/windows/info.json
new file mode 100644
index 000000000..9727946b7
--- /dev/null
+++ b/v2/pkg/buildassets/build/windows/info.json
@@ -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}}"
+ }
+ }
+}
\ No newline at end of file
diff --git a/v2/pkg/buildassets/build/windows/info.tmpl.json b/v2/pkg/buildassets/build/windows/info.tmpl.json
deleted file mode 100644
index 5f914fe04..000000000
--- a/v2/pkg/buildassets/build/windows/info.tmpl.json
+++ /dev/null
@@ -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)"
- }
- }
-}
\ No newline at end of file
diff --git a/v2/pkg/buildassets/build/windows/installer/project.nsi b/v2/pkg/buildassets/build/windows/installer/project.nsi
new file mode 100644
index 000000000..15abde335
--- /dev/null
+++ b/v2/pkg/buildassets/build/windows/installer/project.nsi
@@ -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
diff --git a/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh b/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh
new file mode 100644
index 000000000..c06ed2bbb
--- /dev/null
+++ b/v2/pkg/buildassets/build/windows/installer/wails_tools.nsh
@@ -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
\ No newline at end of file
diff --git a/v2/pkg/buildassets/build/windows/wails.exe.manifest b/v2/pkg/buildassets/build/windows/wails.exe.manifest
index 0cb94320a..17e1a2387 100644
--- a/v2/pkg/buildassets/build/windows/wails.exe.manifest
+++ b/v2/pkg/buildassets/build/windows/wails.exe.manifest
@@ -1,6 +1,6 @@
-
+
diff --git a/v2/pkg/buildassets/buildassets.go b/v2/pkg/buildassets/buildassets.go
index 008ca0abc..5cd128793 100644
--- a/v2/pkg/buildassets/buildassets.go
+++ b/v2/pkg/buildassets/buildassets.go
@@ -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
}
diff --git a/v2/pkg/commands/build/build.go b/v2/pkg/commands/build/build.go
index 083fcb621..e256170fa 100644
--- a/v2/pkg/commands/build/build.go
+++ b/v2/pkg/commands/build/build.go
@@ -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
}
diff --git a/v2/pkg/commands/build/desktop.go b/v2/pkg/commands/build/desktop.go
index 141036b2c..f779f6a40 100644
--- a/v2/pkg/commands/build/desktop.go
+++ b/v2/pkg/commands/build/desktop.go
@@ -3,7 +3,6 @@ package build
import (
"fmt"
"os"
- "path/filepath"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/html"
@@ -27,7 +26,7 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
// Check assets directory exists
if !fs.DirExists(options.ProjectData.BuildDir) {
// Path to default assets
- err := buildassets.Install(options.ProjectData.Path, options.ProjectData.Name)
+ err := buildassets.Install(options.ProjectData.Path)
if err != nil {
return err
}
@@ -81,7 +80,7 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
d.addFileToDelete(assetsFile)
// Process Icon
- err = d.processApplicationIcon(assetDir)
+ err = d.processApplicationIcon(assetDir, options)
if err != nil {
return err
}
@@ -105,15 +104,10 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
// processApplicationIcon will copy a default icon if one doesn't exist, then, if
// needed, will compile the icon
-func (d *DesktopBuilder) processApplicationIcon(assetDir string) error {
-
- // Copy default icon if one doesn't exist
- iconFile := filepath.Join(d.projectData.BuildDir, "appicon.png")
- if !fs.FileExists(iconFile) {
- err := buildassets.RegenerateAppIcon(iconFile)
- if err != nil {
- return err
- }
+func (d *DesktopBuilder) processApplicationIcon(assetDir string, options *Options) error {
+ iconFile, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
+ if err != nil {
+ return err
}
// Compile Icon
diff --git a/v2/pkg/commands/build/desktop_darwin.go b/v2/pkg/commands/build/desktop_darwin.go
index 2a427e20c..7c0457833 100644
--- a/v2/pkg/commands/build/desktop_darwin.go
+++ b/v2/pkg/commands/build/desktop_darwin.go
@@ -24,7 +24,7 @@ func (d *DesktopBuilder) convertToHexLiteral(bytes []byte) string {
}
// compileIcon will compile the icon found at /icon.png into the application
-func (d *DesktopBuilder) compileIcon(assetDir string, iconFile string) error {
+func (d *DesktopBuilder) compileIcon(assetDir string, iconFile []byte) error {
return nil
}
diff --git a/v2/pkg/commands/build/desktop_linux.go b/v2/pkg/commands/build/desktop_linux.go
index 989cc429d..bde635b11 100644
--- a/v2/pkg/commands/build/desktop_linux.go
+++ b/v2/pkg/commands/build/desktop_linux.go
@@ -4,7 +4,7 @@
package build
// compileIcon will compile the icon found at /icon.png into the application
-func (d *DesktopBuilder) compileIcon(assetDir string, iconFile string) error {
+func (d *DesktopBuilder) compileIcon(assetDir string, iconFile []byte) error {
//
//// Load icon into a databuffer
//targetFilename := "icon"
diff --git a/v2/pkg/commands/build/desktop_windows.go b/v2/pkg/commands/build/desktop_windows.go
index d95cb66de..30f5e91d8 100644
--- a/v2/pkg/commands/build/desktop_windows.go
+++ b/v2/pkg/commands/build/desktop_windows.go
@@ -106,7 +106,7 @@ func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) err
}
// compileIcon will compile the icon found at /icon.png into the application
-func (d *DesktopBuilder) compileIcon(assetDir string, iconFile string) error {
+func (d *DesktopBuilder) compileIcon(assetDir string, iconFile []byte) error {
return nil
}
diff --git a/v2/pkg/commands/build/nsis_installer.go b/v2/pkg/commands/build/nsis_installer.go
new file mode 100644
index 000000000..3b846984f
--- /dev/null
+++ b/v2/pkg/commands/build/nsis_installer.go
@@ -0,0 +1,119 @@
+package build
+
+import (
+ "fmt"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/wailsapp/wails/v2/internal/fs"
+ "github.com/wailsapp/wails/v2/internal/shell"
+ "github.com/wailsapp/wails/v2/internal/webview2runtime"
+ "github.com/wailsapp/wails/v2/pkg/buildassets"
+)
+
+const (
+ nsisTypeSingle = "single"
+ nsisTypeMultiple = "multiple"
+
+ nsisFolder = "windows/installer"
+ nsisProjectFile = "project.nsi"
+ nsisToolsFile = "wails_tools.nsh"
+ nsisWebView2SetupFile = "tmp/MicrosoftEdgeWebview2Setup.exe"
+)
+
+func GenerateNSISInstaller(options *Options, amd64Binary string, arm64Binary string) error {
+ outputLogger := options.Logger
+ outputLogger.Println("Creating NSIS installer\n------------------------------")
+
+ // Ensure the file exists, if not the template will be written.
+ projectFile := path.Join(nsisFolder, nsisProjectFile)
+ if _, err := buildassets.ReadFile(options.ProjectData, projectFile); err != nil {
+ return fmt.Errorf("Unable to generate NSIS installer project template: %w", err)
+ }
+
+ // Write the resolved nsis tools
+ toolsFile := path.Join(nsisFolder, nsisToolsFile)
+ if _, err := buildassets.ReadOriginalFileWithProjectDataAndSave(options.ProjectData, toolsFile); err != nil {
+ return fmt.Errorf("Unable to generate NSIS tools file: %w", err)
+ }
+
+ // Write the WebView2 SetupFile
+ webviewSetup := buildassets.GetLocalPath(options.ProjectData, path.Join(nsisFolder, nsisWebView2SetupFile))
+ if dir := filepath.Dir(webviewSetup); !fs.DirExists(dir) {
+ if err := fs.MkDirs(dir, 0755); err != nil {
+ return err
+ }
+ }
+
+ if err := webview2runtime.WriteInstallerToFile(webviewSetup); err != nil {
+ return fmt.Errorf("Unable to write Webview2 Bootstrapper Setup: %w", err)
+ }
+
+ if !shell.CommandExists("makensis") {
+ outputLogger.Println("Warning: Cannot create installer: makensis not found")
+ return nil
+ }
+
+ nsisType := options.ProjectData.NSISType
+ if nsisType == nsisTypeSingle && (amd64Binary == "" || arm64Binary == "") {
+ nsisType = ""
+ }
+
+ switch nsisType {
+ case "":
+ fallthrough
+ case nsisTypeMultiple:
+ if amd64Binary != "" {
+ if err := makeNSIS(options, "amd64", amd64Binary, ""); err != nil {
+ return err
+ }
+ }
+
+ if arm64Binary != "" {
+ if err := makeNSIS(options, "arm64", "", arm64Binary); err != nil {
+ return err
+ }
+ }
+
+ case nsisTypeSingle:
+ if err := makeNSIS(options, "single", amd64Binary, arm64Binary); err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("Unsupported nsisType: %s", nsisType)
+ }
+
+ return nil
+}
+
+func makeNSIS(options *Options, installerKind string, amd64Binary string, arm64Binary string) error {
+ verbose := options.Verbosity == VERBOSE
+ outputLogger := options.Logger
+
+ outputLogger.Print(" - Building '%s' installer: ", installerKind)
+ var args = []string{}
+ if amd64Binary != "" {
+ args = append(args, "-DARG_WAILS_AMD64_BINARY="+amd64Binary)
+ }
+ if arm64Binary != "" {
+ args = append(args, "-DARG_WAILS_ARM64_BINARY="+arm64Binary)
+ }
+ args = append(args, nsisProjectFile)
+
+ if verbose {
+ outputLogger.Println("makensis %s", strings.Join(args, " "))
+ }
+
+ installerDir := buildassets.GetLocalPath(options.ProjectData, nsisFolder)
+ stdOut, stdErr, err := shell.RunCommand(installerDir, "makensis", args...)
+ if err != nil || verbose {
+ outputLogger.Println(stdOut)
+ outputLogger.Println(stdErr)
+ }
+ if err != nil {
+ return fmt.Errorf("Error during creation of the installer: %w", err)
+ }
+ outputLogger.Println("Done.")
+ return nil
+}
diff --git a/v2/pkg/commands/build/packager.go b/v2/pkg/commands/build/packager.go
index 9cf3ea47a..3f7146dc9 100644
--- a/v2/pkg/commands/build/packager.go
+++ b/v2/pkg/commands/build/packager.go
@@ -1,10 +1,10 @@
package build
import (
+ "bytes"
"fmt"
"image"
"os"
- "path"
"path/filepath"
"runtime"
@@ -63,18 +63,6 @@ func cleanBuildDirectory(options *Options) error {
return nil
}
-// Gets (and creates) the build base directory
-func getBuildBaseDirectory(options *Options) (string, error) {
- buildDirectory := filepath.Join(options.ProjectData.Path, "build")
- if !fs.DirExists(buildDirectory) {
- err := os.MkdirAll(buildDirectory, 0700)
- if err != nil {
- return "", err
- }
- }
- return buildDirectory, nil
-}
-
// Gets the platform dependent package assets directory
func getPackageAssetsDirectory() string {
return fs.RelativePath("internal/packager", runtime.GOOS)
@@ -115,11 +103,7 @@ func packageApplicationForDarwin(options *Options) error {
}
// Generate Icons
- buildDir, err := getBuildBaseDirectory(options)
- if err != nil {
- return err
- }
- err = processApplicationIcon(resourceDir, buildDir)
+ err = processApplicationIcon(options, resourceDir)
if err != nil {
return err
}
@@ -130,53 +114,28 @@ func packageApplicationForDarwin(options *Options) error {
}
func processPList(options *Options, contentsDirectory string) error {
-
- // Check if plist already exists in project dir
- plistFileDir := filepath.Join(options.ProjectData.Path, "build", "darwin")
- plistFile := filepath.Join(plistFileDir, "Info.plist")
- // If the file doesn't exist, generate it
- if !fs.FileExists(plistFile) {
- err := buildassets.RegeneratePlist(plistFileDir, options.ProjectData.Name)
- if err != nil {
- return err
- }
+ // Read the resolved BuildAssets file and copy it to the destination
+ content, err := buildassets.ReadFileWithProjectData(options.ProjectData, "darwin/Info.plist")
+ if err != nil {
+ return err
}
- // Copy it to the contents directory
targetFile := filepath.Join(contentsDirectory, "Info.plist")
- return fs.CopyFile(plistFile, targetFile)
+ return os.WriteFile(targetFile, content, 0644)
}
-func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
-
- appIcon := filepath.Join(iconsDir, "appicon.png")
-
- // Install default icon if one doesn't exist
- if !fs.FileExists(appIcon) {
- // No - Install default icon
- err = buildassets.RegenerateAppIcon(appIcon)
- if err != nil {
- return
- }
- }
-
- tgtBundle := path.Join(resourceDir, "iconfile.icns")
- imageFile, err := os.Open(appIcon)
+func processApplicationIcon(options *Options, resourceDir string) (err error) {
+ appIcon, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
if err != nil {
return err
}
- defer func() {
- err = imageFile.Close()
- if err == nil {
- return
- }
- }()
- srcImg, _, err := image.Decode(imageFile)
+ srcImg, _, err := image.Decode(bytes.NewBuffer(appIcon))
if err != nil {
return err
-
}
+
+ tgtBundle := filepath.Join(resourceDir, "iconfile.icns")
dest, err := os.Create(tgtBundle)
if err != nil {
return err
@@ -199,12 +158,6 @@ func packageApplicationForWindows(options *Options) error {
return err
}
- // Ensure Manifest is present
- err = generateManifest(options)
- if err != nil {
- return err
- }
-
// Create syso file
err = compileResources(options)
if err != nil {
@@ -238,33 +191,31 @@ func packageApplicationForLinux(options *Options) error {
}
func generateManifest(options *Options) error {
- filename := options.ProjectData.Name + ".exe.manifest"
- manifestFile := filepath.Join(options.ProjectData.Path, "build", "windows", filename)
- if !fs.FileExists(manifestFile) {
- return buildassets.RegenerateManifest(manifestFile)
- }
return nil
}
func generateIcoFile(options *Options) error {
// Check ico file exists already
- icoFile := filepath.Join(options.ProjectData.Path, "build", "windows", "icon.ico")
+ icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/icon.ico")
if !fs.FileExists(icoFile) {
- // Check icon exists
- appicon := filepath.Join(options.ProjectData.Path, "build", "appicon.png")
- if !fs.FileExists(appicon) {
- return fmt.Errorf("application icon missing: %s", appicon)
- }
- // Load icon
- input, err := os.Open(appicon)
+ content, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
if err != nil {
return err
}
+
+ if dir := filepath.Dir(icoFile); !fs.DirExists(dir) {
+ if err := fs.MkDirs(dir, 0755); err != nil {
+ return err
+ }
+ }
+
output, err := os.OpenFile(icoFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
- err = winicon.GenerateIcon(input, output, []int{256, 128, 64, 48, 32, 16})
+ defer output.Close()
+
+ err = winicon.GenerateIcon(bytes.NewBuffer(content), output, []int{256, 128, 64, 48, 32, 16})
if err != nil {
return err
}
@@ -302,15 +253,23 @@ func compileResources(options *Options) error {
return err
}
- ManifestFilename := options.ProjectData.Name + ".exe.manifest"
- manifestData, err := os.ReadFile(ManifestFilename)
+ manifestData, err := buildassets.ReadFileWithProjectData(options.ProjectData, "windows/wails.exe.manifest")
+ if err != nil {
+ return err
+ }
+
xmlData, err := winres.AppManifestFromXML(manifestData)
if err != nil {
return err
}
rs.SetManifest(xmlData)
- if versionInfo, _ := os.ReadFile("info.json"); len(versionInfo) != 0 {
+ versionInfo, err := buildassets.ReadFileWithProjectData(options.ProjectData, "windows/info.json")
+ if err != nil {
+ return err
+ }
+
+ if len(versionInfo) != 0 {
var v version.Info
if err := v.UnmarshalJSON(versionInfo); err != nil {
return err
diff --git a/website/docs/reference/project-config.mdx b/website/docs/reference/project-config.mdx
index 524d13e69..88dab6047 100644
--- a/website/docs/reference/project-config.mdx
+++ b/website/docs/reference/project-config.mdx
@@ -20,8 +20,21 @@ The project config resides in the `wails.json` file in the project directory. Th
"outputfilename": "[The name of the binary]",
"debounceMS": 100, // The default time the dev server waits to reload when it detects a vhange in assets
"devserverurl": "[URL to the dev server serving local assets. Default: http://localhost:34115]",
- "appargs": "[Arguments passed to the application in shell style when in dev mode]"
-
+ "appargs": "[Arguments passed to the application in shell style when in dev mode]",
+ "runNonNativeBuildHooks": false, // Defines if build hooks should be run though they are defined for an OS other than the host OS.
+ "postBuildHooks": {
+ "GOOS/GOARCH": "[The command that will be executed after a build of the specified GOOS/GOARCH: ${platform} is replaced with the "GOOS/GOARCH" and ${bin} with the path to the compiled binary. The "GOOS/GOARCH" hook is executed before the "GOOS/*" and "*/*" hook.]",
+ "GOOS/*": "[The command that will be executed after a build of the specified GOOS: ${platform} is replaced with the "GOOS/GOARCH" and ${bin} with the path to the compiled binary. The "GOOS/*" hook is executed before the "*/*" hook.]",
+ "*/*": "[The command that will be executed after every build: ${platform} is replaced with the "GOOS/GOARCH" and ${bin} with the path to the compiled binary.]"
+ },
+ "info": { // Data used to populate manifests and version info.
+ "companyName": "[The company name. Default: [The project name]]",
+ "productName": "[The product name. Default: [The project name]]",
+ "productVersion": "[The version of the product. Default: '1.0.0']",
+ "copyright": "[The copyright of the product. Default: 'Copyright.........']",
+ "comments": "[A short comment of the app. Default: 'Built using Wails (https://wails.app)']"
+ },
+ "nsisType": "['multiple': One installer per achitecture. 'single': Single universal installer for all architectures being built. Default: 'multiple']"
}
```