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']" } ```